1
1
mirror of https://github.com/kanaka/mal.git synced 2024-10-27 14:52:16 +03:00
mal/impls/ps/step9_try.ps
Nicolas Boulenguez fbfe6784d2 Change quasiquote algorithm
- 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
2020-08-11 01:01:56 +02:00

310 lines
9.1 KiB
PostScript

/runlibfile where { pop }{ /runlibfile { run } def } ifelse %
(types.ps) runlibfile
(reader.ps) runlibfile
(printer.ps) runlibfile
(env.ps) runlibfile
(core.ps) runlibfile
% read
/_readline { print flush (%stdin) (r) file 1024 string readline } def
/READ {
/str exch def
str read_str
} def
% eval
% sym ast -> starts_with -> bool
/starts_with {
dup _list? {
0 _nth
eq
}{
pop pop false
} ifelse
} def
% ast -> quasiquote -> new_ast
/quasiquote { 3 dict begin
/ast exch def
ast _sequential? not {
ast _symbol? ast _hash_map? or {
/quote ast 2 _list
}{
ast
} ifelse
}{
/unquote ast starts_with {
ast 1 _nth
}{
/res 0 _list def
ast /data get aload length { % reverse traversal
/elt exch def
/res
/splice-unquote elt starts_with {
/concat
elt 1 _nth
}{
/cons
elt quasiquote
} ifelse
res
3 _list
def
} repeat
ast _list? {
res
}{
/vec res 2 _list
} ifelse
} ifelse
} ifelse
end } def
/is_macro_call? { 3 dict begin
/env exch def
/ast exch def
ast _list? {
/a0 ast 0 _nth def
a0 _symbol? { %if a0 is symbol
env a0 env_find null ne { %if a0 is in env
env a0 env_get _mal_function? { %if user defined function
env a0 env_get /macro? get true eq %if marked as macro
}{ false } ifelse
}{ false } ifelse
}{ false } ifelse
}{ false } ifelse
end } def
/macroexpand { 3 dict begin
/env exch def
/ast exch def
{
ast env is_macro_call? {
/mac env ast 0 _nth env_get def
/ast ast _rest mac fload EVAL def
}{
exit
} ifelse
} loop
ast
end } def
/eval_ast { 2 dict begin
/env exch def
/ast exch def
%(eval_ast: ) print ast ==
ast _symbol? { %if symbol
env ast env_get
}{ ast _sequential? { %elseif list or vector
[
ast /data get { %forall items
env EVAL
} forall
] ast _list? { _list_from_array }{ _vector_from_array } ifelse
}{ ast _hash_map? { %elseif list or vector
<<
ast /data get { %forall entries
env EVAL
} forall
>> _hash_map_from_dict
}{ % else
ast
} ifelse } ifelse } ifelse
end } def
/EVAL { 13 dict begin
{ %loop (TCO)
/env exch def
/ast exch def
/loop? false def
%(EVAL: ) print ast true _pr_str print (\n) print
ast _list? not { %if not a list
ast env eval_ast
}{ %else apply the list
/ast ast env macroexpand def
ast _list? not { %if no longer a list
ast env eval_ast
}{ %else still a list
/a0 ast 0 _nth def
a0 _nil? { %if ()
ast
}{ /def! a0 eq { %if def!
/a1 ast 1 _nth def
/a2 ast 2 _nth def
env a1 a2 env EVAL env_set
}{ /let* a0 eq { %if let*
/a1 ast 1 _nth def
/a2 ast 2 _nth def
/let_env env null null env_new def
0 2 a1 _count 1 sub { %for each pair
/idx exch def
let_env
a1 idx _nth
a1 idx 1 add _nth let_env EVAL
env_set
pop % discard the return value
} for
a2
let_env
/loop? true def % loop
}{ /quote a0 eq { %if quote
ast 1 _nth
}{ /quasiquoteexpand a0 eq {%if quasiquoteexpand
ast 1 _nth quasiquote
}{ /quasiquote a0 eq { %if quasiquote
ast 1 _nth quasiquote
env
/loop? true def % loop
}{ /defmacro! a0 eq { %if defmacro!
/a1 ast 1 _nth def
/a2 ast 2 _nth def
a2 env EVAL
dup /macro? true put % set macro flag
env exch a1 exch env_set % def! it
}{ /macroexpand a0 eq { %if defmacro!
ast 1 _nth env macroexpand
}{ /do a0 eq { %if do
ast _count 2 gt { %if ast has more than 2 elements
ast 1 ast _count 2 sub _slice env eval_ast pop
} if
ast ast _count 1 sub _nth % last ast becomes new ast
env
/loop? true def % loop
}{ /try* a0 eq { %if try*
ast _count 2 gt { %if has catch* block
{ %try
2 dict begin % special dict for dict stack count
countdictstack /dictcnt exch def
count /stackcnt exch def
ast 1 _nth env EVAL
end
} stopped { %catch
% clean up the dictionary stack
1 1 countdictstack dictcnt sub { %foreach added dict
%(popping dict\n) print
pop end % pop idx and pop dict
%(new ast: ) print ast true _pr_str print (\n) print
} for
% clean up the operand stack
count 1 exch 1 exch stackcnt sub { %foreach added operand
%(op stack: ) print pstack
pop pop % pop idx and operand
%(popped op stack\n) print pstack
} for
end % remove special dict
% get error data and reset $error dict
/errdata get_error_data def
$error /newerror false put
$error /errorinfo null put
ast _count 3 lt { %if no third (catch*) form
errdata throw
} if
ast 2 _nth 0 _nth (catch*) eq not { %if third form not catch*
(No catch* in throw form) _throw
} if
ast 2 _nth 2 _nth
env
ast 2 _nth 1 _nth 1 _list
errdata 1 _list
env_new
EVAL
} if
}{ % else no catch* block
ast 1 _nth env EVAL
} ifelse
}{ /if a0 eq { %if if
/a1 ast 1 _nth def
/cond a1 env EVAL def
cond null eq cond false eq or { % if cond is nil or false
ast _count 3 gt { %if false branch with a3
ast 3 _nth env
/loop? true def
}{ % else false branch with no a3
null
} ifelse
}{ % true branch
ast 2 _nth env
/loop? true def
} ifelse
}{ /fn* a0 eq { %if fn*
/a1 ast 1 _nth def
/a2 ast 2 _nth def
a2 env a1 _mal_function
}{
/el ast env eval_ast def
el _rest el _first % stack: ast function
dup _mal_function? { %if user defined function
fload % stack: ast new_env
/loop? true def
}{ dup _function? { %else if builtin function
/data get exec
}{ %else (regular procedure/function)
(cannot apply native proc!\n) print quit
} ifelse } ifelse
} ifelse } ifelse } ifelse } ifelse } ifelse } ifelse } ifelse } ifelse } ifelse } ifelse } ifelse } ifelse
} ifelse
} ifelse
loop? not { exit } if
} loop % TCO
end } def
% print
/PRINT {
true _pr_str
} def
% repl
/repl_env null null null env_new def
/RE { READ repl_env EVAL } def
/REP { READ repl_env EVAL PRINT } def
% core.ps: defined using postscript
/_ref { repl_env 3 1 roll env_set pop } def
core_ns { _function _ref } forall
(eval) { 0 _nth repl_env EVAL } _function _ref
(*ARGV*) [ ] _list_from_array _ref
% core.mal: defined using the language itself
(\(def! not \(fn* \(a\) \(if a false true\)\)\)) RE pop
(\(def! load-file \(fn* \(f\) \(eval \(read-string \(str "\(do " \(slurp f\) "\nnil\)"\)\)\)\)\)) RE pop
(\(defmacro! cond \(fn* \(& xs\) \(if \(> \(count xs\) 0\) \(list 'if \(first xs\) \(if \(> \(count xs\) 1\) \(nth xs 1\) \(throw "odd number of forms to cond"\)\) \(cons 'cond \(rest \(rest xs\)\)\)\)\)\)\)) RE pop
userdict /ARGUMENTS known { %if command line arguments
ARGUMENTS length 0 gt { %if more than 0 arguments
(*ARGV*) ARGUMENTS 1 ARGUMENTS length 1 sub getinterval
_list_from_array _ref
ARGUMENTS 0 get
(\(load-file ") exch ("\)) concatenate concatenate RE pop
quit
} if
} if
% repl loop
{ %loop
(user> ) _readline
not { exit } if % exit if EOF
{ %try
REP print (\n) print
} stopped {
(Error: ) print
get_error_data false _pr_str print (\n) print
$error /newerror false put
$error /errorinfo null put
clear
cleardictstack
} if
} bind loop
(\n) print % final newline before exit for cleanliness
quit