feat: sequencing of expressions with newline to same indent (#11)
All checks were successful
continuous-integration/drone/push Build is passing

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 <glen@studioinfinity.org>
Reviewed-on: #11
Co-Authored-By: Glen Whitney <glen@nobody@nowhere.net>
Co-Committed-By: Glen Whitney <glen@nobody@nowhere.net>
This commit is contained in:
Glen Whitney 2021-02-06 05:11:41 +00:00
parent c4d3f66c51
commit 991976d3a8
10 changed files with 131 additions and 89 deletions

View File

@ -8,9 +8,12 @@ dimensions.
So I embarked on this project to see if I could produce as comfortable a 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 language as possible to work in, given that I inevitably will be doing a
bunch of coding. The language will be bunch of coding. The language will be centrally organized around the
organized around (unary) ++f++unctions, (binary) ++o++perators, and concept of "streams" (somewhat in the spirit of
(nullary) ++str++eams, hence the name "fostr". [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: Other guiding principles:
@ -24,11 +27,9 @@ Other guiding principles:
the language design from the ground up, it can be kept both effective and the language design from the ground up, it can be kept both effective and
natural. natural.
* Code uses functions all the time. So needless to say, functions should be * fostr code uses streams (and their specializations to functions and
first-class entities that are exceptionally easy to create, pass around, operators) all the time, so they are first-class entities that are easy
etc. to create, pass around, compose, etc.
* And true to the name, operators and streams should be just as easy to handle.
* Try to keep the constructs available as simple to reason about as possible, * 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 and practical to use. So side effects are OK, and it should be clear when

View File

@ -19,6 +19,7 @@ language:
sdf: sdf:
pretty-print: fostr pretty-print: fostr
sdf2table: java sdf2table: java
jsglr-version: layout-sensitive
placeholder: placeholder:
prefix: "$" prefix: "$"
stratego: stratego:

View File

@ -10,28 +10,34 @@ context-free start-symbols
context-free sorts context-free sorts
Start Ex Start LineSeq Line Ex
context-free syntax context-free syntax
Start.TopLevel = prog:Ex {layout(offside prog)} Start.TopLevel = LineSeq
LineSeq = <<ln:Ex>> {layout(offside ln)}
LineSeq.Sequence = sq:Ex+ {layout(align-list sq)}
Ex+ = Ex+ ln:Ex {layout(offside ln)}
Ex.Int = INT Ex.Int = INT
Ex.Stdio = <stdio> Ex.Stream = <stream>
Ex.Sum = {Ex "+"}+ Ex.Sum = [[Ex] + [Ex]] {left}
Ex.Receives = [[Ex] << [Ex]] {left} Ex.Gets = [[Ex] << [Ex]] {left}
Ex.Enters = [[Ex] >> [Ex]] {left} Ex.To = [[Ex] >> [Ex]] {left}
Ex = <(<Ex>)> {bracket} Ex = <(<Ex>)> {bracket}
context-free priorities context-free priorities
Ex.Enters Ex.To
> Ex.Sum > Ex.Sum
> Ex.Receives, > Ex.Gets,
// prevent cycle: no singletons // prevent cycle: no singletons
Ex.Sum <0> .> {Ex "+"}+ = Ex, LineSeq.Sequence <0> .> Ex+ = Ex,
// flat: no Sum immediately in Sum: // flat: No LineSeq immediately in LineSeq
{Ex "+"}+ = Ex <0> .> Ex.Sum, Ex+ = Ex <0> .> LineSeq.Sequence,
{Ex "+"}+ = {Ex "+"}+ "+" Ex <2> .> Ex.Sum Ex+ = Ex+ Ex <1> .> LineSeq.Sequence

View File

@ -13,9 +13,9 @@ that writes the sum of the ASCII codes for 'H', 'W', and '!' to standard output:
**/ **/
/** md */ test emit_sum [[ /** md */ test emit_sum [[
stdio << 72 + 87 + 33 stream << 72 + 87 + 33
]]/* **/ parse to TopLevel(Receives(Stdio(), ]]/* **/ parse to TopLevel(Gets(Stream(),
Sum([Int("72"), Int("87"), Int("33")]))) Sum(Sum(Int("72"), Int("87")), Int("33"))))
/** writes /** writes
192**/ 192**/
@ -40,32 +40,27 @@ For example, this snippet generates the following Python:
start: 'Stdio\s=' start: 'Stdio\s='
!} !}
``` ```
(which writes "192" to standard output), or this non-idiomatic, inefficient, but (which writes "192" to standard output); it also generates identical code in
working Javascript: this simple example for
```javascript Javascript, although it generates a different preamble defining Stdio in each
{! ../tests/emit_sum.js extract: case. (Haskell code generation is also currently supported.)
start: '^}'
!}
```
In either case, there's also a preamble defining Stdio that's generated.
(Haskell code generation is also currently supported.)
### Everything has a value ### Everything has a value
As mentioned in the [Introduction](../README.md), everything in a fostr As mentioned in the [Introduction](../README.md), everything in a fostr
program (including the entire program itself) is an expression and has 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 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 receiving an item is (usually) just the stream back again. The `<<` operator
left-associative, so that way we can chain insertions into a stream: is also left-associative, so that way we can chain insertions into a stream:
```fostr ```fostr
**/ **/
/** md */ test emit_twice [[ /** md */ test emit_twice [[
stdio << 72 + 87 + 33 << 291 stream << 72 + 87 + 33 << 291
]]/* **/ parse to TopLevel( ]]/* **/ parse to TopLevel(
Receives(Receives(Stdio(), Sum([Int("72"), Int("87"), Int("33")])), Gets(Gets(Stream(), Sum(Sum(Int("72"), Int("87")), Int("33"))), Int("291")))
Int("291")))
/** writes /** writes
192291**/ 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 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" 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 ```fostr
**/ **/
/** md */ test enters_twice [[ /** md */ test enters_twice [[
(7 + 8 >> stdio + 9) >> stdio (7 + 8 >> stream + 9) >> stream
]]/* **/ parse to TopLevel( ]]/* **/ 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 /** writes
824**/ 824**/
@ -97,12 +92,12 @@ lines are indented from the start of the initial line:
**/ **/
/** md */ test receive_enter_break [[ /** md */ test receive_enter_break [[
stdio << stream <<
7 7
+ 8 >> stdio + 8 >> stream
+ 9 + 9
]]/* **/ parse to TopLevel( ]]/* **/ 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 /** writes
824**/ 824**/
@ -113,19 +108,56 @@ stdio <<
**/ **/
/** md */ test enter_receive_bad_break [[ /** md */ test enter_receive_bad_break [[
(7 + 8 >> stdio + 9) (7 + 8 >> stream + 9)
>> (stdio << 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 >> stdio + 9) >> (stdio << 9 + 2) (7 + 8 >> stream + 9) >> (stream << 9 + 2)
]]/* **/ parse to TopLevel( ]]/* **/ parse to TopLevel(
Enters(Sum([Int("7"),Enters(Int("8"),Stdio()),Int("9")]), To(Sum(Sum(Int("7"),To(Int("8"),Stream())),Int("9")),
Receives(Stdio(),Sum([Int("9"),Int("2")])))) Gets(Stream(),Sum(Int("9"),Int("2")))))
/** writes /** writes
81124**/ 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 /** md
``` ```
**/ **/

View File

@ -1 +1 @@
stdio << 72 + 87 + 33 stream << 72 + 87 + 33

5
tests/emit_thrice.fos Normal file
View File

@ -0,0 +1,5 @@
stream << 72 + 87
stream << 88
+ 96
99 + 12 >>
stream

View File

@ -17,33 +17,32 @@ rules
hs: TopLevel((c,p)) -> $[import System.IO hs: TopLevel((c,p)) -> $[import System.IO
data IOStream = StdIO data IOStream = StdIO
stdio :: IO IOStream gets :: Show b => a -> b -> IO a
stdio = return StdIO gets s d = do
receives :: Show b => IO a -> b -> IO a
receives s d = do
temp <- s
putStr(show d) putStr(show d)
return temp return s
main = do main = do
[p]return [c]] [p]return [c]]
hs: Stdio() -> ("stdio", "") hs: Stream() -> ("StdIO", "")
hs: Int(x) -> (x, "") hs: Int(x) -> (x, "")
hs: Sum((c,p)) -> ($[sum [c]], p) hs: Sum( (c, p), (d, q)) -> ($[([c] + [d])], <conc-strings>(p,q))
hs: Receives((c, p), (d, s)) -> ($[[c] `receives` [d]], <conc-strings>(p,s)) hs: Gets((c, p), (d, q)) -> <hsget>(c,d,<conc-strings>(p,q),<newname>"fosgt")
hs: Enters((c, p), (d, s)) -> <hsenter>(c,d,<conc-strings>(p,s),<newname>"fos")
hsenter: (x, s, p, v) -> (v, <concat-strings>[$[[p]let [v] = [x]], "\n", hsget: (s, x, p, v) -> (v, <concat-strings>[p, $[[v] <- [s] `gets` [x]],
$[[s] `receives` [v]], "\n"]) "\n"])
hslist: x -> (<map(Fst); join(|", "); brack>x, <map(Snd); concat-strings>x) hs: To( (c, p), (d, q)) -> <hsto>(c,d,<conc-strings>(p,q),<newname>"fosto")
brack: x -> $<[<x>]>
hsto: (x, s, p, v) -> (v, <concat-strings>[p, $[let [v] = [x]], "\n",
$[[s] `gets` [v]], "\n"])
hs: Sequence(l) -> (<last; Fst>l, <map(Snd); concat-strings>l)
strategies strategies
haskell = bottomup(try(hs <+ hslist)) haskell = bottomup(try(hs))
// Interface haskell code generation with editor services and file system // Interface haskell code generation with editor services and file system
to-haskell: (selected, _, _, path, project-path) -> (filename, result) to-haskell: (selected, _, _, path, project-path) -> (filename, result)

View File

@ -7,25 +7,24 @@ signature
rules rules
js: TopLevel(x) -> $[const Stdio = { 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) { function to(data, strm) {
strm.receives(data); strm.gets(data);
return data; return data;
} }
[x]] [x]]
js: Stdio() -> $[Stdio] js: Stream() -> $[Stdio]
js: Int(x) -> x js: Int(x) -> x
js: Sum(x) -> $[[x].reduce((v,w) => v+w)] js: Sum(x,y) -> $[[x] + [y]]
js: Receives(x, y) -> $[[x].receives([y])] js: Gets(x, y) -> $[[x].gets([y])]
js: Enters(x, y) -> $[forwards([x],[y])] js: To(x, y) -> $[to([x],[y])]
js: Sequence(l) -> <join(|";\n")>l
jslist: x -> $<[<<join(|", ")>x>]>
strategies strategies
javascript = bottomup(try(js <+ jslist)) javascript = bottomup(try(js))
// Interface javascript code generation with editor services and file system // Interface javascript code generation with editor services and file system
to-javascript: (selected, _, _, path, project-path) -> (filename, result) to-javascript: (selected, _, _, path, project-path) -> (filename, result)

View File

@ -9,26 +9,25 @@ rules
py: TopLevel(x) -> $[import sys py: TopLevel(x) -> $[import sys
class StdioC: class StdioC:
def receives(self, v): def gets(self, v):
print(v, file=sys.stdout, end='') print(v, file=sys.stdout, end='')
return self return self
def forwards(data,strm): def to(data,strm):
strm.receives(data) strm.gets(data)
return data return data
Stdio = StdioC() Stdio = StdioC()
[x]] [x]]
py: Stdio() -> $[Stdio] py: Stream() -> $[Stdio]
py: Int(x) -> x py: Int(x) -> x
py: Sum(x) -> $[sum([x])] py: Sum(x,y) -> $[[x] + [y]]
py: Receives(x, y) -> $[[x].receives([y])] py: Gets(x, y) -> $[[x].gets([y])]
py: Enters(x, y) -> $[forwards([x],[y])] py: To(x, y) -> $[to([x],[y])]
py: Sequence(l) -> <join(|"\n")>l
pylist: x -> $<[<<join(|", ")>x>]>
strategies strategies
python = bottomup(try(py <+ pylist)) python = bottomup(try(py))
// Interface python code generation with editor services and file system // Interface python code generation with editor services and file system
to-python: (selected, _, _, path, project-path) -> (filename, result) to-python: (selected, _, _, path, project-path) -> (filename, result)

View File

@ -3,7 +3,7 @@ imports libstrategolib
rules rules
join(|infix) : [] -> "" join(|infix) : [] -> ""
join(|infix) : [x | xs] -> $[[x][<prejoin(|infix)>xs]] join(|infix) : [x | xs] -> <conc-strings>(x, <prejoin(|infix)>xs)
prejoin(|infix) : [] -> "" prejoin(|infix) : [] -> ""
prejoin(|infix) : [x | xs] -> $[[infix][x][<prejoin(|infix)>xs]] prejoin(|infix) : [x | xs] -> <concat-strings>[infix,x,<prejoin(|infix)>xs]