diff --git a/ada.2/Makefile b/ada.2/Makefile index 3cc1a3e8..6fa626ce 100644 --- a/ada.2/Makefile +++ b/ada.2/Makefile @@ -9,7 +9,7 @@ else endif # Compiler arguments. -CARGS = -gnat2020 $(OPT) $(ADAFLAGS) +CARGS = $(ADAFLAGS) # Linker arguments. LARGS = $(LDFLAGS) -lreadline @@ -33,25 +33,26 @@ clean: # Tell Make how to detect out-of-date executables, and let gnatmake do # the rest when it must be executed. -TYPES := \ - envs.ads envs.adb \ - err.ads err.adb \ - eval_cb.ads \ - garbage_collected.ads garbage_collected.adb \ - printer.ads printer.adb \ - reader.ads reader.adb \ - readline.ads \ - types-atoms.ads types-atoms.adb \ - types-builtins.ads types-builtins.adb \ - types-fns.ads types-fns.adb \ - types-sequences.ads types-sequences.adb \ - types-mal.ads types-mal.adb \ - types-maps.ads types-maps.adb \ - types-symbols-names.ads \ - types-symbols.ads types-symbols.adb \ - types.ads -CORE := \ - core.ads core.adb +sources = $(foreach unit,$1,$(unit).adb $(unit).ads) +TYPES := $(call sources,\ + envs \ + err \ + garbage_collected \ + printer \ + reader \ + readline \ + types \ + types-atoms \ + types-builtins \ + types-fns \ + types-macros \ + types-maps \ + types-sequences \ + types-strings \ +) +CORE := $(call sources,\ + core \ +) $(step0) : %: %.adb $(step13): %: %.adb $(TYPES) diff --git a/ada.2/README b/ada.2/README index f05e7a70..4bb287d4 100644 --- a/ada.2/README +++ b/ada.2/README @@ -2,62 +2,38 @@ Comparison with the first Ada implementation. -- The first implementation was deliberately compatible with all Ada -compilers, while this one illustrates various Ada 2020 features: +compilers, while this one illustrates various Ada 2012 features: assertions, preconditions, invariants, initial assignment for limited -types, limited imports, indexing aspects... +types, limited imports... The variant MAL type is implemented with a discriminant instead of object-style dispatching. This allows more static and dynamic checks, but also two crucial performance improvements: * Nil, boolean, integers and pointers to built-in functions are passed by value without dynamic allocation. -* Lists are implemented as C-style arrays, and most of them can be +* Lists are implemented as C-style arrays, and can often be allocated on the stack. Another difference is that a minimal form of garbage collecting is implemented, removing objects not referenced from the main -environment. Reference counting is convenient for symbols or strings, -but never deallocates cyclic structures. The implementation collects +environment. Reference counting does not seem efficient even for symbols, +and never deallocates cyclic structures. The implementation collects garbage after each Read-Eval-Print cycle. It would be much more difficult to collect garbage inside scripts. If this is ever done, it would be better to reimplement load-file in Ada and run a cycle after each root evaluation. +It is possible to execute the recursion marking references in parallel +with the recursion printing the result, which does not modify anything +and ignores the reference marking. This works but is less performant +than sequential execution even with Linux threads and a single task +initialized at startup. +Each pointer type goes on using its own memory pool, enabling better +performance when the designated subtype has a fixed size. The eventual performances compete with C-style languages, allthough all user input is checked (implicit language-defined checks like array bounds and discriminant consistency are only enabled during tests). -Notes for contributors that do not fit in a specific package. --- - -* All packages can call Eval back via a reference in the Eval_Cb - package, set during startup. I am interested in a prettier solution - ensuring a valid value during elaboration. - Note that generic packages cannot export access values. - -* Symbol pointers are non null, new variables must be assigned - immediately. This is usually enforced by a hidden discriminant, but - here we want the type to become a field inside Types.Mal.T. So the - check happens at run time with a private invariant. - - The finalize procedure may be called twice, so it does nothing when - the reference count is zero, meaning that we are reaching Finalize - recursively. - -* In implementations with reference counting, a consistent object - (that will be deallocated automatically) must be built before any - exception is raised by user code (for example the 'map' built-in - function may run user code). Garbage collection simplifies a lot - this kind of situations. - -* Each module encapsulating dynamic allocation counts allocations and - deallocations. With debugging options, a failure is reported if - - too many deallocation happen (via a numeric range check) - - all storage is not freed (via a dedicated call from the step file) - - The main program only checks that the garbage collector removes all - allocations at the end of execution. - Debugging -- diff --git a/ada.2/core.adb b/ada.2/core.adb index 1e53c503..5c00bb55 100644 --- a/ada.2/core.adb +++ b/ada.2/core.adb @@ -4,28 +4,27 @@ with Ada.Strings.Unbounded; with Ada.Text_IO.Unbounded_IO; with Err; +with Printer; +with Reader; with Types.Atoms; with Types.Builtins; with Types.Fns; -with Types.Mal; with Types.Maps; with Types.Sequences; -with Types.Symbols.Names; -with Printer; -with Reader; +with Types.Strings; package body Core is - use Types; package ASU renames Ada.Strings.Unbounded; + use all type Types.Kind_Type; -- Used by time_ms. Start_Time : constant Ada.Calendar.Time := Ada.Calendar.Clock; generic - Kind : in Kind_Type; - function Generic_Kind_Test (Args : in Mal.T_Array) return Mal.T; - function Generic_Kind_Test (Args : in Mal.T_Array) return Mal.T is + Kind : in Types.Kind_Type; + function Generic_Kind_Test (Args : in Types.T_Array) return Types.T; + function Generic_Kind_Test (Args : in Types.T_Array) return Types.T is begin Err.Check (Args'Length = 1, "expected 1 parameter"); return (Kind_Boolean, Args (Args'First).Kind = Kind); @@ -33,84 +32,82 @@ package body Core is generic with function Ada_Operator (Left, Right : in Integer) return Integer; - function Generic_Mal_Operator (Args : in Mal.T_Array) return Mal.T; - function Generic_Mal_Operator (Args : in Mal.T_Array) return Mal.T is + function Generic_Mal_Operator (Args : in Types.T_Array) return Types.T; + function Generic_Mal_Operator (Args : in Types.T_Array) return Types.T is begin - Err.Check (Args'Length = 2, "expected 2 parameters"); - Err.Check (Args (Args'First).Kind = Kind_Number, - "parameter 1 must be a number"); - Err.Check (Args (Args'Last).Kind = Kind_Number, - "parameter 2 must be a number"); + Err.Check (Args'Length = 2 + and then Args (Args'First).Kind = Kind_Number + and then Args (Args'Last).Kind = Kind_Number, + "expected two numbers"); return (Kind_Number, Ada_Operator (Args (Args'First).Number, Args (Args'Last).Number)); end Generic_Mal_Operator; generic with function Ada_Operator (Left, Right : in Integer) return Boolean; - function Generic_Comparison (Args : in Mal.T_Array) return Mal.T; - function Generic_Comparison (Args : in Mal.T_Array) return Mal.T is + function Generic_Comparison (Args : in Types.T_Array) return Types.T; + function Generic_Comparison (Args : in Types.T_Array) return Types.T is begin - Err.Check (Args'Length = 2, "expected 2 parameters"); - Err.Check (Args (Args'First).Kind = Kind_Number, - "parameter 1 must be a number"); - Err.Check (Args (Args'Last).Kind = Kind_Number, - "parameter 2 must be a number"); + Err.Check (Args'Length = 2 + and then Args (Args'First).Kind = Kind_Number + and then Args (Args'Last).Kind = Kind_Number, + "expected two numbers"); return (Kind_Boolean, Ada_Operator (Args (Args'First).Number, Args (Args'Last).Number)); end Generic_Comparison; function Addition is new Generic_Mal_Operator ("+"); - function Apply (Args : in Mal.T_Array) return Mal.T; + function Apply (Args : in Types.T_Array) return Types.T; function Division is new Generic_Mal_Operator ("/"); - function Equals (Args : in Mal.T_Array) return Mal.T; + function Equals (Args : in Types.T_Array) return Types.T; function Greater_Equal is new Generic_Comparison (">="); function Greater_Than is new Generic_Comparison (">"); function Is_Atom is new Generic_Kind_Test (Kind_Atom); - function Is_False (Args : in Mal.T_Array) return Mal.T; - function Is_Function (Args : in Mal.T_Array) return Mal.T; + function Is_False (Args : in Types.T_Array) return Types.T; + function Is_Function (Args : in Types.T_Array) return Types.T; function Is_Keyword is new Generic_Kind_Test (Kind_Keyword); function Is_List is new Generic_Kind_Test (Kind_List); function Is_Macro is new Generic_Kind_Test (Kind_Macro); function Is_Map is new Generic_Kind_Test (Kind_Map); function Is_Nil is new Generic_Kind_Test (Kind_Nil); function Is_Number is new Generic_Kind_Test (Kind_Number); - function Is_Sequential (Args : in Mal.T_Array) return Mal.T; + function Is_Sequential (Args : in Types.T_Array) return Types.T; function Is_String is new Generic_Kind_Test (Kind_String); function Is_Symbol is new Generic_Kind_Test (Kind_Symbol); - function Is_True (Args : in Mal.T_Array) return Mal.T; + function Is_True (Args : in Types.T_Array) return Types.T; function Is_Vector is new Generic_Kind_Test (Kind_Vector); - function Keyword (Args : in Mal.T_Array) return Mal.T; + function Keyword (Args : in Types.T_Array) return Types.T; function Less_Equal is new Generic_Comparison ("<="); function Less_Than is new Generic_Comparison ("<"); - function Mal_Do (Args : in Mal.T_Array) return Mal.T; - function Meta (Args : in Mal.T_Array) return Mal.T; - function Pr_Str (Args : in Mal.T_Array) return Mal.T; - function Println (Args : in Mal.T_Array) return Mal.T; - function Prn (Args : in Mal.T_Array) return Mal.T; + function Mal_Do (Args : in Types.T_Array) return Types.T; + function Meta (Args : in Types.T_Array) return Types.T; + function Pr_Str (Args : in Types.T_Array) return Types.T; + function Println (Args : in Types.T_Array) return Types.T; + function Prn (Args : in Types.T_Array) return Types.T; function Product is new Generic_Mal_Operator ("*"); - function Read_String (Args : in Mal.T_Array) return Mal.T; - function Readline (Args : in Mal.T_Array) return Mal.T; - function Seq (Args : in Mal.T_Array) return Mal.T; - function Slurp (Args : in Mal.T_Array) return Mal.T; - function Str (Args : in Mal.T_Array) return Mal.T; + function Read_String (Args : in Types.T_Array) return Types.T; + function Readline (Args : in Types.T_Array) return Types.T; + function Seq (Args : in Types.T_Array) return Types.T; + function Slurp (Args : in Types.T_Array) return Types.T; + function Str (Args : in Types.T_Array) return Types.T; function Subtraction is new Generic_Mal_Operator ("-"); - function Symbol (Args : in Mal.T_Array) return Mal.T; - function Time_Ms (Args : in Mal.T_Array) return Mal.T; - function With_Meta (Args : in Mal.T_Array) return Mal.T; + function Symbol (Args : in Types.T_Array) return Types.T; + function Time_Ms (Args : in Types.T_Array) return Types.T; + function With_Meta (Args : in Types.T_Array) return Types.T; ---------------------------------------------------------------------- - function Apply (Args : in Mal.T_Array) return Mal.T is + function Apply (Args : in Types.T_Array) return Types.T is begin - Err.Check (2 <= Args'Length, "expected at least 2 parameters"); - Err.Check (Args (Args'Last).Kind in Kind_Sequence, - "last parameter must be a sequence"); + Err.Check (2 <= Args'Length + and then Args (Args'Last).Kind in Types.Kind_Sequence, + "expected a function, optional arguments then a sequence"); declare - use type Sequences.Instance; - F : Mal.T renames Args (Args'First); - A : constant Mal.T_Array + use type Types.T_Array; + F : Types.T renames Args (Args'First); + A : constant Types.T_Array := Args (Args'First + 1 .. Args'Last - 1) - & Args (Args'Last).Sequence.all; + & Args (Args'Last).Sequence.all.Data; begin case F.Kind is when Kind_Builtin => @@ -125,60 +122,60 @@ package body Core is end; end Apply; - function Equals (Args : in Mal.T_Array) return Mal.T is - use type Mal.T; + function Equals (Args : in Types.T_Array) return Types.T is + use type Types.T; begin Err.Check (Args'Length = 2, "expected 2 parameters"); return (Kind_Boolean, Args (Args'First) = Args (Args'Last)); end Equals; - function Is_False (Args : in Mal.T_Array) return Mal.T is + function Is_False (Args : in Types.T_Array) return Types.T is begin Err.Check (Args'Length = 1, "expected 1 parameter"); return (Kind_Boolean, Args (Args'First).Kind = Kind_Boolean and then not Args (Args'First).Ada_Boolean); end Is_False; - function Is_Function (Args : in Mal.T_Array) return Mal.T is + function Is_Function (Args : in Types.T_Array) return Types.T is begin Err.Check (Args'Length = 1, "expected 1 parameter"); - return (Kind_Boolean, Args (Args'First).Kind in Kind_Function); + return (Kind_Boolean, Args (Args'First).Kind in Types.Kind_Function); end Is_Function; - function Is_Sequential (Args : in Mal.T_Array) return Mal.T is + function Is_Sequential (Args : in Types.T_Array) return Types.T is begin Err.Check (Args'Length = 1, "expected 1 parameter"); - return (Kind_Boolean, Args (Args'First).Kind in Kind_Sequence); + return (Kind_Boolean, Args (Args'First).Kind in Types.Kind_Sequence); end Is_Sequential; - function Is_True (Args : in Mal.T_Array) return Mal.T is + function Is_True (Args : in Types.T_Array) return Types.T is begin Err.Check (Args'Length = 1, "expected 1 parameter"); return (Kind_Boolean, Args (Args'First).Kind = Kind_Boolean and then Args (Args'First).Ada_Boolean); end Is_True; - function Keyword (Args : in Mal.T_Array) return Mal.T is + function Keyword (Args : in Types.T_Array) return Types.T is begin - Err.Check (Args'Length = 1, "expected 1 parameter"); - Err.Check (Args (Args'First).Kind = Kind_String, "expected a string"); - return (Kind_Keyword, Args (Args'First).S); + Err.Check (Args'Length = 1 and then Args (Args'First).Kind = Kind_String, + "expected a string"); + return (Kind_Keyword, Args (Args'First).Str); end Keyword; - function Mal_Do (Args : in Mal.T_Array) return Mal.T is + function Mal_Do (Args : in Types.T_Array) return Types.T is begin Err.Check (0 < Args'Length, "expected at least 1 parameter"); return Args (Args'Last); end Mal_Do; - function Meta (Args : in Mal.T_Array) return Mal.T is + function Meta (Args : in Types.T_Array) return Types.T is begin Err.Check (Args'Length = 1, "expected 1 parameter"); declare - A1 : Mal.T renames Args (Args'First); + A1 : Types.T renames Args (Args'First); begin case A1.Kind is - when Kind_Sequence => + when Types.Kind_Sequence => return A1.Sequence.all.Meta; when Kind_Map => return A1.Map.all.Meta; @@ -187,7 +184,7 @@ package body Core is when Kind_Builtin_With_Meta => return A1.Builtin_With_Meta.all.Meta; when Kind_Builtin => - return Mal.Nil; + return Types.Nil; when others => Err.Raise_With ("expected a function, map or sequence"); end case; @@ -195,79 +192,80 @@ package body Core is end Meta; procedure NS_Add_To_Repl (Repl : in Envs.Ptr) is - procedure P (S : in Symbols.Ptr; - B : in Mal.Builtin_Ptr) with Inline; - procedure P (S : in Symbols.Ptr; - B : in Mal.Builtin_Ptr) + procedure P (S : in String; + B : in Types.Builtin_Ptr) with Inline; + procedure P (S : in String; + B : in Types.Builtin_Ptr) is begin - Repl.all.Set (S, (Kind_Builtin, B)); + Repl.all.Set ((Kind_Symbol, Types.Strings.Alloc (S)), + (Kind_Builtin, B)); end P; begin - P (Symbols.Constructor ("+"), Addition'Access); - P (Symbols.Constructor ("apply"), Apply'Access); - P (Symbols.Constructor ("assoc"), Maps.Assoc'Access); - P (Symbols.Constructor ("atom"), Atoms.Atom'Access); - P (Symbols.Constructor ("concat"), Sequences.Concat'Access); - P (Symbols.Constructor ("conj"), Sequences.Conj'Access); - P (Symbols.Constructor ("cons"), Sequences.Cons'Access); - P (Symbols.Constructor ("contains?"), Maps.Contains'Access); - P (Symbols.Constructor ("count"), Sequences.Count'Access); - P (Symbols.Names.Deref, Atoms.Deref'Access); - P (Symbols.Constructor ("dissoc"), Maps.Dissoc'Access); - P (Symbols.Constructor ("/"), Division'Access); - P (Symbols.Constructor ("do"), Mal_Do'Access); - P (Symbols.Constructor ("="), Equals'Access); - P (Symbols.Constructor ("first"), Sequences.First'Access); - P (Symbols.Constructor ("get"), Maps.Get'Access); - P (Symbols.Constructor (">="), Greater_Equal'Access); - P (Symbols.Constructor (">"), Greater_Than'Access); - P (Symbols.Constructor ("hash-map"), Maps.Hash_Map'Access); - P (Symbols.Constructor ("atom?"), Is_Atom'Access); - P (Symbols.Constructor ("empty?"), Sequences.Is_Empty'Access); - P (Symbols.Constructor ("false?"), Is_False'Access); - P (Symbols.Constructor ("fn?"), Is_Function'Access); - P (Symbols.Constructor ("keyword?"), Is_Keyword'Access); - P (Symbols.Constructor ("list?"), Is_List'Access); - P (Symbols.Constructor ("macro?"), Is_Macro'Access); - P (Symbols.Constructor ("map?"), Is_Map'Access); - P (Symbols.Constructor ("nil?"), Is_Nil'Access); - P (Symbols.Constructor ("number?"), Is_Number'Access); - P (Symbols.Constructor ("sequential?"), Is_Sequential'Access); - P (Symbols.Constructor ("string?"), Is_String'Access); - P (Symbols.Constructor ("symbol?"), Is_Symbol'Access); - P (Symbols.Constructor ("true?"), Is_True'Access); - P (Symbols.Constructor ("vector?"), Is_Vector'Access); - P (Symbols.Constructor ("keys"), Maps.Keys'Access); - P (Symbols.Constructor ("keyword"), Keyword'Access); - P (Symbols.Constructor ("<="), Less_Equal'Access); - P (Symbols.Constructor ("<"), Less_Than'Access); - P (Symbols.Constructor ("list"), Sequences.List'Access); - P (Symbols.Constructor ("map"), Sequences.Map'Access); - P (Symbols.Constructor ("meta"), Meta'Access); - P (Symbols.Constructor ("nth"), Sequences.Nth'Access); - P (Symbols.Constructor ("pr-str"), Pr_Str'Access); - P (Symbols.Constructor ("println"), Println'Access); - P (Symbols.Constructor ("prn"), Prn'Access); - P (Symbols.Constructor ("*"), Product'Access); - P (Symbols.Constructor ("read-string"), Read_String'Access); - P (Symbols.Constructor ("readline"), Readline'Access); - P (Symbols.Constructor ("reset!"), Atoms.Reset'Access); - P (Symbols.Constructor ("rest"), Sequences.Rest'Access); - P (Symbols.Constructor ("seq"), Seq'Access); - P (Symbols.Constructor ("slurp"), Slurp'Access); - P (Symbols.Constructor ("str"), Str'Access); - P (Symbols.Constructor ("-"), Subtraction'Access); - P (Symbols.Constructor ("swap!"), Atoms.Swap'Access); - P (Symbols.Constructor ("symbol"), Symbol'Access); - P (Symbols.Constructor ("throw"), Err.Throw'Access); - P (Symbols.Constructor ("time-ms"), Time_Ms'Access); - P (Symbols.Constructor ("vals"), Maps.Vals'Access); - P (Symbols.Constructor ("vector"), Sequences.Vector'Access); - P (Symbols.Names.With_Meta, With_Meta'Access); + P ("+", Addition'Access); + P ("apply", Apply'Access); + P ("assoc", Types.Maps.Assoc'Access); + P ("atom", Types.Atoms.Atom'Access); + P ("concat", Types.Sequences.Concat'Access); + P ("conj", Types.Sequences.Conj'Access); + P ("cons", Types.Sequences.Cons'Access); + P ("contains?", Types.Maps.Contains'Access); + P ("count", Types.Sequences.Count'Access); + P ("deref", Types.Atoms.Deref'Access); + P ("dissoc", Types.Maps.Dissoc'Access); + P ("/", Division'Access); + P ("do", Mal_Do'Access); + P ("=", Equals'Access); + P ("first", Types.Sequences.First'Access); + P ("get", Types.Maps.Get'Access); + P (">=", Greater_Equal'Access); + P (">", Greater_Than'Access); + P ("hash-map", Types.Maps.Hash_Map'Access); + P ("atom?", Is_Atom'Access); + P ("empty?", Types.Sequences.Is_Empty'Access); + P ("false?", Is_False'Access); + P ("fn?", Is_Function'Access); + P ("keyword?", Is_Keyword'Access); + P ("list?", Is_List'Access); + P ("macro?", Is_Macro'Access); + P ("map?", Is_Map'Access); + P ("nil?", Is_Nil'Access); + P ("number?", Is_Number'Access); + P ("sequential?", Is_Sequential'Access); + P ("string?", Is_String'Access); + P ("symbol?", Is_Symbol'Access); + P ("true?", Is_True'Access); + P ("vector?", Is_Vector'Access); + P ("keys", Types.Maps.Keys'Access); + P ("keyword", Keyword'Access); + P ("<=", Less_Equal'Access); + P ("<", Less_Than'Access); + P ("list", Types.Sequences.List'Access); + P ("map", Types.Sequences.Map'Access); + P ("meta", Meta'Access); + P ("nth", Types.Sequences.Nth'Access); + P ("pr-str", Pr_Str'Access); + P ("println", Println'Access); + P ("prn", Prn'Access); + P ("*", Product'Access); + P ("read-string", Read_String'Access); + P ("readline", Readline'Access); + P ("reset!", Types.Atoms.Reset'Access); + P ("rest", Types.Sequences.Rest'Access); + P ("seq", Seq'Access); + P ("slurp", Slurp'Access); + P ("str", Str'Access); + P ("-", Subtraction'Access); + P ("swap!", Types.Atoms.Swap'Access); + P ("symbol", Symbol'Access); + P ("throw", Err.Throw'Access); + P ("time-ms", Time_Ms'Access); + P ("vals", Types.Maps.Vals'Access); + P ("vector", Types.Sequences.Vector'Access); + P ("with-meta", With_Meta'Access); end NS_Add_To_Repl; - function Pr_Str (Args : in Mal.T_Array) return Mal.T is + function Pr_Str (Args : in Types.T_Array) return Types.T is R : ASU.Unbounded_String; Started : Boolean := False; begin @@ -279,10 +277,10 @@ package body Core is end if; Printer.Pr_Str (R, A); end loop; - return (Kind_String, R); + return (Kind_String, Types.Strings.Alloc (ASU.To_String (R))); end Pr_Str; - function Println (Args : in Mal.T_Array) return Mal.T is + function Println (Args : in Types.T_Array) return Types.T is Started : Boolean := False; Buffer : ASU.Unbounded_String; begin @@ -295,63 +293,84 @@ package body Core is Printer.Pr_Str (Buffer, A, Readably => False); end loop; Ada.Text_IO.Unbounded_IO.Put_Line (Buffer); - return Mal.Nil; + return Types.Nil; end Println; - function Prn (Args : in Mal.T_Array) return Mal.T is + function Prn (Args : in Types.T_Array) return Types.T is + -- Calling Pr_Str would create an intermediate copy. + Buffer : ASU.Unbounded_String; + Started : Boolean := False; begin - Ada.Text_IO.Unbounded_IO.Put_Line (Pr_Str (Args).S); - return Mal.Nil; + for A of Args loop + if Started then + ASU.Append (Buffer, ' '); + else + Started := True; + end if; + Printer.Pr_Str (Buffer, A); + end loop; + Ada.Text_IO.Unbounded_IO.Put_Line (Buffer); + return Types.Nil; end Prn; - function Readline (Args : in Mal.T_Array) return Mal.T is + function Readline (Args : in Types.T_Array) return Types.T is begin - Err.Check (Args'Length = 1, "expected 1 parameter"); - Err.Check (Args (Args'First).Kind = Kind_String, "expected a string"); - Ada.Text_IO.Unbounded_IO.Put (Args (Args'First).S); + Err.Check (Args'Length = 1 and then Args (Args'First).Kind = Kind_String, + "expected a string"); + Ada.Text_IO.Put (Args (Args'First).Str.all.To_String); if Ada.Text_IO.End_Of_File then - return Mal.Nil; + return Types.Nil; else - return (Kind_String, Ada.Text_IO.Unbounded_IO.Get_Line); + return (Kind_String, Types.Strings.Alloc (Ada.Text_IO.Get_Line)); end if; end Readline; - function Read_String (Args : in Mal.T_Array) return Mal.T is - begin - Err.Check (Args'Length = 1, "expected 1 parameter"); - Err.Check (Args (Args'First).Kind = Kind_String, "expected a string"); - declare - R : constant Mal.T_Array - := Reader.Read_Str (ASU.To_String (Args (Args'First).S)); + function Read_String (Args : in Types.T_Array) return Types.T is + Result : Types.T; + procedure Process (Element : in String); + procedure Process (Element : in String) is + R : constant Types.T_Array := Reader.Read_Str (Element); begin Err.Check (R'Length = 1, "parameter must contain 1 expression"); - return R (R'First); - end; + Result := R (R'First); + end Process; + begin + Err.Check (Args'Length = 1 and then Args (Args'First).Kind = Kind_String, + "expected a string"); + Args (Args'First).Str.all.Query_Element (Process'Access); + return Result; end Read_String; - function Seq (Args : in Mal.T_Array) return Mal.T is + function Seq (Args : in Types.T_Array) return Types.T is begin Err.Check (Args'Length = 1, "expected 1 parameter"); case Args (Args'First).Kind is when Kind_Nil => - return Mal.Nil; + return Types.Nil; when Kind_String => - if ASU.Length (Args (Args'First).S) = 0 then - return Mal.Nil; - else - declare - A1 : constant ASU.Unbounded_String := Args (Args'First).S; - R : Mal.T_Array (1 .. ASU.Length (A1)); + declare + Result : Types.T; + procedure Process (S : in String); + procedure Process (S : in String) is begin - for I in R'Range loop - R (I) := (Kind_String, ASU.Unbounded_Slice (A1, I, I)); - end loop; - return Sequences.List (R); - end; - end if; - when Kind_Sequence => + if S'Length = 0 then + Result := Types.Nil; + else + Result := (Kind_List, + Types.Sequences.Constructor (S'Length)); + for I in S'Range loop + Result.Sequence.all.Data (S'First - 1 + I) + := (Kind_String, Types.Strings.Alloc (S (I .. I))); + end loop; + end if; + end Process; + begin + Args (Args'First).Str.all.Query_Element (Process'Access); + return Result; + end; + when Types.Kind_Sequence => if Args (Args'First).Sequence.all.Length = 0 then - return Mal.Nil; + return Types.Nil; else return (Kind_List, Args (Args'First).Sequence); end if; @@ -360,20 +379,20 @@ package body Core is end case; end Seq; - function Slurp (Args : in Mal.T_Array) return Mal.T is + function Slurp (Args : in Types.T_Array) return Types.T is use Ada.Text_IO; File : File_Type; Buffer : ASU.Unbounded_String; begin - Err.Check (Args'Length = 1, "expected 1 parameter"); - Err.Check (Args (Args'First).Kind = Kind_String, "expected a string"); - Open (File, In_File, ASU.To_String (Args (Args'First).S)); + Err.Check (Args'Length = 1 and then Args (Args'First).Kind = Kind_String, + "expected a string"); + Open (File, In_File, Args (Args'First).Str.all.To_String); while not End_Of_File (File) loop ASU.Append (Buffer, Get_Line (File)); ASU.Append (Buffer, Ada.Characters.Latin_1.LF); end loop; Close (File); - return (Kind_String, Buffer); + return (Kind_String, Types.Strings.Alloc (ASU.To_String (Buffer))); exception -- Catch I/O errors, but not Err.Error... when E : Status_Error | Name_Error | Use_Error | Mode_Error => @@ -383,24 +402,23 @@ package body Core is Err.Raise_In_Mal (E); end Slurp; - function Str (Args : in Mal.T_Array) return Mal.T is + function Str (Args : in Types.T_Array) return Types.T is R : ASU.Unbounded_String; begin - for A of Args loop - Printer.Pr_Str (R, A, Readably => False); + for Arg of Args loop + Printer.Pr_Str (R, Arg, Readably => False); end loop; - return (Kind_String, R); + return (Kind_String, Types.Strings.Alloc (ASU.To_String (R))); end Str; - function Symbol (Args : in Mal.T_Array) return Mal.T is + function Symbol (Args : in Types.T_Array) return Types.T is begin - Err.Check (Args'Length = 1, "expected 1 parameter"); - Err.Check (Args (Args'First).Kind = Kind_String, "expected a string"); - return (Kind_Symbol, - Symbols.Constructor (ASU.To_String (Args (Args'First).S))); + Err.Check (Args'Length = 1 and then Args (Args'First).Kind = Kind_String, + "expected a string"); + return (Kind_Symbol, Args (Args'First).Str); end Symbol; - function Time_Ms (Args : in Mal.T_Array) return Mal.T is + function Time_Ms (Args : in Types.T_Array) return Types.T is use type Ada.Calendar.Time; begin Err.Check (Args'Length = 0, "expected no parameter"); @@ -408,26 +426,35 @@ package body Core is Integer (1000.0 * (Ada.Calendar.Clock - Start_Time))); end Time_Ms; - function With_Meta (Args : in Mal.T_Array) return Mal.T is + function With_Meta (Args : in Types.T_Array) return Types.T is begin Err.Check (Args'Length = 2, "expected 2 parameters"); declare - A1 : Mal.T renames Args (Args'First); - A2 : Mal.T renames Args (Args'Last); + A1 : Types.T renames Args (Args'First); + A2 : Types.T renames Args (Args'Last); begin case A1.Kind is when Kind_Builtin_With_Meta => - return Builtins.With_Meta (A1.Builtin_With_Meta.all, A2); + return A1.Builtin_With_Meta.all.With_Meta (A2); when Kind_Builtin => - return Builtins.With_Meta (A1.Builtin, A2); + return Types.Builtins.With_Meta (A1.Builtin, A2); when Kind_List => - return (Kind_List, Sequences.With_Meta (A1.Sequence.all, A2)); + return R : constant Types.T + := Types.Sequences.List (A1.Sequence.all.Data) + do + R.Sequence.all.Meta := A2; + end return; when Kind_Vector => - return (Kind_Vector, Sequences.With_Meta (A1.Sequence.all, A2)); + return R : constant Types.T + := Types.Sequences.Vector (A1.Sequence.all.Data) + do + R.Sequence.all.Meta := A2; + end return; when Kind_Map => - return Maps.With_Meta (A1.Map.all, A2); + return A1.Map.all.With_Meta (A2); when Kind_Fn => - return Fns.With_Meta (A1.Fn.all, A2); + return Types.Fns.New_Function (A1.Fn.all.Params, A1.Fn.all.Ast, + A1.Fn.all.Env, A2); when others => Err.Raise_With ("parameter 1 must be a function, map or sequence"); diff --git a/ada.2/core.ads b/ada.2/core.ads index de6027e8..bc2858d6 100644 --- a/ada.2/core.ads +++ b/ada.2/core.ads @@ -1,6 +1,6 @@ with Envs; -package Core with Elaborate_Body is +package Core is procedure NS_Add_To_Repl (Repl : in Envs.Ptr); -- Add built-in functions. diff --git a/ada.2/envs.adb b/ada.2/envs.adb index 0575d87a..8638b925 100644 --- a/ada.2/envs.adb +++ b/ada.2/envs.adb @@ -3,23 +3,23 @@ with Ada.Text_IO.Unbounded_IO; with Err; with Printer; with Types.Sequences; -with Types.Symbols.Names; package body Envs is - use Types; + use all type Types.Kind_Type; + use type Types.Strings.Instance; ---------------------------------------------------------------------- procedure Dump_Stack (Env : in Instance) is use Ada.Text_IO; begin - Put_Line ("environment:"); + Put ("environment:"); for P in Env.Data.Iterate loop -- Do not print builtins for repl. if HM.Element (P).Kind /= Kind_Builtin or Env.Outer /= null then Put (" "); - Put (HM.Key (P).To_String); + HM.Key (P).all.Query_Element (Put'Access); Put (':'); Unbounded_IO.Put (Printer.Pr_Str (HM.Element (P))); New_Line; @@ -32,70 +32,78 @@ package body Envs is end Dump_Stack; function Get (Env : in Instance; - Key : in Symbols.Ptr) return Mal.T + Key : in Types.String_Ptr) return Types.T is - -- Trust the compiler to detect the tail call. A loop would - -- require a Ptr parameter or a separated first iteration. - Position : constant HM.Cursor := Env.Data.Find (Key); + Position : HM.Cursor := Env.Data.Find (Key); + Ref : Link; begin - if HM.Has_Element (Position) then - return HM.Element (Position); + if not HM.Has_Element (Position) then + Ref := Env.Outer; + loop + if Ref = null then + -- Not using Err.Check, which would compute the + -- argument even if the assertion holds... + Err.Raise_With ("'" & Key.To_String & "' not found"); + end if; + Position := Ref.all.Data.Find (Key); + exit when HM.Has_Element (Position); + Ref := Ref.all.Outer; + end loop; end if; - Err.Check (Env.Outer /= null, - "'" & Symbols.To_String (Key) & "' not found"); - return Env.Outer.all.Get (Key); + return HM.Element (Position); end Get; procedure Keep_References (Object : in out Instance) is - -- Same remarks than for Get. begin - for Element of Object.Data loop - Mal.Keep (Element); + for Position in Object.Data.Iterate loop + HM.Key (Position).all.Keep; + Types.Keep (HM.Element (Position)); end loop; if Object.Outer /= null then Object.Outer.all.Keep; end if; end Keep_References; - function New_Env (Outer : in Ptr := null; - Binds : in Symbols.Symbol_Array := No_Binds; - Exprs : in Mal.T_Array := No_Exprs) return Ptr - is - use type Symbols.Ptr; - Ref : constant Ptr := new Instance'(Garbage_Collected.Instance with - Outer => Outer, - Data => HM.Empty_Map); + function New_Env (Outer : in Link := null) return Ptr is + Ref : constant Ptr := new Instance; begin Garbage_Collected.Register (Garbage_Collected.Pointer (Ref)); - if 2 <= Binds'Length - and then Binds (Binds'Last - 1) = Symbols.Names.Ampersand - then + Ref.all.Outer := Outer; + return Ref; + end New_Env; + + procedure Set_Binds (Env : in out Instance; + Binds : in Types.T_Array; + Exprs : in Types.T_Array) + is + begin + if 2 <= Binds'Length and then Binds (Binds'Last - 1).Str.all = "&" then Err.Check (Binds'Length - 2 <= Exprs'Length, "not enough actual parameters for vararg function"); for I in 0 .. Binds'Length - 3 loop - Ref.all.Data.Include (Key => Binds (Binds'First + I), - New_Item => Exprs (Exprs'First + I)); + Env.Data.Include (Key => Binds (Binds'First + I).Str, + New_Item => Exprs (Exprs'First + I)); end loop; - Ref.all.Data.Include (Key => Binds (Binds'Last), - New_Item => Sequences.List + Env.Data.Include (Key => Binds (Binds'Last).Str, + New_Item => Types.Sequences.List (Exprs (Exprs'First + Binds'Length - 2 .. Exprs'Last))); else Err.Check (Binds'Length = Exprs'Length, "wrong parameter count for (not vararg) function"); for I in 0 .. Binds'Length - 1 loop - Ref.all.Data.Include (Key => Binds (Binds'First + I), - New_Item => Exprs (Exprs'First + I)); + Env.Data.Include (Key => Binds (Binds'First + I).Str, + New_Item => Exprs (Exprs'First + I)); end loop; end if; - return Ref; - end New_Env; + end Set_Binds; procedure Set (Env : in out Instance; - Key : in Symbols.Ptr; - New_Item : in Mal.T) + Key : in Types.T; + New_Item : in Types.T) is begin - Env.Data.Include (Key, New_Item); + Err.Check (Key.Kind = Kind_Symbol, "environment keys must be symbols"); + Env.Data.Include (Key.Str, New_Item); end Set; end Envs; diff --git a/ada.2/envs.ads b/ada.2/envs.ads index 94955e5e..e6652dbc 100644 --- a/ada.2/envs.ads +++ b/ada.2/envs.ads @@ -1,8 +1,7 @@ private with Ada.Containers.Hashed_Maps; with Garbage_Collected; -with Types.Mal; -with Types.Symbols; +with Types.Strings; package Envs is @@ -10,23 +9,28 @@ package Envs is -- parameters to be named like a package dependency, and it seems -- that readability inside Eval is more important. - type Instance (<>) is new Garbage_Collected.Instance with private; - type Ptr is access Instance; + type Instance (<>) is abstract new Garbage_Collected.Instance with private; + type Link is access Instance; + subtype Ptr is not null Link; - No_Binds : Types.Symbols.Symbol_Array renames Types.Symbols.Empty_Array; - No_Exprs : constant Types.Mal.T_Array := (1 .. 0 => Types.Mal.Nil); + function New_Env (Outer : in Link := null) return Ptr with Inline; + -- Set_Binds is provided as distinct subprograms because we some + -- time spare the creation of a subenvironment. - function New_Env (Outer : in Ptr := null; - Binds : in Types.Symbols.Symbol_Array := No_Binds; - Exprs : in Types.Mal.T_Array := No_Exprs) - return Ptr; + procedure Set_Binds (Env : in out Instance; + Binds : in Types.T_Array; + Exprs : in Types.T_Array); + -- Equivalent to successive calls to Set, except that if Binds + -- ends with "&" followed by a symbol, the trailing symbol + -- receives all remaining values as a list. function Get (Env : in Instance; - Key : in Types.Symbols.Ptr) return Types.Mal.T; + Key : in Types.String_Ptr) return Types.T; procedure Set (Env : in out Instance; - Key : in Types.Symbols.Ptr; - New_Item : in Types.Mal.T) with Inline; + Key : in Types.T; + New_Item : in Types.T) with Inline; + -- Raises an exception if Key is not a symbol. -- Debug. procedure Dump_Stack (Env : in Instance); @@ -34,14 +38,18 @@ package Envs is private package HM is new Ada.Containers.Hashed_Maps - (Key_Type => Types.Symbols.Ptr, - Element_Type => Types.Mal.T, - Hash => Types.Symbols.Hash, - Equivalent_Keys => Types.Symbols."=", - "=" => Types.Mal."="); + (Key_Type => Types.String_Ptr, + Element_Type => Types.T, + Hash => Types.Strings.Hash, + Equivalent_Keys => Types.Strings.Same_Contents, + "=" => Types."="); + + -- It may be tempting to subclass Types.Map, but this would not + -- simplify the code much. And adding metadata to a structure that + -- is allocated very often has a cost. type Instance is new Garbage_Collected.Instance with record - Outer : Ptr; + Outer : Link; Data : HM.Map; end record; overriding procedure Keep_References (Object : in out Instance) with Inline; diff --git a/ada.2/err.adb b/ada.2/err.adb index 52595f2f..e2411f16 100644 --- a/ada.2/err.adb +++ b/ada.2/err.adb @@ -1,16 +1,16 @@ with Ada.Characters.Latin_1; with Printer; +with Types.Strings; package body Err is use Ada.Strings.Unbounded; - use Types; ---------------------------------------------------------------------- procedure Add_Trace_Line (Action : in String; - Ast : in Types.Mal.T) + Ast : in Types.T) is begin Append (Trace, " in "); @@ -31,23 +31,28 @@ package body Err is procedure Raise_In_Mal (E : in Ada.Exceptions.Exception_Occurrence) is Message : String renames Ada.Exceptions.Exception_Information (E); + procedure Process (S : in String); + procedure Process (S : in String) is + begin + Append (Trace, S); + end Process; begin - Data := (Kind_String, To_Unbounded_String (Message)); + Data := (Types.Kind_String, Types.Strings.Alloc (Message)); Set_Unbounded_String (Trace, "Uncaught exception: "); - Append (Trace, Message); + Data.Str.all.Query_Element (Process'Access); raise Error; end Raise_In_Mal; procedure Raise_With (Message : in String) is begin - Data := (Kind_String, To_Unbounded_String (Message)); + Data := (Types.Kind_String, Types.Strings.Alloc (Message)); Set_Unbounded_String (Trace, "Uncaught exception: "); Append (Trace, Message); Append (Trace, Ada.Characters.Latin_1.LF); raise Error; end Raise_With; - function Throw (Args : in Mal.T_Array) return Mal.T is + function Throw (Args : in Types.T_Array) return Types.T is begin Check (Args'Length = 1, "expected 1 parameter"); Data := Args (Args'First); diff --git a/ada.2/err.ads b/ada.2/err.ads index 7cca2715..a83078a7 100644 --- a/ada.2/err.ads +++ b/ada.2/err.ads @@ -1,17 +1,16 @@ with Ada.Exceptions; with Ada.Strings.Unbounded; -with Types.Mal; +with Types; +-- We declare a variable of type Types.T. +pragma Elaborate (Types); --- We declare a variable of type Types.Mal.T. -pragma Elaborate (Types.Mal); - -package Err with Elaborate_Body is +package Err is -- Error handling. -- Built-in function. - function Throw (Args : in Types.Mal.T_Array) return Types.Mal.T; + function Throw (Args : in Types.T_Array) return Types.T; -- Ada exceptions can only carry an immutable String in each -- occurence, so we require a global variable to store the last @@ -19,27 +18,33 @@ package Err with Elaborate_Body is -- simple string messages. Error : exception; - Data : Types.Mal.T; + Data : Types.T; Trace : Ada.Strings.Unbounded.Unbounded_String; -- Convenient shortcuts. - procedure Raise_With (Message : in String) with Inline, No_Return; + procedure Raise_With (Message : in String) with No_Return; -- Similar to a "raise with Message" Ada statement. -- Store the message into Data, -- store the message and "Uncaught exception: " into Trace, -- then raise Error. procedure Raise_In_Mal (E : in Ada.Exceptions.Exception_Occurrence) - with Inline, No_Return; + with No_Return; -- Raise_With (Ada.Exceptions.Exception_Information (E)) procedure Add_Trace_Line (Action : in String; - Ast : in Types.Mal.T) with Inline; + Ast : in Types.T); -- Appends a line like "Action: Ast" to Trace. procedure Check (Condition : in Boolean; Message : in String) with Inline; -- Raise_With if Condition fails. + -- It is probably more efficient to construct a boolean and call + -- this procedure once, as "inline" is only a recommendation. + + -- Beware of the classical performance issue that the Message is + -- formatted even if the Condition does not hold. + end Err; diff --git a/ada.2/eval_cb.ads b/ada.2/eval_cb.ads deleted file mode 100644 index 9319f3b2..00000000 --- a/ada.2/eval_cb.ads +++ /dev/null @@ -1,11 +0,0 @@ -with Envs; -with Types.Mal; - -package Eval_Cb is - - Cb : access function (Ast : in Types.Mal.T; - Env : in Envs.Ptr) return Types.Mal.T; - -- The main program must register this global callback to the main - -- eval function before some built-in functions are executed. - -end Eval_Cb; diff --git a/ada.2/garbage_collected.adb b/ada.2/garbage_collected.adb index 0552a064..373f26d9 100644 --- a/ada.2/garbage_collected.adb +++ b/ada.2/garbage_collected.adb @@ -2,15 +2,15 @@ with Ada.Unchecked_Deallocation; package body Garbage_Collected is - procedure Free is new Ada.Unchecked_Deallocation (Class, Pointer); + procedure Free is new Ada.Unchecked_Deallocation (Class, Link); - Top : Pointer := null; + Top : Link := null; ---------------------------------------------------------------------- procedure Clean is - Current : Pointer := Top; - Previous : Pointer; + Current : Link := Top; + Previous : Link; begin while Current /= null and then not Current.all.Kept loop Previous := Current; @@ -30,11 +30,11 @@ package body Garbage_Collected is end loop; end Clean; - procedure Keep (Object : in out Instance) is + procedure Keep (Object : in out Class) is begin if not Object.Kept then Object.Kept := True; - Class (Object).Keep_References; -- dispatching + Object.Keep_References; -- dispatching end if; end Keep; @@ -43,7 +43,7 @@ package body Garbage_Collected is pragma Assert (Top = null); end Check_Allocations; - procedure Register (Ref : in not null Pointer) is + procedure Register (Ref : in Pointer) is begin pragma Assert (Ref.all.Kept = False); pragma Assert (Ref.all.Next = null); diff --git a/ada.2/garbage_collected.ads b/ada.2/garbage_collected.ads index e91c994e..1f23f2b9 100644 --- a/ada.2/garbage_collected.ads +++ b/ada.2/garbage_collected.ads @@ -1,4 +1,4 @@ -package Garbage_Collected with Preelaborate is +package Garbage_Collected is -- A generic would not be convenient for lists. We want the -- extended type to be able to have a discriminant. @@ -8,7 +8,8 @@ package Garbage_Collected with Preelaborate is type Instance is abstract tagged limited private; subtype Class is Instance'Class; - type Pointer is access all Class; + type Link is access all Class; + subtype Pointer is not null Link; procedure Keep_References (Object : in out Instance) is null with Inline; -- A dispatching call in Keep allows subclasses to override this @@ -16,13 +17,13 @@ package Garbage_Collected with Preelaborate is -- The following methods have no reason to be overridden. - procedure Keep (Object : in out Instance) with Inline; + procedure Keep (Object : in out Class) with Inline; -- Mark this object so that it is not deleted by next clean, -- then make a dispatching call to Keep_References. -- Does nothing if it has already been called for this object -- since startup or last Clean. - procedure Register (Ref : in not null Pointer) with Inline; + procedure Register (Ref : in Pointer) with Inline; -- Each subclass defines its own allocation pool, but every call -- to new must be followed by a call to Register. @@ -32,14 +33,14 @@ package Garbage_Collected with Preelaborate is -- then deallocate the memory for the object. -- Debug. - procedure Check_Allocations with Inline; + procedure Check_Allocations; -- Does nothing if assertions are disabled. private type Instance is abstract tagged limited record Kept : Boolean := False; - Next : Pointer := null; + Next : Link := null; end record; end Garbage_Collected; diff --git a/ada.2/printer.adb b/ada.2/printer.adb index e3a0c805..48a708a9 100644 --- a/ada.2/printer.adb +++ b/ada.2/printer.adb @@ -2,34 +2,36 @@ with Ada.Characters.Latin_1; with Types.Atoms; with Types.Fns; -with Types.Sequences; -with Types.Symbols; +with Types.Macros; with Types.Maps; +pragma Warnings (Off, "unit ""Types.Sequences"" is not referenced"); +with Types.Sequences; +pragma Warnings (On, "unit ""Types.Sequences"" is not referenced"); package body Printer is use Ada.Strings.Unbounded; - use Types; + use all type Types.Kind_Type; procedure Pr_Str (Buffer : in out Unbounded_String; - Ast : in Mal.T; + Ast : in Types.T; Readably : in Boolean := True) is - procedure Print_Form (Form_Ast : in Mal.T); + procedure Print_Form (Form_Ast : in Types.T); -- The recursive function traversing Ast for Pr_Str. -- Form_Ast is the current node. -- Helpers for Print_Form. - procedure Print_Number (Number : in Integer) with Inline; - procedure Print_List (List : in Sequences.Instance) with Inline; - procedure Print_Map (Map : in Maps.Instance) with Inline; - procedure Print_Readably (S : in Unbounded_String) with Inline; - procedure Print_Function (Fn : in Fns.Instance) with Inline; + procedure Print_Number (Number : in Integer); + procedure Print_List (List : in Types.T_Array); + procedure Print_Map (Map : in Types.Maps.Instance); + procedure Print_Readably (S : in String); + procedure Print_String (S : in String); ---------------------------------------------------------------------- - procedure Print_Form (Form_Ast : in Mal.T) is + procedure Print_Form (Form_Ast : in Types.T) is begin case Form_Ast.Kind is when Kind_Nil => @@ -41,27 +43,27 @@ package body Printer is Append (Buffer, "false"); end if; when Kind_Symbol => - Append (Buffer, Symbols.To_String (Form_Ast.Symbol)); + Form_Ast.Str.all.Query_Element (Print_String'Access); when Kind_Number => Print_Number (Form_Ast.Number); when Kind_Keyword => Append (Buffer, ':'); - Append (Buffer, Form_Ast.S); + Form_Ast.Str.all.Query_Element (Print_String'Access); when Kind_String => if Readably then Append (Buffer, '"'); - Print_Readably (Form_Ast.S); + Form_Ast.Str.all.Query_Element (Print_Readably'Access); Append (Buffer, '"'); else - Append (Buffer, Form_Ast.S); + Form_Ast.Str.all.Query_Element (Print_String'Access); end if; when Kind_List => Append (Buffer, '('); - Print_List (Form_Ast.Sequence.all); + Print_List (Form_Ast.Sequence.all.Data); Append (Buffer, ')'); when Kind_Vector => Append (Buffer, '['); - Print_List (Form_Ast.Sequence.all); + Print_List (Form_Ast.Sequence.all.Data); Append (Buffer, ']'); when Kind_Map => Append (Buffer, '{'); @@ -71,11 +73,15 @@ package body Printer is Append (Buffer, "#"); when Kind_Fn => Append (Buffer, "# "); + Print_Form (Form_Ast.Fn.all.Ast); Append (Buffer, '>'); when Kind_Macro => Append (Buffer, "# "); + Print_Form (Form_Ast.Macro.all.Ast); Append (Buffer, '>'); when Kind_Atom => Append (Buffer, "(atom "); @@ -84,53 +90,31 @@ package body Printer is end case; end Print_Form; - procedure Print_Function (Fn : in Fns.Instance) is - Started : Boolean := False; + procedure Print_List (List : in Types.T_Array) is begin - Append (Buffer, '('); - for Param of Fn.Params loop - if Started then - Append (Buffer, ' '); - else - Started := True; - end if; - Append (Buffer, Symbols.To_String (Param)); - end loop; - Append (Buffer, ") -> "); - Print_Form (Fn.Ast); - end Print_Function; - - procedure Print_List (List : in Sequences.Instance) is - begin - if 0 < List.Length then - Print_Form (List (1)); - for I in 2 .. List.Length loop + if 0 < List'Length then + Print_Form (List (List'First)); + for I in List'First + 1 .. List'Last loop Append (Buffer, ' '); Print_Form (List (I)); end loop; end if; end Print_List; - procedure Print_Map (Map : in Maps.Instance) is - procedure Process (Key : in Mal.T; - Element : in Mal.T) with Inline; - procedure Iterate is new Maps.Iterate (Process); - Started : Boolean := False; - procedure Process (Key : in Mal.T; - Element : in Mal.T) - is - begin - if Started then - Append (Buffer, ' '); - else - Started := True; - end if; - Print_Form (Key); - Append (Buffer, ' '); - Print_Form (Element); - end Process; + procedure Print_Map (Map : in Types.Maps.Instance) is + use all type Types.Maps.Cursor; + Position : Types.Maps.Cursor := Map.First; begin - Iterate (Map); + if Has_Element (Position) then + loop + Print_Form (Key (Position)); + Append (Buffer, ' '); + Print_Form (Element (Position)); + Next (Position); + exit when not Has_Element (Position); + Append (Buffer, ' '); + end loop; + end if; end Print_Map; procedure Print_Number (Number : in Integer) is @@ -143,12 +127,9 @@ package body Printer is Append (Buffer, Image (First .. Image'Last)); end Print_Number; - procedure Print_Readably (S : in Unbounded_String) is + procedure Print_Readably (S : in String) is begin - for I in 1 .. Length (S) loop - declare - C : constant Character := Element (S, I); - begin + for C of S loop case C is when '"' | '\' => Append (Buffer, '\'); @@ -158,17 +139,21 @@ package body Printer is when others => Append (Buffer, C); end case; - end; end loop; end Print_Readably; + procedure Print_String (S : in String) is + begin + Append (Buffer, S); + end Print_String; + ---------------------------------------------------------------------- begin -- Pr_Str Print_Form (Ast); end Pr_Str; - function Pr_Str (Ast : in Mal.T; + function Pr_Str (Ast : in Types.T; Readably : in Boolean := True) return Unbounded_String is begin diff --git a/ada.2/printer.ads b/ada.2/printer.ads index 02cf6036..22646dc6 100644 --- a/ada.2/printer.ads +++ b/ada.2/printer.ads @@ -1,18 +1,19 @@ with Ada.Strings.Unbounded; -with Types.Mal; +with Types; -package Printer with Elaborate_Body is +package Printer is procedure Pr_Str (Buffer : in out Ada.Strings.Unbounded.Unbounded_String; - Ast : in Types.Mal.T; + Ast : in Types.T; Readably : in Boolean := True); -- Append the text to Buffer. - function Pr_Str (Ast : in Types.Mal.T; - Readably : in Boolean := True) + function Pr_Str (Ast : in Types.T; + Readably : in Boolean := True) return Ada.Strings.Unbounded.Unbounded_String; -- Return a freshly created unbounded string. + -- Convenient, but inefficient. end Printer; diff --git a/ada.2/reader.adb b/ada.2/reader.adb index 1af4162b..65037e84 100644 --- a/ada.2/reader.adb +++ b/ada.2/reader.adb @@ -9,23 +9,23 @@ with Err; with Printer; with Types.Maps; with Types.Sequences; -with Types.Symbols.Names; +with Types.Strings; package body Reader is Debug : constant Boolean := Ada.Environment_Variables.Exists ("dbgread"); - use Types; - use type Ada.Strings.Maps.Character_Set; + use all type Types.Kind_Type; + use all type Ada.Strings.Maps.Character_Set; Ignored_Set : constant Ada.Strings.Maps.Character_Set := Ada.Strings.Maps.Constants.Control_Set - or Ada.Strings.Maps.To_Set (" ,;"); + or To_Set (" ,;"); Symbol_Set : constant Ada.Strings.Maps.Character_Set - := not (Ignored_Set or Ada.Strings.Maps.To_Set ("""'()@[]^`{}~")); + := not (Ignored_Set or To_Set ("""'()@[]^`{}~")); - function Read_Str (Source : in String) return Types.Mal.T_Array is + function Read_Str (Source : in String) return Types.T_Array is I : Positive := Source'First; -- Index in Source of the currently read character. @@ -33,16 +33,16 @@ package body Reader is -- Big arrays on the stack are faster than repeated dynamic -- reallocations. This single buffer is used by all Read_List -- recursive invocations, and by Read_Str. - Buffer : Mal.T_Array (1 .. Source'Length); + Buffer : Types.T_Array (1 .. Source'Length); B_Last : Natural := Buffer'First - 1; -- Index in Buffer of the currently written MAL expression. - function Read_Form return Mal.T; + function Read_Form return Types.T; -- The recursive part of Read_Str. -- Helpers for Read_Form: - procedure Skip_Ignored with Inline; + procedure Skip_Ignored; -- Check if the current character is ignorable or a comment. -- Increment I until it exceeds Source'Last or designates -- an interesting character. @@ -59,15 +59,15 @@ package body Reader is -- Read_Atom has been merged into the same case/switch -- statement, for clarity and efficiency. - function Read_List (Ending : in Character) return Natural with Inline; + function Read_List (Ending : in Character) return Natural; -- Returns the index of the last elements in Buffer. -- The elements have been stored in Buffer (B_Last .. result). - function Read_Quote (Symbol : in Symbols.Ptr) return Mal.T with Inline; + function Read_Quote (Symbol : in String) return Types.T; - function Read_String return Mal.T with Inline; + function Read_String return Types.T; - function Read_With_Meta return Mal.T with Inline; + function Read_With_Meta return Types.T; ---------------------------------------------------------------------- @@ -90,23 +90,22 @@ package body Reader is return Result; end Read_List; - function Read_Quote (Symbol : in Symbols.Ptr) return Mal.T is - R : constant Mal.Sequence_Ptr := Sequences.Constructor (2); + function Read_Quote (Symbol : in String) return Types.T is + R : constant Types.Sequence_Ptr := Types.Sequences.Constructor (2); begin I := I + 1; -- Skip the initial ' or similar. - R.Replace_Element (1, (Kind_Symbol, Symbol)); + R.all.Data (1) := (Kind_Symbol, Types.Strings.Alloc (Symbol)); Skip_Ignored; - Err.Check (I <= Source'Last, - "Incomplete '" & Symbols.To_String (Symbol) & "'"); - R.Replace_Element (2, Read_Form); + Err.Check (I <= Source'Last, "Incomplete '" & Symbol & "'"); + R.all.Data (2) := Read_Form; return (Kind_List, R); end Read_Quote; - function Read_Form return Mal.T is + function Read_Form return Types.T is -- After I has been increased, current token is be -- Source (F .. I - 1). F : Positive; - R : Mal.T; -- The result of this function. + R : Types.T; -- The result of this function. begin case Source (I) is when ')' | ']' | '}' => @@ -117,8 +116,7 @@ package body Reader is I := I + 1; F := I; Skip_Symbol; - R := (Kind_Keyword, Ada.Strings.Unbounded.To_Unbounded_String - (Source (F .. I - 1))); + R := (Kind_Keyword, Types.Strings.Alloc (Source (F .. I - 1))); when '-' => F := I; Skip_Digits; @@ -127,45 +125,48 @@ package body Reader is else Skip_Symbol; R := (Kind_Symbol, - Symbols.Constructor (Source (F .. I - 1))); + Types.Strings.Alloc (Source (F .. I - 1))); end if; when '~' => if I < Source'Last and then Source (I + 1) = '@' then I := I + 1; - R := Read_Quote (Symbols.Names.Splice_Unquote); + R := Read_Quote ("splice-unquote"); else - R := Read_Quote (Symbols.Names.Unquote); + R := Read_Quote ("unquote"); end if; when '0' .. '9' => F := I; Skip_Digits; R := (Kind_Number, Integer'Value (Source (F .. I - 1))); when ''' => - R := Read_Quote (Symbols.Names.Quote); + R := Read_Quote ("quote"); when '`' => - R := Read_Quote (Symbols.Names.Quasiquote); + R := Read_Quote ("quasiquote"); when '@' => - R := Read_Quote (Symbols.Names.Deref); + R := Read_Quote ("deref"); when '^' => R := Read_With_Meta; when '(' => - R := Sequences.List (Buffer (B_Last + 1 .. Read_List (')'))); + R := Types.Sequences.List + (Buffer (B_Last + 1 .. Read_List (')'))); when '[' => - R := Sequences.Vector (Buffer (B_Last + 1 .. Read_List (']'))); + R := Types.Sequences.Vector + (Buffer (B_Last + 1 .. Read_List (']'))); when '{' => - R := Maps.Hash_Map (Buffer (B_Last + 1 .. Read_List ('}'))); + R := Types.Maps.Hash_Map + (Buffer (B_Last + 1 .. Read_List ('}'))); when others => F := I; Skip_Symbol; if Source (F .. I - 1) = "false" then R := (Kind_Boolean, False); elsif Source (F .. I - 1) = "nil" then - R := Mal.Nil; + R := Types.Nil; elsif Source (F .. I - 1) = "true" then R := (Kind_Boolean, True); else R := (Kind_Symbol, - Symbols.Constructor (Source (F .. I - 1))); + Types.Strings.Alloc (Source (F .. I - 1))); end if; end case; if Debug then @@ -175,7 +176,7 @@ package body Reader is return R; end Read_Form; - function Read_String return Mal.T is + function Read_String return Types.T is use Ada.Strings.Unbounded; Result : Unbounded_String; begin @@ -201,18 +202,18 @@ package body Reader is end case; end loop; I := I + 1; -- Skip closing double quote. - return (Kind_String, Result); + return (Kind_String, Types.Strings.Alloc (To_String (Result))); end Read_String; - function Read_With_Meta return Mal.T is - List : constant Mal.Sequence_Ptr := Sequences.Constructor (3); + function Read_With_Meta return Types.T is + List : constant Types.Sequence_Ptr := Types.Sequences.Constructor (3); begin I := I + 1; -- Skip the initial ^. - List.all.Replace_Element (1, (Kind_Symbol, Symbols.Names.With_Meta)); + List.all.Data (1) := (Kind_Symbol, Types.Strings.Alloc ("with-meta")); for I in reverse 2 .. 3 loop Skip_Ignored; Err.Check (I <= Source'Last, "Incomplete 'with-meta'"); - List.all.Replace_Element (I, Read_Form); + List.all.Data (I) := Read_Form; end loop; return (Kind_List, List); end Read_With_Meta; @@ -229,7 +230,6 @@ package body Reader is procedure Skip_Ignored is use Ada.Characters.Handling; - use Ada.Strings.Maps; begin Ignored : while I <= Source'Last and then Is_In (Source (I), Ignored_Set) @@ -246,7 +246,6 @@ package body Reader is end Skip_Ignored; procedure Skip_Symbol is - use Ada.Strings.Maps; begin while I <= Source'Last and then Is_In (Source (I), Symbol_Set) loop I := I + 1; diff --git a/ada.2/reader.ads b/ada.2/reader.ads index 88a6ca4d..033fc33e 100644 --- a/ada.2/reader.ads +++ b/ada.2/reader.ads @@ -1,8 +1,8 @@ -with Types.Mal; +with Types; -package Reader with Elaborate_Body is +package Reader is - function Read_Str (Source : in String) return Types.Mal.T_Array; + function Read_Str (Source : in String) return Types.T_Array; -- The language does not explicitly define what happens when the -- input string contains more than one expression. -- This implementation returns all of them. diff --git a/ada.2/readline.ads b/ada.2/readline.ads index 534ee7f7..81bdb839 100644 --- a/ada.2/readline.ads +++ b/ada.2/readline.ads @@ -1,4 +1,4 @@ -package Readline with Preelaborate is +package Readline is function Input (Prompt : in String) return String; diff --git a/ada.2/step0_repl.adb b/ada.2/step0_repl.adb index 9eda4865..5a09040d 100644 --- a/ada.2/step0_repl.adb +++ b/ada.2/step0_repl.adb @@ -14,7 +14,8 @@ procedure Step0_Repl is ---------------------------------------------------------------------- - function Eval (Ast : in String) return String is (Ast); + function Eval (Ast : in String) return String + is (Ast); procedure Print (Ast : in String) is begin @@ -38,6 +39,7 @@ begin when Readline.End_Of_File => exit; end; + -- Other exceptions are really unexpected. end loop; Ada.Text_IO.New_Line; end Step0_Repl; diff --git a/ada.2/step1_read_print.adb b/ada.2/step1_read_print.adb index 51aaaa39..4079879e 100644 --- a/ada.2/step1_read_print.adb +++ b/ada.2/step1_read_print.adb @@ -5,31 +5,28 @@ with Garbage_Collected; with Printer; with Reader; with Readline; -with Types.Mal; -with Types.Symbols; +with Types; procedure Step1_Read_Print is - use Types; + function Read return Types.T_Array with Inline; - function Read return Mal.T_Array with Inline; + function Eval (Ast : in Types.T) return Types.T; - function Eval (Ast : in Mal.T) return Mal.T; - - procedure Print (Ast : in Mal.T) with Inline; + procedure Print (Ast : in Types.T) with Inline; procedure Rep with Inline; ---------------------------------------------------------------------- - function Eval (Ast : in Mal.T) return Mal.T is (Ast); + function Eval (Ast : in Types.T) return Types.T is (Ast); - procedure Print (Ast : in Mal.T) is + procedure Print (Ast : in Types.T) is begin Ada.Text_IO.Unbounded_IO.Put_Line (Printer.Pr_Str (Ast)); end Print; - function Read return Mal.T_Array + function Read return Types.T_Array is (Reader.Read_Str (Readline.Input ("user> "))); procedure Rep is @@ -54,12 +51,15 @@ begin -- Other exceptions are really unexpected. -- Collect garbage. - Err.Data := Mal.Nil; + Err.Data := Types.Nil; + -- No data survives at this stage, Repl only contains static + -- pointers to built-in functions. 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; - Symbols.Check_Allocations; end Step1_Read_Print; diff --git a/ada.2/step2_eval.adb b/ada.2/step2_eval.adb index 44abc648..7eadeb69 100644 --- a/ada.2/step2_eval.adb +++ b/ada.2/step2_eval.adb @@ -8,45 +8,49 @@ with Garbage_Collected; with Printer; with Reader; with Readline; -with Types.Mal; with Types.Maps; with Types.Sequences; -with Types.Symbols; +with Types.Strings; procedure Step2_Eval is Dbgeval : constant Boolean := Ada.Environment_Variables.Exists ("dbgeval"); - use Types; + use type Types.T; + use all type Types.Kind_Type; package Envs is new Ada.Containers.Indefinite_Hashed_Maps (Key_Type => String, - Element_Type => Mal.Builtin_Ptr, + Element_Type => Types.Builtin_Ptr, Hash => Ada.Strings.Hash, Equivalent_Keys => "=", - "=" => Mal."="); + "=" => Types."="); - function Read return Mal.T_Array with Inline; + function Read return Types.T_Array with Inline; - function Eval (Ast : in Mal.T; - Env : in Envs.Map) return Mal.T; + function Eval (Ast : in Types.T; + Env : in Envs.Map) return Types.T; - procedure Print (Ast : in Mal.T) with Inline; + procedure Print (Ast : in Types.T) with Inline; procedure Rep (Env : in Envs.Map) with Inline; generic with function Ada_Operator (Left, Right : in Integer) return Integer; - function Generic_Mal_Operator (Args : in Mal.T_Array) return Mal.T; + function Generic_Mal_Operator (Args : in Types.T_Array) return Types.T; - function Eval_Map_Elts is new Maps.Generic_Eval (Envs.Map, Eval); + function Eval_Map (Source : in Types.Maps.Instance; + Env : in Envs.Map) return Types.T; + function Eval_Vector (Source : in Types.Sequences.Instance; + Env : in Envs.Map) return Types.T; + -- Helpers for the Eval function. ---------------------------------------------------------------------- - function Eval (Ast : in Mal.T; - Env : in Envs.Map) return Mal.T + function Eval (Ast : in Types.T; + Env : in Envs.Map) return Types.T is - First : Mal.T; + First : Types.T; begin if Dbgeval then Ada.Text_IO.New_Line; @@ -55,12 +59,12 @@ procedure Step2_Eval is end if; case Ast.Kind is - when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_Key - | Kind_Macro | Kind_Function => + when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Types.Kind_Key + | Kind_Macro | Types.Kind_Function => return Ast; when Kind_Symbol => declare - S : constant String := Ast.Symbol.To_String; + S : constant String := Ast.Str.all.To_String; C : constant Envs.Cursor := Env.Find (S); begin -- The predefined error message does not pass tests. @@ -68,17 +72,9 @@ procedure Step2_Eval is return (Kind_Builtin, Envs.Element (C)); end; when Kind_Map => - return Eval_Map_Elts (Ast.Map.all, Env); + return Eval_Map (Ast.Map.all, Env); when Kind_Vector => - declare - Len : constant Natural := Ast.Sequence.all.Length; - List : constant Mal.Sequence_Ptr := Sequences.Constructor (Len); - begin - for I in 1 .. Len loop - List.all.Replace_Element (I, Eval (Ast.Sequence.all (I), Env)); - end loop; - return (Kind_Vector, List); - end; + return Eval_Vector (Ast.Sequence.all, Env); when Kind_List => null; end case; @@ -87,7 +83,7 @@ procedure Step2_Eval is if Ast.Sequence.all.Length = 0 then return Ast; end if; - First := Ast.Sequence.all (1); + First := Ast.Sequence.all.Data (1); -- Ast is a non-empty list, First is its first element. First := Eval (First, Env); @@ -95,35 +91,61 @@ procedure Step2_Eval is -- Apply phase. -- Ast is a non-empty list, -- First is its evaluated first element. - case First.Kind is - when Kind_Builtin => - declare - Args : Mal.T_Array (2 .. Ast.Sequence.all.Length); - begin - for I in Args'Range loop - Args (I) := Eval (Ast.Sequence.all (I), Env); - end loop; - return First.Builtin.all (Args); - end; - when others => - Err.Raise_With ("first element must be a function"); - end case; + 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 Generic_Mal_Operator (Args : in Mal.T_Array) return Mal.T + function Eval_Map (Source : in Types.Maps.Instance; + Env : in Envs.Map) 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.Map) 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 Mal.T) is + procedure Print (Ast : in Types.T) is begin Ada.Text_IO.Unbounded_IO.Put_Line (Printer.Pr_Str (Ast)); end Print; - function Read return Mal.T_Array + function Read return Types.T_Array is (Reader.Read_Str (Readline.Input ("user> "))); procedure Rep (Env : in Envs.Map) is @@ -158,13 +180,16 @@ begin -- Other exceptions are really unexpected. -- Collect garbage. - Err.Data := Mal.Nil; + Err.Data := Types.Nil; + -- No data survives at this stage, Repl only contains static + -- pointers to built-in functions. 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; - Symbols.Check_Allocations; end Step2_Eval; diff --git a/ada.2/step3_env.adb b/ada.2/step3_env.adb index 3a15ff25..cee5987d 100644 --- a/ada.2/step3_env.adb +++ b/ada.2/step3_env.adb @@ -7,39 +7,43 @@ with Garbage_Collected; with Printer; with Reader; with Readline; -with Types.Mal; with Types.Maps; with Types.Sequences; -with Types.Symbols.Names; +with Types.Strings; procedure Step3_Env is Dbgeval : constant Boolean := Ada.Environment_Variables.Exists ("dbgeval"); - use Types; + use type Types.T; + use all type Types.Kind_Type; + use type Types.Strings.Instance; - function Read return Mal.T_Array with Inline; + function Read return Types.T_Array with Inline; - function Eval (Ast : in Mal.T; - Env : in Envs.Ptr) return Mal.T; + function Eval (Ast : in Types.T; + Env : in Envs.Ptr) return Types.T; - procedure Print (Ast : in Mal.T) with Inline; + 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 Mal.T_Array) return Mal.T; + function Generic_Mal_Operator (Args : in Types.T_Array) return Types.T; - function Eval_Map_Elts is new Maps.Generic_Eval (Envs.Ptr, Eval); + 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 Mal.T; - Env : in Envs.Ptr) return Mal.T + function Eval (Ast : in Types.T; + Env : in Envs.Ptr) return Types.T is - use type Symbols.Ptr; - First : Mal.T; + First : Types.T; begin if Dbgeval then Ada.Text_IO.New_Line; @@ -49,23 +53,15 @@ procedure Step3_Env is end if; case Ast.Kind is - when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_Key - | Kind_Macro | Kind_Function => + 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.Symbol); + return Env.all.Get (Ast.Str); when Kind_Map => - return Eval_Map_Elts (Ast.Map.all, Env); + return Eval_Map (Ast.Map.all, Env); when Kind_Vector => - declare - Len : constant Natural := Ast.Sequence.all.Length; - List : constant Mal.Sequence_Ptr := Sequences.Constructor (Len); - begin - for I in 1 .. Len loop - List.all.Replace_Element (I, Eval (Ast.Sequence.all (I), Env)); - end loop; - return (Kind_Vector, List); - end; + return Eval_Vector (Ast.Sequence.all, Env); when Kind_List => null; end case; @@ -74,38 +70,37 @@ procedure Step3_Env is if Ast.Sequence.all.Length = 0 then return Ast; end if; - First := Ast.Sequence.all (1); + 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.Symbol = Symbols.Names.Def then - Err.Check (Ast.Sequence.all.Length = 3, "expected 2 parameters"); - Err.Check (Ast.Sequence.all (2).Kind = Kind_Symbol, - "parameter 1 must be a symbol"); - return R : constant Mal.T := Eval (Ast.Sequence.all (3), Env) do - Env.all.Set (Ast.Sequence.all (2).Symbol, R); - end return; - elsif First.Symbol = Symbols.Names.Let then - Err.Check (Ast.Sequence.all.Length = 3, "expected 2 parameters"); - Err.Check (Ast.Sequence.all (2).Kind in Kind_Sequence, - "parameter 1 must be a sequence"); + 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 : constant Mal.Sequence_Ptr - := Ast.Sequence.all (2).Sequence; - New_Env : Envs.Ptr; + 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.all.Length mod 2 = 0, - "parameter 1 must have an even length"); - New_Env := Envs.New_Env (Outer => Env); - for I in 1 .. Bindings.all.Length / 2 loop - Err.Check (Bindings.all (2 * I - 1).Kind = Kind_Symbol, - "binding keys must be symbols"); - New_Env.all.Set (Bindings.all (2 * I - 1).Symbol, - Eval (Bindings.all (2 * I), New_Env)); + 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 (3), New_Env); + 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); @@ -117,35 +112,61 @@ procedure Step3_Env is -- Apply phase. -- Ast is a non-empty list, -- First is its non-special evaluated first element. - case First.Kind is - when Kind_Builtin => - declare - Args : Mal.T_Array (2 .. Ast.Sequence.all.Length); - begin - for I in Args'Range loop - Args (I) := Eval (Ast.Sequence.all (I), Env); - end loop; - return First.Builtin.all (Args); - end; - when others => - Err.Raise_With ("first element must be a function"); - end case; + 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 Generic_Mal_Operator (Args : in Mal.T_Array) return Mal.T + 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 Mal.T) is + procedure Print (Ast : in Types.T) is begin Ada.Text_IO.Unbounded_IO.Put_Line (Printer.Pr_Str (Ast)); end Print; - function Read return Mal.T_Array + function Read return Types.T_Array is (Reader.Read_Str (Readline.Input ("user> "))); procedure Rep (Env : in Envs.Ptr) is @@ -165,13 +186,13 @@ procedure Step3_Env is Repl : constant Envs.Ptr := Envs.New_Env; begin -- Add Core functions into the top environment. - Repl.all.Set (Symbols.Constructor ("+"), + Repl.all.Set ((Kind_Symbol, Types.Strings.Alloc ("+")), (Kind_Builtin, Addition 'Unrestricted_Access)); - Repl.all.Set (Symbols.Constructor ("-"), + Repl.all.Set ((Kind_Symbol, Types.Strings.Alloc ("-")), (Kind_Builtin, Subtraction'Unrestricted_Access)); - Repl.all.Set (Symbols.Constructor ("*"), + Repl.all.Set ((Kind_Symbol, Types.Strings.Alloc ("*")), (Kind_Builtin, Product 'Unrestricted_Access)); - Repl.all.Set (Symbols.Constructor ("/"), + Repl.all.Set ((Kind_Symbol, Types.Strings.Alloc ("/")), (Kind_Builtin, Division 'Unrestricted_Access)); -- Execute user commands. loop @@ -186,14 +207,15 @@ begin -- Other exceptions are really unexpected. -- Collect garbage. - Err.Data := Mal.Nil; + 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; - Symbols.Check_Allocations; end Step3_Env; diff --git a/ada.2/step4_if_fn_do.adb b/ada.2/step4_if_fn_do.adb index 3dd53e8e..6d26a79e 100644 --- a/ada.2/step4_if_fn_do.adb +++ b/ada.2/step4_if_fn_do.adb @@ -4,34 +4,37 @@ with Ada.Text_IO.Unbounded_IO; with Core; with Envs; with Err; -with Eval_Cb; with Garbage_Collected; with Printer; with Reader; with Readline; with Types.Fns; -with Types.Mal; with Types.Maps; with Types.Sequences; -with Types.Symbols.Names; +with Types.Strings; procedure Step4_If_Fn_Do is Dbgeval : constant Boolean := Ada.Environment_Variables.Exists ("dbgeval"); - use Types; - use type Mal.T; + use type Types.T; + use all type Types.Kind_Type; + use type Types.Strings.Instance; - function Read return Mal.T_Array with Inline; + function Read return Types.T_Array with Inline; - function Eval (Ast : in Mal.T; - Env : in Envs.Ptr) return Mal.T; + function Eval (Ast : in Types.T; + Env : in Envs.Ptr) return Types.T; - procedure Print (Ast : in Mal.T) with Inline; + procedure Print (Ast : in Types.T) with Inline; procedure Rep (Env : in Envs.Ptr) with Inline; - function Eval_Map_Elts is new Maps.Generic_Eval (Envs.Ptr, Eval); + 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; @@ -39,11 +42,10 @@ procedure Step4_If_Fn_Do is ---------------------------------------------------------------------- - function Eval (Ast : in Mal.T; - Env : in Envs.Ptr) return Mal.T + function Eval (Ast : in Types.T; + Env : in Envs.Ptr) return Types.T is - use type Symbols.Ptr; - First : Mal.T; + First : Types.T; begin if Dbgeval then Ada.Text_IO.New_Line; @@ -53,23 +55,15 @@ procedure Step4_If_Fn_Do is end if; case Ast.Kind is - when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_Key - | Kind_Macro | Kind_Function => + 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.Symbol); + return Env.all.Get (Ast.Str); when Kind_Map => - return Eval_Map_Elts (Ast.Map.all, Env); + return Eval_Map (Ast.Map.all, Env); when Kind_Vector => - declare - Len : constant Natural := Ast.Sequence.all.Length; - List : constant Mal.Sequence_Ptr := Sequences.Constructor (Len); - begin - for I in 1 .. Len loop - List.all.Replace_Element (I, Eval (Ast.Sequence.all (I), Env)); - end loop; - return (Kind_Vector, List); - end; + return Eval_Vector (Ast.Sequence.all, Env); when Kind_List => null; end case; @@ -78,61 +72,64 @@ procedure Step4_If_Fn_Do is if Ast.Sequence.all.Length = 0 then return Ast; end if; - First := Ast.Sequence.all (1); + 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.Symbol = Symbols.Names.Def then - Err.Check (Ast.Sequence.all.Length = 3, "expected 2 parameters"); - Err.Check (Ast.Sequence.all (2).Kind = Kind_Symbol, - "parameter 1 must be a symbol"); - return R : constant Mal.T := Eval (Ast.Sequence.all (3), Env) do - Env.all.Set (Ast.Sequence.all (2).Symbol, R); - end return; - -- do is a built-in function, shortening this test cascade. - elsif First.Symbol = Symbols.Names.Fn then - Err.Check (Ast.Sequence.all.Length = 3, "expected 2 parameters"); - Err.Check (Ast.Sequence.all (2).Kind in Kind_Sequence, - "parameter 1 must be a sequence"); - return Fns.New_Function - (Params => Ast.Sequence.all (2).Sequence.all, - Ast => Ast.Sequence.all (3), - Env => Env); - elsif First.Symbol = Symbols.Names.Mal_If then + if First.Str.all = "if" then Err.Check (Ast.Sequence.all.Length in 3 .. 4, "expected 2 or 3 parameters"); declare - Test : constant Mal.T := Eval (Ast.Sequence.all (2), Env); + Tst : constant Types.T := Eval (Ast.Sequence.all.Data (2), Env); begin - if Test /= Mal.Nil and Test /= (Kind_Boolean, False) then - return Eval (Ast.Sequence.all (3), Env); + if Tst /= Types.Nil and Tst /= (Kind_Boolean, False) then + return Eval (Ast.Sequence.all.Data (3), Env); elsif Ast.Sequence.all.Length = 3 then - return Mal.Nil; + return Types.Nil; else - return Eval (Ast.Sequence.all (4), Env); + return Eval (Ast.Sequence.all.Data (4), Env); end if; end; - elsif First.Symbol = Symbols.Names.Let then - Err.Check (Ast.Sequence.all.Length = 3, "expected 2 parameters"); - Err.Check (Ast.Sequence.all (2).Kind in Kind_Sequence, - "parameter 1 must be a sequence"); + 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 : constant Mal.Sequence_Ptr - := Ast.Sequence.all (2).Sequence; - New_Env : Envs.Ptr; + 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.all.Length mod 2 = 0, - "parameter 1 must have an even length"); - New_Env := Envs.New_Env (Outer => Env); - for I in 1 .. Bindings.all.Length / 2 loop - Err.Check (Bindings.all (2 * I - 1).Kind = Kind_Symbol, - "binding keys must be symbols"); - New_Env.all.Set (Bindings.all (2 * I - 1).Symbol, - Eval (Bindings.all (2 * I), New_Env)); + 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 (3), New_Env); + 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; + -- do is a built-in function, shortening this test cascade. + 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"); + return Types.Fns.New_Function + (Params => Params.Sequence, + Ast => Ast.Sequence.all.Data (3), + Env => Env); end; else First := Eval (First, Env); @@ -144,38 +141,58 @@ procedure Step4_If_Fn_Do is -- Apply phase. -- Ast is a non-empty list, -- First is its non-special evaluated first element. - case First.Kind is - when Kind_Builtin => - declare - Args : Mal.T_Array (2 .. Ast.Sequence.all.Length); - begin - for I in Args'Range loop - Args (I) := Eval (Ast.Sequence.all (I), Env); - end loop; - return First.Builtin.all (Args); - end; - when Kind_Fn => - declare - Args : Mal.T_Array (2 .. Ast.Sequence.all.Length); - begin - for I in Args'Range loop - Args (I) := Eval (Ast.Sequence.all (I), Env); - end loop; - return First.Fn.all.Apply (Args); - end; - when others => - Err.Raise_With ("first element must be a function"); - end case; + 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; + return First.Fn.all.Apply (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; + procedure Exec (Script : in String; Env : in Envs.Ptr) is - Result : Mal.T; + Result : Types.T; begin for Expression of Reader.Read_Str (Script) loop Result := Eval (Expression, Env); @@ -183,12 +200,12 @@ procedure Step4_If_Fn_Do is pragma Unreferenced (Result); end Exec; - procedure Print (Ast : in Mal.T) is + procedure Print (Ast : in Types.T) is begin Ada.Text_IO.Unbounded_IO.Put_Line (Printer.Pr_Str (Ast)); end Print; - function Read return Mal.T_Array + function Read return Types.T_Array is (Reader.Read_Str (Readline.Input ("user> "))); procedure Rep (Env : in Envs.Ptr) is @@ -205,7 +222,7 @@ procedure Step4_If_Fn_Do is Repl : constant Envs.Ptr := Envs.New_Env; begin -- Show the Eval function to other packages. - Eval_Cb.Cb := Eval'Unrestricted_Access; + Types.Fns.Eval_Cb := Eval'Unrestricted_Access; -- Add Core functions into the top environment. Core.NS_Add_To_Repl (Repl); -- Native startup procedure. @@ -223,14 +240,15 @@ begin -- Other exceptions are really unexpected. -- Collect garbage. - Err.Data := Mal.Nil; + 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; - Symbols.Check_Allocations; end Step4_If_Fn_Do; diff --git a/ada.2/step5_tco.adb b/ada.2/step5_tco.adb index c3baf5a8..5dbcc2a1 100644 --- a/ada.2/step5_tco.adb +++ b/ada.2/step5_tco.adb @@ -4,34 +4,37 @@ with Ada.Text_IO.Unbounded_IO; with Core; with Envs; with Err; -with Eval_Cb; with Garbage_Collected; with Printer; with Reader; with Readline; with Types.Fns; -with Types.Mal; with Types.Maps; with Types.Sequences; -with Types.Symbols.Names; +with Types.Strings; procedure Step5_Tco is Dbgeval : constant Boolean := Ada.Environment_Variables.Exists ("dbgeval"); - use Types; - use type Mal.T; + use type Types.T; + use all type Types.Kind_Type; + use type Types.Strings.Instance; - function Read return Mal.T_Array with Inline; + function Read return Types.T_Array with Inline; - function Eval (Ast0 : in Mal.T; - Env0 : in Envs.Ptr) return Mal.T; + function Eval (Ast0 : in Types.T; + Env0 : in Envs.Ptr) return Types.T; - procedure Print (Ast : in Mal.T) with Inline; + procedure Print (Ast : in Types.T) with Inline; procedure Rep (Env : in Envs.Ptr) with Inline; - function Eval_Map_Elts is new Maps.Generic_Eval (Envs.Ptr, Eval); + 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; @@ -39,15 +42,18 @@ procedure Step5_Tco is ---------------------------------------------------------------------- - function Eval (Ast0 : in Mal.T; - Env0 : in Envs.Ptr) return Mal.T + function Eval (Ast0 : in Types.T; + Env0 : in Envs.Ptr) return Types.T is - use type Symbols.Ptr; -- Use local variables, that can be rewritten when tail call -- optimization goes to <>. - Ast : Mal.T := Ast0; + Ast : Types.T := Ast0; Env : Envs.Ptr := Env0; - First : Mal.T; + 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 <> if Dbgeval then @@ -58,23 +64,15 @@ procedure Step5_Tco is end if; case Ast.Kind is - when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_Key - | Kind_Macro | Kind_Function => + 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.Symbol); + return Env.all.Get (Ast.Str); when Kind_Map => - return Eval_Map_Elts (Ast.Map.all, Env); + return Eval_Map (Ast.Map.all, Env); when Kind_Vector => - declare - Len : constant Natural := Ast.Sequence.all.Length; - List : constant Mal.Sequence_Ptr := Sequences.Constructor (Len); - begin - for I in 1 .. Len loop - List.all.Replace_Element (I, Eval (Ast.Sequence.all (I), Env)); - end loop; - return (Kind_Vector, List); - end; + return Eval_Vector (Ast.Sequence.all, Env); when Kind_List => null; end case; @@ -83,75 +81,83 @@ procedure Step5_Tco is if Ast.Sequence.all.Length = 0 then return Ast; end if; - First := Ast.Sequence.all (1); + 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.Symbol = Symbols.Names.Def then - Err.Check (Ast.Sequence.all.Length = 3, "expected 2 parameters"); - Err.Check (Ast.Sequence.all (2).Kind = Kind_Symbol, - "parameter 1 must be a symbol"); - return R : constant Mal.T := Eval (Ast.Sequence.all (3), Env) do - Env.all.Set (Ast.Sequence.all (2).Symbol, R); - end return; - -- do is a built-in function, shortening this test cascade. - elsif First.Symbol = Symbols.Names.Fn then - Err.Check (Ast.Sequence.all.Length = 3, "expected 2 parameters"); - Err.Check (Ast.Sequence.all (2).Kind in Kind_Sequence, - "parameter 1 must be a sequence"); - return Fns.New_Function - (Params => Ast.Sequence.all (2).Sequence.all, - Ast => Ast.Sequence.all (3), - Env => Env); - elsif First.Symbol = Symbols.Names.Mal_If then + if First.Str.all = "if" then Err.Check (Ast.Sequence.all.Length in 3 .. 4, "expected 2 or 3 parameters"); declare - Test : constant Mal.T := Eval (Ast.Sequence.all (2), Env); + Tst : constant Types.T := Eval (Ast.Sequence.all.Data (2), Env); begin - if Test /= Mal.Nil and Test /= (Kind_Boolean, False) then - Ast := Ast.Sequence.all (3); + 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 Mal.Nil; + return Types.Nil; else - Ast := Ast.Sequence.all (4); + Ast := Ast.Sequence.all.Data (4); goto Restart; end if; end; - elsif First.Symbol = Symbols.Names.Let then - Err.Check (Ast.Sequence.all.Length = 3, "expected 2 parameters"); - Err.Check (Ast.Sequence.all (2).Kind in Kind_Sequence, - "parameter 1 must be a sequence"); + 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 : constant Mal.Sequence_Ptr - := Ast.Sequence.all (2).Sequence; + Bindings : Types.T_Array + renames Ast.Sequence.all.Data (2).Sequence.all.Data; begin - Err.Check (Bindings.all.Length mod 2 = 0, - "parameter 1 must have an even length"); - Env := Envs.New_Env (Outer => Env); - for I in 1 .. Bindings.all.Length / 2 loop - Err.Check (Bindings.all (2 * I - 1).Kind = Kind_Symbol, - "binding keys must be symbols"); - Env.all.Set (Bindings.all (2 * I - 1).Symbol, - Eval (Bindings.all (2 * I), Env)); + 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 (3); + 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; + -- do is a built-in function, shortening this test cascade. + 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.Symbol); + First := Env.all.Get (First.Str); end if; - when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_Key - | Kind_Macro | Kind_Function => + 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 Kind_Sequence | Kind_Map => + 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); @@ -160,42 +166,64 @@ procedure Step5_Tco is -- Apply phase. -- Ast is a non-empty list, -- First is its non-special evaluated first element. - case First.Kind is - when Kind_Builtin => - declare - Args : Mal.T_Array (2 .. Ast.Sequence.all.Length); - begin - for I in Args'Range loop - Args (I) := Eval (Ast.Sequence.all (I), Env); - end loop; - return First.Builtin.all (Args); - end; - when Kind_Fn => - declare - Args : Mal.T_Array (2 .. Ast.Sequence.all.Length); - begin - for I in Args'Range loop - Args (I) := Eval (Ast.Sequence.all (I), Env); - end loop; - Env := Envs.New_Env (Outer => First.Fn.all.Env, - Binds => First.Fn.all.Params, - Exprs => Args); - Ast := First.Fn.all.Ast; - goto Restart; - end; - when others => - Err.Raise_With ("first element must be a function"); - end case; + 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 : Mal.T; + Result : Types.T; begin for Expression of Reader.Read_Str (Script) loop Result := Eval (Expression, Env); @@ -203,12 +231,12 @@ procedure Step5_Tco is pragma Unreferenced (Result); end Exec; - procedure Print (Ast : in Mal.T) is + procedure Print (Ast : in Types.T) is begin Ada.Text_IO.Unbounded_IO.Put_Line (Printer.Pr_Str (Ast)); end Print; - function Read return Mal.T_Array + function Read return Types.T_Array is (Reader.Read_Str (Readline.Input ("user> "))); procedure Rep (Env : in Envs.Ptr) is @@ -225,7 +253,7 @@ procedure Step5_Tco is Repl : constant Envs.Ptr := Envs.New_Env; begin -- Show the Eval function to other packages. - Eval_Cb.Cb := Eval'Unrestricted_Access; + Types.Fns.Eval_Cb := Eval'Unrestricted_Access; -- Add Core functions into the top environment. Core.NS_Add_To_Repl (Repl); -- Native startup procedure. @@ -243,14 +271,15 @@ begin -- Other exceptions are really unexpected. -- Collect garbage. - Err.Data := Mal.Nil; + 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; - Symbols.Check_Allocations; end Step5_Tco; diff --git a/ada.2/step6_file.adb b/ada.2/step6_file.adb index 9215bec2..bd22cff8 100644 --- a/ada.2/step6_file.adb +++ b/ada.2/step6_file.adb @@ -1,43 +1,44 @@ with Ada.Command_Line; with Ada.Environment_Variables; -with Ada.Strings.Unbounded; with Ada.Text_IO.Unbounded_IO; with Core; with Envs; with Err; -with Eval_Cb; with Garbage_Collected; with Printer; with Reader; with Readline; with Types.Fns; -with Types.Mal; with Types.Maps; with Types.Sequences; -with Types.Symbols.Names; +with Types.Strings; procedure Step6_File is Dbgeval : constant Boolean := Ada.Environment_Variables.Exists ("dbgeval"); - use Types; - use type Mal.T; + use type Types.T; + use all type Types.Kind_Type; + use type Types.Strings.Instance; package ACL renames Ada.Command_Line; - package ASU renames Ada.Strings.Unbounded; - function Read return Mal.T_Array with Inline; + function Read return Types.T_Array with Inline; - function Eval (Ast0 : in Mal.T; - Env0 : in Envs.Ptr) return Mal.T; - function Eval_Builtin (Args : in Mal.T_Array) return Mal.T; + function Eval (Ast0 : in Types.T; + Env0 : in Envs.Ptr) return Types.T; + function Eval_Builtin (Args : in Types.T_Array) return Types.T; -- The built-in variant needs to see the Repl variable. - procedure Print (Ast : in Mal.T) with Inline; + procedure Print (Ast : in Types.T) with Inline; procedure Rep (Env : in Envs.Ptr) with Inline; - function Eval_Map_Elts is new Maps.Generic_Eval (Envs.Ptr, Eval); + 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; @@ -45,15 +46,18 @@ procedure Step6_File is ---------------------------------------------------------------------- - function Eval (Ast0 : in Mal.T; - Env0 : in Envs.Ptr) return Mal.T + function Eval (Ast0 : in Types.T; + Env0 : in Envs.Ptr) return Types.T is - use type Symbols.Ptr; -- Use local variables, that can be rewritten when tail call -- optimization goes to <>. - Ast : Mal.T := Ast0; + Ast : Types.T := Ast0; Env : Envs.Ptr := Env0; - First : Mal.T; + 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 <> if Dbgeval then @@ -64,23 +68,15 @@ procedure Step6_File is end if; case Ast.Kind is - when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_Key - | Kind_Macro | Kind_Function => + 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.Symbol); + return Env.all.Get (Ast.Str); when Kind_Map => - return Eval_Map_Elts (Ast.Map.all, Env); + return Eval_Map (Ast.Map.all, Env); when Kind_Vector => - declare - Len : constant Natural := Ast.Sequence.all.Length; - List : constant Mal.Sequence_Ptr := Sequences.Constructor (Len); - begin - for I in 1 .. Len loop - List.all.Replace_Element (I, Eval (Ast.Sequence.all (I), Env)); - end loop; - return (Kind_Vector, List); - end; + return Eval_Vector (Ast.Sequence.all, Env); when Kind_List => null; end case; @@ -89,75 +85,83 @@ procedure Step6_File is if Ast.Sequence.all.Length = 0 then return Ast; end if; - First := Ast.Sequence.all (1); + 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.Symbol = Symbols.Names.Def then - Err.Check (Ast.Sequence.all.Length = 3, "expected 2 parameters"); - Err.Check (Ast.Sequence.all (2).Kind = Kind_Symbol, - "parameter 1 must be a symbol"); - return R : constant Mal.T := Eval (Ast.Sequence.all (3), Env) do - Env.all.Set (Ast.Sequence.all (2).Symbol, R); - end return; - -- do is a built-in function, shortening this test cascade. - elsif First.Symbol = Symbols.Names.Fn then - Err.Check (Ast.Sequence.all.Length = 3, "expected 2 parameters"); - Err.Check (Ast.Sequence.all (2).Kind in Kind_Sequence, - "parameter 1 must be a sequence"); - return Fns.New_Function - (Params => Ast.Sequence.all (2).Sequence.all, - Ast => Ast.Sequence.all (3), - Env => Env); - elsif First.Symbol = Symbols.Names.Mal_If then + if First.Str.all = "if" then Err.Check (Ast.Sequence.all.Length in 3 .. 4, "expected 2 or 3 parameters"); declare - Test : constant Mal.T := Eval (Ast.Sequence.all (2), Env); + Tst : constant Types.T := Eval (Ast.Sequence.all.Data (2), Env); begin - if Test /= Mal.Nil and Test /= (Kind_Boolean, False) then - Ast := Ast.Sequence.all (3); + 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 Mal.Nil; + return Types.Nil; else - Ast := Ast.Sequence.all (4); + Ast := Ast.Sequence.all.Data (4); goto Restart; end if; end; - elsif First.Symbol = Symbols.Names.Let then - Err.Check (Ast.Sequence.all.Length = 3, "expected 2 parameters"); - Err.Check (Ast.Sequence.all (2).Kind in Kind_Sequence, - "parameter 1 must be a sequence"); + 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 : constant Mal.Sequence_Ptr - := Ast.Sequence.all (2).Sequence; + Bindings : Types.T_Array + renames Ast.Sequence.all.Data (2).Sequence.all.Data; begin - Err.Check (Bindings.all.Length mod 2 = 0, - "parameter 1 must have an even length"); - Env := Envs.New_Env (Outer => Env); - for I in 1 .. Bindings.all.Length / 2 loop - Err.Check (Bindings.all (2 * I - 1).Kind = Kind_Symbol, - "binding keys must be symbols"); - Env.all.Set (Bindings.all (2 * I - 1).Symbol, - Eval (Bindings.all (2 * I), Env)); + 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 (3); + 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; + -- do is a built-in function, shortening this test cascade. + 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.Symbol); + First := Env.all.Get (First.Str); end if; - when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_Key - | Kind_Macro | Kind_Function => + 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 Kind_Sequence | Kind_Map => + 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); @@ -166,42 +170,64 @@ procedure Step6_File is -- Apply phase. -- Ast is a non-empty list, -- First is its non-special evaluated first element. - case First.Kind is - when Kind_Builtin => - declare - Args : Mal.T_Array (2 .. Ast.Sequence.all.Length); - begin - for I in Args'Range loop - Args (I) := Eval (Ast.Sequence.all (I), Env); - end loop; - return First.Builtin.all (Args); - end; - when Kind_Fn => - declare - Args : Mal.T_Array (2 .. Ast.Sequence.all.Length); - begin - for I in Args'Range loop - Args (I) := Eval (Ast.Sequence.all (I), Env); - end loop; - Env := Envs.New_Env (Outer => First.Fn.all.Env, - Binds => First.Fn.all.Params, - Exprs => Args); - Ast := First.Fn.all.Ast; - goto Restart; - end; - when others => - Err.Raise_With ("first element must be a function"); - end case; + 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 : Mal.T; + Result : Types.T; begin for Expression of Reader.Read_Str (Script) loop Result := Eval (Expression, Env); @@ -209,12 +235,12 @@ procedure Step6_File is pragma Unreferenced (Result); end Exec; - procedure Print (Ast : in Mal.T) is + procedure Print (Ast : in Types.T) is begin Ada.Text_IO.Unbounded_IO.Put_Line (Printer.Pr_Str (Ast)); end Print; - function Read return Mal.T_Array + function Read return Types.T_Array is (Reader.Read_Str (Readline.Input ("user> "))); procedure Rep (Env : in Envs.Ptr) is @@ -231,33 +257,30 @@ procedure Step6_File is & "(def! load-file (fn* (f)" & " (eval (read-string (str ""(do "" (slurp f) "")"")))))"; Repl : constant Envs.Ptr := Envs.New_Env; - function Eval_Builtin (Args : in Mal.T_Array) return Mal.T is + function Eval_Builtin (Args : in Types.T_Array) return Types.T is begin Err.Check (Args'Length = 1, "expected 1 parameter"); - return Eval_Cb.Cb.all (Args (Args'First), Repl); + return Eval (Args (Args'First), Repl); end Eval_Builtin; Script : constant Boolean := 0 < ACL.Argument_Count; - Argv : Mal.Sequence_Ptr; + Argv : constant Types.Sequence_Ptr + := Types.Sequences.Constructor (Integer'Max (0, ACL.Argument_Count - 1)); begin -- Show the Eval function to other packages. - Eval_Cb.Cb := Eval'Unrestricted_Access; + Types.Fns.Eval_Cb := Eval'Unrestricted_Access; -- Add Core functions into the top environment. Core.NS_Add_To_Repl (Repl); - Repl.all.Set (Symbols.Constructor ("eval"), + Repl.all.Set ((Kind_Symbol, Types.Strings.Alloc ("eval")), (Kind_Builtin, Eval_Builtin'Unrestricted_Access)); -- Native startup procedure. Exec (Startup, Repl); -- Define ARGV from command line arguments. - if Script then - Argv := Sequences.Constructor (ACL.Argument_Count - 1); - for I in 2 .. ACL.Argument_Count loop - Argv.all.Replace_Element - (I - 1, (Kind_String, ASU.To_Unbounded_String (ACL.Argument (I)))); - end loop; - else - Argv := Sequences.Constructor (0); - end if; - Repl.all.Set (Symbols.Constructor ("*ARGV*"), (Kind_List, Argv)); + for I in 2 .. ACL.Argument_Count loop + Argv.all.Data (I - 1) := (Kind_String, + Types.Strings.Alloc (ACL.Argument (I))); + end loop; + Repl.all.Set ((Kind_Symbol, Types.Strings.Alloc ("*ARGV*")), + (Kind_List, Argv)); -- Execute user commands. if Script then Exec ("(load-file """ & ACL.Argument (1) & """)", Repl); @@ -274,7 +297,7 @@ begin -- Other exceptions are really unexpected. -- Collect garbage. - Err.Data := Mal.Nil; + Err.Data := Types.Nil; Repl.all.Keep; Garbage_Collected.Clean; end loop; @@ -282,7 +305,8 @@ begin end if; -- 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; - Symbols.Check_Allocations; end Step6_File; diff --git a/ada.2/step7_quote.adb b/ada.2/step7_quote.adb index b4fe5983..c3ccdfbe 100644 --- a/ada.2/step7_quote.adb +++ b/ada.2/step7_quote.adb @@ -1,51 +1,53 @@ with Ada.Command_Line; with Ada.Containers.Vectors; with Ada.Environment_Variables; -with Ada.Strings.Unbounded; with Ada.Text_IO.Unbounded_IO; with Core; with Envs; with Err; -with Eval_Cb; with Garbage_Collected; with Printer; with Reader; with Readline; with Types.Fns; -with Types.Mal; with Types.Maps; with Types.Sequences; -with Types.Symbols.Names; +with Types.Strings; procedure Step7_Quote is Dbgeval : constant Boolean := Ada.Environment_Variables.Exists ("dbgeval"); - use Types; - use type Mal.T; + use type Types.T; + use all type Types.Kind_Type; + use type Types.Strings.Instance; package ACL renames Ada.Command_Line; - package ASU renames Ada.Strings.Unbounded; + package Vectors is new Ada.Containers.Vectors (Positive, Types.T); - function Read return Mal.T_Array with Inline; + function Read return Types.T_Array with Inline; - function Eval (Ast0 : in Mal.T; - Env0 : in Envs.Ptr) return Mal.T; - function Eval_Builtin (Args : in Mal.T_Array) return Mal.T; + function Eval (Ast0 : in Types.T; + Env0 : in Envs.Ptr) return Types.T; + function Eval_Builtin (Args : in Types.T_Array) return Types.T; -- The built-in variant needs to see the Repl variable. - function Quasiquote (Ast : in Mal.T; - Env : in Envs.Ptr) return Mal.T; + function Quasiquote (Ast : in Types.T; + Env : in Envs.Ptr) return Types.T; -- Mergeing quote and quasiquote into eval with a flag triggering -- a different behaviour as done for macros in step8 would improve -- the performances significantly, but Kanaka finds that it breaks -- too much the step structure shared by all implementations. - procedure Print (Ast : in Mal.T) with Inline; + procedure Print (Ast : in Types.T) with Inline; procedure Rep (Env : in Envs.Ptr) with Inline; - function Eval_Map_Elts is new Maps.Generic_Eval (Envs.Ptr, Eval); + 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; @@ -53,15 +55,18 @@ procedure Step7_Quote is ---------------------------------------------------------------------- - function Eval (Ast0 : in Mal.T; - Env0 : in Envs.Ptr) return Mal.T + function Eval (Ast0 : in Types.T; + Env0 : in Envs.Ptr) return Types.T is - use type Symbols.Ptr; -- Use local variables, that can be rewritten when tail call -- optimization goes to <>. - Ast : Mal.T := Ast0; + Ast : Types.T := Ast0; Env : Envs.Ptr := Env0; - First : Mal.T; + 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 <> if Dbgeval then @@ -72,23 +77,15 @@ procedure Step7_Quote is end if; case Ast.Kind is - when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_Key - | Kind_Macro | Kind_Function => + 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.Symbol); + return Env.all.Get (Ast.Str); when Kind_Map => - return Eval_Map_Elts (Ast.Map.all, Env); + return Eval_Map (Ast.Map.all, Env); when Kind_Vector => - declare - Len : constant Natural := Ast.Sequence.all.Length; - List : constant Mal.Sequence_Ptr := Sequences.Constructor (Len); - begin - for I in 1 .. Len loop - List.all.Replace_Element (I, Eval (Ast.Sequence.all (I), Env)); - end loop; - return (Kind_Vector, List); - end; + return Eval_Vector (Ast.Sequence.all, Env); when Kind_List => null; end case; @@ -97,81 +94,89 @@ procedure Step7_Quote is if Ast.Sequence.all.Length = 0 then return Ast; end if; - First := Ast.Sequence.all (1); + 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.Symbol = Symbols.Names.Def then - Err.Check (Ast.Sequence.all.Length = 3, "expected 2 parameters"); - Err.Check (Ast.Sequence.all (2).Kind = Kind_Symbol, - "parameter 1 must be a symbol"); - return R : constant Mal.T := Eval (Ast.Sequence.all (3), Env) do - Env.all.Set (Ast.Sequence.all (2).Symbol, R); - end return; - -- do is a built-in function, shortening this test cascade. - elsif First.Symbol = Symbols.Names.Fn then - Err.Check (Ast.Sequence.all.Length = 3, "expected 2 parameters"); - Err.Check (Ast.Sequence.all (2).Kind in Kind_Sequence, - "parameter 1 must be a sequence"); - return Fns.New_Function - (Params => Ast.Sequence.all (2).Sequence.all, - Ast => Ast.Sequence.all (3), - Env => Env); - elsif First.Symbol = Symbols.Names.Mal_If then + if First.Str.all = "if" then Err.Check (Ast.Sequence.all.Length in 3 .. 4, "expected 2 or 3 parameters"); declare - Test : constant Mal.T := Eval (Ast.Sequence.all (2), Env); + Tst : constant Types.T := Eval (Ast.Sequence.all.Data (2), Env); begin - if Test /= Mal.Nil and Test /= (Kind_Boolean, False) then - Ast := Ast.Sequence.all (3); + 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 Mal.Nil; + return Types.Nil; else - Ast := Ast.Sequence.all (4); + Ast := Ast.Sequence.all.Data (4); goto Restart; end if; end; - elsif First.Symbol = Symbols.Names.Let then - Err.Check (Ast.Sequence.all.Length = 3, "expected 2 parameters"); - Err.Check (Ast.Sequence.all (2).Kind in Kind_Sequence, - "parameter 1 must be a sequence"); + 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 : constant Mal.Sequence_Ptr - := Ast.Sequence.all (2).Sequence; + Bindings : Types.T_Array + renames Ast.Sequence.all.Data (2).Sequence.all.Data; begin - Err.Check (Bindings.all.Length mod 2 = 0, - "parameter 1 must have an even length"); - Env := Envs.New_Env (Outer => Env); - for I in 1 .. Bindings.all.Length / 2 loop - Err.Check (Bindings.all (2 * I - 1).Kind = Kind_Symbol, - "binding keys must be symbols"); - Env.all.Set (Bindings.all (2 * I - 1).Symbol, - Eval (Bindings.all (2 * I), Env)); + 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 (3); + Ast := Ast.Sequence.all.Data (3); goto Restart; end; - elsif First.Symbol = Symbols.Names.Quasiquote then + elsif First.Str.all = "quote" then Err.Check (Ast.Sequence.all.Length = 2, "expected 1 parameter"); - return Quasiquote (Ast.Sequence.all (2), Env); - elsif First.Symbol = Symbols.Names.Quote then + return Ast.Sequence.all.Data (2); + 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; + -- do is a built-in function, shortening this test cascade. + 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; + elsif First.Str.all = "quasiquote" then Err.Check (Ast.Sequence.all.Length = 2, "expected 1 parameter"); - return Ast.Sequence.all (2); + return Quasiquote (Ast.Sequence.all.Data (2), Env); else -- Equivalent to First := Eval (First, Env) -- except that we already know enough to spare a recursive call. - First := Env.all.Get (First.Symbol); + First := Env.all.Get (First.Str); end if; - when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_Key - | Kind_Macro | Kind_Function => + 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 Kind_Sequence | Kind_Map => + 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); @@ -180,42 +185,64 @@ procedure Step7_Quote is -- Apply phase. -- Ast is a non-empty list, -- First is its non-special evaluated first element. - case First.Kind is - when Kind_Builtin => - declare - Args : Mal.T_Array (2 .. Ast.Sequence.all.Length); - begin - for I in Args'Range loop - Args (I) := Eval (Ast.Sequence.all (I), Env); - end loop; - return First.Builtin.all (Args); - end; - when Kind_Fn => - declare - Args : Mal.T_Array (2 .. Ast.Sequence.all.Length); - begin - for I in Args'Range loop - Args (I) := Eval (Ast.Sequence.all (I), Env); - end loop; - Env := Envs.New_Env (Outer => First.Fn.all.Env, - Binds => First.Fn.all.Params, - Exprs => Args); - Ast := First.Fn.all.Ast; - goto Restart; - end; - when others => - Err.Raise_With ("first element must be a function"); - end case; + 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 : Mal.T; + Result : Types.T; begin for Expression of Reader.Read_Str (Script) loop Result := Eval (Expression, Env); @@ -223,65 +250,66 @@ procedure Step7_Quote is pragma Unreferenced (Result); end Exec; - procedure Print (Ast : in Mal.T) is + procedure Print (Ast : in Types.T) is begin Ada.Text_IO.Unbounded_IO.Put_Line (Printer.Pr_Str (Ast)); end Print; - function Quasiquote (Ast : in Mal.T; - Env : in Envs.Ptr) return Mal.T + function Quasiquote (Ast : in Types.T; + Env : in Envs.Ptr) return Types.T is - function Quasiquote_List (List : in Sequences.Instance) return Mal.T - with Inline; + function Quasiquote_List (List : in Types.T_Array) return Types.T; -- Handle vectors and lists not starting with unquote. - function Quasiquote_List (List : in Sequences.Instance) return Mal.T is - package Vectors is new Ada.Containers.Vectors (Positive, Mal.T); - Vector : Vectors.Vector; -- buffer for concatenation - Sequence : Mal.Sequence_Ptr; - Tmp : Mal.T; + function Quasiquote_List (List : in Types.T_Array) return Types.T is + Vector : Vectors.Vector; -- buffer for concatenation + Tmp : Types.T; begin - for I in 1 .. List.Length loop - if List (I).Kind in Kind_List - and then 0 < List (I).Sequence.all.Length - and then List (I).Sequence.all (1) - = (Kind_Symbol, Symbols.Names.Splice_Unquote) + for Elt of List loop + if Elt.Kind in Kind_List + and then 0 < Elt.Sequence.all.Length + and then Elt.Sequence.all.Data (1).Kind = Kind_Symbol + and then Elt.Sequence.all.Data (1).Str.all = "splice-unquote" then - Err.Check (List (I).Sequence.all.Length = 2, + Err.Check (Elt.Sequence.all.Length = 2, "splice-unquote expects 1 parameter"); - Tmp := Eval (List (I).Sequence.all (2), Env); + Tmp := Eval (Elt.Sequence.all.Data (2), Env); Err.Check (Tmp.Kind = Kind_List, "splice_unquote expects a list"); - for I in 1 .. Tmp.Sequence.all.Length loop - Vector.Append (Tmp.Sequence.all (I)); + for Sub_Elt of Tmp.Sequence.all.Data loop + Vector.Append (Sub_Elt); end loop; else - Vector.Append (Quasiquote (List (I), Env)); + Vector.Append (Quasiquote (Elt, Env)); end if; end loop; -- Now that we know the number of elements, convert to a list. - Sequence := Sequences.Constructor (Natural (Vector.Length)); - for I in 1 .. Natural (Vector.Length) loop - Sequence.Replace_Element (I, Vector (I)); - end loop; - return (Kind_List, Sequence); + declare + Sequence : constant Types.Sequence_Ptr + := Types.Sequences.Constructor (Natural (Vector.Length)); + begin + for I in 1 .. Natural (Vector.Length) loop + Sequence.all.Data (I) := Vector (I); + end loop; + return (Kind_List, Sequence); + end; end Quasiquote_List; begin -- Quasiquote case Ast.Kind is when Kind_Vector => -- When the test is updated, replace Kind_List with Kind_Vector. - return Quasiquote_List (Ast.Sequence.all); + return Quasiquote_List (Ast.Sequence.all.Data); when Kind_List => if 0 < Ast.Sequence.all.Length - and then Ast.Sequence.all (1) = (Kind_Symbol, - Symbols.Names.Unquote) + and then Ast.Sequence.all.Data (1).Kind = Kind_Symbol + and then Ast.Sequence.all.Data (1).Str.all = "unquote" then Err.Check (Ast.Sequence.all.Length = 2, "expected 1 parameter"); - return Eval (Ast.Sequence.all (2), Env); + return Eval (Ast.Sequence.all.Data (2), Env); else - return Quasiquote_List (Ast.Sequence.all); + return Quasiquote_List (Ast.Sequence.all.Data); end if; when others => return Ast; @@ -292,7 +320,7 @@ procedure Step7_Quote is raise; end Quasiquote; - function Read return Mal.T_Array + function Read return Types.T_Array is (Reader.Read_Str (Readline.Input ("user> "))); procedure Rep (Env : in Envs.Ptr) is @@ -309,33 +337,30 @@ procedure Step7_Quote is & "(def! load-file (fn* (f)" & " (eval (read-string (str ""(do "" (slurp f) "")"")))))"; Repl : constant Envs.Ptr := Envs.New_Env; - function Eval_Builtin (Args : in Mal.T_Array) return Mal.T is + function Eval_Builtin (Args : in Types.T_Array) return Types.T is begin Err.Check (Args'Length = 1, "expected 1 parameter"); - return Eval_Cb.Cb.all (Args (Args'First), Repl); + return Eval (Args (Args'First), Repl); end Eval_Builtin; Script : constant Boolean := 0 < ACL.Argument_Count; - Argv : Mal.Sequence_Ptr; + Argv : constant Types.Sequence_Ptr + := Types.Sequences.Constructor (Integer'Max (0, ACL.Argument_Count - 1)); begin -- Show the Eval function to other packages. - Eval_Cb.Cb := Eval'Unrestricted_Access; + Types.Fns.Eval_Cb := Eval'Unrestricted_Access; -- Add Core functions into the top environment. Core.NS_Add_To_Repl (Repl); - Repl.all.Set (Symbols.Constructor ("eval"), + Repl.all.Set ((Kind_Symbol, Types.Strings.Alloc ("eval")), (Kind_Builtin, Eval_Builtin'Unrestricted_Access)); -- Native startup procedure. Exec (Startup, Repl); -- Define ARGV from command line arguments. - if Script then - Argv := Sequences.Constructor (ACL.Argument_Count - 1); - for I in 2 .. ACL.Argument_Count loop - Argv.all.Replace_Element - (I - 1, (Kind_String, ASU.To_Unbounded_String (ACL.Argument (I)))); - end loop; - else - Argv := Sequences.Constructor (0); - end if; - Repl.all.Set (Symbols.Constructor ("*ARGV*"), (Kind_List, Argv)); + for I in 2 .. ACL.Argument_Count loop + Argv.all.Data (I - 1) := (Kind_String, + Types.Strings.Alloc (ACL.Argument (I))); + end loop; + Repl.all.Set ((Kind_Symbol, Types.Strings.Alloc ("*ARGV*")), + (Kind_List, Argv)); -- Execute user commands. if Script then Exec ("(load-file """ & ACL.Argument (1) & """)", Repl); @@ -352,7 +377,7 @@ begin -- Other exceptions are really unexpected. -- Collect garbage. - Err.Data := Mal.Nil; + Err.Data := Types.Nil; Repl.all.Keep; Garbage_Collected.Clean; end loop; @@ -360,7 +385,8 @@ begin end if; -- 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; - Symbols.Check_Allocations; end Step7_Quote; diff --git a/ada.2/step8_macros.adb b/ada.2/step8_macros.adb index 81309218..a5726f0a 100644 --- a/ada.2/step8_macros.adb +++ b/ada.2/step8_macros.adb @@ -1,51 +1,54 @@ with Ada.Command_Line; with Ada.Containers.Vectors; with Ada.Environment_Variables; -with Ada.Strings.Unbounded; with Ada.Text_IO.Unbounded_IO; with Core; with Envs; with Err; -with Eval_Cb; with Garbage_Collected; with Printer; with Reader; with Readline; with Types.Fns; -with Types.Mal; +with Types.Macros; with Types.Maps; with Types.Sequences; -with Types.Symbols.Names; +with Types.Strings; procedure Step8_Macros is Dbgeval : constant Boolean := Ada.Environment_Variables.Exists ("dbgeval"); - use Types; - use type Mal.T; + use type Types.T; + use all type Types.Kind_Type; + use type Types.Strings.Instance; package ACL renames Ada.Command_Line; - package ASU renames Ada.Strings.Unbounded; + package Vectors is new Ada.Containers.Vectors (Positive, Types.T); - function Read return Mal.T_Array with Inline; + function Read return Types.T_Array with Inline; - function Eval (Ast0 : in Mal.T; - Env0 : in Envs.Ptr) return Mal.T; - function Eval_Builtin (Args : in Mal.T_Array) return Mal.T; + function Eval (Ast0 : in Types.T; + Env0 : in Envs.Ptr) return Types.T; + function Eval_Builtin (Args : in Types.T_Array) return Types.T; -- The built-in variant needs to see the Repl variable. - function Quasiquote (Ast : in Mal.T; - Env : in Envs.Ptr) return Mal.T; + function Quasiquote (Ast : in Types.T; + Env : in Envs.Ptr) return Types.T; -- Mergeing quote and quasiquote into eval with a flag triggering -- a different behaviour as done for macros in step8 would improve -- the performances significantly, but Kanaka finds that it breaks -- too much the step structure shared by all implementations. - procedure Print (Ast : in Mal.T) with Inline; + procedure Print (Ast : in Types.T) with Inline; procedure Rep (Env : in Envs.Ptr) with Inline; - function Eval_Map_Elts is new Maps.Generic_Eval (Envs.Ptr, Eval); + 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; @@ -53,16 +56,19 @@ procedure Step8_Macros is ---------------------------------------------------------------------- - function Eval (Ast0 : in Mal.T; - Env0 : in Envs.Ptr) return Mal.T + function Eval (Ast0 : in Types.T; + Env0 : in Envs.Ptr) return Types.T is - use type Symbols.Ptr; -- Use local variables, that can be rewritten when tail call -- optimization goes to <>. - Ast : Mal.T := Ast0; + 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. Macroexpanding : Boolean := False; - First : Mal.T; + First : Types.T; begin <> if Dbgeval then @@ -73,23 +79,15 @@ procedure Step8_Macros is end if; case Ast.Kind is - when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_Key - | Kind_Macro | Kind_Function => + 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.Symbol); + return Env.all.Get (Ast.Str); when Kind_Map => - return Eval_Map_Elts (Ast.Map.all, Env); + return Eval_Map (Ast.Map.all, Env); when Kind_Vector => - declare - Len : constant Natural := Ast.Sequence.all.Length; - List : constant Mal.Sequence_Ptr := Sequences.Constructor (Len); - begin - for I in 1 .. Len loop - List.all.Replace_Element (I, Eval (Ast.Sequence.all (I), Env)); - end loop; - return (Kind_Vector, List); - end; + return Eval_Vector (Ast.Sequence.all, Env); when Kind_List => null; end case; @@ -98,98 +96,106 @@ procedure Step8_Macros is if Ast.Sequence.all.Length = 0 then return Ast; end if; - First := Ast.Sequence.all (1); + 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.Symbol = Symbols.Names.Def then - Err.Check (Ast.Sequence.all.Length = 3, "expected 2 parameters"); - Err.Check (Ast.Sequence.all (2).Kind = Kind_Symbol, - "parameter 1 must be a symbol"); - return R : constant Mal.T := Eval (Ast.Sequence.all (3), Env) do - Env.all.Set (Ast.Sequence.all (2).Symbol, R); - end return; - elsif First.Symbol = Symbols.Names.Defmacro then - Err.Check (Ast.Sequence.all.Length = 3, "expected 2 parameters"); - Err.Check (Ast.Sequence.all (2).Kind = Kind_Symbol, - "parameter 1 must be a symbol"); - declare - F : constant Mal.T := Eval (Ast.Sequence.all (3), Env); - begin - Err.Check (F.Kind = Kind_Fn, "parameter 2 must be a function"); - return R : constant Mal.T := F.Fn.all.New_Macro do - Env.all.Set (Ast.Sequence.all (2).Symbol, R); - end return; - end; - -- do is a built-in function, shortening this test cascade. - elsif First.Symbol = Symbols.Names.Fn then - Err.Check (Ast.Sequence.all.Length = 3, "expected 2 parameters"); - Err.Check (Ast.Sequence.all (2).Kind in Kind_Sequence, - "parameter 1 must be a sequence"); - return Fns.New_Function - (Params => Ast.Sequence.all (2).Sequence.all, - Ast => Ast.Sequence.all (3), - Env => Env); - elsif First.Symbol = Symbols.Names.Mal_If then + if First.Str.all = "if" then Err.Check (Ast.Sequence.all.Length in 3 .. 4, "expected 2 or 3 parameters"); declare - Test : constant Mal.T := Eval (Ast.Sequence.all (2), Env); + Tst : constant Types.T := Eval (Ast.Sequence.all.Data (2), Env); begin - if Test /= Mal.Nil and Test /= (Kind_Boolean, False) then - Ast := Ast.Sequence.all (3); + 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 Mal.Nil; + return Types.Nil; else - Ast := Ast.Sequence.all (4); + Ast := Ast.Sequence.all.Data (4); goto Restart; end if; end; - elsif First.Symbol = Symbols.Names.Let then - Err.Check (Ast.Sequence.all.Length = 3, "expected 2 parameters"); - Err.Check (Ast.Sequence.all (2).Kind in Kind_Sequence, - "parameter 1 must be a sequence"); + 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 : constant Mal.Sequence_Ptr - := Ast.Sequence.all (2).Sequence; + Bindings : Types.T_Array + renames Ast.Sequence.all.Data (2).Sequence.all.Data; begin - Err.Check (Bindings.all.Length mod 2 = 0, - "parameter 1 must have an even length"); - Env := Envs.New_Env (Outer => Env); - for I in 1 .. Bindings.all.Length / 2 loop - Err.Check (Bindings.all (2 * I - 1).Kind = Kind_Symbol, - "binding keys must be symbols"); - Env.all.Set (Bindings.all (2 * I - 1).Symbol, - Eval (Bindings.all (2 * I), Env)); + 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 (3); + Ast := Ast.Sequence.all.Data (3); goto Restart; end; - elsif First.Symbol = Symbols.Names.Macroexpand then + elsif First.Str.all = "quote" then + Err.Check (Ast.Sequence.all.Length = 2, "expected 1 parameter"); + return Ast.Sequence.all.Data (2); + 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 = "defmacro!" then + Err.Check (Ast.Sequence.all.Length = 3, "expected 2 parameters"); + declare + Key : Types.T renames Ast.Sequence.all.Data (2); + Fun : constant Types.T := Eval (Ast.Sequence.all.Data (3), Env); + Val : Types.T; + begin + Err.Check (Fun.Kind = Kind_Fn, "expected a function"); + Val := Types.Macros.New_Macro (Fun.Fn.all); + Env.all.Set (Key, Val); -- Check key kind. + return Val; + end; + -- do is a built-in function, shortening this test cascade. + 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; + elsif First.Str.all = "macroexpand" then Err.Check (Ast.Sequence.all.Length = 2, "expected 1 parameter"); Macroexpanding := True; - Ast := Ast.Sequence.all (2); + Ast := Ast.Sequence.all.Data (2); goto Restart; - elsif First.Symbol = Symbols.Names.Quasiquote then + elsif First.Str.all = "quasiquote" then Err.Check (Ast.Sequence.all.Length = 2, "expected 1 parameter"); - return Quasiquote (Ast.Sequence.all (2), Env); - elsif First.Symbol = Symbols.Names.Quote then - Err.Check (Ast.Sequence.all.Length = 2, "expected 1 parameter"); - return Ast.Sequence.all (2); + return Quasiquote (Ast.Sequence.all.Data (2), Env); else -- Equivalent to First := Eval (First, Env) -- except that we already know enough to spare a recursive call. - First := Env.all.Get (First.Symbol); + First := Env.all.Get (First.Str); end if; - when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_Key - | Kind_Macro | Kind_Function => + 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 Kind_Sequence | Kind_Map => + 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); @@ -199,53 +205,56 @@ procedure Step8_Macros is -- Ast is a non-empty list, -- First is its non-special evaluated first element. case First.Kind is - when Kind_Builtin => + when Kind_Macro => + -- Use the unevaluated arguments. + if Macroexpanding then + -- Evaluate the macro with tail call optimization. + if not Env_Reusable then + Env := Envs.New_Env (Outer => Env); + Env_Reusable := True; + end if; + Env.all.Set_Binds + (Binds => First.Macro.all.Params.all.Data, + Exprs => Ast.Sequence.all.Data (2 .. Ast.Sequence.all.Length)); + Ast := First.Macro.all.Ast; + goto Restart; + else + -- Evaluate the macro normally. declare - Args : Mal.T_Array (2 .. Ast.Sequence.all.Length); + New_Env : constant Envs.Ptr := Envs.New_Env (Outer => Env); begin - for I in Args'Range loop - Args (I) := Eval (Ast.Sequence.all (I), Env); - end loop; - return First.Builtin.all (Args); - end; - when Kind_Fn => - declare - Args : Mal.T_Array (2 .. Ast.Sequence.all.Length); - begin - for I in Args'Range loop - Args (I) := Eval (Ast.Sequence.all (I), Env); - end loop; - Env := Envs.New_Env (Outer => First.Fn.all.Env, - Binds => First.Fn.all.Params, - Exprs => Args); - Ast := First.Fn.all.Ast; + New_Env.all.Set_Binds + (Binds => First.Macro.all.Params.all.Data, + Exprs => Ast.Sequence.all.Data + (2 .. Ast.Sequence.all.Length)); + Ast := Eval (First.Macro.all.Ast, New_Env); + -- Then evaluate the result with TCO. goto Restart; end; - when Kind_Macro => - declare - Args : constant Mal.T_Array - := Ast.Sequence.all.Tail (Ast.Sequence.all.Length - 1); - begin - if Macroexpanding then - -- Evaluate the macro with tail call optimization. - Env := Envs.New_Env (Outer => Env, - Binds => First.Fn.all.Params, - Exprs => Args); - Ast := First.Fn.all.Ast; - goto Restart; - else - -- Evaluate the macro normally. - Ast := Eval (First.Fn.all.Ast, - Envs.New_Env (Outer => Env, - Binds => First.Fn.all.Params, - Exprs => Args)); - -- Then evaluate the result with TCO. - goto Restart; - end if; - end; - when others => - Err.Raise_With ("first element must be a function or macro"); + end if; + when Types.Kind_Function => + null; + when others => + Err.Raise_With ("first element must be a function or macro"); end case; + -- 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 => if Macroexpanding then @@ -256,10 +265,38 @@ procedure Step8_Macros is 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 : Mal.T; + Result : Types.T; begin for Expression of Reader.Read_Str (Script) loop Result := Eval (Expression, Env); @@ -267,65 +304,66 @@ procedure Step8_Macros is pragma Unreferenced (Result); end Exec; - procedure Print (Ast : in Mal.T) is + procedure Print (Ast : in Types.T) is begin Ada.Text_IO.Unbounded_IO.Put_Line (Printer.Pr_Str (Ast)); end Print; - function Quasiquote (Ast : in Mal.T; - Env : in Envs.Ptr) return Mal.T + function Quasiquote (Ast : in Types.T; + Env : in Envs.Ptr) return Types.T is - function Quasiquote_List (List : in Sequences.Instance) return Mal.T - with Inline; + function Quasiquote_List (List : in Types.T_Array) return Types.T; -- Handle vectors and lists not starting with unquote. - function Quasiquote_List (List : in Sequences.Instance) return Mal.T is - package Vectors is new Ada.Containers.Vectors (Positive, Mal.T); - Vector : Vectors.Vector; -- buffer for concatenation - Sequence : Mal.Sequence_Ptr; - Tmp : Mal.T; + function Quasiquote_List (List : in Types.T_Array) return Types.T is + Vector : Vectors.Vector; -- buffer for concatenation + Tmp : Types.T; begin - for I in 1 .. List.Length loop - if List (I).Kind in Kind_List - and then 0 < List (I).Sequence.all.Length - and then List (I).Sequence.all (1) - = (Kind_Symbol, Symbols.Names.Splice_Unquote) + for Elt of List loop + if Elt.Kind in Kind_List + and then 0 < Elt.Sequence.all.Length + and then Elt.Sequence.all.Data (1).Kind = Kind_Symbol + and then Elt.Sequence.all.Data (1).Str.all = "splice-unquote" then - Err.Check (List (I).Sequence.all.Length = 2, + Err.Check (Elt.Sequence.all.Length = 2, "splice-unquote expects 1 parameter"); - Tmp := Eval (List (I).Sequence.all (2), Env); + Tmp := Eval (Elt.Sequence.all.Data (2), Env); Err.Check (Tmp.Kind = Kind_List, "splice_unquote expects a list"); - for I in 1 .. Tmp.Sequence.all.Length loop - Vector.Append (Tmp.Sequence.all (I)); + for Sub_Elt of Tmp.Sequence.all.Data loop + Vector.Append (Sub_Elt); end loop; else - Vector.Append (Quasiquote (List (I), Env)); + Vector.Append (Quasiquote (Elt, Env)); end if; end loop; -- Now that we know the number of elements, convert to a list. - Sequence := Sequences.Constructor (Natural (Vector.Length)); - for I in 1 .. Natural (Vector.Length) loop - Sequence.Replace_Element (I, Vector (I)); - end loop; - return (Kind_List, Sequence); + declare + Sequence : constant Types.Sequence_Ptr + := Types.Sequences.Constructor (Natural (Vector.Length)); + begin + for I in 1 .. Natural (Vector.Length) loop + Sequence.all.Data (I) := Vector (I); + end loop; + return (Kind_List, Sequence); + end; end Quasiquote_List; begin -- Quasiquote case Ast.Kind is when Kind_Vector => -- When the test is updated, replace Kind_List with Kind_Vector. - return Quasiquote_List (Ast.Sequence.all); + return Quasiquote_List (Ast.Sequence.all.Data); when Kind_List => if 0 < Ast.Sequence.all.Length - and then Ast.Sequence.all (1) = (Kind_Symbol, - Symbols.Names.Unquote) + and then Ast.Sequence.all.Data (1).Kind = Kind_Symbol + and then Ast.Sequence.all.Data (1).Str.all = "unquote" then Err.Check (Ast.Sequence.all.Length = 2, "expected 1 parameter"); - return Eval (Ast.Sequence.all (2), Env); + return Eval (Ast.Sequence.all.Data (2), Env); else - return Quasiquote_List (Ast.Sequence.all); + return Quasiquote_List (Ast.Sequence.all.Data); end if; when others => return Ast; @@ -336,7 +374,7 @@ procedure Step8_Macros is raise; end Quasiquote; - function Read return Mal.T_Array + function Read return Types.T_Array is (Reader.Read_Str (Readline.Input ("user> "))); procedure Rep (Env : in Envs.Ptr) is @@ -364,33 +402,30 @@ procedure Step8_Macros is & " `(let* (or_FIXME ~(first xs))" & " (if or_FIXME or_FIXME (or ~@(rest xs))))))))"; Repl : constant Envs.Ptr := Envs.New_Env; - function Eval_Builtin (Args : in Mal.T_Array) return Mal.T is + function Eval_Builtin (Args : in Types.T_Array) return Types.T is begin Err.Check (Args'Length = 1, "expected 1 parameter"); - return Eval_Cb.Cb.all (Args (Args'First), Repl); + return Eval (Args (Args'First), Repl); end Eval_Builtin; Script : constant Boolean := 0 < ACL.Argument_Count; - Argv : Mal.Sequence_Ptr; + Argv : constant Types.Sequence_Ptr + := Types.Sequences.Constructor (Integer'Max (0, ACL.Argument_Count - 1)); begin -- Show the Eval function to other packages. - Eval_Cb.Cb := Eval'Unrestricted_Access; + Types.Fns.Eval_Cb := Eval'Unrestricted_Access; -- Add Core functions into the top environment. Core.NS_Add_To_Repl (Repl); - Repl.all.Set (Symbols.Constructor ("eval"), + Repl.all.Set ((Kind_Symbol, Types.Strings.Alloc ("eval")), (Kind_Builtin, Eval_Builtin'Unrestricted_Access)); -- Native startup procedure. Exec (Startup, Repl); -- Define ARGV from command line arguments. - if Script then - Argv := Sequences.Constructor (ACL.Argument_Count - 1); - for I in 2 .. ACL.Argument_Count loop - Argv.all.Replace_Element - (I - 1, (Kind_String, ASU.To_Unbounded_String (ACL.Argument (I)))); - end loop; - else - Argv := Sequences.Constructor (0); - end if; - Repl.all.Set (Symbols.Constructor ("*ARGV*"), (Kind_List, Argv)); + for I in 2 .. ACL.Argument_Count loop + Argv.all.Data (I - 1) := (Kind_String, + Types.Strings.Alloc (ACL.Argument (I))); + end loop; + Repl.all.Set ((Kind_Symbol, Types.Strings.Alloc ("*ARGV*")), + (Kind_List, Argv)); -- Execute user commands. if Script then Exec ("(load-file """ & ACL.Argument (1) & """)", Repl); @@ -407,7 +442,7 @@ begin -- Other exceptions are really unexpected. -- Collect garbage. - Err.Data := Mal.Nil; + Err.Data := Types.Nil; Repl.all.Keep; Garbage_Collected.Clean; end loop; @@ -415,7 +450,8 @@ begin end if; -- 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; - Symbols.Check_Allocations; end Step8_Macros; diff --git a/ada.2/step9_try.adb b/ada.2/step9_try.adb index a1739989..6c910611 100644 --- a/ada.2/step9_try.adb +++ b/ada.2/step9_try.adb @@ -1,51 +1,54 @@ with Ada.Command_Line; with Ada.Containers.Vectors; with Ada.Environment_Variables; -with Ada.Strings.Unbounded; with Ada.Text_IO.Unbounded_IO; with Core; with Envs; with Err; -with Eval_Cb; with Garbage_Collected; with Printer; with Reader; with Readline; with Types.Fns; -with Types.Mal; +with Types.Macros; with Types.Maps; with Types.Sequences; -with Types.Symbols.Names; +with Types.Strings; procedure Step9_Try is Dbgeval : constant Boolean := Ada.Environment_Variables.Exists ("dbgeval"); - use Types; - use type Mal.T; + use type Types.T; + use all type Types.Kind_Type; + use type Types.Strings.Instance; package ACL renames Ada.Command_Line; - package ASU renames Ada.Strings.Unbounded; + package Vectors is new Ada.Containers.Vectors (Positive, Types.T); - function Read return Mal.T_Array with Inline; + function Read return Types.T_Array with Inline; - function Eval (Ast0 : in Mal.T; - Env0 : in Envs.Ptr) return Mal.T; - function Eval_Builtin (Args : in Mal.T_Array) return Mal.T; + function Eval (Ast0 : in Types.T; + Env0 : in Envs.Ptr) return Types.T; + function Eval_Builtin (Args : in Types.T_Array) return Types.T; -- The built-in variant needs to see the Repl variable. - function Quasiquote (Ast : in Mal.T; - Env : in Envs.Ptr) return Mal.T; + function Quasiquote (Ast : in Types.T; + Env : in Envs.Ptr) return Types.T; -- Mergeing quote and quasiquote into eval with a flag triggering -- a different behaviour as done for macros in step8 would improve -- the performances significantly, but Kanaka finds that it breaks -- too much the step structure shared by all implementations. - procedure Print (Ast : in Mal.T) with Inline; + procedure Print (Ast : in Types.T) with Inline; procedure Rep (Env : in Envs.Ptr) with Inline; - function Eval_Map_Elts is new Maps.Generic_Eval (Envs.Ptr, Eval); + 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; @@ -53,16 +56,19 @@ procedure Step9_Try is ---------------------------------------------------------------------- - function Eval (Ast0 : in Mal.T; - Env0 : in Envs.Ptr) return Mal.T + function Eval (Ast0 : in Types.T; + Env0 : in Envs.Ptr) return Types.T is - use type Symbols.Ptr; -- Use local variables, that can be rewritten when tail call -- optimization goes to <>. - Ast : Mal.T := Ast0; + 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. Macroexpanding : Boolean := False; - First : Mal.T; + First : Types.T; begin <> if Dbgeval then @@ -73,23 +79,15 @@ procedure Step9_Try is end if; case Ast.Kind is - when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_Key - | Kind_Macro | Kind_Function => + 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.Symbol); + return Env.all.Get (Ast.Str); when Kind_Map => - return Eval_Map_Elts (Ast.Map.all, Env); + return Eval_Map (Ast.Map.all, Env); when Kind_Vector => - declare - Len : constant Natural := Ast.Sequence.all.Length; - List : constant Mal.Sequence_Ptr := Sequences.Constructor (Len); - begin - for I in 1 .. Len loop - List.all.Replace_Element (I, Eval (Ast.Sequence.all (I), Env)); - end loop; - return (Kind_Vector, List); - end; + return Eval_Vector (Ast.Sequence.all, Env); when Kind_List => null; end case; @@ -98,127 +96,136 @@ procedure Step9_Try is if Ast.Sequence.all.Length = 0 then return Ast; end if; - First := Ast.Sequence.all (1); + 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.Symbol = Symbols.Names.Def then - Err.Check (Ast.Sequence.all.Length = 3, "expected 2 parameters"); - Err.Check (Ast.Sequence.all (2).Kind = Kind_Symbol, - "parameter 1 must be a symbol"); - return R : constant Mal.T := Eval (Ast.Sequence.all (3), Env) do - Env.all.Set (Ast.Sequence.all (2).Symbol, R); - end return; - elsif First.Symbol = Symbols.Names.Defmacro then - Err.Check (Ast.Sequence.all.Length = 3, "expected 2 parameters"); - Err.Check (Ast.Sequence.all (2).Kind = Kind_Symbol, - "parameter 1 must be a symbol"); - declare - F : constant Mal.T := Eval (Ast.Sequence.all (3), Env); - begin - Err.Check (F.Kind = Kind_Fn, "parameter 2 must be a function"); - return R : constant Mal.T := F.Fn.all.New_Macro do - Env.all.Set (Ast.Sequence.all (2).Symbol, R); - end return; - end; - -- do is a built-in function, shortening this test cascade. - elsif First.Symbol = Symbols.Names.Fn then - Err.Check (Ast.Sequence.all.Length = 3, "expected 2 parameters"); - Err.Check (Ast.Sequence.all (2).Kind in Kind_Sequence, - "parameter 1 must be a sequence"); - return Fns.New_Function - (Params => Ast.Sequence.all (2).Sequence.all, - Ast => Ast.Sequence.all (3), - Env => Env); - elsif First.Symbol = Symbols.Names.Mal_If then + if First.Str.all = "if" then Err.Check (Ast.Sequence.all.Length in 3 .. 4, "expected 2 or 3 parameters"); declare - Test : constant Mal.T := Eval (Ast.Sequence.all (2), Env); + Tst : constant Types.T := Eval (Ast.Sequence.all.Data (2), Env); begin - if Test /= Mal.Nil and Test /= (Kind_Boolean, False) then - Ast := Ast.Sequence.all (3); + 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 Mal.Nil; + return Types.Nil; else - Ast := Ast.Sequence.all (4); + Ast := Ast.Sequence.all.Data (4); goto Restart; end if; end; - elsif First.Symbol = Symbols.Names.Let then - Err.Check (Ast.Sequence.all.Length = 3, "expected 2 parameters"); - Err.Check (Ast.Sequence.all (2).Kind in Kind_Sequence, - "parameter 1 must be a sequence"); + 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 : constant Mal.Sequence_Ptr - := Ast.Sequence.all (2).Sequence; + Bindings : Types.T_Array + renames Ast.Sequence.all.Data (2).Sequence.all.Data; begin - Err.Check (Bindings.all.Length mod 2 = 0, - "parameter 1 must have an even length"); - Env := Envs.New_Env (Outer => Env); - for I in 1 .. Bindings.all.Length / 2 loop - Err.Check (Bindings.all (2 * I - 1).Kind = Kind_Symbol, - "binding keys must be symbols"); - Env.all.Set (Bindings.all (2 * I - 1).Symbol, - Eval (Bindings.all (2 * I), Env)); + 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 (3); + Ast := Ast.Sequence.all.Data (3); goto Restart; end; - elsif First.Symbol = Symbols.Names.Macroexpand then + elsif First.Str.all = "quote" then + Err.Check (Ast.Sequence.all.Length = 2, "expected 1 parameter"); + return Ast.Sequence.all.Data (2); + 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 = "defmacro!" then + Err.Check (Ast.Sequence.all.Length = 3, "expected 2 parameters"); + declare + Key : Types.T renames Ast.Sequence.all.Data (2); + Fun : constant Types.T := Eval (Ast.Sequence.all.Data (3), Env); + Val : Types.T; + begin + Err.Check (Fun.Kind = Kind_Fn, "expected a function"); + Val := Types.Macros.New_Macro (Fun.Fn.all); + Env.all.Set (Key, Val); -- Check key kind. + return Val; + end; + -- do is a built-in function, shortening this test cascade. + 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; + elsif First.Str.all = "macroexpand" then Err.Check (Ast.Sequence.all.Length = 2, "expected 1 parameter"); Macroexpanding := True; - Ast := Ast.Sequence.all (2); + Ast := Ast.Sequence.all.Data (2); goto Restart; - elsif First.Symbol = Symbols.Names.Quasiquote then + elsif First.Str.all = "quasiquote" then Err.Check (Ast.Sequence.all.Length = 2, "expected 1 parameter"); - return Quasiquote (Ast.Sequence.all (2), Env); - elsif First.Symbol = Symbols.Names.Quote then - Err.Check (Ast.Sequence.all.Length = 2, "expected 1 parameter"); - return Ast.Sequence.all (2); - elsif First.Symbol = Symbols.Names.Try then + return Quasiquote (Ast.Sequence.all.Data (2), Env); + elsif First.Str.all = "try*" then if Ast.Sequence.all.Length = 2 then - Ast := Ast.Sequence.all (2); + Ast := Ast.Sequence.all.Data (2); goto Restart; end if; - Err.Check (Ast.Sequence.all.Length = 3, - "expected 1 or 2 parameters"); - Err.Check (Ast.Sequence.all (3).Kind = Kind_List, - "parameter 2 must be a list"); + Err.Check (Ast.Sequence.all.Length = 3 + and then Ast.Sequence.all.Data (3).Kind = Kind_List, + "expected 1 parameter, maybe followed by a list"); declare - A3 : constant Mal.Sequence_Ptr := Ast.Sequence.all (3).Sequence; + A3 : Types.T_Array + renames Ast.Sequence.all.Data (3).Sequence.all.Data; begin - Err.Check (A3.all.Length = 3, - "length of parameter 2 must be 3"); - Err.Check (A3.all (1) = (Kind_Symbol, Symbols.Names.Catch), - "parameter 3 must start with 'catch*'"); - Err.Check (A3.all (2).Kind = Kind_Symbol, - "a symbol must follow catch*"); + Err.Check (A3'Length = 3 + and then A3 (A3'First).Kind = Kind_Symbol + and then A3 (A3'First).Str.all = "catch*", + "3rd parameter if present must be a catch* list"); begin - return Eval (Ast.Sequence.all (2), Env); + return Eval (Ast.Sequence.all.Data (2), Env); exception when Err.Error => null; end; - Env := Envs.New_Env (Outer => Env); - Env.all.Set (A3.all (2).Symbol, Err.Data); - Ast := A3.all (3); + if not Env_Reusable then + Env := Envs.New_Env (Outer => Env); + Env_Reusable := True; + end if; + Env.all.Set (A3 (A3'First + 1), Err.Data); -- check key kind + Ast := A3 (A3'Last); goto Restart; end; else -- Equivalent to First := Eval (First, Env) -- except that we already know enough to spare a recursive call. - First := Env.all.Get (First.Symbol); + First := Env.all.Get (First.Str); end if; - when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_Key - | Kind_Macro | Kind_Function => + 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 Kind_Sequence | Kind_Map => + 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); @@ -228,53 +235,56 @@ procedure Step9_Try is -- Ast is a non-empty list, -- First is its non-special evaluated first element. case First.Kind is - when Kind_Builtin => + when Kind_Macro => + -- Use the unevaluated arguments. + if Macroexpanding then + -- Evaluate the macro with tail call optimization. + if not Env_Reusable then + Env := Envs.New_Env (Outer => Env); + Env_Reusable := True; + end if; + Env.all.Set_Binds + (Binds => First.Macro.all.Params.all.Data, + Exprs => Ast.Sequence.all.Data (2 .. Ast.Sequence.all.Length)); + Ast := First.Macro.all.Ast; + goto Restart; + else + -- Evaluate the macro normally. declare - Args : Mal.T_Array (2 .. Ast.Sequence.all.Length); + New_Env : constant Envs.Ptr := Envs.New_Env (Outer => Env); begin - for I in Args'Range loop - Args (I) := Eval (Ast.Sequence.all (I), Env); - end loop; - return First.Builtin.all (Args); - end; - when Kind_Fn => - declare - Args : Mal.T_Array (2 .. Ast.Sequence.all.Length); - begin - for I in Args'Range loop - Args (I) := Eval (Ast.Sequence.all (I), Env); - end loop; - Env := Envs.New_Env (Outer => First.Fn.all.Env, - Binds => First.Fn.all.Params, - Exprs => Args); - Ast := First.Fn.all.Ast; + New_Env.all.Set_Binds + (Binds => First.Macro.all.Params.all.Data, + Exprs => Ast.Sequence.all.Data + (2 .. Ast.Sequence.all.Length)); + Ast := Eval (First.Macro.all.Ast, New_Env); + -- Then evaluate the result with TCO. goto Restart; end; - when Kind_Macro => - declare - Args : constant Mal.T_Array - := Ast.Sequence.all.Tail (Ast.Sequence.all.Length - 1); - begin - if Macroexpanding then - -- Evaluate the macro with tail call optimization. - Env := Envs.New_Env (Outer => Env, - Binds => First.Fn.all.Params, - Exprs => Args); - Ast := First.Fn.all.Ast; - goto Restart; - else - -- Evaluate the macro normally. - Ast := Eval (First.Fn.all.Ast, - Envs.New_Env (Outer => Env, - Binds => First.Fn.all.Params, - Exprs => Args)); - -- Then evaluate the result with TCO. - goto Restart; - end if; - end; - when others => - Err.Raise_With ("first element must be a function or macro"); + end if; + when Types.Kind_Function => + null; + when others => + Err.Raise_With ("first element must be a function or macro"); end case; + -- 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 => if Macroexpanding then @@ -285,10 +295,38 @@ procedure Step9_Try is 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 : Mal.T; + Result : Types.T; begin for Expression of Reader.Read_Str (Script) loop Result := Eval (Expression, Env); @@ -296,65 +334,66 @@ procedure Step9_Try is pragma Unreferenced (Result); end Exec; - procedure Print (Ast : in Mal.T) is + procedure Print (Ast : in Types.T) is begin Ada.Text_IO.Unbounded_IO.Put_Line (Printer.Pr_Str (Ast)); end Print; - function Quasiquote (Ast : in Mal.T; - Env : in Envs.Ptr) return Mal.T + function Quasiquote (Ast : in Types.T; + Env : in Envs.Ptr) return Types.T is - function Quasiquote_List (List : in Sequences.Instance) return Mal.T - with Inline; + function Quasiquote_List (List : in Types.T_Array) return Types.T; -- Handle vectors and lists not starting with unquote. - function Quasiquote_List (List : in Sequences.Instance) return Mal.T is - package Vectors is new Ada.Containers.Vectors (Positive, Mal.T); - Vector : Vectors.Vector; -- buffer for concatenation - Sequence : Mal.Sequence_Ptr; - Tmp : Mal.T; + function Quasiquote_List (List : in Types.T_Array) return Types.T is + Vector : Vectors.Vector; -- buffer for concatenation + Tmp : Types.T; begin - for I in 1 .. List.Length loop - if List (I).Kind in Kind_List - and then 0 < List (I).Sequence.all.Length - and then List (I).Sequence.all (1) - = (Kind_Symbol, Symbols.Names.Splice_Unquote) + for Elt of List loop + if Elt.Kind in Kind_List + and then 0 < Elt.Sequence.all.Length + and then Elt.Sequence.all.Data (1).Kind = Kind_Symbol + and then Elt.Sequence.all.Data (1).Str.all = "splice-unquote" then - Err.Check (List (I).Sequence.all.Length = 2, + Err.Check (Elt.Sequence.all.Length = 2, "splice-unquote expects 1 parameter"); - Tmp := Eval (List (I).Sequence.all (2), Env); + Tmp := Eval (Elt.Sequence.all.Data (2), Env); Err.Check (Tmp.Kind = Kind_List, "splice_unquote expects a list"); - for I in 1 .. Tmp.Sequence.all.Length loop - Vector.Append (Tmp.Sequence.all (I)); + for Sub_Elt of Tmp.Sequence.all.Data loop + Vector.Append (Sub_Elt); end loop; else - Vector.Append (Quasiquote (List (I), Env)); + Vector.Append (Quasiquote (Elt, Env)); end if; end loop; -- Now that we know the number of elements, convert to a list. - Sequence := Sequences.Constructor (Natural (Vector.Length)); - for I in 1 .. Natural (Vector.Length) loop - Sequence.Replace_Element (I, Vector (I)); - end loop; - return (Kind_List, Sequence); + declare + Sequence : constant Types.Sequence_Ptr + := Types.Sequences.Constructor (Natural (Vector.Length)); + begin + for I in 1 .. Natural (Vector.Length) loop + Sequence.all.Data (I) := Vector (I); + end loop; + return (Kind_List, Sequence); + end; end Quasiquote_List; begin -- Quasiquote case Ast.Kind is when Kind_Vector => -- When the test is updated, replace Kind_List with Kind_Vector. - return Quasiquote_List (Ast.Sequence.all); + return Quasiquote_List (Ast.Sequence.all.Data); when Kind_List => if 0 < Ast.Sequence.all.Length - and then Ast.Sequence.all (1) = (Kind_Symbol, - Symbols.Names.Unquote) + and then Ast.Sequence.all.Data (1).Kind = Kind_Symbol + and then Ast.Sequence.all.Data (1).Str.all = "unquote" then Err.Check (Ast.Sequence.all.Length = 2, "expected 1 parameter"); - return Eval (Ast.Sequence.all (2), Env); + return Eval (Ast.Sequence.all.Data (2), Env); else - return Quasiquote_List (Ast.Sequence.all); + return Quasiquote_List (Ast.Sequence.all.Data); end if; when others => return Ast; @@ -365,7 +404,7 @@ procedure Step9_Try is raise; end Quasiquote; - function Read return Mal.T_Array + function Read return Types.T_Array is (Reader.Read_Str (Readline.Input ("user> "))); procedure Rep (Env : in Envs.Ptr) is @@ -393,33 +432,30 @@ procedure Step9_Try is & " `(let* (or_FIXME ~(first xs))" & " (if or_FIXME or_FIXME (or ~@(rest xs))))))))"; Repl : constant Envs.Ptr := Envs.New_Env; - function Eval_Builtin (Args : in Mal.T_Array) return Mal.T is + function Eval_Builtin (Args : in Types.T_Array) return Types.T is begin Err.Check (Args'Length = 1, "expected 1 parameter"); - return Eval_Cb.Cb.all (Args (Args'First), Repl); + return Eval (Args (Args'First), Repl); end Eval_Builtin; Script : constant Boolean := 0 < ACL.Argument_Count; - Argv : Mal.Sequence_Ptr; + Argv : constant Types.Sequence_Ptr + := Types.Sequences.Constructor (Integer'Max (0, ACL.Argument_Count - 1)); begin -- Show the Eval function to other packages. - Eval_Cb.Cb := Eval'Unrestricted_Access; + Types.Fns.Eval_Cb := Eval'Unrestricted_Access; -- Add Core functions into the top environment. Core.NS_Add_To_Repl (Repl); - Repl.all.Set (Symbols.Constructor ("eval"), + Repl.all.Set ((Kind_Symbol, Types.Strings.Alloc ("eval")), (Kind_Builtin, Eval_Builtin'Unrestricted_Access)); -- Native startup procedure. Exec (Startup, Repl); -- Define ARGV from command line arguments. - if Script then - Argv := Sequences.Constructor (ACL.Argument_Count - 1); - for I in 2 .. ACL.Argument_Count loop - Argv.all.Replace_Element - (I - 1, (Kind_String, ASU.To_Unbounded_String (ACL.Argument (I)))); - end loop; - else - Argv := Sequences.Constructor (0); - end if; - Repl.all.Set (Symbols.Constructor ("*ARGV*"), (Kind_List, Argv)); + for I in 2 .. ACL.Argument_Count loop + Argv.all.Data (I - 1) := (Kind_String, + Types.Strings.Alloc (ACL.Argument (I))); + end loop; + Repl.all.Set ((Kind_Symbol, Types.Strings.Alloc ("*ARGV*")), + (Kind_List, Argv)); -- Execute user commands. if Script then Exec ("(load-file """ & ACL.Argument (1) & """)", Repl); @@ -436,7 +472,7 @@ begin -- Other exceptions are really unexpected. -- Collect garbage. - Err.Data := Mal.Nil; + Err.Data := Types.Nil; Repl.all.Keep; Garbage_Collected.Clean; end loop; @@ -444,7 +480,8 @@ begin end if; -- 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; - Symbols.Check_Allocations; end Step9_Try; diff --git a/ada.2/stepa_mal.adb b/ada.2/stepa_mal.adb index e18839cc..b912336c 100644 --- a/ada.2/stepa_mal.adb +++ b/ada.2/stepa_mal.adb @@ -1,52 +1,55 @@ with Ada.Command_Line; with Ada.Containers.Vectors; with Ada.Environment_Variables; -with Ada.Strings.Unbounded; with Ada.Text_IO.Unbounded_IO; with Core; with Envs; with Err; -with Eval_Cb; with Garbage_Collected; with Printer; with Reader; with Readline; with Types.Builtins; with Types.Fns; -with Types.Mal; +with Types.Macros; with Types.Maps; with Types.Sequences; -with Types.Symbols.Names; +with Types.Strings; procedure StepA_Mal is Dbgeval : constant Boolean := Ada.Environment_Variables.Exists ("dbgeval"); - use Types; - use type Mal.T; + use type Types.T; + use all type Types.Kind_Type; + use type Types.Strings.Instance; package ACL renames Ada.Command_Line; - package ASU renames Ada.Strings.Unbounded; + package Vectors is new Ada.Containers.Vectors (Positive, Types.T); - function Read return Mal.T_Array with Inline; + function Read return Types.T_Array with Inline; - function Eval (Ast0 : in Mal.T; - Env0 : in Envs.Ptr) return Mal.T; - function Eval_Builtin (Args : in Mal.T_Array) return Mal.T; + function Eval (Ast0 : in Types.T; + Env0 : in Envs.Ptr) return Types.T; + function Eval_Builtin (Args : in Types.T_Array) return Types.T; -- The built-in variant needs to see the Repl variable. - function Quasiquote (Ast : in Mal.T; - Env : in Envs.Ptr) return Mal.T; + function Quasiquote (Ast : in Types.T; + Env : in Envs.Ptr) return Types.T; -- Mergeing quote and quasiquote into eval with a flag triggering -- a different behaviour as done for macros in step8 would improve -- the performances significantly, but Kanaka finds that it breaks -- too much the step structure shared by all implementations. - procedure Print (Ast : in Mal.T) with Inline; + procedure Print (Ast : in Types.T) with Inline; procedure Rep (Env : in Envs.Ptr) with Inline; - function Eval_Map_Elts is new Maps.Generic_Eval (Envs.Ptr, Eval); + 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; @@ -54,16 +57,19 @@ procedure StepA_Mal is ---------------------------------------------------------------------- - function Eval (Ast0 : in Mal.T; - Env0 : in Envs.Ptr) return Mal.T + function Eval (Ast0 : in Types.T; + Env0 : in Envs.Ptr) return Types.T is - use type Symbols.Ptr; -- Use local variables, that can be rewritten when tail call -- optimization goes to <>. - Ast : Mal.T := Ast0; + 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. Macroexpanding : Boolean := False; - First : Mal.T; + First : Types.T; begin <> if Dbgeval then @@ -74,23 +80,15 @@ procedure StepA_Mal is end if; case Ast.Kind is - when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_Key - | Kind_Macro | Kind_Function => + 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.Symbol); + return Env.all.Get (Ast.Str); when Kind_Map => - return Eval_Map_Elts (Ast.Map.all, Env); + return Eval_Map (Ast.Map.all, Env); when Kind_Vector => - declare - Len : constant Natural := Ast.Sequence.all.Length; - List : constant Mal.Sequence_Ptr := Sequences.Constructor (Len); - begin - for I in 1 .. Len loop - List.all.Replace_Element (I, Eval (Ast.Sequence.all (I), Env)); - end loop; - return (Kind_Vector, List); - end; + return Eval_Vector (Ast.Sequence.all, Env); when Kind_List => null; end case; @@ -99,127 +97,136 @@ procedure StepA_Mal is if Ast.Sequence.all.Length = 0 then return Ast; end if; - First := Ast.Sequence.all (1); + 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.Symbol = Symbols.Names.Def then - Err.Check (Ast.Sequence.all.Length = 3, "expected 2 parameters"); - Err.Check (Ast.Sequence.all (2).Kind = Kind_Symbol, - "parameter 1 must be a symbol"); - return R : constant Mal.T := Eval (Ast.Sequence.all (3), Env) do - Env.all.Set (Ast.Sequence.all (2).Symbol, R); - end return; - elsif First.Symbol = Symbols.Names.Defmacro then - Err.Check (Ast.Sequence.all.Length = 3, "expected 2 parameters"); - Err.Check (Ast.Sequence.all (2).Kind = Kind_Symbol, - "parameter 1 must be a symbol"); - declare - F : constant Mal.T := Eval (Ast.Sequence.all (3), Env); - begin - Err.Check (F.Kind = Kind_Fn, "parameter 2 must be a function"); - return R : constant Mal.T := F.Fn.all.New_Macro do - Env.all.Set (Ast.Sequence.all (2).Symbol, R); - end return; - end; - -- do is a built-in function, shortening this test cascade. - elsif First.Symbol = Symbols.Names.Fn then - Err.Check (Ast.Sequence.all.Length = 3, "expected 2 parameters"); - Err.Check (Ast.Sequence.all (2).Kind in Kind_Sequence, - "parameter 1 must be a sequence"); - return Fns.New_Function - (Params => Ast.Sequence.all (2).Sequence.all, - Ast => Ast.Sequence.all (3), - Env => Env); - elsif First.Symbol = Symbols.Names.Mal_If then + if First.Str.all = "if" then Err.Check (Ast.Sequence.all.Length in 3 .. 4, "expected 2 or 3 parameters"); declare - Test : constant Mal.T := Eval (Ast.Sequence.all (2), Env); + Tst : constant Types.T := Eval (Ast.Sequence.all.Data (2), Env); begin - if Test /= Mal.Nil and Test /= (Kind_Boolean, False) then - Ast := Ast.Sequence.all (3); + 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 Mal.Nil; + return Types.Nil; else - Ast := Ast.Sequence.all (4); + Ast := Ast.Sequence.all.Data (4); goto Restart; end if; end; - elsif First.Symbol = Symbols.Names.Let then - Err.Check (Ast.Sequence.all.Length = 3, "expected 2 parameters"); - Err.Check (Ast.Sequence.all (2).Kind in Kind_Sequence, - "parameter 1 must be a sequence"); + 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 : constant Mal.Sequence_Ptr - := Ast.Sequence.all (2).Sequence; + Bindings : Types.T_Array + renames Ast.Sequence.all.Data (2).Sequence.all.Data; begin - Err.Check (Bindings.all.Length mod 2 = 0, - "parameter 1 must have an even length"); - Env := Envs.New_Env (Outer => Env); - for I in 1 .. Bindings.all.Length / 2 loop - Err.Check (Bindings.all (2 * I - 1).Kind = Kind_Symbol, - "binding keys must be symbols"); - Env.all.Set (Bindings.all (2 * I - 1).Symbol, - Eval (Bindings.all (2 * I), Env)); + 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 (3); + Ast := Ast.Sequence.all.Data (3); goto Restart; end; - elsif First.Symbol = Symbols.Names.Macroexpand then + elsif First.Str.all = "quote" then + Err.Check (Ast.Sequence.all.Length = 2, "expected 1 parameter"); + return Ast.Sequence.all.Data (2); + 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 = "defmacro!" then + Err.Check (Ast.Sequence.all.Length = 3, "expected 2 parameters"); + declare + Key : Types.T renames Ast.Sequence.all.Data (2); + Fun : constant Types.T := Eval (Ast.Sequence.all.Data (3), Env); + Val : Types.T; + begin + Err.Check (Fun.Kind = Kind_Fn, "expected a function"); + Val := Types.Macros.New_Macro (Fun.Fn.all); + Env.all.Set (Key, Val); -- Check key kind. + return Val; + end; + -- do is a built-in function, shortening this test cascade. + 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; + elsif First.Str.all = "macroexpand" then Err.Check (Ast.Sequence.all.Length = 2, "expected 1 parameter"); Macroexpanding := True; - Ast := Ast.Sequence.all (2); + Ast := Ast.Sequence.all.Data (2); goto Restart; - elsif First.Symbol = Symbols.Names.Quasiquote then + elsif First.Str.all = "quasiquote" then Err.Check (Ast.Sequence.all.Length = 2, "expected 1 parameter"); - return Quasiquote (Ast.Sequence.all (2), Env); - elsif First.Symbol = Symbols.Names.Quote then - Err.Check (Ast.Sequence.all.Length = 2, "expected 1 parameter"); - return Ast.Sequence.all (2); - elsif First.Symbol = Symbols.Names.Try then + return Quasiquote (Ast.Sequence.all.Data (2), Env); + elsif First.Str.all = "try*" then if Ast.Sequence.all.Length = 2 then - Ast := Ast.Sequence.all (2); + Ast := Ast.Sequence.all.Data (2); goto Restart; end if; - Err.Check (Ast.Sequence.all.Length = 3, - "expected 1 or 2 parameters"); - Err.Check (Ast.Sequence.all (3).Kind = Kind_List, - "parameter 2 must be a list"); + Err.Check (Ast.Sequence.all.Length = 3 + and then Ast.Sequence.all.Data (3).Kind = Kind_List, + "expected 1 parameter, maybe followed by a list"); declare - A3 : constant Mal.Sequence_Ptr := Ast.Sequence.all (3).Sequence; + A3 : Types.T_Array + renames Ast.Sequence.all.Data (3).Sequence.all.Data; begin - Err.Check (A3.all.Length = 3, - "length of parameter 2 must be 3"); - Err.Check (A3.all (1) = (Kind_Symbol, Symbols.Names.Catch), - "parameter 3 must start with 'catch*'"); - Err.Check (A3.all (2).Kind = Kind_Symbol, - "a symbol must follow catch*"); + Err.Check (A3'Length = 3 + and then A3 (A3'First).Kind = Kind_Symbol + and then A3 (A3'First).Str.all = "catch*", + "3rd parameter if present must be a catch* list"); begin - return Eval (Ast.Sequence.all (2), Env); + return Eval (Ast.Sequence.all.Data (2), Env); exception when Err.Error => null; end; - Env := Envs.New_Env (Outer => Env); - Env.all.Set (A3.all (2).Symbol, Err.Data); - Ast := A3.all (3); + if not Env_Reusable then + Env := Envs.New_Env (Outer => Env); + Env_Reusable := True; + end if; + Env.all.Set (A3 (A3'First + 1), Err.Data); -- check key kind + Ast := A3 (A3'Last); goto Restart; end; else -- Equivalent to First := Eval (First, Env) -- except that we already know enough to spare a recursive call. - First := Env.all.Get (First.Symbol); + First := Env.all.Get (First.Str); end if; - when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_Key - | Kind_Macro | Kind_Function => + 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 Kind_Sequence | Kind_Map => + 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); @@ -229,62 +236,61 @@ procedure StepA_Mal is -- Ast is a non-empty list, -- First is its non-special evaluated first element. case First.Kind is - when Kind_Builtin => + when Kind_Macro => + -- Use the unevaluated arguments. + if Macroexpanding then + -- Evaluate the macro with tail call optimization. + if not Env_Reusable then + Env := Envs.New_Env (Outer => Env); + Env_Reusable := True; + end if; + Env.all.Set_Binds + (Binds => First.Macro.all.Params.all.Data, + Exprs => Ast.Sequence.all.Data (2 .. Ast.Sequence.all.Length)); + Ast := First.Macro.all.Ast; + goto Restart; + else + -- Evaluate the macro normally. declare - Args : Mal.T_Array (2 .. Ast.Sequence.all.Length); + New_Env : constant Envs.Ptr := Envs.New_Env (Outer => Env); begin - for I in Args'Range loop - Args (I) := Eval (Ast.Sequence.all (I), Env); - end loop; - return First.Builtin.all (Args); - end; - when Kind_Builtin_With_Meta => - declare - Args : Mal.T_Array (2 .. Ast.Sequence.all.Length); - begin - for I in Args'Range loop - Args (I) := Eval (Ast.Sequence.all (I), Env); - end loop; - return First.Builtin_With_Meta.all.Builtin.all (Args); - end; - when Kind_Fn => - declare - Args : Mal.T_Array (2 .. Ast.Sequence.all.Length); - begin - for I in Args'Range loop - Args (I) := Eval (Ast.Sequence.all (I), Env); - end loop; - Env := Envs.New_Env (Outer => First.Fn.all.Env, - Binds => First.Fn.all.Params, - Exprs => Args); - Ast := First.Fn.all.Ast; + New_Env.all.Set_Binds + (Binds => First.Macro.all.Params.all.Data, + Exprs => Ast.Sequence.all.Data + (2 .. Ast.Sequence.all.Length)); + Ast := Eval (First.Macro.all.Ast, New_Env); + -- Then evaluate the result with TCO. goto Restart; end; - when Kind_Macro => - declare - Args : constant Mal.T_Array - := Ast.Sequence.all.Tail (Ast.Sequence.all.Length - 1); - begin - if Macroexpanding then - -- Evaluate the macro with tail call optimization. - Env := Envs.New_Env (Outer => Env, - Binds => First.Fn.all.Params, - Exprs => Args); - Ast := First.Fn.all.Ast; - goto Restart; - else - -- Evaluate the macro normally. - Ast := Eval (First.Fn.all.Ast, - Envs.New_Env (Outer => Env, - Binds => First.Fn.all.Params, - Exprs => Args)); - -- Then evaluate the result with TCO. - goto Restart; - end if; - end; - when others => - Err.Raise_With ("first element must be a function or macro"); + end if; + when Types.Kind_Function => + null; + when others => + Err.Raise_With ("first element must be a function or macro"); end case; + -- 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; + case First.Kind is + when Kind_Builtin => + return First.Builtin.all (Args); + when Kind_Builtin_With_Meta => + return First.Builtin_With_Meta.all.Builtin.all (Args); + when others => + null; + end case; + -- 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 => if Macroexpanding then @@ -295,10 +301,38 @@ procedure StepA_Mal is 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 : Mal.T; + Result : Types.T; begin for Expression of Reader.Read_Str (Script) loop Result := Eval (Expression, Env); @@ -306,65 +340,66 @@ procedure StepA_Mal is pragma Unreferenced (Result); end Exec; - procedure Print (Ast : in Mal.T) is + procedure Print (Ast : in Types.T) is begin Ada.Text_IO.Unbounded_IO.Put_Line (Printer.Pr_Str (Ast)); end Print; - function Quasiquote (Ast : in Mal.T; - Env : in Envs.Ptr) return Mal.T + function Quasiquote (Ast : in Types.T; + Env : in Envs.Ptr) return Types.T is - function Quasiquote_List (List : in Sequences.Instance) return Mal.T - with Inline; + function Quasiquote_List (List : in Types.T_Array) return Types.T; -- Handle vectors and lists not starting with unquote. - function Quasiquote_List (List : in Sequences.Instance) return Mal.T is - package Vectors is new Ada.Containers.Vectors (Positive, Mal.T); - Vector : Vectors.Vector; -- buffer for concatenation - Sequence : Mal.Sequence_Ptr; - Tmp : Mal.T; + function Quasiquote_List (List : in Types.T_Array) return Types.T is + Vector : Vectors.Vector; -- buffer for concatenation + Tmp : Types.T; begin - for I in 1 .. List.Length loop - if List (I).Kind in Kind_List - and then 0 < List (I).Sequence.all.Length - and then List (I).Sequence.all (1) - = (Kind_Symbol, Symbols.Names.Splice_Unquote) + for Elt of List loop + if Elt.Kind in Kind_List + and then 0 < Elt.Sequence.all.Length + and then Elt.Sequence.all.Data (1).Kind = Kind_Symbol + and then Elt.Sequence.all.Data (1).Str.all = "splice-unquote" then - Err.Check (List (I).Sequence.all.Length = 2, + Err.Check (Elt.Sequence.all.Length = 2, "splice-unquote expects 1 parameter"); - Tmp := Eval (List (I).Sequence.all (2), Env); + Tmp := Eval (Elt.Sequence.all.Data (2), Env); Err.Check (Tmp.Kind = Kind_List, "splice_unquote expects a list"); - for I in 1 .. Tmp.Sequence.all.Length loop - Vector.Append (Tmp.Sequence.all (I)); + for Sub_Elt of Tmp.Sequence.all.Data loop + Vector.Append (Sub_Elt); end loop; else - Vector.Append (Quasiquote (List (I), Env)); + Vector.Append (Quasiquote (Elt, Env)); end if; end loop; -- Now that we know the number of elements, convert to a list. - Sequence := Sequences.Constructor (Natural (Vector.Length)); - for I in 1 .. Natural (Vector.Length) loop - Sequence.Replace_Element (I, Vector (I)); - end loop; - return (Kind_List, Sequence); + declare + Sequence : constant Types.Sequence_Ptr + := Types.Sequences.Constructor (Natural (Vector.Length)); + begin + for I in 1 .. Natural (Vector.Length) loop + Sequence.all.Data (I) := Vector (I); + end loop; + return (Kind_List, Sequence); + end; end Quasiquote_List; begin -- Quasiquote case Ast.Kind is when Kind_Vector => -- When the test is updated, replace Kind_List with Kind_Vector. - return Quasiquote_List (Ast.Sequence.all); + return Quasiquote_List (Ast.Sequence.all.Data); when Kind_List => if 0 < Ast.Sequence.all.Length - and then Ast.Sequence.all (1) = (Kind_Symbol, - Symbols.Names.Unquote) + and then Ast.Sequence.all.Data (1).Kind = Kind_Symbol + and then Ast.Sequence.all.Data (1).Str.all = "unquote" then Err.Check (Ast.Sequence.all.Length = 2, "expected 1 parameter"); - return Eval (Ast.Sequence.all (2), Env); + return Eval (Ast.Sequence.all.Data (2), Env); else - return Quasiquote_List (Ast.Sequence.all); + return Quasiquote_List (Ast.Sequence.all.Data); end if; when others => return Ast; @@ -375,7 +410,7 @@ procedure StepA_Mal is raise; end Quasiquote; - function Read return Mal.T_Array + function Read return Types.T_Array is (Reader.Read_Str (Readline.Input ("user> "))); procedure Rep (Env : in Envs.Ptr) is @@ -408,33 +443,30 @@ procedure StepA_Mal is & " (if ~condvar ~condvar (or ~@(rest xs)))))))))" & "(def! *host-language* ""ada.2"")"; Repl : constant Envs.Ptr := Envs.New_Env; - function Eval_Builtin (Args : in Mal.T_Array) return Mal.T is + function Eval_Builtin (Args : in Types.T_Array) return Types.T is begin Err.Check (Args'Length = 1, "expected 1 parameter"); - return Eval_Cb.Cb.all (Args (Args'First), Repl); + return Eval (Args (Args'First), Repl); end Eval_Builtin; Script : constant Boolean := 0 < ACL.Argument_Count; - Argv : Mal.Sequence_Ptr; + Argv : constant Types.Sequence_Ptr + := Types.Sequences.Constructor (Integer'Max (0, ACL.Argument_Count - 1)); begin -- Show the Eval function to other packages. - Eval_Cb.Cb := Eval'Unrestricted_Access; + Types.Fns.Eval_Cb := Eval'Unrestricted_Access; -- Add Core functions into the top environment. Core.NS_Add_To_Repl (Repl); - Repl.all.Set (Symbols.Constructor ("eval"), + Repl.all.Set ((Kind_Symbol, Types.Strings.Alloc ("eval")), (Kind_Builtin, Eval_Builtin'Unrestricted_Access)); -- Native startup procedure. Exec (Startup, Repl); -- Define ARGV from command line arguments. - if Script then - Argv := Sequences.Constructor (ACL.Argument_Count - 1); - for I in 2 .. ACL.Argument_Count loop - Argv.all.Replace_Element - (I - 1, (Kind_String, ASU.To_Unbounded_String (ACL.Argument (I)))); - end loop; - else - Argv := Sequences.Constructor (0); - end if; - Repl.all.Set (Symbols.Constructor ("*ARGV*"), (Kind_List, Argv)); + for I in 2 .. ACL.Argument_Count loop + Argv.all.Data (I - 1) := (Kind_String, + Types.Strings.Alloc (ACL.Argument (I))); + end loop; + Repl.all.Set ((Kind_Symbol, Types.Strings.Alloc ("*ARGV*")), + (Kind_List, Argv)); -- Execute user commands. if Script then Exec ("(load-file """ & ACL.Argument (1) & """)", Repl); @@ -452,7 +484,7 @@ begin -- Other exceptions are really unexpected. -- Collect garbage. - Err.Data := Mal.Nil; + Err.Data := Types.Nil; Repl.all.Keep; Garbage_Collected.Clean; end loop; @@ -460,7 +492,8 @@ begin end if; -- 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; - Symbols.Check_Allocations; end StepA_Mal; diff --git a/ada.2/types-atoms.adb b/ada.2/types-atoms.adb index 473df198..766a1f18 100644 --- a/ada.2/types-atoms.adb +++ b/ada.2/types-atoms.adb @@ -1,54 +1,52 @@ with Err; - with Types.Builtins; with Types.Fns; package body Types.Atoms is - function Atom (Args : in Mal.T_Array) return Mal.T is - Ref : Mal.Atom_Ptr; + function Atom (Args : in T_Array) return T is begin Err.Check (Args'Length = 1, "expected 1 parameter"); - Ref := new Instance'(Garbage_Collected.Instance with - Data => Args (Args'First)); - Garbage_Collected.Register (Garbage_Collected.Pointer (Ref)); - return (Kind_Atom, Ref); + declare + Ref : constant Atom_Ptr := new Instance; + begin + Garbage_Collected.Register (Garbage_Collected.Pointer (Ref)); + Ref.all.Data := Args (Args'First); + return (Kind_Atom, Ref); + end; end Atom; - function Deref (Args : in Mal.T_Array) return Mal.T is + function Deref (Args : in T_Array) return T is begin - Err.Check (Args'Length = 1, "expected 1 parameter"); - Err.Check (Args (Args'First).Kind = Kind_Atom, "expected an atom"); + Err.Check (Args'Length = 1 and then Args (Args'First).Kind = Kind_Atom, + "expected an atom"); return Args (Args'First).Atom.all.Data; end Deref; - function Deref (Item : in Instance) return Mal.T + function Deref (Item : in Instance) return T is (Item.Data); procedure Keep_References (Object : in out Instance) is begin - Mal.Keep (Object.Data); + Keep (Object.Data); end Keep_References; - function Reset (Args : in Mal.T_Array) return Mal.T is + function Reset (Args : in T_Array) return T is begin - Err.Check (Args'Length = 2, "expected 2 parameters"); - Err.Check (Args (Args'First).Kind = Kind_Atom, - "parameter 1 must be an atom"); + Err.Check (Args'Length = 2 and then Args (Args'First).Kind = Kind_Atom, + "expected an atom then a value"); Args (Args'First).Atom.all.Data := Args (Args'Last); return Args (Args'Last); end Reset; - function Swap (Args : in Mal.T_Array) return Mal.T is + function Swap (Args : in T_Array) return T is begin - Err.Check (2 <= Args'Length, "expected at least 2 parameters"); - Err.Check (Args (Args'First).Kind = Kind_Atom, - "parameter 1 must be an atom"); + Err.Check (2 <= Args'Length and then Args (Args'First).Kind = Kind_Atom, + "expected an atom, optional arguments then a function"); declare - use type Mal.T_Array; - X : Mal.T renames Args (Args'First).Atom.all.Data; - F : Mal.T renames Args (Args'First + 1); - A : constant Mal.T_Array := X & Args (Args'First + 2 .. Args'Last); + X : T renames Args (Args'First).Atom.all.Data; + F : T renames Args (Args'First + 1); + A : constant T_Array := X & Args (Args'First + 2 .. Args'Last); begin case F.Kind is when Kind_Builtin => diff --git a/ada.2/types-atoms.ads b/ada.2/types-atoms.ads index 6d1381b3..8764ad44 100644 --- a/ada.2/types-atoms.ads +++ b/ada.2/types-atoms.ads @@ -1,24 +1,24 @@ with Garbage_Collected; -with Types.Mal; package Types.Atoms is - type Instance (<>) is new Garbage_Collected.Instance with private; + type Instance (<>) is abstract new Garbage_Collected.Instance with private; -- Built-in functions. - function Atom (Args : in Mal.T_Array) return Mal.T; - function Deref (Args : in Mal.T_Array) return Mal.T; - function Reset (Args : in Mal.T_Array) return Mal.T; - function Swap (Args : in Mal.T_Array) return Mal.T; + function Atom (Args : in T_Array) return T; + function Deref (Args : in T_Array) return T; + function Reset (Args : in T_Array) return T; + function Swap (Args : in T_Array) return T; -- Helper for print. - function Deref (Item : in Instance) return Mal.T with Inline; + function Deref (Item : in Instance) return T with Inline; private type Instance is new Garbage_Collected.Instance with record - Data : Mal.T; + Data : T; end record; + overriding procedure Keep_References (Object : in out Instance) with Inline; end Types.Atoms; diff --git a/ada.2/types-builtins.adb b/ada.2/types-builtins.adb index ad20d0f7..cd85b526 100644 --- a/ada.2/types-builtins.adb +++ b/ada.2/types-builtins.adb @@ -1,20 +1,21 @@ package body Types.Builtins is - function Builtin (Item : in Instance) return Mal.Builtin_Ptr + function Builtin (Item : in Instance) return Builtin_Ptr is (Item.F_Builtin); procedure Keep_References (Object : in out Instance) is begin - Mal.Keep (Object.F_Meta); + Keep (Object.F_Meta); end Keep_References; - function Meta (Item : in Instance) return Mal.T + function Meta (Item : in Instance) return T is (Item.F_Meta); - function With_Meta (Builtin : in Mal.Builtin_Ptr; - Metadata : in Mal.T) return Mal.T + function With_Meta (Builtin : in Builtin_Ptr; + Metadata : in T) return T is - Ref : constant Mal.Builtin_With_Meta_Ptr + -- Builtin is not null and requires an immediate initialization. + Ref : constant Builtin_With_Meta_Ptr := new Instance'(Garbage_Collected.Instance with F_Builtin => Builtin, F_Meta => Metadata); @@ -23,8 +24,8 @@ package body Types.Builtins is return (Kind_Builtin_With_Meta, Ref); end With_Meta; - function With_Meta (Item : in Instance; - Metadata : in Mal.T) return Mal.T - is (With_Meta (Item.Builtin, Metadata)); + function With_Meta (Builtin : in Instance; + Metadata : in T) return T + is (With_Meta (Builtin.F_Builtin, Metadata)); end Types.Builtins; diff --git a/ada.2/types-builtins.ads b/ada.2/types-builtins.ads index c180c996..3da6a1c8 100644 --- a/ada.2/types-builtins.ads +++ b/ada.2/types-builtins.ads @@ -1,28 +1,28 @@ with Garbage_Collected; -with Types.Mal; package Types.Builtins is -- Types.Mal.Builtin_Ptr is efficient and sufficient for most - -- purposes, as counting references is a waste of time for native - -- functions. The controlled type below is only useful when one - -- has the silly idea to add metadata to a built-in. + -- purposes, as native function need no deallocation. The type + -- below is only useful to add metadata to a built-in. - type Instance is new Garbage_Collected.Instance with private; + type Instance (<>) is abstract new Garbage_Collected.Instance with private; - function With_Meta (Builtin : in Mal.Builtin_Ptr; - Metadata : in Mal.T) return Mal.T with Inline; - function With_Meta (Item : in Instance; - Metadata : in Mal.T) return Mal.T with Inline; - function Meta (Item : in Instance) return Mal.T with Inline; - function Builtin (Item : in Instance) return Mal.Builtin_Ptr with Inline; + function With_Meta (Builtin : in Builtin_Ptr; + Metadata : in T) return T with Inline; + function With_Meta (Builtin : in Instance; + Metadata : in T) return T with Inline; + + function Meta (Item : in Instance) return T with Inline; + function Builtin (Item : in Instance) return Builtin_Ptr with Inline; private type Instance is new Garbage_Collected.Instance with record - F_Builtin : Mal.Builtin_Ptr; - F_Meta : Mal.T; + F_Builtin : Builtin_Ptr; + F_Meta : T; end record; + overriding procedure Keep_References (Object : in out Instance) with Inline; end Types.Builtins; diff --git a/ada.2/types-fns.adb b/ada.2/types-fns.adb index cbb96bbf..edffd320 100644 --- a/ada.2/types-fns.adb +++ b/ada.2/types-fns.adb @@ -1,20 +1,22 @@ with Err; -with Eval_Cb; +pragma Warnings (Off, "unit ""Types.Sequences"" is not referenced"); +with Types.Sequences; +pragma Warnings (On, "unit ""Types.Sequences"" is not referenced"); package body Types.Fns is - use type Envs.Ptr; - - ---------------------------------------------------------------------- - function Apply (Item : in Instance; - Args : in Mal.T_Array) return Mal.T - is (Eval_Cb.Cb.all (Ast => Item.F_Ast, - Env => Envs.New_Env (Outer => Item.F_Env, - Binds => Item.F_Params, - Exprs => Args))); + Args : in T_Array) return T + is + Env : constant Envs.Ptr := Envs.New_Env (Outer => Item.F_Env); + begin + Env.all.Set_Binds (Binds => Item.F_Params.all.Data, + Exprs => Args); + return Eval_Cb.all (Ast => Item.F_Ast, + Env => Env); + end Apply; - function Ast (Item : in Instance) return Mal.T + function Ast (Item : in Instance) return T is (Item.F_Ast); function Env (Item : in Instance) return Envs.Ptr @@ -22,65 +24,36 @@ package body Types.Fns is procedure Keep_References (Object : in out Instance) is begin - Mal.Keep (Object.F_Ast); - if Object.F_Env /= null then - Object.F_Env.all.Keep; - end if; - Mal.Keep (Object.F_Meta); + Keep (Object.F_Ast); + Object.F_Params.all.Keep; + Object.F_Env.all.Keep; + Keep (Object.F_Meta); end Keep_References; - function Meta (Item : in Instance) return Mal.T + function Meta (Item : in Instance) return T is (Item.F_Meta); - function New_Function (Params : in Sequences.Instance; - Ast : in Mal.T; - Env : in Envs.Ptr) - return Mal.T + function New_Function (Params : in Sequence_Ptr; + Ast : in T; + Env : in Envs.Ptr; + Metadata : in T := Nil) return T is - Ref : constant Mal.Fn_Ptr + -- Env and Params are not null and require an immediate + -- initialization. + Ref : constant Fn_Ptr := new Instance'(Garbage_Collected.Instance with - Last => Params.Length, - F_Ast => Ast, - F_Env => Env, - others => <>); + F_Ast => Ast, + F_Env => Env, + F_Meta => Metadata, + F_Params => Params); begin Garbage_Collected.Register (Garbage_Collected.Pointer (Ref)); - for I in Ref.all.F_Params'Range loop - Err.Check (Params (I).Kind = Kind_Symbol, - "formal parameters must be symbols"); - Ref.all.F_Params (I) := Params (I).Symbol; - end loop; + Err.Check ((for all P of Params.all.Data => P.Kind = Kind_Symbol), + "formal parameters must be symbols"); return (Kind_Fn, Ref); end New_Function; - function New_Macro (Item : in Instance) return Mal.T is - Ref : constant Mal.Fn_Ptr - := new Instance'(Garbage_Collected.Instance with - Last => Item.Last, - F_Params => Item.F_Params, - F_Ast => Item.F_Ast, - others => <>); - begin - Garbage_Collected.Register (Garbage_Collected.Pointer (Ref)); - return (Kind_Macro, Ref); - end New_Macro; - - function Params (Item : in Instance) return Symbols.Symbol_Array + function Params (Item : in Instance) return Sequence_Ptr is (Item.F_Params); - function With_Meta (Item : in Instance; - Metadata : in Mal.T) return Mal.T - is - Ref : constant Mal.Fn_Ptr - := new Instance'(Garbage_Collected.Instance with - Last => Item.Last, - F_Params => Item.F_Params, - F_Ast => Item.F_Ast, - F_Env => Item.F_Env, - F_Meta => Metadata); - begin - Garbage_Collected.Register (Garbage_Collected.Pointer (Ref)); - return (Kind_Fn, Ref); - end With_Meta; - end Types.Fns; diff --git a/ada.2/types-fns.ads b/ada.2/types-fns.ads index 3127520d..77f9b8a4 100644 --- a/ada.2/types-fns.ads +++ b/ada.2/types-fns.ads @@ -1,47 +1,44 @@ with Envs; with Garbage_Collected; -with Types.Mal; -with Types.Sequences; -with Types.Symbols; package Types.Fns is - type Instance (<>) is new Garbage_Collected.Instance with private; - -- A pointer to an user-defined function or macro. + Eval_Cb : access function (Ast : in T; + Env : in Envs.Ptr) return T; + -- The main program must register this global callback to the main + -- eval function before Apply is called. - function New_Function (Params : in Types.Sequences.Instance; - Ast : in Mal.T; - Env : in Envs.Ptr) return Mal.T + type Instance (<>) is abstract new Garbage_Collected.Instance with private; + + function New_Function (Params : in Sequence_Ptr; + Ast : in T; + Env : in Envs.Ptr; + Metadata : in T := Nil) return T with Inline; -- Raise an exception if Params contains something else than symbols. - function New_Macro (Item : in Instance) return Mal.T with Inline; - - function Params (Item : in Instance) return Symbols.Symbol_Array + function Params (Item : in Instance) return Sequence_Ptr with Inline; - function Ast (Item : in Instance) return Mal.T with Inline; + function Ast (Item : in Instance) return T with Inline; -- Useful to print. function Apply (Item : in Instance; - Args : in Mal.T_Array) return Mal.T with Inline; - -- Returns null for macros. + Args : in T_Array) return T with Inline; + -- Duplicated in the step files because of TCO. function Env (Item : in Instance) return Envs.Ptr with Inline; - -- Returns null for macros. -- Required for TCO, instead of Apply. - function Meta (Item : in Instance) return Mal.T with Inline; - function With_Meta (Item : in Instance; - Metadata : in Mal.T) return Mal.T with Inline; + function Meta (Item : in Instance) return T with Inline; private - type Instance (Last : Natural) is new Garbage_Collected.Instance + type Instance is new Garbage_Collected.Instance with record - F_Ast : Mal.T; + F_Ast : T; F_Env : Envs.Ptr; - F_Meta : Mal.T; - F_Params : Symbols.Symbol_Array (1 .. Last); + F_Meta : T; + F_Params : Sequence_Ptr; end record; overriding procedure Keep_References (Object : in out Instance) with Inline; diff --git a/ada.2/types-macros.adb b/ada.2/types-macros.adb new file mode 100644 index 00000000..6e3ddd08 --- /dev/null +++ b/ada.2/types-macros.adb @@ -0,0 +1,28 @@ +pragma Warnings (Off, "unit ""Types.Sequences"" is not referenced"); +with Types.Sequences; +pragma Warnings (On, "unit ""Types.Sequences"" is not referenced"); + +package body Types.Macros is + + function Ast (Item : in Instance) return T + is (Item.F_Ast); + + procedure Keep_References (Object : in out Instance) is + begin + Keep (Object.F_Ast); + Object.F_Params.all.Keep; + end Keep_References; + + function New_Macro (Func : in Fns.Instance) return T is + -- Params is not null and requires an immediate initialization. + Ref : constant Macro_Ptr := new Instance' + (Garbage_Collected.Instance with Func.Ast, Func.Params); + begin + Garbage_Collected.Register (Garbage_Collected.Pointer (Ref)); + return (Kind_Macro, Ref); + end New_Macro; + + function Params (Item : in Instance) return Sequence_Ptr + is (Item.F_Params); + +end Types.Macros; diff --git a/ada.2/types-macros.ads b/ada.2/types-macros.ads new file mode 100644 index 00000000..be3a3763 --- /dev/null +++ b/ada.2/types-macros.ads @@ -0,0 +1,22 @@ +with Garbage_Collected; +with Types.Fns; + +package Types.Macros is + + type Instance (<>) is abstract new Garbage_Collected.Instance with private; + + function New_Macro (Func : in Fns.Instance) return T with Inline; + + function Ast (Item : in Instance) return T with Inline; + function Params (Item : in Instance) return Sequence_Ptr with Inline; + +private + + type Instance is new Garbage_Collected.Instance with record + F_Ast : T; + F_Params : Sequence_Ptr; + end record; + + overriding procedure Keep_References (Object : in out Instance) with Inline; + +end Types.Macros; diff --git a/ada.2/types-mal.ads b/ada.2/types-mal.ads deleted file mode 100644 index 425d9312..00000000 --- a/ada.2/types-mal.ads +++ /dev/null @@ -1,96 +0,0 @@ -with Ada.Strings.Unbounded; - -limited with Types.Atoms; -limited with Types.Builtins; -limited with Types.Fns; -limited with Types.Maps; -limited with Types.Sequences; -with Types.Symbols; - -package Types.Mal is - - -- A type with a default value for the discriminant is the Ada - -- equivalent of a C union. It uses a fixed size, and allows - -- efficient arrays. A class hierarchy would make this impossible, - -- for little gain. - -- Native types may seem to consume too much memory, but - -- 1/ they require no allocation/deallocation. - -- 2/ the overhead would actually be higher with an intermediate - -- reference (the size of the pointer plus the size of the native - -- type, while an union uses the minimum of both and a single - -- memory area ). - -- Each instance has the size required for the largest possible - -- value, so subtypes should attempt to reduce their size when - -- possible (see Types.Symbols for such a compromise). - - -- The idea is inspired from the Haskell and OCaml interpreters, - -- which use a bit to distinguish pointers from integers. Ada - -- allows to specify the bit position of each component, but - -- generating such architecture-dependent definitions seems a lot - -- of work for MAL. - - -- The Ada tradition is to give explicit names to types, but this - -- one will be used very often, and almost each package declares - -- an "use Types;" clause, so Mal.T will do. - - -- The only problem with a hidden discriminant is that "in out" - -- parameters cannot be reaffected with a different discriminant. - -- Eval would be more efficient with "in out" parameters than with - -- "in" parameters and a result, because lots of reference - -- counting would be spared, and the implementation would be able - -- to reuse dynamic memory more efficiently. Environments, and - -- some list/map operations already attempt such reuse behind the - -- curtain. - - -- This would obfuscate the implementation of a functional - -- language, and require deep changes (the discriminant can be - -- changed for an in out or access parameter). - - type T; - type T_Array; - - type Atom_Ptr is access Atoms.Instance; - type Builtin_Ptr is access function (Args : in T_Array) return T; - type Builtin_With_Meta_Ptr is access Builtins.Instance; - type Fn_Ptr is access Fns.Instance; - type Map_Ptr is access Maps.Instance; - type Sequence_Ptr is access Sequences.Instance; - - type T (Kind : Kind_Type := Kind_Nil) is record - case Kind is - when Kind_Nil => - null; - when Kind_Boolean => - Ada_Boolean : Boolean; - when Kind_Number => - Number : Integer; - when Kind_Atom => - Atom : Atom_Ptr; - when Kind_Key => - S : Ada.Strings.Unbounded.Unbounded_String; - when Kind_Symbol => - Symbol : Symbols.Ptr; - when Kind_Sequence => - Sequence : Sequence_Ptr; - when Kind_Map => - Map : Map_Ptr; - when Kind_Builtin => - Builtin : Builtin_Ptr; - when Kind_Builtin_With_Meta => - Builtin_With_Meta : Builtin_With_Meta_Ptr; - when Kind_Fn | Kind_Macro => - Fn : Fn_Ptr; - end case; - end record; - - -- Useful for recursive automatic definition of equality for - -- composite types like the array type below. - function "=" (Left, Right : in T) return Boolean with Inline; - - Nil : constant T := (Kind => Kind_Nil); - - procedure Keep (Object : in Mal.T) with Inline; - - type T_Array is array (Positive range <>) of T; - -end Types.Mal; diff --git a/ada.2/types-maps.adb b/ada.2/types-maps.adb index 9f864398..1b9f9398 100644 --- a/ada.2/types-maps.adb +++ b/ada.2/types-maps.adb @@ -1,184 +1,198 @@ -with Ada.Strings.Unbounded.Hash; - with Err; with Types.Sequences; +with Types.Strings; package body Types.Maps is - function Constructor return Mal.Map_Ptr with Inline; + use type HM.Map; + + function Assoc (Initial : in HM.Map; + Bind : in T_Array) return T; + + function Constructor return Map_Ptr with Inline; ---------------------------------------------------------------------- function "=" (Left, Right : in Instance) return Boolean is (Left.Data = Right.Data); - function Assoc (Args : in Mal.T_Array) return Mal.T is - Ref : constant Mal.Map_Ptr := Constructor; + function Assoc (Initial : in HM.Map; + Bind : in T_Array) return T + is begin - Err.Check (Args'Length mod 2 = 1, "expected an odd parameter count"); - Err.Check (Args (Args'First).Kind = Kind_Map, - "parameter 1 must be a map"); - Ref.all.Data := Args (Args'First).Map.all.Data; - for I in 1 .. Args'Length / 2 loop - Ref.all.Data.Include (Key => Args (Args'First + 2 * I - 1), - New_Item => Args (Args'First + 2 * I)); - -- This call checks the kind of the key. - end loop; - return (Kind_Map, Ref); + Err.Check (Bind'Length mod 2 = 0, "expected an even bind count"); + declare + Len : constant Natural := Bind'Length / 2; + Ref : constant Map_Ptr := Constructor; + begin + Ref.all.Data := Initial; + for I in 0 .. Len - 1 loop + Ref.all.Data.Include (Bind (Bind'First + 2 * I), + Bind (Bind'First + 2 * I + 1)); + end loop; + return (Kind_Map, Ref); + end; end Assoc; - function Contains (Args : in Mal.T_Array) return Mal.T is + function Assoc (Args : in T_Array) return T is begin - Err.Check (Args'Length = 2, "expected 2 parameters"); - Err.Check (Args (Args'First).Kind = Kind_Map, - "parameter 1 must be a map"); - return (Kind_Boolean, - Args (Args'First).Map.all.Data.Contains (Args (Args'Last))); - end Contains; + Err.Check (0 < Args'Length and then Args (Args'First).Kind = Kind_Map, + "first parameter must be a map"); + return Assoc (Args (Args'First).Map.all.Data, + Args (Args'First + 1 .. Args'Last)); + end Assoc; - function Constructor return Mal.Map_Ptr is - Ref : constant Mal.Map_Ptr := new Instance; + function Constructor return Map_Ptr is + Ref : constant Map_Ptr := new Instance; begin Garbage_Collected.Register (Garbage_Collected.Pointer (Ref)); return Ref; end Constructor; - function Dissoc (Args : in Mal.T_Array) return Mal.T is - Ref : constant Mal.Map_Ptr := Constructor; + function Contains (Args : in T_Array) return T is begin - Err.Check (0 < Args'Length, "expected at least 1 parameter"); - Err.Check (Args (Args'First).Kind = Kind_Map, - "parameter 1 must be a map"); - Ref.all.Data := Args (Args'First).Map.all.Data; - for I in Args'First + 1 .. Args'Last loop - Ref.all.Data.Exclude (Args (I)); - -- This call checks the kind of the key. - end loop; - return (Kind_Map, Ref); + Err.Check (Args'Length = 2 and then Args (Args'First).Kind = Kind_Map, + "expected a map then a key"); + return (Kind_Boolean, + Args (Args'First).Map.all.Data.Contains (Args (Args'Last))); + end Contains; + + function Dissoc (Args : in T_Array) return T is + begin + Err.Check (0 < Args'Length and then Args (Args'First).Kind = Kind_Map, + "expected a map then keys"); + declare + Ref : constant Map_Ptr := Constructor; + begin + Ref.all.Data := Args (Args'First).Map.all.Data; + for I in Args'First + 1 .. Args'Last loop + Ref.all.Data.Exclude (Args (I)); + -- This call checks the kind of the key. + end loop; + return (Kind_Map, Ref); + end; end Dissoc; - function Generic_Eval (Container : in Instance; - Env : in Env_Type) return Mal.T - is - -- Copy the whole hash in order to avoid recomputing the hash - -- for each key, even if it implies unneeded calls to adjust - -- and finalize for Mal_Type values. - Ref : constant Mal.Map_Ptr := Constructor; - begin - Ref.Data := Container.Data; - for Position in Ref.all.Data.Iterate loop - Ref.all.Data.Replace_Element (Position, - Eval (HM.Element (Position), Env)); - -- This call may raise exceptions. - end loop; - return Mal.T'(Kind_Map, Ref); - end Generic_Eval; + function Element (Position : in Cursor) return T + is (HM.Element (HM.Cursor (Position))); - function Get (Args : in Mal.T_Array) return Mal.T is - Position : HM.Cursor; + function First (Container : in Instance) return Cursor + is (Cursor (Container.Data.First)); + + function Get (Args : in T_Array) return T is begin Err.Check (Args'Length = 2, "expected 2 parameters"); case Args (Args'First).Kind is when Kind_Nil => Err.Check (Args (Args'Last).Kind in Kind_Key, "key must be a keyword or string"); - return Mal.Nil; + return Nil; when Kind_Map => - Position := Args (Args'First).Map.all.Data.Find (Args (Args'Last)); - -- This call checks the kind of the key. - if HM.Has_Element (Position) then - return HM.Element (Position); - else - return Mal.Nil; - end if; + declare + Position : constant HM.Cursor + := Args (Args'First).Map.all.Data.Find (Args (Args'Last)); + begin + if HM.Has_Element (Position) then + return HM.Element (Position); + else + return Nil; + end if; + end; when others => Err.Raise_With ("parameter 1 must be nil or a map"); end case; end Get; - function Hash (Item : in Mal.T) return Ada.Containers.Hash_Type is + function Has_Element (Position : in Cursor) return Boolean + is (HM.Has_Element (HM.Cursor (Position))); + + function Hash (Item : in T) return Ada.Containers.Hash_Type is begin Err.Check (Item.Kind in Kind_Key, "keys must be keywords or strings"); - return (Ada.Strings.Unbounded.Hash (Item.S)); + return Strings.Hash (Item.Str); end Hash; - function Hash_Map (Args : in Mal.T_Array) return Mal.T is - Binds : constant Natural := Args'Length / 2; - Ref : Mal.Map_Ptr; - begin - Err.Check (Args'Length mod 2 = 0, "expected an even parameter count"); - Ref := Constructor; - Ref.all.Data.Reserve_Capacity (Ada.Containers.Count_Type (Binds)); - for I in 0 .. Binds - 1 loop - Ref.all.Data.Include (Key => Args (Args'First + 2 * I), - New_Item => Args (Args'First + 2 * I + 1)); - -- This call checks the kind of the key. - end loop; - return (Kind_Map, Ref); - end Hash_Map; - - procedure Iterate (Container : in Instance) is - begin - for Position in Container.Data.Iterate loop - Process (HM.Key (Position), HM.Element (Position)); - end loop; - end Iterate; + function Hash_Map (Args : in T_Array) return T + is (Assoc (HM.Empty_Map, Args)); procedure Keep_References (Object : in out Instance) is begin for Position in Object.Data.Iterate loop - Mal.Keep (HM.Key (Position)); - Mal.Keep (HM.Element (Position)); + Keep (HM.Key (Position)); + Keep (HM.Element (Position)); end loop; - Mal.Keep (Object.F_Meta); + Keep (Object.F_Meta); end Keep_References; - function Keys (Args : in Mal.T_Array) return Mal.T is - A1 : Mal.Map_Ptr; - R : Mal.Sequence_Ptr; - I : Positive := 1; + function Key (Position : in Cursor) return T + is (HM.Key (HM.Cursor (Position))); + + function Keys (Args : in T_Array) return T is begin - Err.Check (Args'Length = 1, "expected 1 parameter"); - Err.Check (Args (Args'First).Kind = Kind_Map, - "parameter 1 must be a map"); - A1 := Args (Args'First).Map; - R := Sequences.Constructor (A1.all.Length); - for Position in A1.all.Data.Iterate loop - R.all.Replace_Element (I, HM.Key (Position)); - I := I + 1; - end loop; - return (Kind_List, R); + Err.Check (Args'Length = 1 and then Args (Args'First).Kind = Kind_Map, + "expected a map"); + declare + A1 : HM.Map renames Args (Args'First).Map.all.Data; + Ref : constant Sequence_Ptr + := Sequences.Constructor (Natural (A1.Length)); + I : Positive := 1; + begin + for Position in A1.Iterate loop + Ref.all.Data (I) := HM.Key (Position); + I := I + 1; + end loop; + return (Kind_List, Ref); + end; end Keys; - function Length (Container : in Instance) return Natural - is (Natural (Container.Data.Length)); - - function Meta (Container : in Instance) return Mal.T + function Meta (Container : in Instance) return T is (Container.F_Meta); - function Vals (Args : in Mal.T_Array) return Mal.T is - A1 : Mal.Map_Ptr; - R : Mal.Sequence_Ptr; - I : Positive := 1; + procedure Next (Position : in out Cursor) is begin - Err.Check (Args'Length = 1, "expected 1 parameter"); - Err.Check (Args (Args'First).Kind = Kind_Map, - "parameter 1 must be a map"); - A1 := Args (Args'First).Map; - R := Sequences.Constructor (A1.all.Length); - for Element of A1.all.Data loop - R.all.Replace_Element (I, Element); - I := I + 1; - end loop; - return (Kind_List, R); + HM.Next (HM.Cursor (Position)); + end Next; + + function New_Map (Source : in Instance) return T + is + Ref : constant Map_Ptr := Constructor; + begin + Ref.all.Data := Source.Data; + return (Kind_Map, Ref); + end New_Map; + + procedure Replace_Element (Container : in out Instance; + Position : in Cursor; + New_Item : in T) + is + begin + Container.Data.Replace_Element (HM.Cursor (Position), New_Item); + end Replace_Element; + + function Vals (Args : in T_Array) return T is + begin + Err.Check (Args'Length = 1 and then Args (Args'First).Kind = Kind_Map, + "expected a map"); + declare + A1 : HM.Map renames Args (Args'First).Map.all.Data; + R : constant Sequence_Ptr + := Sequences.Constructor (Natural (A1.Length)); + I : Positive := 1; + begin + for Element of A1 loop + R.all.Data (I) := Element; + I := I + 1; + end loop; + return (Kind_List, R); + end; end Vals; - function With_Meta (Data : in Instance; - Metadata : in Mal.T) return Mal.T + function With_Meta (Container : in Instance; + Metadata : in T) return T is - Ref : constant Mal.Map_Ptr := Constructor; + Ref : constant Map_Ptr := Constructor; begin - Ref.all.Data := Data.Data; + Ref.all.Data := Container.Data; Ref.all.F_Meta := Metadata; return (Kind_Map, Ref); end With_Meta; diff --git a/ada.2/types-maps.ads b/ada.2/types-maps.ads index bd472dbe..09f481c2 100644 --- a/ada.2/types-maps.ads +++ b/ada.2/types-maps.ads @@ -1,67 +1,62 @@ private with Ada.Containers.Hashed_Maps; with Garbage_Collected; -with Types.Mal; package Types.Maps is - type Instance (<>) is new Garbage_Collected.Instance with private; + -- All function receiving a key check that its kind is keyword or + -- string. + + type Instance (<>) is abstract new Garbage_Collected.Instance with private; -- Built-in functions. - function Assoc (Args : in Mal.T_Array) return Mal.T; - function Contains (Args : in Mal.T_Array) return Mal.T; - function Dissoc (Args : in Mal.T_Array) return Mal.T; - function Get (Args : in Mal.T_Array) return Mal.T; - function Hash_Map (Args : in Mal.T_Array) return Mal.T; - function Keys (Args : in Mal.T_Array) return Mal.T; - function Vals (Args : in Mal.T_Array) return Mal.T; + function Assoc (Args : in T_Array) return T; + function Contains (Args : in T_Array) return T; + function Dissoc (Args : in T_Array) return T; + function Get (Args : in T_Array) return T; + function Hash_Map (Args : in T_Array) return T; + function Keys (Args : in T_Array) return T; + function Vals (Args : in T_Array) return T; function "=" (Left, Right : in Instance) return Boolean with Inline; - -- A generic is better than an access to function because of - -- https://gcc.gnu.org/bugzilla/show_bug.cgi?id=89159 + -- Used to print each element of a map. + type Cursor (<>) is limited private; + function Has_Element (Position : in Cursor) return Boolean with Inline; + function Key (Position : in Cursor) return T with Inline; + function Element (Position : in Cursor) return T with Inline; + function First (Container : in Instance) return Cursor with Inline; + procedure Next (Position : in out Cursor) with Inline; -- Used to evaluate each element of a map. + function New_Map (Source : in Instance) return T with Inline; + procedure Replace_Element (Container : in out Instance; + Position : in Cursor; + New_Item : in T) with Inline; - generic - type Env_Type (<>) is limited private; - with function Eval (Ast : in Mal.T; - Env : in Env_Type) - return Mal.T; - function Generic_Eval (Container : in Instance; - Env : in Env_Type) - return Mal.T; - - -- Used to print a map. - generic - with procedure Process (Key : in Mal.T; - Element : in Mal.T); - procedure Iterate (Container : in Instance); - - function Length (Container : in Instance) return Natural with Inline; - - function Meta (Container : in Instance) return Mal.T with Inline; - function With_Meta (Data : in Instance; - Metadata : in Mal.T) - return Mal.T; + function Meta (Container : in Instance) return T with Inline; + function With_Meta (Container : in Instance; + Metadata : in T) return T with Inline; private - function Hash (Item : in Mal.T) return Ada.Containers.Hash_Type with Inline; + function Hash (Item : in T) return Ada.Containers.Hash_Type with Inline; -- This function also checks the kind of the key, and raise an -- error in case of problem. - package HM is new Ada.Containers.Hashed_Maps (Key_Type => Mal.T, - Element_Type => Mal.T, + package HM is new Ada.Containers.Hashed_Maps (Key_Type => T, + Element_Type => T, Hash => Hash, - Equivalent_Keys => Mal."=", - "=" => Mal."="); - use type HM.Map; + Equivalent_Keys => "=", + "=" => "="); type Instance is new Garbage_Collected.Instance with record Data : HM.Map; - F_Meta : Mal.T; + F_Meta : T; end record; + overriding procedure Keep_References (Object : in out Instance) with Inline; + type Cursor is new HM.Cursor; + end Types.Maps; diff --git a/ada.2/types-sequences.adb b/ada.2/types-sequences.adb index 8d7098ec..8169d7f1 100644 --- a/ada.2/types-sequences.adb +++ b/ada.2/types-sequences.adb @@ -1,56 +1,50 @@ with Err; -with Types.Builtins; with Types.Fns; +with Types.Builtins; package body Types.Sequences is - use type Mal.T_Array; - - ---------------------------------------------------------------------- - function "=" (Left, Right : in Instance) return Boolean is - -- Should become Left.Ref.all.Data = Right.Ref.all.Data when + -- Should become Left.all.Data = Right.all.Data when -- https://gcc.gnu.org/bugzilla/show_bug.cgi?id=89178 is fixed. - use type Mal.T; begin - return Left.Last = Right.Last + return Left.Length = Right.Length and then - (for all I in 1 .. Left.Last => Left.Data (I) = Right.Data (I)); + (for all I in 1 .. Left.Data'Length => Left.Data (I) = Right.Data (I)); end "="; - function "&" (Left : in Mal.T_Array; - Right : in Instance) return Mal.T_Array - is (Left & Right.Data); - - function Concat (Args : in Mal.T_Array) return Mal.T is + function Concat (Args : in T_Array) return T is Sum : Natural := 0; First : Positive := 1; Last : Natural; - Ref : Mal.Sequence_Ptr; begin + Err.Check ((for all A of Args => A.Kind in Kind_Sequence), + "expected sequences"); for Arg of Args loop - Err.Check (Arg.Kind in Kind_Sequence, "expected sequences"); Sum := Sum + Arg.Sequence.all.Data'Length; end loop; - Ref := Constructor (Sum); - for Arg of Args loop - Last := First - 1 + Arg.Sequence.all.Data'Length; - Ref.all.Data (First .. Last) := Arg.Sequence.all.Data; - First := Last + 1; - end loop; - return (Kind_List, Ref); + declare + Ref : constant Sequence_Ptr := Constructor (Sum); + begin + for Arg of Args loop + Last := First - 1 + Arg.Sequence.all.Data'Last; + Ref.all.Data (First .. Last) := Arg.Sequence.all.Data; + First := Last + 1; + end loop; + return (Kind_List, Ref); + end; end Concat; - function Conj (Args : in Mal.T_Array) return Mal.T is + function Conj (Args : in T_Array) return T is begin Err.Check (0 < Args'Length, "expected at least 1 parameter"); case Args (Args'First).Kind is when Kind_Sequence => declare - Data : Mal.T_Array renames Args (Args'First).Sequence.all.Data; + Data : T_Array renames Args (Args'First).Sequence.all.Data; Last : constant Natural := Args'Length - 1 + Data'Length; -- Avoid exceptions until Ref is controlled. - Ref : constant Mal.Sequence_Ptr := Constructor (Last); + Ref : constant Sequence_Ptr := Constructor (Last); begin if Args (Args'First).Kind = Kind_List then for I in 1 .. Args'Length - 1 loop @@ -68,29 +62,29 @@ package body Types.Sequences is end case; end Conj; - function Cons (Args : in Mal.T_Array) return Mal.T is + function Cons (Args : in T_Array) return T is begin - Err.Check (Args'Length = 2, "expected 2 parameters"); - Err.Check (Args (Args'Last).Kind in Kind_Sequence, - "parameter 2 must be a sequence"); + Err.Check (Args'Length = 2 + and then Args (Args'Last).Kind in Kind_Sequence, + "expected a value then a sequence"); declare - Head : Mal.T renames Args (Args'First); - Tail : Mal.T_Array renames Args (Args'Last).Sequence.all.Data; - Ref : constant Mal.Sequence_Ptr := Constructor (1 + Tail'Length); + Head : T renames Args (Args'First); + Tail : T_Array renames Args (Args'Last).Sequence.all.Data; + Ref : constant Sequence_Ptr := Constructor (1 + Tail'Length); begin Ref.all.Data := Head & Tail; return (Kind_List, Ref); end; end Cons; - function Constructor (Length : in Natural) return Mal.Sequence_Ptr is - Ref : constant Mal.Sequence_Ptr := new Instance (Length); + function Constructor (Length : in Natural) return Sequence_Ptr is + Ref : constant Sequence_Ptr := new Instance (Length); begin Garbage_Collected.Register (Garbage_Collected.Pointer (Ref)); return Ref; end Constructor; - function Count (Args : in Mal.T_Array) return Mal.T is + function Count (Args : in T_Array) return T is begin Err.Check (Args'Length = 1, "expected 1 parameter"); case Args (Args'First).Kind is @@ -103,22 +97,18 @@ package body Types.Sequences is end case; end Count; - function Element (Container : in Instance; - Index : in Positive) return Mal.T - is (Container.Data (Index)); - - function First (Args : in Mal.T_Array) return Mal.T is + function First (Args : in T_Array) return T is begin Err.Check (Args'Length = 1, "expected 1 parameter"); case Args (Args'First).Kind is when Kind_Nil => - return Mal.Nil; + return Nil; when Kind_Sequence => declare - Data : Mal.T_Array renames Args (Args'First).Sequence.all.Data; + Data : T_Array renames Args (Args'First).Sequence.all.Data; begin if Data'Length = 0 then - return Mal.Nil; + return Nil; else return Data (Data'First); end if; @@ -128,41 +118,39 @@ package body Types.Sequences is end case; end First; - function Is_Empty (Args : in Mal.T_Array) return Mal.T is + function Is_Empty (Args : in T_Array) return T is begin - Err.Check (Args'Length = 1, "expected 1 parameter"); - Err.Check (Args (Args'First).Kind in Kind_Sequence, - "parameter must be a sequence"); + Err.Check (Args'Length = 1 + and then Args (Args'First).Kind in Kind_Sequence, + "expected a sequence"); return (Kind_Boolean, Args (Args'First).Sequence.all.Data'Length = 0); end Is_Empty; procedure Keep_References (Object : in out Instance) is begin - Mal.Keep (Object.F_Meta); + Keep (Object.Meta); for M of Object.Data loop - Mal.Keep (M); + Keep (M); end loop; end Keep_References; - function Length (Source : in Instance) return Natural - is (Source.Data'Length); - - function List (Args : in Mal.T_Array) return Mal.T is - Ref : constant Mal.Sequence_Ptr := Constructor (Args'Length); + function List (Args : in T_Array) return T + is + Ref : constant Sequence_Ptr := Constructor (Args'Length); begin Ref.all.Data := Args; return (Kind_List, Ref); end List; - function Map (Args : in Mal.T_Array) return Mal.T is + function Map (Args : in T_Array) return T is begin - Err.Check (Args'Length = 2, "expected 2 parameters"); - Err.Check (Args (Args'Last).Kind in Kind_Sequence, - "parameter 2 must be a sequence"); + Err.Check (Args'Length = 2 + and then Args (Args'Last).Kind in Kind_Sequence, + "expected a function then a sequence"); declare - F : Mal.T renames Args (Args'First); - Src : Mal.T_Array renames Args (Args'Last).Sequence.all.Data; - Ref : constant Mal.Sequence_Ptr := Constructor (Src'Length); + F : T renames Args (Args'First); + Src : T_Array renames Args (Args'Last).Sequence.all.Data; + Ref : constant Sequence_Ptr := Constructor (Src'Length); begin case F.Kind is when Kind_Builtin => @@ -185,18 +173,14 @@ package body Types.Sequences is end; end Map; - function Meta (Item : in Instance) return Mal.T - is (Item.F_Meta); - - function Nth (Args : in Mal.T_Array) return Mal.T is + function Nth (Args : in T_Array) return T is begin - Err.Check (Args'Length = 2, "expected 2 parameters"); - Err.Check (Args (Args'First).Kind in Kind_Sequence, - "paramater 1 must be a sequence"); - Err.Check (Args (Args'Last).Kind = Kind_Number, - "parameter 2 must be a number"); + Err.Check (Args'Length = 2 + and then Args (Args'First).Kind in Kind_Sequence + and then Args (Args'Last).Kind = Kind_Number, + "expected a sequence then a number"); declare - L : Mal.T_Array renames Args (Args'First).Sequence.all.Data; + L : T_Array renames Args (Args'First).Sequence.all.Data; I : constant Integer := Args (Args'Last).Number + 1; begin Err.Check (I in L'Range, "index out of bounds"); @@ -204,62 +188,32 @@ package body Types.Sequences is end; end Nth; - procedure Replace_Element (Container : in out Instance; - Index : in Positive; - New_Item : in Mal.T) - is - begin - Container.Data (Index) := New_Item; - end Replace_Element; - - function Rest (Args : in Mal.T_Array) return Mal.T is + function Rest (Args : in T_Array) return T is begin Err.Check (Args'Length = 1, "expected 1 parameter"); - declare - A1 : Mal.T renames Args (Args'First); - Ref : Mal.Sequence_Ptr; - begin - -- Avoid exceptions until Ref is controlled. - case A1.Kind is - when Kind_Nil => - Ref := Constructor (0); - when Kind_Sequence => - if A1.Sequence.all.Last = 0 then - Ref := Constructor (0); - else - Ref := Constructor (A1.Sequence.all.Last - 1); - Ref.all.Data - := A1.Sequence.all.Data (2 .. A1.Sequence.all.Data'Last); - end if; - when others => - Err.Raise_With ("parameter must be nil or a sequence"); - end case; - return (Kind_List, Ref); - end; + case Args (Args'First).Kind is + when Kind_Nil => + return (Kind_List, Constructor (0)); + when Kind_Sequence => + declare + A1 : T_Array renames Args (Args'First).Sequence.all.Data; + Ref : constant Sequence_Ptr + := Constructor (Integer'Max (0, A1'Length - 1)); + begin + Ref.all.Data := A1 (A1'First + 1 .. A1'Last); + return (Kind_List, Ref); + end; + when others => + Err.Raise_With ("parameter must be nil or a sequence"); + end case; end Rest; - function Tail (Source : in Instance; - Count : in Natural) return Mal.T_Array is - Data : Mal.T_Array renames Source.Data; - begin - return Data (Data'Last - Count + 1 .. Data'Last); - end Tail; - - function Vector (Args : in Mal.T_Array) return Mal.T is - Ref : constant Mal.Sequence_Ptr := Constructor (Args'Length); + function Vector (Args : in T_Array) return T + is + Ref : constant Sequence_Ptr := Constructor (Args'Length); begin Ref.all.Data := Args; return (Kind_Vector, Ref); end Vector; - function With_Meta (Data : in Instance; - Metadata : in Mal.T) return Mal.Sequence_Ptr - is - Ref : constant Mal.Sequence_Ptr := Constructor (Data.Last); - begin - Ref.all.Data := Data.Data; - Ref.all.F_Meta := Metadata; - return Ref; - end With_Meta; - end Types.Sequences; diff --git a/ada.2/types-sequences.ads b/ada.2/types-sequences.ads index 85bddb18..1b4664d5 100644 --- a/ada.2/types-sequences.ads +++ b/ada.2/types-sequences.ads @@ -1,59 +1,39 @@ with Garbage_Collected; -with Types.Mal; package Types.Sequences is - type Instance (<>) is new Garbage_Collected.Instance with private - with Constant_Indexing => Element; + -- Hiding the implementation would either cause a significative + -- performance hit (the compiler performs better optimization with + -- explicit arrays) or a convoluted interface (demonstrated for + -- strings and maps, where the balance is different). + + type Instance (Length : Natural) is new Garbage_Collected.Instance with + record + Meta : T; + Data : T_Array (1 .. Length); + end record; -- Built-in functions. - function Concat (Args : in Mal.T_Array) return Mal.T; - function Conj (Args : in Mal.T_Array) return Mal.T; - function Cons (Args : in Mal.T_Array) return Mal.T; - function Count (Args : in Mal.T_Array) return Mal.T; - function First (Args : in Mal.T_Array) return Mal.T; - function Is_Empty (Args : in Mal.T_Array) return Mal.T; - function List (Args : in Mal.T_Array) return Mal.T; - function Map (Args : in Mal.T_Array) return Mal.T; - function Nth (Args : in Mal.T_Array) return Mal.T; - function Rest (Args : in Mal.T_Array) return Mal.T; - function Vector (Args : in Mal.T_Array) return Mal.T; + function Concat (Args : in T_Array) return T; + function Conj (Args : in T_Array) return T; + function Cons (Args : in T_Array) return T; + function Count (Args : in T_Array) return T; + function First (Args : in T_Array) return T; + function Is_Empty (Args : in T_Array) return T; + function List (Args : in T_Array) return T; + function Map (Args : in T_Array) return T; + function Nth (Args : in T_Array) return T; + function Rest (Args : in T_Array) return T; + function Vector (Args : in T_Array) return T; - function "=" (Left, Right : in Instance) return Boolean with Inline; + -- New instances must be created via this constructor. + function Constructor (Length : in Natural) return Sequence_Ptr with Inline; - function Length (Source : in Instance) return Natural with Inline; - - function Element (Container : in Instance; - Index : in Positive) return Mal.T - with Inline, Pre => Index <= Length (Container); - - function "&" (Left : in Mal.T_Array; - Right : in Instance) return Mal.T_Array with Inline; - -- Used to implement Core.Apply. - - function Constructor (Length : in Natural) return Mal.Sequence_Ptr - with Inline; - procedure Replace_Element (Container : in out Instance; - Index : in Positive; - New_Item : in Mal.T) - with Inline, Pre => Index <= Length (Container); - - -- Used in macro implementation. - function Tail (Source : in Instance; - Count : in Natural) return Mal.T_Array - with Inline, Pre => Count <= Length (Source); - - function Meta (Item : in Instance) return Mal.T with Inline; - function With_Meta (Data : in Instance; - Metadata : in Mal.T) - return Mal.Sequence_Ptr; + -- Helper for Types."=". + function "=" (Left, Right : in Instance) return Boolean; private - type Instance (Last : Natural) is new Garbage_Collected.Instance with record - F_Meta : Mal.T; - Data : Mal.T_Array (1 .. Last); - end record; overriding procedure Keep_References (Object : in out Instance) with Inline; end Types.Sequences; diff --git a/ada.2/types-strings.adb b/ada.2/types-strings.adb new file mode 100644 index 00000000..a51f01fd --- /dev/null +++ b/ada.2/types-strings.adb @@ -0,0 +1,34 @@ +with Ada.Strings.Hash; + +package body Types.Strings is + + function "=" (Left : in Instance; + Right : in String) return Boolean + is (Left.Data = Right); + + function Alloc (Data : in String) return String_Ptr is + Ref : constant String_Ptr := new Instance (Data'Length); + begin + Garbage_Collected.Register (Garbage_Collected.Pointer (Ref)); + Ref.all.Data := Data; + return Ref; + end Alloc; + + function Hash (Item : in String_Ptr) return Ada.Containers.Hash_Type + is (Ada.Strings.Hash (Item.all.Data)); + + procedure Query_Element + (Container : in Instance; + Process : not null access procedure (Element : in String)) + is + begin + Process.all (Container.Data); + end Query_Element; + + function Same_Contents (Left, Right : in String_Ptr) return Boolean + is (Left = Right or else Left.all.Data = Right.all.Data); + + function To_String (Container : in Instance) return String + is (Container.Data); + +end Types.Strings; diff --git a/ada.2/types-strings.ads b/ada.2/types-strings.ads new file mode 100644 index 00000000..58bd0c63 --- /dev/null +++ b/ada.2/types-strings.ads @@ -0,0 +1,49 @@ +with Ada.Containers; + +with Garbage_Collected; + +package Types.Strings is + + ------------------------------------ + -- Keywords, Strings and Symbols -- + ------------------------------------ + + -- Tests seem to show that manual garbage collection is faster + -- than reference counting in Ada.Strings.Unbounded, probably + -- because we know that the values will never change. + + -- Also, maintaining a global structure in order to avoid similar + -- symbol allocations does not seem to improve performances. + + type Instance (<>) is abstract new Garbage_Collected.Instance with private; + + function Alloc (Data : in String) return String_Ptr + with Inline; + + function "=" (Left : in Instance; + Right : in String) return Boolean + with Inline; + + -- This kind of accessor is more efficient than a function + -- returning an array. + procedure Query_Element + (Container : in Instance; + Process : not null access procedure (Element : in String)); + + -- These methods could be implemented with Query_Element, + -- but we want to optimize Envs.Get. + function Hash (Item : in String_Ptr) return Ada.Containers.Hash_Type + with Inline; + function Same_Contents (Left, Right : in String_Ptr) return Boolean + with Inline; + + -- When readability is more important than copying a string. + function To_String (Container : in Instance) return String with Inline; + +private + + type Instance (Last : Natural) is new Garbage_Collected.Instance with record + Data : String (1 .. Last); + end record; + +end Types.Strings; diff --git a/ada.2/types-symbols-names.ads b/ada.2/types-symbols-names.ads deleted file mode 100644 index 1478ab38..00000000 --- a/ada.2/types-symbols-names.ads +++ /dev/null @@ -1,30 +0,0 @@ -package Types.Symbols.Names is - - -- These symbols are used once by Read/Eval/Print cycle. Declare - -- them here in order to avoid an allocation and a desallocation - -- during each call of eval. - -- The built-in functions declared in Core will remain allocated - -- during the lifetime of the program and do not require this. - - -- A separate package is required because the constructor must be - -- callable, and a child package makes sense because without this - -- problem, these definition would be in Symbols. - Ampersand : constant Ptr := Constructor ("&"); - Catch : constant Ptr := Constructor ("catch*"); - Def : constant Ptr := Constructor ("def!"); - Defmacro : constant Ptr := Constructor ("defmacro!"); - Fn : constant Ptr := Constructor ("fn*"); - Let : constant Ptr := Constructor ("let*"); - Macroexpand : constant Ptr := Constructor ("macroexpand"); - Mal_If : constant Ptr := Constructor ("if"); - Quasiquote : constant Ptr := Constructor ("quasiquote"); - Quote : constant Ptr := Constructor ("quote"); - Splice_Unquote : constant Ptr := Constructor ("splice-unquote"); - Try : constant Ptr := Constructor ("try*"); - Unquote : constant Ptr := Constructor ("unquote"); - - -- These are used by both Core and Reader. Spare a search. - Deref : constant Ptr := Constructor ("deref"); - With_Meta : constant Ptr := Constructor ("with-meta"); - -end Types.Symbols.Names; diff --git a/ada.2/types-symbols.adb b/ada.2/types-symbols.adb deleted file mode 100644 index db49b2f5..00000000 --- a/ada.2/types-symbols.adb +++ /dev/null @@ -1,84 +0,0 @@ -with Ada.Containers.Hashed_Sets; -with Ada.Strings.Hash; -with Ada.Unchecked_Deallocation; - -package body Types.Symbols is - - type Rec (Last : Positive) is limited record - Refs : Natural; - Data : String (1 .. Last); - end record; - procedure Free is new Ada.Unchecked_Deallocation (Rec, Acc); - Allocations : Natural := 0; - - function Hash (Item : in Acc) return Ada.Containers.Hash_Type with Inline; - package Sets is new Ada.Containers.Hashed_Sets (Element_Type => Acc, - Hash => Hash, - Equivalent_Elements => "=", - "=" => "="); - function Key (Item : in Acc) return String with Inline; - package Keys is new Sets.Generic_Keys (Key_Type => String, - Key => Key, - Hash => Ada.Strings.Hash, - Equivalent_Keys => "="); - - Dict : Sets.Set; - - ---------------------------------------------------------------------- - - procedure Adjust (Object : in out Ptr) is - begin - Object.Ref.all.Refs := @ + 1; - end Adjust; - - procedure Check_Allocations is - begin - -- See Types.Symbols.Names. - pragma Assert (Allocations = 15); - end Check_Allocations; - - function Constructor (Source : in String) return Ptr is - Position : constant Sets.Cursor := Keys.Find (Dict, Source); - Ref : Acc; - begin - -- Avoid exceptions until Ref is controlled. - if Sets.Has_Element (Position) then - Ref := Sets.Element (Position); - Ref.all.Refs := Ref.all.Refs + 1; - else - Allocations := Allocations + 1; - Ref := new Rec'(Data => Source, - Last => Source'Length, - Refs => 1); - Dict.Insert (Ref); - end if; - return (Ada.Finalization.Controlled with Ref); - end Constructor; - - procedure Finalize (Object : in out Ptr) is - begin - if Object.Ref /= null and then 0 < Object.Ref.all.Refs then - Object.Ref.all.Refs := @ - 1; - if 0 < Object.Ref.all.Refs then - Object.Ref := null; - else - Dict.Delete (Object.Ref); - Allocations := Allocations - 1; - Free (Object.Ref); - end if; - end if; - end Finalize; - - function Hash (Item : in Acc) return Ada.Containers.Hash_Type - is (Ada.Strings.Hash (Item.all.Data)); - - function Hash (Item : in Ptr) return Ada.Containers.Hash_Type - is (Ada.Strings.Hash (Item.Ref.all.Data)); - - function Key (Item : in Acc) return String - is (Item.all.Data); - - function To_String (Item : in Ptr) return String - is (Item.Ref.all.Data); - -end Types.Symbols; diff --git a/ada.2/types-symbols.ads b/ada.2/types-symbols.ads deleted file mode 100644 index c763a3d1..00000000 --- a/ada.2/types-symbols.ads +++ /dev/null @@ -1,67 +0,0 @@ -with Ada.Containers; -private with Ada.Finalization; - -package Types.Symbols with Preelaborate is - - -- Like keys, symbols are immutable final nodes in the internal - -- data structures. For them, reference counting is probably more - -- efficient than garbage collecting. - - type Ptr is tagged private; - - function Constructor (Source : in String) return Ptr with Inline; - - function To_String (Item : in Ptr) return String with Inline; - - -- The hash value is made available because symbols have a high - -- probability to end up as keys in an environment. - function Hash (Item : in Ptr) return Ada.Containers.Hash_Type with Inline; - - -- The implementation ensures that a given content is only - -- allocated once, so equality of pointers gives the same result - -- than comparing the strings. - - type Symbol_Array is array (Positive range <>) of Ptr; - Empty_Array : constant Symbol_Array; - -- It is convenient to define this here because the default value - -- for Ptr is invalid. - - -- Debug. - procedure Check_Allocations with Inline; - -- Does nothing if assertions are disabled. - -private - - -- Only one instance is allocated with a given content. This - -- avoids many allocations and deallocations, since symbols are - -- expected to be used many times. - - -- Tests seem to show that this solution is a few percents faster - -- than Ada.Strings.Unbounded. - - -- As a side effect, some frequent string comparisons (with "def!" - -- or "fn*" for example) will become a bit more efficient because - -- comparing pointers is faster than comparing strings. - - -- It would be natural to store a Cursor from the global - -- dictionnary into Ptr, but this actually reduces the speed, - -- probably because it significantly increases the size of - -- Mal_Type. - - type Rec; - type Acc is access Rec; - type Ptr is new Ada.Finalization.Controlled with record - Ref : Acc := null; - end record - with Invariant => Ptr.Ref /= null; - overriding procedure Adjust (Object : in out Ptr) with Inline; - overriding procedure Finalize (Object : in out Ptr) with Inline; - -- Predefined equality is fine. - pragma Finalize_Storage_Only (Ptr); - - Empty_Array : constant Symbol_Array - := (1 .. 0 => (Ada.Finalization.Controlled with null)); - -- This will not trigger the invariant check because no element is - -- ever actually instantiated. - -end Types.Symbols; diff --git a/ada.2/types-mal.adb b/ada.2/types.adb similarity index 56% rename from ada.2/types-mal.adb rename to ada.2/types.adb index 4230a6f0..96ca3ef4 100644 --- a/ada.2/types-mal.adb +++ b/ada.2/types.adb @@ -1,17 +1,14 @@ +pragma Warnings (Off, "no entities of ""Types.*"" are referenced"); with Types.Atoms; with Types.Builtins; +with Types.Fns; +with Types.Macros; with Types.Maps; with Types.Sequences; -with Types.Fns; +pragma Warnings (On, "no entities of ""Types.*"" are referenced"); +with Types.Strings; -package body Types.Mal is - - use type Ada.Strings.Unbounded.Unbounded_String; - use type Maps.Instance; - use type Sequences.Instance; - use type Symbols.Ptr; - - ---------------------------------------------------------------------- +package body Types is function "=" (Left, Right : in T) return Boolean is (case Left.Kind is @@ -22,26 +19,30 @@ package body Types.Mal is and then Left.Ada_Boolean = Right.Ada_Boolean, when Kind_Number => Right.Kind = Kind_Number and then Left.Number = Right.Number, - when Kind_Symbol => - Right.Kind = Kind_Symbol and then Left.Symbol = Right.Symbol, - when Kind_Key => - Right.Kind = Left.Kind and then Left.S = Right.S, -- Here comes the part that differs from the predefined equality. + when Kind_Key | Kind_Symbol => + Right.Kind = Left.Kind + and then Strings.Same_Contents (Left.Str, Right.Str), when Kind_Sequence => Right.Kind in Kind_Sequence - and then Left.Sequence.all = Right.Sequence.all, + and then (Left.Sequence = Right.Sequence + or else Sequences."=" (Left.Sequence.all, Right.Sequence.all)), when Kind_Map => - Right.Kind = Kind_Map and then Left.Map.all = Right.Map.all, + Right.Kind = Kind_Map + and then (Left.Map = Right.Map + or else Maps."=" (Left.Map.all, Right.Map.all)), -- Also, comparing functions is an interesting problem. when others => False); procedure Keep (Object : in T) is + -- No dynamic dispatching happens here. begin case Object.Kind is - when Kind_Nil | Kind_Boolean | Kind_Number | Kind_Key | Kind_Builtin - | Kind_Symbol => + when Kind_Nil | Kind_Boolean | Kind_Number | Kind_Builtin => null; + when Kind_Key | Kind_Symbol => + Object.Str.all.Keep; when Kind_Atom => Object.Atom.all.Keep; when Kind_Sequence => @@ -50,9 +51,11 @@ package body Types.Mal is Object.Map.all.Keep; when Kind_Builtin_With_Meta => Object.Builtin_With_Meta.all.Keep; - when Kind_Fn | Kind_Macro => + when Kind_Fn => Object.Fn.all.Keep; + when Kind_Macro => + Object.Macro.all.Keep; end case; end Keep; -end Types.Mal; +end Types; diff --git a/ada.2/types.ads b/ada.2/types.ads index f01c830b..6dd0b71d 100644 --- a/ada.2/types.ads +++ b/ada.2/types.ads @@ -1,4 +1,32 @@ -package Types with Pure is +limited with Types.Atoms; +limited with Types.Builtins; +limited with Types.Fns; +limited with Types.Macros; +limited with Types.Maps; +limited with Types.Sequences; +limited with Types.Strings; + +package Types is + + -- A type with a default value for the discriminant is the Ada + -- equivalent of a C union. It uses a fixed size, and allows + -- efficient arrays. A class hierarchy would make this impossible, + -- for little gain. + -- Native types may seem to consume too much memory, but + -- 1/ they require no allocation/deallocation. + -- 2/ the overhead would actually be higher with an intermediate + -- reference (the size of the pointer plus the size of the native + -- type, while an union uses the minimum of both and a single + -- memory area ). + + -- The idea is inspired from the Haskell and OCaml interpreters, + -- which use a bit to distinguish pointers from integers. Ada + -- allows to specify the bit position of each component, but + -- generating such architecture-dependent definitions seems a lot + -- of work for MAL. + + -- The Ada tradition is to give explicit names to types, but this + -- one will be used very often. type Kind_Type is (Kind_Nil, @@ -15,4 +43,52 @@ package Types with Pure is subtype Kind_Sequence is Kind_Type range Kind_List .. Kind_Vector; subtype Kind_Function is Kind_Type range Kind_Fn .. Kind_Builtin; + type T; + type T_Array; + type Atom_Ptr is not null access Atoms.Instance; + type Builtin_Ptr is not null access function (Args : in T_Array) return T; + type Builtin_With_Meta_Ptr is not null access Builtins.Instance; + type Fn_Ptr is not null access Fns.Instance; + type Macro_Ptr is not null access Macros.Instance; + type Map_Ptr is not null access Maps.Instance; + type Sequence_Ptr is not null access Sequences.Instance; + type String_Ptr is not null access Strings.Instance; + + type T (Kind : Kind_Type := Kind_Nil) is record + case Kind is + when Kind_Nil => + null; + when Kind_Boolean => + Ada_Boolean : Boolean; + when Kind_Number => + Number : Integer; + when Kind_Atom => + Atom : Atom_Ptr; + when Kind_Key | Kind_Symbol => + Str : String_Ptr; + when Kind_Sequence => + Sequence : Sequence_Ptr; + when Kind_Map => + Map : Map_Ptr; + when Kind_Builtin => + Builtin : Builtin_Ptr; + when Kind_Builtin_With_Meta => + Builtin_With_Meta : Builtin_With_Meta_Ptr; + when Kind_Fn => + Fn : Fn_Ptr; + when Kind_Macro => + Macro : Macro_Ptr; + end case; + end record; + + -- Useful for recursive automatic definition of equality for + -- composite types like the array type below. + function "=" (Left, Right : in T) return Boolean with Inline; + + Nil : constant T := (Kind => Kind_Nil); + + procedure Keep (Object : in T) with Inline; + + type T_Array is array (Positive range <>) of T; + end Types;