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.
This commit is contained in:
Glen Whitney 2021-02-10 12:47:34 -08:00
parent 991976d3a8
commit b9c8532899
10 changed files with 156 additions and 65 deletions

View File

@ -23,8 +23,10 @@ for path in TEST_LIST:
tests = re.split(r'test\s*(.+?)\s*\[\[.*?\n', contents)[1:] tests = re.split(r'test\s*(.+?)\s*\[\[.*?\n', contents)[1:]
testit = iter(tests) testit = iter(tests)
for name, details in zip(testit, testit): for name, details in zip(testit, testit):
pfm = re.search(r'\n\s*\]\].*?parse\s*fails', details) pfm = re.search(r'\n\s*\]\][\s\S]*?parse\s*fails', details)
if pfm: continue # skip examples that don't parse 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) em = re.search(r'\n\s*\]\]', details)
if not em: continue if not em: continue
example = details[:em.start()+1] example = details[:em.start()+1]

View File

@ -20,6 +20,7 @@ menus
action: "Format" = editor-format (source) action: "Format" = editor-format (source)
action: "Show parsed AST" = debug-show-aterm (source) action: "Show parsed AST" = debug-show-aterm (source)
action: "Desugar AST" = debug-desugar-fostr (source)
views views

View File

@ -10,16 +10,24 @@ context-free start-symbols
context-free sorts context-free sorts
Start LineSeq Line Ex Start LineSeq Line OptTermEx TermExLst TermEx Ex
context-free syntax context-free syntax
Start.TopLevel = LineSeq Start.TopLevel = LineSeq
LineSeq = <<ln:Ex>> {layout(offside ln)} LineSeq = Line
LineSeq.Sequence = sq:Ex+ {layout(align-list sq)} 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>;>
Ex.Int = INT Ex.Int = INT
Ex.Stream = <stream> Ex.Stream = <stream>
@ -36,8 +44,4 @@ context-free priorities
> Ex.Gets, > Ex.Gets,
// prevent cycle: no singletons // prevent cycle: no singletons
LineSeq.Sequence <0> .> Ex+ = Ex, LineSeq.Sequence <0> .> Line+ = Line
// flat: No LineSeq immediately in LineSeq
Ex+ = Ex <0> .> LineSeq.Sequence,
Ex+ = Ex+ Ex <1> .> LineSeq.Sequence

View File

