PageRenderTime 60ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/vendor/bundle/gems/haml-4.0.3/lib/haml/parser.rb

https://github.com/esminc/anpikun
Ruby | 765 lines | 606 code | 103 blank | 56 comment | 145 complexity | 91dfe8ac190773a50abc491bb3bd932e MD5 | raw file
Possible License(s): GPL-2.0
  1. require 'strscan'
  2. module Haml
  3. class Parser
  4. include Haml::Util
  5. attr_reader :root
  6. # Designates an XHTML/XML element.
  7. ELEMENT = ?%
  8. # Designates a `<div>` element with the given class.
  9. DIV_CLASS = ?.
  10. # Designates a `<div>` element with the given id.
  11. DIV_ID = ?#
  12. # Designates an XHTML/XML comment.
  13. COMMENT = ?/
  14. # Designates an XHTML doctype or script that is never HTML-escaped.
  15. DOCTYPE = ?!
  16. # Designates script, the result of which is output.
  17. SCRIPT = ?=
  18. # Designates script that is always HTML-escaped.
  19. SANITIZE = ?&
  20. # Designates script, the result of which is flattened and output.
  21. FLAT_SCRIPT = ?~
  22. # Designates script which is run but not output.
  23. SILENT_SCRIPT = ?-
  24. # When following SILENT_SCRIPT, designates a comment that is not output.
  25. SILENT_COMMENT = ?#
  26. # Designates a non-parsed line.
  27. ESCAPE = ?\\
  28. # Designates a block of filtered text.
  29. FILTER = ?:
  30. # Designates a non-parsed line. Not actually a character.
  31. PLAIN_TEXT = -1
  32. # Keeps track of the ASCII values of the characters that begin a
  33. # specially-interpreted line.
  34. SPECIAL_CHARACTERS = [
  35. ELEMENT,
  36. DIV_CLASS,
  37. DIV_ID,
  38. COMMENT,
  39. DOCTYPE,
  40. SCRIPT,
  41. SANITIZE,
  42. FLAT_SCRIPT,
  43. SILENT_SCRIPT,
  44. ESCAPE,
  45. FILTER
  46. ]
  47. # The value of the character that designates that a line is part
  48. # of a multiline string.
  49. MULTILINE_CHAR_VALUE = ?|
  50. # Regex to check for blocks with spaces around arguments. Not to be confused
  51. # with multiline script.
  52. # For example:
  53. # foo.each do | bar |
  54. # = bar
  55. #
  56. BLOCK_WITH_SPACES = /do[\s]*\|[\s]*[^\|]*[\s]+\|\z/
  57. MID_BLOCK_KEYWORDS = %w[else elsif rescue ensure end when]
  58. START_BLOCK_KEYWORDS = %w[if begin case unless]
  59. # Try to parse assignments to block starters as best as possible
  60. START_BLOCK_KEYWORD_REGEX = /(?:\w+(?:,\s*\w+)*\s*=\s*)?(#{START_BLOCK_KEYWORDS.join('|')})/
  61. BLOCK_KEYWORD_REGEX = /^-?\s*(?:(#{MID_BLOCK_KEYWORDS.join('|')})|#{START_BLOCK_KEYWORD_REGEX.source})\b/
  62. # The Regex that matches a Doctype command.
  63. DOCTYPE_REGEX = /(\d(?:\.\d)?)?[\s]*([a-z]*)\s*([^ ]+)?/i
  64. # The Regex that matches a literal string or symbol value
  65. LITERAL_VALUE_REGEX = /:(\w*)|(["'])((?![\\#]|\2).|\\.)*\2/
  66. def initialize(template, options)
  67. # :eod is a special end-of-document marker
  68. @template = (template.rstrip).split(/\r\n|\r|\n/) + [:eod, :eod]
  69. @options = options
  70. @flat = false
  71. @index = 0
  72. # Record the indent levels of "if" statements to validate the subsequent
  73. # elsif and else statements are indented at the appropriate level.
  74. @script_level_stack = []
  75. @template_index = 0
  76. @template_tabs = 0
  77. end
  78. def parse
  79. @root = @parent = ParseNode.new(:root)
  80. @haml_comment = false
  81. @indentation = nil
  82. @line = next_line
  83. raise SyntaxError.new(Error.message(:indenting_at_start), @line.index) if @line.tabs != 0
  84. while next_line
  85. process_indent(@line) unless @line.text.empty?
  86. if flat?
  87. text = @line.full.dup
  88. text = "" unless text.gsub!(/^#{@flat_spaces}/, '')
  89. @filter_buffer << "#{text}\n"
  90. @line = @next_line
  91. next
  92. end
  93. @tab_up = nil
  94. process_line(@line.text, @line.index) unless @line.text.empty? || @haml_comment
  95. if @parent.type != :haml_comment && (block_opened? || @tab_up)
  96. @template_tabs += 1
  97. @parent = @parent.children.last
  98. end
  99. if !@haml_comment && !flat? && @next_line.tabs - @line.tabs > 1
  100. raise SyntaxError.new(Error.message(:deeper_indenting, @next_line.tabs - @line.tabs), @next_line.index)
  101. end
  102. @line = @next_line
  103. end
  104. # Close all the open tags
  105. close until @parent.type == :root
  106. @root
  107. rescue Haml::Error => e
  108. e.backtrace.unshift "#{@options[:filename]}:#{(e.line ? e.line + 1 : @index) + @options[:line] - 1}"
  109. raise
  110. end
  111. private
  112. # @private
  113. class Line < Struct.new(:text, :unstripped, :full, :index, :compiler, :eod)
  114. alias_method :eod?, :eod
  115. # @private
  116. def tabs
  117. line = self
  118. @tabs ||= compiler.instance_eval do
  119. break 0 if line.text.empty? || !(whitespace = line.full[/^\s+/])
  120. if @indentation.nil?
  121. @indentation = whitespace
  122. if @indentation.include?(?\s) && @indentation.include?(?\t)
  123. raise SyntaxError.new(Error.message(:cant_use_tabs_and_spaces), line.index)
  124. end
  125. @flat_spaces = @indentation * (@template_tabs+1) if flat?
  126. break 1
  127. end
  128. tabs = whitespace.length / @indentation.length
  129. break tabs if whitespace == @indentation * tabs
  130. break @template_tabs + 1 if flat? && whitespace =~ /^#{@flat_spaces}/
  131. message = Error.message(:inconsistent_indentation,
  132. Haml::Util.human_indentation(whitespace),
  133. Haml::Util.human_indentation(@indentation)
  134. )
  135. raise SyntaxError.new(message, line.index)
  136. end
  137. end
  138. end
  139. # @private
  140. class ParseNode < Struct.new(:type, :line, :value, :parent, :children)
  141. def initialize(*args)
  142. super
  143. self.children ||= []
  144. end
  145. def inspect
  146. text = "(#{type} #{value.inspect}"
  147. children.each {|c| text << "\n" << c.inspect.gsub(/^/, " ")}
  148. text + ")"
  149. end
  150. end
  151. # Processes and deals with lowering indentation.
  152. def process_indent(line)
  153. return unless line.tabs <= @template_tabs && @template_tabs > 0
  154. to_close = @template_tabs - line.tabs
  155. to_close.times {|i| close unless to_close - 1 - i == 0 && mid_block_keyword?(line.text)}
  156. end
  157. # Processes a single line of Haml.
  158. #
  159. # This method doesn't return anything; it simply processes the line and
  160. # adds the appropriate code to `@precompiled`.
  161. def process_line(text, index)
  162. @index = index + 1
  163. case text[0]
  164. when DIV_CLASS; push div(text)
  165. when DIV_ID
  166. return push plain(text) if text[1] == ?{
  167. push div(text)
  168. when ELEMENT; push tag(text)
  169. when COMMENT; push comment(text[1..-1].strip)
  170. when SANITIZE
  171. return push plain(text[3..-1].strip, :escape_html) if text[1..2] == "=="
  172. return push script(text[2..-1].strip, :escape_html) if text[1] == SCRIPT
  173. return push flat_script(text[2..-1].strip, :escape_html) if text[1] == FLAT_SCRIPT
  174. return push plain(text[1..-1].strip, :escape_html) if text[1] == ?\s
  175. push plain(text)
  176. when SCRIPT
  177. return push plain(text[2..-1].strip) if text[1] == SCRIPT
  178. push script(text[1..-1])
  179. when FLAT_SCRIPT; push flat_script(text[1..-1])
  180. when SILENT_SCRIPT; push silent_script(text)
  181. when FILTER; push filter(text[1..-1].downcase)
  182. when DOCTYPE
  183. return push doctype(text) if text[0...3] == '!!!'
  184. return push plain(text[3..-1].strip, false) if text[1..2] == "=="
  185. return push script(text[2..-1].strip, false) if text[1] == SCRIPT
  186. return push flat_script(text[2..-1].strip, false) if text[1] == FLAT_SCRIPT
  187. return push plain(text[1..-1].strip, false) if text[1] == ?\s
  188. push plain(text)
  189. when ESCAPE; push plain(text[1..-1])
  190. else; push plain(text)
  191. end
  192. end
  193. def block_keyword(text)
  194. return unless keyword = text.scan(BLOCK_KEYWORD_REGEX)[0]
  195. keyword[0] || keyword[1]
  196. end
  197. def mid_block_keyword?(text)
  198. MID_BLOCK_KEYWORDS.include?(block_keyword(text))
  199. end
  200. def push(node)
  201. @parent.children << node
  202. node.parent = @parent
  203. end
  204. def plain(text, escape_html = nil)
  205. if block_opened?
  206. raise SyntaxError.new(Error.message(:illegal_nesting_plain), @next_line.index)
  207. end
  208. unless contains_interpolation?(text)
  209. return ParseNode.new(:plain, @index, :text => text)
  210. end
  211. escape_html = @options[:escape_html] if escape_html.nil?
  212. script(unescape_interpolation(text, escape_html), false)
  213. end
  214. def script(text, escape_html = nil, preserve = false)
  215. raise SyntaxError.new(Error.message(:no_ruby_code, '=')) if text.empty?
  216. text = handle_ruby_multiline(text)
  217. escape_html = @options[:escape_html] if escape_html.nil?
  218. keyword = block_keyword(text)
  219. check_push_script_stack(keyword)
  220. ParseNode.new(:script, @index, :text => text, :escape_html => escape_html,
  221. :preserve => preserve, :keyword => keyword)
  222. end
  223. def flat_script(text, escape_html = nil)
  224. raise SyntaxError.new(Error.message(:no_ruby_code, '~')) if text.empty?
  225. script(text, escape_html, :preserve)
  226. end
  227. def silent_script(text)
  228. return haml_comment(text[2..-1]) if text[1] == SILENT_COMMENT
  229. raise SyntaxError.new(Error.message(:no_end), @index - 1) if text[1..-1].strip == "end"
  230. text = handle_ruby_multiline(text)
  231. keyword = block_keyword(text)
  232. check_push_script_stack(keyword)
  233. if ["else", "elsif", "when"].include?(keyword)
  234. if @script_level_stack.empty?
  235. raise Haml::SyntaxError.new(Error.message(:missing_if, keyword), @line.index)
  236. end
  237. if keyword == 'when' and !@script_level_stack.last[2]
  238. if @script_level_stack.last[1] + 1 == @line.tabs
  239. @script_level_stack.last[1] += 1
  240. end
  241. @script_level_stack.last[2] = true
  242. end
  243. if @script_level_stack.last[1] != @line.tabs
  244. message = Error.message(:bad_script_indent, keyword, @script_level_stack.last[1], @line.tabs)
  245. raise Haml::SyntaxError.new(message, @line.index)
  246. end
  247. end
  248. ParseNode.new(:silent_script, @index,
  249. :text => text[1..-1], :keyword => keyword)
  250. end
  251. def check_push_script_stack(keyword)
  252. if ["if", "case", "unless"].include?(keyword)
  253. # @script_level_stack contents are arrays of form
  254. # [:keyword, stack_level, other_info]
  255. @script_level_stack.push([keyword.to_sym, @line.tabs])
  256. @script_level_stack.last << false if keyword == 'case'
  257. @tab_up = true
  258. end
  259. end
  260. def haml_comment(text)
  261. @haml_comment = block_opened?
  262. ParseNode.new(:haml_comment, @index, :text => text)
  263. end
  264. def tag(line)
  265. tag_name, attributes, attributes_hashes, object_ref, nuke_outer_whitespace,
  266. nuke_inner_whitespace, action, value, last_line = parse_tag(line)
  267. preserve_tag = @options[:preserve].include?(tag_name)
  268. nuke_inner_whitespace ||= preserve_tag
  269. preserve_tag = false if @options[:ugly]
  270. escape_html = (action == '&' || (action != '!' && @options[:escape_html]))
  271. case action
  272. when '/'; self_closing = true
  273. when '~'; parse = preserve_script = true
  274. when '='
  275. parse = true
  276. if value[0] == ?=
  277. value = unescape_interpolation(value[1..-1].strip, escape_html)
  278. escape_html = false
  279. end
  280. when '&', '!'
  281. if value[0] == ?= || value[0] == ?~
  282. parse = true
  283. preserve_script = (value[0] == ?~)
  284. if value[1] == ?=
  285. value = unescape_interpolation(value[2..-1].strip, escape_html)
  286. escape_html = false
  287. else
  288. value = value[1..-1].strip
  289. end
  290. elsif contains_interpolation?(value)
  291. value = unescape_interpolation(value, escape_html)
  292. parse = true
  293. escape_html = false
  294. end
  295. else
  296. if contains_interpolation?(value)
  297. value = unescape_interpolation(value, escape_html)
  298. parse = true
  299. escape_html = false
  300. end
  301. end
  302. attributes = Parser.parse_class_and_id(attributes)
  303. attributes_list = []
  304. if attributes_hashes[:new]
  305. static_attributes, attributes_hash = attributes_hashes[:new]
  306. Buffer.merge_attrs(attributes, static_attributes) if static_attributes
  307. attributes_list << attributes_hash
  308. end
  309. if attributes_hashes[:old]
  310. static_attributes = parse_static_hash(attributes_hashes[:old])
  311. Buffer.merge_attrs(attributes, static_attributes) if static_attributes
  312. attributes_list << attributes_hashes[:old] unless static_attributes || @options[:suppress_eval]
  313. end
  314. attributes_list.compact!
  315. raise SyntaxError.new(Error.message(:illegal_nesting_self_closing), @next_line.index) if block_opened? && self_closing
  316. raise SyntaxError.new(Error.message(:no_ruby_code, action), last_line - 1) if parse && value.empty?
  317. raise SyntaxError.new(Error.message(:self_closing_content), last_line - 1) if self_closing && !value.empty?
  318. if block_opened? && !value.empty? && !is_ruby_multiline?(value)
  319. raise SyntaxError.new(Error.message(:illegal_nesting_line, tag_name), @next_line.index)
  320. end
  321. self_closing ||= !!(!block_opened? && value.empty? && @options[:autoclose].any? {|t| t === tag_name})
  322. value = nil if value.empty? && (block_opened? || self_closing)
  323. value = handle_ruby_multiline(value) if parse
  324. ParseNode.new(:tag, @index, :name => tag_name, :attributes => attributes,
  325. :attributes_hashes => attributes_list, :self_closing => self_closing,
  326. :nuke_inner_whitespace => nuke_inner_whitespace,
  327. :nuke_outer_whitespace => nuke_outer_whitespace, :object_ref => object_ref,
  328. :escape_html => escape_html, :preserve_tag => preserve_tag,
  329. :preserve_script => preserve_script, :parse => parse, :value => value)
  330. end
  331. # Renders a line that creates an XHTML tag and has an implicit div because of
  332. # `.` or `#`.
  333. def div(line)
  334. tag('%div' + line)
  335. end
  336. # Renders an XHTML comment.
  337. def comment(line)
  338. conditional, line = balance(line, ?[, ?]) if line[0] == ?[
  339. line.strip!
  340. conditional << ">" if conditional
  341. if block_opened? && !line.empty?
  342. raise SyntaxError.new(Haml::Error.message(:illegal_nesting_content), @next_line.index)
  343. end
  344. ParseNode.new(:comment, @index, :conditional => conditional, :text => line)
  345. end
  346. # Renders an XHTML doctype or XML shebang.
  347. def doctype(line)
  348. raise SyntaxError.new(Error.message(:illegal_nesting_header), @next_line.index) if block_opened?
  349. version, type, encoding = line[3..-1].strip.downcase.scan(DOCTYPE_REGEX)[0]
  350. ParseNode.new(:doctype, @index, :version => version, :type => type, :encoding => encoding)
  351. end
  352. def filter(name)
  353. raise Error.new(Error.message(:invalid_filter_name, name)) unless name =~ /^\w+$/
  354. @filter_buffer = String.new
  355. if filter_opened?
  356. @flat = true
  357. # If we don't know the indentation by now, it'll be set in Line#tabs
  358. @flat_spaces = @indentation * (@template_tabs+1) if @indentation
  359. end
  360. ParseNode.new(:filter, @index, :name => name, :text => @filter_buffer)
  361. end
  362. def close
  363. node, @parent = @parent, @parent.parent
  364. @template_tabs -= 1
  365. send("close_#{node.type}", node) if respond_to?("close_#{node.type}", :include_private)
  366. end
  367. def close_filter(_)
  368. @flat = false
  369. @flat_spaces = nil
  370. @filter_buffer = nil
  371. end
  372. def close_haml_comment(_)
  373. @haml_comment = false
  374. end
  375. def close_silent_script(node)
  376. @script_level_stack.pop if ["if", "case", "unless"].include? node.value[:keyword]
  377. # Post-process case statements to normalize the nesting of "when" clauses
  378. return unless node.value[:keyword] == "case"
  379. return unless first = node.children.first
  380. return unless first.type == :silent_script && first.value[:keyword] == "when"
  381. return if first.children.empty?
  382. # If the case node has a "when" child with children, it's the
  383. # only child. Then we want to put everything nested beneath it
  384. # beneath the case itself (just like "if").
  385. node.children = [first, *first.children]
  386. first.children = []
  387. end
  388. alias :close_script :close_silent_script
  389. # This is a class method so it can be accessed from {Haml::Helpers}.
  390. #
  391. # Iterates through the classes and ids supplied through `.`
  392. # and `#` syntax, and returns a hash with them as attributes,
  393. # that can then be merged with another attributes hash.
  394. def self.parse_class_and_id(list)
  395. attributes = {}
  396. list.scan(/([#.])([-:_a-zA-Z0-9]+)/) do |type, property|
  397. case type
  398. when '.'
  399. if attributes['class']
  400. attributes['class'] += " "
  401. else
  402. attributes['class'] = ""
  403. end
  404. attributes['class'] += property
  405. when '#'; attributes['id'] = property
  406. end
  407. end
  408. attributes
  409. end
  410. def parse_static_hash(text)
  411. attributes = {}
  412. scanner = StringScanner.new(text)
  413. scanner.scan(/\s+/)
  414. until scanner.eos?
  415. return unless key = scanner.scan(LITERAL_VALUE_REGEX)
  416. return unless scanner.scan(/\s*=>\s*/)
  417. return unless value = scanner.scan(LITERAL_VALUE_REGEX)
  418. return unless scanner.scan(/\s*(?:,|$)\s*/)
  419. attributes[eval(key).to_s] = eval(value).to_s
  420. end
  421. attributes
  422. end
  423. # Parses a line into tag_name, attributes, attributes_hash, object_ref, action, value
  424. def parse_tag(line)
  425. match = line.scan(/%([-:\w]+)([-:\w\.\#]*)(.*)/)[0]
  426. raise SyntaxError.new(Error.message(:invalid_tag, line)) unless match
  427. tag_name, attributes, rest = match
  428. if attributes =~ /[\.#](\.|#|\z)/
  429. raise SyntaxError.new(Error.message(:illegal_element))
  430. end
  431. new_attributes_hash = old_attributes_hash = last_line = nil
  432. object_ref = "nil"
  433. attributes_hashes = {}
  434. while rest
  435. case rest[0]
  436. when ?{
  437. break if old_attributes_hash
  438. old_attributes_hash, rest, last_line = parse_old_attributes(rest)
  439. attributes_hashes[:old] = old_attributes_hash
  440. when ?(
  441. break if new_attributes_hash
  442. new_attributes_hash, rest, last_line = parse_new_attributes(rest)
  443. attributes_hashes[:new] = new_attributes_hash
  444. when ?[
  445. break unless object_ref == "nil"
  446. object_ref, rest = balance(rest, ?[, ?])
  447. else; break
  448. end
  449. end
  450. if rest
  451. nuke_whitespace, action, value = rest.scan(/(<>|><|[><])?([=\/\~&!])?(.*)?/)[0]
  452. nuke_whitespace ||= ''
  453. nuke_outer_whitespace = nuke_whitespace.include? '>'
  454. nuke_inner_whitespace = nuke_whitespace.include? '<'
  455. end
  456. if @options[:remove_whitespace]
  457. nuke_outer_whitespace = true
  458. nuke_inner_whitespace = true
  459. end
  460. value = value.to_s.strip
  461. [tag_name, attributes, attributes_hashes, object_ref, nuke_outer_whitespace,
  462. nuke_inner_whitespace, action, value, last_line || @index]
  463. end
  464. def parse_old_attributes(line)
  465. line = line.dup
  466. last_line = @index
  467. begin
  468. attributes_hash, rest = balance(line, ?{, ?})
  469. rescue SyntaxError => e
  470. if line.strip[-1] == ?, && e.message == Error.message(:unbalanced_brackets)
  471. line << "\n" << @next_line.text
  472. last_line += 1
  473. next_line
  474. retry
  475. end
  476. raise e
  477. end
  478. attributes_hash = attributes_hash[1...-1] if attributes_hash
  479. return attributes_hash, rest, last_line
  480. end
  481. def parse_new_attributes(line)
  482. line = line.dup
  483. scanner = StringScanner.new(line)
  484. last_line = @index
  485. attributes = {}
  486. scanner.scan(/\(\s*/)
  487. loop do
  488. name, value = parse_new_attribute(scanner)
  489. break if name.nil?
  490. if name == false
  491. text = (Haml::Util.balance(line, ?(, ?)) || [line]).first
  492. raise Haml::SyntaxError.new(Error.message(:invalid_attribute_list, text.inspect), last_line - 1)
  493. end
  494. attributes[name] = value
  495. scanner.scan(/\s*/)
  496. if scanner.eos?
  497. line << " " << @next_line.text
  498. last_line += 1
  499. next_line
  500. scanner.scan(/\s*/)
  501. end
  502. end
  503. static_attributes = {}
  504. dynamic_attributes = "{"
  505. attributes.each do |name, (type, val)|
  506. if type == :static
  507. static_attributes[name] = val
  508. else
  509. dynamic_attributes << inspect_obj(name) << " => " << val << ","
  510. end
  511. end
  512. dynamic_attributes << "}"
  513. dynamic_attributes = nil if dynamic_attributes == "{}"
  514. return [static_attributes, dynamic_attributes], scanner.rest, last_line
  515. end
  516. def parse_new_attribute(scanner)
  517. unless name = scanner.scan(/[-:\w]+/)
  518. return if scanner.scan(/\)/)
  519. return false
  520. end
  521. scanner.scan(/\s*/)
  522. return name, [:static, true] unless scanner.scan(/=/) #/end
  523. scanner.scan(/\s*/)
  524. unless quote = scanner.scan(/["']/)
  525. return false unless var = scanner.scan(/(@@?|\$)?\w+/)
  526. return name, [:dynamic, var]
  527. end
  528. re = /((?:\\.|\#(?!\{)|[^#{quote}\\#])*)(#{quote}|#\{)/
  529. content = []
  530. loop do
  531. return false unless scanner.scan(re)
  532. content << [:str, scanner[1].gsub(/\\(.)/, '\1')]
  533. break if scanner[2] == quote
  534. content << [:ruby, balance(scanner, ?{, ?}, 1).first[0...-1]]
  535. end
  536. return name, [:static, content.first[1]] if content.size == 1
  537. return name, [:dynamic,
  538. '"' + content.map {|(t, v)| t == :str ? inspect_obj(v)[1...-1] : "\#{#{v}}"}.join + '"']
  539. end
  540. def raw_next_line
  541. text = @template.shift
  542. return unless text
  543. index = @template_index
  544. @template_index += 1
  545. return text, index
  546. end
  547. def next_line
  548. text, index = raw_next_line
  549. return unless text
  550. # :eod is a special end-of-document marker
  551. line =
  552. if text == :eod
  553. Line.new '-#', '-#', '-#', index, self, true
  554. else
  555. Line.new text.strip, text.lstrip.chomp, text, index, self, false
  556. end
  557. # `flat?' here is a little outdated,
  558. # so we have to manually check if either the previous or current line
  559. # closes the flat block, as well as whether a new block is opened.
  560. line_defined = instance_variable_defined?('@line')
  561. @line.tabs if line_defined
  562. unless (flat? && !closes_flat?(line) && !closes_flat?(@line)) ||
  563. (line_defined && @line.text[0] == ?: && line.full =~ %r[^#{@line.full[/^\s+/]}\s])
  564. return next_line if line.text.empty?
  565. handle_multiline(line)
  566. end
  567. @next_line = line
  568. end
  569. def closes_flat?(line)
  570. line && !line.text.empty? && line.full !~ /^#{@flat_spaces}/
  571. end
  572. def un_next_line(line)
  573. @template.unshift line
  574. @template_index -= 1
  575. end
  576. def handle_multiline(line)
  577. return unless is_multiline?(line.text)
  578. line.text.slice!(-1)
  579. while new_line = raw_next_line.first
  580. break if new_line == :eod
  581. next if new_line.strip.empty?
  582. break unless is_multiline?(new_line.strip)
  583. line.text << new_line.strip[0...-1]
  584. end
  585. un_next_line new_line
  586. end
  587. # Checks whether or not `line` is in a multiline sequence.
  588. def is_multiline?(text)
  589. text && text.length > 1 && text[-1] == MULTILINE_CHAR_VALUE && text[-2] == ?\s && text !~ BLOCK_WITH_SPACES
  590. end
  591. def handle_ruby_multiline(text)
  592. text = text.rstrip
  593. return text unless is_ruby_multiline?(text)
  594. un_next_line @next_line.full
  595. begin
  596. new_line = raw_next_line.first
  597. break if new_line == :eod
  598. next if new_line.strip.empty?
  599. text << " " << new_line.strip
  600. end while is_ruby_multiline?(new_line.strip)
  601. next_line
  602. text
  603. end
  604. # `text' is a Ruby multiline block if it:
  605. # - ends with a comma
  606. # - but not "?," which is a character literal
  607. # (however, "x?," is a method call and not a literal)
  608. # - and not "?\," which is a character literal
  609. #
  610. def is_ruby_multiline?(text)
  611. text && text.length > 1 && text[-1] == ?, &&
  612. !((text[-3..-2] =~ /\W\?/) || text[-3..-2] == "?\\")
  613. end
  614. def balance(*args)
  615. res = Haml::Util.balance(*args)
  616. return res if res
  617. raise SyntaxError.new(Error.message(:unbalanced_brackets))
  618. end
  619. def block_opened?
  620. @next_line.tabs > @line.tabs
  621. end
  622. # Same semantics as block_opened?, except that block_opened? uses Line#tabs,
  623. # which doesn't interact well with filter lines
  624. def filter_opened?
  625. @next_line.full =~ (@indentation ? /^#{@indentation * @template_tabs}/ : /^\s/)
  626. end
  627. def flat?
  628. @flat
  629. end
  630. end
  631. end