forked from glen/fostr
Compare commits
2 Commits
Author | SHA1 | Date | |
---|---|---|---|
7feddbcfbe | |||
cc89ad1e93 |
23
.drone.yml
23
.drone.yml
@ -29,7 +29,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 +52,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 +67,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
|
||||
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -12,10 +12,16 @@
|
||||
|
||||
.pydevproject
|
||||
|
||||
a.out
|
||||
|
||||
*.aterm
|
||||
/site
|
||||
bin/ng
|
||||
tests/extracted/*
|
||||
tests/*.js
|
||||
tests/*.py
|
||||
tests/*.hs
|
||||
tests/*.ml
|
||||
tests/*.cmi
|
||||
tests/*.cmo
|
||||
adhoc*
|
||||
|
@ -13,12 +13,16 @@ 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)
|
||||
@ -33,6 +37,11 @@ for path in TEST_LIST:
|
||||
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}"
|
||||
|
20
bin/fosgen
20
bin/fosgen
@ -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 $?
|
||||
|
@ -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
40
bin/let_sun_shine
Executable 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
|
@ -9,8 +9,14 @@ diffed=0
|
||||
for dir in tests/extracted/*; do
|
||||
for file in $dir/*.$ext; do
|
||||
((total++))
|
||||
if [[ -f ${file%.*}.in ]]; then
|
||||
cat ${file%.*}.in | $command $file > $file.out
|
||||
result=$?
|
||||
else
|
||||
$command $file > $file.out
|
||||
if [[ $? -ne 0 ]]; then
|
||||
result=$?
|
||||
fi
|
||||
if [[ $result -ne 0 ]]; then
|
||||
echo ERROR: $command $file failed.
|
||||
((failed++))
|
||||
else
|
||||
|
@ -4,3 +4,4 @@ menus
|
||||
action: "Python" = to-python
|
||||
action: "Javascript" = to-javascript
|
||||
action: "Haskell" = to-haskell
|
||||
action: "OCaml" = to-ocaml
|
||||
|
@ -9,7 +9,7 @@ plugins:
|
||||
- search
|
||||
- semiliterate:
|
||||
ignore_folders: [target, lib]
|
||||
exclude_extensions: ['.o', '.hi']
|
||||
exclude_extensions: ['.o', '.hi', '.cmi', '.cmo']
|
||||
extract_standard_markdown:
|
||||
terminate: <!-- /md -->
|
||||
theme:
|
||||
|
@ -42,10 +42,13 @@ context-free syntax
|
||||
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}
|
||||
|
||||
@ -53,7 +56,7 @@ context-free priorities
|
||||
|
||||
Ex.To
|
||||
> Ex.DefTo
|
||||
> Ex.Sum
|
||||
> {Ex.Sum Ex.Concat}
|
||||
> Ex.DefGets
|
||||
> Ex.Gets,
|
||||
|
||||
|
@ -59,8 +59,8 @@ For example, this snippet generates the following Python:
|
||||
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.)
|
||||
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...",
|
||||
@ -108,7 +108,7 @@ again. That way we can use the general (left-associative)
|
||||
parse to TopLevel(
|
||||
Gets(Gets(Gets(Gets(DefGets(LitString("'Two and '")),Int("2")),
|
||||
LitString("' make '")),Sum(Int("2"),Int("2"))),
|
||||
EscString("\"./n\"")))
|
||||
EscString("\".\n\"")))
|
||||
/** writes
|
||||
Two and 2 make 4.
|
||||
**/
|
||||
@ -293,4 +293,38 @@ test emit_several_default [[
|
||||
>>>
|
||||
]] parse succeeds
|
||||
/** writes
|
||||
3399677527121313*/
|
||||
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.
|
||||
**/
|
||||
|
@ -17,6 +17,7 @@ rules
|
||||
|
||||
defStream: DefGets(x) -> Gets(Stream(), x)
|
||||
defStream: DefTo(x) -> To(x, Stream())
|
||||
defStream: DefEmits() -> Emits(Stream())
|
||||
|
||||
strategies
|
||||
|
||||
|
@ -6,6 +6,7 @@ imports
|
||||
pp
|
||||
outline
|
||||
analysis
|
||||
ocaml
|
||||
haskell
|
||||
javascript
|
||||
python
|
||||
|
@ -10,7 +10,7 @@ rules
|
||||
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 get get the Statix-associated type when we need to).
|
||||
(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.
|
||||
@ -23,6 +23,7 @@ rules
|
||||
import System.IO
|
||||
data IOStream = StdIO
|
||||
|
||||
-- Danger: These currently assume the stream is StdIO
|
||||
gets :: Show b => a -> b -> IO a
|
||||
gets s d = do
|
||||
putStr(show d)
|
||||
@ -33,6 +34,10 @@ rules
|
||||
putStr(d)
|
||||
return s
|
||||
|
||||
emit s = do
|
||||
l <- getLine
|
||||
return (l ++ "\n")
|
||||
|
||||
main = do
|
||||
[<Preactions>()]return [val]]
|
||||
|
||||
@ -41,6 +46,7 @@ rules
|
||||
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"
|
||||
@ -52,6 +58,10 @@ rules
|
||||
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
|
||||
|
@ -3,24 +3,41 @@ imports libstrategolib signatures/- util
|
||||
|
||||
rules
|
||||
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;
|
||||
}
|
||||
|
||||
const _fostr_body = async () => {
|
||||
// End of preamble
|
||||
|
||||
[x]]
|
||||
[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: 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
|
||||
|
||||
|
66
trans/ocaml.str
Normal file
66
trans/ocaml.str
Normal 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
|
@ -8,6 +8,8 @@ rules
|
||||
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
|
||||
@ -21,8 +23,10 @@ rules
|
||||
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
|
||||
|
||||
|
@ -222,6 +222,13 @@ This pattern lets us specify error messages.
|
||||
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
|
||||
```
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user