110 lines
2.6 KiB
Plaintext
110 lines
2.6 KiB
Plaintext
|
### 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))
|
||
|
)
|
||
|
|
||
|
|