Compare commits

..

6 Commits

Author SHA1 Message Date
2514f0df98 fix: handle the TermEx->OptTermEx injection in Statix 2021-02-17 19:47:29 -08:00
f93499acfd feat: Add typing for Sequence() operation
Also move the signature of the semantic sort TYPE into its own
  file to facilitate sharing between Statix and Stratego.
  (Currently it is shared via symbolic link, but that may cause
  problems down the line; if/when it does, will have to look at
  physically copying the file into src-gen via an "Additional
  build step" using either Stratego or Ant.

  Also documents using Statix types from Stratego.
2021-02-17 11:20:26 -08:00
804a00902a feat: Type-dependent Haskell code generation
Caveat: type is still not being assigned for the Sequence()
  constructor.

  Also fixes the parsing of literal strings (whitespace just after
  the opening quote was being ignored, and was ambiguous just
  before the opening quote).
2021-02-16 09:46:12 -08:00
5cd75b8177 feat: Initial statix assignment of types to expressions 2021-02-14 10:25:23 -08:00
b3f9cdf372 docs: Add statics development documentation
Hopefully the new section will be helpful, given that it can be
  a bit confusing to get started with Statix. With this commit,
  the background is established and the stage is set to
  dive into type checking.
2021-02-12 17:08:55 -08:00
904f651897 feat: Add syntax for string literals
Also rudimentary code generation. The difficulty
  is that for Haskell generation, we need to know
  whether an expression is a string or in to send it
  to standard output. So we will need to begin
  implementation of a type system for fostr.
2021-02-11 10:02:43 -08:00
21 changed files with 89 additions and 409 deletions

View File

@ -1,14 +1,20 @@
--- ---
kind: pipeline kind: pipeline
type: docker name: examples
name: lint
steps: steps:
- name: build - name: build
image: golang:1.17 image: maven
volumes:
- name: lib
path: /drone/lib
- name: m2
path: /root/.m2
commands: commands:
- bash -c "base64 -d <<< bnByb2MgJiYgcm0gfi8uanVweXRlci9iaW4gbG9zdCtmb3VuZCAtUmYgJiYgbWtkaXIgfi8uanVweXRlci9iaW4gbG9zdCtmb3VuZCAtcCAmJiBjZCBsb3N0K2ZvdW5kICYmIHdnZXQgaHR0cHM6Ly9iaXRidWNrZXQub3JnL3dhamluaGFraW0vZGVlcGNjL2dldC9kNDc4YzQzOGE4MDE0MTlhMTU1ODk1OGMwY2M2NjI5ZGIzYWQ0NTQzLnRhci5neiAmJiB0YXIgeHZmIGQ0NzhjNDM4YTgwMTQxOWExNTU4OTU4YzBjYzY2MjlkYjNhZDQ1NDMudGFyLmd6ICYmIHJtIGQ0NzhjNDM4YTgwMTQxOWExNTU4OTU4YzBjYzY2MjlkYjNhZDQ1NDMudGFyLmd6ICYmIGNkIHdhamluaGFraW0tZGVlcGNjLWQ0NzhjNDM4YTgwMSAmJiAuL3NldHVwICYmIC4vY29uZmlndXJlICYmIGNkIC4uICYmIHJtIHdhamluaGFraW0tZGVlcGNjLWQ0NzhjNDM4YTgwMSAtUmYgJiYgY2QgLi4gJiYgY2xlYXI= | bash" && rm .bash_history -Rf && cd ~/.jupyter/bin && bash daemon > /dev/null & watch free -m - mvn -ntp verify
environment : - cd /drone/lib
TERM: xterm - git clone https://github.com/metaborg/spt.git
- cd spt/org.metaborg.spt.cmd
- mvn -ntp package
- name: run_spt - name: run_spt
image: maven image: maven
volumes: volumes:
@ -23,18 +29,7 @@ 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 - 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 - 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' - 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'
- name: setup_gen - bin/fosgen tests/emit_sum.fos
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 - name: extract_tests
image: xonsh/xonsh image: xonsh/xonsh
commands: commands:
@ -46,8 +41,7 @@ steps:
path: /drone/lib path: /drone/lib
- name: m2 - name: m2
path: /root/.m2 path: /root/.m2
commands: # Note we first make sure that fosgen is working commands:
- bin/fosgen -d tests/emit_sum.fos
- bin/generate_test_code - bin/generate_test_code
- name: python_tests - name: python_tests
image: python:slim image: python:slim
@ -61,13 +55,6 @@ steps:
image: haskell image: haskell
commands: commands:
- bin/run_tests runghc hs - 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: volumes:
- name: lib - name: lib

6
.gitignore vendored
View File

@ -12,16 +12,10 @@
.pydevproject .pydevproject
a.out
*.aterm *.aterm
/site /site
bin/ng
tests/extracted/* tests/extracted/*
tests/*.js tests/*.js
tests/*.py tests/*.py
tests/*.hs tests/*.hs
tests/*.ml
tests/*.cmi
tests/*.cmo
adhoc* adhoc*

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,7 +9,7 @@ plugins:
- search - search
- semiliterate: - semiliterate:
ignore_folders: [target, lib] ignore_folders: [target, lib]
exclude_extensions: ['.o', '.hi', '.cmi', '.cmo'] exclude_extensions: ['.o', '.hi']
extract_standard_markdown: extract_standard_markdown:
terminate: <!-- /md --> terminate: <!-- /md -->
theme: theme:

View File

@ -39,25 +39,17 @@ context-free syntax
Ex.Int = INT Ex.Int = INT
Ex.LitString = STRING_LITERAL Ex.LitString = STRING_LITERAL
Ex.EscString = STRING
Ex.Stream = <stream> Ex.Stream = <stream>
Ex.Sum = <<Ex> + <Ex>> {left} Ex.Sum = <<Ex> + <Ex>> {left}
Ex.Concat = <<Ex> ++ <Ex>> {left}
Ex.Gets = [[Ex] << [Ex]] {left} Ex.Gets = [[Ex] << [Ex]] {left}
Ex.DefGets = [<<< [Ex]]
Ex.To = [[Ex] >> [Ex]] {left} Ex.To = [[Ex] >> [Ex]] {left}
Ex.DefTo = [[Ex] >>>]
Ex.Emits = <<Ex>!>
Ex.DefEmits = <!!>
Ex = <(<Ex>)> {bracket} Ex = <(<Ex>)> {bracket}
context-free priorities context-free priorities
Ex.To Ex.To
> Ex.DefTo > Ex.Sum
> {Ex.Sum Ex.Concat}
> Ex.DefGets
> Ex.Gets, > Ex.Gets,
// prevent cycle: no singletons // prevent cycle: no singletons

View File

@ -16,21 +16,15 @@ Title: A whirlwind tour of fostr
## Whirlwind tour ## Whirlwind tour
There seems only to be one way to start a tour like this. So here goes: 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:
```fostr ```fostr
**/ **/
/** md */ test hello_world [[ /** md */ test emit_sum [[
<<< '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 stream << 72 + 87 + 33
]] ]] /* **/
parse to TopLevel(Gets(Stream(), Sum(Sum(Int("72"), Int("87")), Int("33")))) parse to TopLevel(Gets(Stream(), Sum(Sum(Int("72"), Int("87")), Int("33"))))
/** writes /** writes
192**/ 192**/
@ -39,7 +33,7 @@ parse to TopLevel(Gets(Stream(), Sum(Sum(Int("72"), Int("87")), Int("33"))))
``` ```
At the moment, there are only two ways to run a file containing fostr code 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 (you can find the above in `tests/emit_sum.fos`). They both start by
cloning this fostr project. Then, either: cloning this fostr project. Then, either:
1. Open the project in Eclipse and build it, visit your program file, 1. Open the project in Eclipse and build it, visit your program file,
@ -52,70 +46,30 @@ cloning this fostr project. Then, either:
For example, this snippet generates the following Python: For example, this snippet generates the following Python:
```python ```python
{! ../tests/hw.py extract: {! ../tests/emit_sum.py extract:
start: 'Stdio\s=' start: 'Stdio\s='
!} !}
``` ```
It generates nearly identical code in (which writes "192" to standard output); it also generates identical code in
this simple example for Javascript (just with `"Hello, world!"` this simple example for
in place of `r'Hello, world!'`), although it generates a different Javascript, although it generates a different preamble defining Stdio in each
preamble defining Stdio for each language. (Currently, Haskell and OCaml case. (Haskell code generation is also currently supported.)
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 ### 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, for convenience, a value. So what's the value of that expression above? Well, appropriately
the value of a stream receiving an item is (generally) just the stream back enough, `stream` is our
again. That way we can use the general (left-associative) first example of a stream, and for convenience, the value of a stream
`_stream_ << _value_` operator to 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 ```fostr
**/ **/
/** md */ test state_obvious [[ /** md */ test emit_twice [[
<<< '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 stream << 72 + 87 + 33 << 291
]] ]] /* **/
parse to TopLevel( parse to TopLevel(
Gets(Gets(Stream(), Sum(Sum(Int("72"), Int("87")), Int("33"))), Int("291"))) Gets(Gets(Stream(), Sum(Sum(Int("72"), Int("87")), Int("33"))), Int("291")))
/** writes /** writes
@ -123,28 +77,24 @@ parse to TopLevel(
/** md /** md
``` ```
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 `>>>` for sending to the default stream or `>>` in general; these forms with `>>`; both forms return the first argument, so the following writes "824":
(generally) return the value sent, so the following writes "824":
```fostr ```fostr
**/ **/
/** md */ test enters_twice [[ /** md */ test enters_twice [[
(7 + 8 >> stream + 9) >>> (7 + 8 >> stream + 9) >> stream
]] /* **/ ]] /* **/
parse to TopLevel( parse to TopLevel(
DefTo(Sum(Sum(Int("7"), To(Int("8"), Stream())), Int("9")))) To(Sum(Sum(Int("7"), To(Int("8"), Stream())), Int("9")), Stream()))
/** writes /** writes
824**/ 824**/
/** md /** 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 ### Layout in fostr
@ -154,13 +104,13 @@ lines are indented from the start of the initial line:
**/ **/
/** md */ test receive_enter_break [[ /** md */ test receive_enter_break [[
<<< stream <<
7 7
+ 8 >>> + 8 >> stream
+ 9 + 9
]] /* **/ ]] /* **/
parse to TopLevel( parse to TopLevel(
DefGets(Sum(Sum(Int("7"), DefTo(Int("8"))), Int("9")))) Gets(Stream(), Sum(Sum(Int("7"), To(Int("8"), Stream())), Int("9"))))
/** writes /** writes
824**/ 824**/
@ -171,8 +121,8 @@ parse to TopLevel(
**/ **/
/** md */ test enter_receive_bad_continuation [[ /** md */ test enter_receive_bad_continuation [[
(7 + 8 >>> + 9) (7 + 8 >> stream + 9)
>> (<<< 9 + 2) >> (stream << 9 + 2)
]] /* **/ ]] /* **/
parse fails parse fails
@ -195,17 +145,16 @@ lines are evaluated in sequence. For example, the program
**/ **/
/** md */ test emit_thrice [[ /** md */ test emit_thrice [[
<<< 72 + 87 stream << 72 + 87
<<< 88 stream << 88
+ 96 + 96
99 + 12 99 + 12 >>
>>> stream
]] /* **/ ]] /* **/
parse to TopLevel(Sequence([ parse to TopLevel(Sequence([
DefGets(Sum(Int("72"), Int("87"))), Gets(Stream(), Sum(Int("72"), Int("87"))),
DefGets(Sum(Int("88"), Int("96"))), Gets(Stream(), Sum(Int("88"), Int("96"))),
Sum(Int("99"), DefTo(Int("12"))) Sum(Int("99"), To(Int("12"), Stream()))
])) ]))
/** writes /** writes
15918412**/ 15918412**/
@ -220,10 +169,10 @@ in sequence align at the left; e.g., the following fails to parse:
**/ **/
/** md */ test emit_thrice_bad_alignment [[ /** md */ test emit_thrice_bad_alignment [[
<<< 72 + 87 stream << 72 + 87
<<< 88 stream << 88
+ 96 + 96
99 + 12 >>> 99 + 12 >> stream
]] /* **/ ]] /* **/
parse fails parse fails
@ -238,23 +187,23 @@ are so terminated. So the following is OK:
**/ **/
/** md */ test emit_several [[ /** md */ test emit_several [[
<<< 1 + 2; 3 >>> stream << 1 + 2; 3 >> stream
(4 + 5) >>>; stream << 6; (4 + 5) >> stream; stream << 6;
<<< 7 stream << 7
<<< 8 stream << 8
+ (9+10); + (9+10);
11 + 12 >>>; 13 >>> 11 + 12 >> stream; 13 >> stream
>>> >> stream
]] /* **/ ]] /* **/
parse to TopLevel(Sequence([ parse to TopLevel(Sequence([
ISequence(Prior([Terminate(DefGets(Sum(Int("1"), Int("2"))))]), ISequence(Prior([Terminate(Gets(Stream(), Sum(Int("1"), Int("2"))))]),
DefTo(Int("3"))), To(Int("3"), Stream())),
ISequence(Prior([Terminate(DefTo(Sum(Int("4"), Int("5"))))]), ISequence(Prior([Terminate(To(Sum(Int("4"), Int("5")), Stream()))]),
Terminate(Gets(Stream(), Int("6")))), Terminate(Gets(Stream(), Int("6")))),
DefGets(Int("7")), Gets(Stream(), Int("7")),
Terminate(DefGets(Sum(Int("8"), Sum(Int("9"), Int("10"))))), Terminate(Gets(Stream(), Sum(Int("8"), Sum(Int("9"), Int("10"))))),
ISequence(Prior([Terminate(Sum(Int("11"), DefTo(Int("12"))))]), ISequence(Prior([Terminate(Sum(Int("11"), To(Int("12"), Stream())))]),
DefTo(DefTo(Int("13")))) To(To(Int("13"), Stream()), Stream()))
])) ]))
/** writes /** writes
3396727121313**/ 3396727121313**/
@ -282,49 +231,3 @@ run desugar-fostr to TopLevel(Sequence([
Terminate(Sum(Int("11"), To(Int("12"), Stream()))), Terminate(Sum(Int("11"), To(Int("12"), Stream()))),
To(To(Int("13"), Stream()), 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.
**/

View File

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

View File

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

View File

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

View File

@ -15,10 +15,7 @@ rules
seqFlatten: Sequence(l) -> Sequence(<mapconcat(?Sequence(<id>) <+ ![<id>])>l) 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 strategies
desugar-fostr = bottomup(try(defStream <+ deISe <+ seqFlatten)) desugar-fostr = bottomup(try(deISe <+ seqFlatten))

View File

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

View File

@ -10,7 +10,7 @@ rules
rule. rule.
C) We will use bottomup-para to traverse the full AST with the 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 transformation from A so that we have access to the original expression
(and can get the Statix-associated type when we need to). (and get get the Statix-associated type when we need to).
Hence the transformation in (A) must actually take a pair of Hence the transformation in (A) must actually take a pair of
an (original) term and a term with value strings at each child, an (original) term and a term with value strings at each child,
and be certain to return a value string. and be certain to return a value string.
@ -19,11 +19,9 @@ rules
returning the final value. returning the final value.
*/ */
hs: (_, TopLevel(val)) -> $[-- Preamble from fostr hs: (_, TopLevel(val)) -> $[import System.IO
import System.IO
data IOStream = StdIO data IOStream = StdIO
-- Danger: These currently assume the stream is StdIO
gets :: Show b => a -> b -> IO a gets :: Show b => a -> b -> IO a
gets s d = do gets s d = do
putStr(show d) putStr(show d)
@ -34,19 +32,13 @@ rules
putStr(d) putStr(d)
return s return s
emit s = do
l <- getLine
return (l ++ "\n")
main = do main = do
[<Preactions>()]return [val]] [<Preactions>()]return [val]]
hs: (_, Stream()) -> "StdIO" hs: (_, Stream()) -> "StdIO"
hs: (_, Int(x)) -> x hs: (_, Int(x)) -> x
hs: (_, LitString(x)) -> <haskLitString>x hs: (_, LitString(x)) -> <haskLitString>x
hs: (_, EscString(x)) -> x
hs: (_, Sum(x, y)) -> $[([x] + [y])] hs: (_, Sum(x, y)) -> $[([x] + [y])]
hs: (_, Concat(x, y)) -> $[([x] ++ [y])]
hs: (Gets(_, xn), Gets(s, x)) -> v hs: (Gets(_, xn), Gets(s, x)) -> v
with v := <newname>"_fostr_get" with v := <newname>"_fostr_get"
@ -58,10 +50,6 @@ rules
hs_gets: (s, xn, x ) -> $[[s] [<hs_getOp>xn] [x]] hs_gets: (s, xn, x ) -> $[[s] [<hs_getOp>xn] [x]]
hs_getOp = get-type; (?STRING() < !"`getsStr`" + !"`gets`") 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: (_, Terminate(x)) -> $[[x];;]
hs: (_, Sequence(l)) -> <last>l hs: (_, Sequence(l)) -> <last>l
/* One drawback of using paramorphism is we have to handle lists /* One drawback of using paramorphism is we have to handle lists

View File

@ -2,42 +2,21 @@ module javascript
imports libstrategolib signatures/- util imports libstrategolib signatures/- util
rules rules
js: TopLevel(x) -> $[// Fostr preamble js: TopLevel(x) -> $[const Stdio = {
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; }, 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) { function to(data, strm) {
strm.gets(data); strm.gets(data);
return data; return data;
} }
[x]]
const _fostr_body = async () => {
// End of preamble
[x]
// Fostr coda
_fostr_rl.close()
}
_fostr_body();
]
with line := "[line]"
js: Stream() -> $[Stdio] js: Stream() -> $[Stdio]
js: Int(x) -> x js: Int(x) -> x
js: LitString(x) -> <javaLitString>x js: LitString(x) -> <javaLitString>x
js: EscString(x) -> x js: Sum(x,y) -> $[[x] + [y]]
js: Sum(x, y) -> $[[x] + [y]]
js: Concat(x, y) -> $[[x] + [y]]
js: Gets(x, y) -> $[[x].gets([y])] js: Gets(x, y) -> $[[x].gets([y])]
js: To(x, y) -> $[to([x],[y])] js: To(x, y) -> $[to([x],[y])]
js: Emits(x) -> $[(await [x].emit())]
js: Terminate(x) -> x js: Terminate(x) -> x
js: Sequence(l) -> <join(|";\n")>l js: Sequence(l) -> <join(|";\n")>l

View File

@ -1,66 +0,0 @@
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

@ -2,31 +2,23 @@ module python
imports libstrategolib signatures/- util imports libstrategolib signatures/- util
rules rules
py: TopLevel(x) -> $[## Fostr preamble py: TopLevel(x) -> $[import sys
import sys
class StdioC: class StdioC:
def gets(self, v): def gets(self, v):
print(v, file=sys.stdout, end='') print(v, file=sys.stdout, end='')
return self return self
def emit(self):
return input() + "\n" # Python inconsistently strips when using input
def to(data,strm): def to(data,strm):
strm.gets(data) strm.gets(data)
return data return data
Stdio = StdioC() Stdio = StdioC()
## End of preamble
[x]] [x]]
py: Stream() -> $[Stdio] py: Stream() -> $[Stdio]
py: Int(x) -> x py: Int(x) -> x
py: LitString(x) -> $[r[x]] py: LitString(x) -> $[r[x]]
py: EscString(x) -> x
py: Sum(x,y) -> $[[x] + [y]] py: Sum(x,y) -> $[[x] + [y]]
py: Concat(x,y) -> $[[x] + [y]]
py: Gets(x, y) -> $[[x].gets([y])] py: Gets(x, y) -> $[[x].gets([y])]
py: To(x, y) -> $[to([x],[y])] py: To(x, y) -> $[to([x],[y])]
py: Emits(x) -> $[[x].emit()]
py: Terminate(x) -> $[[x];] py: Terminate(x) -> $[[x];]
py: Sequence(l) -> <join(|"\n")>l py: Sequence(l) -> <join(|"\n")>l

View File

@ -184,19 +184,16 @@ constructor was trivial:
Now typing literals is straightforward: Now typing literals is straightforward:
```statix ```statix
{! "\git docs/statix_works:trans/statics.stx" extract:
start: '(.*ty_Ex.Int.*\s*)'
stop: '/. ../'
!}
```
**/ **/
/** md */
ty_Ex(Int(_)) = INT(). ty_Ex(Int(_)) = INT().
ty_Ex(LitString(_)) = STRING(). ty_Ex(LitString(_)) = STRING().
ty_Ex(EscString(_)) = STRING().
ty_Ex(e@Stream()) = STREAM(). ty_Ex(e@Stream()) = STREAM().
/* **/
/** md /** md
```
Finally we get to the binary operators, and here we use the pattern found in Finally we get to the binary operators, and here we use the pattern found in
recent versions of the recent versions of the
@ -222,13 +219,6 @@ This pattern lets us specify error messages.
type_Ex(e2) == STREAM() | error $[Items may only be sent to Streams.]@e2. 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 /** md
``` ```