Compare commits

..

2 Commits

Author SHA1 Message Date
Scott Richmond
3d570beb45 start work on a recursive descent parser 2024-04-28 18:13:49 -04:00
Scott Richmond
0de9f90f27 update .gitignore to exclude repl cruft 2024-04-28 18:10:03 -04:00
4 changed files with 117 additions and 4 deletions

2
.gitignore vendored
View File

@ -30,3 +30,5 @@ node_modules/
.helix/
target/repl-port
.repl-buffer
.repl-buffer.janet
.env

View File

@ -387,6 +387,7 @@ if (:)foo then :bar else :baz
(comment
Okay, so I'm trying to figure out how to manage panics in this declarative (not recursive-descent) parser.
The question is how to
The question is how to do the thing where we pop out of the stack up to the most relevant rule.
Given that we may have deeply nested rules, we either want:
- A stack of rules in the parser, which
)

109
janet/recursive.janet Normal file
View File

@ -0,0 +1,109 @@
### 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))
)

View File

@ -192,12 +192,13 @@
[scanner]
(print "Adding string")
(defn recur [scanner buff interpolate?]
(print "\n printing a thing from add-stirng" buff)
(let [char (current-char scanner)]
(case char
"{" (recur (advance scanner) (buffer/push buff char) true)
# allow multiline strings
"\n" (recur (update (advance scanner) :line inc) (buffer/push buff char) interpolate?)
### TODO: add interpolated strings
### :string -> (if (interpolate? :interpolated :string))
"\"" (add-token (advance scanner) :string (string buff))
"\\" (let [next (next-char scanner)
scanner (if (= next "\n")
@ -314,7 +315,7 @@
(put scanner :start (get scanner :current)))
(defn scan [source &opt input]
(default input "")
(default input :input)
(defn recur [scanner]
(if (at-end? scanner)
(let [scanner (add-token (add-token scanner :break) :eof)]