require "./spec_helper" require "../src/parcom.cr" include Parcom describe Tokens do describe ".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 describe "#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") describe "#[]" 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 describe "#[]?" 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 describe "#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 describe Result do describe "#map" do r = Result.new(Tokens.from_string("abcd"), 'x') r_expected = Result.new(Tokens.from_string("abcd"), 'x'.ord) it "accepts a proc" do f = ->(c : Char) { c.ord } r.map(f).should eq(r_expected) end it "accepts a block" do r.map { |c| c.ord }.should eq(r_expected) end end end describe Parser do describe "self.pure" do v = 'a' p = Parser(Char, Char).pure(v) tokens = Tokens.from_string("____") result = p.parse(tokens) it "returns a value of whatever it was initialized with" do result.value.should eq(v) end it "does not modify the input" do result.tokens.should eq(tokens) end end describe "self.flunk" do p = Parser(Char, Char).flunk it "always fails" do expect_raises(ParserFail) { p.parse(Tokens.from_string("arbitrary")) } end end describe "self.any_token" do p = Parser(Char, Nil).any_token it "parses the first token in the input stream" do tokens = Tokens.from_string("abcd") result = p.parse(tokens) result.value.should eq(tokens[0]) result.tokens.should eq(tokens[1..]) end it "fails if the input stream is empty" do tokens = Tokens.from_string("") expect_raises(ParserFail) { p.parse(tokens) } end end describe "self.eof" do p = Parser(Char, Nil).eof it "succeeds with nil if the input stream is empty" do tokens = Tokens.from_string("") result = p.parse(tokens) result.value.should be_nil result.tokens.empty?.should be_true end it "fails if the input stream is not empty" do tokens = Tokens.from_string("____") expect_raises(ParserFail) { p.parse(tokens) } end end describe "self.satisfy" do p = Parser(Char, Char).satisfy { |c| c == 'a' } it "succeeds if the token passes the predicate" do tokens = Tokens.from_string("abcd") result = p.parse(tokens) result.value.should eq(tokens[0]) result.tokens.should eq(tokens[1..]) end it "fails if the token fails the predicate" do tokens = Tokens.from_string("bbcd") expect_raises(ParserFail) { p.parse(tokens) } end end describe "self.token" do a = Parser(Char, Char).token('a') it "succeeds with the given token if it is at the start" do tokens = Tokens.from_string("abc") result = a.parse(tokens) result.value.should eq('a') result.tokens.should eq(tokens[1..]) end it "fails if the given token is not present" do tokens = Tokens.from_string("bbc") expect_raises(ParserFail) { a.parse(tokens) } end end describe "self.sequence" do it "always succeeds with 0 parsers" do p = Parser(Char, Char).sequence([] of Parser(Char, Char)) tokens = Tokens.from_string("") result = p.parse(tokens) result.value.empty?.should be_true result.tokens.should eq(tokens) end p = Parser(Char, Char).sequence([ Parser(Char, Char).token('a'), Parser(Char, Char).token('b'), Parser(Char, Char).token('c'), ]) it "runs each parser in sequence" do tokens = Tokens.from_string("abcd") result = p.parse(tokens) result.value.should eq("abc".chars) result.tokens.should eq(tokens[3..]) end it "fails if any of the parsers fail" do "xbc axc abx".split.each do |s| tokens = Tokens.from_string(s) expect_raises(ParserFail) { p.parse(tokens) } end end end # most testing should be able to be skipped, since it is already # done for `Parser.sequence` describe "self.token_sequence" do p = Parser(Char, Char).token_sequence("abc".chars) it "parses the specified tokens in sequence" do tokens = Tokens.from_string("abcd") result = p.parse(tokens) result.value.should eq("abc".chars) result.tokens.should eq(tokens[3..]) end end describe "self.first_of" do ps = [ Parser.token('a'), Parser.token('b'), Parser.token('c'), ] abc = Parser(Char, Char).first_of(ps) it "fails to instantiate if array is empty" do expect_raises(ArgumentError) { Parser.first_of([] of Parser(Char, Char)) } end it "returns the result of the first parser that succeeds" do {"a", "b", "c"}.each do |s| tokens = Tokens.from_string(s) result = abc.parse(tokens) result.value.should eq(s[0]) result.tokens.empty?.should be_true end end it "fails if none of the parsers succeed" do {"", "d"}.each do |s| tokens = Tokens.from_string(s) expect_raises(ParserFail) { abc.parse(tokens) } end end end describe "#assert" do p = Parser(Char, Char).any_token.assert { |c| c == 'a' } it "succeeds if the parser succeeds and if the predicate passes" do tokens = Tokens.from_string("abcd") result = p.parse(tokens) result.value.should eq(tokens[0]) result.tokens.should eq(tokens[1..]) end it "fails if the predicate fails" do tokens = Tokens.from_string("bbcd") expect_raises(ParserFail) { p.parse(tokens) } end end describe "#map" do p = Parser(Char, Char).any_token p_mapped = p.map { |c| c.ord } tokens = Tokens.from_string("abcd") normal_result = p.parse(tokens) mapped_result = p_mapped.parse(tokens) it "parses the same amount from the input" do mapped_result.tokens.should eq(normal_result.tokens) end it "changes the value of the result" do mapped_result.value.should eq('a'.ord) end end describe "#|" do a = Parser(Char, Char).token('a') b = Parser(Char, Char).token('b') p = a | b it "succeeds with the first value if both parsers succeed" do p2 = a | Parser(Char, Char).pure('z') tokens = Tokens.from_string("abcd") result = p2.parse(tokens) result.value.should eq('a') result.tokens.should eq(tokens[1..]) end it "succeeds with the first value if only the first parser succeeds" do tokens = Tokens.from_string("abcd") result = p.parse(tokens) result.value.should eq('a') result.tokens.should eq(tokens[1..]) end it "succeeds with the second value if only the second parser succeeds" do tokens = Tokens.from_string("bbcd") result = p.parse(tokens) result.value.should eq('b') result.tokens.should eq(tokens[1..]) end it "fails if both parsers fail" do tokens = Tokens.from_string("cbcd") expect_raises(ParserFail) { p.parse(tokens) } end end describe "#recover" do p = Parser(Char, Char).token('a').recover('z') it "behaves as normal on success" do tokens = Tokens.from_string("abcd") result = p.parse(tokens) result.value.should eq('a') result.tokens.should eq(tokens[1..]) end tokens2 = Tokens.from_string("bbcd") result2 = p.parse(tokens2) it "returns the default value instead of failing" do result2.value.should eq('z') end it "does not modify the input when recovering" do result2.tokens.should eq(tokens2) end end describe "#optional" do p = Parser(Char, Char).token('a').optional it "behaves as normal on success" do tokens = Tokens.from_string("abcd") result = p.parse(tokens) result.value.should eq('a') result.tokens.should eq(tokens[1..]) end tokens2 = Tokens.from_string("bbcd") result2 = p.parse(tokens2) it "returns nil instead of failing" do result2.value.should be_nil end it "does not modify the input when recovering" do result2.tokens.should eq(tokens2) end end describe "#+" do a = Parser(Char, Char).token('a') b = Parser(Char, Char).token('b') p = a + b it "combines both success results if both parsers succeed" do tokens = Tokens.from_string("abcd") result = p.parse(tokens) result.value.should eq({'a', 'b'}) result.tokens.should eq(tokens[2..]) end it "fails if either parser fails" do "aacd bbcd cccd".split.each do |s| tokens = Tokens.from_string(s) expect_raises(ParserFail) { p.parse(tokens) } end end end # Should be able to skip some tests because they are already # written for #+, which this is based on. describe "#<<" do a = Parser(Char, Char).token('a') b = Parser(Char, Char).token('b') p = a << b tokens = Tokens.from_string("abcd") result = p.parse(tokens) it "discards the second parser's value" do result.value.should eq('a') end end # Should be able to skip some tests because they are already # written for #+, which this is based on. describe "#>>" do a = Parser(Char, Char).token('a') b = Parser(Char, Char).token('b') p = a >> b tokens = Tokens.from_string("abcd") result = p.parse(tokens) it "discards the second parser's value" do result.value.should eq('b') end end describe "#many" do p = Parser(Char, Char).token('a').many it "returns no results if it never succeeds" do ["", "bb"].each do |s| tokens = Tokens.from_string(s) result = p.parse(tokens) result.value.empty?.should be_true result.tokens.should eq(tokens) end end it "parses as many times as possible" do ["a", "aaa", "aaaaa"].each do |s| tokens = Tokens.from_string(s) result = p.parse(tokens) result.value.should eq(s.chars) result.tokens.empty?.should be_true end end end describe "#some" do p = Parser(Char, Char).token('a').some it "fails if it cannot parse at least once" do ["", "bb"].each do |s| tokens = Tokens.from_string(s) expect_raises(ParserFail) { p.parse(tokens) } end end end describe "#exactly" do a = Parser.token('a') tokens = Tokens.from_string("aaabbb") it "fails to instantiate if `n` is negative" do expect_raises(ArgumentError) { a * -3 } end it "accepts parsing 0 times" do p = a * 0 result = p.parse(tokens) result.value.empty?.should be_true result.tokens.should eq(tokens) end it "tries to parse exactly n times" do p = a * 3 result = p.parse(tokens) result.value.should eq(['a', 'a', 'a']) result.tokens.should eq(tokens[3..]) end it "does not parse more than n times, even if it could" do p = a * 1 result = p.parse(tokens) result.value.should eq(['a']) result.tokens.should eq(tokens[1..]) end it "fails if unable to parse enough times" do p = a * 5 expect_raises(ParserFail) { p.parse(tokens) } end end describe "#at_least" do a = Parser.token('a') tokens = Tokens.from_string("aaabbb") it "fails to instantiate if `n` is negative" do expect_raises(ArgumentError) { a.at_least(-3) } end it "accepts parsing 0 times" do p = a.at_least(0) result = p.parse(tokens) result.value.should eq(['a', 'a', 'a']) result.tokens.should eq(tokens[3..]) end it "tries to parse at least n times" do p = a.at_least(3) result = p.parse(tokens) result.value.should eq(['a', 'a', 'a']) result.tokens.should eq(tokens[3..]) end it "parses as many times as possible (>=n)" do p = a.at_least(1) result = p.parse(tokens) result.value.should eq(['a', 'a', 'a']) result.tokens.should eq(tokens[3..]) end it "fails if unable to parse enough times" do p = a.at_least(5) expect_raises(ParserFail) { p.parse(tokens) } end end describe "#at_most" do a = Parser.token('a') tokens = Tokens.from_string("aaabbb") it "fails to instantiate if `n` is negative" do expect_raises(ArgumentError) { a.at_most(-3) } end it "accepts parsing 0 times" do p = a.at_most(0) result = p.parse(tokens) result.value.should eq([] of Char) result.tokens.should eq(tokens) end it "tries to parse at most n times" do p = a.at_most(3) result = p.parse(tokens) result.value.should eq(['a', 'a', 'a']) result.tokens.should eq(tokens[3..]) end it "stops if it succeeds `n` times" do p = a.at_most(1) result = p.parse(tokens) result.value.should eq(['a']) result.tokens.should eq(tokens[1..]) end it "stops if unable to parse `n` times" do p = a.at_most(5) result = p.parse(tokens) result.value.should eq(['a', 'a', 'a']) result.tokens.should eq(tokens[3..]) end end describe "#between" do a = Parser.token('a') tokens = Tokens.from_string("aaabbb") it "fails to instantiate if `n` or `m` are negative" do expect_raises(ArgumentError) { a.between(-1, 1) } expect_raises(ArgumentError) { a.between(1, -1) } expect_raises(ArgumentError) { a.between(-1, -1) } end it "fails to instantiate if any range values are negative" do {(-1..3), (-3..-1), (..-3), (-3..)}.each do |r| expect_raises(ArgumentError) { a.between(r) } end end it "accepts either order for `n` and `m`" do p1 = a.between(1,2) r1 = p1.parse(tokens) p2 = a.between(2,1) r2 = p1.parse(tokens) r1.should eq(r2) end it "accepts `0` for `n` and `m`" do p = a.between(0, 2) result = p.parse(tokens) result.value.should eq(['a', 'a']) result.tokens.should eq(tokens[2..]) end it "accepts Range objects" do p = a.between(1..2) result = p.parse(tokens) result.value.should eq(['a', 'a']) result.tokens.should eq(tokens[2..]) end it "accepts beginless ranges" do p = a.between(..2) result = p.parse(tokens) result.value.should eq(['a', 'a']) result.tokens.should eq(tokens[2..]) end it "accepts endless ranges" do p = a.between(2..) result = p.parse(tokens) result.value.should eq(['a', 'a', 'a']) result.tokens.should eq(tokens[3..]) end it "tries to parse between `n` and `m` times" do p = a.between(1, 2) result = p.parse(tokens) result.value.should eq(['a', 'a']) result.tokens.should eq(tokens[2..]) end it "fails if it does not succeed the minimum number of times" do p = a.between(5, 6) expect_raises(ParserFail) { p.parse(tokens) } end it "stops parsing if it fails after succeeding the minimum number of times" do p = a.between(2, 4) result = p.parse(tokens) result.value.should eq(['a', 'a', 'a']) result.tokens.should eq(tokens[3..]) end it "stops parsing if it succeeds the maximum number of times" do p = a.between(1, 2) result = p.parse(tokens) result.value.should eq(['a', 'a']) result.tokens.should eq(tokens[2..]) end end describe "#sep_by" do a = Parser.token('a') b = Parser.token('b') p = a.sep_by(b) it "fails if it cannot parse the first element" do tokens = Tokens.from_string("cba") expect_raises(ParserFail) { p.parse(tokens) } end it "will parse a single element without separators" do tokens = Tokens.from_string("aca") result = p.parse(tokens) result.value.should eq(['a']) result.tokens.should eq(tokens[1..]) end it "will parse one element, and as many others separated by sep" do tokens = Tokens.from_string("abababaa") result = p.parse(tokens) result.value.should eq(['a', 'a', 'a', 'a']) result.tokens.should eq(tokens[7..]) end end # TODO: phrase # TODO: peek end