From 991976d3a8c490e281373ba9e2a292f551f46c88 Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Sat, 6 Feb 2021 05:11:41 +0000 Subject: [PATCH] feat: sequencing of expressions with newline to same indent (#11) feat: sequencing of expressions with newline to same indent Also revised README to reflect greater emphasis on streams. Haskell code generation unsurprisingly required a fairly significant rework. Resolves #3. Co-authored-by: Glen Whitney Reviewed-on: https://code.studioinfinity.org/glen/fostr/pulls/11 Co-Authored-By: Glen Whitney Co-Committed-By: Glen Whitney --- README.md | 17 ++++---- metaborg.yaml | 1 + syntax/fostr.sdf3 | 30 +++++++++------ tests/basic.spt | 90 +++++++++++++++++++++++++++++-------------- tests/emit_sum.fos | 2 +- tests/emit_thrice.fos | 5 +++ trans/haskell.str | 31 ++++++++------- trans/javascript.str | 19 +++++---- trans/python.str | 21 +++++----- trans/util.str | 4 +- 10 files changed, 131 insertions(+), 89 deletions(-) create mode 100644 tests/emit_thrice.fos diff --git a/README.md b/README.md index 010be66..91e1605 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,12 @@ dimensions. So I embarked on this project to see if I could produce as comfortable a language as possible to work in, given that I inevitably will be doing a -bunch of coding. The language will be -organized around (unary) ++f++unctions, (binary) ++o++perators, and -(nullary) ++str++eams, hence the name "fostr". +bunch of coding. The language will be centrally organized around the +concept of "streams" (somewhat in the spirit of +[streem](https://github.com/matz/streem) and/or +[Orc](http://orc.csres.utexas.edu/index.shtml)). In fact all higher-type +entities will be cast in terms of streams, or in slogan form, "++f++unctions +and (binary) ++o++perators are ++str++eams" (hence the name "fostr"). Other guiding principles: @@ -24,11 +27,9 @@ Other guiding principles: the language design from the ground up, it can be kept both effective and natural. -* Code uses functions all the time. So needless to say, functions should be - first-class entities that are exceptionally easy to create, pass around, - etc. - -* And true to the name, operators and streams should be just as easy to handle. +* fostr code uses streams (and their specializations to functions and + operators) all the time, so they are first-class entities that are easy + to create, pass around, compose, etc. * Try to keep the constructs available as simple to reason about as possible, and practical to use. So side effects are OK, and it should be clear when diff --git a/metaborg.yaml b/metaborg.yaml index b1f5407..0b9c842 100644 --- a/metaborg.yaml +++ b/metaborg.yaml @@ -19,6 +19,7 @@ language: sdf: pretty-print: fostr sdf2table: java + jsglr-version: layout-sensitive placeholder: prefix: "$" stratego: diff --git a/syntax/fostr.sdf3 b/syntax/fostr.sdf3 index aa1b048..317cd2d 100644 --- a/syntax/fostr.sdf3 +++ b/syntax/fostr.sdf3 @@ -10,28 +10,34 @@ context-free start-symbols context-free sorts - Start Ex + Start LineSeq Line Ex context-free syntax - Start.TopLevel = prog:Ex {layout(offside prog)} + Start.TopLevel = LineSeq + + LineSeq = <> {layout(offside ln)} + LineSeq.Sequence = sq:Ex+ {layout(align-list sq)} + + Ex+ = Ex+ ln:Ex {layout(offside ln)} Ex.Int = INT - Ex.Stdio = - Ex.Sum = {Ex "+"}+ - Ex.Receives = [[Ex] << [Ex]] {left} - Ex.Enters = [[Ex] >> [Ex]] {left} + Ex.Stream = + Ex.Sum = [[Ex] + [Ex]] {left} + Ex.Gets = [[Ex] << [Ex]] {left} + Ex.To = [[Ex] >> [Ex]] {left} + Ex = <()> {bracket} context-free priorities - Ex.Enters + Ex.To > Ex.Sum - > Ex.Receives, + > Ex.Gets, // prevent cycle: no singletons - Ex.Sum <0> .> {Ex "+"}+ = Ex, + LineSeq.Sequence <0> .> Ex+ = Ex, - // flat: no Sum immediately in Sum: - {Ex "+"}+ = Ex <0> .> Ex.Sum, - {Ex "+"}+ = {Ex "+"}+ "+" Ex <2> .> Ex.Sum + // flat: No LineSeq immediately in LineSeq + Ex+ = Ex <0> .> LineSeq.Sequence, + Ex+ = Ex+ Ex <1> .> LineSeq.Sequence diff --git a/tests/basic.spt b/tests/basic.spt index 24b9b54..f13e67e 100644 --- a/tests/basic.spt +++ b/tests/basic.spt @@ -13,9 +13,9 @@ that writes the sum of the ASCII codes for 'H', 'W', and '!' to standard output: **/ /** md */ test emit_sum [[ -stdio << 72 + 87 + 33 -]]/* **/ parse to TopLevel(Receives(Stdio(), - Sum([Int("72"), Int("87"), Int("33")]))) +stream << 72 + 87 + 33 +]]/* **/ parse to TopLevel(Gets(Stream(), + Sum(Sum(Int("72"), Int("87")), Int("33")))) /** writes 192**/ @@ -40,32 +40,27 @@ For example, this snippet generates the following Python: start: 'Stdio\s=' !} ``` -(which writes "192" to standard output), or this non-idiomatic, inefficient, but -working Javascript: -```javascript -{! ../tests/emit_sum.js extract: - start: '^}' -!} -``` -In either case, there's also a preamble defining Stdio that's generated. -(Haskell code generation is also currently supported.) +(which writes "192" to standard output); it also generates identical code in +this simple example for +Javascript, although it generates a different preamble defining Stdio in each +case. (Haskell code generation is also currently supported.) ### Everything has a value As mentioned in the [Introduction](../README.md), everything in a fostr program (including the entire program itself) is an expression and has -a value. So what's the value of that expression above? Well, `stdio` is our +a value. So what's the value of that expression above? Well, appropriately +enough, `stream` is our first example of a stream, and for convenience, the value of a stream -receiving an item is just the stream back again. The `<<` operator is also -left-associative, so that way we can chain insertions into a stream: +receiving an item is (usually) just the stream back again. The `<<` operator +is also left-associative, so that way we can chain insertions into a stream: ```fostr **/ /** md */ test emit_twice [[ -stdio << 72 + 87 + 33 << 291 +stream << 72 + 87 + 33 << 291 ]]/* **/ parse to TopLevel( - Receives(Receives(Stdio(), Sum([Int("72"), Int("87"), Int("33")])), - Int("291"))) + Gets(Gets(Stream(), Sum(Sum(Int("72"), Int("87")), Int("33"))), Int("291"))) /** writes 192291**/ @@ -75,14 +70,14 @@ Running this program produces a nice palindromic output: "192291". And because sometimes you want to emphasize the value and propagate that instead of the stream, you can also write these expressions "the other way" -with `>>`; both forms return the first argument: +with `>>`; both forms return the first argument, so the following writes "824": ```fostr **/ /** md */ test enters_twice [[ -(7 + 8 >> stdio + 9) >> stdio +(7 + 8 >> stream + 9) >> stream ]]/* **/ parse to TopLevel( - Enters(Sum([Int("7"), Enters(Int("8"), Stdio()), Int("9")]), Stdio())) + To(Sum(Sum(Int("7"), To(Int("8"), Stream())), Int("9")), Stream())) /** writes 824**/ @@ -97,12 +92,12 @@ lines are indented from the start of the initial line: **/ /** md */ test receive_enter_break [[ -stdio << +stream << 7 - + 8 >> stdio + + 8 >> stream + 9 ]]/* **/ parse to TopLevel( - Receives(Stdio(), Sum([Int("7"), Enters(Int("8"), Stdio()), Int("9")]))) + Gets(Stream(), Sum(Sum(Int("7"), To(Int("8"), Stream())), Int("9")))) /** writes 824**/ @@ -113,19 +108,56 @@ stdio << **/ /** md */ test enter_receive_bad_break [[ -(7 + 8 >> stdio + 9) ->> (stdio << 9 + 2) +(7 + 8 >> stream + 9) +>> (stream << 9 + 2) ]] /* **/ parse fails /* Extra tests not in the tour */ test enter_receive [[ -(7 + 8 >> stdio + 9) >> (stdio << 9 + 2) +(7 + 8 >> stream + 9) >> (stream << 9 + 2) ]]/* **/ parse to TopLevel( - Enters(Sum([Int("7"),Enters(Int("8"),Stdio()),Int("9")]), - Receives(Stdio(),Sum([Int("9"),Int("2")])))) + To(Sum(Sum(Int("7"),To(Int("8"),Stream())),Int("9")), + Gets(Stream(),Sum(Int("9"),Int("2"))))) /** writes 81124**/ +/** md +``` + +Of course, fostr programs are not limited to one line; expressions on successive +lines are evaluated in sequence. For example, the program +```fostr +**/ + +/** md */ test emit_thrice [[ + stream << 72 + 87 + stream << 88 + + 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()))])) +/** 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: + +```fostr +**/ + +/** md */ test emit_thrice_bad_alignment [[ + stream << 72 + 87 +stream << 88 + + 96 + 99 + 12 >> stream +]] /* **/ parse fails + /** md ``` **/ diff --git a/tests/emit_sum.fos b/tests/emit_sum.fos index 8dfe808..8826edb 100644 --- a/tests/emit_sum.fos +++ b/tests/emit_sum.fos @@ -1 +1 @@ -stdio << 72 + 87 + 33 +stream << 72 + 87 + 33 diff --git a/tests/emit_thrice.fos b/tests/emit_thrice.fos new file mode 100644 index 0000000..6042aad --- /dev/null +++ b/tests/emit_thrice.fos @@ -0,0 +1,5 @@ + stream << 72 + 87 + stream << 88 + + 96 + 99 + 12 >> + stream diff --git a/trans/haskell.str b/trans/haskell.str index f5fc7e7..ddddbca 100644 --- a/trans/haskell.str +++ b/trans/haskell.str @@ -17,33 +17,32 @@ rules hs: TopLevel((c,p)) -> $[import System.IO data IOStream = StdIO - stdio :: IO IOStream - stdio = return StdIO - - receives :: Show b => IO a -> b -> IO a - receives s d = do - temp <- s + gets :: Show b => a -> b -> IO a + gets s d = do putStr(show d) - return temp + return s main = do [p]return [c]] - hs: Stdio() -> ("stdio", "") + hs: Stream() -> ("StdIO", "") hs: Int(x) -> (x, "") - hs: Sum((c,p)) -> ($[sum [c]], p) - hs: Receives((c, p), (d, s)) -> ($[[c] `receives` [d]], (p,s)) - hs: Enters((c, p), (d, s)) -> (c,d,(p,s),"fos") + hs: Sum( (c, p), (d, q)) -> ($[([c] + [d])], (p,q)) + hs: Gets((c, p), (d, q)) -> (c,d,(p,q),"fosgt") - hsenter: (x, s, p, v) -> (v, [$[[p]let [v] = [x]], "\n", - $[[s] `receives` [v]], "\n"]) + hsget: (s, x, p, v) -> (v, [p, $[[v] <- [s] `gets` [x]], + "\n"]) - hslist: x -> (x, x) - brack: x -> $<[]> + 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"]) + + hs: Sequence(l) -> (l, l) strategies - haskell = bottomup(try(hs <+ hslist)) + haskell = bottomup(try(hs)) // Interface haskell code generation with editor services and file system to-haskell: (selected, _, _, path, project-path) -> (filename, result) diff --git a/trans/javascript.str b/trans/javascript.str index fe88092..5594cda 100644 --- a/trans/javascript.str +++ b/trans/javascript.str @@ -7,25 +7,24 @@ signature rules js: TopLevel(x) -> $[const Stdio = { - receives: v => { process.stdout.write(String(v)); return Stdio; }, + gets: v => { process.stdout.write(String(v)); return Stdio; }, } - function forwards(data, strm) { - strm.receives(data); + function to(data, strm) { + strm.gets(data); return data; } [x]] - js: Stdio() -> $[Stdio] + js: Stream() -> $[Stdio] js: Int(x) -> x - js: Sum(x) -> $[[x].reduce((v,w) => v+w)] - js: Receives(x, y) -> $[[x].receives([y])] - js: Enters(x, y) -> $[forwards([x],[y])] - - jslist: 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 strategies - javascript = bottomup(try(js <+ jslist)) + javascript = bottomup(try(js)) // Interface javascript code generation with editor services and file system to-javascript: (selected, _, _, path, project-path) -> (filename, result) diff --git a/trans/python.str b/trans/python.str index f9f1972..4864ec2 100644 --- a/trans/python.str +++ b/trans/python.str @@ -9,26 +9,25 @@ rules py: TopLevel(x) -> $[import sys class StdioC: - def receives(self, v): + def gets(self, v): print(v, file=sys.stdout, end='') return self - def forwards(data,strm): - strm.receives(data) + def to(data,strm): + strm.gets(data) return data Stdio = StdioC() [x]] - py: Stdio() -> $[Stdio] - py: Int(x) -> x - py: Sum(x) -> $[sum([x])] - py: Receives(x, y) -> $[[x].receives([y])] - py: Enters(x, y) -> $[forwards([x],[y])] - - pylist: x -> $<[<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 strategies - python = bottomup(try(py <+ pylist)) + python = bottomup(try(py)) // Interface python code generation with editor services and file system to-python: (selected, _, _, path, project-path) -> (filename, result) diff --git a/trans/util.str b/trans/util.str index eee9663..78b4ab8 100644 --- a/trans/util.str +++ b/trans/util.str @@ -3,7 +3,7 @@ imports libstrategolib rules join(|infix) : [] -> "" - join(|infix) : [x | xs] -> $[[x][xs]] + join(|infix) : [x | xs] -> (x, xs) prejoin(|infix) : [] -> "" - prejoin(|infix) : [x | xs] -> $[[infix][x][xs]] + prejoin(|infix) : [x | xs] -> [infix,x,xs]