-module(sbeezg).
-vsn('2002.0317').
-author('cpressey@gmail.com').
-copyright('This work is in the public domain; see UNLICENSE for more info').
-export([run/1, test/1]).
%%% BEGIN sbeezg.erl %%%
%%% A simple interpreter for the Sbeezg Programming Language.
%% Interpreter ---------------------------------------------------------
%% Call sbeezg:run("sbeezg-program") to interpret a Sbeezg program.
%% Empties the process dictionary as a side effect.
run(String) when list(String) ->
{ok, Toks, N} = erl_scan:string(String),
% io:fwrite("scan: ~s -> ~w~n", [String, Toks]),
erase(), % clear process dictionary for parser
case sbeezg_parser:parse(Toks ++ [{'$end',999}]) of
{ok, Prog} ->
% io:fwrite("parse: ~s -> ~w~n", [String, Prog]),
erase(), % destroy the garbage left in the process dictionary
run(Prog);
{error, {Line, Module, Message}}=Q ->
io:fwrite("Error: ~s~n", [Message]),
Q
end;
run({assign, L, F, A}=C) ->
% io:fwrite("~w~n", [C]),
L0 = get_name(L),
F0 = run(F),
A0 = run(A),
assign(L0, '$placeholder'),
R0 = execute(F0, A0),
assign(L0, R0);
run({assign, L, R}=C) ->
% io:fwrite("~w~n", [C]),
L0 = get_name(L),
R0 = run(R),
assign(L0, R0);
run({vlist, H, T}=C) ->
% io:fwrite("~w~n", [C]),
H0 = run(H),
T0 = run(T),
[H0 | T0];
run({lambda, N, A, R}=C) ->
% io:fwrite("~w~n", [C]),
{func, N, A, R};
run({alist, H, T}=C) ->
% io:fwrite("~w~n", [C]),
H0 = run(H),
T0 = run(T),
case T0 of
[] -> H0;
_ -> T0
end;
run({lit,{atom,Line,Atom}}) -> Atom;
run({lit,{integer,Line,Int}}) -> Int;
run({name, {atom,Line,Atom}}) ->
case get(Atom) of
undefined ->
io:fwrite("~w undefined!~n", [Atom]),
Atom;
N ->
N
end;
run(nil) ->
[];
run(Q) ->
io:fwrite("UNKNOWN ~w~n", [Q]),
unknown.
get_name({nlist, H, T}=C) ->
% io:fwrite("~w~n", [C]),
H0 = get_name(H),
T0 = get_name(T),
[H0 | T0];
get_name({name, {atom,Line,Atom}}) -> Atom;
get_name(nil) -> [].
%% Utility -------------------------------------------------------------
assign(Name, Value) ->
% io:fwrite("Assigning ~w to ~w~n", [Value, Name]),
case get(Name) of
NonExist when NonExist == undefined; NonExist == '$placeholder' ->
put(Name, Value);
Exist ->
io:fwrite("ERROR: Multiple Assignment to ~w=~w (~w)~n",
[Name, Exist, Value])
end,
Value.
execute(print, [H | T]) ->
io:fwrite("~w", [H]),
execute(print, T);
execute(print, []) -> ok;
execute(is, [A, B, T, F]) ->
% io:fwrite("~w", [C]),
case A of
B -> T;
_ -> F
end;
execute(pred, [X]) ->
% io:fwrite("pred ~w~n", [X]),
X - 1;
execute(succ, [X]) -> X + 1;
execute(Func, Args) when atom(Func) ->
io:fwrite("Unknown built-in '~w(~w)'~n", [Func, Args]),
unknown;
execute({func, P, B, R}, A) ->
P0 = fresh(P),
B0 = fresh(B),
P1 = get_name(P0),
bind(P1, A),
run(B0),
run(R).
fresh({assign, L, F, A}=C) -> {assign, fresh(L), fresh(F), fresh(A)};
fresh({assign, L, R}=C) -> {assign, fresh(L), fresh(R)};
fresh({vlist, H, T}=C) -> {vlist, fresh(H), fresh(T)};
fresh({lambda, X, A}=C) -> {lambda, fresh(X), fresh(A)};
fresh({nlist, H, T}=C) -> {nlist, fresh(H), fresh(T)};
fresh({alist, H, T}=C) -> {alist, fresh(H), fresh(T)};
fresh({lit,X}=C) -> C;
fresh({name,{atom,Line,Atom}}=C) ->
case get(Atom) of
undefined -> C;
_ ->
fresh({name, {atom, Line, list_to_atom([$_ | atom_to_list(Atom)])}})
end;
fresh(X) -> X.
bind([],[]) -> ok;
bind([HN|TN],[HV|TV]) ->
% io:fwrite("bind ~w to ~w~n", [HV,HN]),
assign(HN,HV),
bind(TN,TV).
%% User interface ------------------------------------------------------
test([N]) -> test(list_to_integer(N));
test(N) ->
R = run(prg(N)),
E = erase(),
{R, E}.
prg(1) -> "a={b|p=*print;c=p(b)|b};d=a(*foo);e=a(*bar);f=a(*baz)";
prg(2) -> "a={b|p=*print;c=p(b)|b};d={e,f|g=e(f)|g};h=d(a,*hello)";
prg(3) -> "p=*print;i=*is;r=i(*a,*b,*troo,*fall);q=p(r)";
prg(4) -> "f={a,b|p=*print;g=p(a);k=b(a,b)|a};l=f(*hello,f)";
prg(5) -> "f={a,b|i=*is;s=*pred;p=*print;g=p(*beer);h=s(a);"
"ln={x,m|z=x|x};lg={y,n|q=n(y,n)|y};j=i(h,0,ln,lg);"
"k=j(h,b)|a};l=f(99,f)";
prg(6) -> "f=*a;f=*b"; % intentionally erroneous
prg(_) -> unknown.
%%% END of sbeezg.erl %%%