class Opulent::Parser

@Parser

@Parser

@Parser

@Parser

@Parser

@Parser

@Parser

@Parser

@Parser

@Parser

@Parser

@Parser

@Parser

Attributes

children[R]
definitions[R]
indent[R]
options[R]
type[R]
value[R]

Public Class Methods

new(settings = {}) click to toggle source

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

accept(token, required = false, strip = false) click to toggle source

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
accept_newline() click to toggle source

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
accept_stripped(token, required = false) click to toggle source

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
add_attribute(atts, key, value) click to toggle source

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
apply_definitions(node) click to toggle source

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
array() click to toggle source

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
array_elements(buffer = '') click to toggle source

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
attributes(atts = {}, for_definition = false) click to toggle source

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
attributes_assignments(list, wrapped = true, for_definition = false) click to toggle source

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
block_yield(parent, indent) click to toggle source

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
call() click to toggle source

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
call_elements(buffer = '') click to toggle source

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
comment(parent, indent) click to toggle source

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
control(parent, indent) click to toggle source

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
control_child(structure) click to toggle source

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
control_parent(structure) click to toggle source

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
define(parent, indent) click to toggle source

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
doctype(parent, indent) click to toggle source

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
evaluate(parent, indent) click to toggle source

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
expression(allow_assignments = true, wrapped = true, whitespace = true) click to toggle source

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_attributes() click to toggle source

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
filter(parent, indent) click to toggle source

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
get_indented_lines(indent) click to toggle source

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
hash() click to toggle source

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
hash_elements(buffer = '') click to toggle source

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
html_text(parent, indent) click to toggle source

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
identifier() click to toggle source

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
include_file(_parent, indent) click to toggle source

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_lines(text, indent) click to toggle source

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
lookahead(token) click to toggle source

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
lookahead_next_line(token) click to toggle source

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
method_call() click to toggle source

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
modifier() click to toggle source

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
node(parent, indent = nil) click to toggle source

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
operation() click to toggle source

Accept an operation between two or more expressions

# File lib/opulent/parser/expression.rb, line 336
def operation
  accept(:exp_operation)
end
paranthesis() click to toggle source

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
parse(code) click to toggle source

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
percent() click to toggle source

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
primary_term() click to toggle source

Accept any primary term and return it without the leading whitespace to the expression buffer

“string” 123 123.456 nil true false /.*/

# 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
root(parent = @root, min_indent = -1) click to toggle source

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
shorthand_attributes(atts = {}) click to toggle source

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
symbol() click to toggle source

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
ternary_operator(allow_assignments, wrapped) click to toggle source

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
text(parent, indent = nil, multiline_or_print = true) click to toggle source

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(match) click to toggle source

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
whitespace(required = false) click to toggle source

Match a whitespace by preventing code trimming

# File lib/opulent/parser/text.rb, line 90
def whitespace(required = false)
  accept :whitespace, required
end
wrapped_attributes(list = {}, for_definition = false) click to toggle source

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