1
0
Fork 0
mirror of https://github.com/haml/haml.git synced 2022-11-09 12:33:31 -05:00
haml--haml/lib/hamlit/parser.rb

118 lines
3 KiB
Ruby

require 'strscan'
require 'temple'
require 'hamlit/parsers/attribute'
require 'hamlit/parsers/comment'
require 'hamlit/parsers/doctype'
require 'hamlit/parsers/filter'
require 'hamlit/parsers/multiline'
require 'hamlit/parsers/script'
require 'hamlit/parsers/tag'
require 'hamlit/parsers/text'
require 'hamlit/parsers/whitespace'
module Hamlit
class Parser < Temple::Parser
include Parsers::Attribute
include Parsers::Comment
include Parsers::Doctype
include Parsers::Filter
include Parsers::Multiline
include Parsers::Script
include Parsers::Tag
include Parsers::Text
include Parsers::Whitespace
SKIP_NEWLINE_EXPS = %i[newline code multi].freeze
define_options :format
def call(template)
reset(template)
ast = [:multi]
ast += parse_lines
ast
end
private
# Reset the parser state.
def reset(template)
template = preprocess_multilines(template)
reset_lines(template.split("\n"))
reset_indent
reset_outer_removal
end
# Parse lines in current_indent and return ASTs.
def parse_lines
ast = []
loop do
width = next_width
if width != @current_indent * 2
if width != Hamlit::EOF && (width > @current_indent * 2 || width.odd?)
ast << [:multi, [:newline], [:newline]]
ast << syntax_error(
"inconsistent indentation: #{2 * @current_indent} spaces used for indentation, "\
"but the rest of the document was indented using #{width} spaces"
)
end
break
end
@current_lineno += 1
node = parse_line(current_line)
if @outer_removal.include?(@current_indent) && ast.last == [:static, "\n"]
ast.delete_at(-1)
end
ast << node
ast << [:newline]
ast << [:static, "\n"] unless skip_newline?(node)
end
ast
end
# Parse current line and return AST.
def parse_line(line)
return [:multi] if empty_line?(line)
scanner = StringScanner.new(line)
scanner.scan(/ +/)
if scanner.match?(/\#{/)
return parse_text(scanner)
elsif scanner.match?(/&=/)
return parse_script(scanner, force_escape: true)
elsif scanner.match?(/!=/)
return parse_script(scanner, disable_escape: true)
elsif scanner.match?(/[.#](\Z|[^a-zA-Z0-9_-])/)
return parse_text(scanner)
end
case scanner.peek(1)
when '!'
parse_doctype(scanner)
when '%', '.', '#'
parse_tag(scanner)
when '='
parse_script(scanner)
when '~'
parse_preserve(scanner)
when '-'
parse_silent_script(scanner)
when '/'
parse_comment(scanner)
when ':'
parse_filter(scanner)
else
parse_text(scanner)
end
end
def skip_newline?(ast)
SKIP_NEWLINE_EXPS.include?(ast.first) ||
(ast[0..1] == [:haml, :doctype]) ||
(ast[0..2] == [:haml, :filter, 'ruby']) ||
@outer_removal.include?(@current_indent)
end
end
end