Make-A-Lisp Cheatsheet

Step 1 Step 6
reader.EXT:
  Reader(tokens) object: position, next(), peek()
  tokenize:  /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)/
  read_atom: int, float, string (escaped), keyword, nil, true, false, symbol
  read_list: repeatedly read_form until end token (EOF is error)
  read_form: expand reader macros, read_list (vector/maps too), or read_atom
  read_str:  tokenize, error if no tokens, call read_form(Reader(tokens))
printer.EXT:
  pr_str(ast, print_readably):
    - map pr_str across collections
    - unescape strings if print_readably
step1_read_print.EXT:
  main(args): loop: writeline PRINT(EVAL(READ(readline()), ""))
core.EXT:
  read-string: call reader.read_str
  slurp: return file content as a string
  atom, atom?, deref, reset!, swap!: atom functions
step6_file.EXT:
  main(args):
    - add eval and *ARGV* to repl_env
    - define load-file using rep
    - if args, set *ARGV* to rest(args) and call load-file with args[0]




   
Step 2 Step 7
step2_eval.EXT:
  eval_ast(ast, env): lookup symbols in env, map EVAL across collections
  EVAL(ast, env):
    - if not list?(ast), return eval_ast(ast, env)
    - otherwise apply (ast is a list):
      el = eval_ast(ast, env)
      return el[0](rest(el))
  main(args): loop: writeline PRINT(EVAL(READ(readline()), {+: add, ...}))



core.EXT:
  cons, concat: sequence functions
step7_quote.EXT:
  quasiquote(ast):
    - ast is empty or not a list   -> (quote ast)
    - (unquote FOO)                -> FOO
    - ((splice-unquote FOO) BAR..) -> (concat FOO quasiquote(BAR...))
    - (FOO BAR...)                 -> (cons FOO quasiquote(BAR...))
  EVAL(ast, env):
    - quote      -> return ast[1]
    - quasiquote -> set ast to quasiquote(ast[1]), loop
   
Step 3 Step 8
env.EXT:
  Env(outer) object: data, set(k, v), find(k), get(k)
step3_env.EXT:
  eval_ast(ast, env): switch to env.get for symbol lookup
  EVAL(ast, env):
    - def!  -> return env.set(ast[1], EVAL(ast[2], env))
    - let*  -> create new env let_env
               for each ODD/EVEN pair in ast[1]:
                 let_env.set(ODD, EVAL(EVEN, let_env))
               return EVAL(ast[2], let_env)
  main(args): populate repl_env with numeric functions using repl_env.set
core.EXT:
  nth, first, rest: sequence functions
step8_macros.EXT:
  macroexpand(ast, env):
    - while env.get(ast[0]) is a macro: ast = env.get(ast[0])(rest(ast))
  EVAL(ast, env):
    - before apply section, add ast = macroexpand(ast, env)
    - defmacro!   -> same as def!, but set mal function macro flag
    - macroexpand -> return macroexpand(ast[1], env)


   
Step 4 Step 9
env.EXT:
  Env(outer, binds, exprs) object: map binds to exprs, handle "&" as variadic
core.EXT:
  =: recursive compare of collections
  pr-str, str: return pr_str(arg, true) join " ", pr_str(arg, false) join ""
  prn, println: print pr_str(arg, true) join "", pr_str(arg, false) join ""
  <, <=, >, >=, +, -, *, /: numeric comparison and numeric operations
  list, list?, empty?, count: sequence functions
step4_do_if_fn.EXT:
  EVAL(ast, env):
    - do  -> return last element of eval_ast(ast, env)
    - if  -> if EVAL(ast[1], env): return EVAL(ast[2], env)
             else                : return EVAL(ast[3], env)
    - fn* -> return closure:
               (args) -> EVAL(ast[2], new Env(env, ast[1], args))
  main(args): populate repl_env with core functions, define not using rep()
core.EXT:
  throw: raise mal value as exception (maybe wrap in native exception)
  vector, vector?: sequence functions
  hash-map, get, contains?, keys, vals: hash-map functions
  assoc, dissoc: immutable hash-map transform functions
  apply(f, args..., last): return f(concat(args, last))
  map(f, args): return list of mapping f on each args
step9_try.EXT:
  EVAL(ast, env):
    - try* -> try EVAL(ast[1], env)
                catch exception exc (unwrap if necessary):
                  new err_env with ast[2][1] symbol bound to exc
                  EVAL(ast[2][2], err_env)



   
Step 5 Step A
step5_tco.EXT:
  EVAL(ast, env):
    - top level loop in EVAL
    - let*  -> set env to let_env, set ast to ast[2], loop
    - do    -> eval_ast of middle elements, sets ast to last element, loop
    - if    -> set ast to ast[2] or ast[3] (or nil) depending condition, loop
    - fn*   -> return new mal function type f with:
                f.ast=ast[2], f.params=ast[1], f.env=env
    - apply -> el = eval_ast(ast, env)
               f = el[0]
               if f is a mal function: ast = f.ast and env = f.env, loop
               else                  : return el[0](rest(el))

core.EXT:
  string?: true if string
  readline: prompt and read a line of input (synchronous)
  time-ms: return milliseconds since epoch (1970-1-1)
  conj, seq: type specific sequence functions
  meta, with-meta: metadata functions
step9_try.EXT:
  EVAL(ast, env):
    - set *host-language* in repl_env to host language name
  main(args): rep("(println (str \"Mal [\" *host-language* \"]\"))")