includes :assert :file "parser/parser" "parser/sexp"
add_results setup name: "Brat AST tests" {
ast = { input, expected_ast |
parsed = brat_parser.parse input, :program, :fully
assert parsed, "Failed to parse '#{input}'"
got_ast = parsed.ast
true? got_ast.nodes.length == 1
{ assert_equal expected_ast, got_ast.nodes.last }
{ assert_equal expected_ast, got_ast }
}
test "empty program" {
ast "" s[:program]
}
test "number" {
ast "1" s[:number 1]
}
test "numbers" {
ast "1\n2\n3" s[:program
s[:number 1]
s[:number 2]
s[:number 3]]
}
test "empty array" {
ast "[]" s[:array]
}
test "one element array" {
ast "[1]" s[:array
s[:number 1]]
}
test "two element array" {
ast "[1, 2]" s[:array
s[:number 1]
s[:number 2]]
}
test "empty hash table" {
ast "[:]" s[:hash]
}
test "one pair hash table" {
ast "[1 : 2]" s[:hash
[s[:number 1]
s[:number 2]]]
}
test "two pair hash table" {
ast "[1 : 2, 3 : 4]" s[:hash
[s[:number 1]
s[:number 2]]
[s[:number 3]
s[:number 4]]]
}
test "symbol key hash table" {
ast "[a: 1]" s[:hash
[s[:simple_string :a]
s[:number 1]]]
}
test "symbol keys hash table" {
ast "[a: 1, b: 2]" s[:hash
[s[:simple_string :a]
s[:number 1]]
[s[:simple_string :b]
s[:number 2]]]
}
test "regex" {
ast "/^r\\d+.*?/i" s[:regex "^r\\d+.*?" "i"]
}
test "simple string" {
ast '\'x\'' s[:simple_string 'x']
}
test "simple symbol" {
ast ':x' s[:simple_symbol 'x']
}
test "double quotes" {
ast '"x"' s[:string "x"]
}
test "string interp" {
ast '"#{:x}"' s[:string_interp s[:string_eval s[:simple_symbol :x]]]
}
test "string multiple line interp" {
ast '"#{:x; 1}"' s[:string_interp
s[:string_eval
s[:simple_symbol :x]
s[:number 1]]]
}
# Note: Need to improve when have more expressions implemented
test "string interp mix" {
ast '"hi #{:name}! What is #{:up}?"',
s[:string_interp
s[:string "hi "]
s[:string_eval
s[:simple_symbol :name]]
s[:string "! What is "]
s[:string_eval
s[:simple_symbol :up]]
s[:string "?"]]
}
test "comments are ignored" {
ast "# hi" s[:program]
}
test "multiline comments are ignored" {
ast "#*\n*#" s[:program]
}
test "nested comments are ignored" {
ast "#* # hi \n *#" s[:program]
}
test "comment out rest of file ignored" {
ast "1 #* stuff\nthings" s[:number 1]
}
test "inline comments are ignored" {
ast "1 # nada" s[:number 1]
}
test "empty function with no args" {
ast "{}" s[:function [] []]
ast "{|}" s[:function [] []]
}
test "function with no args" {
ast "{ 1 }" s[:function
[]
[s[:number 1]]]
}
test "multiline expression function with no args" {
ast "{\n1\n'a'\n}" s[:function
[]
[s[:number 1]
s[:simple_string :a]]]
}
test "function with one formal arg" {
ast "{ x | }" s[:function
[s[:arg :x]]
[]]
}
test "function with two formal args" {
ast "{ x, y | }" s[:function
[s[:arg :x]
s[:arg :y]]
[]]
}
test "function with default arg" {
ast "{ x = 1 | }" s[:function
[
s[:def_arg
:x
s[:number 1]]]
[]]
}
test "function with two default args" {
ast "{ x = 1, y = 2 | }" s[:function
[s[:def_arg
:x
s[:number 1]]
s[:def_arg
:y
s[:number 2]]]
[]]
}
test "function with variable args" {
ast "{ *x | }" s[:function
[s[:var_arg :x]]
[]]
}
test "function with plain and default args" {
ast "{ x, y = 1 | }" s[:function
[s[:arg :x]
s[:def_arg
:y
s[:number 1]]]
[]]
}
test "function with plain and variable args" {
ast "{ x, *y | }" s[:function
[s[:arg :x]
s[:var_arg :y]]
[]]
}
test "function with default args and variable args" {
ast "{ x = 1, *y | }" s[:function
[s[:def_arg
:x
s[:number 1]]
s[:var_arg :y]]
[]]
}
test "function with all kinds of args" {
ast "{ x, y = 1, *z | }" s[:function
[s[:arg :x]
s[:def_arg
:y
s[:number 1]]
s[:var_arg :z]]
[]]
}
test "unary operation" {
ast "~>1", s[:call s[:number 1], :_tilde_greater]
}
test "simple call no args" {
ast "x" s[:get_value :x]
}
test "simple call one arg" {
ast "x 1" s[:call
null
:x
[s[:number 1]]]
}
test "simple call two args" {
ast "x 1 []" s[:call
null
:x
[s[:number 1]
s[:array]]]
}
test "chain call no args" {
ast "x.y" s[:call
s[:get_value :x]
:y]
}
test "chain call two args" {
ast "point.new 1 2" s[:call
s[:get_value :point]
:new
[s[:number 1]
s[:number 2]]]
}
test "three chains" {
ast "x.y.z" s[:call
s[:call
s[:get_value :x]
:y]
:z]
}
test "paren call" {
ast "(x)(1)" s[:invoke
s[:get_value :x]
[s[:number 1]]]
}
test "variable assignment" {
ast "x = 1" s[:var_assign
:x
s[:number 1]]
}
test "field assignment to exp" {
ast "x.y = 1" s[:field_assign
s[:field_access
s[:get_value :x]
:y]
s[:number 1]]
}
test "nested field assignment to exp" {
ast "x.y.z = 1" s[:field_assign
s[:field_access
s[:call
s[:get_value :x]
:y]
:z]
s[:number 1]]
}
test "field assignment to function" {
ast "x.y = { }" s[:field_assign
s[:field_access
s[:get_value :x]
:y]
s[:function [] []]]
}
test "field assignment to method access" {
ast "x.y = ->z" s[:field_assign
s[:field_access
s[:get_value :x]
:y]
s[:meth_access, null, :z]]
}
test "binary operation" {
ast "2 * 2" s[:binop
s[:number 2]
:_star
s[:number 2]]
}
test "chained binary operation" {
ast "2 * 2 + 1" s[:binop
s[:number 2]
:_star
s[:number 2]
:_plus
s[:number 1]]
}
test "simple method access with variable" {
ast "->x" s[:meth_access, null, :x]
}
test "regular method access" {
ast "a->b" s[:meth_access, s[:get_value, :a], :b]
}
#*
# Since operators can't be defined without a target,
# I think it's okay to not allow them to be referenced
# without a target.
test "simple method access with operator" {
ast "->!" s[:meth_access, null, :_bang]
}
*#
test "simple paren method access" {
ast "->(x)" s[:meth_access
null
s[:get_value :x]]
}
test "paren method access" {
ast "(x)->y" s[:meth_access
s[:get_value :x]
:y]
}
test "simple index get" {
ast "x[1]" s[:call
s[:get_value :x]
:get
[s[:number 1]]]
}
test "nested index get" {
ast "x[1][:a]" s[:simple_index_get
s[:call
s[:get_value :x]
:get
[s[:number 1]]]
:get
[s[:simple_symbol :a]]]
}
test "simple index get invoke" {
ast "x[1](2)" s[:invoke_index_get
s[:call
s[:get_value :x]
:get
[s[:number 1]]]
[s[:number 2]]]
}
test "nested index get invoke" {
ast "x[1][2](:a)" s[:invoke_index_get
s[:call
s[:call
s[:get_value :x]
:get
[s[:number 1]]]
:get
[s[:number 2]]]
[s[:simple_symbol :a]]]
}
test "index get with multiple indexes" {
ast "x[1, 2]" s[:call
s[:get_value :x]
:get
[s[:number 1]
s[:number 2]]]
}
test "index get with multiple indexes" {
ast "x[1, 2][3, 4]" s[:call
s[:call
s[:get_value :x]
:get
[s[:number 1]
s[:number 2]]]
:get
[s[:number 3]
s[:number 4]]]
}
test "index get binary op" {
ast "x[1] + y[2]" s[:binop
s[:call
s[:get_value :x]
:get
[s[:number 1]]]
:_plus
s[:call
s[:get_value :y]
:get
[s[:number 2]]]]
}
test "index get unary op" {
ast "@x[1]" s[:call
s[:call
s[:get_value :x]
:get
[s[:number 1]]]
:_at]
}
test "simple index set" {
ast "x[1] = :a" s[:call
s[:get_value :x]
:set
[s[:number 1]
s[:simple_symbol :a]]]
}
test "simple symbol index set" {
ast "x[:a] = 1" s[:simple_index_set
s[:get_value :x]
:set
[s[:simple_symbol :a]
s[:number 1]]]
}
test "nested index set" {
ast "x[1][2] = :a" s[:call
s[:call
s[:get_value :x]
:get
[s[:number 1]]]
:set
[s[:number 2]
s[:simple_symbol :a]]]
}
test "string hash value" {
ast "[:! : 1]" s[:hash
[s[:simple_symbol, "!"], s[:number, 1]]]
}
test "parenthesized expression" {
ast "1 / (2 + 3)" s[:binop
s[:number 1]
:_forward
s[:binop s[:number 2], :_plus s[:number 3]]]
}
test "var assign at beginning of function" {
ast "{ a = 1 }" s[:function
[]
[s[:var_assign, :a, s[:number 1]]]]
}
test "method call on array literal access" {
ast "[1][0].to_s" s[:call
s[:call
s[:array, s[:number 1]]
:get
[s[:number 0]]]
:to_unders]
}
}