{-# LANGUAGE StandaloneDeriving
, RelaxedPolyRec
#-}moduleText.JSONb.SchemawhereimportData.OrdimportData.WordimportData.List(permutations)importData.SetasSetimportData.TrieasTrieimportqualifiedText.JSONb.SimpleasSimple{-
JSON Schemas:
document ::= array
element ::= num | str | null | bool | object | array
object ::= "{" Set(element) "}"
array ::= "[" List(element) "]"
num ::= "num"
str ::= "str"
null ::= "null"
bool ::= "bool"
-}{-| The type of JSON schemas. We treat the atomic types simply whereas objects
and arrays are treated specially.
Objects are treated as maps of keys to sets of schema types. Say a certain
type of object sometimes has a string at a certain key and sometimes has a
null at that key; we should merge them and say the schema of that key is a
union of string and null.
Arrays admit measure in the sense of how many elements there are of a
certain kind. We support three measures at present: any, one or more and
individual counts. We expect the "any" measure to prevail practice. Arrays
are also ordered; so one can distinguish an array that interleaves strings
and ints from one that is all strings and then all ints.
-}dataSchemacounter=Num|Str|Bool|Null|Obj(Propscounter)|Arr(Elementscounter)derivinginstance(Eqcounter)=>Eq(Schemacounter)derivinginstance(Ordcounter)=>Ord(Schemacounter){-| Determine a schema for one JSON data item.
-}schema::(Countercounter)=>Simple.JSON->Schemacounterschemajson=casejsonofSimple.Objecttrie->Obj$propstrieSimple.Arraylist->Arr.Elements$schemaslistSimple.String_->StrSimple.Number_->NumSimple.Boolean_->BoolSimple.Null->Nullprops::(Countercounter)=>Trie.TrieSimple.JSON->Propscounterprops=Props.fmap(Set.singleton.schema){-| Develop a schema for a list of JSON data, collating schemas according to
the measure, a well-ordered semigroup.
-}schemas::(Countercounter)=>[Simple.JSON]->[(counter,Schemacounter)]schemasjson=foldrcollate[][(bottom,schemae)|e<-json]{-| Collate a list of counted schemas. Alike counted schemas that are adjacent
are replaced by a counted schema with an incremented counter. This
operation is mutually recursive with 'merge', in order to merge comaptible
object definitions before collating.
-}collate::(Countercounter,Countercounter')=>(counter,Schemacounter')->[(counter,Schemacounter')]->[(counter,Schemacounter')]collates[]=[s]collate(c0,Objp0)((c1,Objp1):t)|matchp0p1=(c0`plus`c1,Obj$mergep0p1):t|otherwise=(c0,Objp0):(c1,Objp1):tcollate(c0,schema0)((c1,schema1):t)|schema0==schema1=(c0`plus`c1,schema0):t|otherwise=(c0,schema0):(c1,schema1):tdataPropscounter=Props(Trie.Trie(Set.Set(Schemacounter)))derivinginstance(Eqcounter)=>Eq(Propscounter)instance(Ordcounter)=>Ord(Propscounter)wherecompare(Propstrie0)(Propstrie1)=comparingTrie.toListtrie0trie1{-| Merge two property sets. This operation is mutually recursive with our
'collate' and relies on polymorphic recusion in 'collate'.
-}merge::(Countercounter)=>Propscounter->Propscounter->Propscountermerge(Propsa)(Propsb)=Props$Trie.mergeBy((Just.).merge')abwheremerge'=((count_in.merge''.count_out).).Set.unionwhere-- We use the unary (existence) counter so that it collates set-like. count_out=fmap((,)()).Set.toListcount_in=Set.fromList.fmapsndmerge''[]=[]merge''(h:t)=foldrcollate't(h:t)where-- We expect only very small sets of schemas.collate'schema=shortest.fmap(collateschema).permutationsshortest[]=[]shortest(h:t)=foldrshortest'htwhereshortest'xh|lengthh<lengthx=h|otherwise=xmatch::(Countercounter)=>Propscounter->Propscounter->Boolmatch(Propsa)(Propsb)=Trie.keysa==Trie.keysbdataElementscounter=Elements[(counter,Schemacounter)]derivinginstance(Eqcounter)=>Eq(Elementscounter)derivinginstance(Ordcounter)=>Ord(Elementscounter)dataOneMany=One|ManyderivinginstanceEqOneManyderivinginstanceOrdOneManyderivinginstanceShowOneMany{-| A well-ordered semigroup has a minimal element and an associative
operation. These are used to provide measures for schema. At present, we
allow three measures: whether there is one or more of a schema (measured
with '()'), whether there is one or more than one of an item (measured with
'OneMany') and positive counts of items (measured with 'Word').
-}class(Eqt,Showt,Ordt)=>Countertwherebottom::tplus::t->t->tinstanceCounterOneManywherebottom=Oneplus__=ManyinstanceCounterWordwherebottom=1plus=(+)instanceCounter()wherebottom=()plus__=()