From b9c85328999ed8ceaa04b5caac34024c5d57ae74 Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Wed, 10 Feb 2021 12:47:34 -0800 Subject: [PATCH] feat: Allow expressions to be terminated/sequenced by ; Note that ultimately a terminated sequence may have a slightly different semantics (applying streams to `_|_`, most likely) but for now they don't. --- bin/extract_tests.xsh | 6 ++- editor/Syntax.esv | 1 + syntax/fostr.sdf3 | 22 ++++++----- tests/basic.spt | 90 +++++++++++++++++++++++++++++++++++------- tests/emit_several.fos | 7 ++++ trans/analysis.str | 21 ++++++---- trans/desugar.str | 22 +++++++++++ trans/haskell.str | 17 +++----- trans/javascript.str | 17 ++++---- trans/python.str | 18 ++++----- 10 files changed, 156 insertions(+), 65 deletions(-) create mode 100644 tests/emit_several.fos create mode 100644 trans/desugar.str diff --git a/bin/extract_tests.xsh b/bin/extract_tests.xsh index 38edb7f..79b2ca8 100644 --- a/bin/extract_tests.xsh +++ b/bin/extract_tests.xsh @@ -23,8 +23,10 @@ for path in TEST_LIST: tests = re.split(r'test\s*(.+?)\s*\[\[.*?\n', contents)[1:] testit = iter(tests) for name, details in zip(testit, testit): - pfm = re.search(r'\n\s*\]\].*?parse\s*fails', details) - if pfm: continue # skip examples that don't parse + pfm = re.search(r'\n\s*\]\][\s\S]*?parse\s*fails', details) + if pfm: continue # skip examples that don't parse + ntfm = re.search(r'\n\s*\]\].*?don.t.test', details) + if ntfm: continue # explicit skip em = re.search(r'\n\s*\]\]', details) if not em: continue example = details[:em.start()+1] diff --git a/editor/Syntax.esv b/editor/Syntax.esv index aad195c..7b732a1 100644 --- a/editor/Syntax.esv +++ b/editor/Syntax.esv @@ -20,6 +20,7 @@ menus action: "Format" = editor-format (source) action: "Show parsed AST" = debug-show-aterm (source) + action: "Desugar AST" = debug-desugar-fostr (source) views diff --git a/syntax/fostr.sdf3 b/syntax/fostr.sdf3 index 317cd2d..4a39131 100644 --- a/syntax/fostr.sdf3 +++ b/syntax/fostr.sdf3 @@ -10,16 +10,24 @@ context-free start-symbols context-free sorts - Start LineSeq Line Ex + Start LineSeq Line OptTermEx TermExLst TermEx Ex context-free syntax Start.TopLevel = LineSeq - LineSeq = <> {layout(offside ln)} - LineSeq.Sequence = sq:Ex+ {layout(align-list sq)} + LineSeq = Line + LineSeq.Sequence = sq:Line+ {layout(align-list sq)} - Ex+ = Ex+ ln:Ex {layout(offside ln)} + Line = OptTermEx + Line.ISequence = TermExLst OptTermEx {layout(0.first.line == 1.first.line)} + + TermExLst.Prior = TermEx+ + + OptTermEx = ex:Ex {layout(offside ex)} + OptTermEx = te:TermEx {layout(offside te)} + + TermEx.Terminate = <;> Ex.Int = INT Ex.Stream = @@ -36,8 +44,4 @@ context-free priorities > Ex.Gets, // prevent cycle: no singletons - LineSeq.Sequence <0> .> Ex+ = Ex, - - // flat: No LineSeq immediately in LineSeq - Ex+ = Ex <0> .> LineSeq.Sequence, - Ex+ = Ex+ Ex <1> .> LineSeq.Sequence + LineSeq.Sequence <0> .> Line+ = Line diff --git a/tests/basic.spt b/tests/basic.spt index f13e67e..e3f4f69 100644 --- a/tests/basic.spt +++ b/tests/basic.spt @@ -14,8 +14,8 @@ that writes the sum of the ASCII codes for 'H', 'W', and '!' to standard output: /** md */ test emit_sum [[ stream << 72 + 87 + 33 -]]/* **/ parse to TopLevel(Gets(Stream(), - Sum(Sum(Int("72"), Int("87")), Int("33")))) +]] /* **/ +parse to TopLevel(Gets(Stream(), Sum(Sum(Int("72"), Int("87")), Int("33")))) /** writes 192**/ @@ -59,7 +59,8 @@ is also left-associative, so that way we can chain insertions into a stream: /** md */ test emit_twice [[ stream << 72 + 87 + 33 << 291 -]]/* **/ parse to TopLevel( +]] /* **/ +parse to TopLevel( Gets(Gets(Stream(), Sum(Sum(Int("72"), Int("87")), Int("33"))), Int("291"))) /** writes 192291**/ @@ -76,7 +77,8 @@ with `>>`; both forms return the first argument, so the following writes "824": /** md */ test enters_twice [[ (7 + 8 >> stream + 9) >> stream -]]/* **/ parse to TopLevel( +]] /* **/ +parse to TopLevel( To(Sum(Sum(Int("7"), To(Int("8"), Stream())), Int("9")), Stream())) /** writes 824**/ @@ -96,7 +98,8 @@ stream << 7 + 8 >> stream + 9 -]]/* **/ parse to TopLevel( +]] /* **/ +parse to TopLevel( Gets(Stream(), Sum(Sum(Int("7"), To(Int("8"), Stream())), Int("9")))) /** writes 824**/ @@ -107,15 +110,17 @@ stream << ```fostr **/ -/** md */ test enter_receive_bad_break [[ +/** md */ test enter_receive_bad_continuation [[ (7 + 8 >> stream + 9) >> (stream << 9 + 2) -]] /* **/ parse fails +]] /* **/ +parse fails /* Extra tests not in the tour */ test enter_receive [[ (7 + 8 >> stream + 9) >> (stream << 9 + 2) -]]/* **/ parse to TopLevel( +]] /* **/ +parse to TopLevel( To(Sum(Sum(Int("7"),To(Int("8"),Stream())),Int("9")), Gets(Stream(),Sum(Int("9"),Int("2"))))) /** writes @@ -135,18 +140,20 @@ lines are evaluated in sequence. For example, the program + 96 99 + 12 >> stream -]] /* **/ parse to TopLevel( Sequence( - [ Gets(Stream(), Sum(Int("72"), Int("87"))) - , Gets(Stream(), Sum(Int("88"), Int("96"))) - , Sum(Int("99"), To(Int("12"), Stream()))])) +]] /* **/ +parse to TopLevel(Sequence([ + Gets(Stream(), Sum(Int("72"), Int("87"))), + Gets(Stream(), Sum(Int("88"), Int("96"))), + Sum(Int("99"), To(Int("12"), Stream())) +])) /** writes 15918412**/ /** md ``` -will write 15918412. fostr enforces that successive expressions in sequence -must line up at the left, i.e., the following will not parse: +will write 15918412. The fostr parser enforces that successive expressions +in sequence align at the left; e.g., the following fails to parse: ```fostr **/ @@ -156,8 +163,61 @@ must line up at the left, i.e., the following will not parse: stream << 88 + 96 99 + 12 >> stream -]] /* **/ parse fails +]] /* **/ +parse fails + +/** md +``` + +Note you can optionally terminate an expression in a sequence with a semicolon, +and you may place multiple expressions on a single line if the earlier one(s) +are so terminated. So the following is OK: + +```fostr +**/ + +/** md */ test emit_several [[ + stream << 1 + 2; 3 >> stream + (4 + 5) >> stream; stream << 6; + stream << 7 + stream << 8 + + (9+10); + 11 + 12 >> stream; 13 >> stream + >> stream +]] /* **/ +parse to TopLevel(Sequence([ + ISequence(Prior([Terminate(Gets(Stream(), Sum(Int("1"), Int("2"))))]), + To(Int("3"), Stream())), + ISequence(Prior([Terminate(To(Sum(Int("4"), Int("5")), Stream()))]), + Terminate(Gets(Stream(), Int("6")))), + Gets(Stream(), Int("7")), + Terminate(Gets(Stream(), Sum(Int("8"), Sum(Int("9"), Int("10"))))), + ISequence(Prior([Terminate(Sum(Int("11"), To(Int("12"), Stream())))]), + To(To(Int("13"), Stream()), Stream())) +])) +/** writes +3396727121313**/ /** md ``` **/ + +test emit_several_desugar [[ + stream << 1 + 2; 3 >> stream + (4 + 5) >> stream; stream << 6; + stream << 7 + stream << 8 + + (9+10); + 11 + 12 >> stream; 13 >> stream + >> stream +]] /* don't test */ +run desugar-fostr to TopLevel(Sequence([ + Terminate(Gets(Stream(), Sum(Int("1"), Int("2")))), + To(Int("3"), Stream()), + Terminate(To(Sum(Int("4"), Int("5")), Stream())), + Terminate(Gets(Stream(), Int("6"))), + Gets(Stream(), Int("7")), + Terminate(Gets(Stream(), Sum(Int("8"), Sum(Int("9"), Int("10"))))), + Terminate(Sum(Int("11"), To(Int("12"), Stream()))), + To(To(Int("13"), Stream()), Stream()) +])) diff --git a/tests/emit_several.fos b/tests/emit_several.fos new file mode 100644 index 0000000..c806fd3 --- /dev/null +++ b/tests/emit_several.fos @@ -0,0 +1,7 @@ +stream << 1 + 2; 3 >> stream +(4 + 5) >> stream; stream << 6; +stream << 7 +stream << 8 + + (9+10); +11 + 12 >> stream; 13 >> stream + >> stream diff --git a/trans/analysis.str b/trans/analysis.str index 265f53e..70919cc 100644 --- a/trans/analysis.str +++ b/trans/analysis.str @@ -9,6 +9,7 @@ imports injections/- libspoofax/term/origin + desugar rules // Analysis @@ -19,7 +20,8 @@ rules // Analysis // multi-file analysis // editor-analyze = stx-editor-analyze(pre-analyze, post-analyze|"statics", "projectOk", "fileOk") - pre-analyze = origin-track-forced(explicate-injections-fostr-Start) + pre-analyze = desugar-fostr + ; origin-track-forced(explicate-injections-fostr-Start) post-analyze = origin-track-forced(implicate-injections-fostr-Start) rules // Editor Services @@ -31,16 +33,21 @@ rules // Editor Services rules // Debugging // Prints the abstract syntax ATerm of a selection. - debug-show-aterm: (selected, _, _, path, project-path) -> (filename, result) + debug-show-aterm: (sel, _, _, path, projp) -> (filename, result) with filename := path - ; result := selected + ; result := sel + + // Prints the desugared abstract syntax ATerm of a selection. + debug-desugar-fostr: (sel, _, _, path, projp) -> (filename, result) + with filename := path + ; result := sel // Prints the pre-analyzed abstract syntax ATerm of a selection. - debug-show-pre-analyzed: (selected, _, _, path, project-path) -> (filename, result) + debug-show-pre-analyzed: (sel, _, _, path, projp) -> (filename, result) with filename := path - ; result := selected + ; result := sel // Prints the analyzed annotated abstract syntax ATerm of a selection. - debug-show-analyzed: (selected, _, _, path, project-path) -> (filename, result) + debug-show-analyzed: (sel, _, _, path, projp) -> (filename, result) with filename := path - ; result := selected + ; result := sel diff --git a/trans/desugar.str b/trans/desugar.str new file mode 100644 index 0000000..e4f52cf --- /dev/null +++ b/trans/desugar.str @@ -0,0 +1,22 @@ +module desugar +imports libstrategolib signatures/- + +rules + /* ISequence() and Prior() are just noise for more expressions in sequence, + put in to get the layout rules right. So we remove them and collapse + all occurrence of them into one big Sequence() call on a list. + + This is slightly tricky because there might not be any Sequence() call + at the top level, but yet an ISequence(). So we do it in two passes, + first converting ISequence()s to Sequence()s, and then collapsing + Sequence()s. + */ + deISe: ISequence(Prior(l),x) -> Sequence((l, [x])) + + enList: x -> [x] + seqFlatten: Sequence(l) -> Sequence() <+ enList)>l) + +strategies + + desugar-fostr = bottomup(try(deISe <+ seqFlatten)) + \ No newline at end of file diff --git a/trans/haskell.str b/trans/haskell.str index ddddbca..eae335e 100644 --- a/trans/haskell.str +++ b/trans/haskell.str @@ -1,10 +1,5 @@ module haskell imports libstrategolib signatures/- util - -signature - constructors - TopLevel: Ex -> Ex - rules /* Approach: Generate code from the bottom up. At every node, we create a pair of the implementation and @@ -25,20 +20,20 @@ rules main = do [p]return [c]] - hs: Stream() -> ("StdIO", "") - hs: Int(x) -> (x, "") + hs: Stream() -> ("StdIO", "") + hs: Int(x) -> (x, "") hs: Sum( (c, p), (d, q)) -> ($[([c] + [d])], (p,q)) - hs: Gets((c, p), (d, q)) -> (c,d,(p,q),"fosgt") + hs: Gets((c, p), (d, q)) -> (c,d,(p,q),"fosgt") hsget: (s, x, p, v) -> (v, [p, $[[v] <- [s] `gets` [x]], "\n"]) hs: To( (c, p), (d, q)) -> (c,d,(p,q),"fosto") - hsto: (x, s, p, v) -> (v, [p, $[let [v] = [x]], "\n", - $[[s] `gets` [v]], "\n"]) + $[[s] `gets` [v]], "\n"]) - hs: Sequence(l) -> (l, l) + hs: Terminate((c,p)) -> ($[[c];;], p) + hs: Sequence(l) -> (l, l) strategies diff --git a/trans/javascript.str b/trans/javascript.str index 5594cda..605b46a 100644 --- a/trans/javascript.str +++ b/trans/javascript.str @@ -1,10 +1,6 @@ module javascript imports libstrategolib signatures/- util -signature - constructors - TopLevel: Ex -> Ex - rules js: TopLevel(x) -> $[const Stdio = { gets: v => { process.stdout.write(String(v)); return Stdio; }, @@ -15,12 +11,13 @@ rules } [x]] - js: Stream() -> $[Stdio] - js: Int(x) -> x - js: Sum(x,y) -> $[[x] + [y]] - js: Gets(x, y) -> $[[x].gets([y])] - js: To(x, y) -> $[to([x],[y])] - js: Sequence(l) -> l + js: Stream() -> $[Stdio] + js: Int(x) -> x + js: Sum(x,y) -> $[[x] + [y]] + js: Gets(x, y) -> $[[x].gets([y])] + js: To(x, y) -> $[to([x],[y])] + js: Terminate(x) -> x + js: Sequence(l) -> l strategies diff --git a/trans/python.str b/trans/python.str index 4864ec2..238c006 100644 --- a/trans/python.str +++ b/trans/python.str @@ -1,10 +1,5 @@ module python imports libstrategolib signatures/- util - -signature - constructors - TopLevel: Ex -> Ex - rules py: TopLevel(x) -> $[import sys @@ -18,12 +13,13 @@ rules Stdio = StdioC() [x]] - py: Stream() -> $[Stdio] - py: Int(x) -> x - py: Sum(x,y) -> $[[x] + [y]] - py: Gets(x, y) -> $[[x].gets([y])] - py: To(x, y) -> $[to([x],[y])] - py: Sequence(l) -> l + py: Stream() -> $[Stdio] + py: Int(x) -> x + py: Sum(x,y) -> $[[x] + [y]] + py: Gets(x, y) -> $[[x].gets([y])] + py: To(x, y) -> $[to([x],[y])] + py: Terminate(x) -> $[[x];] + py: Sequence(l) -> l strategies