module Cannonbol class MatchFailed < Exception; end class MatchString < String attr_reader :captured attr_reader :match_start attr_reader :match_end def initialize(string, match_start, match_end, captured) @cannonbol_string = string @match_start = match_start @match_end = match_end @captured = captured.dup super(@match_end < 0 ? "" : string[@match_start..@match_end]) end def replace_match_with(s) before_match = "" before_match = @cannonbol_string[0..@match_start-1] if @match_start > 0 after_match = @cannonbol_string[@match_end+1..-1] || "" before_match + s + after_match end end class Needle attr_reader :cursor attr_reader :string attr_accessor :captures attr_accessor :match_failed attr_accessor :ignore_case def initialize(string) @string = string end def thread(pattern, opts = {}, &match_block) @captures = {} anchor = opts[:anchor] raise_error = opts[:raise_error] replace_with = opts[:replace_match_with] ignore_case = opts[:ignore_case] @cursor = -1 match = nil begin while !match and !match_failed and @cursor < @string.length-1 @cursor += 1 @starting_character = nil @success_blocks = [] @ignore_case = ignore_case match = pattern._match?(self) break if !match and anchor end rescue MatchFailed end if match @success_blocks.each(&:call) match = MatchString.new(@string, @starting_character || @cursor, @cursor-1, @captures) else raise MatchFailed if raise_error end if match_block match = match_block.call(*([match] + (match_block.parameters[1..-1] || []).collect { |param| @captures[param[1].to_sym] })) elsif replace_with match = match.replace_match_with(replace_with) end match end def capture(name, value) @captures[name.to_sym] = value if name value end def remaining_string @string[@cursor..-1] end def push(length, &success_block) thread_state = [@starting_character, @cursor, @success_blocks.dup, @ignore_case] @starting_character ||= @cursor @cursor += length @success_blocks << success_block if success_block thread_state end def pull(thread_state) @starting_character, @cursor, @success_blocks, @ignore_case = thread_state if thread_state nil end end module Operators def _match?(needle, *args, &block) return if needle.match_failed __match?(needle, *args, &block) end def match?(s, opts = {}, &match_block) Needle.new(s).thread(self, opts, &match_block) end def |(pattern) Choose.new(self, pattern) end def &(pattern) Concat.new(self, pattern) end def -@ CaseSensitiveOff.new(self) end def capture?(opts = {}, &block) OnSuccess.new(self, opts, &block) end def capture!(opts = {}, &block) OnMatch.new(self, opts, &block) end end class Pattern include Operators def __match?(needle) [] end end class Choose < Pattern def __match?(needle, i = 0, s = []) while i < @params.length s = @params[i]._match?(needle, *s) return [i, s] if s s = [] i += 1 end nil end def initialize(p1, p2) @params = [p1, p2] end end class Concat < Pattern def __match?(needle, i = 0, s = []) while i < @params.length and i >= 0 s[i] = @params[i]._match?(needle, *(s[i] || [])) i = s[i] ? i+1 : i-1 end [i-1, s] if i == @params.length end def initialize(p1, p2) @params = [p1, p2] end end class CaseSensitiveOff < Pattern def initialize(pattern) @pattern = pattern end def __match?(needle, s=[]) ignore_case = needle.ignore_case needle.ignore_case = true s = @pattern._match?(needle, *s) needle.ignore_case = ignore_case return [s] if s end end class OnSuccess < Pattern def initialize(pattern, opts, &block) if opts.class == Hash if opts.first @capture_name = opts.first.first @initial_capture_value = opts.first.last end else @capture_name = opts end @pattern = pattern @block = block end def __match?(needle, thread_state = nil, starting_cursor = nil, s=[]) needle.pull(thread_state) starting_cursor ||= needle.cursor if s = @pattern._match?(needle, *s) ending_cursor = needle.cursor-1 push = needle.push(0) do match_string = MatchString.new(needle.string, starting_cursor, ending_cursor, needle.captures) capture_value = @capture_name && (needle.captures.has_key?(@capture_name) ? needle.captures[@capture_name] : @initial_capture_value) if @block match_string = @block.call(match_string, ending_cursor+1, capture_value) elsif capture_value.class == Array match_string = capture_value + [match_string] end needle.capture(@capture_name, match_string) end [ push, starting_cursor, s ] end end end class OnMatch < OnSuccess def __match?(needle, starting_cursor = nil, s=[]) starting_cursor ||= needle.cursor if s = @pattern._match?(needle, *s) match_string = MatchString.new(needle.string, starting_cursor, needle.cursor-1, needle.captures) capture_value = @capture_name && (needle.captures.has_key?(@capture_name) ? needle.captures[@capture_name] : @initial_capture_value) match_string = @block.call(match_string, needle.cursor, capture_value) if @block needle.capture(@capture_name, match_string) [starting_cursor, s] end end end class Match < Pattern def initialize(sub_pattern_or_name = nil, &block) if block @block = block elsif sub_pattern_or_name and sub_pattern_or_name.class == Symbol @name = sub_pattern_or_name elsif sub_pattern_or_name and sub_pattern_or_name.respond_to? "_match?" @pattern = sub_pattern_or_name elsif sub_pattern_or_name and sub_pattern_or_name.respond_to? "to_s" @pattern = sub_pattern_or_name.to_s end end def __match?(needle, pattern = nil, s = []) pattern ||= if @block @block.call elsif @name needle.captures[@name] || "" else @pattern end existing_captures = needle.captures.dup s = pattern._match?(needle, *s) needle.captures = needle.captures.merge(existing_captures) [pattern, s] if s end end class Rem < Pattern def __match?(needle, thread_state = nil) if thread_state needle_pull(thread_state) else [needle.push(needle.string.length-needle.cursor)] end end end class Arb < Pattern def __match?(needle, match_length = 0, thread_state = nil) needle.pull(thread_state) if needle.remaining_string.length >= match_length thread_state = needle.push(match_length) match_length += 1 [match_length, thread_state] end end end class ParameterizedPattern < Pattern def initialize(opts = nil, &block) if opts.class == Hash if opts.first @param_name = opts.first.first @initial_param_value = opts.first.last end else @initial_param_value = opts end @block = block end def self.parameter(name, &post_processor) @post_processor = post_processor define_method(name) do |needle| value = (@param_name && needle.captures.has_key?(@param_name)) ? needle.captures[@param_name] : @initial_param_value value = @block.call(value) if @block needle.capture(@param_name, value) value = post_processor.call(value) if @post_processor value end end end class Len < ParameterizedPattern parameter :len def __match?(needle, thread_state = nil) if thread_state needle.pull(thread_state) else len_temp = len(needle) [needle.push(len_temp)] if needle.remaining_string.length >= len_temp end end end class Pos < ParameterizedPattern parameter :pos def __match?(needle, matched = nil) return [true] if needle.cursor == pos(needle) and !matched end end class RPos < ParameterizedPattern parameter :pos def __match?(needle, matched = nil) return [true] if needle.string.length-needle.cursor == pos(needle) and !matched end end class Tab < ParameterizedPattern parameter :pos def __match?(needle, thread_state = nil) if thread_state needle.pull(thread_state) else len = pos(needle) - needle.cursor [needle.push(len)] if len >= 0 and needle.remaining_string.length >= len end end end class RTab < ParameterizedPattern parameter :pos def __match?(needle, thread_state = nil) if thread_state needle.pull(thread_state) else len = (needle.remaining_string.length - pos(needle)) [needle.push(len)] if len >= 0 and needle.remaining_string.length >= len end end end class Any < ParameterizedPattern parameter :chars, &:split def __match?(needle, thread_state = nil) if thread_state needle.pull(thread_state) elsif chars(needle).include? needle.remaining_string[0..0] [needle.push(1)] end end end class NotAny < ParameterizedPattern parameter :chars, &:split def __match?(needle, thread_state = nil) if thread_state needle.pull(thread_state) elsif !(chars(needle).include? needle.remaining_string[0..0]) [needle.push(1)] end end end class Span < ParameterizedPattern parameter :chars, &:split def __match?(needle, match_length = nil, thread_state = nil) unless match_length the_chars, match_length = chars(needle), 0 while needle.remaining_string.length > match_length and the_chars.include? needle.remaining_string[match_length..match_length] match_length += 1 end end needle.pull(thread_state) if match_length > 0 thread_state = needle.push(match_length) match_length -= 1 [match_length, thread_state] end end end class Break < ParameterizedPattern parameter :chars, &:split def __match?(needle, thread_state = nil) if thread_state needle.pull(thread_state) else the_chars, len = chars(needle), 0 while needle.remaining_string.length > len and !(the_chars.include? needle.remaining_string[len..len]) len += 1 end [needle.push(len)] end end end class BreakX < ParameterizedPattern parameter :chars, &:split def __match?(needle, len = 0, thread_state = nil) needle.pull(thread_state) the_chars = chars(needle) while needle.remaining_string.length > len and !(the_chars.include? needle.remaining_string[len..len]) len += 1 end [len+1, needle.push(len)] if needle.remaining_string.length >= len end end class Arbno < Match def __match?(needle, pattern = nil, s = [[]]) return if s.length == 0 if pattern existing_captures = needle.captures.dup s[-1] = pattern._match?(needle, *(s.last)) s = s[-1] ? s + [[]] : s[0..-2] needle.captures = needle.captures.merge(existing_captures) else if @block pattern = @block.call elsif @name pattern = needle.captures[@name] || "" else pattern = @pattern end end [pattern, s] end end class FailPat < Pattern def __match?(needle) end end class Abort < Pattern def __match?(needle) raise MatchFailed end end class Fence < Match def __match?(needle, on_backtrack = nil) if on_backtrack == :fail_match needle.match_failed = true return nil elsif on_backtrack == :return_nil return nil elsif @block pattern = @block.call elsif @name pattern = needle.captures[@name] || "" elsif @pattern pattern = @pattern else return [:fail_match] end return [:return_nil] if pattern._match?(needle) end end class Succeed < Pattern def _match?(needle, thread_state = nil) needle.pull(thread_state) [needle.push(0)] end end end class String include Cannonbol::Operators def __match?(needle, thread_state = nil) if thread_state needle.pull(thread_state) elsif self.length == 0 or (!needle.ignore_case and needle.remaining_string[0..self.length-1] == self) or (needle.ignore_case and needle.remaining_string[0..self.length-1].upcase == self.upcase) [needle.push(self.length)] end end end class Regexp include Cannonbol::Operators def __match?(needle, thread_state = nil) if RUBY_ENGINE == 'opal' options = "" options += "m" if `#{self}.multiline` options += "g" if `#{self}.global` options += "i" if needle.ignore_case or `#{self}.ignoreCase` else options = self.options | (needle.ignore_case ? Regexp::IGNORECASE : 0) end @cannonbol_regex ||= Regexp.new("^#{self.source}", options ) if thread_state needle.pull(thread_state) elsif m = @cannonbol_regex.match(needle.remaining_string) [needle.push(m[0].length)] end end end if RUBY_ENGINE == 'opal' class Proc def parameters /.*function[^(]*\(([^)]*)\)/.match(`#{self}.toString()`)[1].split(",").collect { |param| [:req, param.strip.to_sym]} end end end module Enumerable def match_any if self.first self[1..-1].inject(self.first) { |memo, item| memo | item } else FAIL end end def match_all self.inject("") { |memo, item| memo & item } end end class Object REM = Cannonbol::Rem.new ARB = Cannonbol::Arb.new FAIL = Cannonbol::FailPat.new ABORT = Cannonbol::Abort.new FENCE = Cannonbol::Fence.new SUCCEED = Cannonbol::Succeed.new def LEN(p={}, &block) Cannonbol::Len.new(p, &block) end def POS(p=nil, &block) Cannonbol::Pos.new(p, &block) end def RPOS(p=nil, &block) Cannonbol::RPos.new(p, &block) end def TAB(p=nil, &block) Cannonbol::Tab.new(p, &block) end def RTAB(p=nil, &block) Cannonbol::RTab.new(p, &block) end def ANY(p=nil, &block) Cannonbol::Any.new(p, &block) end def NOTANY(p=nil, &block) Cannonbol::NotAny.new(p, &block) end def SPAN(p=nil, &block) Cannonbol::Span.new(p, &block) end def BREAK(p=nil, &block) Cannonbol::Break.new(p, &block) end def BREAKX(p=nil, &block) Cannonbol::BreakX.new(p, &block) end def MATCH(p=nil, &block) Cannonbol::Match.new(p, &block) end def ARBNO(p=nil, &block) Cannonbol::Arbno.new(p, &block) end def FENCE(p=nil, &block) Cannonbol::Fence.new(p, &block) end end