class Bytewise::Parser
Constants
- ALLOW_ESCAPE_FOR
- ESCAPE_CHAR
- FILTER_DIVIDER
- OPENING_BRACKETS
- RAW_BRACKETS
- VARIABLE_REGEX
Attributes
Public Class Methods
# File lib/brace_markup/parser.rb, line 14 def self.parse(input, options = {}) self.new.parse(input, options) end
Public Instance Methods
Removes the last stored lexer (apply transaction)
# File lib/brace_markup/parser.rb, line 52 def apply_transaction @lexers.pop @lexer end
Copys the lexer for later recover
# File lib/brace_markup/parser.rb, line 35 def begin_transaction # Store a copy of the real lexer @lexers.push(@lexer.clone) end
Starts the parsing of the input
@param { Hash } options @return { Bytewyse::Ast::Body }
# File lib/brace_markup/parser.rb, line 24 def parse(input, options = {}) @lexer = Lexer.new input # Holds a 'safe copy' of the lexers when starting a transaction @lexers = [] parse_body end
Parses the arguments of a tag (also the parts of a hash)
# File lib/brace_markup/parser.rb, line 299 def parse_arguments args = {} loop do name = parse_tag_name break if name == '' if @lexer.peek[:chunk] == ':' @lexer.next value = parse_value value = value == '' ? nil : value else # Invalid raise Exception.new('Invalid parameters') end args[name.to_sym] = value if @lexer.peek[:chunk] == ',' @lexer.next else break end end args end
Parses an array
# File lib/brace_markup/parser.rb, line 412 def parse_array raise Exception.new('An array begins with a \'[\'') unless @lexer.next == '[' arr = [] loop do arr.push(parse_value) if @lexer.peek[:chunk] == ',' @lexer.next else break end end unless @lexer.next == ']' # Todo: use some sort of an #error method raise Exception.new('Couldn\'t find a closing brace for the array') end arr end
Parses the body with subnodes
@param { Boolean } allow_close_tag
# File lib/brace_markup/parser.rb, line 62 def parse_body(allow_close_tag = false) body = Bytewise::Ast::Body.new chunk = '' loop do if @lexer.peek(length: 2, skip_whitespace: false)[:chunk] == OPENING_BRACKETS begin body << parse_brackets rescue CloseTagException => e # Catches closing tags and stops body parsing unless allow_close_tag raise Exception.new("Wasn't expecting a closing tag #{@lexer.line_and_col}") end body break rescue TagSectionException => e # Catches and reraises it e.body = body raise e end else if @lexer.peek(skip_whitespace: false)[:chunk].nil? if allow_close_tag raise ::BraceMarkup::UnexpectedEndException.new("Unexpected End. Was waiting for a closing tag.") else break end else body << @lexer.next(skip_whitespace: false).to_s end end end body end
# File lib/brace_markup/parser.rb, line 352 def parse_bracket_value # Find out the type of the block tag_type = @lexer.peek(offset: 2)[:chunk] if tag_type =~ VARIABLE_REGEX # Hash or Reference begin_transaction @lexer.next # Parse the name to see if it is a key parse_tag_name if @lexer.peek[:chunk] == ':' rollback_transaction parse_hash else # Its not a hash, It's a reference rollback_transaction @lexer.next parse_reference context: :in_tag end else # Is a tag tag_type = tag_type.to_sym parse_tag_value type: tag_type end end
Tries to parse matching brackets e.g {{name}} or {{@func param: 4}}
Note: The closing brackets are parsed by parse_tag
and parse_reference
for the reason that a reference can begin with three braces.
# File lib/brace_markup/parser.rb, line 126 def parse_brackets raw = false if @lexer.peek(length: 3)[:chunk] == RAW_BRACKETS # This is a raw variable raw = true @lexer.next end if @lexer.peek(length: 2)[:chunk] == OPENING_BRACKETS @lexer.next length: 2 # Find out the type of the block tag_type = @lexer.peek[:chunk] if tag_type =~ VARIABLE_REGEX parse_reference context: (raw ? :raw : :default) elsif tag_type == '(' # Expression expr = parse_expression @lexer.expect '}}', raise_error: true expr else # Is a tag tag_type = tag_type.to_sym @lexer.next parse_tag type: tag_type end else # Todo: Error end end
# File lib/brace_markup/parser.rb, line 99 def parse_comment_body buf = '' loop do if @lexer.expect '{{/!}}', skip_whitespace: false break end curr = @lexer.next skip_whitespace: false if curr.nil? raise ::BraceMarkup::UnexpectedEndException.new("Unexpected End. Was waiting for a closing tag.") end buf += curr end buf end
Parses a constant (nil and numbers)
# File lib/brace_markup/parser.rb, line 470 def parse_const # Test for nil if @lexer.expect 'nil' # Returning own "nil" class, in order to be able to test if # the return value of this method is nil. ::Bytewise::Ast::Chars::Nil.new elsif @lexer.expect 'true' true elsif @lexer.expect 'false' false else parse_numerical end end
Parses the closing tag
# File lib/brace_markup/parser.rb, line 257 def parse_end_tag if @lexer.peek(length: 2)[:chunk] == '}}' @lexer.next length: 2 # Gets caught by Parser#parse_body raise CloseTagException.new else raise UnexpectedCharactersException.new '}}', @lexer end end
Parses an expression Expressions allow you to do actions like adding, subtracting, logical AND, OR, etc.
# File lib/brace_markup/parser.rb, line 520 def parse_expression @lexer.expect '(', raise_error: true parts = [] loop do value = parse_value if value.nil? raise Exception.new('Invalid expression (no value found)') end parts << value operator = parse_operator break if operator.nil? parts << operator end @lexer.expect ')', raise_error: true ::Bytewise::Ast::Expression.new(parts) end
Parses a hash
# File lib/brace_markup/parser.rb, line 398 def parse_hash hash = {} @lexer.expect '{', raise_error: true do hash = parse_arguments @lexer.expect '}', raise_error: true end hash end
# File lib/brace_markup/parser.rb, line 485 def parse_numerical i = 0 buf = '' decimal = false @lexer.next_nowhitespace loop do curr = @lexer.peek skip_whitespace: false if curr[:chunk] == '-' && i != 0 raise UnexpectedCharactersException.new '-', @lexer end if curr[:chunk] == '.' if decimal raise UnexpectedCharactersException.new '.', @lexer else decimal = true end end break unless curr[:chunk] =~ /^[\-0-9\.]$/ buf += @lexer.next(skip_whitespace: false) i += 1 end buf.empty? ? nil : (decimal ? buf.to_f : buf.to_i) end
# File lib/brace_markup/parser.rb, line 545 def parse_operator # @phpdevs @lexer.expect '!==' do return ::Bytewise::Ast::Operators::Equals.new end # @phpdevs @lexer.expect '===' do return ::Bytewise::Ast::Operators::Equals.new end # Equals @lexer.expect '==' do return ::Bytewise::Ast::Operators::Equals.new end @lexer.expect '!=' do return ::Bytewise::Ast::Operators::Equals.new negated: true end # AND @lexer.expect '&&' do return ::Bytewise::Ast::Operators::LogicalAnd.new end # OR @lexer.expect '||' do return ::Bytewise::Ast::Operators::LogicalOr.new end # Addition, Concatenation, etc. @lexer.expect '+' do return ::Bytewise::Ast::Operators::Add.new end # Addition, Concatenation, etc. @lexer.expect '>' do return ::Bytewise::Ast::Operators::GreaterThan.new end # Addition, Concatenation, etc. @lexer.expect '<' do return ::Bytewise::Ast::Operators::SmallerThan.new end @lexer.expect '-' do return ::Bytewise::Ast::Operators::Minus.new end @lexer.expect '*' do return ::Bytewise::Ast::Operators::Star.new end @lexer.expect '/' do return ::Bytewise::Ast::Operators::Slash.new end @lexer.expect '%' do return ::Bytewise::Ast::Operators::Modulo.new end nil end
Parses a reference {{ user.name|to_lower }} or {{{ user.name }}} The context symbolizes the amount of braces required.
:default => }} :raw => }}} :in_tag => }
# File lib/brace_markup/parser.rb, line 169 def parse_reference(context: :default) name = parse_tag_name filter = nil if @lexer.expect '|' # Reference has a filter filter = parse_tag_name end case context when :raw @lexer.expect '}}}', raise_error: true when :in_tag @lexer.expect '}', raise_error: true else @lexer.expect '}}', raise_error: true end # Todo: Validate filter & name ::Bytewise::Ast::Reference.new name, filter, context end
Parses a string with ' or “
Todo: Fix escaped characters
# File lib/brace_markup/parser.rb, line 441 def parse_string quotes = @lexer.next buffer = '' loop do curr = @lexer.peek skip_whitespace: false # Wait for ending quotes if @lexer.expect quotes, skip_whitespace: false, ignore_escaped: true break end # Raise error if document ends without an ending quote raise ::BraceMarkup::UnexpectedEndException.new("Unexpected End. Was waiting for matching quotes.") if curr[:chunk].nil? # Ignore escaping characters unless @lexer.escapes? offset: curr[:offset], escape: [quotes] buffer += curr[:chunk] end @lexer.next skip_whitespace: false end buffer end
Parses a tag with, or without body
# File lib/brace_markup/parser.rb, line 195 def parse_tag(type:, context: :default) name = parse_tag_name(true) body = nil if type == :/ return parse_end_tag end arguments = parse_arguments if @lexer.expect '}}' # Section if type == :':' raise TagSectionException.new name: name, arguments: arguments end # Parse the body of the tag sections = [] curr_section = {} next_section = {} curr_body = nil loop do begin if type == :! curr_body = parse_comment_body else curr_body = parse_body(true) end break rescue TagSectionException => e next_section[:name] = e.tag_name.to_sym next_section[:args]= e.arguments curr_body = e.body ensure if body.nil? body = curr_body else sections << Bytewise::Ast::Tags::Section.new(name: curr_section[:name], arguments: curr_section[:args], body: curr_body) end curr_section = next_section.clone next_section = {} end end # Create tag Bytewise::Ast::Tag.create(type, name: name, arguments: arguments, body: body, sections: sections) elsif @lexer.peek(length: 3)[:chunk] == '/}}' @lexer.next length: 3 # Tag with no content Bytewise::Ast::Tag.create(type, name: name, arguments: arguments) else raise UnexpectedCharactersException.new '}}', @lexer end end
Parses a tag name (also used for hash keys)
# File lib/brace_markup/parser.rb, line 271 def parse_tag_name(allow_strings = false) @lexer.next_nowhitespace if allow_strings && (@lexer.expect('"', continue: false) || @lexer.expect("'", continue: false)) parse_string else name = '' @lexer.peek_loop skip_whitespace: false do |char, offset| if char =~ VARIABLE_REGEX name += char else # Allow ? at the end if char == '?' name += char @lexer.next skip_whitespace: false end @lexer.next(length: offset - 1) return name end end end end
Parses a tag as value
# File lib/brace_markup/parser.rb, line 384 def parse_tag_value(type:) @lexer.expect '{', raise_error: true name = parse_tag_name arguments = parse_arguments @lexer.expect '}', raise_error: true Bytewise::Ast::Tag.create(type, name: name, arguments: arguments, body: nil) end
Parses a value in arguments or a hash
# File lib/brace_markup/parser.rb, line 332 def parse_value peek = @lexer.peek[:chunk] case peek when '"', "'" # String parse_string when '[' # Array parse_array when '{' parse_bracket_value when '(' parse_expression else # Nil, Numbers parse_const end end
Reverts the last transaction
# File lib/brace_markup/parser.rb, line 43 def rollback_transaction unless @lexers.empty? @lexer = @lexers.pop end end