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
|