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 "#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 end