@ -14,8 +14,8 @@ that writes the sum of the ASCII codes for 'H', 'W', and '!' to standard output:
/** md */ test emit_sum [[ /** md */ test emit_sum [[
stream << 72 + 87 + 33 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 /** writes
192**/ 192**/
@ -59,7 +59,8 @@ is also left-associative, so that way we can chain insertions into a stream:
/** md */ test emit_twice [[ /** md */ test emit_twice [[
stream << 72 + 87 + 33 << 291 stream << 72 + 87 + 33 << 291
]]/* **/ parse to TopLevel( ]] /* **/
parse to TopLevel(
Gets(Gets(Stream(), Sum(Sum(Int("72"), Int("87")), Int("33"))), Int("291"))) Gets(Gets(Stream(), Sum(Sum(Int("72"), Int("87")), Int("33"))), Int("291")))
/** writes /** writes
192291**/ 192291**/
@ -76,7 +77,8 @@ with `>>`; both forms return the first argument, so the following writes "824":
/** md */ test enters_twice [[ /** md */ test enters_twice [[
(7 + 8 >> stream + 9) >> stream (7 + 8 >> stream + 9) >> stream
]]/* **/ parse to TopLevel( ]] /* **/
parse to TopLevel(
To(Sum(Sum(Int("7"), To(Int("8"), Stream())), Int("9")), Stream())) To(Sum(Sum(Int("7"), To(Int("8"), Stream())), Int("9")), Stream()))
/** writes /** writes
824**/ 824**/
@ -96,7 +98,8 @@ stream <<
7 7
+ 8 >> stream + 8 >> stream
+ 9 + 9
]]/* **/ parse to TopLevel( ]] /* **/
parse to TopLevel(
Gets(Stream(), Sum(Sum(Int("7"), To(Int("8"), Stream())), Int("9")))) Gets(Stream(), Sum(Sum(Int("7"), To(Int("8"), Stream())), Int("9"))))
/** writes /** writes
824**/ 824**/
@ -107,15 +110,17 @@ stream <<
```fostr ```fostr
**/ **/
/** md */ test enter_receive_bad_break [[ /** md */ test enter_receive_bad_continuation [[
(7 + 8 >> stream + 9) (7 + 8 >> stream + 9)
>> (stream << 9 + 2) >> (stream << 9 + 2)
]] /* **/ parse fails ]] /* **/
parse fails
/* Extra tests not in the tour */ /* Extra tests not in the tour */
test enter_receive [[ test enter_receive [[
(7 + 8 >> stream + 9) >> (stream << 9 + 2) (7 + 8 >> stream + 9) >> (stream << 9 + 2)
]]/* **/ parse to TopLevel( ]] /* **/
parse to TopLevel(
To(Sum(Sum(Int("7"),To(Int("8"),Stream())),Int("9")), To(Sum(Sum(Int("7"),To(Int("8"),Stream())),Int("9")),
Gets(Stream(),Sum(Int("9"),Int("2"))))) Gets(Stream(),Sum(Int("9"),Int("2")))))
/** writes /** writes
@ -135,18 +140,20 @@ lines are evaluated in sequence. For example, the program
+ 96 + 96
99 + 12 >> 99 + 12 >>
stream stream
]] /* **/ parse to TopLevel( Sequence( ]] /* **/
[ Gets(Stream(), Sum(Int("72"), Int("87"))) parse to TopLevel(Sequence([
, Gets(Stream(), Sum(Int("88"), Int("96"))) Gets(Stream(), Sum(Int("72"), Int("87"))),
, Sum(Int("99"), To(Int("12"), Stream()))])) Gets(Stream(), Sum(Int("88"), Int("96"))),
Sum(Int("99"), To(Int("12"), Stream()))
]))
/** writes /** writes
15918412**/ 15918412**/
/** md /** md
``` ```
will write 15918412. fostr enforces that successive expressions in sequence will write 15918412. The fostr parser enforces that successive expressions
must line up at the left, i.e., the following will not parse: in sequence align at the left; e.g., the following fails to parse:
```fostr ```fostr
**/ **/
@ -156,8 +163,61 @@ must line up at the left, i.e., the following will not parse:
stream << 88 stream << 88
+ 96 + 96
99 + 12 >> stream 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 /** 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())
]))

7
tests/emit_several.fos Normal file
View File

@ -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

View File

@ -9,6 +9,7 @@ imports
injections/- injections/-
libspoofax/term/origin libspoofax/term/origin
desugar
rules // Analysis rules // Analysis
@ -19,7 +20,8 @@ rules // Analysis
// multi-file analysis // multi-file analysis
// editor-analyze = stx-editor-analyze(pre-analyze, post-analyze|"statics", "projectOk", "fileOk") // 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) post-analyze = origin-track-forced(implicate-injections-fostr-Start)
rules // Editor Services rules // Editor Services
@ -31,16 +33,21 @@ rules // Editor Services
rules // Debugging rules // Debugging
// Prints the abstract syntax ATerm of a selection. // 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 := <guarantee-extension(|"aterm")> path with filename := <guarantee-extension(|"aterm")> path
; result := selected ; result := sel
// Prints the desugared abstract syntax ATerm of a selection.
debug-desugar-fostr: (sel, _, _, path, projp) -> (filename, result)
with filename := <guarantee-extension(|"desugared.aterm")> path
; result := <desugar-fostr> sel
// Prints the pre-analyzed abstract syntax ATerm of a selection. // 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 := <guarantee-extension(|"pre-analyzed.aterm")> path with filename := <guarantee-extension(|"pre-analyzed.aterm")> path
; result := <pre-analyze> selected ; result := <pre-analyze> sel
// Prints the analyzed annotated abstract syntax ATerm of a selection. // 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 := <guarantee-extension(|"analyzed.aterm")> path with filename := <guarantee-extension(|"analyzed.aterm")> path
; result := selected ; result := sel

22
trans/desugar.str Normal file
View File

@ -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(<conc>(l, [x]))
enList: x -> [x]
seqFlatten: Sequence(l) -> Sequence(<mapconcat(?Sequence(<id>) <+ enList)>l)
strategies
desugar-fostr = bottomup(try(deISe <+ seqFlatten))

View File

@ -1,10 +1,5 @@
module haskell module haskell
imports libstrategolib signatures/- util imports libstrategolib signatures/- util
signature
constructors
TopLevel: Ex -> Ex
rules rules
/* Approach: Generate code from the bottom up. /* Approach: Generate code from the bottom up.
At every node, we create a pair of the implementation and At every node, we create a pair of the implementation and
@ -28,16 +23,16 @@ rules
hs: Stream() -> ("StdIO", "") hs: Stream() -> ("StdIO", "")
hs: Int(x) -> (x, "") hs: Int(x) -> (x, "")
hs: Sum( (c, p), (d, q)) -> ($[([c] + [d])], <conc-strings>(p,q)) hs: Sum( (c, p), (d, q)) -> ($[([c] + [d])], <conc-strings>(p,q))
hs: Gets((c, p), (d, q)) -> <hsget>(c,d,<conc-strings>(p,q),<newname>"fosgt")
hs: Gets((c, p), (d, q)) -> <hsget>(c,d,<conc-strings>(p,q),<newname>"fosgt")
hsget: (s, x, p, v) -> (v, <concat-strings>[p, $[[v] <- [s] `gets` [x]], hsget: (s, x, p, v) -> (v, <concat-strings>[p, $[[v] <- [s] `gets` [x]],
"\n"]) "\n"])
hs: To( (c, p), (d, q)) -> <hsto>(c,d,<conc-strings>(p,q),<newname>"fosto") hs: To( (c, p), (d, q)) -> <hsto>(c,d,<conc-strings>(p,q),<newname>"fosto")
hsto: (x, s, p, v) -> (v, <concat-strings>[p, $[let [v] = [x]], "\n", hsto: (x, s, p, v) -> (v, <concat-strings>[p, $[let [v] = [x]], "\n",
$[[s] `gets` [v]], "\n"]) $[[s] `gets` [v]], "\n"])
hs: Terminate((c,p)) -> ($[[c];;], p)
hs: Sequence(l) -> (<last; Fst>l, <map(Snd); concat-strings>l) hs: Sequence(l) -> (<last; Fst>l, <map(Snd); concat-strings>l)
strategies strategies

View File

@ -1,10 +1,6 @@
module javascript module javascript
imports libstrategolib signatures/- util imports libstrategolib signatures/- util
signature
constructors
TopLevel: Ex -> Ex
rules rules
js: TopLevel(x) -> $[const Stdio = { js: TopLevel(x) -> $[const Stdio = {
gets: v => { process.stdout.write(String(v)); return Stdio; }, gets: v => { process.stdout.write(String(v)); return Stdio; },
@ -20,6 +16,7 @@ rules
js: Sum(x,y) -> $[[x] + [y]] js: Sum(x,y) -> $[[x] + [y]]
js: Gets(x, y) -> $[[x].gets([y])] js: Gets(x, y) -> $[[x].gets([y])]
js: To(x, y) -> $[to([x],[y])] js: To(x, y) -> $[to([x],[y])]
js: Terminate(x) -> x
js: Sequence(l) -> <join(|";\n")>l js: Sequence(l) -> <join(|";\n")>l
strategies strategies

View File

@ -1,10 +1,5 @@
module python module python
imports libstrategolib signatures/- util imports libstrategolib signatures/- util
signature
constructors
TopLevel: Ex -> Ex
rules rules
py: TopLevel(x) -> $[import sys py: TopLevel(x) -> $[import sys
@ -23,6 +18,7 @@ rules
py: Sum(x,y) -> $[[x] + [y]] py: Sum(x,y) -> $[[x] + [y]]
py: Gets(x, y) -> $[[x].gets([y])] py: Gets(x, y) -> $[[x].gets([y])]
py: To(x, y) -> $[to([x],[y])] py: To(x, y) -> $[to([x],[y])]
py: Terminate(x) -> $[[x];]
py: Sequence(l) -> <join(|"\n")>l py: Sequence(l) -> <join(|"\n")>l
strategies strategies