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]
  }
}