Permalink
Browse files

Simple string parser specs.

  • Loading branch information...
1 parent cd22554 commit b044ca1bd62fea3d55e964a68fcdf1b4d79e4830 @vic committed Jan 21, 2011
Showing with 89 additions and 15 deletions.
  1. +8 −2 lib/akin/parser/language.rb
  2. +9 −8 lib/akin/parser/match.rb
  3. +23 −4 lib/akin/parser/syntax.rb
  4. +19 −0 spec/parser/match_spec.rb
  5. +30 −1 spec/parser/syntax_spec.rb
@@ -89,10 +89,10 @@ def fwd
end
end
- module Number
+ module NumberLiteral
def self./(base)
Module.new do
- include Number
+ include NumberLiteral
define_method(:base) { base }
end
end
@@ -107,6 +107,12 @@ def text
end
end
+ module StringLiteral
+ def parts
+ self[1]
+ end
+ end
+
module Language
class << self
attr_accessor :syntax
View
@@ -21,7 +21,7 @@ def clone
class Matcher
- [ :name, :mixin, :block, :seq, :min, :max, :but, :true, :parse ].tap do |attrs|
+ [ :name, :mixin, :block, :seq, :min, :max, :pos, :true, :parse ].tap do |attrs|
attr_reader *attrs
define_method(:with) do |map|
@@ -43,15 +43,15 @@ class Matcher
def initialize(block)
@lookup = block
- @true, @but, @min, @max = true, true, 1, 1
+ @true, @pos, @min, @max = true, true, 1, 1
end
def lookup(name)
@lookup.call(name)
end
def but
- with :but => !@but
+ with :pos => !@pos
end
def not
@@ -120,7 +120,7 @@ def |(other)
private
def positive?(bool)
- if @but
+ if @pos
bool
else
!bool
@@ -143,6 +143,7 @@ def match_new(positive, from, to, fwd, &block)
m.to = to
m.fwd = fwd
m.tap(&block) if block
+ m.expected = self unless positive
end
end
@@ -178,7 +179,7 @@ def match_all(input, ary)
match, at = nil, input
ary.each_with_index do |item, index|
match = match_single(at, item)
- if match.positive?
+ if @true && match.positive?
at = match.fwd
all << match
named[match.name] = match if match.name
@@ -189,12 +190,12 @@ def match_all(input, ary)
end
if all.size == 1
match = all.first
- match.extend @mixin if @mixin && match.positive? && @but == true
+ match.extend @mixin if @mixin && match.positive? && @pos
else
match = match_positive(input, all.last.to, all.last.fwd) do |match|
match.ary = all
match.map = named
- match.extend @mixin if @mixin && @but == true
+ match.extend @mixin if @mixin && @pos
end
end
match
@@ -261,7 +262,7 @@ def match_single(input, rule)
class Match
include Enumerable
- attr_accessor :name, :from, :to, :fwd, :ary, :map, :positive
+ attr_accessor :name, :from, :to, :fwd, :ary, :map, :positive, :expected
def text
buff = ""
View
@@ -5,10 +5,15 @@ module Parser
a(:eof) .is nil
a(:any) .is /./
+ a(:dollar) .is "$"
a(:dash) .is "-"
a(:slash) .is "/"
a(:underscore) .is "_"
a(:back_slash) .is "\\"
+ a(:dot) .is "."
+ a(:comma) .is ","
+ a(:collon) .is ":"
+ a(:semicollon) .is ";"
a(:unix_eol) .is "\n"
a(:win_eol) .is "\r\n"
@@ -34,13 +39,27 @@ module Parser
a(digit, a([ digit, a(:underscore, digit) ]).any)
end
- a(:bin_int).as(Number/2) .is "0", /[bB]/, a(:digits).of(:oct_digit)
- a(:oct_int).as(Number/8) .is "0", a(/[oO]/).opt, a(:digits).of(:oct_digit)
- a(:dec_int).as(Number/10) .is a(:digits).of(:dec_digit)
- a(:hex_int).as(Number/16) .is "0", /[xX]/, a(:digits).of(:hex_digit)
+ a(:bin_int).as(NumberLiteral/2) .is "0", /[bB]/, a(:digits).of(:oct_digit)
+ a(:oct_int).as(NumberLiteral/8) .is "0", a(/[oO]/).opt, a(:digits).of(:oct_digit)
+ a(:dec_int).as(NumberLiteral/10) .is a(:digits).of(:dec_digit)
+ a(:hex_int).as(NumberLiteral/16) .is "0", /[xX]/, a(:digits).of(:hex_digit)
a(:integer).is [:bin_int, :hex_int, :oct_int, :dec_int]
+ text = lambda do |left, right|
+ interpol = a(:dollar, :table)
+ escaped = a(:back_slash, [:dollar, right])
+ char = a(right.not, :any)
+ content = interpol | (escaped | char).many
+ a(left, content.any, right)
+ end
+
+ a(:string).as(StringLiteral).is text.call(a("\""), a("\""))
+ a(:mstring).as(StringLiteral).tap do |ms|
+ mq = a("\"", "\"", "\"")
+ ms.is text.call(mq, mq)
+ end
+
end
end
end
View
@@ -151,6 +151,14 @@ def input(text)
m.to.should == nil
m.fwd.should == i
end
+
+ it "does not consume input on invalid sequence match" do
+ m = a("a", "b", "c").not.match i = input("foo")
+ m.positive?.should be_true
+ m.from.should == i
+ m.to.should == nil
+ m.fwd.should == i
+ end
end
describe "+ operator" do
@@ -331,5 +339,16 @@ def input(text)
end
end
+
+ describe "composite rule" do
+ it "matches an enclosed text between abc" do
+ x = a("a", "b", "c")
+ y = a(x, a(x.not, /./).any, x)
+ y.match(input("abcabc")).positive?.should be_true
+ y.match(input("abc abc")).positive?.should be_true
+ y.match(input("abcdefabc")).positive?.should be_true
+ end
+ end
+
end
View
@@ -56,7 +56,7 @@ def parse(name, input)
m.fwd.position.logical.pos.should == [1, 9, 9]
end
- it "leaves the physical fwd logical position intact" do
+ it "leaves the physical fwd position intact" do
m = parse a(:tab), "\thola"
m.fwd.position.physical.pos.should == [1, 2, 2]
end
@@ -165,4 +165,33 @@ def parse(name, input)
end
end # hex integer
+
+ describe "string" do
+ it "matches a simple string literal" do
+ m = parse(:string, %q("hello"))
+ m.parts.count.should == 1
+ m.to.position.logical.pos.should == [1, 7, 7]
+ end
+
+ it "matches a string literal with escaped quotes" do
+ m = parse(:string, %q("hell\"o"))
+ m.parts.count.should == 1
+ m.to.position.logical.pos.should == [1, 9, 9]
+ end
+ end
+
+ describe "multi string" do
+ it "matches a simple string literal" do
+ m = parse(:mstring, %q("""hello"""))
+ m.parts.count.should == 1
+ m.to.position.logical.pos.should == [1, 11, 11]
+ end
+
+ it "matches a string literal with escaped quotes" do
+ m = parse(:mstring, %q("""hell\"""o"""))
+ m.parts.count.should == 1
+ m.to.position.logical.pos.should == [1, 15, 15]
+ end
+ end
+
end

0 comments on commit b044ca1

Please sign in to comment.