DISABLE FDs (REMOVE ME).
[jackhill/mal.git] / ada / reader.adb
dissimilarity index 68%
index 9388780..94ecc4a 100644 (file)
-with Ada.IO_Exceptions;
-with Ada.Characters.Latin_1;
-with Ada.Exceptions;
-with Ada.Strings.Maps.Constants;
-with Ada.Strings.Unbounded;
-with Ada.Text_IO;
-with Smart_Pointers;
-with Types.Vector;
-with Types.Hash_Map;
-
-package body Reader is
-
-   package ACL renames Ada.Characters.Latin_1;
-
-   type Lexemes is (Whitespace, Comment,
-                    Int, Float_Tok, Sym,
-                    Nil, True_Tok, False_Tok,
-                    LE_Tok, GE_Tok, Exp_Tok, Splice_Unq,
-                    Str, Atom);
-
-   Lisp_Whitespace : constant Ada.Strings.Maps.Character_Set :=
-     Ada.Strings.Maps.To_Set
-       (ACL.HT & ACL.LF & ACL.CR & ACL.Space & ACL.Comma);
-
-   -- [^\s\[\]{}('"`,;)]
-   Terminator_Syms : Ada.Strings.Maps.Character_Set :=
-     Ada.Strings.Maps."or"
-       (Lisp_Whitespace, 
-        Ada.Strings.Maps.To_Set ("[]{}('""`,;)"));
-
-   -- This is raised if an invalid character is encountered
-   Lexical_Error : exception;
-
-   -- The unterminated string error
-   String_Error : exception;
-
-
-   function Convert_String (S : String) return String is
-      use Ada.Strings.Unbounded;
-      Res : Unbounded_String;
-      I : Positive;
-      Str_Last : Natural;
-   begin
-      Str_Last := S'Last;
-      I := S'First;
-      while I <= Str_Last loop
-         if S (I) = '\' then
-            if I+1 > Str_Last then
-               Append (Res, S (I));
-               I := I + 1;
-            elsif S (I+1) = 'n' then
-               Append (Res, Ada.Characters.Latin_1.LF);
-               I := I + 2;
-            elsif S (I+1) = '"' then
-               Append (Res, S (I+1));
-               I := I + 2;
-            elsif S (I+1) = '\' then
-               Append (Res, S (I+1));
-               I := I + 2;
-            else
-               Append (Res, S (I));
-               I := I + 1;
-            end if;
-         else
-            Append (Res, S (I));
-            I := I + 1;
-         end if;
-      end loop;
-      return To_String (Res);
-   end Convert_String;
-
-   subtype String_Indices is Integer range 0 .. Max_Line_Len;
-
-   Str_Len : String_Indices := 0;
-   Saved_Line : String (1..Max_Line_Len);
-   Char_To_Read : String_Indices := 1;
-
-   function Get_Token return Types.Mal_Handle is
-      use Types;
-      Res : Types.Mal_Handle;
-      I, J : String_Indices;
-      Dots : Natural;
-      All_Digits : Boolean;
-   begin
-
-      <<Tail_Call_Opt>>
-      I := Char_To_Read;
-      while I <= Str_Len and then
-            Ada.Strings.Maps.Is_In (Saved_Line (I), Lisp_Whitespace) loop
-         I := I + 1;
-      end loop;
-
-      -- Filter out lines consisting of only whitespace
-      if I > Str_Len then
-         return Smart_Pointers.Null_Smart_Pointer;
-      end if;
-
-      J := I;
-      case Saved_Line (J) is
-         when '~' => -- Circumflex
-            if J+1 <= Str_Len and then Saved_Line(J+1) = '@' then
-               Res := New_Unitary_Mal_Type
-                 (Func => Splice_Unquote,
-                  Op => Smart_Pointers.Null_Smart_Pointer);
-               Char_To_Read := J+2;
-            else
-               -- Just a circumflex
-               Res := New_Atom_Mal_Type (Saved_Line (J..J));
-               Char_To_Read := J+1;
-            end if;
-         when '[' | ']' |
-              '{' | '}' |
-              '(' | ')' |
-              ''' | '`' |
-              '^' | '@' =>
-            
-            Res := New_Atom_Mal_Type (Saved_Line (J..J));
-            Char_To_Read := J+1;
-
-         when '"' => -- a string
-
-            -- Skip over "
-            J := J + 1;
-            while J <= Str_Len and then
-               (Saved_Line (J) /= '"' or else
-                 Saved_Line (J-1) = '\') loop
-               J := J + 1;
-            end loop;
-
-            -- So we either ran out of string..
-            if J > Str_Len then
-               raise String_Error;
-            end if;
-
-            -- or we reached an unescaped "
-            Res := New_String_Mal_Type
-              (Str => Convert_String (Saved_Line (I .. J)));
-            Char_To_Read := J + 1;
-
-         when ';' => -- a comment
-
-            -- Read to the end of the line or until
-            -- the saved_line string is exhausted.
-            -- NB if we reach the end we don't care
-            -- what the last char was.
-            while J < Str_Len and Saved_Line (J) /= ACL.LF loop
-               J := J + 1;
-            end loop;
-            if J = Str_Len then
-               Res := Smart_Pointers.Null_Smart_Pointer;
-            else
-               Char_To_Read := J + 1;
-               -- was: Res := Get_Token;
-               goto Tail_Call_Opt;
-            end if;
-
-         when others => -- an atom
-
-            while J <= Str_Len and then
-               not Ada.Strings.Maps.Is_In (Saved_Line (J), Terminator_Syms) loop
-               J := J + 1;
-            end loop;
-
-            -- Either we ran out of string or
-            -- the one at J was the start of a new token
-            Char_To_Read := J;
-            J := J - 1;
-
-            -- check if all digits or .
-            Dots := 0;
-            All_Digits := True;
-            for K in I .. J loop
-               if Saved_Line (K) = '.' then
-                  Dots := Dots + 1; 
-               elsif not (Saved_Line (K) in '0' .. '9') then
-                  All_Digits := False;
-                  exit;
-               end if;
-            end loop;
-
-            if All_Digits then
-               if Dots = 0 then
-                  Res := New_Int_Mal_Type
-                    (Int => Mal_Integer'Value (Saved_Line (I .. J)));
-               elsif Dots = 1 then
-                  Res := New_Float_Mal_Type
-                    (Floating => Mal_Float'Value (Saved_Line (I..J)));
-               else
-                  Res := New_Atom_Mal_Type (Saved_Line (I..J));
-               end if;
-            else
-               Res := New_Atom_Mal_Type (Saved_Line (I..J));
-            end if;
-
-      end case;
-
-      return Res;
-
-   end Get_Token;
-
-
-   -- Parsing
-   function Read_Form return Types.Mal_Handle;
-
-   function Read_List (LT : Types.List_Types)
-   return Types.Mal_Handle is
-
-      use Types;
-      MTA : Mal_Handle;
-
-   begin
-
-      MTA := Read_Form;
-
-      if Deref (MTA).Sym_Type = Atom and then
-         Deref_Atom (MTA).Get_Atom = "fn*" then
-
-         declare
-            Params, Expr, Close_Lambda : Mal_Handle;
-         begin
-            Params := Read_Form;
-            Expr := Read_Form;
-            Close_Lambda := Read_Form;  -- the ) at the end of the lambda
-            return New_Lambda_Mal_Type (Params, Expr);
-         exception
-            when Lexical_Error =>
-
-              -- List_MT about to go out of scope but its a Mal_Handle
-              -- so it is automatically garbage collected.
-
-              return New_Error_Mal_Type (Str => "Lexical error in fn*");
-
-         end;
-
-      else
-
-         declare
-            List_SP : Mal_Handle;
-            List_P : List_Class_Ptr;
-            Close : String (1..1) := (1 => Types.Closing (LT));
-         begin
-            case LT is
-               when List_List =>
-                  List_SP := New_List_Mal_Type (List_Type => LT);
-               when Vector_List =>
-                  List_SP := Vector.New_Vector_Mal_Type;
-               when Hashed_List =>
-                  List_SP := Hash_Map.New_Hash_Map_Mal_Type;
-            end case;
-
-
-            -- Need to append to a variable so...
-            List_P := Deref_List_Class (List_SP);
-            loop
-               exit when Is_Null (MTA) or else
-                         (Deref (MTA).Sym_Type = Atom and then
-                          Atom_Mal_Type (Deref (MTA).all).Get_Atom = Close);
-               Append (List_P.all, MTA);
-               MTA := Read_Form;
-            end loop;
-            return List_SP;
-         exception
-            when Lexical_Error =>
-
-              -- List_MT about to go out of scope but its a Mal_Handle
-              -- so it is automatically garbage collected.
-
-              return New_Error_Mal_Type (Str => "expected '" & Close & "'");
-
-         end;
-      end if;
-
-   exception
-      when Lexical_Error =>
-
-        return New_Error_Mal_Type (Str => "Lexical error in Read_List");
-
-   end Read_List;
-
-
-   function Read_Form return Types.Mal_Handle is
-      use Types;
-      MTS : Mal_Handle;
-   begin
-
-      MTS := Get_Token;
-
-      if Is_Null (MTS) then
-         return Smart_Pointers.Null_Smart_Pointer;
-      end if;
-
-      if Deref (MTS).Sym_Type = Atom then
-
-         declare
-            Symbol : String := Atom_Mal_Type (Deref (MTS).all).Get_Atom;
-         begin
-            -- Listy things and quoting...
-            if Symbol = "(" then
-               return Read_List (List_List);
-            elsif Symbol = "[" then
-               return Read_List (Vector_List);
-            elsif Symbol = "{" then
-               return Read_List (Hashed_List);
-            elsif Symbol = "^" then
-               declare
-                  Meta, Obj : Mal_Handle;
-               begin
-                  Meta := Read_Form;
-                  Obj := Read_Form;
-                  declare
-                     MT : Mal_Ptr := Deref (Obj);
-                  begin
-                     Set_Meta (MT.all, Meta);
-                  end;
-                  return Obj;
-               end;
-            elsif Symbol = ACL.Apostrophe & "" then
-
-               declare
-                  List_SP : Mal_Handle;
-                  List_P : List_Ptr;
-               begin
-                  List_SP := New_List_Mal_Type (List_Type => List_List);
-                  List_P := Deref_List (List_SP);
-                  Append (List_P.all, New_Atom_Mal_Type ("quote"));
-                  Append (List_P.all, Read_Form);
-                  return List_SP;
-               end;
-
-            elsif Symbol = ACL.Grave & "" then
-
-               declare
-                  List_SP : Mal_Handle;
-                  List_P : List_Ptr;
-               begin
-                  List_SP := New_List_Mal_Type (List_Type => List_List);
-                  List_P := Deref_List (List_SP);
-                  Append (List_P.all, New_Atom_Mal_Type ("quasiquote"));
-                  Append (List_P.all, Read_Form);
-                  return List_SP;
-               end;
-
-            elsif Symbol = ACL.Tilde & "" then
-
-               declare
-                  List_SP : Mal_Handle;
-                  List_P : List_Ptr;
-               begin
-                  List_SP := New_List_Mal_Type (List_Type => List_List);
-                  List_P := Deref_List (List_SP);
-                  Append (List_P.all, New_Atom_Mal_Type ("unquote"));
-                  Append (List_P.all, Read_Form);
-                  return List_SP;
-               end;
-
-            elsif Symbol = ACL.Commercial_At & "" then
-               return New_Unitary_Mal_Type (Func => Deref, Op => Read_Form);
-            else
-               return MTS;
-            end if;
-         end;
-
-      elsif Deref(MTS).Sym_Type = Unitary and then
-            Unitary_Mal_Type (Deref (MTS).all).Get_Func = Splice_Unquote then
-
-         declare
-            List_SP : Mal_Handle;
-            List_P : List_Ptr;
-         begin
-            List_SP := New_List_Mal_Type (List_Type => List_List);
-            List_P := Deref_List (List_SP);
-            Append (List_P.all, New_Atom_Mal_Type ("splice-unquote"));
-            Append (List_P.all, Read_Form);
-            return List_SP;
-         end;
-
-      else
-         return MTS;
-      end if;
-
-   exception
-      when String_Error =>
-        return New_Error_Mal_Type (Str => "expected '""'");
-   end Read_Form;
-
-   procedure Lex_Init (S : String) is
-   begin
-      Str_Len := S'Length;
-      Saved_Line (1..S'Length) := S;  -- Needed for error recovery
-      Char_To_Read := 1;
-   end Lex_Init;
-
-   function Read_Str (S : String) return Types.Mal_Handle is
-      I, Str_Len : Natural := S'Length;
-   begin
-
-      Lex_Init (S);
-
-      return Read_Form;
-
-   end Read_Str;
-   
-
-end Reader;
+with Ada.IO_Exceptions;
+with Ada.Characters.Latin_1;
+with Ada.Exceptions;
+with Ada.Strings.Maps.Constants;
+with Ada.Strings.Unbounded;
+with Ada.Text_IO;
+with Smart_Pointers;
+with Types.Vector;
+with Types.Hash_Map;
+
+package body Reader is
+
+   use Types;
+
+   package ACL renames Ada.Characters.Latin_1;
+
+   type Lexemes is (Ignored_Tok,
+                    Start_List_Tok, Start_Vector_Tok, Start_Hash_Tok,
+                    Meta_Tok, Deref_Tok,
+                    Quote_Tok, Quasi_Quote_Tok, Splice_Unq_Tok, Unquote_Tok,
+                    Int_Tok, Float_Tok,
+                    Str_Tok, Sym_Tok);
+
+   type Token (ID : Lexemes := Ignored_Tok) is record
+      case ID is
+         when Int_Tok =>
+            Int_Val : Mal_Integer;
+         when Float_Tok =>
+            Float_Val : Mal_Float;
+         when Str_Tok | Sym_Tok =>
+            Start_Char, Stop_Char : Natural;
+         when others => null;
+      end case;
+   end record;
+
+   Lisp_Whitespace : constant Ada.Strings.Maps.Character_Set :=
+     Ada.Strings.Maps.To_Set
+       (ACL.HT & ACL.LF & ACL.CR & ACL.Space & ACL.Comma);
+
+   -- [^\s\[\]{}('"`,;)]
+   Terminator_Syms : Ada.Strings.Maps.Character_Set :=
+     Ada.Strings.Maps."or"
+       (Lisp_Whitespace, 
+        Ada.Strings.Maps.To_Set ("[]{}('""`,;)"));
+
+   -- The unterminated string error
+   String_Error : exception;
+
+
+   function Convert_String (S : String) return String is
+      use Ada.Strings.Unbounded;
+      Res : Unbounded_String;
+      I : Positive;
+      Str_Last : Natural;
+   begin
+      Str_Last := S'Last;
+      I := S'First;
+      while I <= Str_Last loop
+         if S (I) = '\' then
+            if I+1 > Str_Last then
+               Append (Res, S (I));
+               I := I + 1;
+            elsif S (I+1) = 'n' then
+               Append (Res, Ada.Characters.Latin_1.LF);
+               I := I + 2;
+            elsif S (I+1) = '"' then
+               Append (Res, S (I+1));
+               I := I + 2;
+            elsif S (I+1) = '\' then
+               Append (Res, S (I+1));
+               I := I + 2;
+            else
+               Append (Res, S (I));
+               I := I + 1;
+            end if;
+         else
+            Append (Res, S (I));
+            I := I + 1;
+         end if;
+      end loop;
+      return To_String (Res);
+   end Convert_String;
+
+   Str_Len : Natural := 0;
+   Saved_Line : Ada.Strings.Unbounded.Unbounded_String;
+   Char_To_Read : Natural := 1;
+
+   function Get_Token return Token is
+      Res : Token;
+      I, J : Natural;
+      use Ada.Strings.Unbounded;
+   begin
+
+      <<Tail_Call_Opt>>
+
+      -- Skip over whitespace...
+      I := Char_To_Read;
+      while I <= Str_Len and then
+            Ada.Strings.Maps.Is_In (Element (Saved_Line, I), Lisp_Whitespace) loop
+         I := I + 1;
+      end loop;
+
+      -- Filter out lines consisting of only whitespace
+      if I > Str_Len then
+         return (ID => Ignored_Tok);
+      end if;
+
+      J := I;
+
+      case Element (Saved_Line, J) is
+
+         when ''' => Res := (ID => Quote_Tok); Char_To_Read := J+1;
+
+         when '`' => Res := (ID => Quasi_Quote_Tok); Char_To_Read := J+1;
+
+         when '~' => -- Tilde
+
+            if J+1 <= Str_Len and then Element (Saved_Line, J+1) = '@' then
+               Res := (ID => Splice_Unq_Tok);
+               Char_To_Read := J+2;
+            else
+               -- Just a Tilde
+               Res := (ID => Unquote_Tok);
+               Char_To_Read := J+1;
+            end if;
+
+         when '(' => Res := (ID => Start_List_Tok); Char_To_Read := J+1;
+         when '[' => Res := (ID => Start_Vector_Tok); Char_To_Read := J+1;
+         when '{' => Res := (ID => Start_Hash_Tok); Char_To_Read := J+1;
+
+         when '^' => Res := (ID => Meta_Tok); Char_To_Read := J+1;
+         when '@' => Res := (ID => Deref_Tok); Char_To_Read := J+1;
+
+         when ']' | '}' | ')' =>
+            
+            Res := (ID => Sym_Tok, Start_Char => J, Stop_Char => J);
+            Char_To_Read := J+1;
+
+         when '"' => -- a string
+
+            loop
+               if Str_Len <= J then
+                  raise String_Error;
+               end if;
+               J := J + 1;
+               exit when Element (Saved_Line, J) = '"';
+               if Element (Saved_Line, J) = '\' then
+                  J := J + 1;
+               end if;
+            end loop;
+
+            Res := (ID => Str_Tok, Start_Char => I, Stop_Char => J);
+            Char_To_Read := J + 1;
+
+         when ';' => -- a comment
+
+            -- Read to the end of the line or until
+            -- the saved_line string is exhausted.
+            -- NB if we reach the end we don't care
+            -- what the last char was.
+            while J < Str_Len and Element (Saved_Line, J) /= ACL.LF loop
+               J := J + 1;
+            end loop;
+            if J = Str_Len then
+               Res := (ID => Ignored_Tok);
+            else
+               Char_To_Read := J + 1;
+               -- was: Res := Get_Token;
+               goto Tail_Call_Opt;
+            end if;
+
+         when others => -- an atom
+
+            while J <= Str_Len and then
+               not Ada.Strings.Maps.Is_In (Element (Saved_Line, J), Terminator_Syms) loop
+               J := J + 1;
+            end loop;
+
+            -- Either we ran out of string or
+            -- the one at J was the start of a new token
+            Char_To_Read := J;
+            J := J - 1;
+
+            declare
+               Dots : Natural;
+               All_Digits : Boolean;
+            begin
+               -- check if all digits or .
+               Dots := 0;
+               All_Digits := True;
+               for K in I .. J loop
+                  if (K = I and K /= J) and then Element (Saved_Line, K) = '-' then
+                     null;
+                  elsif Element (Saved_Line, K) = '.' then
+                     Dots := Dots + 1; 
+                  elsif not (Element (Saved_Line, K) in '0' .. '9') then
+                     All_Digits := False;
+                     exit;
+                  end if;
+               end loop;
+
+               if All_Digits then
+                  if Dots = 0 then
+                     Res :=
+                       (ID => Int_Tok,
+                        Int_Val => Mal_Integer'Value (Slice (Saved_Line, I, J)));
+                  elsif Dots = 1 then
+                     Res :=
+                       (ID => Float_Tok,
+                        Float_Val => Mal_Float'Value (Slice (Saved_Line, I, J)));
+                  else
+                     Res := (ID => Sym_Tok, Start_Char => I, Stop_Char => J);
+                  end if;
+               else
+                  Res := (ID => Sym_Tok, Start_Char => I, Stop_Char => J);
+               end if;
+
+            end;
+
+      end case;
+
+      return Res;
+
+   end Get_Token;
+
+
+   function Read_List (LT : Types.List_Types)
+   return Types.Mal_Handle is
+
+      MTA : Mal_Handle;
+
+   begin
+
+      MTA := Read_Form;
+
+      declare
+         List_SP : Mal_Handle;
+         List_P : List_Class_Ptr;
+         Close : String (1..1) := (1 => Types.Closing (LT));
+      begin
+
+         case LT is
+            when List_List   => List_SP := New_List_Mal_Type (List_Type => LT);
+            when Vector_List => List_SP := Vector.New_Vector_Mal_Type;
+            when Hashed_List => List_SP := Hash_Map.New_Hash_Map_Mal_Type;
+         end case;
+
+         -- Need to append to a variable so...
+         List_P := Deref_List_Class (List_SP);
+
+         loop
+
+            if Is_Null (MTA) then
+               return New_Error_Mal_Type (Str => "expected '" & Close & "', got EOF");
+            end if;
+
+            exit when Deref (MTA).Sym_Type = Sym and then
+                      Symbol_Mal_Type (Deref (MTA).all).Get_Sym = Close;
+
+            Append (List_P.all, MTA);
+
+            MTA := Read_Form;
+
+         end loop;
+
+         return List_SP;
+
+      end;
+
+   end Read_List;
+
+
+   function Read_Form return Types.Mal_Handle is
+      Tok : Token;
+      MTS : Mal_Handle;
+      use Ada.Strings.Unbounded;
+   begin
+
+      Tok := Get_Token;
+
+      case Tok.ID is
+
+         when Ignored_Tok => return Smart_Pointers.Null_Smart_Pointer;
+
+         when Int_Tok => return New_Int_Mal_Type (Tok.Int_Val);
+
+         when Float_Tok => return New_Float_Mal_Type (Tok.Float_Val);
+
+         when Start_List_Tok => return Read_List (List_List);
+
+         when Start_Vector_Tok => return Read_List (Vector_List);
+
+         when Start_Hash_Tok => return Read_List (Hashed_List);
+
+         when Meta_Tok =>
+            
+            declare
+               Meta, Obj : Mal_Handle;
+            begin
+               Meta := Read_Form;
+               Obj := Read_Form;
+               return Make_New_List
+                        ((1 => New_Symbol_Mal_Type ("with-meta"),
+                          2 => Obj,
+                          3 => Meta));
+            end;
+
+         when Deref_Tok =>
+
+            return Make_New_List
+                     ((1 => New_Symbol_Mal_Type ("deref"),
+                       2 => Read_Form));
+
+         when Quote_Tok =>
+
+            return Make_New_List
+                     ((1 => New_Symbol_Mal_Type ("quote"),
+                       2 => Read_Form));
+
+         when Quasi_Quote_Tok =>
+
+            return Make_New_List
+                     ((1 => New_Symbol_Mal_Type ("quasiquote"),
+                       2 => Read_Form));
+
+         when Splice_Unq_Tok =>
+
+            return Make_New_List
+                     ((1 =>  New_Symbol_Mal_Type ("splice-unquote"),
+                       2 => Read_Form));
+
+         when Unquote_Tok =>
+
+            return Make_New_List
+                     ((1 => New_Symbol_Mal_Type ("unquote"),
+                       2 => Read_Form));
+
+         when Str_Tok =>
+
+            -- +/-1 strips out the double quotes.
+            -- Convert_String converts backquoted charaters to raw format.
+            return New_String_Mal_Type
+                     (Convert_String
+                       (Slice (Saved_Line, Tok.Start_Char + 1, Tok.Stop_Char - 1)));
+
+         when Sym_Tok =>
+
+            -- Mal interpreter is required to know about true, false and nil.
+            declare
+               S : String := Slice (Saved_Line, Tok.Start_Char, Tok.Stop_Char);
+            begin
+               if S = "true" then
+                  return New_Bool_Mal_Type (True);
+               elsif S = "false" then
+                  return New_Bool_Mal_Type (False);
+               elsif S = "nil" then
+                  return New_Nil_Mal_Type;
+               else
+                  return New_Symbol_Mal_Type (S);
+               end if;
+            end;
+
+      end case;
+
+   end Read_Form;
+
+
+   procedure Lex_Init (S : String) is
+   begin
+      Str_Len := S'Length;
+      Saved_Line := Ada.Strings.Unbounded.To_Unbounded_String (S);
+      Char_To_Read := 1;
+   end Lex_Init;
+
+
+   function Read_Str (S : String) return Types.Mal_Handle is
+      I, Str_Len : Natural := S'Length;
+   begin
+
+      Lex_Init (S);
+
+      return Read_Form;
+
+   exception
+      when String_Error =>
+        return New_Error_Mal_Type (Str => "expected '""', got EOF");
+   end Read_Str;
+   
+
+end Reader;