# Parse Tamsin source to Tamsin AST, written in Tamsin.
# Distributed under a BSD-style license; see LICENSE.
# REQUIRES lib/tamsin_scanner.tamsin
# REQUIRES lib/list.tamsin
# Note that this may contain support for some features which are not in
# the current released or pre-released version.
tamsin_parser {
parse = grammar using tamsin_scanner:scanner.
grammar = {"@" & pragma & "."} &
LM ← nil &
LP ← nil &
{
production → P & "." & LP ← list(P, LP)
| module → M & LM ← list(M, LM)
} &
list:reverse(LP, nil) → LP &
MM ← module(main, LP) &
list:reverse(LM, nil) → LM &
($:equal(LP, nil) | LM ← list(module(main, LP), LM)) &
return program(LM).
module = word → N &
LP ← nil &
"{" &
{production → P & "." & LP ← list(P, LP)} &
"}" &
list:reverse(LP, nil) → LP &
return module(N, LP).
production = word → N &
F ← nil &
[formals → F] &
"=" &
expr0 → E &
return production(N, list(prodbranch(F, nil, E), nil)).
formals = L ← nil &
"(" &
term → T & L ← list(T, L) &
{"," & term → T & L ← list(T, L)} &
")" &
list:reverse(L, nil) → L &
return L
| "[" & expr0 & "]".
expr0 = expr1 → L & {("|" | "||") & expr1 → R & L ← or(L, R)} & L.
expr1 = expr2 → L & {("&" | "&&") & expr2 → R & L ← and(L, R)} & L.
expr2 = expr3 → L & ["using" & prodref → P & L ← using(L, P)
| "@" & texpr → T & L ← on(L, T)] & L.
expr3 = expr4 → L & [("→" | "->") & variable → V & L ← send(L, V)] & L.
expr4 = expr5 → L & ("/" & texpr → T &
("/" & term → T2 & return fold(L, T, T2)
| return fold(L, T, nil))
| return L).
expr5 = "(" & expr0 → E & ")" & E
| "[" & expr0 → E & "]" &
return or(E, call(prodref('$', return), list(atom(nil), nil)))
| "{" & expr0 → E & "}" & return while(E)
| "!" & expr5 → E & return not(E)
| "set" & variable → V & "=" & texpr → T & return set(V, T)
| "return" & texpr → T & return call(prodref('$', return), list(T, nil))
| "fail" & texpr → T & return call(prodref('$', fail), list(T, nil))
| "print" & texpr → T & return call(prodref('$', print), list(T, nil))
| "any" & return call(prodref('$', any), nil)
| "eof" & return call(prodref('$', 'eof'), nil)
| terminal
| variable → V &
(("←" | "<-") & texpr → T & return set(V, T)
| return call(prodref('$', return), list(V, nil)))
| sq_string → T &
$:unquote(T, '\'', '\'') → T &
return call(prodref('$', return), list(atom(T), nil))
| pq_string → T &
$:unquote(T, '“', '”') → T &
expect_chars(T) → E &
return and(E, call(prodref('$', return), list(atom(T), nil)))
| prodref → P &
L ← nil &
["(" &
texpr → T & L ← list(T, L) &
{"," & texpr → T & L ← list(T, L)} &
")"] &
list:reverse(L, nil) → L &
return call(P, L).
texpr = term → T & {"+" & term → S & T ← concat(T, S)} & T.
term = term0.
term0 = variable
| "[" & L ← atom(nil) &
[term → T & L ← constructor(list, list(T, list(L, nil))) &
{"," & term → T & L ← constructor(list, list(T, list(L, nil)))}] &
Tail ← atom(nil) &
["|" & term → Tail] &
"]" &
reverse_c(L, Tail) → L &
return L
| atom → A & L ← nil & ["(" &
term0 → T & L ← list(T, L) &
{"," & term0 → T & L ← list(T, L)} &
")"] &
list:reverse(L, nil) → L &
($:equal(L, nil) & return atom(A)
| return constructor(A, L)).
atom = word
| sq_string → T &
$:unquote(T, '\'', '\'').
terminal = terminal0 → T & return call(prodref('$', expect), list(T, nil)).
terminal0 = dq_string → T & $:unquote(T, '"', '"') → T & return atom(T)
| ("«" | "<<") & texpr → T & ("»" | ">>") & return T.
prodref = modref → M & ":" & word → P & return prodref(M, P)
| ":" & word → P & return prodref('', P)
| word → P & return prodref('', P).
modref = "$" | word.
pragma = "alias" & word & word & "=" & prodref
| "unalias" & word.
word = $:alnum.
variable = $:upper → V & return variable(V).
sq_string = $:startswith('\'').
dq_string = $:startswith('"').
pq_string = $:startswith('“').
## utility functions on the AST ##
# Given the name of a module and a program AST, return the named
# module AST found within that program, or fail.
find_module(N, program(Ms)) = find_module(N, Ms).
find_module(N1, list(module(N2, Ps), T)) =
$:equal(N1, N2) & return module(N2, Ps) | find_module(N1, T).
find_module(N, list(H, T)) = find_module(N, T).
find_module(N, nil) = fail 'no ' + N + ' module'.
# Given the name of a production and a module AST, return the named
# production AST found within that module, or fail.
find_production(N, module(MN, Ps)) = find_production(N, Ps).
find_production(N1, list(production(N2, Bs), T)) =
$:equal(N1, N2) & return production(N2, Bs) | find_production(N1, T).
find_production(N, list(H, T)) = find_production(N, T).
find_production(N, nil) = fail 'no ' + N + ' production'.
# Given the name of a module and the name of a production,
# return the production AST for module:production in the program, or fail.
find_production_global(MN, PN, P) =
find_module(MN, P) → M & find_production(PN, M).
reverse_c(constructor(list, list(Fst, list(Snd, nil))), Acc) =
Acc ← constructor(list, list(Fst, list(Acc, nil))) &
reverse_c(Snd, Acc).
reverse_c(Other, Acc) = Acc.
# Given a single-character string, return call(prodref('$', 'expect'), S)
# Given a string, return and(call(prodref('$', 'expect'), head(S)),
# expect_chars(tail(S))).
expect_chars(S) = (expect_chars_r using $:utf8) @ S.
expect_chars_r = any → C &
E ← call(prodref('$', 'expect'), list(atom(C), nil)) &
((eof & return E) | (expect_chars_r → R & return and(E, R))).
}