Skip to content

Commit

Permalink
Change quasiquote algorithm
Browse files Browse the repository at this point in the history
- Add a `vec` built-in function in step7 so that `quasiquote` does not
  require `apply` from step9.
- Introduce quasiquoteexpand special in order to help debugging step7.
  This may also prepare newcomers to understand step8.
- Add soft tests.
- Do not quote numbers, strings and so on.

Should ideally have been in separate commits:
- elisp: simplify and fix (keyword :k)
- factor: fix copy/paste error in let*/step7, simplify eval-ast.
- guile: improve list/vector types
- haskell: revert evaluation during quasiquote
- logo, make: cosmetic issues
  • Loading branch information
asarhaddon committed Aug 10, 2020
1 parent ece70f9 commit fbfe678
Show file tree
Hide file tree
Showing 467 changed files with 12,947 additions and 8,868 deletions.
2 changes: 1 addition & 1 deletion docs/exercises.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ make REGRESS=1 TEST_OPTS='--hard --pre-eval=\(load-file\ \"../answer.mal\"\)' te

- Implement `>`, `<=` and `>=` with `<`.

- Implement `list`, `prn`, `hash-map` and `swap!` as non-recursive
- Implement `list`, `vec`, `prn`, `hash-map` and `swap!` as non-recursive
functions.

- Implement `count`, `nth`, `map`, `concat` and `conj` with the empty
Expand Down
6 changes: 3 additions & 3 deletions examples/exercises.mal
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
(def! >= (fn* [a b] (not (< a b))))

(def! list (fn* [& xs] xs))
(def! vec (fn* [xs] (apply vector xs)))
(def! prn (fn* [& xs] (println (apply pr-str xs))))
(def! hash-map (fn* [& xs] (apply assoc {} xs)))
(def! swap! (fn* [a f & xs] (reset! a (apply f (deref a) xs))))
Expand All @@ -48,7 +49,7 @@
(def! conj
(fn* [xs & ys]
(if (vector? xs)
(apply vector (concat xs ys))
(vec (concat xs ys))
(reduce (fn* [acc x] (cons x acc)) xs ys))))

