{-# OPTIONS --safe --cubical #-}

module Spartan6.Import.Yosys where

open import Spartan6.Prelude

import FF.Json as JSON
import FF.Json.Native as Native
import Spartan6.Architecture.Primitive as Architecture
import Spartan6.Import.Json as Import
import Spartan6.Netlist.Raw as Raw
import Spartan6.Validation.Diagnostic as Diagnostic

open import Agda.Builtin.Char using (Char; primCharEquality)
open import Agda.Builtin.String
  using (primShowNat; primStringEquality; primStringToList)
open import Cubical.Data.Nat.Base using (_<ᵇ_; _≡ᵇ_)
open import Cubical.Data.Nat.Mod using (quotient_/_; remainder_/_)

parameterProblem : Diagnostic.DiagnosticCode
                 → Import.JsonPath → String → String → String
                 → Diagnostic.Diagnostics
parameterProblem code path expected observed detail =
  Diagnostic.diagnostic code Diagnostic.reject
    (Import.renderPath path) expected observed detail
  ∷ᴸ []ᴸ

directionProblem : Import.JsonPath → String → String
                 → Diagnostic.Diagnostics
directionProblem path expected observed =
  Diagnostic.diagnostic Diagnostic.directionMismatch Diagnostic.reject
    (Import.renderPath path)
    expected observed
    "Yosys port directions must agree exactly with the canonical architecture schema."
  ∷ᴸ []ᴸ

widthProblem : Import.JsonPath → ℕ → ℕ → Diagnostic.Diagnostics
widthProblem path expected observed =
  Diagnostic.diagnostic Diagnostic.widthMismatch Diagnostic.reject
    (Import.renderPath path)
    (primShowNat expected <> " connections")
    (primShowNat observed <> " connections")
    "The connection count must equal the width in Architecture.PortSpecification."
  ∷ᴸ []ᴸ
  where
  open Import using (_<>_)

unknownPortProblem : Import.JsonPath → String → Diagnostic.Diagnostics
unknownPortProblem path name =
  Diagnostic.diagnostic Diagnostic.unknownPort Diagnostic.reject
    (Import.renderPath (Import.fieldAt path name))
    "a port named by the canonical architecture schema"
    name
    "Known-cell schemas are taken only from Architecture input/output port specifications."
  ∷ᴸ []ᴸ

missingDirectionProblem : Import.JsonPath → String → Diagnostic.Diagnostics
missingDirectionProblem path name =
  Diagnostic.diagnostic Diagnostic.missingPort Diagnostic.reject
    (Import.renderPath (Import.fieldAt path name))
    "one direction for every connection"
    "no direction entry"
    "Unknown cells preserve directions exactly and do not invent an inout fallback."
  ∷ᴸ []ᴸ

extraDirectionProblem : Import.JsonPath → String → Diagnostic.Diagnostics
extraDirectionProblem path name =
  Diagnostic.diagnostic Diagnostic.unknownPort Diagnostic.reject
    (Import.renderPath (Import.fieldAt path name))
    "one connection for every direction"
    "a direction without a connection"
    "Unknown-cell port direction and connection key sets must be identical."
  ∷ᴸ []ᴸ

decodeDirection : Import.JsonPath → Import.JsonValue
                → Diagnostic.CheckResult Raw.PortDirection
decodeDirection path value with Import.expectString path value
... | Diagnostic.rejected problems = Diagnostic.rejected problems
... | Diagnostic.accepted text =
  if primStringEquality text "input"
  then Diagnostic.accepted Raw.inputPort
  else if primStringEquality text "output"
  then Diagnostic.accepted Raw.outputPort
  else if primStringEquality text "inout"
  then Diagnostic.accepted Raw.bidirectionalPort
  else Diagnostic.rejected
    (directionProblem path "input, output, or inout" text)

decodeKnownDirection : Raw.PortDirection → String
                     → Import.JsonPath → Import.JsonValue
                     → Diagnostic.CheckResult Raw.PortDirection
decodeKnownDirection direction expected path value with
  Import.expectString path value
... | Diagnostic.rejected problems = Diagnostic.rejected problems
... | Diagnostic.accepted observed =
  if primStringEquality observed expected
  then Diagnostic.accepted direction
  else Diagnostic.rejected (directionProblem path expected observed)

decodeConnection : Import.JsonPath → Import.JsonValue
                 → Diagnostic.CheckResult Raw.Connection
decodeConnection path (JSON.atom Native.number net-id) =
  Diagnostic.accepted (Raw.net net-id)
decodeConnection path (JSON.atom Native.string text) =
  if primStringEquality text "0"
  then Diagnostic.accepted (Raw.constant low)
  else if primStringEquality text "1"
  then Diagnostic.accepted (Raw.constant high)
  else Diagnostic.rejected
    (parameterProblem Diagnostic.malformedImport path
      "a natural net id or defined constant string 0/1"
      text
      "Constants x and z, uppercase variants, and other strings have no two-valued Raw.Connection representation.")
decodeConnection path value =
  Diagnostic.rejected
    (Import.malformedAt path
      "a natural net id or defined constant string 0/1"
      (Import.jsonShape value)
      "Yosys signal bits must be nets or two-valued constants.")

decodeConnections : Import.JsonPath → Import.JsonValue
                  → Diagnostic.CheckResult (List Raw.Connection)
decodeConnections path value with Import.expectArray path value
... | Diagnostic.rejected problems = Diagnostic.rejected problems
... | Diagnostic.accepted values =
  Import.decodeArray decodeConnection path values

decodeFixedConnections : ℕ → Import.JsonPath → Import.JsonValue
                       → Diagnostic.CheckResult (List Raw.Connection)
decodeFixedConnections expected path value with Import.expectArray path value
... | Diagnostic.rejected problems = Diagnostic.rejected problems
... | Diagnostic.accepted values with lengthList values ≡ᵇ expected
...   | false = Diagnostic.rejected
        (widthProblem path expected (lengthList values))
...   | true = Import.decodeArray decodeConnection path values

hexDigit : ℕ → String
hexDigit zero = "0"
hexDigit (suc zero) = "1"
hexDigit (suc (suc zero)) = "2"
hexDigit (suc (suc (suc zero))) = "3"
hexDigit (suc (suc (suc (suc zero)))) = "4"
hexDigit (suc (suc (suc (suc (suc zero))))) = "5"
hexDigit (suc (suc (suc (suc (suc (suc zero)))))) = "6"
hexDigit (suc (suc (suc (suc (suc (suc (suc zero))))))) = "7"
hexDigit (suc (suc (suc (suc (suc (suc (suc (suc zero)))))))) = "8"
hexDigit (suc (suc (suc (suc (suc (suc (suc (suc (suc zero))))))))) = "9"
hexDigit (suc (suc (suc (suc (suc (suc (suc (suc (suc (suc zero)))))))))) = "A"
hexDigit (suc (suc (suc (suc (suc (suc (suc (suc (suc (suc (suc zero))))))))))) = "B"
hexDigit (suc (suc (suc (suc (suc (suc (suc (suc (suc (suc (suc (suc zero)))))))))))) = "C"
hexDigit (suc (suc (suc (suc (suc (suc (suc (suc (suc (suc (suc (suc (suc zero))))))))))))) = "D"
hexDigit (suc (suc (suc (suc (suc (suc (suc (suc (suc (suc (suc (suc (suc (suc zero)))))))))))))) = "E"
hexDigit number = "F"

fixedHexAcc : ℕ → ℕ → String → String
fixedHexAcc zero number accumulator = accumulator
fixedHexAcc (suc digits) number accumulator =
  fixedHexAcc digits (quotient number / 16)
    (hexDigit (remainder number / 16) <> accumulator)
  where
  open Import using (_<>_)

fixedHex : ℕ → ℕ → String
fixedHex digits number = fixedHexAcc digits number ""

binaryValueFrom : ℕ → List Char → Maybe ℕ
binaryValueFrom accumulator []ᴸ = just accumulator
binaryValueFrom accumulator (digit ∷ᴸ digits) =
  if primCharEquality digit '0'
  then binaryValueFrom (accumulator · 2) digits
  else if primCharEquality digit '1'
  then binaryValueFrom (suc (accumulator · 2)) digits
  else nothing

binaryValue : String → Maybe ℕ
binaryValue text = binaryValueFrom zero (primStringToList text)

decodeLUTInit : ℕ → ℕ → Import.JsonPath → Import.JsonValue
              → Diagnostic.CheckResult String
decodeLUTInit bit-count digit-count path (JSON.atom Native.string text) =
  if lengthList (primStringToList text) ≡ᵇ bit-count
  then decode-binary (binaryValue text)
  else malformed "The binary text has the wrong width."
  where
  malformed : String → Diagnostic.CheckResult String
  malformed detail =
    Diagnostic.rejected
      (parameterProblem Diagnostic.malformedInitialisation path
        (primShowNat bit-count <> " defined binary digits")
        text
        detail)
    where open Import using (_<>_)

  decode-binary : Maybe ℕ → Diagnostic.CheckResult String
  decode-binary nothing =
    malformed "The LUT INIT string contains a non-binary digit; only 0 and 1 are accepted."
  decode-binary (just number) = Diagnostic.accepted (fixedHex digit-count number)
decodeLUTInit bit-count digit-count path (JSON.atom Native.number number) =
  if number <ᵇ (2 ^ bit-count)
  then Diagnostic.accepted (fixedHex digit-count number)
  else Diagnostic.rejected
    (parameterProblem Diagnostic.malformedInitialisation path
      ("a natural number smaller than 2^" <> primShowNat bit-count)
      (primShowNat number)
      "Numeric LUT INIT is interpreted numerically and must fit the complete truth-table width.")
  where open Import using (_<>_)
decodeLUTInit bit-count digit-count path value =
  Diagnostic.rejected
    (parameterProblem Diagnostic.illegalParameter path
      "a fixed-width binary string or natural number"
      (Import.jsonShape value)
      "Other JSON forms cannot encode a LUT truth table.")

firstDisallowed : List String → Import.ObjectFields → Maybe String
firstDisallowed allowed []ᴸ = nothing
firstDisallowed allowed ((name , value) ∷ᴸ fields) =
  if member name allowed
  then firstDisallowed allowed fields
  else just name
  where
  member : String → List String → Bool
  member candidate []ᴸ = false
  member candidate (name ∷ᴸ names) =
    primStringEquality candidate name or member candidate names

requireOnly : Import.JsonPath → List String → Import.ObjectFields
            → Diagnostic.CheckResult Unit
requireOnly path allowed fields with firstDisallowed allowed fields
... | nothing = Diagnostic.accepted tt
... | just name =
  Diagnostic.rejected
    (parameterProblem Diagnostic.illegalParameter
      (Import.fieldAt path name)
      "only parameters supported by this selected primitive mode"
      name
      "Unknown parameter names are rejected rather than ignored.")

decodeLUTParameters : ℕ → ℕ → Import.JsonPath → Import.JsonValue
                    → Diagnostic.CheckResult
                        (List Raw.RawParameter × Maybe Bit)
decodeLUTParameters bit-count digit-count path value with
  Import.expectUniqueObject path value
... | Diagnostic.rejected problems = Diagnostic.rejected problems
... | Diagnostic.accepted fields with requireOnly path ("INIT" ∷ᴸ []ᴸ) fields
...   | Diagnostic.rejected problems = Diagnostic.rejected problems
...   | Diagnostic.accepted unit with
        Import.requiredField path "INIT" fields
...     | Diagnostic.rejected problems = Diagnostic.rejected problems
...     | Diagnostic.accepted init-value with
          decodeLUTInit bit-count digit-count
            (Import.fieldAt path "INIT") init-value
...       | Diagnostic.rejected problems = Diagnostic.rejected problems
...       | Diagnostic.accepted canonical =
          Diagnostic.accepted
            (Raw.rawParameter "INIT" canonical ∷ᴸ []ᴸ , nothing)

decodeRegisterInit : Import.JsonPath → Import.JsonValue
                   → Diagnostic.CheckResult Bit
decodeRegisterInit path (JSON.atom Native.string text) =
  if primStringEquality text "0"
  then Diagnostic.accepted low
  else if primStringEquality text "1"
  then Diagnostic.accepted high
  else Diagnostic.rejected
    (parameterProblem Diagnostic.malformedInitialisation path
      "defined scalar bit 0 or 1" text
      "Register INIT does not admit x, z, wider strings, or alternate spellings.")
decodeRegisterInit path (JSON.atom Native.number zero) =
  Diagnostic.accepted low
decodeRegisterInit path (JSON.atom Native.number (suc zero)) =
  Diagnostic.accepted high
decodeRegisterInit path (JSON.atom Native.number number) =
  Diagnostic.rejected
    (parameterProblem Diagnostic.malformedInitialisation path
      "natural number 0 or 1" (primShowNat number)
      "Register INIT is exactly one defined bit.")
decodeRegisterInit path value =
  Diagnostic.rejected
    (parameterProblem Diagnostic.illegalParameter path
      "string or natural scalar bit" (Import.jsonShape value)
      "Other JSON forms cannot encode register initial state.")

decodeRegisterParameters : Import.JsonPath → Import.JsonValue
                         → Diagnostic.CheckResult
                             (List Raw.RawParameter × Maybe Bit)
decodeRegisterParameters path value with Import.expectUniqueObject path value
... | Diagnostic.rejected problems = Diagnostic.rejected problems
... | Diagnostic.accepted []ᴸ = Diagnostic.accepted ([]ᴸ , nothing)
... | Diagnostic.accepted ((name , init-value) ∷ᴸ []ᴸ) =
  if primStringEquality name "INIT"
  then decode-init (decodeRegisterInit (Import.fieldAt path name) init-value)
  else Diagnostic.rejected
    (parameterProblem Diagnostic.illegalParameter
      (Import.fieldAt path name) "INIT or no parameters" name
      "FDRE/FDSE expose only scalar INIT in the selected mode.")
  where
  decode-init : Diagnostic.CheckResult Bit
              → Diagnostic.CheckResult (List Raw.RawParameter × Maybe Bit)
  decode-init (Diagnostic.accepted bit) =
    Diagnostic.accepted ([]ᴸ , just bit)
  decode-init (Diagnostic.rejected problems) = Diagnostic.rejected problems
decodeRegisterParameters path value
  | Diagnostic.accepted (first ∷ᴸ second ∷ᴸ rest) =
  Diagnostic.rejected
    (parameterProblem Diagnostic.illegalParameter path
      "zero parameters or exactly one INIT"
      "multiple parameter fields"
      "Register parameters are not assigned precedence by object order.")

decodeParameterless : Import.JsonPath → Import.JsonValue
                    → Diagnostic.CheckResult
                        (List Raw.RawParameter × Maybe Bit)
decodeParameterless path value with Import.expectUniqueObject path value
... | Diagnostic.rejected problems = Diagnostic.rejected problems
... | Diagnostic.accepted []ᴸ = Diagnostic.accepted ([]ᴸ , nothing)
... | Diagnostic.accepted fields =
  Diagnostic.rejected
    (parameterProblem Diagnostic.illegalParameter path
      "an empty parameter object"
      "one or more parameter fields"
      "The selected digital primitive mode is parameterless.")

unsupportedKnownParameters : Import.JsonPath → Import.JsonValue
                           → Diagnostic.CheckResult
                               (List Raw.RawParameter × Maybe Bit)
unsupportedKnownParameters path value =
  Diagnostic.rejected
    (parameterProblem Diagnostic.unsupportedMode path
      "a parameter mode supported by the Yosys import slice"
      "an unsupported architecture mode"
      "Unsupported architecture kinds are normally retained as unknown primitives.")

decodeKnownParameters : Architecture.PrimitiveKind
                      → Import.JsonPath → Import.JsonValue
                      → Diagnostic.CheckResult
                          (List Raw.RawParameter × Maybe Bit)
decodeKnownParameters Architecture.LUT1 = decodeLUTParameters 2 1
decodeKnownParameters Architecture.LUT2 = decodeLUTParameters 4 1
decodeKnownParameters Architecture.LUT3 = decodeLUTParameters 8 2
decodeKnownParameters Architecture.LUT4 = decodeLUTParameters 16 4
decodeKnownParameters Architecture.LUT5 = decodeLUTParameters 32 8
decodeKnownParameters Architecture.LUT6 = decodeLUTParameters 64 16
decodeKnownParameters Architecture.MUXF7 = decodeParameterless
decodeKnownParameters Architecture.MUXF8 = decodeParameterless
decodeKnownParameters Architecture.CARRY4 = decodeParameterless
decodeKnownParameters Architecture.FDRE = decodeRegisterParameters
decodeKnownParameters Architecture.FDSE = decodeRegisterParameters
decodeKnownParameters Architecture.SRL16E = unsupportedKnownParameters
decodeKnownParameters Architecture.RAM64X1S = unsupportedKnownParameters
decodeKnownParameters Architecture.IBUF = decodeParameterless
decodeKnownParameters Architecture.OBUF = decodeParameterless
decodeKnownParameters Architecture.OBUFDS = decodeParameterless
decodeKnownParameters Architecture.OBUFT = unsupportedKnownParameters
decodeKnownParameters Architecture.BUFG = decodeParameterless
decodeKnownParameters Architecture.BUFGCE = decodeParameterless

supportedKind : String → Maybe Architecture.PrimitiveKind
supportedKind text =
  if primStringEquality text "LUT1" then just Architecture.LUT1 else
  if primStringEquality text "LUT2" then just Architecture.LUT2 else
  if primStringEquality text "LUT3" then just Architecture.LUT3 else
  if primStringEquality text "LUT4" then just Architecture.LUT4 else
  if primStringEquality text "LUT5" then just Architecture.LUT5 else
  if primStringEquality text "LUT6" then just Architecture.LUT6 else
  if primStringEquality text "MUXF7" then just Architecture.MUXF7 else
  if primStringEquality text "MUXF8" then just Architecture.MUXF8 else
  if primStringEquality text "CARRY4" then just Architecture.CARRY4 else
  if primStringEquality text "FDRE" then just Architecture.FDRE else
  if primStringEquality text "FDSE" then just Architecture.FDSE else
  if primStringEquality text "IBUF" then just Architecture.IBUF else
  if primStringEquality text "OBUF" then just Architecture.OBUF else
  if primStringEquality text "OBUFDS" then just Architecture.OBUFDS else
  if primStringEquality text "BUFG" then just Architecture.BUFG else
  if primStringEquality text "BUFGCE" then just Architecture.BUFGCE else
  nothing

specificationNames : List Architecture.PortSpecification → List String
specificationNames = mapList Architecture.canonicalPortName

knownPortNames : Architecture.PrimitiveKind → List String
knownPortNames kind =
  specificationNames (Architecture.inputPortSpecifications kind)
  ++ᴸ specificationNames (Architecture.outputPortSpecifications kind)

schemaKeyCheck : Architecture.PrimitiveKind → Import.JsonPath
               → Import.ObjectFields → Diagnostic.CheckResult Unit
schemaKeyCheck kind path fields with
  firstDisallowed (knownPortNames kind) fields
... | nothing = Diagnostic.accepted tt
... | just name = Diagnostic.rejected (unknownPortProblem path name)

decodeSpecificationPorts : Raw.PortDirection → String
                         → Import.ObjectFields → Import.ObjectFields
                         → Import.JsonPath → Import.JsonPath
                         → List Architecture.PortSpecification
                         → Diagnostic.CheckResult (List Raw.RawPort)
decodeSpecificationPorts direction expected connections directions
  connection-path direction-path []ᴸ = Diagnostic.accepted []ᴸ
decodeSpecificationPorts direction expected connections directions
  connection-path direction-path (specification ∷ᴸ specifications) =
  Import.prependResult decode-port
    (decodeSpecificationPorts direction expected connections directions
      connection-path direction-path specifications)
  where
  name : String
  name = Architecture.canonicalPortName specification

  decode-port : Diagnostic.CheckResult Raw.RawPort
  decode-port with Import.requiredField connection-path name connections
  ... | Diagnostic.rejected problems = Diagnostic.rejected problems
  ... | Diagnostic.accepted connection-value with
        Import.requiredField direction-path name directions
  ...   | Diagnostic.rejected problems = Diagnostic.rejected problems
  ...   | Diagnostic.accepted direction-value with
          Diagnostic.appendResult
            (decodeKnownDirection direction expected
              (Import.fieldAt direction-path name) direction-value)
            (decodeFixedConnections
              (Architecture.canonicalPortWidth specification)
              (Import.fieldAt connection-path name) connection-value)
  ...     | Diagnostic.rejected problems = Diagnostic.rejected problems
  ...     | Diagnostic.accepted (decoded-direction , decoded-connections) =
          Diagnostic.accepted
            (Raw.rawPort name decoded-direction
              (Architecture.canonicalPortWidth specification)
              decoded-connections)

assembleKnownPorts : Diagnostic.CheckResult Unit
                   → Diagnostic.CheckResult Unit
                   → Diagnostic.CheckResult (List Raw.RawPort)
                   → Diagnostic.CheckResult (List Raw.RawPort)
                   → Diagnostic.CheckResult (List Raw.RawPort)
assembleKnownPorts connection-keys direction-keys inputs outputs =
  Diagnostic.mapResult
    (λ combined → fst (snd combined) ++ᴸ snd (snd combined))
    (Diagnostic.appendResult
      (Diagnostic.appendResult connection-keys direction-keys)
      (Diagnostic.appendResult inputs outputs))

decodeKnownPorts : Architecture.PrimitiveKind
                 → Import.JsonPath → Import.JsonValue
                 → Import.JsonPath → Import.JsonValue
                 → Diagnostic.CheckResult (List Raw.RawPort)
decodeKnownPorts kind connection-path connection-value
  direction-path direction-value with
  Import.expectUniqueObject connection-path connection-value
... | Diagnostic.rejected problems = Diagnostic.rejected problems
... | Diagnostic.accepted connections with
      Import.expectUniqueObject direction-path direction-value
...   | Diagnostic.rejected problems = Diagnostic.rejected problems
...   | Diagnostic.accepted directions =
      assembleKnownPorts
        (schemaKeyCheck kind connection-path connections)
        (schemaKeyCheck kind direction-path directions)
        (decodeSpecificationPorts Raw.inputPort "input"
          connections directions connection-path direction-path
          (Architecture.inputPortSpecifications kind))
        (decodeSpecificationPorts Raw.outputPort "output"
          connections directions connection-path direction-path
          (Architecture.outputPortSpecifications kind))

coverageCheck : Import.JsonPath → Import.JsonPath
              → Import.ObjectFields → Import.ObjectFields
              → Diagnostic.CheckResult Unit
coverageCheck connection-path direction-path connections directions =
  combine missing extra
  where
  missingProblems : Import.ObjectFields → Diagnostic.Diagnostics
  missingProblems []ᴸ = []ᴸ
  missingProblems ((name , value) ∷ᴸ fields) =
    (if Import.containsField name directions
     then []ᴸ else missingDirectionProblem direction-path name)
    ++ᴸ missingProblems fields

  extraProblems : Import.ObjectFields → Diagnostic.Diagnostics
  extraProblems []ᴸ = []ᴸ
  extraProblems ((name , value) ∷ᴸ fields) =
    (if Import.containsField name connections
     then []ᴸ else extraDirectionProblem direction-path name)
    ++ᴸ extraProblems fields

  missing : Diagnostic.Diagnostics
  missing = missingProblems connections

  extra : Diagnostic.Diagnostics
  extra = extraProblems directions

  combine : Diagnostic.Diagnostics → Diagnostic.Diagnostics
          → Diagnostic.CheckResult Unit
  combine []ᴸ []ᴸ = Diagnostic.accepted tt
  combine first second = Diagnostic.rejected (first ++ᴸ second)

decodeUnknownPort : Import.ObjectFields → Import.JsonPath
                  → Import.JsonPath → Import.JsonPath
                  → String → Import.JsonValue
                  → Diagnostic.CheckResult Raw.RawPort
decodeUnknownPort directions connection-path direction-path entry-path name value with
  Import.requiredField direction-path name directions
... | Diagnostic.rejected problems = Diagnostic.rejected problems
... | Diagnostic.accepted direction-value with
      Diagnostic.appendResult
        (decodeDirection (Import.fieldAt direction-path name) direction-value)
        (decodeConnections entry-path value)
...   | Diagnostic.rejected problems = Diagnostic.rejected problems
...   | Diagnostic.accepted (direction , connections) =
      Diagnostic.accepted
        (Raw.rawPort name direction (lengthList connections) connections)

decodeUnknownPorts : Import.JsonPath → Import.JsonValue
                   → Import.JsonPath → Import.JsonValue
                   → Diagnostic.CheckResult (List Raw.RawPort)
decodeUnknownPorts connection-path connection-value
  direction-path direction-value with
  Import.expectUniqueObject connection-path connection-value
... | Diagnostic.rejected problems = Diagnostic.rejected problems
... | Diagnostic.accepted connections with
      Import.expectUniqueObject direction-path direction-value
...   | Diagnostic.rejected problems = Diagnostic.rejected problems
...   | Diagnostic.accepted directions with
        coverageCheck connection-path direction-path connections directions
...     | Diagnostic.rejected problems = Diagnostic.rejected problems
...     | Diagnostic.accepted unit =
        Import.decodeObjectFields
          (decodeUnknownPort directions connection-path direction-path)
          connection-path connections

decodeUnknownParameter : Import.JsonPath → String → Import.JsonValue
                       → Diagnostic.CheckResult Raw.RawParameter
decodeUnknownParameter path name (JSON.atom Native.string value) =
  Diagnostic.accepted (Raw.rawParameter name value)
decodeUnknownParameter path name (JSON.atom Native.number value) =
  Diagnostic.accepted (Raw.rawParameter name (primShowNat value))
decodeUnknownParameter path name value =
  Diagnostic.rejected
    (parameterProblem Diagnostic.illegalParameter path
      "a string or natural parameter value"
      (Import.jsonShape value)
      "Raw.RawParameter preserves supported Yosys scalar parameter text only.")

decodeUnknownParameters : Import.JsonPath → Import.JsonValue
                        → Diagnostic.CheckResult (List Raw.RawParameter)
decodeUnknownParameters path value with Import.expectUniqueObject path value
... | Diagnostic.rejected problems = Diagnostic.rejected problems
... | Diagnostic.accepted fields =
  Import.decodeObjectFields decodeUnknownParameter path fields

decodeTopPort : Import.JsonPath → String → Import.JsonValue
              → Diagnostic.CheckResult Raw.RawTopPort
decodeTopPort path name value with Import.expectUniqueObject path value
... | Diagnostic.rejected problems = Diagnostic.rejected problems
... | Diagnostic.accepted fields with
      Import.requiredField path "direction" fields
...   | Diagnostic.rejected problems = Diagnostic.rejected problems
...   | Diagnostic.accepted direction-value with
        Import.requiredField path "bits" fields
...     | Diagnostic.rejected problems = Diagnostic.rejected problems
...     | Diagnostic.accepted bits-value with
          Diagnostic.appendResult
            (decodeDirection (Import.fieldAt path "direction") direction-value)
            (decodeConnections (Import.fieldAt path "bits") bits-value)
...       | Diagnostic.rejected problems = Diagnostic.rejected problems
...       | Diagnostic.accepted (direction , connections) =
          Diagnostic.accepted
            (Raw.rawTopPort name direction
              (lengthList connections) connections)

decodeCellFields : Import.JsonPath → String → Import.ObjectFields
                 → Diagnostic.CheckResult Raw.RawInstance
decodeCellFields path name fields with Import.requiredField path "type" fields
... | Diagnostic.rejected problems = Diagnostic.rejected problems
... | Diagnostic.accepted type-value with
      Import.expectString (Import.fieldAt path "type") type-value
...   | Diagnostic.rejected problems = Diagnostic.rejected problems
...   | Diagnostic.accepted type-name with
        Import.requiredField path "connections" fields
...     | Diagnostic.rejected problems = Diagnostic.rejected problems
...     | Diagnostic.accepted connection-value with
          Import.requiredField path "port_directions" fields
...       | Diagnostic.rejected problems = Diagnostic.rejected problems
...       | Diagnostic.accepted direction-value with
            Import.requiredField path "parameters" fields
...         | Diagnostic.rejected problems = Diagnostic.rejected problems
...         | Diagnostic.accepted parameter-value with supportedKind type-name
...           | nothing with
              Diagnostic.appendResult
                (decodeUnknownPorts
                  (Import.fieldAt path "connections") connection-value
                  (Import.fieldAt path "port_directions") direction-value)
                (decodeUnknownParameters
                  (Import.fieldAt path "parameters") parameter-value)
...             | Diagnostic.rejected problems = Diagnostic.rejected problems
...             | Diagnostic.accepted (ports , parameters) =
                Diagnostic.accepted
                  (Raw.rawInstance name (Raw.unknownPrimitive type-name)
                    ports parameters nothing)
decodeCellFields path name fields
    | Diagnostic.accepted type-value
    | Diagnostic.accepted type-name
    | Diagnostic.accepted connection-value
    | Diagnostic.accepted direction-value
    | Diagnostic.accepted parameter-value
    | just kind with
      Diagnostic.appendResult
        (decodeKnownPorts kind
          (Import.fieldAt path "connections") connection-value
          (Import.fieldAt path "port_directions") direction-value)
        (decodeKnownParameters kind
          (Import.fieldAt path "parameters") parameter-value)
... | Diagnostic.rejected problems = Diagnostic.rejected problems
... | Diagnostic.accepted (ports , (parameters , initial-bit)) =
    Diagnostic.accepted
      (Raw.rawInstance name (Raw.knownPrimitive kind)
        ports parameters initial-bit)

decodeCell : Import.JsonPath → String → Import.JsonValue
           → Diagnostic.CheckResult Raw.RawInstance
decodeCell path name value with Import.expectUniqueObject path value
... | Diagnostic.rejected problems = Diagnostic.rejected problems
... | Diagnostic.accepted fields = decodeCellFields path name fields

decodeModuleFields : Import.JsonPath → Import.ObjectFields
                   → Diagnostic.CheckResult Raw.RawDesign
decodeModuleFields path fields with Import.requiredField path "ports" fields
... | Diagnostic.rejected problems = Diagnostic.rejected problems
... | Diagnostic.accepted ports-value with
      Import.expectUniqueObject (Import.fieldAt path "ports") ports-value
...   | Diagnostic.rejected problems = Diagnostic.rejected problems
...   | Diagnostic.accepted port-fields with
        Import.requiredField path "cells" fields
...     | Diagnostic.rejected problems = Diagnostic.rejected problems
...     | Diagnostic.accepted cells-value with
          Import.expectUniqueObject (Import.fieldAt path "cells") cells-value
...       | Diagnostic.rejected problems = Diagnostic.rejected problems
...       | Diagnostic.accepted cell-fields with
          Diagnostic.appendResult
            (Import.decodeObjectFields decodeTopPort
              (Import.fieldAt path "ports") port-fields)
            (Import.decodeObjectFields decodeCell
              (Import.fieldAt path "cells") cell-fields)
...         | Diagnostic.rejected problems = Diagnostic.rejected problems
...         | Diagnostic.accepted (ports , cells) =
            Diagnostic.accepted (Raw.rawDesign nothing ports cells)

decodeSelectedModule : String → Import.JsonValue
                     → Diagnostic.CheckResult Raw.RawDesign
decodeSelectedModule selected document with
  Import.expectUniqueObject Import.root document
... | Diagnostic.rejected problems = Diagnostic.rejected problems
... | Diagnostic.accepted root-fields with
      Import.requiredField Import.root "modules" root-fields
...   | Diagnostic.rejected problems = Diagnostic.rejected problems
...   | Diagnostic.accepted modules-value with
        Import.expectUniqueObject
          (Import.fieldAt Import.root "modules") modules-value
...     | Diagnostic.rejected problems = Diagnostic.rejected problems
...     | Diagnostic.accepted modules with
          Import.requiredField
            (Import.fieldAt Import.root "modules") selected modules
...       | Diagnostic.rejected problems = Diagnostic.rejected problems
...       | Diagnostic.accepted module-value with
            Import.expectUniqueObject
              (Import.fieldAt
                (Import.fieldAt Import.root "modules") selected)
              module-value
...         | Diagnostic.rejected problems = Diagnostic.rejected problems
...         | Diagnostic.accepted fields =
            decodeModuleFields
              (Import.fieldAt
                (Import.fieldAt Import.root "modules") selected)
              fields

SelectedModuleAccepted : String → Import.JsonValue → Type₀
SelectedModuleAccepted selected document =
  Import.AcceptedResult (decodeSelectedModule selected document)

extractSelectedModule : (selected : String) → (document : Import.JsonValue)
                      → SelectedModuleAccepted selected document
                      → Raw.RawDesign
extractSelectedModule selected document = Import.extractAccepted