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

140 lines
3 KiB
Ruby
Raw Normal View History

2015-03-10 00:37:41 +09:00
require 'temple'
module Hamilton
class Parser < Temple::Parser
2015-03-11 01:00:08 +09:00
TAG_ID_CLASS_REGEXP = /[a-zA-Z0-9_-]+/
2015-03-11 02:20:42 +09:00
TAG_REGEXP = /[a-z]+/
DEFAULT_TAG = 'div'
2015-03-11 01:00:08 +09:00
EOF = -1
2015-03-10 00:37:41 +09:00
def call(template)
2015-03-10 23:38:42 +09:00
reset(template)
2015-03-10 20:01:33 +09:00
2015-03-10 23:38:42 +09:00
ast = [:multi]
ast += parse_lines
ast
2015-03-10 00:37:41 +09:00
end
private
2015-03-10 23:38:42 +09:00
# Reset the parser state.
def reset(template)
@lines = template.split("\n")
2015-03-11 00:17:49 +09:00
@current_lineno = -1
2015-03-10 23:38:42 +09:00
@current_indent = 0
end
2015-03-10 20:01:33 +09:00
2015-03-10 23:38:42 +09:00
# Parse lines in current_indent and return ASTs.
def parse_lines
ast = []
2015-03-11 00:17:49 +09:00
while next_indent == @current_indent
@current_lineno += 1
2015-03-11 01:00:08 +09:00
ast << parse_line(@lines[@current_lineno])
2015-03-11 00:24:51 +09:00
ast << [:static, "\n"] unless ast.last == [:newline]
2015-03-10 23:38:42 +09:00
end
ast
2015-03-10 20:01:33 +09:00
end
2015-03-10 23:38:42 +09:00
# Parse current line and return AST.
def parse_line(line)
return [:newline] if empty_line?(line)
scanner = StringScanner.new(line)
scanner.scan(/ +/)
case scanner.peek(1)
2015-03-10 19:16:07 +09:00
when '!'
parse_doctype(scanner)
2015-03-11 02:20:42 +09:00
when '%', '.', '#'
2015-03-10 23:38:42 +09:00
parse_tag(scanner)
2015-03-10 20:01:33 +09:00
else
parse_text(scanner)
2015-03-10 00:37:41 +09:00
end
end
2015-03-10 19:16:07 +09:00
def parse_doctype(scanner)
2015-03-10 23:38:42 +09:00
raise SyntaxError unless scanner.scan(/!!!/)
2015-03-10 19:16:07 +09:00
[:html, :doctype, 'html']
end
2015-03-10 23:38:42 +09:00
def parse_tag(scanner)
2015-03-11 02:20:42 +09:00
tag = DEFAULT_TAG
tag = scanner.scan(TAG_REGEXP) if scanner.scan(/%/)
2015-03-10 19:16:07 +09:00
2015-03-11 01:00:08 +09:00
attrs = [:html, :attrs]
attrs += parse_tag_id_and_class(scanner)
2015-03-10 23:38:42 +09:00
2015-03-11 01:00:08 +09:00
ast = [:html, :tag, tag, attrs]
2015-03-10 23:38:42 +09:00
if scanner.scan(/ +/) && text = scanner.scan(/.+/)
2015-03-10 19:16:07 +09:00
ast << [:static, text]
2015-03-10 23:38:42 +09:00
return ast
2015-03-10 19:16:07 +09:00
end
2015-03-10 23:38:42 +09:00
return ast if next_indent <= @current_indent
content = [:multi, [:static, "\n"]]
content += with_indented { parse_lines }
ast << content
2015-03-10 19:16:07 +09:00
ast
end
2015-03-10 20:01:33 +09:00
def parse_text(scanner)
2015-03-10 23:38:42 +09:00
ast = [:static]
ast << scanner.scan(/.+/)
ast
end
2015-03-10 20:01:33 +09:00
2015-03-11 01:00:08 +09:00
def parse_tag_id_and_class(scanner)
ids = []
classes = []
while prefix = scanner.scan(/[#.]/)
name = scanner.scan(TAG_ID_CLASS_REGEXP)
raise SyntaxError unless name
case prefix
when '#'
ids << name
when '.'
classes << name
end
end
ast = []
ast << [:html, :attr, 'id', [:static, ids.join(' ')]] if ids.any?
ast << [:html, :attr, 'class', [:static, classes.join(' ')]] if classes.any?
ast
end
2015-03-11 00:17:49 +09:00
# Return nearest line's indent level since next line. This method ignores
2015-03-11 01:00:08 +09:00
# empty line. It returns -1 if next_line does not exist.
2015-03-11 00:17:49 +09:00
def next_indent
lineno = @current_lineno + 1
2015-03-10 23:38:42 +09:00
while @lines[lineno] && empty_line?(@lines[lineno])
lineno += 1
end
2015-03-11 01:00:08 +09:00
return EOF unless @lines[lineno]
count_indent(@lines[lineno])
2015-03-10 20:01:33 +09:00
end
2015-03-10 23:38:42 +09:00
def with_indented(&block)
@current_indent += 1
result = block.call
@current_indent -= 1
result
end
def count_indent(line)
width = line[/\A +/].to_s.length
raise SyntaxError if width.odd?
width / 2
end
def empty_line?(line)
line =~ /\A *\Z/
2015-03-10 20:01:33 +09:00
end
2015-03-10 00:37:41 +09:00
end
end