summaryrefslogtreecommitdiff
path: root/src/compiler/GF/Grammar/CanonicalJSON.hs
blob: 39cd32e80ffa850692b324e806dbfba26efd7f88 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
{-# language OverloadedStrings, OverloadedLists #-}

module GF.Grammar.CanonicalJSON (
  encodeJSON, encodeYAML,
  decodeJSON, decodeYAML
  ) where


import qualified Control.Monad       as CM    (mapM, msum)
import qualified Data.HashMap.Strict as HM    (toList)
import qualified Data.Yaml           as Yaml  (encodeFile, decodeFileEither, ParseException)
import qualified Data.Aeson          as Aeson (encodeFile, eitherDecodeFileStrict')
import Data.Aeson                             (ToJSON(..), object, (.=))
import Data.Aeson                             (FromJSON(..), Value(..), withObject, (.:), (.:?), (.!=))
import Data.Aeson.Types                       (typeMismatch, modifyFailure, Pair, Parser)
import Data.Text                              (Text, pack, unpack)
import Data.Scientific                        (floatingOrInteger)

import GF.Grammar.Canonical


encodeJSON :: FilePath -> Grammar -> IO ()
encodeJSON = Aeson.encodeFile

encodeYAML :: FilePath -> Grammar -> IO ()
encodeYAML = Yaml.encodeFile

decodeJSON :: FilePath -> IO (Either String Grammar)
decodeJSON = Aeson.eitherDecodeFileStrict'

decodeYAML :: FilePath -> IO (Either Yaml.ParseException Grammar)
decodeYAML = Yaml.decodeFileEither


-- in general we encode grammars using JSON objects/records,
-- except for newtypes/coercions/direct values

-- the top-level definitions use normal record labels,
-- but recursive types/values/ids use labels staring with a "."

instance ToJSON Grammar where
  toJSON (Grammar abs cncs) = object ["abstract" .= abs, "concretes" .= cncs]

instance FromJSON Grammar where
  parseJSON = withObject "Grammar" $ \o -> Grammar <$> o .: "abstract" <*> o .: "concretes"


--------------------------------------------------------------------------------
-- ** Abstract Syntax

instance ToJSON Abstract where
  toJSON (Abstract absid flags cats funs) 
    = object ["abs"   .= absid,
              "flags" .= flags,
              "cats"  .= cats,
              "funs"  .= funs]

instance FromJSON Abstract where
  parseJSON = withObject "Abstract" $ \o -> Abstract
    <$> o .:  "abs"
    <*> o .:? "flags" .!= Flags []
    <*> o .:  "cats"
    <*> o .:  "funs"


instance ToJSON CatDef where
  -- non-dependent categories are encoded as simple strings:
  toJSON (CatDef c []) = toJSON c
  toJSON (CatDef c cs) = object ["cat" .= c, "args" .= cs]

instance FromJSON CatDef where
  parseJSON (String s) = return $ CatDef (CatId (unpack s)) []
  parseJSON (Object o) = CatDef <$> o .: "cat" <*> o .: "args"
  parseJSON val        = typeMismatch "CatDef" val


instance ToJSON FunDef where
  toJSON (FunDef f ty) = object ["fun" .= f, "type" .= ty]

instance FromJSON FunDef where
  parseJSON = withObject "FunDef" $ \o -> FunDef <$> o .: "fun" <*> o .: "type"


instance ToJSON Type where
  toJSON (Type bs ty) = object [".args" .= bs, ".result" .= ty]

instance FromJSON Type where
  parseJSON = withObject "Type" $ \o -> Type <$> o .: ".args" <*> o .: ".result"


instance ToJSON TypeApp where
  -- non-dependent categories are encoded as simple strings:
  toJSON (TypeApp c [])   = toJSON c
  toJSON (TypeApp c args) = object [".cat" .= c, ".args" .= args]

instance FromJSON TypeApp where
  parseJSON (String s) = return $ TypeApp (CatId (unpack s)) []
  parseJSON (Object o) = TypeApp <$> o .: ".cat" <*> o .: ".args"
  parseJSON val        = typeMismatch "TypeApp" val


instance ToJSON TypeBinding where
  -- non-dependent categories are encoded as simple strings:
  toJSON (TypeBinding Anonymous (Type [] (TypeApp c []))) = toJSON c
  toJSON (TypeBinding x ty) = object [".var" .= x, ".type" .= ty]

instance FromJSON TypeBinding where
  parseJSON (String s) = return $ TypeBinding Anonymous (Type [] (TypeApp (CatId (unpack s)) []))
  parseJSON (Object o) = TypeBinding <$> o .: ".var" <*> o .: ".type"
  parseJSON val        = typeMismatch "TypeBinding" val


--------------------------------------------------------------------------------
-- ** Concrete syntax

instance ToJSON Concrete where
  toJSON (Concrete cncid absid flags params lincats lins) 
    = object ["cnc"     .= cncid,
              "abs"     .= absid,
              "flags"   .= flags,
              "params"  .= params,
              "lincats" .= lincats,
              "lins"    .= lins]

instance FromJSON Concrete where
  parseJSON = withObject "Concrete" $ \o -> Concrete
    <$> o .:  "cnc"
    <*> o .:  "abs"
    <*> o .:? "flags" .!= Flags []
    <*> o .:  "params"
    <*> o .:  "lincats"
    <*> o .:  "lins"


instance ToJSON ParamDef where
  toJSON (ParamDef      p pvs) = object ["param" .= p, "values" .= pvs]
  toJSON (ParamAliasDef p t)   = object ["param" .= p, "alias"  .= t]

instance FromJSON ParamDef where
  parseJSON = withObject "ParamDef" $ \o ->
    choose [ ParamDef      <$> o .: "param" <*> o .: "values"
           , ParamAliasDef <$> o .: "param" <*> o .: "alias"
           ]


instance ToJSON LincatDef where
  toJSON (LincatDef c lt) = object ["cat" .= c, "lintype" .= lt]

instance FromJSON LincatDef where
  parseJSON = withObject "LincatDef" $ \v -> LincatDef <$> v .: "cat" <*> v .: "lintype"


instance ToJSON LinDef where
  toJSON (LinDef f xs lv) = object ["fun" .= f, "args" .= xs, "lin" .= lv]

instance FromJSON LinDef where
  parseJSON = withObject "LinDef" $ \v -> LinDef <$> v .: "fun" <*> v .: "args" <*> v .: "lin"


instance ToJSON LinType where
  toJSON lt = case lt of
    -- the basic types (Str, Float, Int) are encoded as strings:
    StrType         -> "Str"
    FloatType       -> "Float"
    IntType         -> "Int"
    -- parameters are also encoded as strings:
    ParamType pt    -> toJSON pt
    -- tables/tuples are encoded as JSON objects:
    TableType pt lt -> object [".tblarg" .= pt, ".tblval" .= lt]
    TupleType lts   -> object [".tuple"  .= lts]
    -- records are encoded as records:
    RecordType rows -> toJSON rows

instance FromJSON LinType where
  parseJSON (String "Str")   = return StrType
  parseJSON (String "Float") = return FloatType
  parseJSON (String "Int")   = return IntType
  parseJSON (String param)   = return (ParamType (ParamTypeId (ParamId (unpack param))))
  parseJSON val@(Object o)   = choose [ (TableType  <$> o .: ".tblarg" <*> o .: ".tblval")
                                      , (TupleType  <$> o .: ".tuple")
                                      , (RecordType <$> parseJSON val)
                                      ]
  parseJSON val = typeMismatch "LinType" val


instance ToJSON LinValue where
  toJSON lv = case lv of
    -- basic values (Str, Float, Int) are encoded as JSON strings/numbers:
    StrConstant    s  -> toJSON s
    FloatConstant  f  -> toJSON f
    IntConstant    n  -> toJSON n
    -- concatenation is encoded as a JSON array:
    ConcatValue  v v' -> Array [toJSON v, toJSON v']
    -- most values are encoded as JSON objects:
    ParamConstant  pv -> object [".param"    .= pv]
    PredefValue    p  -> object [".predef"   .= p]
    TableValue  t tvs -> object [".tblarg"   .= t, ".tblrows"    .= tvs]
--  VTableValue  t ts -> object [".vtblarg"  .= t, ".vtblrows"   .= ts]
    TupleValue    lvs -> object [".tuple"    .= lvs]
    VarValue       v  -> object [".var"      .= v]
    ErrorValue     s  -> object [".error"    .= s]
    Projection  lv l  -> object [".project"  .= lv, ".label" .= l]
    Selection   tv pv -> object [".select"   .= tv, ".key" .= pv]
    VariantValue   vs -> object [".variants" .= vs]
    PreValue alts def -> object [".pre"      .= alts, ".default" .= def]
    -- records are encoded directly as JSON records:
    RecordValue  rows -> toJSON rows

instance FromJSON LinValue where
  parseJSON (String s)     = return (StrConstant (unpack s))
  parseJSON (Number n)     = return (either FloatConstant IntConstant (floatingOrInteger n))
  parseJSON (Array [v,v']) = ConcatValue <$> parseJSON v <*> parseJSON v'
  parseJSON val@(Object o) = choose [ ParamConstant <$> o .: ".param"
                                    , PredefValue   <$> o .: ".predef"
                                    , TableValue    <$> o .: ".tblarg"  <*> o .: ".tblrows"
--                                  , VTableValue   <$> o .: ".vtblarg" <*> o .: ".vtblrows"
                                    , TupleValue    <$> o .: ".tuple"
                                    , VarValue      <$> o .: ".var"
                                    , ErrorValue    <$> o .: ".error"
                                    , Projection    <$> o .: ".project" <*> o .: ".label"
                                    , Selection     <$> o .: ".select"  <*> o .: ".key"
                                    , VariantValue  <$> o .: ".variants"
                                    , PreValue      <$> o .: ".pre"     <*> o .: ".default"
                                    , RecordValue   <$> parseJSON val
                                    ]
  parseJSON val = typeMismatch "LinValue" val


instance ToJSON LinPattern where
  toJSON linpat = case linpat of
    -- wildcards and patterns without arguments are encoded as strings:
    WildPattern -> "_"
    ParamPattern (Param p []) -> toJSON p
    -- complex patterns are encoded as JSON objects:
    ParamPattern pv -> toJSON pv
    -- and records as records:
    RecordPattern r -> toJSON r

instance FromJSON LinPattern where
  parseJSON (String "_") = return WildPattern
  parseJSON (String  s)  = return (ParamPattern (Param (ParamId (unpack s)) []))
  parseJSON val = choose [ ParamPattern  <$> parseJSON val
                         , RecordPattern <$> parseJSON val
                         , typeMismatch "LinPattern" val
                         ]


instance ToJSON arg => ToJSON (Param arg) where
  -- parameters without arguments are encoded as strings:
  toJSON (Param p [])   = toJSON p
  toJSON (Param p args) = object [".paramid" .= p, ".args" .= args]

instance FromJSON arg => FromJSON (Param arg) where
  parseJSON (String p) = return (Param (ParamId (unpack p)) [])
  parseJSON (Object o) = Param <$> o .: ".paramid" <*> o .: ".args"
  parseJSON val = typeMismatch "Param" val


instance ToJSON a => ToJSON (RecordRow a) where
  -- record rows and lists of record rows are both encoded as JSON records (i.e., objects)
  toJSON row = object [toJSONRecordRow row]
  toJSONList = object . map toJSONRecordRow

toJSONRecordRow :: ToJSON a => RecordRow a -> Pair
toJSONRecordRow (RecordRow (LabelId lbl) val) = pack lbl .= val

instance FromJSON a => FromJSON (RecordRow a) where
  parseJSON     = withObject  "RecordRow"  $ \o -> parseJSONRecordRow (head (HM.toList o))
  parseJSONList = withObject "[RecordRow]" $ \o -> CM.mapM parseJSONRecordRow (HM.toList o) 

parseJSONRecordRow :: FromJSON a => (Text, Value) -> Parser (RecordRow a)
parseJSONRecordRow (lbl, val) = do val' <- parseJSON val
                                   return (RecordRow (LabelId (unpack lbl)) val')


instance ToJSON TableRowValue where
  toJSON (TableRowValue l v) = object [".pattern" .= l, ".value" .= v]

instance FromJSON TableRowValue where
  parseJSON = withObject "TableRowValue" $ \v -> TableRowValue <$> v .: ".pattern" <*> v .: ".value"


-- *** Identifiers in Concrete Syntax

instance ToJSON PredefId   where toJSON (PredefId    s) = toJSON s
instance ToJSON LabelId    where toJSON (LabelId     s) = toJSON s
instance ToJSON VarValueId where toJSON (VarValueId  s) = toJSON s
instance ToJSON ParamId    where toJSON (ParamId     s) = toJSON s
instance ToJSON ParamType  where toJSON (ParamTypeId s) = toJSON s

instance FromJSON PredefId   where parseJSON = coerceFrom "PredefId"   PredefId
instance FromJSON LabelId    where parseJSON = coerceFrom "LabelId"    LabelId
instance FromJSON VarValueId where parseJSON = coerceFrom "VarValueId" VarValueId
instance FromJSON ParamId    where parseJSON = coerceFrom "ParamId"    ParamId
instance FromJSON ParamType  where parseJSON = coerceFrom "ParamType"  ParamTypeId


--------------------------------------------------------------------------------
-- ** Used in both Abstract and Concrete Syntax

instance ToJSON ModId where toJSON (ModId s) = toJSON s
instance ToJSON CatId where toJSON (CatId s) = toJSON s
instance ToJSON FunId where toJSON (FunId s) = toJSON s

instance FromJSON ModId where parseJSON = coerceFrom "ModId" ModId
instance FromJSON CatId where parseJSON = coerceFrom "CatId" CatId
instance FromJSON FunId where parseJSON = coerceFrom "FunId" FunId


instance ToJSON VarId where
  -- the anonymous variable is the underscore:
  toJSON Anonymous = "_"
  toJSON (VarId x) = toJSON x

instance FromJSON VarId where
  parseJSON (String "_") = return Anonymous
  parseJSON (String  s)  = return (VarId (unpack s))
  parseJSON val = typeMismatch "VarId" val


instance ToJSON Flags where
  -- flags are encoded directly as JSON records (i.e., objects):
  toJSON (Flags fs) = object [ pack f .= v | (f, v) <- fs ]

instance FromJSON Flags where
  parseJSON = withObject "Flags" $ \o -> Flags <$> CM.mapM parseJSONFlag (HM.toList o)
    where parseJSONFlag (flag, val) = do val' <- parseJSON val
                                         return (unpack flag, val')


instance ToJSON FlagValue where
  -- flag values are encoded as basic JSON types:
  toJSON (Str s) = toJSON s
  toJSON (Int i) = toJSON i
  toJSON (Flt f) = toJSON f

instance FromJSON FlagValue where
  parseJSON (String s) = return $ Str (unpack s)
  parseJSON (Number n) = return $ case floatingOrInteger n of
                                    Left  f -> Flt f
                                    Right i -> Int i
  parseJSON invalid = typeMismatch "FlagValue" invalid


--------------------------------------------------------------------------------
-- ** Helper functions

choose :: [Parser a] -> Parser a
choose = CM.msum

coerceFrom :: FromJSON s => String -> (s -> a) -> Value -> Parser a
coerceFrom expected constructor obj = modifyFailure failure $ fmap constructor $ parseJSON obj
  where failure f = "(while parsing " ++ expected ++ ") " ++ f