diff --git a/lib/akin/parser/language.rb b/lib/akin/parser/language.rb index c136374..03ff886 100644 --- a/lib/akin/parser/language.rb +++ b/lib/akin/parser/language.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 diff --git a/lib/akin/parser/match.rb b/lib/akin/parser/match.rb index f4fa09d..96f49c0 100644 --- a/lib/akin/parser/match.rb +++ b/lib/akin/parser/match.rb @@ -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,7 +43,7 @@ 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) @@ -51,7 +51,7 @@ def lookup(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 = "" diff --git a/lib/akin/parser/syntax.rb b/lib/akin/parser/syntax.rb index 1eebdb9..aa7946b 100644 --- a/lib/akin/parser/syntax.rb +++ b/lib/akin/parser/syntax.rb @@ -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 diff --git a/spec/parser/match_spec.rb b/spec/parser/match_spec.rb index 59167a8..00a6924 100644 --- a/spec/parser/match_spec.rb +++ b/spec/parser/match_spec.rb @@ -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 diff --git a/spec/parser/syntax_spec.rb b/spec/parser/syntax_spec.rb index 1abf6ee..f806531 100644 --- a/spec/parser/syntax_spec.rb +++ b/spec/parser/syntax_spec.rb @@ -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