| Class | Sass::Script::Parser |
| In: |
lib/sass/script/parser.rb
|
| Parent: | Object |
The parser for SassScript. It parses a string of code into a tree of {Script::Node}s.
| PRECEDENCE | = | [ :comma, :single_eq, :concat, :or, :and, [:eq, :neq], [:gt, :gte, :lt, :lte], [:plus, :minus], [:times, :div, :mod], ] | ||
| ASSOCIATIVE | = | [:comma, :concat, :plus, :times] | ||
| EXPR_NAMES | = | { :string => "string", :default => "expression (e.g. 1px, bold)", :arglist => "mixin argument", :fn_arglist => "function argument", } | It would be possible to have unified assert and try methods, but detecting the method/token difference turns out to be quite expensive. |
Returns whether or not the given operation is associative.
@private
# File lib/sass/script/parser.rb, line 149
149: def associative?(op)
150: ASSOCIATIVE.include?(op)
151: end
@param str [String, StringScanner] The source text to parse @param line [Fixnum] The line on which the SassScript appears.
Used for error reporting
@param offset [Fixnum] The number of characters in on which the SassScript appears.
Used for error reporting
@param options [{Symbol => Object}] An options hash;
see {file:SASS_REFERENCE.md#sass_options the Sass options documentation}
# File lib/sass/script/parser.rb, line 22
22: def initialize(str, line, offset, options = {})
23: @options = options
24: @lexer = lexer_class.new(str, line, offset, options)
25: end
Parses a SassScript expression.
@overload parse(str, line, offset, filename = nil) @return [Script::Node] The root node of the parse tree @see Parser#initialize @see Parser#parse
# File lib/sass/script/parser.rb, line 119
119: def self.parse(*args)
120: new(*args).parse
121: end
Returns an integer representing the precedence of the given operator. A lower integer indicates a looser binding.
@private
# File lib/sass/script/parser.rb, line 139
139: def precedence_of(op)
140: PRECEDENCE.each_with_index do |e, i|
141: return i if Array(e).include?(op)
142: end
143: raise "[BUG] Unknown operator #{op}"
144: end
Defines a simple left-associative production. name is the name of the production, sub is the name of the production beneath it, and ops is a list of operators for this precedence level
# File lib/sass/script/parser.rb, line 159
159: def production(name, sub, *ops)
160: class_eval " def \#{name}\n interp = try_ops_after_interp(\#{ops.inspect}, \#{name.inspect}) and return interp\n return unless e = \#{sub}\n while tok = try_tok(\#{ops.map {|o| o.inspect}.join(', ')})\n interp = try_op_before_interp(tok, e) and return interp\n line = @lexer.line\n e = Operation.new(e, assert_expr(\#{sub.inspect}), tok.type)\n e.line = line\n end\n e\n end\n"
161: end
# File lib/sass/script/parser.rb, line 176
176: def unary(op, sub)
177: class_eval " def unary_\#{op}\n return \#{sub} unless tok = try_tok(:\#{op})\n interp = try_op_before_interp(tok) and return interp\n line = @lexer.line \n op = UnaryOperation.new(assert_expr(:unary_\#{op}), :\#{op})\n op.line = line\n op\n end\n"
178: end
Parses a SassScript expression.
@return [Script::Node] The root node of the parse tree @raise [Sass::SyntaxError] if the expression isn‘t valid SassScript
# File lib/sass/script/parser.rb, line 48
48: def parse
49: expr = assert_expr :expr
50: assert_done
51: expr.options = @options
52: expr
53: rescue Sass::SyntaxError => e
54: e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
55: raise e
56: end
Parses a SassScript expression within an interpolated segment (`#{}`). This means that it stops when it comes across an unmatched `}`, which signals the end of an interpolated segment, it returns rather than throwing an error.
@return [Script::Node] The root node of the parse tree @raise [Sass::SyntaxError] if the expression isn‘t valid SassScript
# File lib/sass/script/parser.rb, line 34
34: def parse_interpolated
35: expr = assert_expr :expr
36: assert_tok :end_interpolation
37: expr.options = @options
38: expr
39: rescue Sass::SyntaxError => e
40: e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
41: raise e
42: end
Parses the argument list for a mixin definition.
@return [Array<Script::Node>] The root nodes of the arguments. @raise [Sass::SyntaxError] if the argument list isn‘t valid SassScript
# File lib/sass/script/parser.rb, line 99
99: def parse_mixin_definition_arglist
100: args = defn_arglist!(false)
101: assert_done
102:
103: args.each do |k, v|
104: k.options = @options
105: v.options = @options if v
106: end
107: args
108: rescue Sass::SyntaxError => e
109: e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
110: raise e
111: end
Parses the argument list for a mixin include.
@return [Array<Script::Node>] The root nodes of the arguments. @raise [Sass::SyntaxError] if the argument list isn‘t valid SassScript
# File lib/sass/script/parser.rb, line 79
79: def parse_mixin_include_arglist
80: args = []
81:
82: if try_tok(:lparen)
83: args = arglist || args
84: assert_tok(:rparen)
85: end
86: assert_done
87:
88: args.each {|a| a.options = @options}
89: args
90: rescue Sass::SyntaxError => e
91: e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
92: raise e
93: end
Parses a SassScript expression, ending it when it encounters one of the given identifier tokens.
@param [include?(String)] A set of strings that delimit the expression. @return [Script::Node] The root node of the parse tree @raise [Sass::SyntaxError] if the expression isn‘t valid SassScript
# File lib/sass/script/parser.rb, line 64
64: def parse_until(tokens)
65: @stop_at = tokens
66: expr = assert_expr :expr
67: assert_done
68: expr.options = @options
69: expr
70: rescue Sass::SyntaxError => e
71: e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
72: raise e
73: end
# File lib/sass/script/parser.rb, line 308
308: def arglist
309: return unless e = interpolation
310: return [e] unless try_tok(:comma)
311: [e, *assert_expr(:arglist)]
312: end
# File lib/sass/script/parser.rb, line 392
392: def assert_done
393: return if @lexer.done?
394: @lexer.expected!(EXPR_NAMES[:default])
395: end
# File lib/sass/script/parser.rb, line 377
377: def assert_expr(name)
378: (e = send(name)) && (return e)
379: @lexer.expected!(EXPR_NAMES[name] || EXPR_NAMES[:default])
380: end
# File lib/sass/script/parser.rb, line 382
382: def assert_tok(*names)
383: (t = try_tok(*names)) && (return t)
384: @lexer.expected!(names.map {|tok| Lexer::TOKEN_NAMES[tok] || tok}.join(" or "))
385: end
# File lib/sass/script/parser.rb, line 235
235: def concat
236: return unless e = or_expr
237: while sub = or_expr
238: e = node(Operation.new(e, sub, :concat))
239: end
240: e
241: end
# File lib/sass/script/parser.rb, line 273
273: def defn_arglist!(must_have_default)
274: return [] unless try_tok(:lparen)
275: return [] if try_tok(:rparen)
276: res = []
277: loop do
278: line = @lexer.line
279: offset = @lexer.offset + 1
280: c = assert_tok(:const)
281: var = Script::Variable.new(c.value)
282: if tok = (try_tok(:colon) || try_tok(:single_eq))
283: val = assert_expr(:concat)
284:
285: if tok.type == :single_eq
286: val.context = :equals
287: val.options = @options
288: Script.equals_warning("mixin argument defaults", "$#{c.value}",
289: val.to_sass, false, line, offset, @options[:filename])
290: end
291: must_have_default = true
292: elsif must_have_default
293: raise SyntaxError.new("Required argument #{var.inspect} must come before any optional arguments.")
294: end
295: res << [var, val]
296: break unless try_tok(:comma)
297: end
298: assert_tok(:rparen)
299: res
300: end
# File lib/sass/script/parser.rb, line 302
302: def fn_arglist
303: return unless e = equals
304: return [e] unless try_tok(:comma)
305: [e, *assert_expr(:fn_arglist)]
306: end
# File lib/sass/script/parser.rb, line 266
266: def funcall
267: return raw unless tok = try_tok(:funcall)
268: args = fn_arglist || []
269: assert_tok(:rparen)
270: node(Script::Funcall.new(tok.value, args))
271: end
# File lib/sass/script/parser.rb, line 255
255: def ident
256: return funcall unless @lexer.peek && @lexer.peek.type == :ident
257: return if @stop_at && @stop_at.include?(@lexer.peek.value)
258:
259: name = @lexer.next
260: if color = Color::HTML4_COLORS[name.value.downcase]
261: return node(Color.new(color))
262: end
263: node(Script::String.new(name.value, :identifier))
264: end
# File lib/sass/script/parser.rb, line 222
222: def interpolation(first = concat)
223: e = first
224: while interp = try_tok(:begin_interpolation)
225: wb = @lexer.whitespace?(interp)
226: line = @lexer.line
227: mid = parse_interpolated
228: wa = @lexer.whitespace?
229: e = Script::Interpolation.new(e, mid, concat, wb, wa)
230: e.line = line
231: end
232: e
233: end
# File lib/sass/script/parser.rb, line 363
363: def literal
364: (t = try_tok(:color, :bool)) && (return t.value)
365: end
# File lib/sass/script/parser.rb, line 397
397: def node(node)
398: node.line = @lexer.line
399: node
400: end
# File lib/sass/script/parser.rb, line 356
356: def number
357: return literal unless tok = try_tok(:number)
358: num = tok.value
359: num.original = num.to_s unless @in_parens
360: num
361: end
# File lib/sass/script/parser.rb, line 329
329: def paren
330: return variable unless try_tok(:lparen)
331: was_in_parens = @in_parens
332: @in_parens = true
333: e = assert_expr(:expr)
334: assert_tok(:rparen)
335: return e
336: ensure
337: @in_parens = was_in_parens
338: end
# File lib/sass/script/parser.rb, line 314
314: def raw
315: return special_fun unless tok = try_tok(:raw)
316: node(Script::String.new(tok.value))
317: end
# File lib/sass/script/parser.rb, line 319
319: def special_fun
320: return paren unless tok = try_tok(:special_fun)
321: first = node(Script::String.new(tok.value.first))
322: Haml::Util.enum_slice(tok.value[1..-1], 2).inject(first) do |l, (i, r)|
323: Script::Interpolation.new(
324: l, i, r && node(Script::String.new(r)),
325: false, false)
326: end
327: end
# File lib/sass/script/parser.rb, line 345
345: def string
346: return number unless first = try_tok(:string)
347: return first.value unless try_tok(:begin_interpolation)
348: line = @lexer.line
349: mid = parse_interpolated
350: last = assert_expr(:string)
351: interp = StringInterpolation.new(first.value, mid, last)
352: interp.line = line
353: interp
354: end
# File lib/sass/script/parser.rb, line 199
199: def try_op_before_interp(op, prev = nil)
200: return unless @lexer.peek && @lexer.peek.type == :begin_interpolation
201: wb = @lexer.whitespace?(op)
202: str = Script::String.new(Lexer::OPERATORS_REVERSE[op.type])
203: str.line = @lexer.line
204: interp = Script::Interpolation.new(prev, str, nil, wb, !:wa, :originally_text)
205: interp.line = @lexer.line
206: interpolation(interp)
207: end
# File lib/sass/script/parser.rb, line 209
209: def try_ops_after_interp(ops, name)
210: return unless @lexer.after_interpolation?
211: return unless op = try_tok(*ops)
212: interp = try_op_before_interp(op) and return interp
213:
214: wa = @lexer.whitespace?
215: str = Script::String.new(Lexer::OPERATORS_REVERSE[op.type])
216: str.line = @lexer.line
217: interp = Script::Interpolation.new(nil, str, assert_expr(name), !:wb, wa, :originally_text)
218: interp.line = @lexer.line
219: return interp
220: end
# File lib/sass/script/parser.rb, line 387
387: def try_tok(*names)
388: peeked = @lexer.peek
389: peeked && names.include?(peeked.type) && @lexer.next
390: end