module Rbexy
class Parser
class ParseError < StandardError; end
attr_reader :tokens
attr_accessor :position
def initialize(tokens)
@tokens = tokens
@position = 0
end
def parse
validate_tokens!
Nodes::Root.new(parse_tokens)
end
def parse_tokens
results = []
while result = parse_token
results << result
end
results
end
def parse_token
parse_text || parse_newline || parse_expression || parse_tag || parse_declaration
end
def parse_text
return unless token = take(:TEXT)
Nodes::Text.new(token[1])
end
def parse_expression
return unless take(:OPEN_EXPRESSION)
members = []
eventually!(:CLOSE_EXPRESSION)
until take(:CLOSE_EXPRESSION)
members << (parse_expression_body || parse_tag)
end
Nodes::ExpressionGroup.new(members)
end
def parse_expression!
peek!(:OPEN_EXPRESSION)
parse_expression
end
def parse_expression_body
return unless token = take(:EXPRESSION_BODY)
Nodes::Expression.new(token[1])
end
def parse_tag
return unless take(:OPEN_TAG_DEF)
details = take!(:TAG_DETAILS)[1]
attr_class = details[:type] == :component ? Nodes::ComponentProp : Nodes::HTMLAttr
members = []
members.concat(take_all(:NEWLINE).map { Nodes::Newline.new })
members.concat(parse_attrs(attr_class))
take!(:CLOSE_TAG_DEF)
children = parse_children
if details[:type] == :component
Nodes::ComponentElement.new(details[:component_class], members, children)
else
Nodes::HTMLElement.new(details[:name], members, children)
end
end
def parse_attrs(attr_class)
return [] unless take(:OPEN_ATTRS)
attrs = []
eventually!(:CLOSE_ATTRS)
until take(:CLOSE_ATTRS)
attrs << (parse_splat_attr || parse_newline || parse_attr(attr_class))
end
attrs
end
def parse_splat_attr
return unless take(:OPEN_ATTR_SPLAT)
expression = parse_expression!
take!(:CLOSE_ATTR_SPLAT)
expression
end
def parse_newline
return unless take(:NEWLINE)
Nodes::Newline.new
end
def parse_attr(attr_class)
name = take!(:ATTR_NAME)[1]
value = nil
if take(:OPEN_ATTR_VALUE)
value = parse_text || parse_expression
raise ParseError, "Missing attribute value" unless value
take(:CLOSE_ATTR_VALUE)
else
value = default_empty_attr_value
end
attr_class.new(name, value)
end
def parse_children
children = []
eventually!(:OPEN_TAG_END)
until take(:OPEN_TAG_END)
children << parse_token
end
take(:TAG_NAME)
take!(:CLOSE_TAG_END)
children
end
private
def parse_declaration
return unless token = take(:DECLARATION)
Nodes::Declaration.new(token[1])
end
def take(token_name)
if token = peek(token_name)
self.position += 1
token
end
end
def take_all(token_name)
result = []
while token = take(token_name)
result << token
end
result
end
def take!(token_name)
take(token_name) || unexpected_token!(token_name)
end
def peek(token_name)
if (token = tokens[position]) && token[0] == token_name
token
end
end
def peek!(token_name)
peek(token_name) || unexpected_token!(token_name)
end
def eventually!(token_name)
tokens[position..-1].first { |t| t[0] == token_name } ||
raise(ParseError, "Expected to find a #{token_name} but never did")
end
def default_empty_attr_value
Nodes::Text.new("")
end
def error_window
window_start = position - 2
window_start = 0 if window_start < 0
window_end = position + 2
window_end = tokens.length-1 if window_end >= tokens.length
tokens[window_start..window_end].map.with_index do |token, i|
prefix = window_start + i == position ? "=>" : " "
"#{prefix} #{token}"
end.join("\n")
end
def unexpected_token!(expected_token)
raise(ParseError, "Unexpected token #{tokens[position][0]}, expecting #{expected_token}\n#{error_window}")
end
def validate_tokens!
validate_all_tags_close!
end
def validate_all_tags_close!
open_count = tokens.count { |t| t[0] == :OPEN_TAG_DEF }
close_count = tokens.count { |t| t[0] == :OPEN_TAG_END }
if open_count != close_count
raise(ParseError, "#{open_count - close_count} tags fail to close. All tags must close, either or self-closing ")
end
end
end
end