1
1
mirror of https://github.com/kanaka/mal.git synced 2024-09-19 01:28:26 +03:00
mal/ada.2/step5_tco.adb
Nicolas Boulenguez c58f50e6be ada.2 switch back do from built-in function to special form
The benefit of this devianceto the process is dubious.
2019-05-08 03:13:47 +02:00

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;