### A recursive descent parser for Ludus ### First, some mutual recursion helpers (defn unreachable "A function that errors out if called." [&] (error "cannot call recursive function before definition")) (defmacro declare "Forward-declares a function name, so that it can be called in a mutually recursive manner." [& names] (def bindings @[]) (loop [name :in names] (def binding ~(var ,name unreachable)) (array/push bindings binding)) ~(upscope ,;bindings)) (defmacro defrec "Defines a function depended on by another function, that has been forward `declare`d." [name & forms] (if-not (dyn name) (error "recursive functions must be declared before they are defined")) ~(set ,name (defn ,name ,;forms))) ### Next: a data structure for a parser (defn- new-parser [tokens] @{ :tokens (tokens :tokens) :ast @[] :current 0 :errors @[] }) ### and some helper functions for interfacing with that data structure (defn- current [parser] (get (parser :tokens) (parser :current))) (defn- peek [parser] (get (parser :tokens) (inc (parser :current)))) (defn- advance [parser] (update parser :current inc)) (defn- type [token] (get token :type)) (defn- expect [parser type] (def current-type (-> parser current (get :type))) (= type current-type)) ### Parsing functions (declare expr) # atoms (defn- bool [parser] (def curr (-> parser current)) (def ttype (type curr)) (def value (if (= ttype :true) true false)) (def ast {:type :bool :value value :token curr}) (update parser :ast array/push ast) (advance parser)) (defn- num [parser] (def curr (-> parser current)) (def ast {:type :number :value (curr :literal) :token curr}) (update parser :ast array/push ast) (advance parser)) (defn- kw [parser] (def curr (-> parser current)) (def ast {:type :keyword :value (curr :literal) :token curr}) (update parser :ast array/push ast) (advance parser)) (defn- nill [parser] (def ast {:type :nil :token (current parser)}) (update parser :ast array/push ast) (advance parser)) (defn- str [parser] (def curr (-> parser current)) (def ast {:type :string :value (curr :literal) :token curr}) (update parser :ast array/push ast) (advance parser)) (defrec expr [parser] (def curr (current parser)) (case (type curr) # atoms :nil (nill parser) :true (bool parser) :false (bool parser) :number (num parser) :keyword (kw parser) # strings :string (str parser) ### TODO: interpolated strings ) ) (do #(comment (os/cd "janet") # For repl to do relative imports (import ./scanner :as s) (def scanned (s/scan "\"foo bar baz\"")) (def a-parser (new-parser scanned)) (def parsed (expr a-parser)) (-> parsed (get :ast) (get 0) (get :value)) )