#require "./spec_helper" # #require "../src/parcom.cr" # #include Parcom # #pending Tokens do # pending ".from_string" do # it "constructs a Tokens(Char) from a String" do # tokens = Tokens.from_string("abcd") # tokens.tokens.should eq("abcd".chars) # end # end # # pending "#initialize" do # it "wraps an array with the contents of the given iterable" do # set = Set{'a', 'b', 'c', 'd'} # tokens = Tokens.new(set) # tokens.tokens.should eq(set.to_a) # # arr = "abcd".chars # tokens = Tokens.new(arr) # tokens.tokens.should eq(arr) # end # end # # context do # tokens_empty = Tokens.new([] of Char) # tokens = Tokens.from_string("abcd") # # pending "#[]" do # it "returns the token at the given index" do # tokens[2].should eq('c') # expect_raises(IndexError) { tokens_empty[2] } # end # # it "returns a new Tokens similar to Array#[](Int, Int)" do # tokens[1, 5].should eq(Tokens.new(['b', 'c', 'd'])) # expect_raises(IndexError) { tokens_empty[1, 5] } # end # # it "returns a new Tokens similar to Array#[](Range)" do # tokens[1..3].should eq(Tokens.new(['b', 'c', 'd'])) # expect_raises(IndexError) { tokens_empty[1..3] } # end # end # # pending "#[]?" do # it "analogous to `Array#[]?`" do # # we should only need to check the nil-returning cases # tokens_empty[2]?.should be_nil # tokens_empty[1, 5]?.should be_nil # tokens_empty[1..3]?.should be_nil # end # end # # pending "#empty?" do # it "exposes the `#empty?` method of the wrapped array" do # tokens.empty?.should be_false # tokens_empty.empty?.should be_true # end # end # end #end # #pending Result do # pending "#initialize" do # it "sets values for #tokens and #value" do # tokens = Tokens.from_string("esting") # value = 't' # result = Result(Char, Char).new(tokens, value) # # result.tokens.should eq(tokens) # result.value.should eq(value) # end # end #end # #pending Parser do # p = AnyToken(Char).new # # pending "#parse?" do # it "returns `nil` if the parser fails" do # result = p.parse?(Tokens.new([] of Char)) # # result.should be_nil # end # # it "returns a `Result(T, V)` if the parser succeeds" do # tokens = Tokens.from_string("testing") # result = p.parse(tokens) # # result.should be_a(Result(Char, Char)) # end # end #end # #pending Flunk do # pending "#parse" do # it "always fails" do # tokens = Tokens.from_string("testing") # # expect_raises(ParserFail) { Flunk(Char, Char).new.parse(tokens) } # end # end #end # #pending AnyToken do # context do # p = AnyToken(Char).new # # pending "#parse" do # it "succeeds when input is non-empty" do # tokens = Tokens.from_string("testing") # result = p.parse(tokens) # # result.tokens.should eq(tokens[1..]) # result.value.should eq('t') # end # # it "fails when input is empty" do # expect_raises(ParserFail) { p.parse(Tokens.new([] of Char)) } # end # end # end #end # #pending EOF do # p = EOF(Char).new # # pending "#parse" do # it "succeeds when input is empty" do # result = p.parse(Tokens.new([] of Char)) # # result.tokens.empty?.should be_true # result.value.should be_nil # end # # it "fails when input is non-empty" do # tokens = Tokens.from_string("testing") # # expect_raises(ParserFail) { p.parse(tokens) } # end # end #end # #pending Peek do # tokens = Tokens.from_string("testing") # p = AnyToken(Char).new # result_normal = p.parse(tokens) # result_peek = Peek.new(p).parse(tokens) # # pending "#parse" do # it "does not modify the result of the wrapped parser" do # result_peek.value.should eq(result_normal.value) # end # # it "does not consume any input" do # result_peek.tokens.should eq(tokens) # end # end #end # #pending Assert do # test_f = ->(x : Char) { x == 't' } # p = AnyToken(Char).new.assert { |x| x == 't' } # # pending "#parse" do # it "fails if the wrapped parser fails" do # expect_raises(ParserFail) do # p.parse(Tokens.new([] of Char)) # end # end # # it "fails if the result value fails the test" do # tokens = Tokens.from_string("_testing") # # expect_raises(ParserFail) { p.parse(tokens) } # end # # it "succeeds if the wrapped parser succeeds and the test passes" do # tokens = Tokens.from_string("testing") # expected_char = tokens[0] # result = p.parse(tokens) # # result.value.should eq(expected_char) # test_f.call(expected_char).should be_true # end # end #end # #pending Satisfy do # p = Satisfy(Char).new { |x| x == 't' } # # pending "#parse" do # it "fails if the input is empty" do # expect_raises(ParserFail) { p.parse(Tokens.new([] of Char)) } # end # # it "fails if the token fails the test" do # tokens = Tokens.from_string("_testing") # # expect_raises(ParserFail) { p.parse(tokens) } # end # # it "succeeds if the token passes the test" do # tokens = Tokens.from_string("testing") # expected_char = tokens[0] # result = p.parse(tokens) # # result.value.should eq(expected_char) # end # end #end # #pending Token do # tokens = Tokens.from_string("testing") # # pending "#parse" do # it "fails if the input is empty" do # p = Token(Char).new('t') # # expect_raises(ParserFail) { p.parse(Tokens.new([] of Char)) } # end # # it "fails if the token is not the expected token" do # p = Token(Char).new('#') # # expect_raises(ParserFail) { p.parse(tokens) } # end # # it "succeeds if the token is the expected token" do # expected_char = tokens[0] # p = Token(Char).new(expected_char) # result = p.parse(tokens) # # result.value.should eq(expected_char) # end # end #end # #pending Map do # pending "#parse" do # it "fails if the wrapped parser fails" do # p = AnyToken(Char).new.map { |x| x } # # expect_raises(ParserFail) { p.parse(Tokens.new([] of Char)) } # end # # it "changes the result value via the provided proc" do # p = AnyToken(Char).new.map { |x| x.letter? } # # result = p.parse(Tokens.from_string("testing")) # result.value.should be_true # # result = p.parse(Tokens.from_string("_testing")) # result.value.should be_false # end # end #end # #pending Plus do # pending "#parse" do # tokens = Tokens.from_string("testing") # p_t = Token(Char).new('t') # p_e = Token(Char).new('e') # p_at = Token(Char).new('@') # # it "fails if the first parser fails" do # p = p_at + p_e # expect_raises(ParserFail) { p.parse(tokens) } # end # # it "fails if the second parser fails" do # p = p_t + p_at # expect_raises(ParserFail) { p.parse(tokens) } # end # # it "fails if both parsers fail" do # p = p_at + p_at # expect_raises(ParserFail) { p.parse(tokens) } # end # # it "succeeds if both parsers succeed" do # p = p_t + p_e # result = p.parse(tokens) # # result.tokens.should eq(tokens[2..]) # result.value[0].should eq('t') # result.value[1].should eq('e') # end # # it "evaluates parsers from left to right (left associative)" do # p_succeeds = p_t + p_e # p_fails = p_e + p_t # # p_succeeds.parse(tokens) # should not raise an exception # expect_raises(ParserFail) { p_fails.parse(tokens) } # # p_s = Token(Char).new('s') # # r = (p_t + p_e + p_s).parse(tokens) # should not raise an exception # r.value.should be_a({ {Char, Char}, Char}) # # r = (p_t + (p_e + p_s)).parse(tokens) # should not raise an exception # r.value.should be_a({Char, {Char, Char} }) # end # end #end # ## most behavior shouldn't need to be tested ## since it is based on tested bbehavior from ## Plus and Map #pending Left do # pending "#parse" do # it "returns the value of the first parser if both succeed" do # tokens = Tokens.from_string("testing") # letter_t = Token.new('t') # letter_e = Token.new('e') # result = (letter_t << letter_e).parse(tokens) # # result.value.should eq('t') # result.tokens.should eq(tokens[2..]) # end # end #end # ## same deal as Left #pending Right do # pending "#parse" do # it "returns the value of the second parser if both succeed" do # tokens = Tokens.from_string("testing") # letter_t = Token.new('t') # letter_e = Token.new('e') # result = (letter_t >> letter_e).parse(tokens) # # result.value.should eq('e') # result.tokens.should eq(tokens[2..]) # end # end #end # #pending Phrase do # p = Phrase.new(Token.new('t')) # # pending "#parse" do # it "fails if the wrapped parser fails" do # tokens = Tokens.from_string("_") # # expect_raises(ParserFail) { p.parse(tokens) } # end # # it "fails if not all of the input tokens are parsed" do # tokens = Tokens.from_string("tt") # # expect_raises(ParserFail) { p.parse(tokens) } # end # # it "succeeds if the wrapped parser successfully parses all of the input" do # tokens = Tokens.from_string("t") # result = p.parse(tokens) # # result.tokens.empty?.should be_true # result.value.should eq('t') # end # end #end # #pending Recover do # p = Token.new('t').recover('@') # # pending "#parse" do # it "succeeds and returns the wrapped parser's value if it succeeds" do # tokens = Tokens.from_string("testing") # result = p.parse(tokens) # # result.tokens.should eq(tokens[1..]) # result.value.should eq('t') # end # # it "succeeds and returns the default value without modifying the input if the wrapped parser fails" do # tokens = Tokens.from_string("_____") # result = p.parse(tokens) # # result.tokens.should eq(tokens) # result.value.should eq('@') # end # end #end # #pending Optional do # p = Optional.new(Token.new('t')) # # pending "#parse" do # it "succeeds and returns the wrapped parser's value if it succeeds" do # tokens = Tokens.from_string("testing") # result = p.parse(tokens) # # result.tokens.should eq(tokens[1..]) # result.value.should eq('t') # end # # it "succeeds and returns a value of `nil` without modifying the input if the wrapped parser fails" do # tokens = Tokens.from_string("_____") # result = p.parse(tokens) # # result.tokens.should eq(tokens) # result.value.should be_nil # end # end #end # #pending Sequence do # # HACK: ps has to be declared this way due to contravariance # # https://crystal-lang.org/reference/1.7/syntax_and_semantics/inheritance.html#covariance-and-contravariance # ps = [] of Parser(Char, Char) # ps = ps + "abcd".chars.map { |c| Token.new(c) } # p = Sequence.new(ps) # # pending "#parse" do # it "runs each wrapped parser in order, returns each result" do # tokens = Tokens.from_string("abcd") # result = p.parse(tokens) # # result.value.should eq("abcd".chars) # result.tokens.empty?.should be_true # end # # it "fails if any of the wrapped parsers fail" do # fail_strings = ["", "abed", "bbcd", "abce"] # fail_strings.each do |s| # tokens = Tokens.from_string(s) # expect_raises(ParserFail) { p.parse(tokens) } # end # end # # it "succeeds and returns empty array if parser iterable is empty" do # tokens = Tokens.from_string("abcd") # empty_p = Sequence.new([] of Parser(Char, Char)) # result = empty_p.parse(tokens) # # result.value.empty?.should be_true # result.tokens.should eq(tokens) # end # end #end # #pending TokenSeq do # p = TokenSeq.new("test".chars) # # pending "#parse" do # it "fails if the input stream is too short" do # input = Tokens.from_string("") # expect_raises(ParserFail) { p.parse(input) } # end # # it "fails if it encounters an unexpected token" do # input = Tokens.from_string("text") # expect_raises(ParserFail) { p.parse(input) } # end # # it "succeeds if the input starts with the expected tokens" do # input = Tokens.from_string("testing") # result = p.parse(input) # # result.tokens.should eq(input[4..]) # result.value.should eq("test".chars) # end # end #end # #pending Many do # p = Many.new(Token.new('a')) # # pending "#parse" do # it "returns an empty array if the wrapped parser never succeeds" do # tokens = Tokens.from_string("bb") # result = p.parse(tokens) # # result.value.empty?.should be_true # result.tokens.should eq(tokens) # end # # it "stops parsing when the wrapped parser fails, returns all successes" do # tokens = Tokens.from_string("aaabcd") # result = p.parse(tokens) # # result.value.should eq("aaa".chars) # result.tokens.should eq(tokens[3..]) # # tokens = Tokens.from_string("aaa") # result = p.parse(tokens) # # result.value.should eq("aaa".chars) # result.tokens.should eq(tokens[3..]) # end # # it "stops parsing when the wapped parser succeeds without consuming any input" do # a_optional : Parser(Char, Char?) # a_optional = Optional.new(Token.new('a')) # tokens = Tokens.from_string("aaa") # result = Many(Char, Char?).new(a_optional).parse(tokens) # # result.value.should eq("aaa".chars) # result.tokens.should eq(tokens[3..]) # end # end #end # #pending Some do # p = Some.new(Token.new('a')) # pending "#parse" do # it "fails if the wrapped parser never succeeds" do # tokens = Tokens.from_string("") # expect_raises(ParserFail) { p.parse(tokens) } # end # # it "stops parsing when the wrapped parser fails, returns all successes" do # tokens = Tokens.from_string("aaabcd") # result = p.parse(tokens) # # result.value.should eq("aaa".chars) # result.tokens.should eq(tokens[3..]) # # tokens = Tokens.from_string("aaa") # result = p.parse(tokens) # # result.value.should eq("aaa".chars) # result.tokens.should eq(tokens[3..]) # end # end #end # #pending Exactly do # letter_a = Token.new('a') # tokens = Tokens.from_string("aaabcd") # # pending "#parse" do # it "tries to parse exactly n of the wrapper parser" do # p = Exactly.new(3, letter_a) # result = p.parse(tokens) # # result.value.should eq("aaa".chars) # result.tokens.should eq(tokens[3..]) # end # # it "always succeeds with an empty array if n < 1" do # p = Exactly.new(0, letter_a) # result = p.parse(tokens) # # result.value.empty?.should be_true # result.tokens.should eq(tokens) # # p = Exactly.new(-42, letter_a) # result = p.parse(tokens) # # result.value.empty?.should be_true # result.tokens.should eq(tokens) # end # # it "does not take extra matching tokens" do # p = Exactly.new(2, letter_a) # result = p.parse(tokens) # # result.value.should eq("aa".chars) # result.tokens.should eq(tokens[2..]) # end # # it "fails if there are not enough matching tokens" do # p = Exactly.new(60, letter_a) # expect_raises(ParserFail) { p.parse(tokens) } # end # end #end # #pending AtLeast do # letter_a = Token.new('a') # tokens = Tokens.from_string("aaaabcd") # # pending "#parse" do # it "fails if there are not enough matching tokens to parse" do # p = AtLeast.new(5, letter_a) # expect_raises(ParserFail) { p.parse(tokens) } # #expect_raises(ParserFail) { raise ParserFail.new("sdgseg") } # end # # it "parses n or more times with the given parser" do # p0 = AtLeast.new(0, letter_a) # p2 = AtLeast.new(2, letter_a) # p4 = AtLeast.new(4, letter_a) # # result0 = p0.parse(tokens) # result2 = p2.parse(tokens) # result4 = p4.parse(tokens) # # result0.value.should eq("aaaa".chars) # result0.tokens.should eq(tokens[4..]) # # result2.should eq(result0) # result4.should eq(result0) # end # end #end # #pending AtMost do # letter_a = Token.new('a') # tokens = Tokens.from_string("aaaabcd") # # pending "#parse" do # it "does not parse more than n times" do # p0 = AtMost.new(0, letter_a) # p2 = AtMost.new(2, letter_a) # p6 = AtMost.new(6, letter_a) # # r0 = p0.parse(tokens) # r0.value.empty?.should be_true # r0.tokens.should eq(tokens) # # r2 = p2.parse(tokens) # r2.value.should eq("aa".chars) # r2.tokens.should eq(tokens[2..]) # # r6 = p6.parse(tokens) # r6.value.should eq("aaaa".chars) # r6.tokens.should eq(tokens[4..]) # end # end #end # #pending Between do # letter_a = Token.new('a') # tokens = Tokens.from_string("aaaabcd") # # pending "#parse" do # it "parses at least i times, up to a limit of j times" do # p0_4 = Between.new(0, 4, letter_a) # r0_4 = p0_4.parse(tokens) # # r0_4.value.should eq("aaaa".chars) # r0_4.tokens.should eq(tokens[4..]) # end # # it "fails if there are not enough parser successes" do # p = Between.new(5, 6, letter_a) # expect_raises(ParserFail) { p.parse(tokens) } # end # end #end # #pending FirstOf do # tokens = Tokens.from_string("abcd") # letter_a = Token.new('a') # f = Flunk(Char, Char).new # # pending "#parse" do # it "cannot be instantiated with an empty Enumerable" do # expect_raises(ArgumentError) { FirstOf.new([] of Parser(Char, Char)) } # end # # it "uses the result of the first successful parser" do # a1 = [letter_a, f, f, f] of Parser(Char, Char) # a2 = [f, letter_a, f, f] of Parser(Char, Char) # a3 = [f, f, letter_a, f] of Parser(Char, Char) # a4 = [f, f, f, letter_a] of Parser(Char, Char) # # [a1, a2, a3, a4].each do |arr| # p = FirstOf.new(arr) # r = p.parse(tokens) # r.value.should eq('a') # r.tokens.should eq(tokens[1..]) # end # end # # it "only fails if no parsers are successful" do # x = Token.new('x') # y = Token.new('x') # z = Token.new('x') # p = FirstOf.new([x, y, z] of Parser(Char, Char)) # expect_raises(ParserFail) { p.parse(tokens) } # end # end #end # #pending SepBy do # pending "#parse" do # letter_a = Token.new('a') # comma = Token.new(',') # tokens = Tokens.from_string("a,a,a,a") # # it "fails if no elements can be parsed" do # p = SepBy(Char, Char, Char).new(comma, comma) # expect_raises(ParserFail) { p.parse(tokens) } # end # # it "succeeds if only one element can be parsed" do # t1 = Tokens.from_string("a") # t2 = Tokens.from_string("a,") # p = SepBy(Char, Char, Char).new(letter_a, comma) # # result = p.parse(t1) # result.value.should eq(['a']) # result.tokens.should eq(t1[1..]) # # result = p.parse(t2) # result.value.should eq(['a']) # result.tokens.should eq(t2[1..]) # end # # it "parses 1 element, then 0 or more (sep >> element)" do # p = SepBy(Char, Char, Char).new(letter_a, comma) # # result = p.parse(tokens) # result.value.should eq("aaaa".chars) # result.tokens.empty?.should be_true # # # drop last char in tokens, should parse three elements # result = p.parse(tokens[..5]) # result.value.should eq("aaa".chars) # result.tokens.should eq(Tokens.from_string(",")) # end # end #end # #pending "Practical use" do # pending "Use case: text surrounded by whitespace" do # space = Satisfy(Char).new { |c| c.whitespace? } # non_space = Satisfy(Char).new { |c| !c.whitespace? } # # # TODO: Figure out why mapping on this parser breaks # # initialization of `body`. # word_chars = Some.new(non_space) # ws = Some.new(space) # # bookend = Optional.new(ws) # body = SepBy.new(word_chars, ws) # tokenizer = (bookend >> body << bookend).map do |arrs| # arrs.map { |chars| chars.join } # end # # good_strings = [ # "test with no trailing whitespace", # " test with whitespace in the front", # "test with whitespace in the back", # " test surrounded by whitespace ", # ] # # good_strings.each do |s| # tokens = Tokens.from_string(s) # result = tokenizer.parse(tokens) # result.value.should eq(s.strip.split(/\s+/)) # result.tokens.empty?.should be_true # end # # bad_strings = [ # "", # " ", # ] # # bad_strings.each do |s| # tokens = Tokens.from_string(s) # expect_raises(ParserFail) { tokenizer.parse(tokens) } # end # end #end #