Skip to content

Instantly share code, notes, and snippets.

@okuramasafumi
Created May 27, 2023 16:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save okuramasafumi/099f3b9590eef0a6a1b55d4ff78b582a to your computer and use it in GitHub Desktop.
Save okuramasafumi/099f3b9590eef0a6a1b55d4ff78b582a to your computer and use it in GitHub Desktop.
Pure Ruby implementation of StringScanner
class MyStringScanner
class ScanError < StandardError; end
attr_reader :string, :captures
attr_accessor :pos
def initialize(string, _obsolete = false, fixed_anchor: false)
@string = string
@fixed_anchor = fixed_anchor
@pos = 0
@previous_pos = 0
end
def fixed_anchor?
!!@fixed_anchor
end
def string=(string)
@string = string
@pos = 0
end
def <<(string)
@string << string
end
alias concat <<
def scan(pattern)
scan_full(pattern, true, true)
end
def scan_until(pattern)
match = yield_if_match(pattern, affect_matches: true, reset_if_fail: true) do |md|
@previous_pos = @pos
@pos += md.offset(0).last
@string[@previous_pos...@pos]
end
end
def scan_full(pattern, advance_pointer_p, return_string_p)
match = yield_if_match(pattern, match_from_current: true, reset_if_fail: true) do |md|
@previous_pos = @pos
@pos += md.match_length(0) if advance_pointer_p
match = md.match(0)
if return_string_p
match.to_s
else
match.size
end
end
end
def skip(pattern)
yield_if_match(pattern, affect_matches: false, match_from_current: true) do |md|
length = md.match_length(0)
@pos += length
length
end
end
def skip_until(pattern)
yield_if_match(pattern, affect_matches: false) do |md|
advanced = md.offset(0).last
@pos += advanced
advanced
end
end
def exist?(pattern)
raise TypeError if pattern.is_a?(String)
yield_if_match(pattern, affect_matches: false) do |md|
md.offset(0).first + 1
end
end
def match?(pattern)
yield_if_match(pattern, match_from_current: true) do |md|
md.match(0).size
end
end
def check(pattern)
yield_if_match(pattern, match_from_current: true) do |md|
md.match(0).to_s
end
end
def check_until(pattern)
yield_if_match(pattern) do |md|
rest[..md.offset(0).last - 1]
end
end
def search_full(pattern, advance_pointer_p, return_string_p)
yield_if_match(pattern, affect_matches: false) do |md|
last_index = md.offset(0).last
r = rest
@pos += last_index if advance_pointer_p
if return_string_p
r[...last_index]
else
last_index
end
end
end
private def yield_if_match(pattern, affect_matches: true, match_from_current: false, reset_if_fail: false)
return nil if rest.nil?
md = rest.match(pattern)
if md && (!match_from_current || md.offset(0).first == 0)
@last_md = md
yield(md)
else
@last_md = nil if affect_matches
if reset_if_fail
@captures = nil
@previous_pos = nil
end
nil
end
end
def bol?
@pos == 0 || @string[@pos - 1] == ?\n
end
alias beginning_of_line? bol?
def eos?
@pos >= string.length
end
def get_byte
byte = @string.bytes[@pos]
char = byte&.chr&.force_encoding(@string.encoding)
get_char(char)
end
def getch
char = @string[@pos]
get_char(char)
end
private def get_char(char)
@last_md = rest[0].match(char) rescue nil
@pos += 1
char
end
def peek(len)
@string[@pos, len]
end
def terminate
@pos = @string.length
@last_md = nil
end
def reset
@pos = 0
end
def unscan
raise ScanError if @previous_pos.nil?
@pos = @previous_pos
end
def [](n)
case n
when Integer
last_matches[n]
when String, Symbol
value = named_captures[n.to_s]
value.nil? ? raise(IndexError) : value
end
end
def matched
last_matches.last
end
def matched_size
matched&.size
end
def size
last_matches.size
end
def pre_match
@last_md.nil? ? nil : @string[...@pos - 1]
end
def post_match
@last_md.nil? ? nil : @string[@pos..]
end
def captures
@last_md&.captures
end
def named_captures
@last_md&.named_captures
end
def rest
@string[@pos..]
end
def rest_size
rest.size
end
def values_at(*args)
return nil if captures.nil?
args.map do |i|
if i.zero?
last_matches[0]
else
i.negative? ? captures[i] : captures[i - 1]
end
end
end
def charpos
@pos
end
private
def last_matches
@last_md&.to_a || []
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment