diff --git a/lib/tsgenruby.pl b/lib/tsgenruby.pl new file mode 100644 index 0000000000000000000000000000000000000000..ebcc86a71042210543a2a12ba9786e079b4bceb8 --- /dev/null +++ b/lib/tsgenruby.pl @@ -0,0 +1,258 @@ +% -*- prolog -*- +% Run with: +% $ swipl -q -t halt -g tsgenruby:prolog tsgenruby.pl >output-file +% Note: this needs to be run from the lib/ directory. + +% So, from the ruby directory +% (cd ../../../lib && swipl -q -t halt -g tsgenruby:prolog tsgenruby.pl ) >test/autogen_test.rb + + + +:- module(tsgenruby, + [gen_ts/2]). + +:- expects_dialect(swi). +:- use_module(tsparser). +:- use_module(library(record)). + +:- record testsuite_state(parser_no:integer = 0, test_no:integer=0). +% TODO: build a Box-like pretty-printer + +to_title_case([], []) :- !. +to_title_case([WSep,S0|Ss], [R0|Rs]) :- + memberchk(WSep, "_-"), !, + code_type(R0, to_upper(S0)), + to_title_case(Ss,Rs). +to_title_case([S0|Ss], [S0|Rs]) :- + \+ memberchk(S0, "_-"), + !, to_title_case(Ss,Rs). + +format_parser_name(Name, Result) :- + atom_codes(Name, CName), + append("h.", CName, Result), !. + +format_test_name(Name, Result) :- + atom_codes(Name, CName), + to_title_case([0x5f|CName], RName), + append("Test", RName, Result), !. + +indent(0) --> "", !. +indent(N) --> + {N > 0}, + " ", + {Np is N - 1}, + indent(Np). + +pp_char_guts(0x22) --> + "\\\"", !. +pp_char_guts(0x27) --> + "\\'", !. +pp_char_guts(A) --> + { A >= 0x20, A < 0x7F } -> + [A]; + "\\x", + { H is A >> 4, L is A /\ 0xF, + code_type(Hc, xdigit(H)), + code_type(Lc, xdigit(L)) }, + [Hc,Lc]. + +pp_hexnum_guts(0) --> !. +pp_hexnum_guts(A) --> + { L is A /\ 0xF, + H is A >> 4, + code_type(Lc, xdigit(L)) }, + pp_hexnum_guts(H), + [Lc], !. +pp_string_guts([]) --> !. +pp_string_guts([X|Xs]) --> + pp_char_guts(X), + pp_string_guts(Xs), !. + +pp_parser_args([]) --> !. +pp_parser_args([X|Rest]) --> + pp_parser(X), + pp_parser_args_rest(Rest). +pp_parser_args_rest([]) --> !. +pp_parser_args_rest([X|Xs]) --> + ", ", + pp_parser(X), + pp_parser_args_rest(Xs). + +pp_parser(parser(Name, Args)) --> + !, + {format_parser_name(Name,Fname)}, + Fname, + ({Args \= []} -> + + "(", pp_parser_args(Args), ")" + ; "") . +pp_parser(string(Str)) --> !, + "\"", + pp_string_guts(Str), + "\"", !. +pp_parser(num(0)) --> "0", !. +pp_parser(num(Num)) --> !, + ( {Num < 0} -> + "-0x", {RNum is -Num}; "0x", {RNum = Num} ), + pp_hexnum_guts(RNum). +pp_parser(char(C)) --> !, + "'", pp_char_guts(C), "'", !. + +pp_parser(ref(Name)) --> + {atom_codes(Name,CName)}, + "@sp_", CName, !. + + +pp_parser(A) --> + { writef("WTF is a %w?\n", [A]), + !, fail + }. + +upd_state_test_elem(parser(_), OldSt, NewSt) :- !, + testsuite_state_parser_no(OldSt, OldRNo), + NewRNo is OldRNo + 1, + set_parser_no_of_testsuite_state(NewRNo, OldSt, NewSt). +upd_state_test_elem(test(_, _), OldSt, NewSt) :- !, + testsuite_state_test_no(OldSt, OldTNo), + NewTNo is OldTNo + 1, + set_test_no_of_testsuite_state(NewTNo, OldSt, NewSt). +upd_state_test_elem(testFail(_), OldSt, NewSt) :- !, + testsuite_state_test_no(OldSt, OldTNo), + NewTNo is OldTNo + 1, + set_test_no_of_testsuite_state(NewTNo, OldSt, NewSt). +upd_state_test_elem(_, St, St). + +curparser_name(St) --> !, + { testsuite_state_parser_no(St, RNo), + format(string(X), "@parser_~w", RNo) }, + X. +curtest_name(St) --> !, + { testsuite_state_test_no(St, RNo), + format(string(X), "test_~w", RNo) }, + X. + +pp_test_elem(decl, parser(_), _) --> !. +pp_test_elem(init, parser(P), St) --> + !, indent(2), + curparser_name(St), " = ", + pp_parser(P), + "\n". +pp_test_elem(exec, parser(_), _) --> !. +pp_test_elem(decl, subparser(Name,_), _) --> + !, indent(2), + pp_parser(ref(Name)), + " = ", + pp_parser(parser(indirect,[])), + "\n". +pp_test_elem(init, subparser(Name, Parser), _) --> + !, indent(2), + pp_parser(ref(Name)), ".bind ", + pp_parser(Parser), + "\n". +pp_test_elem(exec, subparser(_,_), _) --> !. +pp_test_elem(decl, test(_,_), _) --> !. +pp_test_elem(init, test(_,_), _) --> !. +pp_test_elem(decl, testFail(_), _) --> !. +pp_test_elem(init, testFail(_), _) --> !. +pp_test_elem(exec, test(Str, Result), St) --> + !, + "\n", + indent(1), "def ", curtest_name(St), "\n", + indent(2), "assert_parse_ok ", curparser_name(St), ", ", pp_parser(string(Str)), + ", ", + pp_parse_result(Result), + "\n", + indent(1), "end\n". +pp_test_elem(exec, testFail(Str), St) --> + !, + "\n", + indent(1), "def ", curtest_name(St), "\n", + indent(2), "refute_parse_ok ", curparser_name(St), ", ", pp_parser(string(Str)), "\n", + indent(1), "end\n". + +% pp_test_elem(_, _) --> !. + +pp_result_seq([]) --> !. +pp_result_seq([X|Xs]) --> !, + pp_parse_result(X), + pp_result_seq_r(Xs). +pp_result_seq_r([]) --> !. +pp_result_seq_r([X|Xs]) --> !, + ", ", + pp_parse_result(X), + pp_result_seq_r(Xs). + +pp_byte_seq([]) --> !. +pp_byte_seq([X|Xs]) --> !, + pp_parser(num(X)), + pp_byte_seq_r(Xs). +pp_byte_seq_r([]) --> !. +pp_byte_seq_r([X|Xs]) --> !, + ", ", + pp_parser(num(X)), + pp_byte_seq_r(Xs). + +pp_parse_result(char(C)) --> !, + %"(System.UInt64)", + pp_parser(char(C)). +pp_parse_result(seq(Args)) --> !, + "[", pp_result_seq(Args), "]". +pp_parse_result(none) --> !, + "null". +pp_parse_result(uint(V)) --> !, + pp_parser(num(V)). +pp_parse_result(sint(V)) --> !, + pp_parser(num(V)). +pp_parse_result(string(A)) --> !, + pp_parser(string(A)). + +%pp_parse_result(A) --> +% "\x1b[1;31m", +% {with_output_to(codes(C), write(A))}, +% C, +% "\x1b[0m". + + +pp_test_elems(Phase, Elems) --> + { default_testsuite_state(State) }, + pp_test_elems(Phase, Elems, State). +pp_test_elems(_, [], _) --> !. +pp_test_elems(Phase, [X|Xs], St) --> + !, + { upd_state_test_elem(X, St, NewSt) }, + %{NewSt = St}, + pp_test_elem(Phase,X, NewSt), + pp_test_elems(Phase,Xs, NewSt). + +pp_test_case(testcase(Name, Elems)) --> + !, + { format_test_name(Name, TName) }, + indent(0), "class ", TName, " < Minitest::Test\n", + indent(1), "def setup\n", + indent(2), "super\n", + indent(2), "h = Hammer::Parser\n", + pp_test_elems(decl, Elems), + pp_test_elems(init, Elems), + indent(1), "end\n", + pp_test_elems(exec, Elems), + indent(0), "end\n\n". + + +pp_test_cases([]) --> !. +pp_test_cases([A|As]) --> + pp_test_case(A), + pp_test_cases(As). + +pp_test_suite(Suite) --> + "require 'bundler/setup'\n", + "require 'minitest/autorun'\n", + "require 'hammer'\n", + pp_test_cases(Suite). + +gen_ts(Foo,Str) :- + phrase(pp_test_suite(Foo),Str). + +prolog :- + read_tc(A), + gen_ts(A, Res), + writef("%s", [Res]). diff --git a/lib/testgen.pl b/lib/tsparser.pl similarity index 100% rename from lib/testgen.pl rename to lib/tsparser.pl diff --git a/src/bindings/ruby/Rakefile b/src/bindings/ruby/Rakefile index 70b8662375affb9a9da753d4c2e434ef8748315f..c73847038dbb1c07cdaec20738a4acb3008bdafd 100644 --- a/src/bindings/ruby/Rakefile +++ b/src/bindings/ruby/Rakefile @@ -1,7 +1,8 @@ require 'rake/testtask' Rake::TestTask.new do |t| - t.pattern = "test/*_test.rb" + #t.pattern = "test/*_test.rb" + t.test_files = FileList['test/*_test.rb'] end task :default => [:test] diff --git a/src/bindings/ruby/lib/hammer.rb b/src/bindings/ruby/lib/hammer.rb index ebc75be3ea28b33a0d4d4400a4850fa66c5aff33..cec33fc00be11ce94d1efa5fd3d645fe051f43f0 100644 --- a/src/bindings/ruby/lib/hammer.rb +++ b/src/bindings/ruby/lib/hammer.rb @@ -19,14 +19,14 @@ x.bind(Hammer::Parser.token('abd')) #$p = parser $r = parser.parse 'abcabd' -p $r[:ast][:data][:seq].elements.map {|e| e[:data][:bytes].token } +#p $r[:ast][:data][:seq].elements.map {|e| e[:data][:bytes].token } h = Hammer::Parser parser = h.many( h.action(h.uint8) { |r| - p "TT=#{r[:ast][:token_type]}, value=#{r[:ast][:data][:uint]}" + #p "TT=#{r[:ast][:token_type]}, value=#{r[:ast][:data][:uint]}" r[:ast][:data][:uint] *= 2 r[:ast] if r[:ast][:data][:uint] % 3 == 0 }) @@ -43,11 +43,11 @@ parser = $r = parser.parse 'abcdefgh' -p $r[:ast][:data][:seq].elements.map {|e| e[:data][:uint]} +#p $r[:ast][:data][:seq].elements.map {|e| e[:data][:uint]} # or: -p $r.ast.data.map(&:data) +#p $r.ast.data.map(&:data) h = Hammer::Parser parser = h.many(h.attr_bool(h.uint8) { |r| r.ast.data <= 100 }) -p parser.parse('abcdefgh').ast.data.map(&:data) +#p parser.parse('abcdefgh').ast.data.map(&:data) diff --git a/src/bindings/ruby/lib/hammer/internal.rb b/src/bindings/ruby/lib/hammer/internal.rb index 63d25277a6d780a66ea66646739b5c8153399eaa..03bc45de9045d508bf123da1b4e943758c87864a 100644 --- a/src/bindings/ruby/lib/hammer/internal.rb +++ b/src/bindings/ruby/lib/hammer/internal.rb @@ -102,6 +102,23 @@ module Hammer def bit_offset self[:bit_offset] end + + def unmarshal + case token_type + when :sequence + self[:data][:seq].each {|x| x.unmarshal} + when :bytes + self[:data][:bytes].token + when :uint + self[:data][:uint] + when :sint + self[:data][:sint] + when :none + nil + end + end + + end class HParseResult < FFI::Struct diff --git a/src/bindings/ruby/lib/minitest/hamer-parser_plugin.rb b/src/bindings/ruby/lib/minitest/hamer-parser_plugin.rb new file mode 100644 index 0000000000000000000000000000000000000000..393a540bb39a89a656d48ee2fd8efe8ef128c294 --- /dev/null +++ b/src/bindings/ruby/lib/minitest/hamer-parser_plugin.rb @@ -0,0 +1,28 @@ +module Minitest + + module Assertions + HAMMER_JUST_PARSE = Object.new + def assert_parse_ok(parser, probe, expected=HAMMER_JUST_PARSE) + refute_nil parser, "Parser must not be nil (this is a problem with your test)" + parse_result = parser.parse(probe) + refute_nil parse_result, "Parse failed" + if HAMMER_JUST_PARSE != expected + if parse_result.ast == nil + assert_nil expected, "Parser returned nil AST; expected #{expected}" + else + assert_equal parse_result.ast.unmarshal, expected + end + end + end + + def refute_parse_ok(parser, probe) + refute_nil parser, "Parser must not be nil (this is a problem with your test)" + parse_result = parser.parse(probe) + assert_nil parse_result, "Parse succeeded unexpectedly with " + parse_result.ast.inspect + end + end + + + #def self.plugin_hammer-parser_init(options) +end + diff --git a/src/bindings/ruby/test/autogen_test.rb b/src/bindings/ruby/test/autogen_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..a2566bc066c6287198b7c84ad6002526a943390a --- /dev/null +++ b/src/bindings/ruby/test/autogen_test.rb @@ -0,0 +1,781 @@ +require 'bundler/setup' +require 'minitest/autorun' +require 'hammer' +class TestToken < Minitest::Test + def setup + super + h = Hammer::Parser + @parser_1 = h.token("95\xa2") + end + + def test_1 + assert_parse_ok @parser_1, "95\xa2", "95\xa2" + end + + def test_2 + refute_parse_ok @parser_1, "95\xa2" + end +end + +class TestCh < Minitest::Test + def setup + super + h = Hammer::Parser + @parser_1 = h.ch(0xa2) + end + + def test_1 + assert_parse_ok @parser_1, "\xa2", '\xa2' + end + + def test_2 + refute_parse_ok @parser_1, "\xa3" + end +end + +class TestChRange < Minitest::Test + def setup + super + h = Hammer::Parser + @parser_1 = h.ch_range(0x61, 0x63) + end + + def test_1 + assert_parse_ok @parser_1, "b", 'b' + end + + def test_2 + refute_parse_ok @parser_1, "d" + end +end + +class TestInt64 < Minitest::Test + def setup + super + h = Hammer::Parser + @parser_1 = h.int64 + end + + def test_1 + assert_parse_ok @parser_1, "\xff\xff\xff\xfe\x00\x00\x00\x00", -0x200000000 + end + + def test_2 + refute_parse_ok @parser_1, "\xff\xff\xff\xfe\x00\x00\x00" + end +end + +class TestInt32 < Minitest::Test + def setup + super + h = Hammer::Parser + @parser_1 = h.int32 + end + + def test_1 + assert_parse_ok @parser_1, "\xff\xfe\x00\x00", -0x20000 + end + + def test_2 + refute_parse_ok @parser_1, "\xff\xfe\x00" + end + + def test_3 + assert_parse_ok @parser_1, "\x00\x02\x00\x00", 0x20000 + end + + def test_4 + refute_parse_ok @parser_1, "\x00\x02\x00" + end +end + +class TestInt16 < Minitest::Test + def setup + super + h = Hammer::Parser + @parser_1 = h.int16 + end + + def test_1 + assert_parse_ok @parser_1, "\xfe\x00", -0x200 + end + + def test_2 + refute_parse_ok @parser_1, "\xfe" + end + + def test_3 + assert_parse_ok @parser_1, "\x02\x00", 0x200 + end + + def test_4 + refute_parse_ok @parser_1, "\x02" + end +end + +class TestInt8 < Minitest::Test + def setup + super + h = Hammer::Parser + @parser_1 = h.int8 + end + + def test_1 + assert_parse_ok @parser_1, "\x88", -0x78 + end + + def test_2 + refute_parse_ok @parser_1, "" + end +end + +class TestUint64 < Minitest::Test + def setup + super + h = Hammer::Parser + @parser_1 = h.uint64 + end + + def test_1 + assert_parse_ok @parser_1, "\x00\x00\x00\x02\x00\x00\x00\x00", 0x200000000 + end + + def test_2 + refute_parse_ok @parser_1, "\x00\x00\x00\x02\x00\x00\x00" + end +end + +class TestUint32 < Minitest::Test + def setup + super + h = Hammer::Parser + @parser_1 = h.uint32 + end + + def test_1 + assert_parse_ok @parser_1, "\x00\x02\x00\x00", 0x20000 + end + + def test_2 + refute_parse_ok @parser_1, "\x00\x02\x00" + end +end + +class TestUint16 < Minitest::Test + def setup + super + h = Hammer::Parser + @parser_1 = h.uint16 + end + + def test_1 + assert_parse_ok @parser_1, "\x02\x00", 0x200 + end + + def test_2 + refute_parse_ok @parser_1, "\x02" + end +end + +class TestUint8 < Minitest::Test + def setup + super + h = Hammer::Parser + @parser_1 = h.uint8 + end + + def test_1 + assert_parse_ok @parser_1, "x", 0x78 + end + + def test_2 + refute_parse_ok @parser_1, "" + end +end + +class TestIntRange < Minitest::Test + def setup + super + h = Hammer::Parser + @parser_1 = h.int_range(h.uint8, 0x3, 0x10) + end + + def test_1 + assert_parse_ok @parser_1, "\x05", 0x5 + end + + def test_2 + refute_parse_ok @parser_1, "\x0b" + end +end + +class TestWhitespace < Minitest::Test + def setup + super + h = Hammer::Parser + @parser_1 = h.whitespace(h.ch(0x61)) + @parser_2 = h.whitespace(h.end_p) + end + + def test_1 + assert_parse_ok @parser_1, "a", 'a' + end + + def test_2 + assert_parse_ok @parser_1, " a", 'a' + end + + def test_3 + assert_parse_ok @parser_1, " a", 'a' + end + + def test_4 + assert_parse_ok @parser_1, "\x09a", 'a' + end + + def test_5 + refute_parse_ok @parser_1, "_a" + end + + def test_6 + assert_parse_ok @parser_2, "", null + end + + def test_7 + assert_parse_ok @parser_2, " ", null + end + + def test_8 + refute_parse_ok @parser_2, " x" + end +end + +class TestLeft < Minitest::Test + def setup + super + h = Hammer::Parser + @parser_1 = h.left(h.ch(0x61), h.ch(0x20)) + end + + def test_1 + assert_parse_ok @parser_1, "a ", 'a' + end + + def test_2 + refute_parse_ok @parser_1, "a" + end + + def test_3 + refute_parse_ok @parser_1, " " + end + + def test_4 + refute_parse_ok @parser_1, "ba" + end +end + +class TestMiddle < Minitest::Test + def setup + super + h = Hammer::Parser + @parser_1 = h.middle(h.ch(' '), h.ch('a'), h.ch(' ')) + end + + def test_1 + assert_parse_ok @parser_1, " a ", 'a' + end + + def test_2 + refute_parse_ok @parser_1, "a" + end + + def test_3 + refute_parse_ok @parser_1, " a" + end + + def test_4 + refute_parse_ok @parser_1, "a " + end + + def test_5 + refute_parse_ok @parser_1, " b " + end + + def test_6 + refute_parse_ok @parser_1, "ba " + end + + def test_7 + refute_parse_ok @parser_1, " ab" + end +end + +class TestIn < Minitest::Test + def setup + super + h = Hammer::Parser + @parser_1 = h.in("abc") + end + + def test_1 + assert_parse_ok @parser_1, "b", 'b' + end + + def test_2 + refute_parse_ok @parser_1, "d" + end +end + +class TestNotIn < Minitest::Test + def setup + super + h = Hammer::Parser + @parser_1 = h.not_in("abc") + end + + def test_1 + assert_parse_ok @parser_1, "d", 'd' + end + + def test_2 + refute_parse_ok @parser_1, "a" + end +end + +class TestEndP < Minitest::Test + def setup + super + h = Hammer::Parser + @parser_1 = h.sequence(h.ch('a'), h.end_p) + end + + def test_1 + assert_parse_ok @parser_1, "a", ['a'] + end + + def test_2 + refute_parse_ok @parser_1, "aa" + end +end + +class TestNothingP < Minitest::Test + def setup + super + h = Hammer::Parser + @parser_1 = h.nothing_p + end + + def test_1 + refute_parse_ok @parser_1, "a" + end +end + +class TestSequence < Minitest::Test + def setup + super + h = Hammer::Parser + @parser_1 = h.sequence(h.ch('a'), h.ch('b')) + @parser_2 = h.sequence(h.ch('a'), h.whitespace(h.ch('b'))) + end + + def test_1 + assert_parse_ok @parser_1, "ab", ['a', 'b'] + end + + def test_2 + refute_parse_ok @parser_1, "a" + end + + def test_3 + refute_parse_ok @parser_1, "b" + end + + def test_4 + assert_parse_ok @parser_2, "ab", ['a', 'b'] + end + + def test_5 + assert_parse_ok @parser_2, "a b", ['a', 'b'] + end + + def test_6 + assert_parse_ok @parser_2, "a b", ['a', 'b'] + end +end + +class TestChoice < Minitest::Test + def setup + super + h = Hammer::Parser + @parser_1 = h.choice(h.ch('a'), h.ch('b')) + end + + def test_1 + assert_parse_ok @parser_1, "a", 'a' + end + + def test_2 + assert_parse_ok @parser_1, "b", 'b' + end + + def test_3 + assert_parse_ok @parser_1, "ab", 'a' + end + + def test_4 + refute_parse_ok @parser_1, "c" + end +end + +class TestButnot < Minitest::Test + def setup + super + h = Hammer::Parser + @parser_1 = h.butnot(h.ch('a'), h.token("ab")) + @parser_2 = h.butnot(h.ch_range('0', '9'), h.ch('6')) + end + + def test_1 + assert_parse_ok @parser_1, "a", 'a' + end + + def test_2 + refute_parse_ok @parser_1, "ab" + end + + def test_3 + assert_parse_ok @parser_1, "aa", 'a' + end + + def test_4 + assert_parse_ok @parser_2, "5", '5' + end + + def test_5 + refute_parse_ok @parser_2, "6" + end +end + +class TestDifference < Minitest::Test + def setup + super + h = Hammer::Parser + @parser_1 = h.difference(h.token("ab"), h.ch('a')) + end + + def test_1 + assert_parse_ok @parser_1, "ab", "ab" + end + + def test_2 + refute_parse_ok @parser_1, "a" + end +end + +class TestXor < Minitest::Test + def setup + super + h = Hammer::Parser + @parser_1 = h.xor(h.ch_range('0', '6'), h.ch_range('5', '9')) + end + + def test_1 + assert_parse_ok @parser_1, "0", '0' + end + + def test_2 + assert_parse_ok @parser_1, "9", '9' + end + + def test_3 + refute_parse_ok @parser_1, "5" + end + + def test_4 + refute_parse_ok @parser_1, "a" + end +end + +class TestMany < Minitest::Test + def setup + super + h = Hammer::Parser + @parser_1 = h.many(h.choice(h.ch('a'), h.ch('b'))) + end + + def test_1 + assert_parse_ok @parser_1, "", [] + end + + def test_2 + assert_parse_ok @parser_1, "a", ['a'] + end + + def test_3 + assert_parse_ok @parser_1, "b", ['b'] + end + + def test_4 + assert_parse_ok @parser_1, "aabbaba", ['a', 'a', 'b', 'b', 'a', 'b', 'a'] + end +end + +class TestMany1 < Minitest::Test + def setup + super + h = Hammer::Parser + @parser_1 = h.many1(h.choice(h.ch('a'), h.ch('b'))) + end + + def test_1 + refute_parse_ok @parser_1, "" + end + + def test_2 + assert_parse_ok @parser_1, "a", ['a'] + end + + def test_3 + assert_parse_ok @parser_1, "b", ['b'] + end + + def test_4 + assert_parse_ok @parser_1, "aabbaba", ['a', 'a', 'b', 'b', 'a', 'b', 'a'] + end + + def test_5 + refute_parse_ok @parser_1, "daabbabadef" + end +end + +class TestRepeatN < Minitest::Test + def setup + super + h = Hammer::Parser + @parser_1 = h.repeat_n(h.choice(h.ch('a'), h.ch('b')), 0x2) + end + + def test_1 + refute_parse_ok @parser_1, "adef" + end + + def test_2 + assert_parse_ok @parser_1, "abdef", ['a', 'b'] + end + + def test_3 + refute_parse_ok @parser_1, "dabdef" + end +end + +class TestOptional < Minitest::Test + def setup + super + h = Hammer::Parser + @parser_1 = h.sequence(h.ch('a'), h.optional(h.choice(h.ch('b'), h.ch('c'))), h.ch('d')) + end + + def test_1 + assert_parse_ok @parser_1, "abd", ['a', 'b', 'd'] + end + + def test_2 + assert_parse_ok @parser_1, "acd", ['a', 'c', 'd'] + end + + def test_3 + assert_parse_ok @parser_1, "ad", ['a', null, 'd'] + end + + def test_4 + refute_parse_ok @parser_1, "aed" + end + + def test_5 + refute_parse_ok @parser_1, "ab" + end + + def test_6 + refute_parse_ok @parser_1, "ac" + end +end + +class TestIgnore < Minitest::Test + def setup + super + h = Hammer::Parser + @parser_1 = h.sequence(h.ch('a'), h.ignore(h.ch('b')), h.ch('c')) + end + + def test_1 + assert_parse_ok @parser_1, "abc", ['a', 'c'] + end + + def test_2 + refute_parse_ok @parser_1, "ac" + end +end + +class TestSepBy < Minitest::Test + def setup + super + h = Hammer::Parser + @parser_1 = h.sepBy(h.choice(h.ch('1'), h.ch('2'), h.ch('3')), h.ch(',')) + end + + def test_1 + assert_parse_ok @parser_1, "1,2,3", ['1', '2', '3'] + end + + def test_2 + assert_parse_ok @parser_1, "1,3,2", ['1', '3', '2'] + end + + def test_3 + assert_parse_ok @parser_1, "1,3", ['1', '3'] + end + + def test_4 + assert_parse_ok @parser_1, "3", ['3'] + end + + def test_5 + assert_parse_ok @parser_1, "", [] + end +end + +class TestSepBy1 < Minitest::Test + def setup + super + h = Hammer::Parser + @parser_1 = h.sepBy1(h.choice(h.ch('1'), h.ch('2'), h.ch('3')), h.ch(',')) + end + + def test_1 + assert_parse_ok @parser_1, "1,2,3", ['1', '2', '3'] + end + + def test_2 + assert_parse_ok @parser_1, "1,3,2", ['1', '3', '2'] + end + + def test_3 + assert_parse_ok @parser_1, "1,3", ['1', '3'] + end + + def test_4 + assert_parse_ok @parser_1, "3", ['3'] + end + + def test_5 + refute_parse_ok @parser_1, "" + end +end + +class TestAnd < Minitest::Test + def setup + super + h = Hammer::Parser + @parser_1 = h.sequence(h.and(h.ch('0')), h.ch('0')) + @parser_2 = h.sequence(h.and(h.ch('0')), h.ch('1')) + @parser_3 = h.sequence(h.ch('1'), h.and(h.ch('2'))) + end + + def test_1 + assert_parse_ok @parser_1, "0", ['0'] + end + + def test_2 + refute_parse_ok @parser_1, "1" + end + + def test_3 + refute_parse_ok @parser_2, "0" + end + + def test_4 + refute_parse_ok @parser_2, "1" + end + + def test_5 + assert_parse_ok @parser_3, "12", ['1'] + end + + def test_6 + refute_parse_ok @parser_3, "13" + end +end + +class TestNot < Minitest::Test + def setup + super + h = Hammer::Parser + @parser_1 = h.sequence(h.ch('a'), h.choice(h.token("+"), h.token("++")), h.ch('b')) + @parser_2 = h.sequence(h.ch('a'), h.choice(h.sequence(h.token("+"), h.not(h.ch('+'))), h.token("++")), h.ch('b')) + end + + def test_1 + assert_parse_ok @parser_1, "a+b", ['a', "+", 'b'] + end + + def test_2 + refute_parse_ok @parser_1, "a++b" + end + + def test_3 + assert_parse_ok @parser_2, "a+b", ['a', ["+"], 'b'] + end + + def test_4 + assert_parse_ok @parser_2, "a++b", ['a', "++", 'b'] + end +end + +class TestRightrec < Minitest::Test + def setup + super + h = Hammer::Parser + @sp_rr = h.indirect + @sp_rr.bind h.choice(h.sequence(h.ch('a'), @sp_rr), h.epsilon_p) + @parser_1 = @sp_rr + end + + def test_1 + assert_parse_ok @parser_1, "a", ['a'] + end + + def test_2 + assert_parse_ok @parser_1, "aa", ['a', ['a']] + end + + def test_3 + assert_parse_ok @parser_1, "aaa", ['a', ['a', ['a']]] + end +end + +class TestAmbiguous < Minitest::Test + def setup + super + h = Hammer::Parser + @sp_d = h.indirect + @sp_p = h.indirect + @sp_e = h.indirect + @sp_d.bind h.ch('d') + @sp_p.bind h.ch('+') + @sp_e.bind h.choice(h.sequence(@sp_e, @sp_p, @sp_e), @sp_d) + @parser_1 = @sp_e + end + + def test_1 + assert_parse_ok @parser_1, "d", 'd' + end + + def test_2 + assert_parse_ok @parser_1, "d+d", ['d', '+', 'd'] + end + + def test_3 + assert_parse_ok @parser_1, "d+d+d", [['d', '+', 'd'], '+', 'd'] + end +end +