# File lib/opulent/parser/expression.rb, line 325 def primary_term accept_stripped(:exp_string) || accept_stripped(:exp_fixnum) || accept_stripped(:exp_double) || accept_stripped(:exp_nil) || accept_stripped(:exp_regex) || accept_stripped(:exp_boolean) end
class Opulent::Parser
@Parser
@Parser
@Parser
@Parser
@Parser
@Parser
@Parser
@Parser
@Parser
@Parser
@Parser
@Parser
@Parser
Attributes
Public Class Methods
All node Objects (Array) must follow the next convention in order to make parsing faster
- :node_type, :value, :attributes, :children, :indent
# File lib/opulent/parser.rb, line 25 def initialize(settings = {}) # Convention accessors @type = 0 @value = 1 @options = 2 @children = 3 @indent = 4 # Inherit settings from Engine @settings = settings # Set current compiled file as the first in the file stack together with # its base indentation. The stack is used to allow include directives to # be used with the last parent path found @file = [[@settings.delete(:file), -1]] # Create a definition stack to disallow recursive calls. When inside a # definition and a named node is called, we render it as a plain node @definition_stack = [] # Initialize definitions for the parser @definitions = @settings.delete(:def) || {} end
Public Instance Methods
Check and accept or reject a given token as long as we have tokens remaining. Shift the code with the match length plus any extra character count around the capture group
@param token [RegEx] Token to be accepted by the parser @param required [Boolean] Expect the given token syntax @param strip [Boolean] Left strip the current code to remove whitespace
# File lib/opulent/parser.rb, line 131 def accept(token, required = false, strip = false) # Consume leading whitespace if we want to ignore it accept :whitespace if strip # We reached the end of the parsing process and there are no more lines # left to parse return nil unless @line # Match the token to the current line. If we find it, return the match. # If it is required, signal an :expected error if (match = @line[@offset..-1].match(Tokens[token])) # Advance current offset with match length @offset += match[0].size @j += match[0].size return match[0] elsif required Logger.error :parse, @code, @i, @j, :expected, token end end
Allow expressions to continue on a new line in certain conditions
# File lib/opulent/parser.rb, line 198 def accept_newline return unless @line[@offset..-1].strip.empty? @line = @code[(@i += 1)] @j = 0 @offset = 0 end
Helper method which automatically sets the stripped options to true, so that we do not have to explicitly specify it
@param token [RegEx] Token to be accepted by the parser @param required [Boolean] Expect the given token syntax
# File lib/opulent/parser.rb, line 158 def accept_stripped(token, required = false) accept(token, required, true) end
Helper method to create an array of values when an attribute is set multiple times. This happens unless the key is id, which is unique
@param atts [Hash] Current node attributes hash @param key [Symbol] Attribute name @param value [String] Attribute value
# File lib/opulent/parser/node.rb, line 144 def add_attribute(atts, key, value) # Check for unique key and arrays of attributes if key == :class # If the key is already associated to an array, add the value to the # array, otherwise, create a new array or set it if atts[key] atts[key] << value else atts[key] = [value] end else atts[key] = value end end
Set each node as a defined node if a definition exists with the given node name, unless we're inside a definition with the same name as the node because we want to avoid recursion.
@param node [Node] Input Node to checked for existence of definitions
# File lib/opulent/parser.rb, line 93 def apply_definitions(node) # Apply definition check to all of the node's children process_definitions = proc do |children| children.each do |child| # Check if we have a definition is_definition = if child[@value] == @current_def child[@options][:recursive] else @definitions.key?(child[@value]) end # Set child as a defined node child[@type] = :def if is_definition # Recursively apply definitions to child nodes apply_definitions child if child[@children] end end # Apply definitions to each case of the control node if [:if, :unless, :case].include? node[@type] node[@children].each do |array| process_definitions[array] end # Apply definition to all of the node's children else process_definitions[node[@children]] end end
Check if it's possible to parse a ruby array literal. First, try to see if the next sequence is a hash_open token: “[”, and if it is, then a hash_close: “]” token is required next
array_elements
- access][access
# File lib/opulent/parser/expression.rb, line 60 def array buffer = '' while (bracket = accept :square_bracket) buffer += bracket accept_newline buffer += array_elements accept_newline buffer += accept :'[', :* end buffer == '' ? nil : buffer end
Recursively gather expressions separated by a comma and add them to the expression buffer
experssion1, experssion2, experssion3
@param buffer [String] Accumulator for the array elements
# File lib/opulent/parser/expression.rb, line 81 def array_elements(buffer = '') if (term = expression) buffer += term[@value] # If there is an array_terminator ",", recursively gather the next # array element into the buffer if (terminator = accept_stripped :comma) accept_newline buffer += array_elements terminator end end # Array ended prematurely with a trailing comma, therefore the current # parsing process will stop error :array_elements_terminator if buffer.strip[-1] == ',' buffer end
Get element attributes
@param atts [Hash] Accumulator for node attributes @param for_definition [Boolean] Set default value in wrapped to nil
# File lib/opulent/parser/node.rb, line 195 def attributes(atts = {}, for_definition = false) wrapped_attributes atts, for_definition attributes_assignments atts, false, for_definition atts end
Get all attribute assignments as key=value pairs or standalone keys
- assignments
-
@param list [Hash] Parent to which we append nodes @param as_parameters [Boolean] Accept or reject identifier nodes
# File lib/opulent/parser/node.rb, line 226 def attributes_assignments(list, wrapped = true, for_definition = false) if wrapped && lookahead(:exp_identifier_stripped_lookahead).nil? || !wrapped && lookahead(:assignment_lookahead).nil? return list end return unless (argument = accept_stripped :node) argument = argument.to_sym if accept_stripped :assignment # Check if we have an attribute escape or not escaped = if accept :assignment_unescaped false else true end # Get the argument value if we have an assignment if (value = expression(false, wrapped)) value[@options][:escaped] = escaped # IDs are unique, the rest of the attributes turn into arrays in # order to allow multiple values or identifiers add_attribute(list, argument, value) else Logger.error :parse, @code, @i, @j, :assignments_colon end else unless list[argument] default_value = for_definition ? 'nil' : 'true' list[argument] = [:expression, default_value, { escaped: false }] end end # If our attributes are wrapped, we allow method calls without # paranthesis, ruby style, therefore we need a terminator to signify # the expression end. If they are not wrapped (inline), we require # paranthesis and allow inline calls if wrapped # Accept optional comma between attributes accept_stripped :assignment_terminator # Lookahead for attributes on the current line and the next one if lookahead(:exp_identifier_stripped_lookahead) attributes_assignments list, wrapped, for_definition elsif lookahead_next_line(:exp_identifier_stripped_lookahead) accept_newline attributes_assignments list, wrapped, for_definition end elsif !wrapped && lookahead(:assignment_lookahead) attributes_assignments list, wrapped, for_definition end list end
Match a yield with a explicit or implicit target
yield target
@param parent [Node] Parent node to which we append the definition
# File lib/opulent/parser/yield.rb, line 11 def block_yield(parent, indent) return unless accept :yield # Consume the newline from the end of the element error :yield unless accept(:line_feed).strip.empty? # Create a new node yield_node = [:yield, nil, {}, [], indent] parent[@children] << yield_node end
Check if it's possible to parse a ruby call literal. First, try to see if the next sequence is a hash_open token: “(”, and if it is, then a hash_close: “)” token is required next
( call_elements
)
# File lib/opulent/parser/expression.rb, line 203 def call return unless (buffer = accept :round_bracket) buffer += call_elements buffer += accept_stripped :'(', :* buffer end
Recursively gather expression attributes separated by a comma and add them to the expression buffer
expression1, a: expression2, expression3
@param buffer [String] Accumulator for the call elements
# File lib/opulent/parser/expression.rb, line 218 def call_elements(buffer = '') # Accept both shorthand and default ruby hash style. Following DRY # principles, a Proc is used to assign the value to the current key # # key: value # :key => value # value if (symbol = accept_stripped :hash_symbol) buffer += symbol # Get the value associated to the current hash key if (exp = expression(true)) buffer += exp[@value] else error :call_elements end # If there is an comma ",", recursively gather the next # array element into the buffer if (terminator = accept_stripped :comma) buffer += call_elements terminator end elsif (exp = expression(true)) buffer += exp[@value] if (assign = accept_stripped :hash_assignment) buffer += assign # Get the value associated to the current hash key if (exp = expression(true)) buffer += exp[@value] else error :call_elements end end # If there is an comma ",", recursively gather the next # array element into the buffer if (terminator = accept_stripped :comma) buffer += call_elements terminator end end buffer end
Match one line or multiline comments
@param parent [Node] Parent node of comment @param indent [Fixnum] Number of indentation characters
# File lib/opulent/parser/comment.rb, line 10 def comment(parent, indent) return unless accept :comment # Get first comment line buffer = accept(:line_feed) buffer += accept(:newline) || '' # Get indented comment lines buffer += get_indented_lines indent # If we have a comment which is visible in the output, we will # create a new comment element. Otherwise, we ignore the current # gathered text and we simply begin the root parsing again if buffer[0] == '!' offset = 1 options = {} # Allow leading comment newline if buffer[1] == '^' offset = 2 options[:newline] = true end parent[@children] << [ :comment, buffer[offset..-1].strip, options, nil, indent ] end parent end
Match an if-else control structure
# File lib/opulent/parser/control.rb, line 7 def control(parent, indent) # Accept eval or multiline eval syntax and return a new node, return unless (structure = accept(:control)) structure = structure.to_sym # Handle each and the other control structures condition = accept(:line_feed).strip # Process each control structure condition if structure == :each # Check if arguments provided correctly unless condition.match Tokens[:each_pattern] Logger.error :parse, @code, @i, @j, :each_arguments end # Conditions for each iterator condition = [ Regexp.last_match[1].split(' '), Regexp.last_match[2].split(/,(.+)$/).map(&:strip).map(&:to_sym) ] # Array loop as default condition[0].unshift '{}' if condition[0].length == 1 end # Else and default structures are not allowed to have any condition # set and the other control structures require a condition if structure == :else unless condition.empty? Logger.error :parse, @code, @i, @j, :condition_exists end else if condition.empty? Logger.error :parse, @code, @i, @j, :condition_missing end end # Add the condition and create a new child to the control parent. # The control parent keeps condition -> children matches for our # document's content # add_options = proc do |control_parent| # control_parent.value << condition # control_parent.children << [] # # root control_parent # end # If the current control structure is a parent which allows multiple # branches, such as an if or case, we create an array of conditions # which can be matched and an array of children belonging to each # conditional branch if [:if, :unless].include? structure # Create the control structure and get its child nodes control_structure = [structure, [condition], {}, [], indent] root control_structure, indent # Turn children into an array because we allow multiple branches control_structure[@children] = [control_structure[@children]] # Add it to the parent node parent[@children] << control_structure elsif structure == :case # Create the control structure and get its child nodes control_structure = [ structure, [], { condition: condition }, [], indent ] # Add it to the parent node parent[@children] << control_structure # If the control structure is a child structure, we need to find the # node it belongs to amont the current parent. Search from end to # beginning until we find the node parent elsif control_child structure # During the search, we try to find the matching parent type unless control_parent(structure).include? parent[@children][-1][@type] Logger.error :parse, @code, @i, @j, :control_child, control_parent(structure) end # Gather child elements for current structure control_structure = [structure, [condition], {}, [], indent] root control_structure, indent # Add the new condition and children to our parent structure parent[@children][-1][@value] << condition parent[@children][-1][@children] << control_structure[@children] # When our control structure isn't a complex composite, we create # it the same way as a normal node else control_structure = [structure, condition, {}, [], indent] root control_structure, indent parent[@children] << control_structure end end
Check if the current control structure requires a parent node and return the parent's node type
# File lib/opulent/parser/control.rb, line 117 def control_child(structure) [:else, :elsif, :when].include? structure end
Check if the current control structure requires a parent node and return the parent's node type
# File lib/opulent/parser/control.rb, line 124 def control_parent(structure) case structure when :else then [:if, :unless, :case] when :elsif then [:if] when :when then [:case] end end
Check if we match a new node definition to use within our page.
Definitions will not be recursive because, by the time we parse the definition children, the definition itself is not in the knowledgebase yet.
However, we may use previously defined nodes inside new definitions, due to the fact that they are known at parse time.
@param nodes [Array] Parent node to which we append to
# File lib/opulent/parser/define.rb, line 16 def define(parent, indent) return unless accept(:def) # Definition parent check Logger.error :parse, @code, @i, @j, :definition if parent[@type] != :root # Process data name = accept(:node, :*).to_sym # Create node definition = [ :def, name, { parameters: attributes({}, true) }, [], indent ] # Set definition as root node and let the parser know that we're inside # a definition. This is used because inside definitions we do not # process nodes (we do not check if they are have a definition or not). root definition, indent # Add to parent @definitions[name] = definition end
Match one line or multiline comments
# File lib/opulent/parser/doctype.rb, line 7 def doctype(parent, indent) return unless accept :doctype buffer = accept(:line_feed) parent[@children] << [:doctype, buffer.strip.to_sym, {}, nil, indent] end
Match one line or multiline, escaped or unescaped text
# File lib/opulent/parser/eval.rb, line 7 def evaluate(parent, indent) # Accept eval or multiline eval syntax and return a new node, return unless accept :eval multiline = accept(:text) if multiline # Get first evaluation line evaluate_code = accept(:line_feed) || '' # Get all the lines which are more indented than the current one eval_node = [:evaluate, evaluate_code.strip, {}, nil, indent] eval_node[@value] += accept(:newline) || '' eval_node[@value] += get_indented_lines(indent) else evaluate_code = accept(:line_feed) || '' eval_node = [:evaluate, evaluate_code.strip, {}, [], indent] root eval_node, indent end parent[@children] << eval_node end
Check if the parser matches an expression node
# File lib/opulent/parser/expression.rb, line 7 def expression(allow_assignments = true, wrapped = true, whitespace = true) buffer = '' # Build a ruby expression out of accepted literals while (term = (whitespace ? accept(:whitespace) : nil) || modifier || identifier || method_call || paranthesis || array || hash || symbol || percent || primary_term) buffer += term # Accept operations which have a right term and raise an error if # we have an unfinished expression such as "a +", "b - 1 >" and other # expressions following the same pattern if wrapped && (op = operation || (allow_assignments ? accept_stripped(:exp_assignment) : nil)) buffer += op if (right_term = expression(allow_assignments, wrapped)).nil? Logger.error :parse, @code, @i, @j, :expression else buffer += right_term[@value] end elsif (op = array || op = method_call || op = ternary_operator(allow_assignments, wrapped)) buffer += op end # Do not continue if the expression has whitespace method calls in # an unwrapped context because this will confuse the parser unless buffer.strip.empty? break if lookahead(:exp_identifier_lookahead).nil? end end if buffer.strip.empty? undo buffer else [:expression, buffer.strip, {}] end end
Extend node attributes with hash from
+value +{hash: “value”} +(paranthesis)
# File lib/opulent/parser/node.rb, line 289 def extend_attributes return unless accept :extend_attributes unescaped = accept :unescaped_value extension = expression(false, false, false) extension[@options][:escaped] = !unescaped extension end
Check if we match an compile time filter
:filter
@param parent [Node] Parent node to which we append the element
# File lib/opulent/parser/filter.rb, line 11 def filter(parent, indent) return unless (filter_name = accept :filter) # Get element attributes atts = attributes(shorthand_attributes) || {} # Accept inline text or multiline text feed as first child error :fiter unless accept(:line_feed).strip.empty? # Get everything under the filter and set it as the node value # and create a new node and set its extension parent[@children] << [ :filter, filter_name[1..-1].to_sym, atts, get_indented_lines(indent), indent ] end
Gather all the lines which have higher indentation than the one given as parameter and put them into the buffer
@param indentation [Fixnum] parent node strating indentation
# File lib/opulent/parser/text.rb, line 99 def get_indented_lines(indent) buffer = '' # Gather multiple blank lines between lines of text blank_lines = proc do while lookahead_next_line :line_whitespace @line = @code[(@i += 1)] @offset = 0 buffer += accept :line_whitespace end end # Get blank lines until we match something blank_lines[] # Get the next indentation after the parent line # and set it as primary indent first_indent = (lookahead_next_line(:indent).to_s || '').size next_indent = first_indent # While the indentation is smaller, add the line feed to our buffer while next_indent > indent # Advance current line and reset offset @line = @code[(@i += 1)] @offset = 0 # Get leading whitespace trimmed with first_indent's size next_line_indent = accept(:indent)[first_indent..-1] || '' next_line_indent = next_line_indent.size # Add next line feed, prepend the indent and append the newline buffer += ' ' * next_line_indent if next_line_indent > 0 buffer += accept_stripped(:line_feed) || '' buffer += accept(:newline) || '' # Get blank lines until we match something blank_lines[] # Check the indentation on the following line. When we reach EOF, # set the indentation to 0 and cause the loop to stop if (next_indent = lookahead_next_line :indent) next_indent = next_indent[0].size else next_indent = 0 end end buffer end
Check if it's possible to parse a ruby hash literal. First, try to see if the next sequence is a hash_open token: “{”, and if it is, then a hash_close: “}” token is required next
{ hash_elements
}
# File lib/opulent/parser/expression.rb, line 105 def hash return unless (buffer = accept :curly_bracket) accept_newline buffer += hash_elements accept_newline buffer += accept :'{', :* buffer end
Recursively gather expression attributions separated by a comma and add them to the expression buffer
key1: experssion1, key2 => experssion2, :key3 => experssion3
@param buffer [String] Accumulator for the hash elements
# File lib/opulent/parser/expression.rb, line 121 def hash_elements(buffer = '') value = proc do # Get the value associated to the current hash key if (exp = expression) buffer += exp[@value] else error :hash_elements end # If there is an hash_terminator ",", recursively gather the next # array element into the buffer if (terminator = accept_stripped :comma) accept_newline buffer += hash_elements terminator end end # Accept both shorthand and default ruby hash style. Following DRY # principles, a Proc is used to assign the value to the current key # # key: # :key => if (symbol = accept_stripped :hash_symbol) buffer += symbol value[] elsif (exp = expression false) buffer += exp[@value] if (assign = accept_stripped :hash_assignment) buffer += assign value[] else error :hash_assignment end end # Array ended prematurely with a trailing comma, therefore the current # parsing process will stop error :hash_elements_terminator if buffer.strip[-1] == ',' buffer end
Match one line or multiline, escaped or unescaped text
# File lib/opulent/parser/text.rb, line 71 def html_text(parent, indent) return unless (text_feed = accept :html_text) text_node = [ :plain, :text, { value: text_feed.strip, escaped: false }, nil, indent ] parent[@children] << text_node end
Accept a ruby identifier such as a class, module, method or variable
# File lib/opulent/parser/expression.rb, line 165 def identifier return unless (buffer = accept :exp_identifier) if (args = call) buffer += args end buffer end
Check if we have an include node, which will include a new file inside of the current one to be parsed
@param parent [Array] Parent node to which we append to
# File lib/opulent/parser/include.rb, line 10 def include_file(_parent, indent) return unless accept :include # Process data name = accept :line_feed || '' name.strip! # Check if there is any string after the include input Logger.error :parse, @code, @i, @j, :include_end if name.empty? # Get the complete file path based on the current file being compiled include_path = File.expand_path name, File.dirname(@file[-1][0]) # Try to see if it has any existing extension, otherwise add .op include_path += Settings::FILE_EXTENSION if File.extname(name).empty? # Throw an error if the file doesn't exist unless Dir[include_path].any? Logger.error :parse, @code, @i, @j, :include, name end # include entire directory tree Dir[include_path].each do |file| # Skip current file when including from same directory next if file == @file[-1][0] @file << [include_path, indent] # Throw an error if the file doesn't exist if File.directory? file Logger.error :parse, @code, @i, @j, :include_dir, file end # Throw an error if the file doesn't exist unless File.file? file Logger.error :parse, @code, @i, @j, :include, file end # Indent all lines and prepare them for the parser lines = indent_lines File.read(file), ' ' * indent lines << ' ' # Indent all the output lines with the current indentation @code.insert @i + 1, *lines.lines end true end
Indent all lines of the input text using give indentation
@param text [String] Input text to be indented @param indent [String] Indentation string to be appended
# File lib/opulent/parser.rb, line 210 def indent_lines(text, indent) text ||= '' text.lines.map { |line| indent + line }.join end
Check if the lookahead matches the chosen regular expression
@param token [RegEx] Token to be checked by the parser
# File lib/opulent/parser.rb, line 166 def lookahead(token) return nil unless @line # Check if we match the token to the current line. @line[@offset..-1].match Tokens[token] end
Check if the lookahead matches the chosen regular expression on the following line which needs to be parsed
@param token [RegEx] Token to be checked by the parser
# File lib/opulent/parser.rb, line 178 def lookahead_next_line(token) return nil unless @code[@i + 1] # Check if we match the token to the current line. @code[@i + 1].match Tokens[token] end
Accept a ruby method call modifier
# File lib/opulent/parser/expression.rb, line 185 def method_call method_code = '' while (method_start = accept(:exp_method_call)) method_code += method_start argument = call method_code += argument if argument end method_code == '' ? nil : method_code end
Accept a ruby module, method or context modifier
- Module
-
@, @@, $
# File lib/opulent/parser/expression.rb, line 285 def modifier accept(:exp_context) || accept(:exp_module) end
Check if we match an node node with its attributes and possibly inline text
node [ attributes ] Inline text
@param parent [Node] Parent node to which we append the node
# File lib/opulent/parser/node.rb, line 12 def node(parent, indent = nil) return unless (name = lookahead(:node_lookahead) || lookahead(:shorthand_lookahead)) # Skip node if it's a reserved keyword return nil if KEYWORDS.include? name[0].to_sym # Accept either explicit node_name or implicit :div node_name # with shorthand attributes if (node_name = accept :node) node_name = node_name.to_sym shorthand = shorthand_attributes elsif (shorthand = shorthand_attributes) node_name = :div end # Node creation options options = {} # Get leading whitespace options[:recursive] = accept(:recursive) # Get leading whitespace options[:leading_whitespace] = accept_stripped(:leading_whitespace) # Get trailing whitespace options[:trailing_whitespace] = accept_stripped(:trailing_whitespace) # Get wrapped node attributes atts = attributes(shorthand) || {} # Inherit attributes from definition options[:extension] = extend_attributes # Get unwrapped node attributes options[:attributes] = attributes_assignments atts, false # Create node current_node = [:node, node_name, options, [], indent] # Check for self enclosing tags and definitions def_check = !@definitions.keys.include?(node_name) && Settings::SELF_ENCLOSING.include?(node_name) # Check if the node is explicitly self enclosing if (close = accept_stripped :self_enclosing) || def_check current_node[@options][:self_enclosing] = true unless close.nil? || close.strip.empty? undo close Logger.error :parse, @code, @i, @j, :self_enclosing end end # Check whether we have explicit inline elements and add them # with increased base indentation if accept :inline_child # Inline node element Logger.error :parse, @code, @i, @j, :inline_child unless node current_node, indent elsif comment current_node, indent # Accept same line comments else # Accept inline text element text current_node, indent, false end # Add the current node to the root root current_node, indent # Add the parsed node to the parent parent[@children] << current_node end
Accept an operation between two or more expressions
# File lib/opulent/parser/expression.rb, line 336 def operation accept(:exp_operation) end
Check if it's possible to parse a ruby paranthesis expression wrapper.
# File lib/opulent/parser/expression.rb, line 175 def paranthesis return unless (buffer = accept :round_bracket) buffer += expression[@value] buffer += accept_stripped :'(', :* buffer end
Initialize the parsing process by splitting the code into lines and instantiationg parser variables with their default values
@param code [String] Opulent
code that needs to be analyzed
@return Nodes array
# File lib/opulent/parser.rb, line 56 def parse(code) # Split the code into lines and parse them one by one @code = code.lines.to_a # Current line index @i = -1 # Current character index @j = 0 # Initialize root node @root = [:root, nil, {}, [], -1] # Get all nodes starting from the root element and return output # nodes and definitions root @root # Check whether nodes inside definitions have a custom definition @definitions.each do |name, node| @current_def = name apply_definitions node end @current_def = nil # Check whether nodes have a custom definition apply_definitions @root # Return root element [@root, @definitions] end
Accept a ruby percentage operator for arrays of strings, symbols and simple escaped strings
%w(word1 word2 word3)
# File lib/opulent/parser/expression.rb, line 294 def percent return unless (buffer = accept_stripped :exp_percent) match_start = buffer[-1] match_name = :"percent#{match_start}" unless Tokens[match_name] match_end = Tokens.bracket(match_start) || match_start match_inner = "\\#{match_start}" match_inner += "\\#{match_end}" unless match_end == match_start pattern = /(((?:[^#{match_inner}\\]|\\.)*?)#{'\\' + match_end})/ Tokens[match_name] = pattern end buffer += accept match_name buffer end
Accept any primary term and return it without the leading whitespace to the expression buffer
“string” 123 123.456 nil true false /.*/
Analyze the input code and check for matching tokens. In case no match was found, throw an exception. In special cases, modify the token hash.
@param parent [Array] Parent node to which we append to
# File lib/opulent/parser/root.rb, line 11 def root(parent = @root, min_indent = -1) while (@line = @code[(@i += 1)]) # Reset character position cursor @j = 0 # Skip to next iteration if we have a blank line next if @line =~ /\A\s*\Z/ # Reset the line offset @offset = 0 # Parse the current line by trying to match each node type towards it # Add current indentation to the indent stack indent = accept(:indent).size # Stop using the current parent as root if it does not match the # minimum indentation includements unless min_indent < indent @i -= 1 break end # If last include path had a greater indentation, pop the last file path @file.pop if @file[-1][1] >= indent # Try the main Opulent node types and process each one of them using # their matching evaluation procedure current_node = node(parent, indent) || text(parent, indent) || comment(parent, indent) || define(parent, indent) || control(parent, indent) || evaluate(parent, indent) || filter(parent, indent) || block_yield(parent, indent) || include_file(parent, indent) || html_text(parent, indent) || doctype(parent, indent) # Throw an error if we couldn't find any valid node unless current_node Logger.error :parse, @code, @i, @j, :unknown_node_type end end parent end
Accept node shorthand attributes. Each shorthand attribute is directly mapped to an attribute key
@param atts [Hash] Node attributes
# File lib/opulent/parser/node.rb, line 164 def shorthand_attributes(atts = {}) while (key = accept :shorthand) key = Settings::SHORTHAND[key.to_sym] # Check whether the value is escaped or unescaped escaped = accept(:unescaped_value) ? false : true # Get the attribute value and process it if (value = accept(:shorthand_node)) value = [:expression, value.inspect, { escaped: escaped }] elsif (value = accept(:exp_string)) value = [:expression, value, { escaped: escaped }] elsif (value = paranthesis) value = [:expression, value, { escaped: escaped }] else Logger.error :parse, @code, @i, @j, :shorthand end # IDs are unique, the rest of the attributes turn into arrays in # order to allow multiple values or identifiers add_attribute(atts, key, value) end atts end
Accept a ruby symbol defined through a colon and a trailing expression
:'symbol' :symbol
# File lib/opulent/parser/expression.rb, line 269 def symbol return unless (colon = accept :colon) return undo colon if lookahead(:whitespace) if (exp = expression).nil? error :symbol else colon + exp[@value] end end
Accept ternary operator syntax
condition ? expression1 : expression2
# File lib/opulent/parser/expression.rb, line 344 def ternary_operator(allow_assignments, wrapped) if (buffer = accept :exp_ternary) buffer += expression(allow_assignments, wrapped)[@value] if (else_branch = accept :exp_ternary_else) buffer += else_branch buffer += expression(allow_assignments, wrapped)[@value] end return buffer end end
Match one line or multiline, escaped or unescaped text
@param parent [Array] Parent node element @param indent [Fixnum] Size of the current indentation @param multiline_or_print [Boolean] Allow only multiline text or print
# File lib/opulent/parser/text.rb, line 11 def text(parent, indent = nil, multiline_or_print = true) # Try to see if we can match a multiline operator. If we can accept_stripped only # multiline, which is the case for filters, undo the operation. if accept :multiline multiline = true elsif multiline_or_print return nil unless lookahead :print_lookahead end # Get text node type type = accept(:print) ? :print : :text # Get leading whitespace leading_whitespace = accept(:leading_whitespace) # Get trailing whitespace trailing_whitespace = accept(:trailing_whitespace) # Check if the text or print node is escaped or unescaped escaped = accept(:unescaped_value) ? false : true # Get text value value = accept :line_feed value = value[1..-1] if value[0] == '\\' # Create the text node using input data text_node = [ :plain, type, { value: value.strip, escaped: escaped, leading_whitespace: leading_whitespace, trailing_whitespace: trailing_whitespace }, nil, indent ] # If we have a multiline node, get all the text which has higher # indentation than our indentation node. if multiline text_node[@options][:value] += accept(:newline) || '' text_node[@options][:value] += get_indented_lines(indent) text_node[@options][:value].strip! elsif value.empty? # If our value is empty and we're not going to add any more lines to # our buffer, skip the node return nil end # Increase indentation if this is an inline text node text_node[@indent] += @settings[:indent] unless multiline_or_print # Add text node to the parent element parent[@children] << text_node end
Undo a found match by removing the token from the consumed code and adding it back to the code chunk
@param match [String] Matched string to be undone
# File lib/opulent/parser.rb, line 190 def undo(match) return if match.empty? @offset -= match.size nil end
Match a whitespace by preventing code trimming
# File lib/opulent/parser/text.rb, line 90 def whitespace(required = false) accept :whitespace, required end
Check if we match node attributes
- assignments
-
@param as_parameters [Boolean] Accept or reject identifier nodes
# File lib/opulent/parser/node.rb, line 207 def wrapped_attributes(list = {}, for_definition = false) return unless (bracket = accept :brackets) accept_newline attributes_assignments list, true, for_definition accept_newline accept_stripped bracket.to_sym, :* list end