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