1
1
mirror of https://github.com/kanaka/mal.git synced 2024-09-19 17:47:53 +03:00
mal/ada.2/step3_env.adb
Nicolas Boulenguez 8185fe141c Clarify and optimize ada.2.
Makefile:
Drop OPT and -gnat2020, not used anymore.
Simplify file list now that each unit has a body.

README:
Remove obsolete items.

Global:
Restrict most pointers to a provable non-null value.

Types: merge intermediate Types.Mal into the Types package.  (the
intermediate package was created in order to prevent a circular
dependency, but is not needed anymore).
Most of the noise in the diff is caused by this change.
This allows to remove most Elaboration pragmas.
Declare most types abstract in the visible part,
enforcing the use of the constructor outside the declaring package.

Envs:
Replace the Get recursion with a more efficient loop.
Use MAL objects as key, string pointers do not change speed.
This delegates some checks from the step files.
Split the constructor and Set_Binds, so that an existing environment
can be reused during TCO.

Err:
Attempt to group the calls.
Avoid computing the message when the assertion holds.

Fns:
Declare and use the eval callback only here.
Separate function and macro interfaces.
Keep a reference to the provided parameter list instead of copying them.

Garbage_Collected:
Make explicit that Keep is not inherited.

Printer:
Remove obsolete inline indications and redundant Print_Function helper.

Maps:
Provide a cleaner interface copied from standard library.

Sequences: stop encapsulating the implementation because of the
performance hit.

Steps:
Move map and vector evaluations into separate functions for readability.
Replace return blocks with normal blocks (MAL values are not finalized
anymore).
Rename standard arrays instead of sequence_ptr when possible.
Remove some duplication and indentation from the apply phase.
Move the frequent special forms in front of the test cascade.
When an environment has been created in the same Eval, reuse it.

Strings:
Use the same garbage-collected storage model for all strings.
This seems faster than the default (mutable) string types.
Hide most of the implementation to avoid leaks.

Symbols: stop ensuring unique allocation of symbols. The reduced
garbage collection and comparison time was compensed by the
maintainance of a global hash.
2019-05-02 21:19:34 +02:00

222 lines
7.3 KiB
Ada

with Ada.Environment_Variables;
with Ada.Text_IO.Unbounded_IO;
with Envs;
with Err;
with Garbage_Collected;
with Printer;
with Reader;
with Readline;
with Types.Maps;
with Types.Sequences;
with Types.Strings;
procedure Step3_Env 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 (Ast : in Types.T;
Env : in Envs.Ptr) return Types.T;
procedure Print (Ast : in Types.T) with Inline;
procedure Rep (Env : in Envs.Ptr) with Inline;
generic
with function Ada_Operator (Left, Right : in Integer) return Integer;
function Generic_Mal_Operator (Args : in Types.T_Array) return Types.T;
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.
----------------------------------------------------------------------
function Eval (Ast : in Types.T;
Env : in Envs.Ptr) return Types.T
is
First : Types.T;
begin
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 = "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;
New_Env : constant Envs.Ptr := Envs.New_Env (Outer => Env);
begin
Err.Check (Bindings'Length mod 2 = 0, "expected even binds");
for I in 0 .. Bindings'Length / 2 - 1 loop
New_Env.all.Set (Bindings (Bindings'First + 2 * I),
Eval (Bindings (Bindings'First + 2 * I + 1), New_Env));
-- This call checks key kind.
end loop;
return Eval (Ast.Sequence.all.Data (3), New_Env);
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;
else
First := Eval (First, Env);
end if;
when others =>
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 = Kind_Builtin,
"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;
return First.Builtin.all (Args);
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;
function Generic_Mal_Operator (Args : in Types.T_Array) return Types.T
is (Kind_Number, Ada_Operator (Args (Args'First).Number,
Args (Args'Last).Number));
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;
----------------------------------------------------------------------
function Addition is new Generic_Mal_Operator ("+");
function Subtraction is new Generic_Mal_Operator ("-");
function Product is new Generic_Mal_Operator ("*");
function Division is new Generic_Mal_Operator ("/");
Repl : constant Envs.Ptr := Envs.New_Env;
begin
-- Add Core functions into the top environment.
Repl.all.Set ((Kind_Symbol, Types.Strings.Alloc ("+")),
(Kind_Builtin, Addition 'Unrestricted_Access));
Repl.all.Set ((Kind_Symbol, Types.Strings.Alloc ("-")),
(Kind_Builtin, Subtraction'Unrestricted_Access));
Repl.all.Set ((Kind_Symbol, Types.Strings.Alloc ("*")),
(Kind_Builtin, Product 'Unrestricted_Access));
Repl.all.Set ((Kind_Symbol, Types.Strings.Alloc ("/")),
(Kind_Builtin, Division 'Unrestricted_Access));
-- 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 Step3_Env;