gems.sass-3.4.19.lib.sass.script.parser.rb Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of sass-maven-plugin Show documentation
Show all versions of sass-maven-plugin Show documentation
A Maven Plugin that compiles Sass files.
require 'sass/script/lexer'
module Sass
module Script
# The parser for SassScript.
# It parses a string of code into a tree of {Script::Tree::Node}s.
class Parser
# The line number of the parser's current position.
#
# @return [Fixnum]
def line
@lexer.line
end
# The column number of the parser's current position.
#
# @return [Fixnum]
def offset
@lexer.offset
end
# @param str [String, StringScanner] The source text to parse
# @param line [Fixnum] The line on which the SassScript appears.
# Used for error reporting and sourcemap building
# @param offset [Fixnum] The character (not byte) offset where the script starts in the line.
# Used for error reporting and sourcemap building
# @param options [{Symbol => Object}] An options hash;
# see {file:SASS_REFERENCE.md#sass_options the Sass options documentation}
def initialize(str, line, offset, options = {})
@options = options
@lexer = lexer_class.new(str, line, offset, options)
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.
#
# @param warn_for_color [Boolean] Whether raw color values passed to
# interoplation should cause a warning.
# @return [Script::Tree::Node] The root node of the parse tree
# @raise [Sass::SyntaxError] if the expression isn't valid SassScript
def parse_interpolated(warn_for_color = false)
# Start two characters back to compensate for #{
start_pos = Sass::Source::Position.new(line, offset - 2)
expr = assert_expr :expr
assert_tok :end_interpolation
expr = Sass::Script::Tree::Interpolation.new(
nil, expr, nil, !:wb, !:wa, !:originally_text, warn_for_color)
expr.options = @options
node(expr, start_pos)
rescue Sass::SyntaxError => e
e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
raise e
end
# Parses a SassScript expression.
#
# @return [Script::Tree::Node] The root node of the parse tree
# @raise [Sass::SyntaxError] if the expression isn't valid SassScript
def parse
expr = assert_expr :expr
assert_done
expr.options = @options
expr
rescue Sass::SyntaxError => e
e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
raise e
end
# Parses a SassScript expression,
# ending it when it encounters one of the given identifier tokens.
#
# @param tokens [#include?(String)] A set of strings that delimit the expression.
# @return [Script::Tree::Node] The root node of the parse tree
# @raise [Sass::SyntaxError] if the expression isn't valid SassScript
def parse_until(tokens)
@stop_at = tokens
expr = assert_expr :expr
assert_done
expr.options = @options
expr
rescue Sass::SyntaxError => e
e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
raise e
end
# Parses the argument list for a mixin include.
#
# @return [(Array,
# {String => Script::Tree::Node},
# Script::Tree::Node,
# Script::Tree::Node)]
# The root nodes of the positional arguments, keyword arguments, and
# splat argument(s). Keyword arguments are in a hash from names to values.
# @raise [Sass::SyntaxError] if the argument list isn't valid SassScript
def parse_mixin_include_arglist
args, keywords = [], {}
if try_tok(:lparen)
args, keywords, splat, kwarg_splat = mixin_arglist
assert_tok(:rparen)
end
assert_done
args.each {|a| a.options = @options}
keywords.each {|k, v| v.options = @options}
splat.options = @options if splat
kwarg_splat.options = @options if kwarg_splat
return args, keywords, splat, kwarg_splat
rescue Sass::SyntaxError => e
e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
raise e
end
# Parses the argument list for a mixin definition.
#
# @return [(Array, Script::Tree::Node)]
# The root nodes of the arguments, and the splat argument.
# @raise [Sass::SyntaxError] if the argument list isn't valid SassScript
def parse_mixin_definition_arglist
args, splat = defn_arglist!(false)
assert_done
args.each do |k, v|
k.options = @options
v.options = @options if v
end
splat.options = @options if splat
return args, splat
rescue Sass::SyntaxError => e
e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
raise e
end
# Parses the argument list for a function definition.
#
# @return [(Array, Script::Tree::Node)]
# The root nodes of the arguments, and the splat argument.
# @raise [Sass::SyntaxError] if the argument list isn't valid SassScript
def parse_function_definition_arglist
args, splat = defn_arglist!(true)
assert_done
args.each do |k, v|
k.options = @options
v.options = @options if v
end
splat.options = @options if splat
return args, splat
rescue Sass::SyntaxError => e
e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
raise e
end
# Parse a single string value, possibly containing interpolation.
# Doesn't assert that the scanner is finished after parsing.
#
# @return [Script::Tree::Node] The root node of the parse tree.
# @raise [Sass::SyntaxError] if the string isn't valid SassScript
def parse_string
unless (peek = @lexer.peek) &&
(peek.type == :string ||
(peek.type == :funcall && peek.value.downcase == 'url'))
lexer.expected!("string")
end
expr = assert_expr :funcall
expr.options = @options
@lexer.unpeek!
expr
rescue Sass::SyntaxError => e
e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
raise e
end
# Parses a SassScript expression.
#
# @overload parse(str, line, offset, filename = nil)
# @return [Script::Tree::Node] The root node of the parse tree
# @see Parser#initialize
# @see Parser#parse
def self.parse(*args)
new(*args).parse
end
PRECEDENCE = [
:comma, :single_eq, :space, :or, :and,
[:eq, :neq],
[:gt, :gte, :lt, :lte],
[:plus, :minus],
[:times, :div, :mod],
]
ASSOCIATIVE = [:plus, :times]
class << self
# Returns an integer representing the precedence
# of the given operator.
# A lower integer indicates a looser binding.
#
# @private
def precedence_of(op)
PRECEDENCE.each_with_index do |e, i|
return i if Array(e).include?(op)
end
raise "[BUG] Unknown operator #{op.inspect}"
end
# Returns whether or not the given operation is associative.
#
# @private
def associative?(op)
ASSOCIATIVE.include?(op)
end
private
# 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
def production(name, sub, *ops)
class_eval < "string",
:default => "expression (e.g. 1px, bold)",
:mixin_arglist => "mixin argument",
:fn_arglist => "function argument",
:splat => "...",
:special_fun => '")"',
}
def assert_expr(name, expected = nil)
e = send(name)
return e if e
@lexer.expected!(expected || EXPR_NAMES[name] || EXPR_NAMES[:default])
end
def assert_tok(name)
# Avoids an array allocation caused by argument globbing in assert_toks.
t = try_tok(name)
return t if t
@lexer.expected!(Lexer::TOKEN_NAMES[name] || name.to_s)
end
def assert_toks(*names)
t = try_toks(*names)
return t if t
@lexer.expected!(names.map {|tok| Lexer::TOKEN_NAMES[tok] || tok}.join(" or "))
end
def try_tok(name)
# Avoids an array allocation caused by argument globbing in the try_toks method.
peeked = @lexer.peek
peeked && name == peeked.type && @lexer.next
end
def try_toks(*names)
peeked = @lexer.peek
peeked && names.include?(peeked.type) && @lexer.next
end
def assert_done
return if @lexer.done?
@lexer.expected!(EXPR_NAMES[:default])
end
# @overload node(value, source_range)
# @param value [Sass::Script::Value::Base]
# @param source_range [Sass::Source::Range]
# @overload node(value, start_pos, end_pos = source_position)
# @param value [Sass::Script::Value::Base]
# @param start_pos [Sass::Source::Position]
# @param end_pos [Sass::Source::Position]
def literal_node(value, source_range_or_start_pos, end_pos = source_position)
node(Sass::Script::Tree::Literal.new(value), source_range_or_start_pos, end_pos)
end
# @overload node(node, source_range)
# @param node [Sass::Script::Tree::Node]
# @param source_range [Sass::Source::Range]
# @overload node(node, start_pos, end_pos = source_position)
# @param node [Sass::Script::Tree::Node]
# @param start_pos [Sass::Source::Position]
# @param end_pos [Sass::Source::Position]
def node(node, source_range_or_start_pos, end_pos = source_position)
source_range =
if source_range_or_start_pos.is_a?(Sass::Source::Range)
source_range_or_start_pos
else
range(source_range_or_start_pos, end_pos)
end
node.line = source_range.start_pos.line
node.source_range = source_range
node.filename = @options[:filename]
node
end
end
end
end
© 2015 - 2025 Weber Informatics LLC | Privacy Policy