include :assert
include :peg

add_results setup name: "PEG tests" {
  parses = { grammar, input, fully = true |
    parser = peg.new {
      set :rule grammar
    }

    assert parser.parse(input, null, fully) "#{input} did not parse"
  }

  doesnt_parse = { grammar, input, fully = true |
    parser = peg.new {
      set :rule grammar
    }

    assert_false parser.parse(input, null, fully) "#{input} parsed, but was not expected to"
  }

  test "string literal" {
    parses { str(:hi) } :hi
  }

  test "sequence" {
    parses { seq str(:h) str(:i) } :hi
    parses { seq str(:h) } :h
  }

  test "many" {
    parses { many str :l } :llllll
    parses { many str :l } :l
    doesnt_parse { many str :l } :x
  }

  test "maybe" {
    parses { maybe str :h } :h
    parses { maybe str :h } :x false
  }

  test "kleene star" {
    parses { kleene str :h } :hhhhhhhhh
    parses { kleene str :h } :h
    parses { kleene str :h } :nope false

    grams = peg.new {
      set :test kleene str :h
    }

    assert_equal 7 { grams.parse(:hhhhhhh :test).elements.length }
  }

  test "any" {
    parses { any str(:h) str(:i) } :h
    parses { any str(:h) str(:i) } :i
    parses { any str(:i) } :i
    doesnt_parse { any str(:h) str(:i) } :n false
  }

  test "that's a negative" {
    parses { seq(str(:h) no(str(:x)) str(:i)) } :hi
    parses { seq(str(:h) any(no(str(:x)) str(:x))) } :hx
    doesnt_parse { seq(str(:h) no(str(:x))) } :hx 
  }

  test "regex literal" {
    parses { reg /[a-z]+\d+!\d+/ } "omgz11!111"
    doesnt_parse { reg /[a-z]+\d+!\d+/ } "#omgz11!111"
  }

  test "named rules" {
    number_parser = peg.new {
      set :number seq maybe(str('-')) kleene(ref :digit) maybe(seq(str(".") many(ref :digit)))
      set :digit any(str('0') str('1'))
    }

    assert number_parser.parse("1" :number true) "Didn't parse 1"
    assert number_parser.parse("10" :number true) "Didn't parse 10"
    assert number_parser.parse("1.0" :number true) "Didn't parse 1.0"
    assert number_parser.parse("10.0" :number true) "Didn't parse 10.0"
    assert number_parser.parse("10.10" :number true) "Didn't parse 10.10"

    result = number_parser.parse "-1"
    assert_equal :number result.rule_name
    assert_equal :result? result.elements.first.rule_name
    assert_equal :digit* result.elements[1].rule_name
  }

  test "simple calculator" {
    simple_calc = peg.new {
      set :exp any(ref(:paren) ref(:binary) ref(:number))
       set :number many(reg(/[0-9]/))
       set :binary seq(ref(:number) ref(:op) ref(:exp))
       set :op any(str("+") str("*") str("/") str("-"))
       set :paren seq(str("(") ref(:exp) str(")"))
    }

    assert simple_calc.parse("1-(1+1/1+2+3+(4*11))", :exp, true)
  }

  test "simple backtracking" {
    path = peg.new {
      set :a any(ref(:b) ref(:c))
       set :b seq(str(:x) ref(:d) str(:y))
       set :c seq(str(:x) ref(:d) str(:z))
       set :d any(ref(:a) and(reg //))
    }

    assert path.parse "xxxzzz", :a, true
  }

  test "recursive reference" {
    rec = peg.new {
      set :a seq(str("a") any(ref(:a) str("b")))
    }

    assert rec.parse "aaab", :a, true
  }

  test "anything" {
    parses { anything } "h"
    parses { kleene anything } "hell\no!"
  }

  test "parse comments" {
    comment = peg.new {
     set :comment seq(str("#*") kleene(seq(no(str("*#")) any(ref(:comment) reg(/./m)))) str("*#"))
    }

    assert comment.parse "#* comment *#" :comment true
    assert comment.parse "#*\ncomment\n*#" :comment true
  }

  test "labels" {
    calc = peg.new {
      set :spaces reg /\s*/
      set :num action reg(/[1-9][0-9]*/) { my.result = matched.full_match.to_i }
      set :plus action seq(label(:lhs ref(:num)) ref(:spaces) str(:+) ref(:spaces) label(:rhs ref(:num))) { my.result = lhs.result + rhs.result }
      set :test label(:hs kleene str :h)
      set :nested action label(:thing seq str(:x) label(:thing str(:y))) { my.result = my.thing }
    }

    assert calc.parse("1+1" :plus true)
    assert_equal 2 { calc.parse("1+1" :plus true).result }
    assert_equal 928 { calc.parse("40 + 888" :plus true).result }
    assert_equal 7 { calc.parse(:hhhhhhh :test).elements.length }
    assert_equal :xy { calc.parse(:xy :nested).result.text  } 
  }

  test "any_ref" {
    refs = peg.new {
      set :a str(:a)
      set :b str(:b)
      set :c any_ref :a :b
    }

    assert { refs.parse "a" :c true }
    assert { refs.parse "b" :c true }
    assert_false { refs.parse "c" :c true }
    assert_false { refs.parse "ab" :c true }
  }

  test "seq_ref" {
    refs = peg.new {
      set :a str(:d)
      set :b str(:e)
      set :c seq_ref :a :b
    }

    assert { refs.parse "de" :c true }
    assert_false { refs.parse "ab" :c true }
    assert_false { refs.parse "a" :c true }
  }
}