Files
rose-ash/lib/ruby/parser.sx
giles 15eb133311
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 11s
ruby: Phase 1 parser (+83 tests, 190 total)
2026-04-25 18:50:49 +00:00

832 lines
35 KiB
Plaintext

;; Ruby parser: token list → AST.
;; Entry: (rb-parse tokens) or (rb-parse-str src)
;; AST nodes: dicts with :type plus type-specific fields.
(define rb-parse
(fn (tokens)
(let ((pos 0) (tok-count (len tokens)))
(define rb-p-cur
(fn () (nth tokens pos)))
(define rb-p-peek
(fn (n)
(if (< (+ pos n) tok-count)
(nth tokens (+ pos n))
{:type "eof" :value nil :line 0 :col 0})))
(define rb-p-advance!
(fn () (set! pos (+ pos 1))))
(define rb-p-type
(fn () (get (rb-p-cur) :type)))
(define rb-p-val
(fn () (get (rb-p-cur) :value)))
(define rb-p-sep?
(fn () (or (= (rb-p-type) "newline") (= (rb-p-type) "semi"))))
(define rb-p-skip-seps!
(fn ()
(when (rb-p-sep?)
(do (rb-p-advance!) (rb-p-skip-seps!)))))
(define rb-p-skip-newlines!
(fn ()
(when (= (rb-p-type) "newline")
(do (rb-p-advance!) (rb-p-skip-newlines!)))))
(define rb-p-expect!
(fn (type)
(if (= (rb-p-type) type)
(let ((tok (rb-p-cur)))
(rb-p-advance!)
tok)
{:type "error"
:msg (join "" (list "expected " type " got " (rb-p-type)))})))
(define rb-p-expect-kw!
(fn (kw)
(when (and (= (rb-p-type) "keyword") (= (rb-p-val) kw))
(rb-p-advance!))))
;; Block: do |params| body end or { |params| body }
(define rb-p-parse-block-params
(fn ()
(if (= (rb-p-type) "pipe")
(do
(rb-p-advance!)
(let ((params (list)))
(define rb-p-bp-loop
(fn ()
(when (not (or (= (rb-p-type) "pipe") (= (rb-p-type) "eof")))
(do
(cond
((and (= (rb-p-type) "op") (= (rb-p-val) "**"))
(do
(rb-p-advance!)
(append! params {:type "param-kwrest" :name (rb-p-val)})
(rb-p-advance!)))
((and (= (rb-p-type) "op") (= (rb-p-val) "*"))
(do
(rb-p-advance!)
(if (= (rb-p-type) "ident")
(do
(append! params {:type "param-rest" :name (rb-p-val)})
(rb-p-advance!))
(append! params {:type "param-rest" :name nil}))))
(:else
(do
(append! params {:type "param-req" :name (rb-p-val)})
(rb-p-advance!))))
(when (= (rb-p-type) "comma") (rb-p-advance!))
(rb-p-bp-loop)))))
(rb-p-bp-loop)
(rb-p-expect! "pipe")
params))
(list))))
(define rb-p-parse-block
(fn ()
(cond
((and (= (rb-p-type) "keyword") (= (rb-p-val) "do"))
(do
(rb-p-advance!)
(let ((params (rb-p-parse-block-params)))
(rb-p-skip-seps!)
(let ((body (rb-p-parse-stmts (list "end"))))
(rb-p-expect-kw! "end")
{:type "block" :params params :body body}))))
((= (rb-p-type) "lbrace")
(do
(rb-p-advance!)
(let ((params (rb-p-parse-block-params)))
(rb-p-skip-seps!)
(let ((body (rb-p-parse-stmts (list "rbrace"))))
(rb-p-expect! "rbrace")
{:type "block" :params params :body body}))))
(:else nil))))
;; Method def params
(define rb-p-parse-def-params
(fn ()
(let ((params (list)))
(define rb-p-dp-one
(fn ()
(cond
((and (= (rb-p-type) "op") (= (rb-p-val) "&"))
(do
(rb-p-advance!)
(append! params {:type "param-block" :name (rb-p-val)})
(rb-p-advance!)))
((and (= (rb-p-type) "op") (= (rb-p-val) "**"))
(do
(rb-p-advance!)
(append! params {:type "param-kwrest" :name (rb-p-val)})
(rb-p-advance!)))
((and (= (rb-p-type) "op") (= (rb-p-val) "*"))
(do
(rb-p-advance!)
(if (= (rb-p-type) "ident")
(do
(append! params {:type "param-rest" :name (rb-p-val)})
(rb-p-advance!))
(append! params {:type "param-rest" :name nil}))))
((and (= (rb-p-type) "ident")
(= (get (rb-p-peek 1) :type) "colon"))
(do
(let ((name (rb-p-val)))
(rb-p-advance!)
(rb-p-advance!)
(if (or (rb-p-sep?) (= (rb-p-type) "comma")
(= (rb-p-type) "rparen") (= (rb-p-type) "eof"))
(append! params {:type "param-kw" :name name :default nil})
(append! params {:type "param-kw" :name name
:default (rb-p-parse-assign)})))))
(:else
(let ((name (rb-p-val)))
(rb-p-advance!)
(if (and (= (rb-p-type) "op") (= (rb-p-val) "="))
(do
(rb-p-advance!)
(append! params {:type "param-opt" :name name
:default (rb-p-parse-assign)}))
(append! params {:type "param-req" :name name})))))))
(define rb-p-dp-loop
(fn ()
(when (not (or (= (rb-p-type) "rparen") (rb-p-sep?)
(= (rb-p-type) "eof")))
(do
(rb-p-dp-one)
(when (= (rb-p-type) "comma")
(do (rb-p-advance!) (rb-p-skip-newlines!)))
(rb-p-dp-loop)))))
(rb-p-dp-loop)
params)))
;; def [recv.] name [(params)] body end
(define rb-p-parse-def
(fn ()
(rb-p-advance!)
(let ((recv nil) (name nil))
(cond
((and (= (rb-p-type) "keyword") (= (rb-p-val) "self")
(= (get (rb-p-peek 1) :type) "dot"))
(do
(set! recv {:type "self"})
(rb-p-advance!)
(rb-p-advance!)
(set! name (rb-p-val))
(rb-p-advance!)))
((and (= (rb-p-type) "ident")
(= (get (rb-p-peek 1) :type) "dot"))
(do
(set! recv {:type "lvar" :name (rb-p-val)})
(rb-p-advance!)
(rb-p-advance!)
(set! name (rb-p-val))
(rb-p-advance!)))
(:else
(do
(set! name (rb-p-val))
(rb-p-advance!))))
(let ((params (list)))
(cond
((= (rb-p-type) "lparen")
(do
(rb-p-advance!)
(rb-p-skip-newlines!)
(set! params (rb-p-parse-def-params))
(rb-p-expect! "rparen")))
((not (or (rb-p-sep?) (= (rb-p-type) "eof")))
(set! params (rb-p-parse-def-params)))
(:else nil))
(rb-p-skip-seps!)
(let ((body (rb-p-parse-stmts (list "end"))))
(rb-p-expect-kw! "end")
{:type "method-def" :recv recv :name name
:params params :body body})))))
;; class [<<obj | Name [<Super]] body end
(define rb-p-parse-class
(fn ()
(rb-p-advance!)
(if (and (= (rb-p-type) "op") (= (rb-p-val) "<<"))
(do
(rb-p-advance!)
(let ((obj (rb-p-parse-primary)))
(rb-p-skip-seps!)
(let ((body (rb-p-parse-stmts (list "end"))))
(rb-p-expect-kw! "end")
{:type "sclass" :obj obj :body body})))
(let ((name (rb-p-parse-const-path)))
(let ((super nil))
(when (and (= (rb-p-type) "op") (= (rb-p-val) "<"))
(do
(rb-p-advance!)
(set! super (rb-p-parse-const-path))))
(rb-p-skip-seps!)
(let ((body (rb-p-parse-stmts (list "end"))))
(rb-p-expect-kw! "end")
{:type "class-def" :name name :super super :body body}))))))
;; module Name body end
(define rb-p-parse-module
(fn ()
(rb-p-advance!)
(let ((name (rb-p-parse-const-path)))
(rb-p-skip-seps!)
(let ((body (rb-p-parse-stmts (list "end"))))
(rb-p-expect-kw! "end")
{:type "module-def" :name name :body body}))))
;; Const or Const::Const::...
(define rb-p-parse-const-path
(fn ()
(let ((node {:type "const" :name (rb-p-val)}))
(rb-p-advance!)
(define rb-p-cp-loop
(fn ()
(when (= (rb-p-type) "dcolon")
(do
(rb-p-advance!)
(let ((name (rb-p-val)))
(rb-p-advance!)
(set! node {:type "const-path" :left node :name name})
(rb-p-cp-loop))))))
(rb-p-cp-loop)
node)))
;; [e, *e, ...]
(define rb-p-parse-array
(fn ()
(rb-p-advance!)
(rb-p-skip-newlines!)
(let ((elems (list)))
(define rb-p-arr-loop
(fn ()
(when (not (or (= (rb-p-type) "rbracket") (= (rb-p-type) "eof")))
(do
(if (and (= (rb-p-type) "op") (= (rb-p-val) "*"))
(do
(rb-p-advance!)
(append! elems {:type "splat" :value (rb-p-parse-assign)}))
(append! elems (rb-p-parse-assign)))
(rb-p-skip-newlines!)
(when (= (rb-p-type) "comma")
(do (rb-p-advance!) (rb-p-skip-newlines!)))
(rb-p-arr-loop)))))
(rb-p-arr-loop)
(rb-p-expect! "rbracket")
{:type "array" :elems elems})))
;; {k: v, k => v, ...}
(define rb-p-parse-hash
(fn ()
(rb-p-advance!)
(rb-p-skip-newlines!)
(let ((pairs (list)))
(define rb-p-hash-loop
(fn ()
(when (not (or (= (rb-p-type) "rbrace") (= (rb-p-type) "eof")))
(do
(let ((key nil) (val nil) (style nil))
(cond
((and (or (= (rb-p-type) "ident") (= (rb-p-type) "const"))
(= (get (rb-p-peek 1) :type) "colon"))
(do
(set! key {:type "lit-sym" :value (rb-p-val)})
(set! style "colon")
(rb-p-advance!)
(rb-p-advance!)))
(:else
(do
(set! key (rb-p-parse-assign))
(set! style "rocket")
(when (and (= (rb-p-type) "op") (= (rb-p-val) "=>"))
(rb-p-advance!)))))
(rb-p-skip-newlines!)
(set! val (rb-p-parse-assign))
(append! pairs {:key key :val val :style style}))
(rb-p-skip-newlines!)
(when (= (rb-p-type) "comma")
(do (rb-p-advance!) (rb-p-skip-newlines!)))
(rb-p-hash-loop)))))
(rb-p-hash-loop)
(rb-p-expect! "rbrace")
{:type "hash" :pairs pairs})))
;; (a, *b, **c, &d)
(define rb-p-parse-args-parens
(fn ()
(rb-p-advance!)
(rb-p-skip-newlines!)
(let ((args (list)))
(define rb-p-ap-loop
(fn ()
(when (not (or (= (rb-p-type) "rparen") (= (rb-p-type) "eof")))
(do
(cond
((and (= (rb-p-type) "op") (= (rb-p-val) "**"))
(do (rb-p-advance!)
(append! args {:type "dsplat" :value (rb-p-parse-assign)})))
((and (= (rb-p-type) "op") (= (rb-p-val) "*"))
(do (rb-p-advance!)
(append! args {:type "splat" :value (rb-p-parse-assign)})))
((and (= (rb-p-type) "op") (= (rb-p-val) "&"))
(do (rb-p-advance!)
(append! args {:type "block-pass" :value (rb-p-parse-assign)})))
(:else (append! args (rb-p-parse-assign))))
(rb-p-skip-newlines!)
(when (= (rb-p-type) "comma")
(do (rb-p-advance!) (rb-p-skip-newlines!)))
(rb-p-ap-loop)))))
(rb-p-ap-loop)
(rb-p-expect! "rparen")
args)))
;; No-paren arg list up to sep/end-keyword
(define rb-p-parse-args-bare
(fn ()
(let ((args (list)) (going true))
(define rb-p-ab-loop
(fn ()
(when (and going
(not (rb-p-sep?))
(not (= (rb-p-type) "eof"))
(not (= (rb-p-type) "rparen"))
(not (= (rb-p-type) "rbracket"))
(not (= (rb-p-type) "rbrace"))
(not (and (= (rb-p-type) "keyword")
(contains? (list "end" "else" "elsif" "when"
"rescue" "ensure" "then" "do")
(rb-p-val)))))
(do
(cond
((and (= (rb-p-type) "op") (= (rb-p-val) "*"))
(do (rb-p-advance!)
(append! args {:type "splat" :value (rb-p-parse-assign)})))
((and (= (rb-p-type) "op") (= (rb-p-val) "**"))
(do (rb-p-advance!)
(append! args {:type "dsplat" :value (rb-p-parse-assign)})))
((and (= (rb-p-type) "op") (= (rb-p-val) "&"))
(do (rb-p-advance!)
(append! args {:type "block-pass" :value (rb-p-parse-assign)})))
(:else (append! args (rb-p-parse-assign))))
(if (= (rb-p-type) "comma")
(do (rb-p-advance!) (rb-p-skip-newlines!) (rb-p-ab-loop))
(set! going false))))))
(rb-p-ab-loop)
args)))
;; Primary expression
(define rb-p-parse-primary
(fn ()
(cond
((= (rb-p-type) "int")
(let ((v (rb-p-val))) (rb-p-advance!) {:type "lit-int" :value v}))
((= (rb-p-type) "float")
(let ((v (rb-p-val))) (rb-p-advance!) {:type "lit-float" :value v}))
((= (rb-p-type) "string")
(let ((v (rb-p-val))) (rb-p-advance!) {:type "lit-str" :value v}))
((= (rb-p-type) "symbol")
(let ((v (rb-p-val))) (rb-p-advance!) {:type "lit-sym" :value v}))
((= (rb-p-type) "words")
(let ((v (rb-p-val))) (rb-p-advance!) {:type "lit-words" :elems v}))
((= (rb-p-type) "isymbols")
(let ((v (rb-p-val))) (rb-p-advance!) {:type "lit-isyms" :elems v}))
((= (rb-p-type) "ivar")
(let ((v (rb-p-val))) (rb-p-advance!) {:type "ivar" :name v}))
((= (rb-p-type) "cvar")
(let ((v (rb-p-val))) (rb-p-advance!) {:type "cvar" :name v}))
((= (rb-p-type) "gvar")
(let ((v (rb-p-val))) (rb-p-advance!) {:type "gvar" :name v}))
((= (rb-p-type) "const")
(rb-p-parse-const-path))
((= (rb-p-type) "ident")
(let ((name (rb-p-val)))
(rb-p-advance!)
(if (= (rb-p-type) "lparen")
(let ((args (rb-p-parse-args-parens))
(blk (rb-p-parse-block)))
{:type "send" :name name :args args :block blk})
{:type "send" :name name :args (list) :block nil})))
((= (rb-p-type) "keyword")
(cond
((= (rb-p-val) "nil")
(do (rb-p-advance!) {:type "lit-nil"}))
((= (rb-p-val) "true")
(do (rb-p-advance!) {:type "lit-bool" :value true}))
((= (rb-p-val) "false")
(do (rb-p-advance!) {:type "lit-bool" :value false}))
((= (rb-p-val) "self")
(do (rb-p-advance!) {:type "self"}))
((= (rb-p-val) "super")
(do
(rb-p-advance!)
(let ((args (if (= (rb-p-type) "lparen")
(rb-p-parse-args-parens) (list)))
(blk (rb-p-parse-block)))
{:type "send" :name "super" :args args :block blk})))
(:else
{:type "error"
:msg (join "" (list "unexpected kw " (rb-p-val)))})))
((= (rb-p-type) "lbracket")
(rb-p-parse-array))
((= (rb-p-type) "lbrace")
(rb-p-parse-hash))
((= (rb-p-type) "lparen")
(do
(rb-p-advance!)
(rb-p-skip-seps!)
(let ((node (rb-p-parse-expr)))
(rb-p-skip-seps!)
(rb-p-expect! "rparen")
node)))
(:else
(do
(rb-p-advance!)
{:type "error"
:msg (join "" (list "unexpected " (rb-p-type)
" '" (or (rb-p-val) "") "'"))})))))
;; .method ::Const [index] chains
(define rb-p-parse-postfix
(fn ()
(let ((node (rb-p-parse-primary)))
(define rb-p-pf-loop
(fn ()
(cond
((= (rb-p-type) "dot")
(do
(rb-p-advance!)
(let ((method (rb-p-val)))
(rb-p-advance!)
(let ((args (if (= (rb-p-type) "lparen")
(rb-p-parse-args-parens) (list)))
(blk (rb-p-parse-block)))
(set! node {:type "call" :recv node :method method
:args args :block blk})
(rb-p-pf-loop)))))
((= (rb-p-type) "dcolon")
(do
(rb-p-advance!)
(let ((name (rb-p-val)))
(rb-p-advance!)
(if (= (rb-p-type) "lparen")
(let ((args (rb-p-parse-args-parens))
(blk (rb-p-parse-block)))
(set! node {:type "call" :recv node :method name
:args args :block blk}))
(set! node {:type "const-path" :left node :name name}))
(rb-p-pf-loop))))
((= (rb-p-type) "lbracket")
(do
(rb-p-advance!)
(rb-p-skip-newlines!)
(let ((idxargs (list)))
(define rb-p-idx-loop
(fn ()
(when (not (or (= (rb-p-type) "rbracket") (= (rb-p-type) "eof")))
(do
(append! idxargs (rb-p-parse-assign))
(when (= (rb-p-type) "comma")
(do (rb-p-advance!) (rb-p-skip-newlines!)))
(rb-p-idx-loop)))))
(rb-p-idx-loop)
(rb-p-expect! "rbracket")
(set! node {:type "index" :recv node :args idxargs})
(rb-p-pf-loop))))
(:else nil))))
(rb-p-pf-loop)
node)))
(define rb-p-parse-unary
(fn ()
(cond
((and (= (rb-p-type) "op") (= (rb-p-val) "!"))
(do (rb-p-advance!)
{:type "unop" :op "!" :value (rb-p-parse-unary)}))
((and (= (rb-p-type) "op") (= (rb-p-val) "~"))
(do (rb-p-advance!)
{:type "unop" :op "~" :value (rb-p-parse-unary)}))
((and (= (rb-p-type) "op") (= (rb-p-val) "-"))
(do (rb-p-advance!)
{:type "unop" :op "-" :value (rb-p-parse-unary)}))
((and (= (rb-p-type) "op") (= (rb-p-val) "+"))
(do (rb-p-advance!) (rb-p-parse-unary)))
(:else (rb-p-parse-postfix)))))
(define rb-p-parse-power
(fn ()
(let ((node (rb-p-parse-unary)))
(if (and (= (rb-p-type) "op") (= (rb-p-val) "**"))
(do (rb-p-advance!)
{:type "binop" :op "**" :left node :right (rb-p-parse-power)})
node))))
(define rb-p-parse-mul
(fn ()
(let ((node (rb-p-parse-power)))
(define rb-p-mul-loop
(fn ()
(if (and (= (rb-p-type) "op")
(or (= (rb-p-val) "*") (= (rb-p-val) "/") (= (rb-p-val) "%")))
(let ((op (rb-p-val)))
(rb-p-advance!)
(set! node {:type "binop" :op op :left node :right (rb-p-parse-power)})
(rb-p-mul-loop))
node)))
(rb-p-mul-loop))))
(define rb-p-parse-add
(fn ()
(let ((node (rb-p-parse-mul)))
(define rb-p-add-loop
(fn ()
(if (and (= (rb-p-type) "op")
(or (= (rb-p-val) "+") (= (rb-p-val) "-")))
(let ((op (rb-p-val)))
(rb-p-advance!)
(set! node {:type "binop" :op op :left node :right (rb-p-parse-mul)})
(rb-p-add-loop))
node)))
(rb-p-add-loop))))
(define rb-p-parse-shift
(fn ()
(let ((node (rb-p-parse-add)))
(define rb-p-sh-loop
(fn ()
(if (and (= (rb-p-type) "op")
(or (= (rb-p-val) "<<") (= (rb-p-val) ">>")))
(let ((op (rb-p-val)))
(rb-p-advance!)
(set! node {:type "binop" :op op :left node :right (rb-p-parse-add)})
(rb-p-sh-loop))
node)))
(rb-p-sh-loop))))
(define rb-p-parse-bitand
(fn ()
(let ((node (rb-p-parse-shift)))
(define rb-p-ba-loop
(fn ()
(if (and (= (rb-p-type) "op") (= (rb-p-val) "&"))
(do
(rb-p-advance!)
(set! node {:type "binop" :op "&" :left node :right (rb-p-parse-shift)})
(rb-p-ba-loop))
node)))
(rb-p-ba-loop))))
;; | is "pipe" token (not "op")
(define rb-p-parse-bitor
(fn ()
(let ((node (rb-p-parse-bitand)))
(define rb-p-bo-loop
(fn ()
(cond
((= (rb-p-type) "pipe")
(do
(rb-p-advance!)
(set! node {:type "binop" :op "|" :left node :right (rb-p-parse-bitand)})
(rb-p-bo-loop)))
((and (= (rb-p-type) "op") (= (rb-p-val) "^"))
(do
(rb-p-advance!)
(set! node {:type "binop" :op "^" :left node :right (rb-p-parse-bitand)})
(rb-p-bo-loop)))
(:else node))))
(rb-p-bo-loop))))
(define rb-p-parse-comparison
(fn ()
(let ((node (rb-p-parse-bitor)))
(if (and (= (rb-p-type) "op")
(contains? (list "==" "!=" "<" ">" "<=" ">="
"<=>" "===" "=~" "!~") (rb-p-val)))
(let ((op (rb-p-val)))
(rb-p-advance!)
{:type "binop" :op op :left node :right (rb-p-parse-bitor)})
node))))
(define rb-p-parse-not
(fn ()
(if (and (= (rb-p-type) "keyword") (= (rb-p-val) "not"))
(do (rb-p-advance!)
{:type "not" :value (rb-p-parse-not)})
(rb-p-parse-comparison))))
(define rb-p-parse-and
(fn ()
(let ((node (rb-p-parse-not)))
(define rb-p-and-loop
(fn ()
(cond
((and (= (rb-p-type) "op") (= (rb-p-val) "&&"))
(do
(rb-p-advance!)
(set! node {:type "binop" :op "&&" :left node :right (rb-p-parse-not)})
(rb-p-and-loop)))
((and (= (rb-p-type) "keyword") (= (rb-p-val) "and"))
(do
(rb-p-advance!)
(set! node {:type "binop" :op "and" :left node :right (rb-p-parse-not)})
(rb-p-and-loop)))
(:else node))))
(rb-p-and-loop))))
(define rb-p-parse-or
(fn ()
(let ((node (rb-p-parse-and)))
(define rb-p-or-loop
(fn ()
(cond
((and (= (rb-p-type) "op") (= (rb-p-val) "||"))
(do
(rb-p-advance!)
(set! node {:type "binop" :op "||" :left node :right (rb-p-parse-and)})
(rb-p-or-loop)))
((and (= (rb-p-type) "keyword") (= (rb-p-val) "or"))
(do
(rb-p-advance!)
(set! node {:type "binop" :op "or" :left node :right (rb-p-parse-and)})
(rb-p-or-loop)))
(:else node))))
(rb-p-or-loop))))
(define rb-p-parse-range
(fn ()
(let ((node (rb-p-parse-or)))
(cond
((= (rb-p-type) "dotdot")
(do (rb-p-advance!)
{:type "range" :from node :to (rb-p-parse-or) :exclusive false}))
((= (rb-p-type) "dotdotdot")
(do (rb-p-advance!)
{:type "range" :from node :to (rb-p-parse-or) :exclusive true}))
(:else node)))))
(define rb-p-parse-assign
(fn ()
(let ((node (rb-p-parse-range)))
(cond
((and (= (rb-p-type) "op") (= (rb-p-val) "="))
(do (rb-p-advance!)
{:type "assign" :target node :value (rb-p-parse-assign)}))
((and (= (rb-p-type) "op")
(contains? (list "+=" "-=" "*=" "/=" "%=" "**="
"<<=" ">>=" "&=" "|=" "^=" "&&=" "||=")
(rb-p-val)))
(let ((op (substring (rb-p-val) 0 (- (len (rb-p-val)) 1))))
(rb-p-advance!)
{:type "op-assign" :target node :op op :value (rb-p-parse-assign)}))
(:else node)))))
(define rb-p-parse-expr
(fn () (rb-p-parse-assign)))
;; e, e, ... → single node or array
(define rb-p-parse-multi-val
(fn ()
(let ((vals (list)))
(define rb-p-mv-loop
(fn ()
(append! vals (rb-p-parse-assign))
(when (= (rb-p-type) "comma")
(do (rb-p-advance!) (rb-p-skip-newlines!) (rb-p-mv-loop)))))
(rb-p-mv-loop)
(if (= (len vals) 1)
(nth vals 0)
{:type "array" :elems vals}))))
;; a, b, *c = rhs
(define rb-p-parse-massign
(fn ()
(let ((targets (list)))
(define rb-p-ma-loop
(fn ()
(cond
((and (= (rb-p-type) "op") (= (rb-p-val) "*"))
(do
(rb-p-advance!)
(if (= (rb-p-type) "ident")
(do
(append! targets {:type "splat-target" :name (rb-p-val)})
(rb-p-advance!))
(append! targets {:type "splat-target" :name nil}))))
((= (rb-p-type) "ident")
(do (append! targets {:type "lvar" :name (rb-p-val)}) (rb-p-advance!)))
((= (rb-p-type) "ivar")
(do (append! targets {:type "ivar" :name (rb-p-val)}) (rb-p-advance!)))
((= (rb-p-type) "cvar")
(do (append! targets {:type "cvar" :name (rb-p-val)}) (rb-p-advance!)))
((= (rb-p-type) "gvar")
(do (append! targets {:type "gvar" :name (rb-p-val)}) (rb-p-advance!)))
((= (rb-p-type) "const")
(do (append! targets {:type "const" :name (rb-p-val)}) (rb-p-advance!)))
(:else nil))
(when (= (rb-p-type) "comma")
(do (rb-p-advance!) (rb-p-skip-newlines!) (rb-p-ma-loop)))))
(rb-p-ma-loop)
(rb-p-advance!)
{:type "massign" :targets targets :value (rb-p-parse-multi-val)})))
(define rb-p-parse-stmt
(fn ()
(cond
((and (= (rb-p-type) "keyword") (= (rb-p-val) "def"))
(rb-p-parse-def))
((and (= (rb-p-type) "keyword") (= (rb-p-val) "class"))
(rb-p-parse-class))
((and (= (rb-p-type) "keyword") (= (rb-p-val) "module"))
(rb-p-parse-module))
((and (= (rb-p-type) "keyword") (= (rb-p-val) "return"))
(do (rb-p-advance!)
{:type "return"
:value (if (or (rb-p-sep?) (= (rb-p-type) "eof"))
nil (rb-p-parse-multi-val))}))
((and (= (rb-p-type) "keyword") (= (rb-p-val) "yield"))
(do (rb-p-advance!)
{:type "yield"
:args (cond
((= (rb-p-type) "lparen") (rb-p-parse-args-parens))
((or (rb-p-sep?) (= (rb-p-type) "eof")) (list))
(:else (rb-p-parse-args-bare)))}))
((and (= (rb-p-type) "keyword") (= (rb-p-val) "break"))
(do (rb-p-advance!)
{:type "break"
:value (if (or (rb-p-sep?) (= (rb-p-type) "eof"))
nil (rb-p-parse-expr))}))
((and (= (rb-p-type) "keyword") (= (rb-p-val) "next"))
(do (rb-p-advance!)
{:type "next"
:value (if (or (rb-p-sep?) (= (rb-p-type) "eof"))
nil (rb-p-parse-expr))}))
((and (= (rb-p-type) "keyword") (= (rb-p-val) "redo"))
(do (rb-p-advance!) {:type "redo"}))
((and (= (rb-p-type) "keyword") (= (rb-p-val) "raise"))
(do (rb-p-advance!)
{:type "raise"
:value (if (or (rb-p-sep?) (= (rb-p-type) "eof"))
nil (rb-p-parse-expr))}))
;; Massign: token followed by comma
((and (or (= (rb-p-type) "ident") (= (rb-p-type) "ivar")
(= (rb-p-type) "cvar") (= (rb-p-type) "gvar")
(= (rb-p-type) "const"))
(= (get (rb-p-peek 1) :type) "comma"))
(rb-p-parse-massign))
(:else
(let ((node (rb-p-parse-assign)))
(if (and (= (get node :type) "send")
(= (len (get node :args)) 0)
(nil? (get node :block)))
;; Bare send: check for block or no-paren args
(cond
;; Block immediately follows (do or {)
((or (and (= (rb-p-type) "keyword") (= (rb-p-val) "do"))
(= (rb-p-type) "lbrace"))
(let ((blk (rb-p-parse-block)))
{:type "send" :name (get node :name) :args (list) :block blk}))
;; No-paren args (stop before block/sep/end keywords)
((and (not (rb-p-sep?))
(not (= (rb-p-type) "eof"))
(not (= (rb-p-type) "op"))
(not (= (rb-p-type) "dot"))
(not (= (rb-p-type) "dcolon"))
(not (= (rb-p-type) "rparen"))
(not (= (rb-p-type) "rbracket"))
(not (= (rb-p-type) "rbrace"))
(not (= (rb-p-type) "lbrace"))
(not (and (= (rb-p-type) "keyword")
(contains? (list "end" "else" "elsif" "when"
"rescue" "ensure" "then" "do"
"and" "or" "not")
(rb-p-val)))))
(let ((args (rb-p-parse-args-bare))
(blk (rb-p-parse-block)))
(if (> (len args) 0)
{:type "send" :name (get node :name) :args args :block blk}
node)))
(:else node))
node))))))
(define rb-p-parse-stmts
(fn (terminators)
(let ((stmts (list)))
(define rb-p-at-term?
(fn ()
(or (= (rb-p-type) "eof")
(and (= (rb-p-type) "keyword")
(contains? terminators (rb-p-val)))
(and (= (rb-p-type) "rbrace")
(contains? terminators "rbrace")))))
(define rb-p-ps-loop
(fn ()
(rb-p-skip-seps!)
(when (not (rb-p-at-term?))
(do
(append! stmts (rb-p-parse-stmt))
(rb-p-skip-seps!)
(rb-p-ps-loop)))))
(rb-p-ps-loop)
stmts)))
{:type "program" :stmts (rb-p-parse-stmts (list))})))
(define rb-parse-str
(fn (src) (rb-parse (rb-tokenize src))))