class Crass::Scanner
CSS while preserving the original text.
Similar to a StringScanner, but with extra functionality needed to tokenize
def consume
Consumes the next character and returns it, advancing the pointer, or
def consume if @pos < @len @pos += 1 @current = @scanner.getch else '' end end
def consume_rest
the end of the string. Returns an empty string is the end of the string
Consumes the rest of the string and returns it, advancing the pointer to
def consume_rest result = @scanner.rest # `StringScanner#rest` does not advance the scan pointer, so move it to # the end of the input to keep the byte offset in sync with {#pos}. This # ensures a subsequent {#marked} extracts the correct substring. @scanner.terminate @current = result[-1] @pos = @len result end
def eos?
Returns `true` if the end of the string has been reached, `false`
def eos? @pos == @len end
def initialize(input)
def initialize(input) @string = input.is_a?(IO) ? input.read : input.to_s @scanner = StringScanner.new(@string) reset end
def mark
Sets the marker to the position of the next character that will be
def mark @byte_marker = @scanner.pos @marker = @pos end
def marked
Returns the substring between {#marker} and {#pos}, without altering the
def marked # Extract the marked text using byte offsets rather than character # offsets. Slicing the original string by character offset is O(n) on # multi-byte input (Ruby must translate the character index into a byte # index), which makes tokenizing non-ASCII input superlinear. Byte slicing # is O(length) regardless of how far into the string we are. @string.byteslice(@byte_marker, @scanner.pos - @byte_marker) || '' end
def peek(length = 1)
doesn't consume them. The number of characters returned may be less than
Returns up to _length_ characters starting at the current position, but
def peek(length = 1) # Grab the bytes for up to _length_ characters and then take the first # _length_ characters. A UTF-8 character is at most four bytes, so `length # * 4` bytes always contains at least _length_ whole characters when that # many remain. This avoids the O(n) character-offset slice that # `@string[pos, length]` would otherwise perform on multi-byte input. @string.byteslice(@scanner.pos, length * 4).slice(0, length) || '' end
def reconsume
{#current}. The next call to {#consume} will re-consume the current
Moves the pointer back one character without changing the value of
def reconsume @scanner.unscan @pos -= 1 if @pos > 0 end
def reset
def reset @scanner.reset @byte_marker = 0 @current = nil @len = @string.size @marker = 0 @pos = 0 end
def scan(pattern)
matched substring will be returned and the pointer will be advanced.
Tries to match _pattern_ at the current position. If it matches, the
def scan(pattern) if match = @scanner.scan(pattern) @pos += match.size @current = match[-1] end match end
def scan_until(pattern)
to and including the end of the match, and advances the pointer. If there
Scans the string until the _pattern_ is matched. Returns the substring up
def scan_until(pattern) if match = @scanner.scan_until(pattern) @pos += match.size @current = match[-1] end match end