Right now we have a fully functional (in the general sense, not the math/CS sense) Lisp, ready-to-go. Where could we possibly take it from here?
Well for one, I’d like to replace the relatively crappy hacked-together parser with something nicer, like a parser generator. This post certainly won’t do that, or even explain what that means, but it will pave the way toward swappable parsers.
Since all of our code right now is rather intertwined with a complicated dependency graph, there’s no simple way to, say, drop in a replacement parser. If we were to separate out all the responsibilities and concerns into their separate modules, this problem would be significantly reduced.
Without modifying much code (really only function names, etc), I created the following modules:
module Env : sig type 'a env = (string * 'a option ref) list exception NotFound of string exception UnspecifiedValue of string val mkloc : 'a -> 'b option ref val bind : string * 'a * 'a env -> 'a env val bindloc : string * 'a option ref * 'a env -> 'a env val bindlist : string list -> 'a list -> 'a env -> 'a env val bindloclist : string list -> 'a option ref list -> 'a env -> 'a env val lookup : string * 'a env -> 'a val extend : 'a env -> 'a env -> 'a env end = struct (* ... *) end module Ast = struct (* ... *) end module PushbackReader : sig type 'a t val of_string : string -> char t val of_channel : in_channel -> char t val do_stdin : 'a t -> 'b -> ('b -> unit) -> unit val read_char : char t -> char val unread_char : char t -> char -> char t end = struct (* ... *) end module type READER = sig val read_exp : char PushbackReader.t -> Ast.exp end module type EVALUATOR = sig val eval : Ast.exp -> Ast.value Env.env -> Ast.value * Ast.value Env.env end module Reader : READER = struct (* ... *) end module Eval : EVALUATOR = struct (* ... *) end
The only “big” change I made was to bring AST building into the Reader
module, and change the name of
read_exp — because now it
really reads expressions.
For people not super familiar with OCaml modules, let’s chat about these modules a bit before continuing.
Modules are OCaml’s way of separating functionality into reusable containers. Most modules contain a data type and a set of functions for operating on that data type.
Modules by default expose all that they contain to the outside world. If you
would like to constrain what users of the modules can see (so that they need
not concern themselves with helper functions, etc), you can provide an
interface to the module. In our case, we’re doing that with
I have deliberately restricted the available functionality of
Evaluator to the
module types (interfaces)
respectively. This enables us to swap out those modules with any other
modules so long as they conform to the right interfaces.
Download the code here if you want to mess with it.
In the next chapter, I
plan on replacing the reader with a
much better-designed lexer/parser. An auto-generated one, even. do some
syntax transforms to remove