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