ludus/janet/recursive.janet
2024-04-28 18:13:49 -04:00

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))
)