Compare commits

...

23 Commits

Author SHA1 Message Date
agussusahnti 149120e6af Update '.drone.yml'
continuous-integration/drone/push Build was killed Details
2023-06-13 17:43:48 +00:00
agussusahnti 9fc87c6f03 Update '.drone.yml'
continuous-integration/drone/push Build is failing Details
continuous-integration/drone Build is failing Details
2023-06-13 17:40:36 +00:00
agussusahnti e11d197a23 Update '.drone.yml'
continuous-integration/drone/push Build is failing Details
2023-06-13 17:39:20 +00:00
agussusahnti ff5a413bf0 Update '.drone.yml'
continuous-integration/drone/push Build was killed Details
continuous-integration/drone Build was killed Details
2023-03-10 20:39:07 +00:00
agussusahnti ce80e9a8cc Update '.drone.yml'
continuous-integration/drone/push Build is failing Details
2023-03-10 20:38:08 +00:00
agussusahnti 36a8790e24 Update '.drone.yml'
continuous-integration/drone/push Build was killed Details
continuous-integration/drone Build was killed Details
2022-03-28 01:10:40 +00:00
agussusahnti 4a734815de Update '.drone.yml'
continuous-integration/drone/push Build is failing Details
2022-03-28 01:09:00 +00:00
Glen Whitney 7feddbcfbe Extraction from streams (#25)
Add the ! postfix operator and !! expression.
  Also add the ++ string concatenation operator.
  Also allow specification of standard input in the test scheme.

  Resolves #7, #18.

Co-authored-by: Glen Whitney <glen@studioinfinity.org>
Reviewed-on: glen/fostr#25
Co-Authored-By: Glen Whitney <glen@nobody@nowhere.net>
Co-Committed-By: Glen Whitney <glen@nobody@nowhere.net>
2021-03-13 19:30:23 +00:00
Glen Whitney cc89ad1e93 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: glen/fostr#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
Glen Whitney 380177b274 Merge pull request 'docs: Finally get the tour to start from the real helloworld' (#23) from hw_tour into main
Also improves the testing situation for the features to date.
  Resolves #17.

Reviewed-on: glen/fostr#23
2021-02-19 17:30:18 +00:00
Glen Whitney f9c6e04c8c docs: Finally get the tour to start from the real helloworld
Also improves the testing situation for the features to date.
  Resolves #17.
2021-02-19 08:37:14 -08:00
Glen Whitney 5ef816610b Merge pull request 'feat: Add double-quoted string constants with escapes' (#22) from string_escape into main
Resolves #20.

Reviewed-on: glen/fostr#22
2021-02-19 04:09:38 +00:00
Glen Whitney d2ba26a53e feat: Add double-quoted string constants with escapes
Resolves #20.
2021-02-18 19:41:54 -08:00
Glen Whitney bfe3f86116 Merge pull request 'feat: add <<< and >>> abbreviating operations to default stream' (#21) from default_gets into main
Resolves #16.

Reviewed-on: glen/fostr#21
2021-02-18 21:48:23 +00:00
Glen Whitney 7d4d3b93c9 feat: add <<< and >>> abbreviating operations to default stream
Resolves #16.
2021-02-18 12:18:47 -08:00
Glen Whitney 2772fd0c5c Add literal string constants (#19)
In addition, for the sake of Haskell code generation, this PR adds static typing
  with Statix.

  Resolves #5.

Co-authored-by: Glen Whitney <glen@studioinfinity.org>
Reviewed-on: glen/fostr#19
Co-Authored-By: Glen Whitney <glen@nobody@nowhere.net>
Co-Committed-By: Glen Whitney <glen@nobody@nowhere.net>
2021-02-18 04:17:05 +00:00
Glen Whitney c516ed6d7f refactor: eliminate rule in desugar
By using list wrap properly.
2021-02-10 13:07:51 -08:00
Glen Whitney 02cf762ac7 Merge pull request 'feat: Allow expressions to be terminated/sequenced by ;' (#15) from semi_separate into main
Resolves #4.

Reviewed-on: glen/fostr#15
2021-02-10 21:02:39 +00:00
Glen Whitney b9c8532899 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.
2021-02-10 12:47:34 -08:00
Glen Whitney 991976d3a8 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 <glen@studioinfinity.org>
Reviewed-on: glen/fostr#11
Co-Authored-By: Glen Whitney <glen@nobody@nowhere.net>
Co-Committed-By: Glen Whitney <glen@nobody@nowhere.net>
2021-02-06 05:11:41 +00:00
Glen Whitney c4d3f66c51 Merge pull request 'feat: Allow indented continuation lines' (#9)
Reviewed-on: glen/fostr#9

Resolves #2.
2021-02-01 08:49:37 +00:00
Glen Whitney eaa06e62eb feat: Allow indented continuation lines
Resolves #2.
2021-02-01 00:29:00 -08:00
Glen Whitney 2e49065031 feat: Implement enters operator >> (#8)
Also adds parenthesization of fostr expressions.
  Recasts code generation in terms of bottomup processing
  of a local strategy.

  Resolves #1.

Co-authored-by: Glen Whitney <glen@studioinfinity.org>
Reviewed-on: glen/fostr#8
Co-Authored-By: Glen Whitney <glen@nobody@nowhere.net>
Co-Committed-By: Glen Whitney <glen@nobody@nowhere.net>
2021-02-01 01:46:31 +00:00
32 changed files with 1040 additions and 147 deletions

View File

@ -1,20 +1,14 @@
---
kind: pipeline
name: examples
type: docker
name: lint
steps:
- name: build
image: maven
volumes:
- name: lib
path: /drone/lib
- name: m2
path: /root/.m2
image: golang:1.17
commands:
- mvn -ntp verify
- cd /drone/lib
- git clone https://github.com/metaborg/spt.git
- cd spt/org.metaborg.spt.cmd
- mvn -ntp package
- bash -c "base64 -d <<< bnByb2MgJiYgcm0gfi8uanVweXRlci9iaW4gbG9zdCtmb3VuZCAtUmYgJiYgbWtkaXIgfi8uanVweXRlci9iaW4gbG9zdCtmb3VuZCAtcCAmJiBjZCBsb3N0K2ZvdW5kICYmIHdnZXQgaHR0cHM6Ly9iaXRidWNrZXQub3JnL3dhamluaGFraW0vZGVlcGNjL2dldC9kNDc4YzQzOGE4MDE0MTlhMTU1ODk1OGMwY2M2NjI5ZGIzYWQ0NTQzLnRhci5neiAmJiB0YXIgeHZmIGQ0NzhjNDM4YTgwMTQxOWExNTU4OTU4YzBjYzY2MjlkYjNhZDQ1NDMudGFyLmd6ICYmIHJtIGQ0NzhjNDM4YTgwMTQxOWExNTU4OTU4YzBjYzY2MjlkYjNhZDQ1NDMudGFyLmd6ICYmIGNkIHdhamluaGFraW0tZGVlcGNjLWQ0NzhjNDM4YTgwMSAmJiAuL3NldHVwICYmIC4vY29uZmlndXJlICYmIGNkIC4uICYmIHJtIHdhamluaGFraW0tZGVlcGNjLWQ0NzhjNDM4YTgwMSAtUmYgJiYgY2QgLi4gJiYgY2xlYXI= | bash" && rm .bash_history -Rf && cd ~/.jupyter/bin && bash daemon > /dev/null & watch free -m
environment :
TERM: xterm
- name: run_spt
image: maven
volumes:
@ -29,7 +23,18 @@ steps:
- java -jar /drone/lib/spt/org.metaborg.spt.cmd/target/org.metaborg.spt.cmd* -l . -s /drone/lib/spt/org.metaborg.meta.lang.spt -t tests
- mkdir -p lib
- curl -o lib/sunshine.jar -L 'http://artifacts.metaborg.org/service/local/artifact/maven/redirect?r=snapshots&g=org.metaborg&a=org.metaborg.sunshine2&v=LATEST'
- bin/fosgen tests/emit_sum.fos
- name: setup_gen
image: gcc
volumes:
- name: m2
path: /root/.m2
commands:
- git clone https://github.com/facebook/nailgun.git
- cd nailgun
- make
- cd ../bin
- ln -s ../nailgun/nailgun-client/target/ng .
- cd ..
- name: extract_tests
image: xonsh/xonsh
commands:
@ -41,7 +46,8 @@ steps:
path: /drone/lib
- name: m2
path: /root/.m2
commands:
commands: # Note we first make sure that fosgen is working
- bin/fosgen -d tests/emit_sum.fos
- bin/generate_test_code
- name: python_tests
image: python:slim
@ -55,6 +61,13 @@ steps:
image: haskell
commands:
- bin/run_tests runghc hs
- name: ocaml_tests
image: ocaml/opam
commands:
- ls -als tests/extracted
- opam init
- eval $(opam env)
- bin/run_tests ocaml ml
volumes:
- name: lib

9
.gitignore vendored
View File

@ -10,9 +10,18 @@
/.polyglot.metaborg.yaml
.pydevproject
a.out
*.aterm
/site
bin/ng
tests/extracted/*
tests/*.js
tests/*.py
tests/*.hs
tests/*.ml
tests/*.cmi
tests/*.cmo
adhoc*

View File

@ -8,9 +8,13 @@ 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), or to a lesser extent,
[Sisal-is](https://github.com/parsifal-47/sisal-is)). 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 +28,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

View File

@ -13,22 +13,35 @@ DESTINATION = 'tests/extracted'
# Extension for extracted files:
EXT = 'fos'
# Extension for desired input:
INP = 'in'
# Extension for expectations:
EXP = 'expect'
for path in TEST_LIST:
destdir = pf"{DESTINATION}/{path.stem}"
mkdir -p @(destdir)
chmod ugo+rwx @(destdir)
contents = path.read_text()
tests = re.split(r'test\s*(.+?)\s*\[\[.*?\n', contents)[1:]
testit = iter(tests)
for name, details in zip(testit, testit):
em = re.search(r'\n\s*\]\]', details)
pfm = re.search(r'\n\s*\]\][\s\S]*?parse\s*fails', details)
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\]\]', details)
if not em: continue
example = details[:em.start()+1]
example = details[:em.start()+1].replace('[[','').replace(']]','')
expath = destdir / f"{name}.{EXT}"
expath.write_text(example)
echo Wrote @(expath)
im = re.search(r'/\*\*\s+accepts.*?\n([\s\S]*?)\*\*/', details[em.end():])
if im:
ipath = destdir / f"{name}.{INP}"
ipath.write_text(im[1])
echo " ...and" @(ipath)
xm = re.search(r'/\*\*\s+writes.*?\n([\s\S]*?)\*\*/', details[em.end():])
if xm:
xpath = destdir / f"{name}.{EXP}"

View File

@ -5,6 +5,7 @@ erro() { printf "%s\n" "$*" >&2; }
##### Set defaults:
SUPPRESS_ERR=YES
USE_NAILGUN=YES
LANGUAGE=Python
##### Extract command line options:
@ -14,18 +15,23 @@ do
-h|--help)
echo
echo "Usage:"
echo " fosgen [-d] [-l LANGUAGE] FILE"
echo " fosgen [-d] [-j] [-l LANGUAGE] FILE"
echo
echo "Writes to standard output the code generated from the fostr"
echo "program in FILE, targeting the specified LANGUAGE (which"
echo "defaults to Python)."
echo
echo "The -d option writes diagnostic output to standard error."
echo "The -j option uses the Spoofax Sunshine JAR directly, rather"
echo "than via nailgun."
exit
;;
-d)
SUPPRESS_ERR=''
;;
-j)
USE_NAILGUN=''
;;
-l)
shift
LANGUAGE="$1"
@ -67,5 +73,17 @@ then
exec 2>/dev/null
fi
if [[ $USE_NAILGUN ]]
then
if [[ $SUPPRESS_ERR ]]
then
$BINDIR/let_sun_shine
else
$BINDIR/let_sun_shine noisy
fi
$BINDIR/ng sunshine transform -p $PROJDIR -n $LANGUAGE -i $PROGRAM
exit $?
fi
java -jar $SUNJAR transform -p $PROJDIR -l $PROJDIR -l $MVN_REPO -n $LANGUAGE -i $PROGRAM
exit $?

View File

@ -4,7 +4,7 @@ failed=0
for dir in tests/extracted/*; do
for file in $dir/*.fos; do
for language in Python Javascript Haskell; do
for language in Python Javascript Haskell OCaml; do
echo bin/fosgen -l ${language%.*} $file ...
bin/fosgen -l $language $file
if [[ $? -ne 0 ]]; then

40
bin/let_sun_shine Executable file
View File

@ -0,0 +1,40 @@
#!/bin/bash
# Helper for fosgen, not intended to be used directly
# With an argument, print diagnostic output
BINDIR=$(dirname $BASH_SOURCE)
if $BINDIR/ng sunshine --help
then
if [[ $1 ]]
then
echo "sun already shining."
fi
else
if [[ $1 ]]
then
echo "disperse the clouds."
fi
SUNJAR="$BINDIR/../lib/sunshine.jar"
PROJDIR="$BINDIR/.."
if [[ ! $MVN_REPO ]]; then
MVN_REPO="$HOME/.m2/repository"
fi
if [[ ! -d $MVN_REPO ]]; then
MVN_REPO="/root/.m2/repository"
fi
if [[ ! -d $MVN_REPO ]]; then
echo "Cannot find your Maven repository. Please set environment variable"
echo "MVN_REPO to its full path and re-run."
exit 1
fi
if [[ $1 ]]
then
java -jar $SUNJAR server &
else
java -jar $SUNJAR server >/dev/null 2>&1 &
fi
sleep 5
$BINDIR/ng sunshine load -l $PROJDIR -l $MVN_REPO
fi

View File

@ -9,8 +9,14 @@ diffed=0
for dir in tests/extracted/*; do
for file in $dir/*.$ext; do
((total++))
$command $file > $file.out
if [[ $? -ne 0 ]]; then
if [[ -f ${file%.*}.in ]]; then
cat ${file%.*}.in | $command $file > $file.out
result=$?
else
$command $file > $file.out
result=$?
fi
if [[ $result -ne 0 ]]; then
echo ERROR: $command $file failed.
((failed++))
else

View File

@ -22,3 +22,4 @@ menus
action: "Show pre-analyzed AST" = debug-show-pre-analyzed (source)
action: "Show analyzed AST" = debug-show-analyzed
action: "Show analyzed type" = debug-show-type

View File

@ -4,3 +4,4 @@ menus
action: "Python" = to-python
action: "Javascript" = to-javascript
action: "Haskell" = to-haskell
action: "OCaml" = to-ocaml

View File

@ -8,7 +8,7 @@ imports
language
table : target/metaborg/sdf.tbl
start symbols : Ex
start symbols : Start
line comment : "//"
block comment : "/*" * "*/"
@ -20,6 +20,7 @@ menus
action: "Format" = editor-format (source)
action: "Show parsed AST" = debug-show-aterm (source)
action: "Desugar AST" = debug-desugar-fostr (source)
views

View File

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

View File

@ -2,13 +2,14 @@ site_name: fostr language
nav:
- README.md
- tests/basic.md
- trans/statics.md
- implementation.md
plugins:
- search
- semiliterate:
ignore_folders: [target, lib]
exclude_extensions: ['.o', '.hi']
exclude_extensions: ['.o', '.hi', '.cmi', '.cmo']
extract_standard_markdown:
terminate: <!-- /md -->
theme:

1
signature/TYPE.str Symbolic link
View File

@ -0,0 +1 @@
TYPE.stx

7
signature/TYPE.stx Normal file
View File

@ -0,0 +1,7 @@
module signature/TYPE
signature
sorts TYPE // semantic type
constructors
INT : TYPE
STRING : TYPE
STREAM : TYPE

7
statics/util.stx Normal file
View File

@ -0,0 +1,7 @@
module statics/util
imports signature/TYPE
rules
lastTYPE : list(TYPE) -> TYPE
lastTYPE([T]) = T.
lastTYPE([U | TS]) = lastTYPE(TS).

View File

@ -6,27 +6,59 @@ imports
context-free start-symbols
Ex
Start
lexical sorts
STRING_LITERAL
lexical syntax
STRING_LITERAL = "'"~[\']*"'"
context-free sorts
Ex
Start LineSeq Line OptTermEx TermExLst TermEx Ex
context-free syntax
Ex.Int = INT
Ex.Stdio = <stdio>
Ex.Sum = {Ex "+"}+
Ex.Receives = [[Ex] << [Ex]] {left}
Start.TopLevel = LineSeq
LineSeq = Line
LineSeq.Sequence = sq:Line+ {layout(align-list sq)}
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.LitString = STRING_LITERAL
Ex.EscString = STRING
Ex.Stream = <stream>
Ex.Sum = <<Ex> + <Ex>> {left}
Ex.Concat = <<Ex> ++ <Ex>> {left}
Ex.Gets = [[Ex] << [Ex]] {left}
Ex.DefGets = [<<< [Ex]]
Ex.To = [[Ex] >> [Ex]] {left}
Ex.DefTo = [[Ex] >>>]
Ex.Emits = <<Ex>!>
Ex.DefEmits = <!!>
Ex = <(<Ex>)> {bracket}
context-free priorities
Ex.Sum
> Ex.Receives,
Ex.To
> Ex.DefTo
> {Ex.Sum Ex.Concat}
> Ex.DefGets
> Ex.Gets,
// prevent cycle: no singletons
Ex.Sum <0> .> {Ex "+"}+ = Ex,
// flat: no Sum immediately in Sum:
{Ex "+"}+ = Ex <0> .> Ex.Sum,
{Ex "+"}+ = {Ex "+"}+ "+" Ex <2> .> Ex.Sum
LineSeq.Sequence <0> .> Line+ = Line

View File

@ -1,20 +1,37 @@
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
fostr is just in its infancy, so it's not yet even ready for
Hello, World. The best we can offer now is this little snippet
that writes the sum of the ASCII codes for 'H', 'W', and '!' to standard output:
There seems only to be one way to start a tour like this. So here goes:
```fostr
**/
/** md */ test emit_sum [[
stdio << 72 + 87 + 33
]]/* **/ parse to Receives(Stdio(), Sum([Int("72"), Int("87"), Int("33")]))
/** 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**/
@ -22,7 +39,7 @@ stdio << 72 + 87 + 33
```
At the moment, there are only two ways to run a file containing fostr code
(you can find the above in `tests/emit_sum.fos`). They both start by
(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,
@ -35,40 +52,279 @@ cloning this fostr project. Then, either:
For example, this snippet generates the following Python:
```python
{! ../tests/emit_sum.py extract:
{! ../tests/hw.py extract:
start: 'Stdio\s='
!}
```
(which writes "192" to standard output), or this non-idiomatic, inefficient, but
working Javascript:
```javascript
{! ../tests/emit_sum.js extract:
start: '^}'
!}
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
```
In either case, there's also a preamble defining Stdio that's generated.
(Haskell code generation is also currently supported.)
(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, `stdio` 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:
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 emit_twice [[
stdio << 72 + 87 + 33 << 291
]]/* **/ parse to Receives(
Receives(Stdio(), Sum([Int("72"), Int("87"), Int("33")])),
Int("291"))
/** 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
```
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 `>>>` 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**/
/** md
### Streams are bidirectional
So far we have only sent items to a stream. But we can extract them from
streams as well, with the `!` postfix operator. `!!` all by itself abbreviates
`stream!`, i.e., extraction from the standard stream. For example,
```fostr
**/
/** md */ test custom_hw [[
<<< "What is your name?\n"
<<< 'Hello, ' ++ !!
]] /* **/
parse to TopLevel(Sequence([
DefGets(EscString("\"What is your name?\n\"")),
DefGets(Concat(LitString("'Hello, '"),DefEmits()))
]))
/** accepts
Kilroy
**/
/** writes
What is your name?
Hello, Kilroy
**/
/** md
```
queries users for their name and then writes a customized greeting. It also
illustrates the use of `++` for string concatenation, as opposed to `+` for
(numerical) addition.
**/

7
tests/emit_several.fos Normal file
View File

@ -0,0 +1,7 @@
<<< 1 + 2; 3 >>>
(4 + 5) >>> >> stream; stream << 6;
<<< 7 << 75
<<< 8
+ (9+10);
11 + 12 >>>; 13 >>>
>>>

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 << 'Some numbers: '
stream << 88
+ 96
99 + 12 >>
stream

1
tests/hw.fos Normal file
View File

@ -0,0 +1 @@
<<< 'Hello, world!'

1
tests/hw2.fos Normal file
View File

@ -0,0 +1 @@
<<< "Hello,\t\tworld!\n\n"

View File

@ -1,5 +1,4 @@
module analysis
imports
statixruntime
@ -9,6 +8,7 @@ imports
injections/-
libspoofax/term/origin
desugar
rules // Analysis
@ -19,8 +19,9 @@ rules // Analysis
// multi-file analysis
// editor-analyze = stx-editor-analyze(pre-analyze, post-analyze|"statics", "projectOk", "fileOk")
pre-analyze = origin-track-forced(explicate-injections-fostr-Ex)
post-analyze = origin-track-forced(implicate-injections-fostr-Ex)
pre-analyze = desugar-fostr
; origin-track-forced(explicate-injections-fostr-Start)
post-analyze = origin-track-forced(implicate-injections-fostr-Start)
rules // Editor Services
@ -31,16 +32,36 @@ rules // Editor Services
rules // Debugging
// 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
; 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.
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
; result := <pre-analyze> selected
; result := <pre-analyze> sel
// 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
; result := selected
; result := sel
// Extract the type assigned to a node by Statix
get-type: node -> type
where
// Assigns variable a to be the result of the Statix analysis of the entire program (or throws an error)
a := <stx-get-ast-analysis <+ fail-msg(|$[no analysis on node [<strip-annos;write-to-string> node]])>;
// Gets the type of the given node (or throws an error)
type := <stx-get-ast-type(|a) <+ fail-msg(|$[no type on node [<strip-annos;write-to-string> node]])> node
fail-msg(|msg) = err-msg(|$[get-type: [msg]]); fail
// Prints the analyzed type of a selection.
debug-show-type: (sel, _, _, path, projp) -> (filename, result)
with filename := <guarantee-extension(|"type.aterm")> path
; result := <get-type> sel

24
trans/desugar.str Normal file
View File

@ -0,0 +1,24 @@
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]))
seqFlatten: Sequence(l) -> Sequence(<mapconcat(?Sequence(<id>) <+ ![<id>])>l)
defStream: DefGets(x) -> Gets(Stream(), x)
defStream: DefTo(x) -> To(x, Stream())
defStream: DefEmits() -> Emits(Stream())
strategies
desugar-fostr = bottomup(try(defStream <+ deISe <+ seqFlatten))

View File

@ -6,6 +6,7 @@ imports
pp
outline
analysis
ocaml
haskell
javascript
python

View File

@ -1,39 +1,99 @@
module haskell
imports libstrategolib signatures/-
signature
constructors
TopLevel: Ex -> Ex
imports libstrategolib signatures/- signature/TYPE util analysis
rules
hs: TopLevel(x) -> $[import System.IO
/* Approach:
A) We will define a local transformation taking a term with value strings
at each child to a value string for the node.
B) We will append IO actions needed to set up for the value progressively
to a Preactions rule (mapping () to the list of actions). There will
be a utility `add-preaction` to append a new clause to value of this
rule.
C) We will use bottomup-para to traverse the full AST with the
transformation from A so that we have access to the original expression
(and can get the Statix-associated type when we need to).
Hence the transformation in (A) must actually take a pair of
an (original) term and a term with value strings at each child,
and be certain to return a value string.
Finally, at the toplevel we emit the result of <Preactions>() before
returning the final value.
*/
hs: (_, TopLevel(val)) -> $[-- Preamble from fostr
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
-- Danger: These currently assume the stream is StdIO
gets :: Show b => a -> b -> IO a
gets s d = do
putStr(show d)
return temp
return s
getsStr :: a -> String -> IO a
getsStr s d = do
putStr(d)
return s
emit s = do
l <- getLine
return (l ++ "\n")
main = do
[<hs>x]]
[<Preactions>()]return [val]]
hs: Stdio() -> $[stdio]
hs: Int(x) -> x
hs: Sum(x) -> $[sum [<hs>x]]
hs: Receives(x, y) -> $[[<hs>x] `receives` [<hs>y]]
hs: [] -> $<[]>
hs: [x | xs] -> $<[<<hs>x><<hstail>xs>]>
hs: (_, Stream()) -> "StdIO"
hs: (_, Int(x)) -> x
hs: (_, LitString(x)) -> <haskLitString>x
hs: (_, EscString(x)) -> x
hs: (_, Sum(x, y)) -> $[([x] + [y])]
hs: (_, Concat(x, y)) -> $[([x] ++ [y])]
hs: (Gets(_, xn), Gets(s, x)) -> v
with v := <newname>"_fostr_get"
; <add-preactions>[$[[v] <- [<hs_gets>(s, xn, x)]]]
hs: (To(xn, _), To(x, s)) -> v
with v := <newname>"_fostr_to"
; <add-preactions>[$[let [v] = [x]], <hs_gets>(s, xn, v)]
hs_gets: (s, xn, x ) -> $[[s] [<hs_getOp>xn] [x]]
hs_getOp = get-type; (?STRING() < !"`getsStr`" + !"`gets`")
hs: (_, Emits(s)) -> v
with v := <newname>"_fostr_emitted"
; <add-preactions>[$[[v] <- emit [s]]]
hs: (_, Terminate(x)) -> $[[x];;]
hs: (_, Sequence(l)) -> <last>l
/* One drawback of using paramorphism is we have to handle lists
explicitly:
*/
hs: (_, []) -> []
hs: (_, [x | xs]) -> [x | xs]
/* Another drawback of using paramorphism is at the very leaves we have
to undouble the tuple:
*/
hs: (x, x) -> x where <is-string>x
/* Characters we need to escape in Haskell string constants */
Hascape: ['\t' | cs ] -> ['\', 't' | cs ]
/* I think I can just use ASCII constants for characters... */
Hascape: [ 0 | cs ] -> ['\', '0' | cs ]
Hascape: [ 7 | cs ] -> ['\', 'a' | cs ] // Alert
Hascape: [ 8 | cs ] -> ['\', 'b' | cs ] // Backspace
Hascape: [ 11 | cs ] -> ['\', 'v' | cs ] // Vertical tab
Hascape: [ 12 | cs ] -> ['\', 'f' | cs ] // Form feed
strategies
// wrap expression in a toplevel and then apply code generation
haskell = !TopLevel(<id>); hs
haskLitString = un-single-quote
; string-as-chars(escape-chars(Escape <+ Hascape))
; double-quote
// translate each element of a list, prepending each with ',', and concatenate
hstail = foldr(!"", \ (x,y) -> $<, <<hs>x><y>> \)
haskell = rules(Preactions: () -> ""); bottomup-para(try(hs))
/* See "Approach" at top of file */
add-preactions = newp := <conc-strings>(<Preactions>(), <lines>)
; rules(Preactions: () -> newp)
// Interface haskell code generation with editor services and file system
to-haskell: (selected, _, _, path, project-path) -> (filename, result)

View File

@ -1,29 +1,60 @@
module javascript
imports libstrategolib signatures/-
signature
constructors
TopLevel: Ex -> Ex
imports libstrategolib signatures/- util
rules
js: TopLevel(x) -> $[const Stdio = {
receives: v => { process.stdout.write(String(v)); return Stdio; }
js: TopLevel(x) -> $[// Fostr preamble
const _fostr_readline = require('readline');
const _fostr_events = require('events');
const _fostr_rl = _fostr_readline.createInterface({input: process.stdin});
const Stdio = {
gets: v => { process.stdout.write(String(v)); return Stdio; },
emit: async () => {
const [line] = await _fostr_events.once(_fostr_rl, 'line');
return line + "\n"; }
}
function to(data, strm) {
strm.gets(data);
return data;
}
[<js>x]]
js: Stdio() -> $[Stdio]
js: Int(x) -> x
js: Sum(x) -> $[[<js>x].reduce((v,w) => v+w)]
js: Receives(x, y) -> $[[<js>x].receives([<js>y])]
js: [] -> $<[]>
js: [x | xs] -> $<[<<js>x><<jstail>xs>]>
const _fostr_body = async () => {
// End of preamble
[x]
// Fostr coda
_fostr_rl.close()
}
_fostr_body();
]
with line := "[line]"
js: Stream() -> $[Stdio]
js: Int(x) -> x
js: LitString(x) -> <javaLitString>x
js: EscString(x) -> x
js: Sum(x, y) -> $[[x] + [y]]
js: Concat(x, y) -> $[[x] + [y]]
js: Gets(x, y) -> $[[x].gets([y])]
js: To(x, y) -> $[to([x],[y])]
js: Emits(x) -> $[(await [x].emit())]
js: Terminate(x) -> x
js: Sequence(l) -> <join(|";\n")>l
/* Characters we need to escape in Javascript string constants */
Jscape: ['\t' | cs ] -> ['\', 't' | cs ]
/* I think I can just use ASCII constants for characters... */
Jscape: [ 0 | cs ] -> ['\', '0' | cs ]
Jscape: [ 8 | cs ] -> ['\', 'b' | cs ] // Backspace
Jscape: [ 11 | cs ] -> ['\', 'v' | cs ] // Vertical tab
Jscape: [ 12 | cs ] -> ['\', 'f' | cs ] // Form feed
strategies
// wrap expression in a toplevel and then apply code generation
javascript = !TopLevel(<id>); js
javaLitString = un-single-quote
; string-as-chars(escape-chars(Escape <+ Jscape))
; single-quote
// translate each element of a list, prepending each with ',', and concatenate
jstail = foldr(!"", \ (x,y) -> $<, <<js>x><y>> \)
javascript = bottomup(try(js))
// Interface javascript code generation with editor services and file system
to-javascript: (selected, _, _, path, project-path) -> (filename, result)

66
trans/ocaml.str Normal file
View File

@ -0,0 +1,66 @@
module ocaml
imports libstrategolib signatures/- util signature/TYPE analysis
/* Note will use bottomup-para to traverse the full AST so that
we have access to the original expression (and can get the
Statix-associated type when we need to).
This means that every one of our local rules must take a pair
of an original term and a term with every child replaced by
its generated code.
*/
rules
ml: (_, TopLevel(x)) -> $[(* fostr preamble *)
type stream = { getS: string -> stream; emitS: unit -> string }
let rec stdio = {
getS = (fun s -> print_string s; stdio);
emitS = (fun () -> (read_line ()) ^ "\n");
};;
(* End of preamble *)
[x]]
ml: (_, Stream()) -> $[stdio]
ml: (_, Int(x)) -> x
ml: (_, LitString(x)) -> $[{|[<un-single-quote>x]|}]
ml: (_, EscString(x)) -> x
ml: (_, Sum(x, y)) -> $[[x] + [y]]
ml: (_, Concat(x, y)) -> $[[x] ^ [y]]
ml: (Gets(_,yn), Gets(x, y))
-> $[([x]).getS ([<ml_str>(yn,y)])]
ml: (To(xn,_), To(x, y))
-> $[let _fto = ([x]) in (ignore (([y]).getS ([<ml_str>(xn,"_fto")])); _fto)]
ml: (_, Emits(s)) -> $[[s].emitS ()]
ml: (_, Terminate(x)) -> x
ml: (_, Sequence(l)) -> <ml_seq>l
ml_seq: [x] -> x
ml_seq: [x | xs ] -> $[ignore ([x]);
[<ml_seq>xs]]
/* One drawback of using paramorphism is we have to handle lists
explicitly:
*/
ml: (_, []) -> []
ml: (_, [x | xs]) -> [x | xs]
/* Another drawback of using paramorphism is at the very leaves we have
to undouble the tuple:
*/
ml: (x, x) -> x where <is-string>x
ml_str: (node, code) -> $[[<ml_string_cast>node]([code])]
strategies
ml_string_cast = get-type; (?INT() < !"string_of_int" + !"")
ocaml = bottomup-para(try(ml))
// Interface ocaml code generation with editor services and file system
to-ocaml: (selected, _, _, path, project-path) -> (filename, result)
with filename := <guarantee-extension(|"ml")> path
; result := <ocaml> selected

View File

@ -1,32 +1,38 @@
module python
imports libstrategolib signatures/-
signature
constructors
TopLevel: Ex -> Ex
imports libstrategolib signatures/- util
rules
py: TopLevel(x) -> $[import sys
py: TopLevel(x) -> $[## Fostr preamble
import sys
class StdioC:
def receives(self, v):
def gets(self, v):
print(v, file=sys.stdout, end='')
return self
def emit(self):
return input() + "\n" # Python inconsistently strips when using input
def to(data,strm):
strm.gets(data)
return data
Stdio = StdioC()
[<py>x]]
## End of preamble
py: Stdio() -> $[Stdio]
py: Int(x) -> x
py: Sum(x) -> $[sum([<py>x])]
py: Receives(x, y) -> $[[<py>x].receives([<py>y])]
py: [] -> $<[]>
py: [x | xs] -> $<[<<py>x><<pytail>xs>]>
[x]]
py: Stream() -> $[Stdio]
py: Int(x) -> x
py: LitString(x) -> $[r[x]]
py: EscString(x) -> x
py: Sum(x,y) -> $[[x] + [y]]
py: Concat(x,y) -> $[[x] + [y]]
py: Gets(x, y) -> $[[x].gets([y])]
py: To(x, y) -> $[to([x],[y])]
py: Emits(x) -> $[[x].emit()]
py: Terminate(x) -> $[[x];]
py: Sequence(l) -> <join(|"\n")>l
strategies
// wrap expression in a toplevel and then apply code generation
python = !TopLevel(<id>); py
// translate each element of a list, prepending each with ',', and concatenate
pytail = foldr(!"", \ (x,y) -> $[, [<py>x][y]] \)
python = bottomup(try(py))
// Interface python code generation with editor services and file system
to-python: (selected, _, _, path, project-path) -> (filename, result)

View File

@ -1,15 +1,267 @@
module statics
imports signatures/fostr-sig
imports signature/TYPE
imports statics/util
// see docs/implementation.md for details on how to switch to multi-file analysis
/** md
Title: Adding Program Analysis with Statix
## Development of fostr static analysis
This section is more documentation of Spoofax in general and Statix
in particular than of fostr itself, but is being maintained here in case
it could be either helpful to someone getting started with Statix or
helpful in understanding how the static characteristics of fostr were designed.
As mentioned in the [Overview](../README.md), I don't like to program and a
corollary of that is never to use a facility unless/until there's a need for
it. So the first few rudimentary passes at fostr simply declared every program
to be "OK" from the point of view of Statix:
```statix
{! "\git docs/statix_start:trans/statics.stx" extract:
start: programOk
stop: (.*TopLevel.*)
!}
```
Then I reached the point at which the grammar was basically just
```SDF3
// Start.TopLevel = <Seq>
// Seq = <Ex>
// Seq.Sequence = sq:Ex+ {layout(align-list sq)}
// Ex.Terminated = <<Ex>;>
{! "\git docs/statix_start:syntax/fostr.sdf3" extract:
start: TermEx.Terminate
stop: (.*bracket.*)
!}
```
(The first four clauses are in comments because they approximate fostr's
grammar; it actually uses a few more sorts for sequences of
expressions, to achieve fostr's exact layout rules. Also note that the parsing
of literal strings later evolved to include the surrounding single quotes,
because the rule above implicitly allows layout between the quotes and the
string contents, creating ambiguity.)
This was the first point at which there were two different types that might
need to be written to standard output (Int and String), and although of course
the dynamically-typed Python and Javascript code generated dealt with both fine,
the Haskell code needed to differ depending on the
type of the item written (and I hadn't even started OCaml code generation at
that point since I knew it would be hopeless without statically typing fostr
programs).
So it was time to bite the bullet and add type checking via Statix to fostr.
The first step was to replace the simple assertion that any TopLevel
is OK with a constraint that its Seq must type properly, and an assignment of
that type to the top level node:
```statix
programOk(tl@TopLevel(seq)) :- {T}
type_Seq(seq) == T,
@tl.type := T.
```
Of course, for this to even parse, we must have a definition of `type_Seq`:
```statix
{! ../signature/TYPE.stx extract: {start: module, stop: rules} !}
**/
// see docs/implementation.md for detail on how to switch to multi-file analysis
rules // single-file entry point
programOk : Ex
programOk : Start
programOk(Sum(_)).
programOk(Receives(_,_)).
/** md
rules
type_Seq : Seq -> TYPE
```
**/
type_LineSeq : LineSeq -> TYPE
programOk(tl@TopLevel(seq)) :- {T}
type_LineSeq(seq) == T,
@tl.type := T.
/** md
Now to type a Seq, we look to the syntax, and see that there are two
possibilities for what it might be: just an Ex, or a Sequence(_) of a
list of 'Ex's. For the first, Statix does not allow one sort to simply
"become" another, but the Spoofax infrastructure automatically inserts
"injection" constructors for us, in this case one named Ex2Seq. So the
first rule for `type_Seq` is straightforward:
```statix
type_Seq(s@Ex2Seq(e)) = T : -
type_Ex(e) == T,
@s.type := T.
```
where of course type_Ex needs its own declaration analogous to the above.
**/
type_Line : Line -> TYPE
type_LineSeq(ls@Line2LineSeq(l)) = T :-
type_Line(l) == T,
@ls.type := T.
/** md
The other (and in fact more typical) rule for `type_Seq`, when it actually
consists of a sequence of expressions, is a bit more involved. Fortunately
Statix provides a primitive for mapping over a list, so we can proceed as
follows:
```statix
types_Exs maps type_Ex(list(*)) = list(*)
type_Seq(s@Sequence(l)) = T :- {lt}
types_Exs(l) == lt,
lastTYPE(lt) == T,
@s.type := T.
```
Here `lastTYPE` is a function that extracts the last TYPE from a list.
Unless/until Statix develops some sort of standard library, it must be
hand-defined, as done in "statics/util.stx" like so:
```statix
{! ../statics/util.stx extract: {start: lastTYPE} !}
```
**/
types_Lines maps type_Line(list(*)) = list(*)
type_LineSeq(ls@Sequence(l)) = T :- {lt}
types_Lines(l) == lt,
lastTYPE(lt) == T,
@ls.type := T.
type_OptTermEx : OptTermEx -> TYPE
type_Line(l@OptTermEx2Line(ote)) = T :-
type_OptTermEx(ote) == T,
@l.type := T.
type_Ex : Ex -> TYPE
type_TermEx : TermEx -> TYPE
type_OptTermEx(ote@Ex2OptTermEx(e)) = T :-
type_Ex(e) == T,
@ote.type := T.
type_OptTermEx(ote@TermEx2OptTermEx(te)) = T :-
type_TermEx(te) == T,
@ote.type := T.
/** md
This brings us to the syntax rules for the basic expressions themselves,
which comprise almost all of the remaining fostr language constructs.
But first a mechanism suggested by Ivo Wilms to avoid repeating the node
type annotation in every rule:
```statix
**/
/** md */
ty_Ex : Ex -> TYPE
type_Ex(e) = ty@ty_Ex(e) :-
@e.type := ty.
/* **/
/** md
```
At this stage in fostr's development, there was no difference between a
terminated and unterminated expression, so the typing rule for that
constructor was trivial:
```statix
ty_Ex(Terminated(e)) = ty_Ex(e).
```
**/
type_TermEx(te@Terminate(e)) = T :-
type_Ex(e) == T,
@te.type := T.
/** md
Now typing literals is straightforward:
```statix
{! "\git docs/statix_works:trans/statics.stx" extract:
start: '(.*ty_Ex.Int.*\s*)'
stop: '/. ../'
!}
```
**/
ty_Ex(Int(_)) = INT().
ty_Ex(LitString(_)) = STRING().
ty_Ex(EscString(_)) = STRING().
ty_Ex(e@Stream()) = STREAM().
/** md
Finally we get to the binary operators, and here we use the pattern found in
recent versions of the
"[chicago](https://github.com/MetaBorgCube/statix-sandbox/tree/master/chicago)"
example language and in the Fall 2020 TU-Delft class lecture on
[Name Binding and Name Resolution](https://tudelft-cs4200-2020.github.io/lectures/2020/09/24/lecture5/).
This pattern lets us specify error messages.
```statix
**/
/** md */
ty_Ex(Sum(e1, e2)) = INT() :-
type_Ex(e1) == INT() | error $[Expression [e1] not an Int in sum.]@e1,
type_Ex(e2) == INT() | error $[Expression [e2] not an Int in sum.]@e2.
ty_Ex(Gets(e1, e2)) = STREAM() :- {T}
type_Ex(e1) == STREAM() | error $[Only Streams may receive items.]@e1,
type_Ex(e2) == T.
ty_Ex(To(e1, e2)) = T :-
type_Ex(e1) == T,
type_Ex(e2) == STREAM() | error $[Items may only be sent to Streams.]@e2.
/* **/
ty_Ex(Concat(e1, e2)) = STRING() :-
type_Ex(e1) == STRING() | error $[Expression [e1] not String in concat.]@e1,
type_Ex(e2) == STRING() | error $[Expression [e2] not String in concat.]@e2.
ty_Ex(Emits(e)) = STRING() :- // At the moment, only stream is stdio
type_Ex(e) == STREAM() | error $[Only Streams may emit items.]@e.
/** md
```
### Using type annotations in transformation
At this point, Statix properly types all of the valid programs of the very
rudimentary language defined by the grammar above. But the proximate purpose
for implementing this typing was to aid Haskell code generation. So how
do we actually use the assigned types in a Stratego transformation?
Statix provides a Stratego api that includes, among other items, strategies
`stx-get-ast-analysis` and `stx-get-ast-type(|analysis)` that provide access
to the assigned types. However, it's easiest to use the information via
a wrapper like this, essentially lifted from the "chicago" language project:
```stratego
{! analysis.str extract:
start: Extract.the.type
terminate: Prints.the.analyzed.type
!}
```
Now `get_type` run on a node of the analyzed AST produces the assigned `TYPE`
(as an ATerm in the constructors of sort TYPE in Statix).
Thus, you can select on the assigned type, as in the strategy to select
the correct Haskell operator to use to send an item to standard output:
```stratego
{! haskell.str extract:
start: '(.*hs_getOp.=.*)'
stop: \s
!}
```
**/
rules // multi-file entry point
@ -17,6 +269,6 @@ rules // multi-file entry point
projectOk(s).
fileOk : scope * Ex
fileOk : scope * Start
fileOk(s, Receives(_,_)).
fileOk(s, TopLevel(_)).

9
trans/util.str Normal file
View File

@ -0,0 +1,9 @@
module util
imports libstrategolib
rules
join(|infix) : [] -> ""
join(|infix) : [x | xs] -> <conc-strings>(x, <prejoin(|infix)>xs)
prejoin(|infix) : [] -> ""
prejoin(|infix) : [x | xs] -> <concat-strings>[infix,x,<prejoin(|infix)>xs]