2 with Ada
.Containers
.Vectors
;
3 with Ada
.Environment_Variables
;
4 with Ada
.Text_IO
.Unbounded_IO
;
9 with Garbage_Collected
;
19 procedure Step8_Macros
is
21 Dbgeval
: constant Boolean := Ada
.Environment_Variables
.Exists
("dbgeval");
24 use all type Types
.Kind_Type
;
25 use type Types
.Strings
.Instance
;
26 package ACL
renames Ada
.Command_Line
;
27 package Vectors
is new Ada
.Containers
.Vectors
(Positive, Types
.T
);
29 function Read
return Types
.T_Array
with Inline
;
31 function Eval
(Ast0
: in Types
.T
;
32 Env0
: in Envs
.Ptr
) return Types
.T
;
33 function Eval_Builtin
(Args
: in Types
.T_Array
) return Types
.T
;
34 -- The built-in variant needs to see the Repl variable.
36 function Quasiquote
(Ast
: in Types
.T
;
37 Env
: in Envs
.Ptr
) return Types
.T
;
38 -- Mergeing quote and quasiquote into eval with a flag triggering
39 -- a different behaviour as done for macros in step8 would improve
40 -- the performances significantly, but Kanaka finds that it breaks
41 -- too much the step structure shared by all implementations.
43 procedure Print
(Ast
: in Types
.T
) with Inline
;
45 procedure Rep
(Env
: in Envs
.Ptr
) with Inline
;
47 function Eval_Map
(Source
: in Types
.Maps
.Instance
;
48 Env
: in Envs
.Ptr
) return Types
.T
;
49 function Eval_Vector
(Source
: in Types
.Sequences
.Instance
;
50 Env
: in Envs
.Ptr
) return Types
.T
;
51 -- Helpers for the Eval function.
53 procedure Exec
(Script
: in String;
54 Env
: in Envs
.Ptr
) with Inline
;
55 -- Read the script, eval its elements, but ignore the result.
57 ----------------------------------------------------------------------
59 function Eval
(Ast0
: in Types
.T
;
60 Env0
: in Envs
.Ptr
) return Types
.T
62 -- Use local variables, that can be rewritten when tail call
63 -- optimization goes to <<Restart>>.
64 Ast
: Types
.T
:= Ast0
;
65 Env
: Envs
.Ptr
:= Env0
;
66 Env_Reusable
: Boolean := False;
67 -- True when the environment has been created in this recursion
68 -- level, and has not yet been referenced by a closure. If so,
69 -- we can reuse it instead of creating a subenvironment.
70 Macroexpanding
: Boolean := False;
76 Ada
.Text_IO
.Put
("EVAL: ");
78 Envs
.Dump_Stack
(Env
.all);
82 when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Types
.Kind_Key
83 | Kind_Macro | Types
.Kind_Function
=>
86 return Env
.all.Get
(Ast
.Str
);
88 return Eval_Map
(Ast
.Map
.all, Env
);
90 return Eval_Vector
(Ast
.Sequence
.all, Env
);
96 if Ast
.Sequence
.all.Length
= 0 then
99 First
:= Ast
.Sequence
.all.Data
(1);
102 -- Ast is a non-empty list, First is its first element.
105 if First
.Str
.all = "if" then
106 Err
.Check
(Ast
.Sequence
.all.Length
in 3 .. 4,
107 "expected 2 or 3 parameters");
109 Tst
: constant Types
.T
:= Eval
(Ast
.Sequence
.all.Data
(2), Env
);
111 if Tst
/= Types
.Nil
and Tst
/= (Kind_Boolean
, False) then
112 Ast
:= Ast
.Sequence
.all.Data
(3);
114 elsif Ast
.Sequence
.all.Length
= 3 then
117 Ast
:= Ast
.Sequence
.all.Data
(4);
121 elsif First
.Str
.all = "let*" then
122 Err
.Check
(Ast
.Sequence
.all.Length
= 3
123 and then Ast
.Sequence
.all.Data
(2).Kind
in Types
.Kind_Sequence
,
124 "expected a sequence then a value");
126 Bindings
: Types
.T_Array
127 renames Ast
.Sequence
.all.Data
(2).Sequence
.all.Data
;
129 Err
.Check
(Bindings
'Length mod 2 = 0, "expected even binds");
130 if not Env_Reusable
then
131 Env
:= Envs
.New_Env
(Outer
=> Env
);
132 Env_Reusable
:= True;
134 for I
in 0 .. Bindings
'Length / 2 - 1 loop
135 Env
.all.Set
(Bindings
(Bindings
'First + 2 * I
),
136 Eval
(Bindings
(Bindings
'First + 2 * I
+ 1), Env
));
137 -- This call checks key kind.
139 Ast
:= Ast
.Sequence
.all.Data
(3);
142 elsif First
.Str
.all = "quote" then
143 Err
.Check
(Ast
.Sequence
.all.Length
= 2, "expected 1 parameter");
144 return Ast
.Sequence
.all.Data
(2);
145 elsif First
.Str
.all = "def!" then
146 Err
.Check
(Ast
.Sequence
.all.Length
= 3, "expected 2 parameters");
148 Key
: Types
.T
renames Ast
.Sequence
.all.Data
(2);
149 Val
: constant Types
.T
:= Eval
(Ast
.Sequence
.all.Data
(3), Env
);
151 Env
.all.Set
(Key
, Val
); -- Check key kind.
154 elsif First
.Str
.all = "defmacro!" then
155 Err
.Check
(Ast
.Sequence
.all.Length
= 3, "expected 2 parameters");
157 Key
: Types
.T
renames Ast
.Sequence
.all.Data
(2);
158 Fun
: constant Types
.T
:= Eval
(Ast
.Sequence
.all.Data
(3), Env
);
161 Err
.Check
(Fun
.Kind
= Kind_Fn
, "expected a function");
162 Val
:= Types
.Macros
.New_Macro
(Fun
.Fn
.all);
163 Env
.all.Set
(Key
, Val
); -- Check key kind.
166 -- do is a built-in function, shortening this test cascade.
167 elsif First
.Str
.all = "fn*" then
168 Err
.Check
(Ast
.Sequence
.all.Length
= 3, "expected 2 parameters");
170 Params
: Types
.T
renames Ast
.Sequence
.all.Data
(2);
172 Err
.Check
(Params
.Kind
in Types
.Kind_Sequence
,
173 "first argument of fn* must be a sequence");
174 Env_Reusable
:= False;
175 return Types
.Fns
.New_Function
176 (Params
=> Params
.Sequence
,
177 Ast
=> Ast
.Sequence
.all.Data
(3),
180 elsif First
.Str
.all = "macroexpand" then
181 Err
.Check
(Ast
.Sequence
.all.Length
= 2, "expected 1 parameter");
182 Macroexpanding
:= True;
183 Ast
:= Ast
.Sequence
.all.Data
(2);
185 elsif First
.Str
.all = "quasiquote" then
186 Err
.Check
(Ast
.Sequence
.all.Length
= 2, "expected 1 parameter");
187 return Quasiquote
(Ast
.Sequence
.all.Data
(2), Env
);
189 -- Equivalent to First := Eval (First, Env)
190 -- except that we already know enough to spare a recursive call.
191 First
:= Env
.all.Get
(First
.Str
);
193 when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Types
.Kind_Key
194 | Kind_Macro | Types
.Kind_Function
=>
195 -- Equivalent to First := Eval (First, Env)
196 -- except that we already know enough to spare a recursive call.
198 when Types
.Kind_Sequence | Kind_Map
=>
199 -- Lists are definitely worth a recursion, and the two other
200 -- cases should be rare (they will report an error later).
201 First
:= Eval
(First
, Env
);
205 -- Ast is a non-empty list,
206 -- First is its non-special evaluated first element.
209 -- Use the unevaluated arguments.
210 if Macroexpanding
then
211 -- Evaluate the macro with tail call optimization.
212 if not Env_Reusable
then
213 Env
:= Envs
.New_Env
(Outer
=> Env
);
214 Env_Reusable
:= True;
217 (Binds
=> First
.Macro
.all.Params
.all.Data
,
218 Exprs
=> Ast
.Sequence
.all.Data
(2 .. Ast
.Sequence
.all.Length
));
219 Ast
:= First
.Macro
.all.Ast
;
222 -- Evaluate the macro normally.
224 New_Env
: constant Envs
.Ptr
:= Envs
.New_Env
(Outer
=> Env
);
226 New_Env
.all.Set_Binds
227 (Binds
=> First
.Macro
.all.Params
.all.Data
,
228 Exprs
=> Ast
.Sequence
.all.Data
229 (2 .. Ast
.Sequence
.all.Length
));
230 Ast
:= Eval
(First
.Macro
.all.Ast
, New_Env
);
231 -- Then evaluate the result with TCO.
235 when Types
.Kind_Function
=>
238 Err
.Raise_With
("first element must be a function or macro");
240 -- We are applying a function. Evaluate its arguments.
242 Args
: Types
.T_Array
(2 .. Ast
.Sequence
.all.Length
);
244 for I
in Args
'Range loop
245 Args
(I
) := Eval
(Ast
.Sequence
.all.Data
(I
), Env
);
247 if First
.Kind
= Kind_Builtin
then
248 return First
.Builtin
.all (Args
);
250 -- Like Types.Fns.Apply, except that we use TCO.
251 Env
:= Envs
.New_Env
(Outer
=> First
.Fn
.all.Env
);
252 Env_Reusable
:= True;
253 Env
.all.Set_Binds
(Binds
=> First
.Fn
.all.Params
.all.Data
,
255 Ast
:= First
.Fn
.all.Ast
;
260 if Macroexpanding
then
261 Err
.Add_Trace_Line
("macroexpand", Ast
);
263 Err
.Add_Trace_Line
("eval", Ast
);
268 function Eval_Map
(Source
: in Types
.Maps
.Instance
;
269 Env
: in Envs
.Ptr
) return Types
.T
271 use all type Types
.Maps
.Cursor
;
272 -- Copy the whole map so that keys are not hashed again.
273 Result
: constant Types
.T
:= Types
.Maps
.New_Map
(Source
);
274 Position
: Types
.Maps
.Cursor
:= Result
.Map
.all.First
;
276 while Has_Element
(Position
) loop
277 Result
.Map
.all.Replace_Element
(Position
,
278 Eval
(Element
(Position
), Env
));
284 function Eval_Vector
(Source
: in Types
.Sequences
.Instance
;
285 Env
: in Envs
.Ptr
) return Types
.T
287 Ref
: constant Types
.Sequence_Ptr
288 := Types
.Sequences
.Constructor
(Source
.Length
);
290 for I
in Source
.Data
'Range loop
291 Ref
.all.Data
(I
) := Eval
(Source
.Data
(I
), Env
);
293 return (Kind_Vector
, Ref
);
296 procedure Exec
(Script
: in String;
301 for Expression
of Reader
.Read_Str
(Script
) loop
302 Result
:= Eval
(Expression
, Env
);
304 pragma Unreferenced
(Result
);
307 procedure Print
(Ast
: in Types
.T
) is
309 Ada
.Text_IO
.Unbounded_IO
.Put_Line
(Printer
.Pr_Str
(Ast
));
312 function Quasiquote
(Ast
: in Types
.T
;
313 Env
: in Envs
.Ptr
) return Types
.T
316 function Quasiquote_List
(List
: in Types
.T_Array
) return Types
.T
;
317 -- Handle vectors and lists not starting with unquote.
319 function Quasiquote_List
(List
: in Types
.T_Array
) return Types
.T
is
320 Vector
: Vectors
.Vector
; -- buffer for concatenation
324 if Elt
.Kind
in Kind_List
325 and then 0 < Elt
.Sequence
.all.Length
326 and then Elt
.Sequence
.all.Data
(1).Kind
= Kind_Symbol
327 and then Elt
.Sequence
.all.Data
(1).Str
.all = "splice-unquote"
329 Err
.Check
(Elt
.Sequence
.all.Length
= 2,
330 "splice-unquote expects 1 parameter");
331 Tmp
:= Eval
(Elt
.Sequence
.all.Data
(2), Env
);
332 Err
.Check
(Tmp
.Kind
= Kind_List
,
333 "splice_unquote expects a list");
334 for Sub_Elt
of Tmp
.Sequence
.all.Data
loop
335 Vector
.Append
(Sub_Elt
);
338 Vector
.Append
(Quasiquote
(Elt
, Env
));
341 -- Now that we know the number of elements, convert to a list.
343 Sequence
: constant Types
.Sequence_Ptr
344 := Types
.Sequences
.Constructor
(Natural (Vector
.Length
));
346 for I
in 1 .. Natural (Vector
.Length
) loop
347 Sequence
.all.Data
(I
) := Vector
(I
);
349 return (Kind_List
, Sequence
);
356 -- When the test is updated, replace Kind_List with Kind_Vector.
357 return Quasiquote_List
(Ast
.Sequence
.all.Data
);
359 if 0 < Ast
.Sequence
.all.Length
360 and then Ast
.Sequence
.all.Data
(1).Kind
= Kind_Symbol
361 and then Ast
.Sequence
.all.Data
(1).Str
.all = "unquote"
363 Err
.Check
(Ast
.Sequence
.all.Length
= 2, "expected 1 parameter");
364 return Eval
(Ast
.Sequence
.all.Data
(2), Env
);
366 return Quasiquote_List
(Ast
.Sequence
.all.Data
);
373 Err
.Add_Trace_Line
("quasiquote", Ast
);
377 function Read
return Types
.T_Array
378 is (Reader
.Read_Str
(Readline
.Input
("user> ")));
380 procedure Rep
(Env
: in Envs
.Ptr
) is
382 for Expression
of Read
loop
383 Print
(Eval
(Expression
, Env
));
387 ----------------------------------------------------------------------
389 Startup
: constant String
390 := "(def! not (fn* (a) (if a false true)))"
391 & "(def! load-file (fn* (f)"
392 & " (eval (read-string (str ""(do "" (slurp f) "")"")))))"
393 & "(defmacro! cond (fn* (& xs)"
394 & " (if (> (count xs) 0)"
395 & " (list 'if (first xs)"
396 & " (if (> (count xs) 1) (nth xs 1)"
397 & " (throw ""odd number of forms to cond""))"
398 & " (cons 'cond (rest (rest xs)))))))"
399 & "(defmacro! or (fn* (& xs)"
400 & " (if (empty? xs) nil"
401 & " (if (= 1 (count xs)) (first xs)"
402 & " `(let* (or_FIXME ~(first xs))"
403 & " (if or_FIXME or_FIXME (or ~@(rest xs))))))))";
404 Repl
: constant Envs
.Ptr
:= Envs
.New_Env
;
405 function Eval_Builtin
(Args
: in Types
.T_Array
) return Types
.T
is
407 Err
.Check
(Args
'Length = 1, "expected 1 parameter");
408 return Eval
(Args
(Args
'First), Repl
);
410 Script
: constant Boolean := 0 < ACL
.Argument_Count
;
411 Argv
: constant Types
.Sequence_Ptr
412 := Types
.Sequences
.Constructor
(Integer'Max (0, ACL
.Argument_Count
- 1));
414 -- Show the Eval function to other packages.
415 Types
.Fns
.Eval_Cb
:= Eval
'Unrestricted_Access;
416 -- Add Core functions into the top environment.
417 Core
.NS_Add_To_Repl
(Repl
);
418 Repl
.all.Set
((Kind_Symbol
, Types
.Strings
.Alloc
("eval")),
419 (Kind_Builtin
, Eval_Builtin
'Unrestricted_Access));
420 -- Native startup procedure.
421 Exec
(Startup
, Repl
);
422 -- Define ARGV from command line arguments.
423 for I
in 2 .. ACL
.Argument_Count
loop
424 Argv
.all.Data
(I
- 1) := (Kind_String
,
425 Types
.Strings
.Alloc
(ACL
.Argument
(I
)));
427 Repl
.all.Set
((Kind_Symbol
, Types
.Strings
.Alloc
("*ARGV*")),
429 -- Execute user commands.
431 Exec
("(load-file """ & ACL
.Argument
(1) & """)", Repl
);
437 when Readline
.End_Of_File
=>
440 Ada
.Text_IO
.Unbounded_IO
.Put
(Err
.Trace
);
442 -- Other exceptions are really unexpected.
445 Err
.Data
:= Types
.Nil
;
447 Garbage_Collected
.Clean
;
449 Ada
.Text_IO
.New_Line
;
452 -- If assertions are enabled, check deallocations.
453 -- Normal runs do not need to deallocate before termination.
454 -- Beware that all pointers are now dangling.
455 pragma Debug
(Garbage_Collected
.Clean
);
456 Garbage_Collected
.Check_Allocations
;