Glen Whitney
f789ed94fd
Some checks failed
continuous-integration/drone/push Build is failing
Also start using nailgun to speed up code generation. Resolves #6.
297 lines
7.4 KiB
Cheetah
297 lines
7.4 KiB
Cheetah
module basic
|
|
language fostr
|
|
|
|
test hw1_type [[
|
|
[[stream]] << [['Hello, world! ']] << [[3+2]] << ' times.'
|
|
]]
|
|
run get-type on #1 to STREAM()
|
|
run get-type on #2 to STRING()
|
|
run get-type on #3 to INT()
|
|
run get-type to STREAM()
|
|
/** writes
|
|
Hello, world! 5 times.**/
|
|
|
|
/** md
|
|
Title: A whirlwind tour of fostr
|
|
|
|
## Whirlwind tour
|
|
|
|
There seems only to be one way to start a tour like this. So here goes:
|
|
```fostr
|
|
**/
|
|
|
|
/** md */ test hello_world [[
|
|
<<< 'Hello, world!'
|
|
]] /* **/
|
|
parse to TopLevel(DefGets(LitString("'Hello, world!'")))
|
|
/** writes
|
|
Hello, world!**/
|
|
|
|
// Prior proto-hello-world, no longer in the tour.
|
|
test emit_sum [[
|
|
stream << 72 + 87 + 33
|
|
]]
|
|
parse to TopLevel(Gets(Stream(), Sum(Sum(Int("72"), Int("87")), Int("33"))))
|
|
/** writes
|
|
192**/
|
|
|
|
/** md
|
|
```
|
|
|
|
At the moment, there are only two ways to run a file containing fostr code
|
|
(you can find the above in `tests/hw.fos`). They both start by
|
|
cloning this fostr project. Then, either:
|
|
|
|
1. Open the project in Eclipse and build it, visit your program file,
|
|
generate code from it in your preferred target language (among
|
|
the options available in the "Spoofax > Generate" menu), and execute the
|
|
resulting code.
|
|
|
|
1. Use the `bin/fosgen` bash script to generate code in a target language,
|
|
and execute the resulting code.
|
|
|
|
For example, this snippet generates the following Python:
|
|
```python
|
|
{! ../tests/hw.py extract:
|
|
start: 'Stdio\s='
|
|
!}
|
|
```
|
|
It generates nearly identical code in
|
|
this simple example for Javascript (just with `"Hello, world!"`
|
|
in place of `r'Hello, world!'`), although it generates a different
|
|
preamble defining Stdio for each language. (Currently, Haskell code
|
|
generation is also supported.)
|
|
|
|
There's not much to break down in such a tiny program as this, but let's do
|
|
it. The prefix operator `<<<` could be read as "the default stream receives...",
|
|
and unsurprisingly in a main program the default stream is standard input and
|
|
output. And `'Hello, world!'` is a literal string constant; what you see is
|
|
what you get. The only detail to know is that such constants must occur
|
|
within a single line of your source file. So depending on how you
|
|
ran the program and how closely you looked at its output,
|
|
you may have noticed this program does not write a newline at the end
|
|
of its message. Nothing is ever implicitly sent to a stream. So if you want
|
|
newlines, you should switch to a (double-quoted) string that allows
|
|
the usual array of escape sequences:
|
|
|
|
```fostr
|
|
**/
|
|
|
|
/** md */ test hello_esc_world [[
|
|
<<< "Hello,\t\tworld!\n\n"
|
|
]] /* **/
|
|
parse to TopLevel(DefGets(EscString("\"Hello,\t\tworld!\n\n\"")))
|
|
/** writes
|
|
Hello, world!
|
|
|
|
**/
|
|
|
|
/** md
|
|
```
|
|
(We threw in two of each so you could clearly see them in the output if
|
|
you run this program.)
|
|
|
|
### 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, for convenience,
|
|
the value of a stream receiving an item is (generally) just the stream back
|
|
again. That way we can use the general (left-associative)
|
|
`_stream_ << _value_` operator to chain insertions into a stream:
|
|
```fostr
|
|
**/
|
|
|
|
/** md */ test state_obvious [[
|
|
<<< 'Two and ' << 2 << ' make ' << 2+2 << ".\n"
|
|
]] /* **/
|
|
parse to TopLevel(
|
|
Gets(Gets(Gets(Gets(DefGets(LitString("'Two and '")),Int("2")),
|
|
LitString("' make '")),Sum(Int("2"),Int("2"))),
|
|
EscString("\".\n\"")))
|
|
/** writes
|
|
Two and 2 make 4.
|
|
**/
|
|
|
|
test emit_twice [[
|
|
stream << 72 + 87 + 33 << 291
|
|
]]
|
|
parse to TopLevel(
|
|
Gets(Gets(Stream(), Sum(Sum(Int("72"), Int("87")), Int("33"))), Int("291")))
|
|
/** writes
|
|
192291**/
|
|
|
|
/** md
|
|
```
|
|
|
|
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 `>>>` for sending to the default stream or `>>` in general; these forms
|
|
(generally) return the value sent, so the following writes "824":
|
|
```fostr
|
|
**/
|
|
|
|
/** md */ test enters_twice [[
|
|
(7 + 8 >> stream + 9) >>>
|
|
]] /* **/
|
|
parse to TopLevel(
|
|
DefTo(Sum(Sum(Int("7"), To(Int("8"), Stream())), Int("9"))))
|
|
/** writes
|
|
824**/
|
|
|
|
/** md
|
|
```
|
|
Two things are worth noting here: the default stream can always be referred to
|
|
directly via the identifier `stream`, and the precedences of `<<` and `>>` are
|
|
different so that generally full expressions go to a stream with `<<` but
|
|
just individual terms are sent with `>>`.
|
|
|
|
### Layout in fostr
|
|
|
|
Expressions may be laid out onto multiple lines, as long as all continuation
|
|
lines are indented from the start of the initial line:
|
|
```fostr
|
|
**/
|
|
|
|
/** md */ test receive_enter_break [[
|
|
<<<
|
|
7
|
|
+ 8 >>>
|
|
+ 9
|
|
]] /* **/
|
|
parse to TopLevel(
|
|
DefGets(Sum(Sum(Int("7"), DefTo(Int("8"))), Int("9"))))
|
|
/** writes
|
|
824**/
|
|
|
|
/** md
|
|
```
|
|
(So for example you will get a parse error with something like this:)
|
|
```fostr
|
|
**/
|
|
|
|
/** md */ test enter_receive_bad_continuation [[
|
|
(7 + 8 >>> + 9)
|
|
>> (<<< 9 + 2)
|
|
]] /* **/
|
|
parse fails
|
|
|
|
/* Extra tests not in the tour */
|
|
test enter_receive [[
|
|
(7 + 8 >> stream + 9) >> (stream << 9 + 2)
|
|
]] /* **/
|
|
parse to TopLevel(
|
|
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 [[
|
|
<<< 72 + 87
|
|
<<< 88
|
|
+ 96
|
|
99 + 12
|
|
>>>
|
|
|
|
]] /* **/
|
|
parse to TopLevel(Sequence([
|
|
DefGets(Sum(Int("72"), Int("87"))),
|
|
DefGets(Sum(Int("88"), Int("96"))),
|
|
Sum(Int("99"), DefTo(Int("12")))
|
|
]))
|
|
/** writes
|
|
15918412**/
|
|
|
|
/** md
|
|
```
|
|
|
|
will write 15918412. The fostr parser enforces that successive expressions
|
|
in sequence align at the left; e.g., the following fails to parse:
|
|
|
|
```fostr
|
|
**/
|
|
|
|
/** md */ test emit_thrice_bad_alignment [[
|
|
<<< 72 + 87
|
|
<<< 88
|
|
+ 96
|
|
99 + 12 >>>
|
|
]] /* **/
|
|
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 [[
|
|
<<< 1 + 2; 3 >>>
|
|
(4 + 5) >>>; stream << 6;
|
|
<<< 7
|
|
<<< 8
|
|
+ (9+10);
|
|
11 + 12 >>>; 13 >>>
|
|
>>>
|
|
]] /* **/
|
|
parse to TopLevel(Sequence([
|
|
ISequence(Prior([Terminate(DefGets(Sum(Int("1"), Int("2"))))]),
|
|
DefTo(Int("3"))),
|
|
ISequence(Prior([Terminate(DefTo(Sum(Int("4"), Int("5"))))]),
|
|
Terminate(Gets(Stream(), Int("6")))),
|
|
DefGets(Int("7")),
|
|
Terminate(DefGets(Sum(Int("8"), Sum(Int("9"), Int("10"))))),
|
|
ISequence(Prior([Terminate(Sum(Int("11"), DefTo(Int("12"))))]),
|
|
DefTo(DefTo(Int("13"))))
|
|
]))
|
|
/** 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())
|
|
]))
|
|
|
|
test emit_several_default [[
|
|
<<< 1 + 2; 3 >>>
|
|
(4 + 5) >>> >> stream; stream << 6;
|
|
<<< 7 << 75
|
|
<<< 8
|
|
+ (9+10);
|
|
11 + 12 >>>; 13 >>>
|
|
>>>
|
|
]] parse succeeds
|
|
/** writes
|
|
3399677527121313*/
|