(def! do2 (fn* [& xs] (nth xs (- (count xs) 1))))
Expand All @@ -69,8 +70,7 @@
(first (rest ast))
(foldr _quasiquote_iter () ast))
(if (vector? ast)
;; TODO: once tests are fixed, replace 'list with 'vector.
(list 'apply 'list (foldr _quasiquote_iter () ast))
(list 'vec (foldr _quasiquote_iter () ast))
(list 'quote ast)))))

;; Interpret kvs as [k1 v1 k2 v2 ... kn vn] and returns
Expand Down
1 change: 1 addition & 0 deletions impls/ada.2/core.adb
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ package body Core is
P ("throw", Err.Throw'Access);
P ("time-ms", Time_Ms'Access);
P ("vals", Types.Maps.Vals'Access);
P ("vec", Types.Sequences.Vec'Access);
P ("vector", Types.Sequences.Vector'Access);
P ("with-meta", With_Meta'Access);
end NS_Add_To_Repl;
Expand Down
89 changes: 39 additions & 50 deletions impls/ada.2/step7_quote.adb
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
with Ada.Command_Line;
with Ada.Containers.Vectors;
with Ada.Environment_Variables;
with Ada.Text_IO.Unbounded_IO;

Expand All @@ -23,7 +22,6 @@ procedure Step7_Quote is
use all type Types.Kind_Type;
use type Types.Strings.Instance;
package ACL renames Ada.Command_Line;
package Vectors is new Ada.Containers.Vectors (Positive, Types.T);

function Read return Types.T_Array with Inline;

Expand All @@ -32,12 +30,7 @@ procedure Step7_Quote is
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 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.
function Quasiquote (Ast : in Types.T) return Types.T;

procedure Print (Ast : in Types.T) with Inline;

Expand Down Expand Up @@ -174,9 +167,13 @@ procedure Step7_Quote is
Ast => Ast.Sequence.all.Data (3),
Env => Env));
end;
elsif First.Str.all = "quasiquoteexpand" then
Err.Check (Ast.Sequence.all.Length = 2, "expected 1 parameter");
return Quasiquote (Ast.Sequence.all.Data (2));
elsif First.Str.all = "quasiquote" then
Err.Check (Ast.Sequence.all.Length = 2, "expected 1 parameter");
return Quasiquote (Ast.Sequence.all.Data (2), Env);
Ast := Quasiquote (Ast.Sequence.all.Data (2));
goto Restart;
else
-- Equivalent to First := Eval (First, Env)
-- except that we already know enough to spare a recursive call.
Expand Down Expand Up @@ -266,62 +263,54 @@ procedure Step7_Quote is
Ada.Text_IO.Unbounded_IO.Put_Line (Printer.Pr_Str (Ast));
end Print;

function Quasiquote (Ast : in Types.T;
Env : in Envs.Ptr) return Types.T
is
function Quasiquote (Ast : in Types.T) return Types.T is

function Quasiquote_List (List : in Types.T_Array) return Types.T;
-- Handle vectors and lists not starting with unquote.
function Qq_Seq return Types.T;
function Starts_With (Sequence : Types.T_Array;
Symbol : String) return Boolean;

function Quasiquote_List (List : in Types.T_Array) return Types.T is
Vector : Vectors.Vector; -- buffer for concatenation
Tmp : Types.T;
function Qq_Seq return Types.T is
Result : Types.T := Types.Sequences.List ((1 .. 0 => Types.Nil));
begin
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"
for Elt of reverse Ast.Sequence.all.Data loop
if Elt.Kind = Kind_List
and then Starts_With (Elt.Sequence.all.Data, "splice-unquote")
then
Err.Check (Elt.Sequence.all.Length = 2,
"splice-unquote expects 1 parameter");
Tmp := Eval (Elt.Sequence.all.Data (2), Env);
Err.Check (Tmp.Kind = Kind_List,
"splice_unquote expects a list");
for Sub_Elt of Tmp.Sequence.all.Data loop
Vector.Append (Sub_Elt);
end loop;
Result := Types.Sequences.List
(((Kind_Symbol, Types.Strings.Alloc ("concat")),
Elt.Sequence.all.Data (2), Result));
else
Vector.Append (Quasiquote (Elt, Env));
Result := Types.Sequences.List
(((Kind_Symbol, Types.Strings.Alloc ("cons")),
Quasiquote (Elt), Result));
end if;
end loop;
-- Now that we know the number of elements, convert to a list.
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;
return Result;
end Qq_Seq;

begin -- Quasiquote
function Starts_With (Sequence : Types.T_Array;
Symbol : String) return Boolean is
(0 < Sequence'Length
and then Sequence (Sequence'First).Kind = Kind_Symbol
and then Sequence (Sequence'First).Str.all = Symbol);

begin
case Ast.Kind is
when Kind_Vector =>
-- When the test is updated, replace Kind_List with Kind_Vector.
return Quasiquote_List (Ast.Sequence.all.Data);
when Kind_List =>
if 0 < Ast.Sequence.all.Length
and then Ast.Sequence.all.Data (1).Kind = Kind_Symbol
and then Ast.Sequence.all.Data (1).Str.all = "unquote"
then
if Starts_With (Ast.Sequence.all.Data, "unquote") then
Err.Check (Ast.Sequence.all.Length = 2, "expected 1 parameter");
return Eval (Ast.Sequence.all.Data (2), Env);
return Ast.Sequence.all.Data (2);
else
return Quasiquote_List (Ast.Sequence.all.Data);
return Qq_Seq;
end if;
when Kind_Vector =>
return Types.Sequences.List
(((Kind_Symbol, Types.Strings.Alloc ("vec")), Qq_Seq));
when Kind_Map | Kind_Symbol =>
return Types.Sequences.List
(((Kind_Symbol, Types.Strings.Alloc ("quote")), Ast));
when others =>
return Ast;
end case;
Expand Down
89 changes: 39 additions & 50 deletions impls/ada.2/step8_macros.adb
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
with Ada.Command_Line;
with Ada.Containers.Vectors;
with Ada.Environment_Variables;
with Ada.Text_IO.Unbounded_IO;

Expand All @@ -23,7 +22,6 @@ procedure Step8_Macros is
use all type Types.Kind_Type;
use type Types.Strings.Instance;
package ACL renames Ada.Command_Line;
package Vectors is new Ada.Containers.Vectors (Positive, Types.T);

function Read return Types.T_Array with Inline;

Expand All @@ -32,12 +30,7 @@ procedure Step8_Macros is
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 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.
function Quasiquote (Ast : in Types.T) return Types.T;

procedure Print (Ast : in Types.T) with Inline;

Expand Down Expand Up @@ -195,9 +188,13 @@ procedure Step8_Macros is
Macroexpanding := True;
Ast := Ast.Sequence.all.Data (2);
goto Restart;
elsif First.Str.all = "quasiquoteexpand" then
Err.Check (Ast.Sequence.all.Length = 2, "expected 1 parameter");
return Quasiquote (Ast.Sequence.all.Data (2));
elsif First.Str.all = "quasiquote" then
Err.Check (Ast.Sequence.all.Length = 2, "expected 1 parameter");
return Quasiquote (Ast.Sequence.all.Data (2), Env);
Ast := Quasiquote (Ast.Sequence.all.Data (2));
goto Restart;
else
-- Equivalent to First := Eval (First, Env)
-- except that we already know enough to spare a recursive call.
Expand Down Expand Up @@ -315,62 +312,54 @@ procedure Step8_Macros is
Ada.Text_IO.Unbounded_IO.Put_Line (Printer.Pr_Str (Ast));
end Print;

function Quasiquote (Ast : in Types.T;
Env : in Envs.Ptr) return Types.T
is
function Quasiquote (Ast : in Types.T) return Types.T is

function Quasiquote_List (List : in Types.T_Array) return Types.T;
-- Handle vectors and lists not starting with unquote.
function Qq_Seq return Types.T;
function Starts_With (Sequence : Types.T_Array;
Symbol : String) return Boolean;

function Quasiquote_List (List : in Types.T_Array) return Types.T is
Vector : Vectors.Vector; -- buffer for concatenation
Tmp : Types.T;
function Qq_Seq return Types.T is
Result : Types.T := Types.Sequences.List ((1 .. 0 => Types.Nil));
begin
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"
for Elt of reverse Ast.Sequence.all.Data loop
if Elt.Kind = Kind_List
and then Starts_With (Elt.Sequence.all.Data, "splice-unquote")
then
Err.Check (Elt.Sequence.all.Length = 2,
"splice-unquote expects 1 parameter");
Tmp := Eval (Elt.Sequence.all.Data (2), Env);
Err.Check (Tmp.Kind = Kind_List,
"splice_unquote expects a list");
for Sub_Elt of Tmp.Sequence.all.Data loop
Vector.Append (Sub_Elt);
end loop;
Result := Types.Sequences.List
(((Kind_Symbol, Types.Strings.Alloc ("concat")),
Elt.Sequence.all.Data (2), Result));
else
Vector.Append (Quasiquote (Elt, Env));
Result := Types.Sequences.List
(((Kind_Symbol, Types.Strings.Alloc ("cons")),
Quasiquote (Elt), Result));
end if;
end loop;
-- Now that we know the number of elements, convert to a list.
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;
return Result;
end Qq_Seq;

begin -- Quasiquote
function Starts_With (Sequence : Types.T_Array;
Symbol : String) return Boolean is
(0 < Sequence'Length
and then Sequence (Sequence'First).Kind = Kind_Symbol
and then Sequence (Sequence'First).Str.all = Symbol);

begin
case Ast.Kind is
when Kind_Vector =>
-- When the test is updated, replace Kind_List with Kind_Vector.
return Quasiquote_List (Ast.Sequence.all.Data);
when Kind_List =>
if 0 < Ast.Sequence.all.Length
and then Ast.Sequence.all.Data (1).Kind = Kind_Symbol
and then Ast.Sequence.all.Data (1).Str.all = "unquote"
then
if Starts_With (Ast.Sequence.all.Data, "unquote") then
Err.Check (Ast.Sequence.all.Length = 2, "expected 1 parameter");
return Eval (Ast.Sequence.all.Data (2), Env);
return Ast.Sequence.all.Data (2);
else
return Quasiquote_List (Ast.Sequence.all.Data);
return Qq_Seq;
end if;
when Kind_Vector =>
return Types.Sequences.List
(((Kind_Symbol, Types.Strings.Alloc ("vec")), Qq_Seq));
when Kind_Map | Kind_Symbol =>
return Types.Sequences.List
(((Kind_Symbol, Types.Strings.Alloc ("quote")), Ast));
when others =>
return Ast;
end case;
Expand Down
Loading

0 comments on commit fbfe678

Please sign in to comment.