mirror of
https://github.com/kanaka/mal.git
synced 2024-09-19 01:28:26 +03:00
c58f50e6be
The benefit of this devianceto the process is dubious.
295 lines
10 KiB
Ada
295 lines
10 KiB
Ada
with Ada.Environment_Variables;
|
|
with Ada.Text_IO.Unbounded_IO;
|
|
|
|
with Core;
|
|
with Envs;
|
|
with Err;
|
|
with Garbage_Collected;
|
|
with Printer;
|
|
with Reader;
|
|
with Readline;
|
|
with Types.Fns;
|
|
with Types.Maps;
|
|
with Types.Sequences;
|
|
with Types.Strings;
|
|
|
|
procedure Step5_Tco is
|
|
|
|
Dbgeval : constant Boolean := Ada.Environment_Variables.Exists ("dbgeval");
|
|
|
|
use type Types.T;
|
|
use all type Types.Kind_Type;
|
|
use type Types.Strings.Instance;
|
|
|
|
function Read return Types.T_Array with Inline;
|
|
|
|
function Eval (Ast0 : in Types.T;
|
|
Env0 : in Envs.Ptr) return Types.T;
|
|
|
|
procedure Print (Ast : in Types.T) with Inline;
|
|
|
|
procedure Rep (Env : in Envs.Ptr) with Inline;
|
|
|
|
function Eval_Map (Source : in Types.Maps.Instance;
|
|
Env : in Envs.Ptr) return Types.T;
|
|
function Eval_Vector (Source : in Types.Sequences.Instance;
|
|
Env : in Envs.Ptr) return Types.T;
|
|
-- Helpers for the Eval function.
|
|
|
|
procedure Exec (Script : in String;
|
|
Env : in Envs.Ptr) with Inline;
|
|
-- Read the script, eval its elements, but ignore the result.
|
|
|
|
----------------------------------------------------------------------
|
|
|
|
function Eval (Ast0 : in Types.T;
|
|
Env0 : in Envs.Ptr) return Types.T
|
|
is
|
|
-- Use local variables, that can be rewritten when tail call
|
|
-- optimization goes to <<Restart>>.
|
|
Ast : Types.T := Ast0;
|
|
Env : Envs.Ptr := Env0;
|
|
Env_Reusable : Boolean := False;
|
|
-- True when the environment has been created in this recursion
|
|
-- level, and has not yet been referenced by a closure. If so,
|
|
-- we can reuse it instead of creating a subenvironment.
|
|
First : Types.T;
|
|
begin
|
|
<<Restart>>
|
|
if Dbgeval then
|
|
Ada.Text_IO.New_Line;
|
|
Ada.Text_IO.Put ("EVAL: ");
|
|
Print (Ast);
|
|
Envs.Dump_Stack (Env.all);
|
|
end if;
|
|
|
|
case Ast.Kind is
|
|
when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Types.Kind_Key
|
|
| Kind_Macro | Types.Kind_Function =>
|
|
return Ast;
|
|
when Kind_Symbol =>
|
|
return Env.all.Get (Ast.Str);
|
|
when Kind_Map =>
|
|
return Eval_Map (Ast.Map.all, Env);
|
|
when Kind_Vector =>
|
|
return Eval_Vector (Ast.Sequence.all, Env);
|
|
when Kind_List =>
|
|
null;
|
|
end case;
|
|
|
|
-- Ast is a list.
|
|
if Ast.Sequence.all.Length = 0 then
|
|
return Ast;
|
|
end if;
|
|
First := Ast.Sequence.all.Data (1);
|
|
|
|
-- Special forms
|
|
-- Ast is a non-empty list, First is its first element.
|
|
case First.Kind is
|
|
when Kind_Symbol =>
|
|
if First.Str.all = "if" then
|
|
Err.Check (Ast.Sequence.all.Length in 3 .. 4,
|
|
"expected 2 or 3 parameters");
|
|
declare
|
|
Tst : constant Types.T := Eval (Ast.Sequence.all.Data (2), Env);
|
|
begin
|
|
if Tst /= Types.Nil and Tst /= (Kind_Boolean, False) then
|
|
Ast := Ast.Sequence.all.Data (3);
|
|
goto Restart;
|
|
elsif Ast.Sequence.all.Length = 3 then
|
|
return Types.Nil;
|
|
else
|
|
Ast := Ast.Sequence.all.Data (4);
|
|
goto Restart;
|
|
end if;
|
|
end;
|
|
elsif First.Str.all = "let*" then
|
|
Err.Check (Ast.Sequence.all.Length = 3
|
|
and then Ast.Sequence.all.Data (2).Kind in Types.Kind_Sequence,
|
|
"expected a sequence then a value");
|
|
declare
|
|
Bindings : Types.T_Array
|
|
renames Ast.Sequence.all.Data (2).Sequence.all.Data;
|
|
begin
|
|
Err.Check (Bindings'Length mod 2 = 0, "expected even binds");
|
|
if not Env_Reusable then
|
|
Env := Envs.New_Env (Outer => Env);
|
|
Env_Reusable := True;
|
|
end if;
|
|
for I in 0 .. Bindings'Length / 2 - 1 loop
|
|
Env.all.Set (Bindings (Bindings'First + 2 * I),
|
|
Eval (Bindings (Bindings'First + 2 * I + 1), Env));
|
|
-- This call checks key kind.
|
|
end loop;
|
|
Ast := Ast.Sequence.all.Data (3);
|
|
goto Restart;
|
|
end;
|
|
elsif First.Str.all = "def!" then
|
|
Err.Check (Ast.Sequence.all.Length = 3, "expected 2 parameters");
|
|
declare
|
|
Key : Types.T renames Ast.Sequence.all.Data (2);
|
|
Val : constant Types.T := Eval (Ast.Sequence.all.Data (3), Env);
|
|
begin
|
|
Env.all.Set (Key, Val); -- Check key kind.
|
|
return Val;
|
|
end;
|
|
elsif First.Str.all = "do" then
|
|
Err.Check (1 < Ast.Sequence.all.Length, "do expects arguments");
|
|
declare
|
|
Result : Types.T;
|
|
begin
|
|
for I in 2 .. Ast.Sequence.all.Length loop
|
|
Result := Eval (Ast.Sequence.all.Data (I), Env);
|
|
end loop;
|
|
return Result;
|
|
end;
|
|
elsif First.Str.all = "fn*" then
|
|
Err.Check (Ast.Sequence.all.Length = 3, "expected 2 parameters");
|
|
declare
|
|
Params : Types.T renames Ast.Sequence.all.Data (2);
|
|
begin
|
|
Err.Check (Params.Kind in Types.Kind_Sequence,
|
|
"first argument of fn* must be a sequence");
|
|
Env_Reusable := False;
|
|
return Types.Fns.New_Function
|
|
(Params => Params.Sequence,
|
|
Ast => Ast.Sequence.all.Data (3),
|
|
Env => Env);
|
|
end;
|
|
else
|
|
-- Equivalent to First := Eval (First, Env)
|
|
-- except that we already know enough to spare a recursive call.
|
|
First := Env.all.Get (First.Str);
|
|
end if;
|
|
when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Types.Kind_Key
|
|
| Kind_Macro | Types.Kind_Function =>
|
|
-- Equivalent to First := Eval (First, Env)
|
|
-- except that we already know enough to spare a recursive call.
|
|
null;
|
|
when Types.Kind_Sequence | Kind_Map =>
|
|
-- Lists are definitely worth a recursion, and the two other
|
|
-- cases should be rare (they will report an error later).
|
|
First := Eval (First, Env);
|
|
end case;
|
|
|
|
-- Apply phase.
|
|
-- Ast is a non-empty list,
|
|
-- First is its non-special evaluated first element.
|
|
Err.Check (First.Kind in Types.Kind_Function,
|
|
"first element must be a function");
|
|
-- We are applying a function. Evaluate its arguments.
|
|
declare
|
|
Args : Types.T_Array (2 .. Ast.Sequence.all.Length);
|
|
begin
|
|
for I in Args'Range loop
|
|
Args (I) := Eval (Ast.Sequence.all.Data (I), Env);
|
|
end loop;
|
|
if First.Kind = Kind_Builtin then
|
|
return First.Builtin.all (Args);
|
|
end if;
|
|
-- Like Types.Fns.Apply, except that we use TCO.
|
|
Env := Envs.New_Env (Outer => First.Fn.all.Env);
|
|
Env_Reusable := True;
|
|
Env.all.Set_Binds (Binds => First.Fn.all.Params.all.Data,
|
|
Exprs => Args);
|
|
Ast := First.Fn.all.Ast;
|
|
goto Restart;
|
|
end;
|
|
exception
|
|
when Err.Error =>
|
|
Err.Add_Trace_Line ("eval", Ast);
|
|
raise;
|
|
end Eval;
|
|
|
|
function Eval_Map (Source : in Types.Maps.Instance;
|
|
Env : in Envs.Ptr) return Types.T
|
|
is
|
|
use all type Types.Maps.Cursor;
|
|
-- Copy the whole map so that keys are not hashed again.
|
|
Result : constant Types.T := Types.Maps.New_Map (Source);
|
|
Position : Types.Maps.Cursor := Result.Map.all.First;
|
|
begin
|
|
while Has_Element (Position) loop
|
|
Result.Map.all.Replace_Element (Position,
|
|
Eval (Element (Position), Env));
|
|
Next (Position);
|
|
end loop;
|
|
return Result;
|
|
end Eval_Map;
|
|
|
|
function Eval_Vector (Source : in Types.Sequences.Instance;
|
|
Env : in Envs.Ptr) return Types.T
|
|
is
|
|
Ref : constant Types.Sequence_Ptr
|
|
:= Types.Sequences.Constructor (Source.Length);
|
|
begin
|
|
for I in Source.Data'Range loop
|
|
Ref.all.Data (I) := Eval (Source.Data (I), Env);
|
|
end loop;
|
|
return (Kind_Vector, Ref);
|
|
end Eval_Vector;
|
|
|
|
procedure Exec (Script : in String;
|
|
Env : in Envs.Ptr)
|
|
is
|
|
Result : Types.T;
|
|
begin
|
|
for Expression of Reader.Read_Str (Script) loop
|
|
Result := Eval (Expression, Env);
|
|
end loop;
|
|
pragma Unreferenced (Result);
|
|
end Exec;
|
|
|
|
procedure Print (Ast : in Types.T) is
|
|
begin
|
|
Ada.Text_IO.Unbounded_IO.Put_Line (Printer.Pr_Str (Ast));
|
|
end Print;
|
|
|
|
function Read return Types.T_Array
|
|
is (Reader.Read_Str (Readline.Input ("user> ")));
|
|
|
|
procedure Rep (Env : in Envs.Ptr) is
|
|
begin
|
|
for Expression of Read loop
|
|
Print (Eval (Expression, Env));
|
|
end loop;
|
|
end Rep;
|
|
|
|
----------------------------------------------------------------------
|
|
|
|
Startup : constant String
|
|
:= "(def! not (fn* (a) (if a false true)))";
|
|
Repl : constant Envs.Ptr := Envs.New_Env;
|
|
begin
|
|
-- Show the Eval function to other packages.
|
|
Types.Fns.Eval_Cb := Eval'Unrestricted_Access;
|
|
-- Add Core functions into the top environment.
|
|
Core.NS_Add_To_Repl (Repl);
|
|
-- Native startup procedure.
|
|
Exec (Startup, Repl);
|
|
-- Execute user commands.
|
|
loop
|
|
begin
|
|
Rep (Repl);
|
|
exception
|
|
when Readline.End_Of_File =>
|
|
exit;
|
|
when Err.Error =>
|
|
Ada.Text_IO.Unbounded_IO.Put (Err.Trace);
|
|
end;
|
|
-- Other exceptions are really unexpected.
|
|
|
|
-- Collect garbage.
|
|
Err.Data := Types.Nil;
|
|
Repl.all.Keep;
|
|
Garbage_Collected.Clean;
|
|
end loop;
|
|
Ada.Text_IO.New_Line;
|
|
|
|
-- If assertions are enabled, check deallocations.
|
|
-- Normal runs do not need to deallocate before termination.
|
|
-- Beware that all pointers are now dangling.
|
|
pragma Debug (Garbage_Collected.Clean);
|
|
Garbage_Collected.Check_Allocations;
|
|
end Step5_Tco;
|