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

190 lines
4.3 KiB
Ruby
Raw Normal View History

2015-03-16 01:32:02 +09:00
require 'strscan'
2015-03-10 00:37:41 +09:00
require 'temple'
2015-03-16 02:37:10 +09:00
require 'hamlit/concerns/brace_reader'
require 'hamlit/concerns/escapable'
2015-03-16 02:37:10 +09:00
require 'hamlit/concerns/indentable'
require 'hamlit/concerns/line_reader'
2015-03-10 00:37:41 +09:00
module Hamlit
class Parser < Temple::Parser
2015-03-16 02:37:10 +09:00
include Concerns::BraceReader
include Concerns::Escapable
2015-03-16 02:37:10 +09:00
include Concerns::Indentable
include Concerns::LineReader
2015-03-11 01:00:08 +09:00
TAG_ID_CLASS_REGEXP = /[a-zA-Z0-9_-]+/
2015-03-11 22:35:33 +09:00
INTERNAL_STATEMENTS = %w[else elsif when].freeze
2015-03-11 22:16:50 +09:00
SKIP_NEWLINE_EXPS = %i[newline code multi].freeze
2015-03-15 12:40:58 +09:00
TAG_REGEXP = /[a-zA-Z0-9\-_]+/
2015-03-11 02:20:42 +09:00
DEFAULT_TAG = 'div'
2015-03-11 01:00:08 +09:00
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-16 02:37:10 +09:00
# Reset the parser state.
def reset(template)
reset_lines(template.split("\n"))
reset_indent
end
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 21:26:46 +09:00
ast << [:static, "\n"] unless skip_newline?(ast.last)
ast << [: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-11 19:51:04 +09:00
when '='
parse_script(scanner)
2015-03-11 21:26:46 +09:00
when '-'
parse_silent_script(scanner)
2015-03-15 00:47:44 +09:00
when '/'
parse_comment(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
attrs = [:haml, :attrs]
2015-03-13 11:47:51 +09:00
attrs += parse_tag_id_and_class(scanner)
2015-03-13 00:50:00 +09:00
attrs += parse_attributes(scanner)
2015-03-10 23:38:42 +09:00
2015-03-11 01:00:08 +09:00
ast = [:html, :tag, tag, attrs]
2015-03-11 19:51:04 +09:00
if scanner.match?(/=/)
ast << parse_script(scanner)
return ast
elsif scanner.scan(/ +/) && scanner.rest?
2015-03-11 19:38:22 +09:00
ast << parse_text(scanner)
2015-03-10 23:38:42 +09:00
return ast
2015-03-11 19:51:04 +09:00
elsif next_indent <= @current_indent
return ast
2015-03-10 19:16:07 +09:00
end
2015-03-10 23:38:42 +09:00
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
2015-03-11 19:51:04 +09:00
def parse_script(scanner)
raise SyntaxError unless scanner.scan(/=/)
2015-03-15 04:02:13 +09:00
code = scanner.scan(/.+/)
return escape_html([:dynamic, code]) unless has_block?
2015-03-15 04:02:13 +09:00
ast = [:haml, :script, code]
2015-03-15 04:02:13 +09:00
ast += with_indented { parse_lines }
ast << [:code, 'end']
2015-03-11 19:51:04 +09:00
ast
end
2015-03-11 21:26:46 +09:00
def parse_silent_script(scanner)
raise SyntaxError unless scanner.scan(/-/)
ast = [:code]
ast << scanner.scan(/.+/)
2015-03-15 04:02:13 +09:00
return ast unless has_block?
2015-03-11 21:49:28 +09:00
ast = [:multi, ast]
ast += with_indented { parse_lines }
2015-03-16 00:08:08 +09:00
ast << [:code, 'end'] unless same_indent?(next_line) && internal_statement?(next_line)
2015-03-11 21:26:46 +09:00
ast
end
2015-03-15 00:47:44 +09:00
def parse_comment(scanner)
raise SyntaxError unless scanner.scan(/\//)
ast = [:html, :comment]
ast << [:static, " #{scanner.scan(/.+/).strip} "]
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-13 00:50:00 +09:00
def parse_attributes(scanner)
2015-03-16 02:37:10 +09:00
[read_brace(scanner)].compact
2015-03-13 00:50:00 +09:00
end
2015-03-11 01:00:08 +09:00
def parse_tag_id_and_class(scanner)
2015-03-13 00:50:00 +09:00
attributes = Hash.new { |h, k| h[k] = [] }
2015-03-11 01:00:08 +09:00
while prefix = scanner.scan(/[#.]/)
name = scanner.scan(TAG_ID_CLASS_REGEXP)
raise SyntaxError unless name
case prefix
when '#'
2015-03-13 00:50:00 +09:00
attributes['id'] << name
2015-03-11 01:00:08 +09:00
when '.'
2015-03-13 00:50:00 +09:00
attributes['class'] << name
2015-03-11 01:00:08 +09:00
end
end
2015-03-13 11:47:51 +09:00
ast = []
attributes.each do |name, values|
ast << [:html, :attr, name, [:static, values.join(' ')]]
end
ast
2015-03-11 01:00:08 +09:00
end
2015-03-11 22:35:33 +09:00
def internal_statement?(line)
2015-03-11 22:16:50 +09:00
return false unless line
scanner = StringScanner.new(line)
2015-03-15 18:49:59 +09:00
scanner.scan(/ +/)
2015-03-11 22:16:50 +09:00
return false unless scanner.scan(/-/)
scanner.scan(/ +/)
statement = scanner.scan(/[^ ]+/)
2015-03-11 22:20:06 +09:00
INTERNAL_STATEMENTS.include?(statement)
2015-03-11 22:16:50 +09:00
end
2015-03-11 21:26:46 +09:00
def skip_newline?(ast)
SKIP_NEWLINE_EXPS.include?(ast.first)
end
2015-03-15 04:02:13 +09:00
def has_block?
next_indent == @current_indent + 1
end
2015-03-10 00:37:41 +09:00
end
end