2 with Ada
.Containers
.Vectors
;
3 with Ada
.Environment_Variables
;
4 with Ada
.Strings
.Unbounded
;
5 with Ada
.Text_IO
.Unbounded_IO
;
11 with Garbage_Collected
;
20 with Types
.Symbols
.Names
;
22 procedure StepA_Mal
is
24 Dbgeval
: constant Boolean := Ada
.Environment_Variables
.Exists
("dbgeval");
28 package ACL
renames Ada
.Command_Line
;
29 package ASU
renames Ada
.Strings
.Unbounded
;
31 function Read
return Mal
.T_Array
with Inline
;
33 function Eval
(Ast0
: in Mal
.T
;
34 Env0
: in Envs
.Ptr
) return Mal
.T
;
35 function Eval_Builtin
(Args
: in Mal
.T_Array
) return Mal
.T
;
36 -- The built-in variant needs to see the Repl variable.
38 function Quasiquote
(Ast
: in Mal
.T
;
39 Env
: in Envs
.Ptr
) return Mal
.T
;
40 -- Mergeing quote and quasiquote into eval with a flag triggering
41 -- a different behaviour as done for macros in step8 would improve
42 -- the performances significantly, but Kanaka finds that it breaks
43 -- too much the step structure shared by all implementations.
45 procedure Print
(Ast
: in Mal
.T
) with Inline
;
47 procedure Rep
(Env
: in Envs
.Ptr
) with Inline
;
49 function Eval_Map_Elts
is new Maps
.Generic_Eval
(Envs
.Ptr
, Eval
);
51 procedure Exec
(Script
: in String;
52 Env
: in Envs
.Ptr
) with Inline
;
53 -- Read the script, eval its elements, but ignore the result.
55 ----------------------------------------------------------------------
57 function Eval
(Ast0
: in Mal
.T
;
58 Env0
: in Envs
.Ptr
) return Mal
.T
61 -- Use local variables, that can be rewritten when tail call
62 -- optimization goes to <<Restart>>.
64 Env
: Envs
.Ptr
:= Env0
;
65 Macroexpanding
: Boolean := False;
71 Ada
.Text_IO
.Put
("EVAL: ");
73 Envs
.Dump_Stack
(Env
.all);
77 when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_Key
78 | Kind_Macro | Kind_Function
=>
81 return Env
.all.Get
(Ast
.Symbol
);
83 return Eval_Map_Elts
(Ast
.Map
.all, Env
);
86 Len
: constant Natural := Ast
.Sequence
.all.Length
;
87 List
: constant Mal
.Sequence_Ptr
:= Sequences
.Constructor
(Len
);
89 for I
in 1 .. Len
loop
90 List
.all.Replace_Element
(I
, Eval
(Ast
.Sequence
.all (I
), Env
));
92 return (Kind_Vector
, List
);
99 if Ast
.Sequence
.all.Length
= 0 then
102 First
:= Ast
.Sequence
.all (1);
105 -- Ast is a non-empty list, First is its first element.
108 if First
.Symbol
= Symbols
.Names
.Def
then
109 Err
.Check
(Ast
.Sequence
.all.Length
= 3, "expected 2 parameters");
110 Err
.Check
(Ast
.Sequence
.all (2).Kind
= Kind_Symbol
,
111 "parameter 1 must be a symbol");
112 return R
: constant Mal
.T
:= Eval
(Ast
.Sequence
.all (3), Env
) do
113 Env
.all.Set
(Ast
.Sequence
.all (2).Symbol
, R
);
115 elsif First
.Symbol
= Symbols
.Names
.Defmacro
then
116 Err
.Check
(Ast
.Sequence
.all.Length
= 3, "expected 2 parameters");
117 Err
.Check
(Ast
.Sequence
.all (2).Kind
= Kind_Symbol
,
118 "parameter 1 must be a symbol");
120 F
: constant Mal
.T
:= Eval
(Ast
.Sequence
.all (3), Env
);
122 Err
.Check
(F
.Kind
= Kind_Fn
, "parameter 2 must be a function");
123 return R
: constant Mal
.T
:= F
.Fn
.all.New_Macro
do
124 Env
.all.Set
(Ast
.Sequence
.all (2).Symbol
, R
);
127 -- do is a built-in function, shortening this test cascade.
128 elsif First
.Symbol
= Symbols
.Names
.Fn
then
129 Err
.Check
(Ast
.Sequence
.all.Length
= 3, "expected 2 parameters");
130 Err
.Check
(Ast
.Sequence
.all (2).Kind
in Kind_Sequence
,
131 "parameter 1 must be a sequence");
132 return Fns
.New_Function
133 (Params
=> Ast
.Sequence
.all (2).Sequence
.all,
134 Ast
=> Ast
.Sequence
.all (3),
136 elsif First
.Symbol
= Symbols
.Names
.Mal_If
then
137 Err
.Check
(Ast
.Sequence
.all.Length
in 3 .. 4,
138 "expected 2 or 3 parameters");
140 Test
: constant Mal
.T
:= Eval
(Ast
.Sequence
.all (2), Env
);
142 if Test
/= Mal
.Nil
and Test
/= (Kind_Boolean
, False) then
143 Ast
:= Ast
.Sequence
.all (3);
145 elsif Ast
.Sequence
.all.Length
= 3 then
148 Ast
:= Ast
.Sequence
.all (4);
152 elsif First
.Symbol
= Symbols
.Names
.Let
then
153 Err
.Check
(Ast
.Sequence
.all.Length
= 3, "expected 2 parameters");
154 Err
.Check
(Ast
.Sequence
.all (2).Kind
in Kind_Sequence
,
155 "parameter 1 must be a sequence");
157 Bindings
: constant Mal
.Sequence_Ptr
158 := Ast
.Sequence
.all (2).Sequence
;
160 Err
.Check
(Bindings
.all.Length
mod 2 = 0,
161 "parameter 1 must have an even length");
162 Env
:= Envs
.New_Env
(Outer
=> Env
);
163 for I
in 1 .. Bindings
.all.Length
/ 2 loop
164 Err
.Check
(Bindings
.all (2 * I
- 1).Kind
= Kind_Symbol
,
165 "binding keys must be symbols");
166 Env
.all.Set
(Bindings
.all (2 * I
- 1).Symbol
,
167 Eval
(Bindings
.all (2 * I
), Env
));
169 Ast
:= Ast
.Sequence
.all (3);
172 elsif First
.Symbol
= Symbols
.Names
.Macroexpand
then
173 Err
.Check
(Ast
.Sequence
.all.Length
= 2, "expected 1 parameter");
174 Macroexpanding
:= True;
175 Ast
:= Ast
.Sequence
.all (2);
177 elsif First
.Symbol
= Symbols
.Names
.Quasiquote
then
178 Err
.Check
(Ast
.Sequence
.all.Length
= 2, "expected 1 parameter");
179 return Quasiquote
(Ast
.Sequence
.all (2), Env
);
180 elsif First
.Symbol
= Symbols
.Names
.Quote
then
181 Err
.Check
(Ast
.Sequence
.all.Length
= 2, "expected 1 parameter");
182 return Ast
.Sequence
.all (2);
183 elsif First
.Symbol
= Symbols
.Names
.Try
then
184 if Ast
.Sequence
.all.Length
= 2 then
185 Ast
:= Ast
.Sequence
.all (2);
188 Err
.Check
(Ast
.Sequence
.all.Length
= 3,
189 "expected 1 or 2 parameters");
190 Err
.Check
(Ast
.Sequence
.all (3).Kind
= Kind_List
,
191 "parameter 2 must be a list");
193 A3
: constant Mal
.Sequence_Ptr
:= Ast
.Sequence
.all (3).Sequence
;
195 Err
.Check
(A3
.all.Length
= 3,
196 "length of parameter 2 must be 3");
197 Err
.Check
(A3
.all (1) = (Kind_Symbol
, Symbols
.Names
.Catch
),
198 "parameter 3 must start with 'catch*'");
199 Err
.Check
(A3
.all (2).Kind
= Kind_Symbol
,
200 "a symbol must follow catch*");
202 return Eval
(Ast
.Sequence
.all (2), Env
);
207 Env
:= Envs
.New_Env
(Outer
=> Env
);
208 Env
.all.Set
(A3
.all (2).Symbol
, Err
.Data
);
213 -- Equivalent to First := Eval (First, Env)
214 -- except that we already know enough to spare a recursive call.
215 First
:= Env
.all.Get
(First
.Symbol
);
217 when Kind_Nil | Kind_Atom | Kind_Boolean | Kind_Number | Kind_Key
218 | Kind_Macro | Kind_Function
=>
219 -- Equivalent to First := Eval (First, Env)
220 -- except that we already know enough to spare a recursive call.
222 when Kind_Sequence | Kind_Map
=>
223 -- Lists are definitely worth a recursion, and the two other
224 -- cases should be rare (they will report an error later).
225 First
:= Eval
(First
, Env
);
229 -- Ast is a non-empty list,
230 -- First is its non-special evaluated first element.
234 Args
: Mal
.T_Array
(2 .. Ast
.Sequence
.all.Length
);
236 for I
in Args
'Range loop
237 Args
(I
) := Eval
(Ast
.Sequence
.all (I
), Env
);
239 return First
.Builtin
.all (Args
);
241 when Kind_Builtin_With_Meta
=>
243 Args
: Mal
.T_Array
(2 .. Ast
.Sequence
.all.Length
);
245 for I
in Args
'Range loop
246 Args
(I
) := Eval
(Ast
.Sequence
.all (I
), Env
);
248 return First
.Builtin_With_Meta
.all.Builtin
.all (Args
);
252 Args
: Mal
.T_Array
(2 .. Ast
.Sequence
.all.Length
);
254 for I
in Args
'Range loop
255 Args
(I
) := Eval
(Ast
.Sequence
.all (I
), Env
);
257 Env
:= Envs
.New_Env
(Outer
=> First
.Fn
.all.Env
,
258 Binds
=> First
.Fn
.all.Params
,
260 Ast
:= First
.Fn
.all.Ast
;
265 Args
: constant Mal
.T_Array
266 := Ast
.Sequence
.all.Tail
(Ast
.Sequence
.all.Length
- 1);
268 if Macroexpanding
then
269 -- Evaluate the macro with tail call optimization.
270 Env
:= Envs
.New_Env
(Outer
=> Env
,
271 Binds
=> First
.Fn
.all.Params
,
273 Ast
:= First
.Fn
.all.Ast
;
276 -- Evaluate the macro normally.
277 Ast
:= Eval
(First
.Fn
.all.Ast
,
278 Envs
.New_Env
(Outer
=> Env
,
279 Binds
=> First
.Fn
.all.Params
,
281 -- Then evaluate the result with TCO.
286 Err
.Raise_With
("first element must be a function or macro");
290 if Macroexpanding
then
291 Err
.Add_Trace_Line
("macroexpand", Ast
);
293 Err
.Add_Trace_Line
("eval", Ast
);
298 procedure Exec
(Script
: in String;
303 for Expression
of Reader
.Read_Str
(Script
) loop
304 Result
:= Eval
(Expression
, Env
);
306 pragma Unreferenced
(Result
);
309 procedure Print
(Ast
: in Mal
.T
) is
311 Ada
.Text_IO
.Unbounded_IO
.Put_Line
(Printer
.Pr_Str
(Ast
));
314 function Quasiquote
(Ast
: in Mal
.T
;
315 Env
: in Envs
.Ptr
) return Mal
.T
318 function Quasiquote_List
(List
: in Sequences
.Instance
) return Mal
.T
320 -- Handle vectors and lists not starting with unquote.
322 function Quasiquote_List
(List
: in Sequences
.Instance
) return Mal
.T
is
323 package Vectors
is new Ada
.Containers
.Vectors
(Positive, Mal
.T
);
324 Vector
: Vectors
.Vector
; -- buffer for concatenation
325 Sequence
: Mal
.Sequence_Ptr
;
328 for I
in 1 .. List
.Length
loop
329 if List
(I
).Kind
in Kind_List
330 and then 0 < List
(I
).Sequence
.all.Length
331 and then List
(I
).Sequence
.all (1)
332 = (Kind_Symbol
, Symbols
.Names
.Splice_Unquote
)
334 Err
.Check
(List
(I
).Sequence
.all.Length
= 2,
335 "splice-unquote expects 1 parameter");
336 Tmp
:= Eval
(List
(I
).Sequence
.all (2), Env
);
337 Err
.Check
(Tmp
.Kind
= Kind_List
,
338 "splice_unquote expects a list");
339 for I
in 1 .. Tmp
.Sequence
.all.Length
loop
340 Vector
.Append
(Tmp
.Sequence
.all (I
));
343 Vector
.Append
(Quasiquote
(List
(I
), Env
));
346 -- Now that we know the number of elements, convert to a list.
347 Sequence
:= Sequences
.Constructor
(Natural (Vector
.Length
));
348 for I
in 1 .. Natural (Vector
.Length
) loop
349 Sequence
.Replace_Element
(I
, Vector
(I
));
351 return (Kind_List
, Sequence
);
357 -- When the test is updated, replace Kind_List with Kind_Vector.
358 return Quasiquote_List
(Ast
.Sequence
.all);
360 if 0 < Ast
.Sequence
.all.Length
361 and then Ast
.Sequence
.all (1) = (Kind_Symbol
,
362 Symbols
.Names
.Unquote
)
364 Err
.Check
(Ast
.Sequence
.all.Length
= 2, "expected 1 parameter");
365 return Eval
(Ast
.Sequence
.all (2), Env
);
367 return Quasiquote_List
(Ast
.Sequence
.all);
374 Err
.Add_Trace_Line
("quasiquote", Ast
);
378 function Read
return Mal
.T_Array
379 is (Reader
.Read_Str
(Readline
.Input
("user> ")));
381 procedure Rep
(Env
: in Envs
.Ptr
) is
383 for Expression
of Read
loop
384 Print
(Eval
(Expression
, Env
));
388 ----------------------------------------------------------------------
390 Startup
: constant String
391 := "(def! not (fn* (a) (if a false true)))"
392 & "(def! load-file (fn* (f)"
393 & " (eval (read-string (str ""(do "" (slurp f) "")"")))))"
394 & "(defmacro! cond (fn* (& xs)"
395 & " (if (> (count xs) 0)"
396 & " (list 'if (first xs)"
397 & " (if (> (count xs) 1) (nth xs 1)"
398 & " (throw ""odd number of forms to cond""))"
399 & " (cons 'cond (rest (rest xs)))))))"
400 & "(def! *gensym-counter* (atom 0))"
401 & "(def! gensym (fn* [] "
402 & " (symbol (str ""G__"" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))"
403 & "(defmacro! or (fn* (& xs)"
404 & " (if (empty? xs) nil"
405 & " (if (= 1 (count xs)) (first xs)"
406 & " (let* (condvar (gensym))"
407 & " `(let* (~condvar ~(first xs))"
408 & " (if ~condvar ~condvar (or ~@(rest xs)))))))))"
409 & "(def! *host-language* ""ada.2"")";
410 Repl
: constant Envs
.Ptr
:= Envs
.New_Env
;
411 function Eval_Builtin
(Args
: in Mal
.T_Array
) return Mal
.T
is
413 Err
.Check
(Args
'Length = 1, "expected 1 parameter");
414 return Eval_Cb
.Cb
.all (Args
(Args
'First), Repl
);
416 Script
: constant Boolean := 0 < ACL
.Argument_Count
;
417 Argv
: Mal
.Sequence_Ptr
;
419 -- Show the Eval function to other packages.
420 Eval_Cb
.Cb
:= Eval
'Unrestricted_Access;
421 -- Add Core functions into the top environment.
422 Core
.NS_Add_To_Repl
(Repl
);
423 Repl
.all.Set
(Symbols
.Constructor
("eval"),
424 (Kind_Builtin
, Eval_Builtin
'Unrestricted_Access));
425 -- Native startup procedure.
426 Exec
(Startup
, Repl
);
427 -- Define ARGV from command line arguments.
429 Argv
:= Sequences
.Constructor
(ACL
.Argument_Count
- 1);
430 for I
in 2 .. ACL
.Argument_Count
loop
431 Argv
.all.Replace_Element
432 (I
- 1, (Kind_String
, ASU
.To_Unbounded_String
(ACL
.Argument
(I
))));
435 Argv
:= Sequences
.Constructor
(0);
437 Repl
.all.Set
(Symbols
.Constructor
("*ARGV*"), (Kind_List
, Argv
));
438 -- Execute user commands.
440 Exec
("(load-file """ & ACL
.Argument
(1) & """)", Repl
);
442 Exec
("(println (str ""Mal ["" *host-language* ""]""))", Repl
);
447 when Readline
.End_Of_File
=>
450 Ada
.Text_IO
.Unbounded_IO
.Put
(Err
.Trace
);
452 -- Other exceptions are really unexpected.
457 Garbage_Collected
.Clean
;
459 Ada
.Text_IO
.New_Line
;
462 -- If assertions are enabled, check deallocations.
463 pragma Debug
(Garbage_Collected
.Clean
);
464 Garbage_Collected
.Check_Allocations
;
465 Symbols
.Check_Allocations
;