# backtick_javascript: true class StringScanner attr_reader :pos, :matched def initialize(string) @string = string @pos = 0 @matched = nil @working = string @match = [] end attr_reader :string def beginning_of_line? `#{@pos} === 0 || #{@string}.charAt(#{@pos} - 1) === "\n"` end def scan(pattern) pattern = anchor(pattern) %x{ var result = pattern.exec(#{@working}); if (result == null) { return #{@matched} = nil; } #{@prev_pos} = #{@pos}; #{@pos} += result[0].length; #{@working} = #{@working}.substring(result[0].length); #{@matched} = result[0]; #{@match} = result; return result[0]; } end def scan_until(pattern) pattern = anchor(pattern) %x{ var working = #{@working} for(var i = 0; working.length != i; ++i) { var result = pattern.exec(working.substr(i)); if (result !== null) { var matched_size = i + result[0].length var matched = working.substr(0, matched_size) #{@matched} = result[0] #{@match} = result #{@prev_pos} = #{@pos} + i; // Position of first character of matched #{@pos} += matched_size // Position one after last character of matched #{@working} = working.substr(matched_size) return matched } } return #{@matched} = nil; } end def [](idx) if @match.empty? return nil end case idx when Symbol idx = idx.to_s when String # noop else idx = ::Opal.coerce_to!(idx, Integer, :to_int) end %x{ var match = #{@match}; if (idx < 0) { idx += match.length; } if (idx < 0 || idx >= match.length) { return nil; } if (match[idx] == null) { return nil; } return match[idx]; } end def check(pattern) pattern = anchor(pattern) %x{ var result = pattern.exec(#{@working}); if (result == null) { return #{@matched} = nil; } return #{@matched} = result[0]; } end def check_until(pattern) %x{ var old_prev_pos = #{@prev_pos}; var old_pos = #{@pos}; var old_working = #{@working}; var result = #{scan_until(pattern)}; #{@prev_pos} = old_prev_pos; #{@pos} = old_pos; #{@working} = old_working; return result; } end def peek(length) `#{@working}.substring(0, length)` end def eos? `#{@working}.length === 0` end def exist?(pattern) %x{ var result = pattern.exec(#{@working}); if (result == null) { return nil; } else if (result.index == 0) { return 0; } else { return result.index + 1; } } end def skip(pattern) pattern = anchor(pattern) %x{ var result = pattern.exec(#{@working}); if (result == null) { #{@match} = []; return #{@matched} = nil; } else { var match_str = result[0]; var match_len = match_str.length; #{@matched} = match_str; #{@match} = result; #{@prev_pos} = #{@pos}; #{@pos} += match_len; #{@working} = #{@working}.substring(match_len); return match_len; } } end def skip_until(pattern) %x{ var result = #{scan_until(pattern)}; if (result === nil) { return nil; } else { #{@matched} = result.substr(-1); return result.length; } } end def get_byte %x{ var result = nil; if (#{@pos} < #{@string}.length) { #{@prev_pos} = #{@pos}; #{@pos} += 1; result = #{@matched} = #{@working}.substring(0, 1); #{@working} = #{@working}.substring(1); } else { #{@matched} = nil; } return result; } end def match?(pattern) pattern = anchor(pattern) %x{ var result = pattern.exec(#{@working}); if (result == null) { return nil; } else { #{@prev_pos} = #{@pos}; return result[0].length; } } end def pos=(pos) %x{ if (pos < 0) { pos += #{@string.length}; } } @pos = pos @working = `#{@string}.slice(pos)` end def matched_size %x{ if (#{@matched} === nil) { return nil; } return #{@matched}.length } end def post_match %x{ if (#{@matched} === nil) { return nil; } return #{@string}.substr(#{@pos}); } end def pre_match %x{ if (#{@matched} === nil) { return nil; } return #{@string}.substr(0, #{@prev_pos}); } end def reset @working = @string @matched = nil @pos = 0 end def rest @working end def rest? `#{@working}.length !== 0` end def rest_size rest.size end def terminate @match = nil self.pos = @string.length end def unscan @pos = @prev_pos @prev_pos = nil @match = nil self end alias bol? beginning_of_line? alias getch get_byte # not exactly the same, but for now... private def anchor(pattern) %x{ var flags = pattern.toString().match(/\/([^\/]+)$/); flags = flags ? flags[1] : undefined; return new RegExp('^(?:' + pattern.source + ')', flags); } end end