Class: Opulent::Parser
- Inherits:
-
Object
- Object
- Opulent::Parser
- Defined in:
- lib/opulent/parser.rb,
lib/opulent/parser/eval.rb,
lib/opulent/parser/node.rb,
lib/opulent/parser/root.rb,
lib/opulent/parser/text.rb,
lib/opulent/parser/yield.rb,
lib/opulent/parser/define.rb,
lib/opulent/parser/filter.rb,
lib/opulent/parser/comment.rb,
lib/opulent/parser/control.rb,
lib/opulent/parser/doctype.rb,
lib/opulent/parser/include.rb,
lib/opulent/parser/expression.rb
Instance Attribute Summary collapse
-
#children ⇒ Object
readonly
Returns the value of attribute children.
-
#definitions ⇒ Object
readonly
Returns the value of attribute definitions.
-
#indent ⇒ Object
readonly
Returns the value of attribute indent.
-
#options ⇒ Object
readonly
Returns the value of attribute options.
-
#type ⇒ Object
readonly
Returns the value of attribute type.
-
#value ⇒ Object
readonly
Returns the value of attribute value.
Instance Method Summary collapse
-
#accept(token, required = false, strip = false) ⇒ Object
Check and accept or reject a given token as long as we have tokens remaining.
-
#accept_newline ⇒ Object
Allow expressions to continue on a new line in certain conditions.
-
#accept_stripped(token, required = false) ⇒ Object
Helper method which automatically sets the stripped options to true, so that we do not have to explicitly specify it.
-
#add_attribute(atts, key, value) ⇒ Object
Helper method to create an array of values when an attribute is set multiple times.
-
#apply_definitions(node) ⇒ Object
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.
-
#array ⇒ Object
Check if it’s possible to parse a ruby array literal.
-
#array_elements(buffer = '') ⇒ Object
Recursively gather expressions separated by a comma and add them to the expression buffer.
-
#attributes(atts = {}, for_definition = false) ⇒ Object
Get element attributes.
-
#attributes_assignments(list, wrapped = true, for_definition = false) ⇒ Object
Get all attribute assignments as key=value pairs or standalone keys.
-
#block_yield(parent, indent) ⇒ Object
Match a yield with a explicit or implicit target.
-
#call ⇒ Object
Check if it’s possible to parse a ruby call literal.
-
#call_elements(buffer = '') ⇒ Object
Recursively gather expression attributes separated by a comma and add them to the expression buffer.
-
#comment(parent, indent) ⇒ Object
Match one line or multiline comments.
-
#control(parent, indent) ⇒ Object
Match an if-else control structure.
-
#control_child(structure) ⇒ Object
Check if the current control structure requires a parent node and return the parent’s node type.
-
#control_parent(structure) ⇒ Object
Check if the current control structure requires a parent node and return the parent’s node type.
-
#define(parent, indent) ⇒ Object
Check if we match a new node definition to use within our page.
-
#doctype(parent, indent) ⇒ Object
Match one line or multiline comments.
-
#evaluate(parent, indent) ⇒ Object
Match one line or multiline, escaped or unescaped text.
-
#expression(allow_assignments = true, wrapped = true, whitespace = true) ⇒ Object
Check if the parser matches an expression node.
-
#extend_attributes ⇒ Object
Extend node attributes with hash from.
-
#filter(parent, indent) ⇒ Object
Check if we match an compile time filter.
-
#get_indented_lines(indent) ⇒ Object
Gather all the lines which have higher indentation than the one given as parameter and put them into the buffer.
-
#hash ⇒ Object
Check if it’s possible to parse a ruby hash literal.
-
#hash_elements(buffer = '') ⇒ Object
Recursively gather expression attributions separated by a comma and add them to the expression buffer.
-
#html_text(parent, indent) ⇒ Object
Match one line or multiline, escaped or unescaped text.
-
#identifier ⇒ Object
Accept a ruby identifier such as a class, module, method or variable.
-
#include_file(_parent, indent) ⇒ Object
Check if we have an include node, which will include a new file inside of the current one to be parsed.
-
#indent_lines(text, indent) ⇒ Object
Indent all lines of the input text using give indentation.
-
#initialize(settings = {}) ⇒ Parser
constructor
All node Objects (Array) must follow the next convention in order to make parsing faster.
-
#lookahead(token) ⇒ Object
Check if the lookahead matches the chosen regular expression.
-
#lookahead_next_line(token) ⇒ Object
Check if the lookahead matches the chosen regular expression on the following line which needs to be parsed.
-
#method_call ⇒ Object
Accept a ruby method call modifier.
-
#modifier ⇒ Object
Accept a ruby module, method or context modifier.
-
#node(parent, indent = nil) ⇒ Object
Check if we match an node node with its attributes and possibly inline text.
-
#operation ⇒ Object
Accept an operation between two or more expressions.
-
#paranthesis ⇒ Object
Check if it’s possible to parse a ruby paranthesis expression wrapper.
-
#parse(code) ⇒ Object
Initialize the parsing process by splitting the code into lines and instantiationg parser variables with their default values.
-
#percent ⇒ Object
Accept a ruby percentage operator for arrays of strings, symbols and simple escaped strings.
-
#primary_term ⇒ Object
Accept any primary term and return it without the leading whitespace to the expression buffer.
-
#root(parent = @root, min_indent = -1)) ⇒ Object
Analyze the input code and check for matching tokens.
-
#shorthand_attributes(atts = {}) ⇒ Object
Accept node shorthand attributes.
-
#symbol ⇒ Object
Accept a ruby symbol defined through a colon and a trailing expression.
-
#ternary_operator(allow_assignments, wrapped) ⇒ Object
Accept ternary operator syntax.
-
#text(parent, indent = nil, multiline_or_print = true) ⇒ Object
Match one line or multiline, escaped or unescaped text.
-
#undo(match) ⇒ Object
Undo a found match by removing the token from the consumed code and adding it back to the code chunk.
-
#whitespace(required = false) ⇒ Object
Match a whitespace by preventing code trimming.
-
#wrapped_attributes(list = {}, for_definition = false) ⇒ Object
Check if we match node attributes.
Constructor Details
#initialize(settings = {}) ⇒ Parser
All node Objects (Array) must follow the next convention in order to make parsing faster
- :node_type, :value, :attributes, :children, :indent
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/opulent/parser.rb', line 25 def initialize(settings = {}) # Convention accessors @type = 0 @value = 1 = 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 |
Instance Attribute Details
#children ⇒ Object (readonly)
Returns the value of attribute children.
18 19 20 |
# File 'lib/opulent/parser.rb', line 18 def children @children end |
#definitions ⇒ Object (readonly)
Returns the value of attribute definitions.
18 19 20 |
# File 'lib/opulent/parser.rb', line 18 def definitions @definitions end |
#indent ⇒ Object (readonly)
Returns the value of attribute indent.
18 19 20 |
# File 'lib/opulent/parser.rb', line 18 def indent @indent end |
#options ⇒ Object (readonly)
Returns the value of attribute options.
18 19 20 |
# File 'lib/opulent/parser.rb', line 18 def end |
#type ⇒ Object (readonly)
Returns the value of attribute type.
18 19 20 |
# File 'lib/opulent/parser.rb', line 18 def type @type end |
#value ⇒ Object (readonly)
Returns the value of attribute value.
18 19 20 |
# File 'lib/opulent/parser.rb', line 18 def value @value end |
Instance Method Details
#accept(token, required = false, strip = false) ⇒ Object
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
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
# 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 ⇒ Object
Allow expressions to continue on a new line in certain conditions
198 199 200 201 202 203 |
# 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) ⇒ Object
Helper method which automatically sets the stripped options to true, so that we do not have to explicitly specify it
158 159 160 |
# File 'lib/opulent/parser.rb', line 158 def accept_stripped(token, required = false) accept(token, required, true) end |
#add_attribute(atts, key, value) ⇒ Object
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
144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
# 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) ⇒ Object
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.
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
# 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[][: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 ⇒ Object
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
60 61 62 63 64 65 66 67 68 69 70 71 72 |
# 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 = '') ⇒ Object
Recursively gather expressions separated by a comma and add them to the expression buffer
experssion1, experssion2, experssion3
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
# 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) ⇒ Object
Get element attributes
195 196 197 198 199 |
# 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) ⇒ Object
Get all attribute assignments as key=value pairs or standalone keys
- assignments
226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 |
# 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[][: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) ⇒ Object
Match a yield with a explicit or implicit target
yield target
11 12 13 14 15 16 17 18 19 20 21 |
# 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 ⇒ Object
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 )
203 204 205 206 207 208 209 |
# 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 = '') ⇒ Object
Recursively gather expression attributes separated by a comma and add them to the expression buffer
expression1, a: expression2, expression3
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 |
# 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) ⇒ Object
Match one line or multiline comments
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
# 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 = {} # Allow leading comment newline if buffer[1] == '^' offset = 2 [:newline] = true end parent[@children] << [ :comment, buffer[offset..-1].strip, , nil, indent ] end parent end |
#control(parent, indent) ⇒ Object
Match an if-else control structure
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
# 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) ⇒ Object
Check if the current control structure requires a parent node and return the parent’s node type
117 118 119 |
# File 'lib/opulent/parser/control.rb', line 117 def control_child(structure) [:else, :elsif, :when].include? structure end |
#control_parent(structure) ⇒ Object
Check if the current control structure requires a parent node and return the parent’s node type
124 125 126 127 128 129 130 |
# 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) ⇒ Object
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.
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
# 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) ⇒ Object
Match one line or multiline comments
7 8 9 10 11 12 13 |
# 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) ⇒ Object
Match one line or multiline, escaped or unescaped text
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
# 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) ⇒ Object
Check if the parser matches an expression node
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# 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 ⇒ Object
Extend node attributes with hash from
value “value” +(paranthesis)
289 290 291 292 293 294 295 296 297 |
# 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[][:escaped] = !unescaped extension end |
#filter(parent, indent) ⇒ Object
Check if we match an compile time filter
:filter
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
# 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) ⇒ Object
Gather all the lines which have higher indentation than the one given as parameter and put them into the buffer
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
# 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 ⇒ Object
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 }
105 106 107 108 109 110 111 112 |
# 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 = '') ⇒ Object
Recursively gather expression attributions separated by a comma and add them to the expression buffer
key1: experssion1, key2 => experssion2, :key3 => experssion3
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 |
# 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) ⇒ Object
Match one line or multiline, escaped or unescaped text
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
# 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 ⇒ Object
Accept a ruby identifier such as a class, module, method or variable
165 166 167 168 169 170 171 |
# 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) ⇒ Object
Check if we have an include node, which will include a new file inside of the current one to be parsed
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
# 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. 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) ⇒ Object
Indent all lines of the input text using give indentation
210 211 212 213 |
# File 'lib/opulent/parser.rb', line 210 def indent_lines(text, indent) text ||= '' text.lines.map { |line| indent + line }.join end |
#lookahead(token) ⇒ Object
Check if the lookahead matches the chosen regular expression
166 167 168 169 170 171 |
# 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) ⇒ Object
Check if the lookahead matches the chosen regular expression on the following line which needs to be parsed
178 179 180 181 182 183 |
# 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 ⇒ Object
Accept a ruby method call modifier
185 186 187 188 189 190 191 192 193 194 195 |
# 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 ⇒ Object
Accept a ruby module, method or context modifier
- Module
-
@, @@, $
285 286 287 |
# File 'lib/opulent/parser/expression.rb', line 285 def modifier accept(:exp_context) || accept(:exp_module) end |
#node(parent, indent = nil) ⇒ Object
Check if we match an node node with its attributes and possibly inline text
node [ attributes ] Inline text
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
# 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 = {} # Get leading whitespace [:recursive] = accept(:recursive) # Get leading whitespace [:leading_whitespace] = accept_stripped(:leading_whitespace) # Get trailing whitespace [:trailing_whitespace] = accept_stripped(:trailing_whitespace) # Get wrapped node attributes atts = attributes(shorthand) || {} # Inherit attributes from definition [:extension] = extend_attributes # Get unwrapped node attributes [:attributes] = attributes_assignments atts, false # Create node current_node = [:node, node_name, , [], 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[][: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 ⇒ Object
Accept an operation between two or more expressions
336 337 338 |
# File 'lib/opulent/parser/expression.rb', line 336 def operation accept(:exp_operation) end |
#paranthesis ⇒ Object
Check if it’s possible to parse a ruby paranthesis expression wrapper.
175 176 177 178 179 180 |
# 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) ⇒ Object
Initialize the parsing process by splitting the code into lines and instantiationg parser variables with their default values
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# 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 ⇒ Object
Accept a ruby percentage operator for arrays of strings, symbols and simple escaped strings
%w(word1 word2 word3)
294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 |
# 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 ⇒ Object
Accept any primary term and return it without the leading whitespace to the expression buffer
“string” 123 123.456 nil true false /.*/
325 326 327 328 329 330 331 332 |
# 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)) ⇒ Object
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.
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
# 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 = {}) ⇒ Object
Accept node shorthand attributes. Each shorthand attribute is directly mapped to an attribute key
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
# 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 ⇒ Object
Accept a ruby symbol defined through a colon and a trailing expression
:‘symbol’ :symbol
269 270 271 272 273 274 275 276 277 278 |
# 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) ⇒ Object
Accept ternary operator syntax
condition ? expression1 : expression2
344 345 346 347 348 349 350 351 352 353 |
# 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) ⇒ Object
Match one line or multiline, escaped or unescaped text
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
# 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[][:value] += accept(:newline) || '' text_node[][:value] += get_indented_lines(indent) text_node[][: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) ⇒ Object
Undo a found match by removing the token from the consumed code and adding it back to the code chunk
190 191 192 193 194 |
# File 'lib/opulent/parser.rb', line 190 def undo(match) return if match.empty? @offset -= match.size nil end |
#whitespace(required = false) ⇒ Object
Match a whitespace by preventing code trimming
90 91 92 |
# File 'lib/opulent/parser/text.rb', line 90 def whitespace(required = false) accept :whitespace, required end |
#wrapped_attributes(list = {}, for_definition = false) ⇒ Object
Check if we match node attributes
- assignments
207 208 209 210 211 212 213 214 215 216 217 |
# 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 |