Commit | Line | Data |
---|---|---|
7f918cf1 CE |
1 | (* Copyright (C) 2017 Jason Carr. |
2 | * | |
3 | * MLton is released under a BSD-style license. | |
4 | * See the file MLton-LICENSE for details. | |
5 | *) | |
6 | ||
7 | functor ParseSxml (S: PARSE_SXML_STRUCTS): PARSE_SXML = | |
8 | struct | |
9 | open S | |
10 | open XmlTree | |
11 | structure P = Parse | |
12 | open P.Ops | |
13 | infix 1 <|> >>= | |
14 | infix 3 <*> <* *> | |
15 | infixr 4 <$> <$$> <$$$> <$ <$?> | |
16 | ||
17 | fun isInfixChar b = case List.index | |
18 | (String.explode "!%&$#+-/:<=>?@\\~'^|*", | |
19 | fn c => b = c) of | |
20 | SOME _ => true | |
21 | | NONE => false | |
22 | ||
23 | fun isIdentFirst b = Char.isAlpha b orelse b = #"'" | |
24 | fun isIdentRest b = Char.isAlphaNum b orelse b = #"'" orelse b = #"_" orelse b = #"." | |
25 | ||
26 | val stringToken = (fn (x, y) => [x, y]) <$$> (P.char #"\\", P.next) <|> | |
27 | (fn x => [x] ) <$> P.next | |
28 | fun skipComments () = P.any | |
29 | [P.str "(*" *> P.cut (P.manyCharsFailing(P.str "*)" <|> P.str "(*") *> | |
30 | ((P.str "*)" <|> "" <$ P.delay skipComments) *> P.each [P.next] | |
31 | <|> P.failCut "Closing comment")), | |
32 | List.concat <$> P.each | |
33 | [P.each [P.char #"\""], | |
34 | List.concat <$> P.manyFailing(stringToken, P.char #"\""), | |
35 | P.each [P.char #"\""]], | |
36 | P.each [P.next]] | |
37 | ||
38 | fun token s = P.notFollowedBy | |
39 | (P.str s, | |
40 | (P.char #"_") <|> (P.nextSat Char.isAlphaNum)) <* P.spaces | |
41 | fun symbol s = P.notFollowedBy | |
42 | (P.str s, | |
43 | (P.nextSat (fn b => isInfixChar b orelse b = #"_"))) <* P.spaces | |
44 | ||
45 | val clOptions = P.manyCharsFailing(P.str "Datatypes:") | |
46 | ||
47 | fun 'a makeNameResolver(f: string -> 'a): string -> 'a = | |
48 | let | |
49 | val hash = String.hash | |
50 | val map = HashSet.new{hash= hash o #1} | |
51 | fun eq x (a: string * 'a) = String.equals(x, #1 a) | |
52 | in | |
53 | fn x => (#2 o HashSet.lookupOrInsert)(map, hash x, eq x, fn () => (x, f x)) | |
54 | end | |
55 | ||
56 | ||
57 | val ident = ( | |
58 | String.implode <$> (P.any | |
59 | [(op ::) <$$> | |
60 | (P.nextSat isIdentFirst, | |
61 | P.many (P.nextSat isIdentRest)), | |
62 | List.append <$$> | |
63 | (P.many1 (P.nextSat isInfixChar), | |
64 | (op ::) <$$> (P.char #"_", P.many (P.nextSat Char.isDigit)) <|> P.pure [] | |
65 | (* just for collecting _0 *) | |
66 | )])) <|> P.failCut "identifier" | |
67 | ||
68 | fun doneRecord' () = P.char #"{" <* P.many(P.delay doneRecord' <|> P.failing(P.char #"}") *> P.next) <* P.char #"}" | |
69 | val doneRecord = doneRecord' () | |
70 | fun fromRecord name p = P.peek | |
71 | (P.char #"{" *> P.many (() <$ P.delay doneRecord' <|> () <$ P.failing (token name <* symbol "=") <* P.next) | |
72 | *> token name *> symbol "=" *> p) | |
73 | ||
74 | fun casesOf(con, left, right) = Vector.fromList <$> P.sepBy1 | |
75 | (left <* P.spaces <* token "=>" >>= (fn l => | |
76 | right >>= (fn r => con (l, r))), | |
77 | P.spaces) | |
78 | ||
79 | fun optionOf p = SOME <$> (token "Some" *> P.cut(p)) <|> NONE <$ token "None" | |
80 | ||
81 | ||
82 | (* too many arguments for the maps, curried to use <*> instead *) | |
83 | fun makeTyp resolveTycon (args, ident) = Type.con (resolveTycon ident, args) | |
84 | ||
85 | local | |
86 | fun typ' resolveTycon () = (makeTyp resolveTycon) <$$> | |
87 | (((P.tuple (P.delay (typ' resolveTycon))) <|> P.pure (Vector.new0 ())), | |
88 | (P.spaces *> ident <* P.spaces)) | |
89 | in | |
90 | fun typ resolveTycon = typ' resolveTycon () | |
91 | end | |
92 | ||
93 | val ctype = (P.any o List.map) | |
94 | (CType.all, fn ct => | |
95 | ct <$ token (CType.toString ct)) | |
96 | ||
97 | fun makeCon resolveCon (name, arg) = {con = resolveCon name, arg = arg} | |
98 | ||
99 | (* parse in a constructor (to Con.t) *) | |
100 | fun constructor resolveCon resolveTycon = (makeCon resolveCon) <$$> | |
101 | (ident <* P.spaces, | |
102 | P.optional (token "of" *> (typ resolveTycon) <* P.spaces) ) | |
103 | ||
104 | ||
105 | fun makeDt resolveTycon (tycon, cons) = | |
106 | {tyvars = Vector.new0 (), | |
107 | tycon = resolveTycon tycon, cons = cons} | |
108 | ||
109 | fun datatyp resolveCon resolveTycon = (makeDt resolveTycon) <$$> | |
110 | ((P.spaces *> ident <* P.spaces <* symbol "="), | |
111 | (Vector.fromList <$> P.sepBy1 | |
112 | ((constructor resolveCon resolveTycon) <* P.spaces, | |
113 | P.char #"|" *> P.spaces))) | |
114 | ||
115 | fun datatypes resolveCon resolveTycon = | |
116 | token "Datatypes:" *> Vector.fromList <$> P.many (datatyp resolveCon resolveTycon) | |
117 | ||
118 | fun overflow resolveVar = P.str "Overflow:" *> P.spaces *> ( | |
119 | (P.str "Some" *> P.spaces *> SOME <$> resolveVar <$> ident <* P.spaces) <|> | |
120 | (NONE <$ P.str "None" <* P.spaces)) | |
121 | ||
122 | ||
123 | val digits = P.many (P.nextSat (fn c => Char.isDigit c orelse c = #"~")) | |
124 | val parseIntInf = ((IntInf.fromString o String.implode) <$?> digits) <|> P.failCut "integer" | |
125 | val parseString = ((String.fromString o String.implode o List.concat) <$?> | |
126 | (P.char #"\"" *> (P.manyFailing(stringToken, P.char #"\"")) <* P.char #"\"")) | |
127 | fun makeReal s = (case (RealX.make s) of NONE => NONE | x => x) handle Fail _ => NONE | |
128 | fun parseReal sz = (makeReal <$?> (fn p => p) <$$> (String.implode <$> | |
129 | List.concat <$> P.each | |
130 | [P.many (P.nextSat (fn c => Char.isDigit c orelse c = #"~")), | |
131 | P.char #"." *> P.pure [#"."] <|> P.pure [], | |
132 | P.many (P.nextSat (fn c => Char.isDigit c orelse c = #"E" orelse c = #"~"))], | |
133 | P.pure sz)) | |
134 | val parseHex = P.fromReader (IntInf.scan(StringCvt.HEX, P.toReader P.next)) | |
135 | val parseBool = true <$ token "true" <|> false <$ token "false" | |
136 | ||
137 | fun makeWord typ int = | |
138 | if Tycon.isWordX typ | |
139 | then P.pure (WordX.fromIntInf(int, (Tycon.deWordX typ))) | |
140 | else P.fail "Invalid word" | |
141 | val parseWord8Vector = WordXVector.fromVector <$$> | |
142 | (P.pure {elementSize=WordSize.word8}, | |
143 | P.char #"#" *> P.vector (parseHex >>= makeWord (Tycon.word WordSize.word8))) | |
144 | ||
145 | fun exp resolveCon resolveTycon resolveVar = | |
146 | let | |
147 | fun makeLet(decs, result) = Exp.make {decs=decs, result=result} | |
148 | fun makeExnDec(ca) = Dec.Exception ca | |
149 | fun makeValDec(var, ty, exp) = Dec.MonoVal {exp=exp, ty=ty, var=var} | |
150 | fun makeFunDecs(lambdas) = Dec.Fun {decs=Vector.fromList lambdas, tyvars=Vector.new0 ()} | |
151 | fun makeFunDec((var, ty), lambda) = {lambda=lambda, ty=ty, var=var} | |
152 | val var = resolveVar <$> ident <* P.spaces | |
153 | val typedvar = (fn (x,y) => (x,y)) <$$> | |
154 | (var, | |
155 | symbol ":" *> (typ resolveTycon) <* P.spaces) | |
156 | fun makeVarExp var = VarExp.T {var=var, targs = Vector.new0 ()} | |
157 | val varExp = | |
158 | P.failing (token "in" <|> token "exception" <|> token "val") *> | |
159 | makeVarExp <$> var | |
160 | fun makeApp(func, arg) = {arg=arg, func=func} | |
161 | fun makeConApp(con, targs, arg) = {arg=arg, con=con, targs=targs} | |
162 | fun conApp v = makeConApp <$$$> | |
163 | (resolveCon <$> ident <* P.spaces, | |
164 | P.pure (Vector.new0 ()), | |
165 | P.optional v) | |
166 | val conAppExp = token "new" *> P.cut (conApp varExp) | |
167 | fun constExp typ = | |
168 | if Tycon.isWordX typ then | |
169 | Const.Word <$> (P.str "0x" *> parseHex >>= makeWord typ) <|> P.failCut "word" | |
170 | else if Tycon.isRealX typ then | |
171 | Const.Real <$> parseReal (Tycon.deRealX typ) <|> P.failCut "real" | |
172 | else if Tycon.isIntX typ then | |
173 | Const.IntInf <$> parseIntInf <|> P.failCut "integer" | |
174 | else if Tycon.equals(typ, Tycon.vector) then | |
175 | (* assume it's a word8 vector *) | |
176 | P.any | |
177 | [Const.string <$> parseString, | |
178 | Const.wordVector <$> parseWord8Vector, | |
179 | P.failCut "string constant"] | |
180 | ||
181 | else | |
182 | P.fail "constant" | |
183 | fun makeRaise(NONE, exn) = {exn=exn, extend=false} | |
184 | | makeRaise(SOME _, exn) = {exn=exn, extend=true} | |
185 | val raiseExp = token "raise" *> P.cut (makeRaise <$$> (P.optional (token "extend"), varExp <* P.spaces)) | |
186 | fun makeHandle(try, catch, handler) = {catch=catch, handler=handler, try=try} | |
187 | fun makeLambda(mayInline, (var, typ), exp) = Lambda.make {arg=var, argType=typ, body=exp, mayInline=mayInline} | |
188 | ||
189 | val parseConvention = CFunction.Convention.Cdecl <$ token "cdecl" <|> | |
190 | CFunction.Convention.Stdcall <$ token "stdcall" | |
191 | fun makeRuntimeTarget bytes ens mayGC maySwitch modifies readsSt writesSt = | |
192 | CFunction.Kind.Runtime ({bytesNeeded=bytes, ensuresBytesFree=ens, | |
193 | mayGC=mayGC, maySwitchThreads=maySwitch, modifiesFrontier=modifies, | |
194 | readsStackTop=readsSt, writesStackTop=writesSt}) | |
195 | val parseRuntimeTarget = makeRuntimeTarget | |
196 | <$> fromRecord "bytesNeeded" (optionOf P.uint) | |
197 | <*> fromRecord "ensuresBytesFree" parseBool | |
198 | <*> fromRecord "mayGC" parseBool | |
199 | <*> fromRecord "maySwitchThreads" parseBool | |
200 | <*> fromRecord "modifiesFrontier" parseBool | |
201 | <*> fromRecord "readsStackTop" parseBool | |
202 | <*> fromRecord "writesStackTop" parseBool | |
203 | <* doneRecord | |
204 | val parseKind = CFunction.Kind.Impure <$ token "Impure" <|> | |
205 | CFunction.Kind.Pure <$ token "Pure" <|> | |
206 | token "Runtime" *> P.cut parseRuntimeTarget | |
207 | ||
208 | val parsePrototype = (fn x => x) <$$> | |
209 | (fromRecord "args" (P.tuple ctype), | |
210 | fromRecord "res" (optionOf ctype)) <* doneRecord | |
211 | val parseSymbolScope = P.any | |
212 | [CFunction.SymbolScope.External <$ token "external", | |
213 | CFunction.SymbolScope.Private <$ token "private", | |
214 | CFunction.SymbolScope.Public <$ token "public"] | |
215 | ||
216 | ||
217 | val parseTarget = CFunction.Target.Indirect <$ symbol "<*>" <|> | |
218 | CFunction.Target.Direct <$> ident | |
219 | fun makeFFI args conv kind prototype return symbolScope target = | |
220 | Prim.ffi (CFunction.T | |
221 | {args=args, convention=conv, kind=kind, | |
222 | prototype=prototype, return=return, symbolScope=symbolScope, | |
223 | target = target}) | |
224 | val resolveFFI = token "FFI" *> P.cut( | |
225 | makeFFI | |
226 | <$> fromRecord "args" (P.tuple (typ resolveTycon)) | |
227 | <*> fromRecord "convention" parseConvention | |
228 | <*> fromRecord "kind" parseKind | |
229 | <*> fromRecord "prototype" parsePrototype | |
230 | <*> fromRecord "return" (typ resolveTycon) | |
231 | <*> fromRecord "symbolScope" parseSymbolScope | |
232 | <*> fromRecord "target" parseTarget | |
233 | <* doneRecord) | |
234 | fun makeFFISym name cty symbolScope = Prim.ffiSymbol {name=name, cty=cty, symbolScope=symbolScope} | |
235 | val resolveFFISym = token "FFI_Symbol" *> P.cut( | |
236 | makeFFISym | |
237 | <$> fromRecord "name" ident | |
238 | <*> fromRecord "cty" (optionOf ctype) | |
239 | <*> fromRecord "symbolScope" parseSymbolScope | |
240 | <* doneRecord) | |
241 | ||
242 | fun resolvePrim p = case Prim.fromString p | |
243 | of SOME p' => P.pure p' | |
244 | | NONE => P.fail ("valid primitive, got " ^ p) | |
245 | fun makePrimApp(prim, targs, args) = {args=args, prim=prim, targs=targs} | |
246 | val primAppExp = token "prim" *> P.cut (makePrimApp <$$$> | |
247 | (P.any [ | |
248 | resolveFFI, | |
249 | resolveFFISym, | |
250 | (ident <* P.spaces >>= resolvePrim)], | |
251 | (P.vector (typ resolveTycon) <|> P.pure (Vector.new0 ())) <* P.spaces, | |
252 | P.tuple varExp <* P.spaces)) | |
253 | fun makeSelect(offset, var) = {offset=offset, tuple=var} | |
254 | val selectExp = symbol "#" *> P.cut(makeSelect <$$> | |
255 | (P.uint <* P.spaces, | |
256 | varExp)) | |
257 | val profileExp = (ProfileExp.Enter <$ token "Enter" <|> | |
258 | ProfileExp.Leave <$ token "Leave") <*> | |
259 | P.cut ((SourceInfo.fromC o String.implode) <$> | |
260 | P.manyCharsFailing(P.char #"\n") <* P.char #"\n" <* P.spaces) | |
261 | fun makeConCases var (cons, def) = | |
262 | {test=var, | |
263 | cases=Cases.Con cons, | |
264 | default=Option.map(def, fn x => (x, Region.bogus))} | |
265 | fun makeWordCases var s (wds, def) = | |
266 | {test=var, | |
267 | cases=Cases.Word (case s of | |
268 | 8 => Type.WordSize.word8 | |
269 | | 16 => Type.WordSize.word16 | |
270 | | 32 => Type.WordSize.word32 | |
271 | | 64 => Type.WordSize.word64 | |
272 | | _ => raise Fail "makeWordCases" (* can't happen *) | |
273 | , wds), | |
274 | default=Option.map(def, fn x => (x, Region.bogus))} | |
275 | fun makePat(con, exp) = P.pure (Pat.T con, exp) | |
276 | fun makeCaseWord size (int, exp) = case size of | |
277 | (* this is repetetive, but it's a bit awkward to rework around the fail *) | |
278 | 8 => P.pure ((WordX.fromIntInf(int, Type.WordSize.word8)), exp) | |
279 | | 16 => P.pure ((WordX.fromIntInf(int, Type.WordSize.word16)), exp) | |
280 | | 32 => P.pure ((WordX.fromIntInf(int, Type.WordSize.word32)), exp) | |
281 | | 64 => P.pure ((WordX.fromIntInf(int, Type.WordSize.word64)), exp) | |
282 | | _ => P.fail "valid word size for cases (8, 16, 32 or 64)" | |
283 | ||
284 | ||
285 | fun exp' () = makeLet <$$> | |
286 | (token "let" *> | |
287 | P.many (dec ()) <* token "in", varExp <* P.str "end") | |
288 | and dec () = P.any | |
289 | [token "exception" *> | |
290 | P.cut (makeExnDec <$> (constructor resolveCon resolveTycon)), | |
291 | token "val" *> token "rec" *> | |
292 | P.cut (makeFunDecs <$> | |
293 | P.many (makeFunDec <$$> (typedvar <* symbol "=" <* P.spaces, lambdaExp ()))), | |
294 | token "val" *> | |
295 | P.cut (makeValDec <$> | |
296 | (typedvar >>= (fn (var, ty) => | |
297 | (symbol "=" *> primexp ty <* P.spaces) >>= (fn primexp => | |
298 | P.pure (var, ty, primexp)))))] | |
299 | and primexp typ = P.any | |
300 | [PrimExp.Case <$> casesExp (), | |
301 | PrimExp.ConApp <$> conAppExp, | |
302 | PrimExp.Lambda <$> lambdaExp (), | |
303 | (* const must come before select due to vector constants *) | |
304 | PrimExp.Const <$> constExp (Type.tycon typ), | |
305 | PrimExp.Handle <$> handleExp (), | |
306 | PrimExp.PrimApp <$> primAppExp, | |
307 | PrimExp.Profile <$> profileExp, | |
308 | PrimExp.Raise <$> raiseExp, | |
309 | PrimExp.Select <$> selectExp, | |
310 | PrimExp.Tuple <$> (P.tuple varExp) <* P.spaces, | |
311 | (* put these last, they just take identifiers so they're pretty greedy *) | |
312 | (* App *must* procede var, due to greediness *) | |
313 | PrimExp.App <$> makeApp <$$> (varExp, varExp), | |
314 | PrimExp.Var <$> varExp] | |
315 | and handleExp () = makeHandle <$$$> | |
316 | (P.delay exp' <* P.spaces, | |
317 | token "handle" *> P.cut typedvar, | |
318 | P.cut (token "=>" *> P.delay exp' <* P.spaces) | |
319 | ) | |
320 | and lambdaExp () = token "fn" *> P.cut(makeLambda <$$$> | |
321 | (false <$ (token "noinline") <|> P.pure true, | |
322 | typedvar, | |
323 | token "=>" *> P.delay exp' <* P.spaces)) | |
324 | and casesExp () = P.str "case" *> | |
325 | P.optional P.uint <* P.many1 P.space >>= (fn size => P.cut( | |
326 | varExp <* token "of" <* P.spaces >>= (fn var => | |
327 | case size of | |
328 | NONE => makeConCases var <$$> | |
329 | (casesOf(makePat, conApp typedvar, P.delay exp'), | |
330 | P.spaces *> P.optional(token "_" *> token "=>" *> P.delay exp')) | |
331 | | SOME s => makeWordCases var s <$$> | |
332 | (casesOf(makeCaseWord s, P.str "0x" *> parseHex, P.delay exp'), | |
333 | P.spaces *> P.optional(token "_" *> token "=>" *> P.delay exp')) | |
334 | ))) | |
335 | in | |
336 | exp' () | |
337 | end | |
338 | ||
339 | fun body resolveCon resolveTycon resolveVar = P.str "Body:" *> P.spaces | |
340 | *> exp resolveCon resolveTycon resolveVar | |
341 | (*pure (Exp.fromPrimExp (PrimExp.Tuple (Vector.new0 ()), | |
342 | Type.tuple (Vector.new0 ())))*) | |
343 | ||
344 | fun makeProgram(datatypes, overflow, body) = | |
345 | Program.T | |
346 | {body = body, | |
347 | datatypes = datatypes, | |
348 | overflow = overflow} | |
349 | ||
350 | ||
351 | val program : Program.t Parse.t = | |
352 | let | |
353 | fun strip_unique s = case P.parseString | |
354 | (String.implode <$> P.manyCharsFailing( | |
355 | P.char #"_" *> P.many1 (P.nextSat Char.isDigit) *> P.failing P.next), | |
356 | s) of Result.Yes s' => s' | |
357 | | Result.No _ => s | |
358 | val resolveCon0 = makeNameResolver(Con.newString o strip_unique) | |
359 | fun resolveCon ident = | |
360 | case List.peek ([Con.falsee, Con.truee, Con.overflow, Con.reff], fn con => | |
361 | ident = Con.toString con) of | |
362 | SOME con => con | |
363 | | NONE => resolveCon0 ident | |
364 | val resolveTycon0 = makeNameResolver(Tycon.newString o strip_unique) | |
365 | fun resolveTycon ident = | |
366 | case List.peekMap (Tycon.prims, fn {name, tycon, ...} => | |
367 | if ident = name then SOME tycon else NONE) of | |
368 | SOME con => con | |
369 | | NONE => if ident = "unit" | |
370 | then Tycon.tuple | |
371 | else resolveTycon0 ident | |
372 | val resolveVar = makeNameResolver(Var.newString o strip_unique) | |
373 | in | |
374 | P.compose(skipComments (), | |
375 | clOptions *> | |
376 | (makeProgram <$$$> | |
377 | (datatypes resolveCon resolveTycon, | |
378 | overflow resolveVar, | |
379 | body resolveCon resolveTycon resolveVar <* P.spaces <* (P.failing P.next <|> P.failCut "End of file")))) | |
380 | (* failing next to check for end of file *) | |
381 | end | |
382 | ||
383 | end |