2009-12-20 05:57:35 -08:00
|
|
|
require 'sass/scss/rx'
|
2009-12-21 21:48:04 -08:00
|
|
|
require 'sass/scss/script_parser'
|
2009-12-20 05:57:35 -08:00
|
|
|
|
2009-12-20 01:13:38 -08:00
|
|
|
require 'strscan'
|
2009-12-21 20:05:13 -08:00
|
|
|
require 'set'
|
2009-12-20 01:13:38 -08:00
|
|
|
|
|
|
|
module Sass
|
|
|
|
module SCSS
|
|
|
|
class Parser
|
|
|
|
def initialize(str)
|
|
|
|
@scanner = StringScanner.new(str)
|
2009-12-21 20:41:27 -08:00
|
|
|
@line = 1
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
def parse
|
2009-12-20 05:57:35 -08:00
|
|
|
root = stylesheet
|
2009-12-21 13:21:43 -08:00
|
|
|
expected("selector or at-rule") unless @scanner.eos?
|
2009-12-20 05:57:35 -08:00
|
|
|
root
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
2009-12-21 13:36:28 -08:00
|
|
|
include Sass::SCSS::RX
|
|
|
|
|
2009-12-20 01:13:38 -08:00
|
|
|
def stylesheet
|
2009-12-21 20:41:27 -08:00
|
|
|
block_contents(node(Sass::Tree::RootNode.new(@scanner.string))) {s}
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
def s
|
2009-12-21 13:36:28 -08:00
|
|
|
nil while tok(S) || tok(CDC) || tok(CDO) || tok(COMMENT)
|
2009-12-20 05:45:58 -08:00
|
|
|
true
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
def ss
|
2009-12-21 13:36:28 -08:00
|
|
|
nil while tok(S) || tok(COMMENT)
|
2009-12-20 05:45:58 -08:00
|
|
|
true
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
2009-12-21 20:05:13 -08:00
|
|
|
DIRECTIVES = Set[:mixin, :include]
|
|
|
|
|
|
|
|
def directive
|
|
|
|
return unless name = tok(ATRULE)
|
|
|
|
ss
|
|
|
|
|
|
|
|
sym = name.gsub(/^@/, '').gsub('-', '_').to_sym
|
|
|
|
return send(sym) if DIRECTIVES.include?(sym)
|
|
|
|
|
2009-12-20 05:57:35 -08:00
|
|
|
val = str do
|
2009-12-21 14:23:48 -08:00
|
|
|
# Most at-rules take expressions (e.g. @media, @import),
|
|
|
|
# but some (e.g. @page) take selector-like arguments
|
|
|
|
expr || selector
|
2009-12-20 05:57:35 -08:00
|
|
|
end
|
2009-12-21 20:41:27 -08:00
|
|
|
node = node(Sass::Tree::DirectiveNode.new("#{name} #{val.strip}"))
|
2009-12-20 01:13:38 -08:00
|
|
|
|
2009-12-21 14:23:48 -08:00
|
|
|
@expected = '"{" or ";"'
|
|
|
|
if raw '{'
|
2009-12-21 20:18:38 -08:00
|
|
|
block_contents(node)
|
2009-12-21 14:23:48 -08:00
|
|
|
else
|
|
|
|
raw! ';'
|
2009-12-20 05:45:58 -08:00
|
|
|
end
|
|
|
|
|
2009-12-20 05:57:35 -08:00
|
|
|
node
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
2009-12-21 20:05:13 -08:00
|
|
|
def mixin
|
2009-12-21 20:41:27 -08:00
|
|
|
node = node(Sass::Tree::MixinDefNode.new(tok!(IDENT), []))
|
2009-12-21 20:05:13 -08:00
|
|
|
ss
|
2009-12-21 20:18:38 -08:00
|
|
|
block(node)
|
2009-12-21 20:05:13 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
def include
|
2009-12-21 20:41:27 -08:00
|
|
|
node = node(Sass::Tree::MixinNode.new(tok!(IDENT), []))
|
2009-12-21 20:05:13 -08:00
|
|
|
ss
|
|
|
|
node
|
|
|
|
end
|
|
|
|
|
2009-12-21 21:30:20 -08:00
|
|
|
def variable
|
|
|
|
return unless raw '!'
|
|
|
|
name = tok! IDENT
|
|
|
|
ss
|
|
|
|
|
|
|
|
if raw '|'
|
|
|
|
raw! '|'
|
|
|
|
guarded = true
|
|
|
|
end
|
|
|
|
|
|
|
|
raw! '='
|
|
|
|
ss
|
2009-12-21 21:48:04 -08:00
|
|
|
expr = sass_script_parser.parse
|
2009-12-21 21:30:20 -08:00
|
|
|
|
|
|
|
node(Sass::Tree::VariableNode.new(name, expr, guarded))
|
|
|
|
end
|
|
|
|
|
2009-12-20 01:13:38 -08:00
|
|
|
def operator
|
2009-12-21 01:22:45 -08:00
|
|
|
# Many of these operators (all except / and ,)
|
|
|
|
# are disallowed by the CSS spec,
|
|
|
|
# but they're included here for compatibility
|
|
|
|
# with some proprietary MS properties
|
|
|
|
ss if raw('/') || raw(',') || raw(':') || raw('.') || raw('=')
|
2009-12-20 05:45:58 -08:00
|
|
|
true
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
def unary_operator
|
2009-12-20 05:45:58 -08:00
|
|
|
raw('-') || raw('+')
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
def property
|
2009-12-21 13:36:28 -08:00
|
|
|
return unless name = tok(IDENT)
|
2009-12-20 05:45:58 -08:00
|
|
|
ss
|
2009-12-20 05:57:35 -08:00
|
|
|
name
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
2009-12-21 19:50:04 -08:00
|
|
|
def ruleset
|
2009-12-20 05:57:35 -08:00
|
|
|
rules = str do
|
2009-12-21 19:50:04 -08:00
|
|
|
return unless selector
|
2009-12-21 14:13:31 -08:00
|
|
|
|
2009-12-20 05:57:35 -08:00
|
|
|
while raw ','
|
|
|
|
ss; expr!(:selector)
|
|
|
|
end
|
2009-12-20 05:45:58 -08:00
|
|
|
end
|
2009-12-21 14:13:31 -08:00
|
|
|
|
2009-12-21 20:41:27 -08:00
|
|
|
block(node(Sass::Tree::RuleNode.new(rules.strip)))
|
2009-12-21 14:23:48 -08:00
|
|
|
end
|
|
|
|
|
2009-12-21 20:18:38 -08:00
|
|
|
def block(node)
|
2009-12-21 14:23:48 -08:00
|
|
|
raw! '{'
|
2009-12-21 20:18:38 -08:00
|
|
|
block_contents(node)
|
|
|
|
raw! '}'
|
|
|
|
ss
|
|
|
|
node
|
2009-12-20 05:57:35 -08:00
|
|
|
end
|
|
|
|
|
2009-12-21 14:13:31 -08:00
|
|
|
# A block may contain declarations and/or rulesets
|
2009-12-21 20:18:38 -08:00
|
|
|
def block_contents(node)
|
|
|
|
block_given? ? yield : ss
|
2009-12-21 21:30:20 -08:00
|
|
|
node << (child = block_child)
|
2009-12-21 14:34:09 -08:00
|
|
|
while raw(';') || (child && !child.children.empty?)
|
2009-12-21 20:18:38 -08:00
|
|
|
block_given? ? yield : ss
|
2009-12-21 21:30:20 -08:00
|
|
|
node << (child = block_child)
|
2009-12-20 05:45:58 -08:00
|
|
|
end
|
2009-12-20 05:57:35 -08:00
|
|
|
node
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
2009-12-21 21:30:20 -08:00
|
|
|
def block_child
|
|
|
|
variable || directive || declaration_or_ruleset
|
|
|
|
end
|
|
|
|
|
2009-12-21 19:50:04 -08:00
|
|
|
# This is a nasty hack, and the only place in the parser
|
|
|
|
# that requires backtracking.
|
|
|
|
# The reason is that we can't figure out if certain strings
|
|
|
|
# are declarations or rulesets with fixed finite lookahead.
|
|
|
|
# For example, "foo:bar baz baz baz..." could be either a property
|
|
|
|
# or a selector.
|
|
|
|
#
|
|
|
|
# To handle this, we simply check if it works as a property
|
|
|
|
# (which is the most common case)
|
|
|
|
# and, if it doesn't, try it as a ruleset.
|
|
|
|
#
|
|
|
|
# We could eke some more efficiency out of this
|
|
|
|
# by handling some easy cases (first token isn't an identifier,
|
|
|
|
# no colon after the identifier, whitespace after the colon),
|
|
|
|
# but I'm not sure the gains would be worth the added complexity.
|
2009-12-21 14:13:31 -08:00
|
|
|
def declaration_or_ruleset
|
2009-12-21 19:50:04 -08:00
|
|
|
pos = @scanner.pos
|
2009-12-21 20:41:27 -08:00
|
|
|
line = @line
|
2009-12-21 19:50:04 -08:00
|
|
|
begin
|
|
|
|
decl = declaration
|
|
|
|
rescue Sass::SyntaxError
|
2009-12-21 14:13:31 -08:00
|
|
|
end
|
2009-12-21 19:50:04 -08:00
|
|
|
|
|
|
|
return decl if decl && tok?(/[;}]/)
|
2009-12-21 20:41:27 -08:00
|
|
|
@line = line
|
2009-12-21 19:50:04 -08:00
|
|
|
@scanner.pos = pos
|
|
|
|
return ruleset
|
2009-12-21 14:13:31 -08:00
|
|
|
end
|
|
|
|
|
2009-12-21 19:50:04 -08:00
|
|
|
def selector
|
2009-12-21 02:15:10 -08:00
|
|
|
# The combinator here allows the "> E" hack
|
2009-12-21 19:50:04 -08:00
|
|
|
return unless combinator || simple_selector_sequence
|
2009-12-20 03:57:33 -08:00
|
|
|
simple_selector_sequence while combinator
|
|
|
|
true
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
2009-12-20 03:57:33 -08:00
|
|
|
def combinator
|
2009-12-21 13:36:28 -08:00
|
|
|
tok(PLUS) || tok(GREATER) || tok(TILDE) || tok(S)
|
2009-12-20 03:57:33 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
def simple_selector_sequence
|
2009-12-21 13:36:28 -08:00
|
|
|
unless element_name || tok(HASH) || class_expr ||
|
2009-12-20 04:20:10 -08:00
|
|
|
attrib || negation || pseudo
|
|
|
|
# This allows for stuff like http://www.w3.org/TR/css3-animations/#keyframes-
|
|
|
|
return expr
|
|
|
|
end
|
|
|
|
|
2009-12-21 02:15:10 -08:00
|
|
|
# The raw('*') allows the "E*" hack
|
2009-12-21 13:36:28 -08:00
|
|
|
nil while tok(HASH) || class_expr || attrib ||
|
2009-12-21 02:15:10 -08:00
|
|
|
negation || pseudo || raw('*')
|
2009-12-20 03:57:33 -08:00
|
|
|
true
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
def class_expr
|
2009-12-20 05:45:58 -08:00
|
|
|
return unless raw '.'
|
2009-12-21 13:36:28 -08:00
|
|
|
tok! IDENT
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
def element_name
|
2009-12-21 13:36:28 -08:00
|
|
|
res = tok(IDENT) || raw('*')
|
2009-12-20 15:03:18 -08:00
|
|
|
if raw '|'
|
|
|
|
@expected = "element name or *"
|
2009-12-21 13:36:28 -08:00
|
|
|
res = tok(IDENT) || raw!('*')
|
2009-12-20 15:03:18 -08:00
|
|
|
end
|
2009-12-20 03:57:33 -08:00
|
|
|
res
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
def attrib
|
2009-12-20 05:45:58 -08:00
|
|
|
return unless raw('[')
|
|
|
|
ss
|
2009-12-20 03:57:33 -08:00
|
|
|
attrib_name!; ss
|
2009-12-20 05:45:58 -08:00
|
|
|
if raw('=') ||
|
2009-12-21 13:36:28 -08:00
|
|
|
tok(INCLUDES) ||
|
|
|
|
tok(DASHMATCH) ||
|
|
|
|
tok(PREFIXMATCH) ||
|
|
|
|
tok(SUFFIXMATCH) ||
|
|
|
|
tok(SUBSTRINGMATCH)
|
2009-12-20 01:13:38 -08:00
|
|
|
ss
|
2009-12-20 15:03:18 -08:00
|
|
|
@expected = "identifier or string"
|
2009-12-21 13:36:28 -08:00
|
|
|
tok(IDENT) || tok!(STRING); ss
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
2009-12-20 05:45:58 -08:00
|
|
|
raw! ']'
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
2009-12-20 03:57:33 -08:00
|
|
|
def attrib_name!
|
2009-12-21 13:36:28 -08:00
|
|
|
if tok(IDENT)
|
2009-12-20 03:57:33 -08:00
|
|
|
# E or E|E
|
2009-12-21 13:36:28 -08:00
|
|
|
tok! IDENT if raw('|')
|
2009-12-20 03:57:33 -08:00
|
|
|
elsif raw('*')
|
|
|
|
# *|E
|
|
|
|
raw! '|'
|
2009-12-21 13:36:28 -08:00
|
|
|
tok! IDENT
|
2009-12-20 03:57:33 -08:00
|
|
|
else
|
|
|
|
# |E or E
|
|
|
|
raw '|'
|
2009-12-21 13:36:28 -08:00
|
|
|
tok! IDENT
|
2009-12-20 03:57:33 -08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2009-12-20 01:13:38 -08:00
|
|
|
def pseudo
|
2009-12-20 05:45:58 -08:00
|
|
|
return unless raw ':'
|
2009-12-20 03:57:33 -08:00
|
|
|
raw ':'
|
|
|
|
|
2009-12-20 15:03:18 -08:00
|
|
|
@expected = "pseudoclass or pseudoelement"
|
2009-12-21 13:36:28 -08:00
|
|
|
functional_pseudo || tok!(IDENT)
|
2009-12-20 03:57:33 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
def functional_pseudo
|
2009-12-21 13:36:28 -08:00
|
|
|
return unless tok FUNCTION
|
2009-12-20 03:57:33 -08:00
|
|
|
ss
|
|
|
|
expr! :pseudo_expr
|
2009-12-20 05:45:58 -08:00
|
|
|
raw! ')'
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
2009-12-20 03:57:33 -08:00
|
|
|
def pseudo_expr
|
2009-12-21 13:36:28 -08:00
|
|
|
return unless tok(PLUS) || raw('-') || tok(NUMBER) ||
|
|
|
|
tok(STRING) || tok(IDENT)
|
2009-12-20 03:57:33 -08:00
|
|
|
ss
|
2009-12-21 13:36:28 -08:00
|
|
|
ss while tok(PLUS) || raw('-') || tok(NUMBER) ||
|
|
|
|
tok(STRING) || tok(IDENT)
|
2009-12-20 03:57:33 -08:00
|
|
|
true
|
|
|
|
end
|
|
|
|
|
|
|
|
def negation
|
2009-12-21 13:36:28 -08:00
|
|
|
return unless tok(NOT)
|
2009-12-20 03:57:33 -08:00
|
|
|
ss
|
2009-12-20 15:03:18 -08:00
|
|
|
@expected = "selector"
|
2009-12-21 13:36:28 -08:00
|
|
|
element_name || tok(HASH) || class_expr || attrib || expr!(:pseudo)
|
2009-12-20 03:57:33 -08:00
|
|
|
end
|
|
|
|
|
2009-12-21 19:50:04 -08:00
|
|
|
def declaration
|
|
|
|
# The raw('*') allows the "*prop: val" hack
|
|
|
|
if raw '*'
|
|
|
|
name = expr!(:property)
|
|
|
|
else
|
|
|
|
return unless name = property
|
|
|
|
end
|
|
|
|
|
2009-12-21 21:35:46 -08:00
|
|
|
value =
|
|
|
|
if raw '='
|
2009-12-21 21:48:04 -08:00
|
|
|
sass_script_parser.parse
|
2009-12-21 21:35:46 -08:00
|
|
|
else
|
|
|
|
@expected = '":" or "="'
|
|
|
|
raw! ':'; ss
|
|
|
|
|
|
|
|
str do
|
|
|
|
expr! :expr
|
|
|
|
prio
|
|
|
|
end.strip
|
|
|
|
end
|
2009-12-21 19:50:04 -08:00
|
|
|
|
2009-12-21 21:35:46 -08:00
|
|
|
node(Sass::Tree::PropNode.new(name, value, :new))
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
def prio
|
2009-12-21 13:36:28 -08:00
|
|
|
return unless tok IMPORTANT
|
2009-12-20 05:45:58 -08:00
|
|
|
ss
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
def expr
|
2009-12-20 05:45:58 -08:00
|
|
|
return unless term
|
|
|
|
nil while operator && term
|
|
|
|
true
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
def term
|
2009-12-21 13:36:28 -08:00
|
|
|
unless tok(NUMBER) ||
|
|
|
|
tok(URI) ||
|
2009-12-21 01:34:46 -08:00
|
|
|
function ||
|
2009-12-21 13:36:28 -08:00
|
|
|
tok(STRING) ||
|
|
|
|
tok(IDENT) ||
|
|
|
|
tok(UNICODERANGE) ||
|
2009-12-21 01:34:46 -08:00
|
|
|
hexcolor
|
|
|
|
return unless unary_operator
|
2009-12-20 15:03:18 -08:00
|
|
|
@expected = "number or function"
|
2009-12-21 13:36:28 -08:00
|
|
|
tok(NUMBER) || expr!(:function)
|
2009-12-20 05:45:58 -08:00
|
|
|
end
|
|
|
|
ss
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
def function
|
2009-12-21 13:36:28 -08:00
|
|
|
return unless tok FUNCTION
|
2009-12-20 05:45:58 -08:00
|
|
|
ss
|
2009-12-21 02:20:50 -08:00
|
|
|
expr
|
2009-12-20 05:45:58 -08:00
|
|
|
raw! ')'; ss
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
# There is a constraint on the color that it must
|
|
|
|
# have either 3 or 6 hex-digits (i.e., [0-9a-fA-F])
|
|
|
|
# after the "#"; e.g., "#000" is OK, but "#abcd" is not.
|
|
|
|
def hexcolor
|
2009-12-21 13:36:28 -08:00
|
|
|
return unless tok HASH
|
2009-12-20 05:45:58 -08:00
|
|
|
ss
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
2009-12-20 05:57:35 -08:00
|
|
|
def str
|
|
|
|
@str = ""
|
|
|
|
yield
|
|
|
|
@str
|
|
|
|
ensure
|
|
|
|
@str = nil
|
|
|
|
end
|
|
|
|
|
2009-12-21 20:41:27 -08:00
|
|
|
def node(node)
|
|
|
|
node.line = @line
|
|
|
|
node
|
|
|
|
end
|
|
|
|
|
2009-12-21 21:30:20 -08:00
|
|
|
def sass_script_parser
|
2009-12-21 21:48:04 -08:00
|
|
|
ScriptParser.new(@scanner, @line,
|
2009-12-21 21:30:20 -08:00
|
|
|
@scanner.pos - @scanner.string.rindex("\n"))
|
|
|
|
end
|
|
|
|
|
2009-12-20 15:03:18 -08:00
|
|
|
EXPR_NAMES = {
|
|
|
|
:medium => "medium (e.g. print, screen)",
|
|
|
|
:pseudo_expr => "expression (e.g. fr, 2n+1)",
|
|
|
|
:expr => "expression (e.g. 1px, bold)",
|
|
|
|
}
|
|
|
|
|
2009-12-21 13:36:28 -08:00
|
|
|
TOK_NAMES = Haml::Util.to_hash(
|
|
|
|
Sass::SCSS::RX.constants.map {|c| [Sass::SCSS::RX.const_get(c), c.downcase]}).
|
|
|
|
merge(:ident => "identifier")
|
2009-12-20 15:03:18 -08:00
|
|
|
|
2009-12-21 19:50:04 -08:00
|
|
|
def tok?(rx)
|
|
|
|
@scanner.match?(rx)
|
|
|
|
end
|
|
|
|
|
2009-12-20 05:45:58 -08:00
|
|
|
def expr!(name)
|
2009-12-20 05:57:35 -08:00
|
|
|
(e = send(name)) && (return e)
|
2009-12-20 15:09:10 -08:00
|
|
|
expected(EXPR_NAMES[name] || name.to_s)
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
2009-12-21 13:36:28 -08:00
|
|
|
def tok!(rx)
|
|
|
|
(t = tok(rx)) && (return t)
|
|
|
|
expected(TOK_NAMES[rx])
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
2009-12-20 05:45:58 -08:00
|
|
|
def raw!(chr)
|
|
|
|
return true if raw(chr)
|
2009-12-20 15:09:10 -08:00
|
|
|
expected(chr.inspect)
|
|
|
|
end
|
|
|
|
|
|
|
|
def expected(name)
|
|
|
|
pos = @scanner.pos
|
|
|
|
|
|
|
|
after = @scanner.string[[pos - 15, 0].max...pos].gsub(/.*\n/m, '')
|
|
|
|
after = "..." + after if pos >= 15
|
|
|
|
|
|
|
|
expected = @expected || name
|
|
|
|
|
|
|
|
was = @scanner.rest[0...15].gsub(/\n.*/m, '')
|
|
|
|
was += "..." if @scanner.rest.size >= 15
|
|
|
|
raise Sass::SyntaxError.new(
|
|
|
|
"Invalid CSS after #{after.inspect}: expected #{expected}, was #{was.inspect}",
|
2009-12-21 20:41:27 -08:00
|
|
|
:line => @line)
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
2009-12-21 13:36:28 -08:00
|
|
|
def tok(rx)
|
|
|
|
res = @scanner.scan(rx)
|
2009-12-21 20:41:27 -08:00
|
|
|
if res
|
|
|
|
@line += res.count("\n")
|
|
|
|
@expected = nil
|
|
|
|
@str << res if @str && rx != COMMENT
|
|
|
|
end
|
|
|
|
|
2009-12-20 05:57:35 -08:00
|
|
|
res
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
def raw(chr)
|
2009-12-20 05:57:35 -08:00
|
|
|
res = @scanner.scan(RX.quote(chr))
|
2009-12-20 15:03:18 -08:00
|
|
|
@expected = nil if res
|
2009-12-20 05:57:35 -08:00
|
|
|
@str << res if res && @str
|
|
|
|
res
|
2009-12-20 01:13:38 -08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|