Some checks are pending
Test, Build, and Deploy / test-build-deploy (push) Waiting to run
440 lines
12 KiB
Plaintext
440 lines
12 KiB
Plaintext
;; Parser tests for Ruby 2.7 subset.
|
|
|
|
(define rb-deep=?
|
|
(fn (a b)
|
|
(cond
|
|
((= a b) true)
|
|
((and (dict? a) (dict? b))
|
|
(let ((ak (keys a)) (bk (keys b)))
|
|
(if (not (= (len ak) (len bk)))
|
|
false
|
|
(every?
|
|
(fn (k)
|
|
(and (has-key? b k) (rb-deep=? (get a k) (get b k))))
|
|
ak))))
|
|
((and (list? a) (list? b))
|
|
(if (not (= (len a) (len b)))
|
|
false
|
|
(let ((i 0) (ok true))
|
|
(define rb-de-loop
|
|
(fn ()
|
|
(when (and ok (< i (len a)))
|
|
(do
|
|
(when (not (rb-deep=? (nth a i) (nth b i)))
|
|
(set! ok false))
|
|
(set! i (+ i 1))
|
|
(rb-de-loop)))))
|
|
(rb-de-loop)
|
|
ok)))
|
|
(:else false))))
|
|
|
|
(define rb-test-pass 0)
|
|
(define rb-test-fail 0)
|
|
(define rb-test-fails (list))
|
|
|
|
(define rb-test
|
|
(fn (name actual expected)
|
|
(if (rb-deep=? actual expected)
|
|
(set! rb-test-pass (+ rb-test-pass 1))
|
|
(do
|
|
(set! rb-test-fail (+ rb-test-fail 1))
|
|
(append! rb-test-fails {:name name :actual actual :expected expected})))))
|
|
|
|
;; Shorthand: parse src and extract :stmts list
|
|
(define rb-p-stmts
|
|
(fn (src)
|
|
(get (rb-parse-str src) :stmts)))
|
|
|
|
;; Shorthand: parse and get first statement
|
|
(define rb-p-first
|
|
(fn (src)
|
|
(nth (rb-p-stmts src) 0)))
|
|
|
|
;; ── Literals ─────────────────────────────────────────────────────────────────
|
|
|
|
(rb-test "int literal"
|
|
(rb-p-first "42")
|
|
{:type "lit-int" :value 42})
|
|
|
|
(rb-test "negative int"
|
|
(rb-p-first "-7")
|
|
{:type "unop" :op "-" :value {:type "lit-int" :value 7}})
|
|
|
|
(rb-test "float literal"
|
|
(rb-p-first "3.14")
|
|
{:type "lit-float" :value "3.14"})
|
|
|
|
(rb-test "string literal"
|
|
(rb-p-first "\"hello\"")
|
|
{:type "lit-str" :value "hello"})
|
|
|
|
(rb-test "symbol literal"
|
|
(rb-p-first ":foo")
|
|
{:type "lit-sym" :value "foo"})
|
|
|
|
(rb-test "nil literal"
|
|
(rb-p-first "nil")
|
|
{:type "lit-nil"})
|
|
|
|
(rb-test "true literal"
|
|
(rb-p-first "true")
|
|
{:type "lit-bool" :value true})
|
|
|
|
(rb-test "false literal"
|
|
(rb-p-first "false")
|
|
{:type "lit-bool" :value false})
|
|
|
|
(rb-test "self"
|
|
(rb-p-first "self")
|
|
{:type "self"})
|
|
|
|
(rb-test "%w[] words"
|
|
(rb-p-first "%w[a b c]")
|
|
{:type "lit-words" :elems (list "a" "b" "c")})
|
|
|
|
(rb-test "%i[] isymbols"
|
|
(rb-p-first "%i[x y]")
|
|
{:type "lit-isyms" :elems (list "x" "y")})
|
|
|
|
;; ── Variables ─────────────────────────────────────────────────────────────────
|
|
|
|
(rb-test "local var / send"
|
|
(rb-p-first "x")
|
|
{:type "send" :name "x" :args (list) :block nil})
|
|
|
|
(rb-test "ivar"
|
|
(rb-p-first "@foo")
|
|
{:type "ivar" :name "@foo"})
|
|
|
|
(rb-test "cvar"
|
|
(rb-p-first "@@count")
|
|
{:type "cvar" :name "@@count"})
|
|
|
|
(rb-test "gvar"
|
|
(rb-p-first "$stdout")
|
|
{:type "gvar" :name "$stdout"})
|
|
|
|
(rb-test "constant"
|
|
(rb-p-first "Foo")
|
|
{:type "const" :name "Foo"})
|
|
|
|
(rb-test "const path"
|
|
(rb-p-first "Foo::Bar")
|
|
{:type "const-path"
|
|
:left {:type "const" :name "Foo"}
|
|
:name "Bar"})
|
|
|
|
(rb-test "triple const path"
|
|
(rb-p-first "A::B::C")
|
|
{:type "const-path"
|
|
:left {:type "const-path"
|
|
:left {:type "const" :name "A"}
|
|
:name "B"}
|
|
:name "C"})
|
|
|
|
;; ── Arrays and Hashes ─────────────────────────────────────────────────────────
|
|
|
|
(rb-test "empty array"
|
|
(rb-p-first "[]")
|
|
{:type "array" :elems (list)})
|
|
|
|
(rb-test "array literal"
|
|
(rb-p-first "[1, 2, 3]")
|
|
{:type "array" :elems (list {:type "lit-int" :value 1}
|
|
{:type "lit-int" :value 2}
|
|
{:type "lit-int" :value 3})})
|
|
|
|
(rb-test "hash colon style"
|
|
(get (rb-p-first "{a: 1}") :type)
|
|
"hash")
|
|
|
|
(rb-test "hash pair style"
|
|
(get (nth (get (rb-p-first "{a: 1}") :pairs) 0) :style)
|
|
"colon")
|
|
|
|
(rb-test "hash symbol key"
|
|
(get (get (nth (get (rb-p-first "{a: 1}") :pairs) 0) :key) :value)
|
|
"a")
|
|
|
|
;; ── Binary operators ──────────────────────────────────────────────────────────
|
|
|
|
(rb-test "addition"
|
|
(rb-p-first "1 + 2")
|
|
{:type "binop" :op "+"
|
|
:left {:type "lit-int" :value 1}
|
|
:right {:type "lit-int" :value 2}})
|
|
|
|
(rb-test "subtraction"
|
|
(get (rb-p-first "a - b") :op)
|
|
"-")
|
|
|
|
(rb-test "multiplication"
|
|
(get (rb-p-first "x * y") :op)
|
|
"*")
|
|
|
|
(rb-test "precedence: * before +"
|
|
(rb-p-first "1 + 2 * 3")
|
|
{:type "binop" :op "+"
|
|
:left {:type "lit-int" :value 1}
|
|
:right {:type "binop" :op "*"
|
|
:left {:type "lit-int" :value 2}
|
|
:right {:type "lit-int" :value 3}}})
|
|
|
|
(rb-test "power right-assoc"
|
|
(rb-p-first "2 ** 3 ** 4")
|
|
{:type "binop" :op "**"
|
|
:left {:type "lit-int" :value 2}
|
|
:right {:type "binop" :op "**"
|
|
:left {:type "lit-int" :value 3}
|
|
:right {:type "lit-int" :value 4}}})
|
|
|
|
(rb-test "equality"
|
|
(get (rb-p-first "a == b") :op)
|
|
"==")
|
|
|
|
(rb-test "logical and"
|
|
(get (rb-p-first "a && b") :op)
|
|
"&&")
|
|
|
|
(rb-test "logical or"
|
|
(get (rb-p-first "a || b") :op)
|
|
"||")
|
|
|
|
(rb-test "range inclusive"
|
|
(rb-p-first "1..5")
|
|
{:type "range"
|
|
:from {:type "lit-int" :value 1}
|
|
:to {:type "lit-int" :value 5}
|
|
:exclusive false})
|
|
|
|
(rb-test "range exclusive"
|
|
(get (rb-p-first "1...5") :exclusive)
|
|
true)
|
|
|
|
;; ── Assignment ────────────────────────────────────────────────────────────────
|
|
|
|
(rb-test "assign"
|
|
(rb-p-first "x = 1")
|
|
{:type "assign"
|
|
:target {:type "send" :name "x" :args (list) :block nil}
|
|
:value {:type "lit-int" :value 1}})
|
|
|
|
(rb-test "op-assign +="
|
|
(get (rb-p-first "x += 1") :type)
|
|
"op-assign")
|
|
|
|
(rb-test "op-assign op"
|
|
(get (rb-p-first "x += 1") :op)
|
|
"+")
|
|
|
|
(rb-test "massign"
|
|
(get (rb-p-first "a, b = 1, 2") :type)
|
|
"massign")
|
|
|
|
(rb-test "massign targets"
|
|
(len (get (rb-p-first "a, b = 1, 2") :targets))
|
|
2)
|
|
|
|
(rb-test "massign value array"
|
|
(get (get (rb-p-first "a, b = 1, 2") :value) :type)
|
|
"array")
|
|
|
|
;; ── Method calls ──────────────────────────────────────────────────────────────
|
|
|
|
(rb-test "call with parens"
|
|
(rb-p-first "foo(1, 2)")
|
|
{:type "send" :name "foo"
|
|
:args (list {:type "lit-int" :value 1}
|
|
{:type "lit-int" :value 2})
|
|
:block nil})
|
|
|
|
(rb-test "chained call"
|
|
(get (rb-p-first "obj.foo") :type)
|
|
"call")
|
|
|
|
(rb-test "chained call method"
|
|
(get (rb-p-first "obj.foo") :method)
|
|
"foo")
|
|
|
|
(rb-test "chained call with args"
|
|
(len (get (rb-p-first "obj.foo(1, 2)") :args))
|
|
2)
|
|
|
|
(rb-test "no-paren call"
|
|
(get (rb-p-first "puts \"hello\"") :type)
|
|
"send")
|
|
|
|
(rb-test "no-paren call name"
|
|
(get (rb-p-first "puts \"hello\"") :name)
|
|
"puts")
|
|
|
|
(rb-test "no-paren call args"
|
|
(len (get (rb-p-first "puts \"hello\"") :args))
|
|
1)
|
|
|
|
(rb-test "indexing"
|
|
(get (rb-p-first "a[0]") :type)
|
|
"index")
|
|
|
|
;; ── Unary operators ───────────────────────────────────────────────────────────
|
|
|
|
(rb-test "unary not"
|
|
(rb-p-first "!x")
|
|
{:type "unop" :op "!"
|
|
:value {:type "send" :name "x" :args (list) :block nil}})
|
|
|
|
(rb-test "unary minus"
|
|
(get (rb-p-first "-x") :op)
|
|
"-")
|
|
|
|
;; ── Method def ────────────────────────────────────────────────────────────────
|
|
|
|
(rb-test "empty method def"
|
|
(get (rb-p-first "def foo; end") :type)
|
|
"method-def")
|
|
|
|
(rb-test "method def name"
|
|
(get (rb-p-first "def foo; end") :name)
|
|
"foo")
|
|
|
|
(rb-test "method def no params"
|
|
(len (get (rb-p-first "def foo; end") :params))
|
|
0)
|
|
|
|
(rb-test "method def with params"
|
|
(len (get (rb-p-first "def foo(a, b); end") :params))
|
|
2)
|
|
|
|
(rb-test "method def param-req"
|
|
(get (nth (get (rb-p-first "def foo(a); end") :params) 0) :type)
|
|
"param-req")
|
|
|
|
(rb-test "method def param name"
|
|
(get (nth (get (rb-p-first "def foo(a); end") :params) 0) :name)
|
|
"a")
|
|
|
|
(rb-test "method def optional param"
|
|
(get (nth (get (rb-p-first "def foo(a, b=1); end") :params) 1) :type)
|
|
"param-opt")
|
|
|
|
(rb-test "method def splat"
|
|
(get (nth (get (rb-p-first "def foo(*args); end") :params) 0) :type)
|
|
"param-rest")
|
|
|
|
(rb-test "method def double splat"
|
|
(get (nth (get (rb-p-first "def foo(**opts); end") :params) 0) :type)
|
|
"param-kwrest")
|
|
|
|
(rb-test "method def block param"
|
|
(get (nth (get (rb-p-first "def foo(&blk); end") :params) 0) :type)
|
|
"param-block")
|
|
|
|
(rb-test "method def all param types"
|
|
(len (get (rb-p-first "def foo(a, b=1, *c, **d, &e); end") :params))
|
|
5)
|
|
|
|
(rb-test "method def singleton recv"
|
|
(get (get (rb-p-first "def self.bar; end") :recv) :type)
|
|
"self")
|
|
|
|
(rb-test "method def body"
|
|
(len (get (rb-p-first "def foo; 1; 2; end") :body))
|
|
2)
|
|
|
|
;; ── Class def ────────────────────────────────────────────────────────────────
|
|
|
|
(rb-test "class def type"
|
|
(get (rb-p-first "class Foo; end") :type)
|
|
"class-def")
|
|
|
|
(rb-test "class def name"
|
|
(get (get (rb-p-first "class Foo; end") :name) :name)
|
|
"Foo")
|
|
|
|
(rb-test "class def no super"
|
|
(nil? (get (rb-p-first "class Foo; end") :super))
|
|
true)
|
|
|
|
(rb-test "class def with super"
|
|
(get (get (rb-p-first "class Foo < Bar; end") :super) :name)
|
|
"Bar")
|
|
|
|
(rb-test "singleton class"
|
|
(get (rb-p-first "class << self; end") :type)
|
|
"sclass")
|
|
|
|
;; ── Module def ────────────────────────────────────────────────────────────────
|
|
|
|
(rb-test "module def type"
|
|
(get (rb-p-first "module M; end") :type)
|
|
"module-def")
|
|
|
|
(rb-test "module def name"
|
|
(get (get (rb-p-first "module M; end") :name) :name)
|
|
"M")
|
|
|
|
;; ── Blocks ────────────────────────────────────────────────────────────────────
|
|
|
|
(rb-test "block do...end"
|
|
(get (get (rb-p-first "foo do |x| x end") :block) :type)
|
|
"block")
|
|
|
|
(rb-test "block brace"
|
|
(get (get (rb-p-first "foo { |x| x }") :block) :type)
|
|
"block")
|
|
|
|
(rb-test "block params"
|
|
(len (get (get (rb-p-first "foo { |a, b| a }") :block) :params))
|
|
2)
|
|
|
|
(rb-test "block no params"
|
|
(len (get (get (rb-p-first "foo { 42 }") :block) :params))
|
|
0)
|
|
|
|
;; ── Control flow ──────────────────────────────────────────────────────────────
|
|
|
|
(rb-test "return type"
|
|
(get (rb-p-first "return 1") :type)
|
|
"return")
|
|
|
|
(rb-test "return value"
|
|
(get (get (rb-p-first "return 1") :value) :value)
|
|
1)
|
|
|
|
(rb-test "return nil"
|
|
(nil? (get (rb-p-first "return") :value))
|
|
true)
|
|
|
|
(rb-test "yield type"
|
|
(get (rb-p-first "yield 1") :type)
|
|
"yield")
|
|
|
|
(rb-test "break type"
|
|
(get (rb-p-first "break") :type)
|
|
"break")
|
|
|
|
(rb-test "next type"
|
|
(get (rb-p-first "next") :type)
|
|
"next")
|
|
|
|
(rb-test "redo type"
|
|
(get (rb-p-first "redo") :type)
|
|
"redo")
|
|
|
|
;; ── Multi-statement program ───────────────────────────────────────────────────
|
|
|
|
(rb-test "two statements"
|
|
(len (rb-p-stmts "1\n2"))
|
|
2)
|
|
|
|
(rb-test "semi-separated"
|
|
(len (rb-p-stmts "1; 2; 3"))
|
|
3)
|
|
|
|
(rb-test "class with method"
|
|
(let ((cls (rb-p-first "class Foo\n def bar\n 1\n end\nend")))
|
|
(len (get cls :body)))
|
|
1)
|
|
|
|
(list rb-test-pass rb-test-fail)
|