/scalate-jruby/src/main/resources/haml-3.0.25/lib/sass/script/parser.rb

http://github.com/scalate/scalate · Ruby · 401 lines · 294 code · 49 blank · 58 comment · 29 complexity · 6d780790bfea25960cf994d4b458b0a9 MD5 · raw file

  1. require 'sass/script/lexer'
  2. module Sass
  3. module Script
  4. # The parser for SassScript.
  5. # It parses a string of code into a tree of {Script::Node}s.
  6. class Parser
  7. # The line number of the parser's current position.
  8. #
  9. # @return [Fixnum]
  10. def line
  11. @lexer.line
  12. end
  13. # @param str [String, StringScanner] The source text to parse
  14. # @param line [Fixnum] The line on which the SassScript appears.
  15. # Used for error reporting
  16. # @param offset [Fixnum] The number of characters in on which the SassScript appears.
  17. # Used for error reporting
  18. # @param options [{Symbol => Object}] An options hash;
  19. # see {file:SASS_REFERENCE.md#sass_options the Sass options documentation}
  20. def initialize(str, line, offset, options = {})
  21. @options = options
  22. @lexer = lexer_class.new(str, line, offset, options)
  23. end
  24. # Parses a SassScript expression within an interpolated segment (`#{}`).
  25. # This means that it stops when it comes across an unmatched `}`,
  26. # which signals the end of an interpolated segment,
  27. # it returns rather than throwing an error.
  28. #
  29. # @return [Script::Node] The root node of the parse tree
  30. # @raise [Sass::SyntaxError] if the expression isn't valid SassScript
  31. def parse_interpolated
  32. expr = assert_expr :expr
  33. assert_tok :end_interpolation
  34. expr.options = @options
  35. expr
  36. rescue Sass::SyntaxError => e
  37. e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
  38. raise e
  39. end
  40. # Parses a SassScript expression.
  41. #
  42. # @return [Script::Node] The root node of the parse tree
  43. # @raise [Sass::SyntaxError] if the expression isn't valid SassScript
  44. def parse
  45. expr = assert_expr :expr
  46. assert_done
  47. expr.options = @options
  48. expr
  49. rescue Sass::SyntaxError => e
  50. e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
  51. raise e
  52. end
  53. # Parses a SassScript expression,
  54. # ending it when it encounters one of the given identifier tokens.
  55. #
  56. # @param [#include?(String)] A set of strings that delimit the expression.
  57. # @return [Script::Node] The root node of the parse tree
  58. # @raise [Sass::SyntaxError] if the expression isn't valid SassScript
  59. def parse_until(tokens)
  60. @stop_at = tokens
  61. expr = assert_expr :expr
  62. assert_done
  63. expr.options = @options
  64. expr
  65. rescue Sass::SyntaxError => e
  66. e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
  67. raise e
  68. end
  69. # Parses the argument list for a mixin include.
  70. #
  71. # @return [Array<Script::Node>] The root nodes of the arguments.
  72. # @raise [Sass::SyntaxError] if the argument list isn't valid SassScript
  73. def parse_mixin_include_arglist
  74. args = []
  75. if try_tok(:lparen)
  76. args = arglist || args
  77. assert_tok(:rparen)
  78. end
  79. assert_done
  80. args.each {|a| a.options = @options}
  81. args
  82. rescue Sass::SyntaxError => e
  83. e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
  84. raise e
  85. end
  86. # Parses the argument list for a mixin definition.
  87. #
  88. # @return [Array<Script::Node>] The root nodes of the arguments.
  89. # @raise [Sass::SyntaxError] if the argument list isn't valid SassScript
  90. def parse_mixin_definition_arglist
  91. args = defn_arglist!(false)
  92. assert_done
  93. args.each do |k, v|
  94. k.options = @options
  95. v.options = @options if v
  96. end
  97. args
  98. rescue Sass::SyntaxError => e
  99. e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
  100. raise e
  101. end
  102. # Parses a SassScript expression.
  103. #
  104. # @overload parse(str, line, offset, filename = nil)
  105. # @return [Script::Node] The root node of the parse tree
  106. # @see Parser#initialize
  107. # @see Parser#parse
  108. def self.parse(*args)
  109. new(*args).parse
  110. end
  111. PRECEDENCE = [
  112. :comma, :single_eq, :concat, :or, :and,
  113. [:eq, :neq],
  114. [:gt, :gte, :lt, :lte],
  115. [:plus, :minus],
  116. [:times, :div, :mod],
  117. ]
  118. ASSOCIATIVE = [:comma, :concat, :plus, :times]
  119. class << self
  120. # Returns an integer representing the precedence
  121. # of the given operator.
  122. # A lower integer indicates a looser binding.
  123. #
  124. # @private
  125. def precedence_of(op)
  126. PRECEDENCE.each_with_index do |e, i|
  127. return i if Array(e).include?(op)
  128. end
  129. raise "[BUG] Unknown operator #{op}"
  130. end
  131. # Returns whether or not the given operation is associative.
  132. #
  133. # @private
  134. def associative?(op)
  135. ASSOCIATIVE.include?(op)
  136. end
  137. private
  138. # Defines a simple left-associative production.
  139. # name is the name of the production,
  140. # sub is the name of the production beneath it,
  141. # and ops is a list of operators for this precedence level
  142. def production(name, sub, *ops)
  143. class_eval <<RUBY
  144. def #{name}
  145. interp = try_ops_after_interp(#{ops.inspect}, #{name.inspect}) and return interp
  146. return unless e = #{sub}
  147. while tok = try_tok(#{ops.map {|o| o.inspect}.join(', ')})
  148. interp = try_op_before_interp(tok, e) and return interp
  149. line = @lexer.line
  150. e = Operation.new(e, assert_expr(#{sub.inspect}), tok.type)
  151. e.line = line
  152. end
  153. e
  154. end
  155. RUBY
  156. end
  157. def unary(op, sub)
  158. class_eval <<RUBY
  159. def unary_#{op}
  160. return #{sub} unless tok = try_tok(:#{op})
  161. interp = try_op_before_interp(tok) and return interp
  162. line = @lexer.line
  163. op = UnaryOperation.new(assert_expr(:unary_#{op}), :#{op})
  164. op.line = line
  165. op
  166. end
  167. RUBY
  168. end
  169. end
  170. private
  171. # @private
  172. def lexer_class; Lexer; end
  173. production :expr, :interpolation, :comma
  174. production :equals, :interpolation, :single_eq
  175. def try_op_before_interp(op, prev = nil)
  176. return unless @lexer.peek && @lexer.peek.type == :begin_interpolation
  177. wb = @lexer.whitespace?(op)
  178. str = Script::String.new(Lexer::OPERATORS_REVERSE[op.type])
  179. str.line = @lexer.line
  180. interp = Script::Interpolation.new(prev, str, nil, wb, !:wa, :originally_text)
  181. interp.line = @lexer.line
  182. interpolation(interp)
  183. end
  184. def try_ops_after_interp(ops, name)
  185. return unless @lexer.after_interpolation?
  186. return unless op = try_tok(*ops)
  187. interp = try_op_before_interp(op) and return interp
  188. wa = @lexer.whitespace?
  189. str = Script::String.new(Lexer::OPERATORS_REVERSE[op.type])
  190. str.line = @lexer.line
  191. interp = Script::Interpolation.new(nil, str, assert_expr(name), !:wb, wa, :originally_text)
  192. interp.line = @lexer.line
  193. return interp
  194. end
  195. def interpolation(first = concat)
  196. e = first
  197. while interp = try_tok(:begin_interpolation)
  198. wb = @lexer.whitespace?(interp)
  199. line = @lexer.line
  200. mid = parse_interpolated
  201. wa = @lexer.whitespace?
  202. e = Script::Interpolation.new(e, mid, concat, wb, wa)
  203. e.line = line
  204. end
  205. e
  206. end
  207. def concat
  208. return unless e = or_expr
  209. while sub = or_expr
  210. e = node(Operation.new(e, sub, :concat))
  211. end
  212. e
  213. end
  214. production :or_expr, :and_expr, :or
  215. production :and_expr, :eq_or_neq, :and
  216. production :eq_or_neq, :relational, :eq, :neq
  217. production :relational, :plus_or_minus, :gt, :gte, :lt, :lte
  218. production :plus_or_minus, :times_div_or_mod, :plus, :minus
  219. production :times_div_or_mod, :unary_plus, :times, :div, :mod
  220. unary :plus, :unary_minus
  221. unary :minus, :unary_div
  222. unary :div, :unary_not # For strings, so /foo/bar works
  223. unary :not, :ident
  224. def ident
  225. return funcall unless @lexer.peek && @lexer.peek.type == :ident
  226. return if @stop_at && @stop_at.include?(@lexer.peek.value)
  227. name = @lexer.next
  228. if color = Color::HTML4_COLORS[name.value.downcase]
  229. return node(Color.new(color))
  230. end
  231. node(Script::String.new(name.value, :identifier))
  232. end
  233. def funcall
  234. return raw unless tok = try_tok(:funcall)
  235. args = fn_arglist || []
  236. assert_tok(:rparen)
  237. node(Script::Funcall.new(tok.value, args))
  238. end
  239. def defn_arglist!(must_have_default)
  240. return [] unless try_tok(:lparen)
  241. return [] if try_tok(:rparen)
  242. res = []
  243. loop do
  244. line = @lexer.line
  245. offset = @lexer.offset + 1
  246. c = assert_tok(:const)
  247. var = Script::Variable.new(c.value)
  248. if tok = (try_tok(:colon) || try_tok(:single_eq))
  249. val = assert_expr(:concat)
  250. if tok.type == :single_eq
  251. val.context = :equals
  252. val.options = @options
  253. Script.equals_warning("mixin argument defaults", "$#{c.value}",
  254. val.to_sass, false, line, offset, @options[:filename])
  255. end
  256. must_have_default = true
  257. elsif must_have_default
  258. raise SyntaxError.new("Required argument #{var.inspect} must come before any optional arguments.")
  259. end
  260. res << [var, val]
  261. break unless try_tok(:comma)
  262. end
  263. assert_tok(:rparen)
  264. res
  265. end
  266. def fn_arglist
  267. return unless e = equals
  268. return [e] unless try_tok(:comma)
  269. [e, *assert_expr(:fn_arglist)]
  270. end
  271. def arglist
  272. return unless e = interpolation
  273. return [e] unless try_tok(:comma)
  274. [e, *assert_expr(:arglist)]
  275. end
  276. def raw
  277. return special_fun unless tok = try_tok(:raw)
  278. node(Script::String.new(tok.value))
  279. end
  280. def special_fun
  281. return paren unless tok = try_tok(:special_fun)
  282. first = node(Script::String.new(tok.value.first))
  283. Haml::Util.enum_slice(tok.value[1..-1], 2).inject(first) do |l, (i, r)|
  284. Script::Interpolation.new(
  285. l, i, r && node(Script::String.new(r)),
  286. false, false)
  287. end
  288. end
  289. def paren
  290. return variable unless try_tok(:lparen)
  291. was_in_parens = @in_parens
  292. @in_parens = true
  293. e = assert_expr(:expr)
  294. assert_tok(:rparen)
  295. return e
  296. ensure
  297. @in_parens = was_in_parens
  298. end
  299. def variable
  300. return string unless c = try_tok(:const)
  301. node(Variable.new(*c.value))
  302. end
  303. def string
  304. return number unless first = try_tok(:string)
  305. return first.value unless try_tok(:begin_interpolation)
  306. line = @lexer.line
  307. mid = parse_interpolated
  308. last = assert_expr(:string)
  309. interp = StringInterpolation.new(first.value, mid, last)
  310. interp.line = line
  311. interp
  312. end
  313. def number
  314. return literal unless tok = try_tok(:number)
  315. num = tok.value
  316. num.original = num.to_s unless @in_parens
  317. num
  318. end
  319. def literal
  320. (t = try_tok(:color, :bool)) && (return t.value)
  321. end
  322. # It would be possible to have unified #assert and #try methods,
  323. # but detecting the method/token difference turns out to be quite expensive.
  324. EXPR_NAMES = {
  325. :string => "string",
  326. :default => "expression (e.g. 1px, bold)",
  327. :arglist => "mixin argument",
  328. :fn_arglist => "function argument",
  329. }
  330. def assert_expr(name)
  331. (e = send(name)) && (return e)
  332. @lexer.expected!(EXPR_NAMES[name] || EXPR_NAMES[:default])
  333. end
  334. def assert_tok(*names)
  335. (t = try_tok(*names)) && (return t)
  336. @lexer.expected!(names.map {|tok| Lexer::TOKEN_NAMES[tok] || tok}.join(" or "))
  337. end
  338. def try_tok(*names)
  339. peeked = @lexer.peek
  340. peeked && names.include?(peeked.type) && @lexer.next
  341. end
  342. def assert_done
  343. return if @lexer.done?
  344. @lexer.expected!(EXPR_NAMES[:default])
  345. end
  346. def node(node)
  347. node.line = @lexer.line
  348. node
  349. end
  350. end
  351. end
  352. end