fostr/tests/basic.spt
Glen Whitney cc89ad1e93
All checks were successful
continuous-integration/drone/push Build is passing
Add OCaml code generation (#24)
Also start using nailgun to speed up code generation.

  Resolves #6.

Co-authored-by: Glen Whitney <glen@studioinfinity.org>
Reviewed-on: #24
Co-Authored-By: Glen Whitney <glen@nobody@nowhere.net>
Co-Committed-By: Glen Whitney <glen@nobody@nowhere.net>
2021-03-01 20:40:35 +00:00

297 lines
7.5 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 and OCaml
code generation are 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*/