{-# OPTIONS --safe --cubical #-}
module Spartan6.Import.Json where
open import Spartan6.Prelude
import FF.Json as JSON
import FF.Json.Native as Native
import Spartan6.Validation.Diagnostic as Diagnostic
open import Agda.Builtin.String
using (primShowNat; primStringAppend; primStringEquality)
infixr 5 _<>_
_<>_ : String → String → String
_<>_ = primStringAppend
JsonValue : Type₀
JsonValue = Native.JsonValue
ObjectFields : Type₀
ObjectFields = List (String × JsonValue)
data PathSegment : Type₀ where
keySegment : String → PathSegment
indexSegment : ℕ → PathSegment
JsonPath : Type₀
JsonPath = List PathSegment
root : JsonPath
root = []ᴸ
fieldAt : JsonPath → String → JsonPath
fieldAt path name = path ++ᴸ (keySegment name ∷ᴸ []ᴸ)
indexAt : JsonPath → ℕ → JsonPath
indexAt path number = path ++ᴸ (indexSegment number ∷ᴸ []ᴸ)
renderPathFrom : String → JsonPath → String
renderPathFrom prefix []ᴸ = prefix
renderPathFrom prefix (keySegment name ∷ᴸ path) =
renderPathFrom (prefix <> "." <> name) path
renderPathFrom prefix (indexSegment number ∷ᴸ path) =
renderPathFrom (prefix <> "[" <> primShowNat number <> "]") path
renderPath : JsonPath → String
renderPath = renderPathFrom "$"
singleton : Diagnostic.Diagnostic → Diagnostic.Diagnostics
singleton problem = problem ∷ᴸ []ᴸ
malformedAt : JsonPath → String → String → String → Diagnostic.Diagnostics
malformedAt path expected observed detail =
singleton
(Diagnostic.diagnostic
Diagnostic.malformedImport
Diagnostic.reject
(renderPath path)
expected
observed
detail)
jsonShape : JsonValue → String
jsonShape (JSON.atom Native.string value) = "string"
jsonShape (JSON.atom Native.number value) = "natural number"
jsonShape (JSON.atom Native.boolean value) = "boolean"
jsonShape (JSON.atom Native.null value) = "null"
jsonShape (JSON.array values) = "array"
jsonShape (JSON.object fields) = "object"
containsField : String → ObjectFields → Bool
containsField name []ᴸ = false
containsField name ((candidate , value) ∷ᴸ fields) =
primStringEquality name candidate or containsField name fields
firstDuplicateField : ObjectFields → Maybe String
firstDuplicateField []ᴸ = nothing
firstDuplicateField ((name , value) ∷ᴸ fields) =
if containsField name fields
then just name
else firstDuplicateField fields
expectUniqueObject : JsonPath → JsonValue
→ Diagnostic.CheckResult ObjectFields
expectUniqueObject path (JSON.object fields) with firstDuplicateField fields
... | nothing = Diagnostic.accepted fields
... | just name =
Diagnostic.rejected
(malformedAt
(fieldAt path name)
"a unique object field"
"a duplicate field"
"Duplicate JSON object fields are rejected; source order never assigns precedence.")
expectUniqueObject path value =
Diagnostic.rejected
(malformedAt path "an object" (jsonShape value)
"This Yosys schema position requires a JSON object.")
matchingFields : String → ObjectFields → List JsonValue
matchingFields name []ᴸ = []ᴸ
matchingFields name ((candidate , value) ∷ᴸ fields) =
if primStringEquality name candidate
then value ∷ᴸ matchingFields name fields
else matchingFields name fields
requiredField : JsonPath → String → ObjectFields
→ Diagnostic.CheckResult JsonValue
requiredField path name fields with matchingFields name fields
... | []ᴸ =
Diagnostic.rejected
(malformedAt
(fieldAt path name)
"one required field"
"a missing field"
"The selected Yosys schema requires this field.")
... | value ∷ᴸ []ᴸ = Diagnostic.accepted value
... | first ∷ᴸ second ∷ᴸ rest =
Diagnostic.rejected
(malformedAt
(fieldAt path name)
"one required field"
"multiple fields with the same name"
"Required fields must be unique; no duplicate wins by position.")
expectArray : JsonPath → JsonValue → Diagnostic.CheckResult (List JsonValue)
expectArray path (JSON.array values) = Diagnostic.accepted values
expectArray path value =
Diagnostic.rejected
(malformedAt path "an array" (jsonShape value)
"This Yosys schema position requires a JSON array.")
expectString : JsonPath → JsonValue → Diagnostic.CheckResult String
expectString path (JSON.atom Native.string value) = Diagnostic.accepted value
expectString path value =
Diagnostic.rejected
(malformedAt path "a string" (jsonShape value)
"This Yosys schema position requires a JSON string.")
expectNatural : JsonPath → JsonValue → Diagnostic.CheckResult ℕ
expectNatural path (JSON.atom Native.number value) = Diagnostic.accepted value
expectNatural path value =
Diagnostic.rejected
(malformedAt path "a natural number" (jsonShape value)
"The native ff-json universe represents only non-negative integral numbers.")
prependResult : ∀ {A : Type₀}
→ Diagnostic.CheckResult A
→ Diagnostic.CheckResult (List A)
→ Diagnostic.CheckResult (List A)
prependResult (Diagnostic.accepted value) (Diagnostic.accepted values) =
Diagnostic.accepted (value ∷ᴸ values)
prependResult (Diagnostic.rejected problems) (Diagnostic.accepted values) =
Diagnostic.rejected problems
prependResult (Diagnostic.accepted value) (Diagnostic.rejected problems) =
Diagnostic.rejected problems
prependResult (Diagnostic.rejected first) (Diagnostic.rejected rest) =
Diagnostic.rejected (first ++ᴸ rest)
decodeArrayFrom : ∀ {A : Type₀}
→ (JsonPath → JsonValue → Diagnostic.CheckResult A)
→ JsonPath → ℕ → List JsonValue
→ Diagnostic.CheckResult (List A)
decodeArrayFrom decoder path next-index []ᴸ = Diagnostic.accepted []ᴸ
decodeArrayFrom decoder path next-index (value ∷ᴸ values) =
prependResult
(decoder (indexAt path next-index) value)
(decodeArrayFrom decoder path (suc next-index) values)
decodeArray : ∀ {A : Type₀}
→ (JsonPath → JsonValue → Diagnostic.CheckResult A)
→ JsonPath → List JsonValue
→ Diagnostic.CheckResult (List A)
decodeArray decoder path = decodeArrayFrom decoder path zero
decodeObjectFields : ∀ {A : Type₀}
→ (JsonPath → String → JsonValue
→ Diagnostic.CheckResult A)
→ JsonPath → ObjectFields
→ Diagnostic.CheckResult (List A)
decodeObjectFields decoder path []ᴸ = Diagnostic.accepted []ᴸ
decodeObjectFields decoder path ((name , value) ∷ᴸ fields) =
prependResult
(decoder (fieldAt path name) name value)
(decodeObjectFields decoder path fields)
record AcceptedResult {A : Type₀}
(result : Diagnostic.CheckResult A) : Type₀ where
constructor acceptedResult
field
acceptedValue : A
acceptedEquality : result ≡ Diagnostic.accepted acceptedValue
open AcceptedResult public
extractAccepted : ∀ {A : Type₀} {result : Diagnostic.CheckResult A}
→ AcceptedResult result → A
extractAccepted evidence = acceptedValue evidence