Class: Opulent::Compiler

Inherits:
Object
  • Object
show all
Defined in:
lib/opulent/compiler.rb,
lib/opulent/compiler/eval.rb,
lib/opulent/compiler/node.rb,
lib/opulent/compiler/root.rb,
lib/opulent/compiler/text.rb,
lib/opulent/compiler/yield.rb,
lib/opulent/compiler/buffer.rb,
lib/opulent/compiler/define.rb,
lib/opulent/compiler/filter.rb,
lib/opulent/compiler/comment.rb,
lib/opulent/compiler/control.rb,
lib/opulent/compiler/doctype.rb

Constant Summary collapse

BUFFER =
:@_opulent_buffer
OPULENT_KEY =
:_opulent_key
OPULENT_VALUE =
:_opulent_value

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(settings = {}) ⇒ Compiler

All node Objects (Array) must follow the next convention in order to make parsing faster

:node_type, :value, :attributes, :children, :indent

Parameters:

  • path (String)

    Current file path needed for include nodes



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
# File 'lib/opulent/compiler.rb', line 29

def initialize(settings = {})
  # Setup convention accessors
  @type = 0
  @value = 1
  @options = 2
  @children = 3
  @indent = 4

  # Inherit settings from Engine
  @settings = settings

  # Get special node types from the settings
  @multi_node = Settings::MULTI_NODE
  @inline_node = Settings::INLINE_NODE

  # Initialize amble object
  @template = []

  # Incrmental counters
  @current_variable_count = 0
  @current_attribute = 0
  @current_extension = 0
  @current_definition = 0

  # The node stack is needed to keep track of all the visited nodes
  # from the current branch level
  @node_stack = []

  # Whenever we enter a definition compilation, add the provided blocks to
  # the current block stack. When exiting a definition, remove blocks.
  @block_stack = []

  # Remember last compiled node, required for pretty printing purposes
  @sibling_stack = [[[:root, nil]], []]

  # Set parent node, required for pretty printing
  @parent_stack = []

  # Flag to determine whether we're inside a definition
  @in_definition = false
end

Class Method Details

.error(context, *data) ⇒ Object

Give an explicit error report where an unexpected sequence of tokens appears and give indications on how to solve it

Parameters:

  • context (Symbol)

    Context name in which the error happens

  • data (Array)

    Additional error information



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
149
# File 'lib/opulent/compiler.rb', line 120

def self.error(context, *data)
  message = case context
            when :enumerable
              "The provided each structure iteration input \"#{data[0]}\"" \
              ' is not Enumerable.'
            when :binding
              data[0] = data[0].to_s.match(/\`(.*)\'/)
              data[0] = data[0][1] if data[0]
              "Found an undefined local variable or method \"#{data[0]}\"."
            when :variable_name
              data[0] = data[0].to_s.match(/\`(.*)\'/)[1]
              "Found an undefined local variable or method \"#{data[0]}\"" \
              ' in locals.'
            when :extension
              "The extension sequence \"#{data[0]}\" is not a valid " \
              'attributes extension. Please use a Hash to extend ' \
              'attributes.'
            when :filter_registered
              "The \"#{data[0]}\" filter could not be recognized by " \
              'Opulent.'
            when :filter_load
              "The gem required for the \"#{data[0]}\" filter is not " \
              "installed. You can install it by running:\n\n#{data[1]}"
            end

  # Reconstruct lines to display where errors occur
  fail "\n\nOpulent " + Logger.red('[Runtime Error]') + "\n---\n" \
  'A runtime error has been encountered when building the compiled ' \
  " node tree.\n #{message}\n\n\n"
end

Instance Method Details

#buffer(string) ⇒ Object

Output an object stream into the template

Parameters:

  • string (String)

    Buffer input string



9
10
11
# File 'lib/opulent/compiler/buffer.rb', line 9

def buffer(string)
  @template << [:buffer, string]
end

#buffer_attributes(attributes, extension) ⇒ Object

Go through the node attributes and apply extension where needed

Parameters:

  • attributes (Array)

    Array of node attributes, from parser

  • extension (String)

    Extension identifier



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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
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
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
# File 'lib/opulent/compiler/buffer.rb', line 107

def buffer_attributes(attributes, extension)
  # Proc for setting class attribute extension, used as DRY closure
  #
  buffer_class_attribute_type_check = proc do |variable, escape = true|
    class_variable = buffer_set_variable :local, variable

    # Check if we need to add the class attribute
    buffer_eval "unless #{class_variable} and true === #{class_variable}"

    # Check if class attribute has array value
    buffer_eval "if Array === #{class_variable}"
    ruby_code = "#{class_variable}.join ' '"
    if escape
      buffer_escape ruby_code
    else
      buffer ruby_code
    end

    # Check if class attribute has hash value
    buffer_eval "elsif Hash === #{class_variable}"
    ruby_code = "#{class_variable}.to_a.join ' '"
    if escape
      buffer_escape ruby_code
    else
      buffer ruby_code
    end

    # Other values
    buffer_eval 'else'
    ruby_code = "#{class_variable}"
    if escape
      buffer_escape ruby_code
    else
      buffer ruby_code
    end

    # End cases
    buffer_eval 'end'

    # End
    buffer_eval 'end'
  end

  # Handle class attributes by checking if they're simple, noninterpolated
  # strings or not and extend them if needed
  #
  buffer_class_attribute = proc do |attribute|
    if attribute[@value] =~ Tokens[:exp_string_match]
      buffer_split_by_interpolation attribute[@value][1..-2],
                                    attribute[@options][:escaped]
    else
      buffer_class_attribute_type_check[
        attribute[@value],
        attribute[@options][:escaped]
      ]
    end
  end

  # If we have class attributes, process each one and check if we have an
  # extension for them
  if attributes[:class]
    buffer_freeze " class=\""

    # Process every class attribute
    attributes[:class].each do |node_class|
      buffer_class_attribute[node_class]
      buffer_freeze ' '
    end

    # Remove trailing whitespace from the buffer
    buffer_remove_last_character

    # Check for extension with :class key
    if extension
      buffer_eval "if #{extension[:name]}.has_key? :class"
      buffer_freeze ' '
      buffer_class_attribute_type_check[
        "#{extension[:name]}.delete(:class)"
      ]
      buffer_eval 'end'
    end

    buffer_freeze '"'
  elsif extension
    # If we do not have class attributes but we do have an extension, try to
    # see if the extension contains a class attribute
    buffer_eval "if #{extension[:name]}.has_key? :class"
    buffer_freeze " class=\""
    buffer_class_attribute_type_check["#{extension[:name]}.delete(:class)"]
    buffer_freeze '"'
    buffer_eval 'end'
  end

  # Proc for setting class attribute extension, used as DRY closure
  #
  buffer_data_attribute_type_check = proc do |key, variable, escape = true, dynamic = false|
    # Check if variable is set
    buffer_eval "if #{variable}"

    # @Array
    buffer_eval "if Array === #{variable}"
    dynamic ? buffer("\" #{key}=\\\"\"") : buffer_freeze(" #{key}=\"")

    ruby_code = "#{variable}.join '_'"
    if escape
      buffer_escape ruby_code
    else
      buffer ruby_code
    end

    buffer_freeze '"'

    # @Hash
    buffer_eval "elsif Hash === #{variable}"
    buffer_eval "#{variable}.each do |#{OPULENT_KEY}, #{OPULENT_VALUE}|"
    # key-hashkey
    dynamic ? buffer("\" #{key}-\"") : buffer_freeze(" #{key}-")
    buffer "#{OPULENT_KEY}.to_s"
    #="value"
    buffer_freeze "=\""
    escape ? buffer_escape(OPULENT_VALUE) : buffer(OPULENT_VALUE)
    buffer_freeze '"'
    buffer_eval 'end'

    # @TrueClass
    buffer_eval "elsif true === #{variable}"
    dynamic ? buffer("\" #{key}\"") : buffer_freeze(" #{key}")

    # @Object
    buffer_eval 'else'
    dynamic ? buffer("\" #{key}=\\\"\"") : buffer_freeze(" #{key}=\"")
    escape ? buffer_escape("#{variable}") : buffer("#{variable}")
    buffer_freeze '"'

    # End Cases
    buffer_eval 'end'

    # End
    buffer_eval 'end'
  end

  # Handle data (normal) attributes by checking if they're simple, noninterpolated
  # strings and extend them if needed
  #
  buffer_data_attribute = proc do |key, attribute|
    # When we have an extension for our attributes, check current key.
    # If it exists, check it's type and generate everything dynamically
    if extension
      buffer_eval "if #{extension[:name]}.has_key? :\"#{key}\""
      variable = buffer_set_variable :local,
                                     "#{extension[:name]}" \
                                     ".delete(:\"#{key}\")"
      buffer_data_attribute_type_check[
        key,
        variable,
        attribute[@options][:escaped]
      ]
      buffer_eval 'else'
    end

    # Check if the set attribute is a simple string. If it is, freeze it or
    # escape it. Otherwise, evaluate and initialize the type check.
    if attribute[@value] =~ Tokens[:exp_string_match]
      buffer_freeze " #{key}=\""
      buffer_split_by_interpolation attribute[@value][1..-2],
                                    attribute[@options][:escaped]
      buffer_freeze "\""
    else
      # Evaluate and type check
      variable = buffer_set_variable :local, attribute[@value]
      buffer_data_attribute_type_check[
        key,
        variable,
        attribute[@options][:escaped]
      ]
    end

    # Extension end
    buffer_eval 'end' if extension
  end

  # Process the remaining, non-class related attributes
  attributes.each do |key, attribute|
    next if key == :class
    buffer_data_attribute[key, attribute]
  end

  # Process remaining extension keys if there are any
  return unless extension

  buffer_eval "#{extension[:name]}.each do " \
              "|ext#{OPULENT_KEY}, ext#{OPULENT_VALUE}|"

  buffer_data_attribute_type_check[
    "\#{ext#{OPULENT_KEY}}",
    "ext#{OPULENT_VALUE}",
    extension[:escaped],
    true
  ]
  buffer_eval 'end'
end

#buffer_attributes_to_hash(attributes) ⇒ Object

Turn call node attributes into a hash string

Parameters:

  • attributes (Array)

    Array of node attributes



88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/opulent/compiler/buffer.rb', line 88

def buffer_attributes_to_hash(attributes)
  '{' + attributes.inject([]) do |extend_map, (key, attribute)|
    extend_map << (
      ":\"#{key}\" => " + if key == :class
                            '[' + attribute.map do |exp|
                              exp[@value]
                            end.join(', ') + ']'
                          else
                            attribute[@value]
                          end
    )
  end.join(', ') + '}'
end

#buffer_escape(string) ⇒ Object

Output and escape an object stream into the template

Parameters:

  • string (String)

    Buffer input string



17
18
19
# File 'lib/opulent/compiler/buffer.rb', line 17

def buffer_escape(string)
  @template << [:escape, string]
end

#buffer_eval(string) ⇒ Object

Evaluate a stream into the template

Parameters:

  • string (String)

    Buffer input string



37
38
39
# File 'lib/opulent/compiler/buffer.rb', line 37

def buffer_eval(string)
  @template << [:eval, string]
end

#buffer_freeze(string) ⇒ Object

Output and freeze a String stream into the template

Parameters:

  • string (String)

    Buffer input string



25
26
27
28
29
30
31
# File 'lib/opulent/compiler/buffer.rb', line 25

def buffer_freeze(string)
  if @template[-1][0] == :freeze
    @template[-1][1] += string
  else
    @template << [:freeze, string]
  end
end

#buffer_remove_last_character(type = :freeze, n = 1) ⇒ Object

Remove last n characters from the most recent template item

Parameters:

  • type (Symbol) (defaults to: :freeze)

    Remove only if last buffer part is of this type

  • n (Fixnum) (defaults to: 1)

    Number of characters to be removed



58
59
60
# File 'lib/opulent/compiler/buffer.rb', line 58

def buffer_remove_last_character(type = :freeze, n = 1)
  @template[-1][1] = @template[-1][1][0..-1 - n] if @template[-1][0] == type
end

#buffer_remove_trailing_newline(type = :freeze) ⇒ Object

Remove last n characters from the most recent template item

Parameters:

  • type (Symbol) (defaults to: :freeze)

    Remove only if last buffer part is of this type

  • n (Fixnum)

    Number of characters to be removed



67
68
69
70
71
# File 'lib/opulent/compiler/buffer.rb', line 67

def buffer_remove_trailing_newline(type = :freeze)
  if @template[-1][0] == type
    @template[-1][1].gsub! /\s*\n+\s*$/, ''
  end
end

#buffer_remove_trailing_whitespace(type = :freeze) ⇒ Object

Remove last n characters from the most recent template item

Parameters:

  • type (Symbol) (defaults to: :freeze)

    Remove only if last buffer part is of this type

  • n (Fixnum)

    Number of characters to be removed



78
79
80
81
82
# File 'lib/opulent/compiler/buffer.rb', line 78

def buffer_remove_trailing_whitespace(type = :freeze)
  if @template[-1][0] == type
    @template[-1][1].rstrip!
  end
end

#buffer_set_variable(name, value) ⇒ Object

Set a local variable through buffer eval an object stream into the template

Parameters:

  • name (String)

    Variable identifier to be set

  • name (String)

    Variable value to be set



47
48
49
50
51
# File 'lib/opulent/compiler/buffer.rb', line 47

def buffer_set_variable(name, value)
  local_variable = "_opulent_#{name}_#{@current_variable_count += 1}"
  buffer_eval "#{local_variable} = #{value}"
  local_variable
end

#buffer_split_by_interpolation(string, escape = true) ⇒ Object

Split a string by its interpolation, then check if it really needs to be escaped or not. Huge performance boost!

Parameters:

  • string (String)

    Input string

  • escape (Boolean) (defaults to: true)

    Escape string



339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
# File 'lib/opulent/compiler/buffer.rb', line 339

def buffer_split_by_interpolation(string, escape = true)
  # Interpolate variables in text (#{variable}).
  # Split the text into multiple dynamic and static parts.
  begin
    case string
    when /\A\\\#\{/
      # Escaped interpolation
      buffer_freeze '#{'
      string = $'
    when /\A#\{((?>[^{}]|(\{(?>[^{}]|\g<1>)*\}))*)\}/
      # Interpolation
      string = $'
      code = $1

      # escape = code !~ /\A\{.*\}\Z/
      if escape
        buffer_escape code
      else
        buffer code
      end
    when /\A([\\#]?[^#\\]*([#\\][^\\#\{][^#\\]*)*)/
      string_remaining = $'
      string_current = $&

      # Static text
      if escape && string_current =~ Utils::ESCAPE_HTML_PATTERN
        buffer_escape string_current.inspect
      else
        buffer_freeze string_current
      end

      string = string_remaining
    end
  end until string.empty?
end

#case_node(node, indent) ⇒ Object

Generate the code for a case-when-else control structure

Parameters:

  • node (Array)

    Node code generation data

  • indent (Fixnum)

    Size of the indentation to be added



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/opulent/compiler/control.rb', line 61

def case_node(node, indent)
  # Evaluate the switching condition
  buffer_eval "case #{node[@options][:condition]}"

  # Check if we have any condition met, or an else branch
  node[@value].each_with_index do |value, index|
    # If we have a branch that meets the condition, generate code for the
    # children related to that specific branch
    case value
    when node[@value].last then buffer_eval 'else'
    else buffer_eval "when #{value}"
    end

    # Evaluate child nodes
    node[@children][index].each do |child|
      root child, indent
    end
  end

  # End
  buffer_eval 'end'
end

#comment(node, indent) ⇒ Object

Generate the code for a while control structure

Parameters:

  • node (Array)

    Node code generation data

  • indent (Fixnum)

    Size of the indentation to be added



10
11
12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/opulent/compiler/comment.rb', line 10

def comment(node, indent)
  buffer_freeze "\n" if node[@options][:newline]
  if @settings[:pretty]
    if @in_definition
      buffer "' ' * (indent + #{indent})"
    else
      buffer_freeze " " * indent
    end
  end
  buffer_freeze '<!-- '
  buffer_split_by_interpolation node[@value].strip, false
  buffer_freeze ' -->'
  buffer_freeze "\n" if @settings[:pretty]
end

#compile(root_node, definitions = {}) ⇒ Object

Compile input nodes, replace them with their definitions and

Parameters:

  • root_node (Array)

    Root node containing all document nodes



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/opulent/compiler.rb', line 75

def compile(root_node, definitions = {})
  # Compiler generated code
  @code = ''
  @generator = ''
  @definitions = definitions

  @template << [:preamble]

  # Write all node definitions as method defs
  @definitions.each do |_, node|
    define node
  end

  # Start building up the code from the root node
  root_node[@children].each do |child|
    root child, 0
  end

  @template << [:postamble]

  templatize
end

#def_node(node, indent) ⇒ Object

Generate code for all nodes by calling the method with their type name

Parameters:

  • current (Array)

    Current node data with options

  • indent (Fixnum)

    Indentation size for current node



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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/opulent/compiler/define.rb', line 41

def def_node(node, indent)
  # Set a namespace for the current node definition and make it a valid ruby
  # method name
  key = "_opulent_definition_#{node[@value].to_s.tr '-', '_'}"


  # If we have attributes set for our defined node, we will need to create
  # an extension parameter which will be o
  if node[@options][:attributes].empty?
    # Call method without any extension
    method_call = "#{key}"

    # Call arguments set to true, in correct order
    arguments = []
    @definitions[node[@value]][@options][:parameters].keys.each do |k|
      arguments << @definitions[
        node[@value]
      ][@options][:parameters][k][@value]
    end
    arguments << '{}'

    if @in_definition
      arguments << "(indent ? indent + #{indent} : #{indent})"
    else
      arguments << indent
    end
    method_call += '(' + arguments.join(', ') + ')'
    method_call += ' do' unless node[@children].empty?

    buffer_eval method_call
  else
    arguments = []

    # Extract node definition arguments in the correct order. If the given
    # key does not exist, set the value to default or true
    @definitions[
      node[@value]
    ][@options][:parameters].keys.each do |k|
      if node[@options][:attributes].key? k
        arguments << node[@options][:attributes].delete(k)[@value]
      else
        arguments << @definitions[
          node[@value]
        ][@options][:parameters][k][@value]
      end
    end

    call_attributes = buffer_attributes_to_hash(
      node[@options][:attributes]
    )

    # If the call node is extended as well, merge the call attributes hash
    # with the extension hash
    if node[@options][:extension]
      # .merge!(var_name)
      call_attributes += '.merge!(' \
                         "#{node[@options][:extension][@value]}" \
                         ')'

      # { |key, value1, value2|
      call_attributes += " { |#{OPULENT_KEY}, " \
                         "#{OPULENT_VALUE}1, #{OPULENT_VALUE}2|"

      # class ? value1 + value2 : value2
      call_attributes += "#{OPULENT_KEY} == :class ? (" \
                         "#{OPULENT_VALUE}1 += " \
                         "#{OPULENT_VALUE}2) : (#{OPULENT_VALUE}2" \
                         ')'

      # }
      call_attributes += '}'
    end

    arguments << call_attributes

    call = "#{key}(#{arguments.join ', '}, #{indent})"
    call += ' do' unless node[@children].empty?

    buffer_eval call
  end

  # Set call node children as block evaluation. Very useful for
  # performance and evaluating them in the parent context
  node[@children].each do |child|
    root child, indent + @settings[:indent]
  end

  # End block
  buffer_eval 'end' unless node[@children].empty?
end

#define(node) ⇒ Object

Write out definition node using ruby def

Parameters:

  • node (Node)

    Current node data with options



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
# File 'lib/opulent/compiler/define.rb', line 9

def define(node)
  # Write out def method_name
  definition = "def _opulent_definition_#{node[@value].to_s.tr '-', '_'}"

  # Node attributes
  parameters = []
  node[@options][:parameters].each do |key, value|
    parameters << "#{key} = #{value[@value]}"
  end
  parameters << 'attributes = {}'
  parameters << 'indent'
  parameters << '&block'
  definition += '(' + parameters.join(', ') + ')'

  buffer_eval 'instance_eval do'
  buffer_eval definition

  @in_definition = true
  node[@children].each do |child|
    root child, 0
  end
  @in_definition = false

  buffer_eval 'end'
  buffer_eval 'end'
end

#doctype_node(node, indent) ⇒ Object

Generate the code for a while control structure

Parameters:

  • node (Array)

    Node code generation data

  • indent (Fixnum)

    Size of the indentation to be added



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
# File 'lib/opulent/compiler/doctype.rb', line 10

def doctype_node(node, indent)
  value = case node[@value]
          when :"", :html, :"5"
            '!DOCTYPE html'
          when :"1.1"
            '!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"'
          when :strict
            '!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"'
          when :frameset
            '!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd"'
          when :mobile
            '!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd"'
          when :basic
            '!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd"'
          when :transitional
            '!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"'
          when :xml
            '?xml version="1.0" encoding="utf-8" ?'
          when :'xml ISO-8859-1'
            '?xml version="1.0" encoding="iso-8859-1" ?'
          else
            "!DOCTYPE #{node[@value]}"
          end

  @node_stack << :doctype
  buffer_freeze "<#{value}>"
  buffer_freeze "\n" if @settings[:pretty]
end

#each_node(node, indent) ⇒ Object

Generate the code for a while control structure

Parameters:

  • node (Array)

    Node code generation data

  • indent (Fixnum)

    Size of the indentation to be added



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
162
163
164
# File 'lib/opulent/compiler/control.rb', line 127

def each_node(node, indent)
  # Process named variables for each structure
  variables = node[@value][1].clone

  # The each structure accept missing arguments as well, therefore we need to
  # substitute them with our defaults
  #
  # each in iterable
  # each value in iterable
  # each key, value in iterable

  # Value argument name provided only
  if variables.length == 1
    variables.unshift Settings::DEFAULT_EACH_KEY

  # Missing key and value arguments
  elsif variables.empty?
    variables[0] = Settings::DEFAULT_EACH_KEY
    variables[1] = Settings::DEFAULT_EACH_VALUE
  end

  # Choose whether to apply each with index (Arrays) or each (Hashes) methods
  #buffer_eval "_opulent_send_method = (#{node[@value][1]}.is_a?(Array) ? :each_with_index : :each)"
  case node[@value][0][0]
  when '[]'
    buffer_eval "#{node[@value][0][1]}.each_with_index do |#{variables.reverse.join ', '}|"
  else
    buffer_eval "#{node[@value][0][1]}.each do |#{variables.join ', '}|"
  end

  # Evaluate child nodes
  node[@children].each do |child|
    root child, indent
  end

  # End
  buffer_eval 'end'
end

#evaluate(node, indent) ⇒ Object

Evaluate the embedded ruby code using the current context

Parameters:

  • node (Array)

    Node code generation data

  • indent (Fixnum)

    Size of the indentation to be added



10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/opulent/compiler/eval.rb', line 10

def evaluate(node, indent)
  # Check if this is a substructure of a control block and remove the last
  # end evaluation if it is
  if node[@value] =~ Settings::END_REMOVAL
    @template.pop if @template[-1] == [:eval, 'end']
  end

  # Check for explicit end node
  if node[@value] =~ Settings::END_EXPLICIT
    Logger.error :compile, @template, :explicit_end, node
  end

  # Evaluate the current expression
  buffer_eval node[@value]

  # If the node has children, evaluate each one of them
  node[@children].each do |child|
    root child, indent + @settings[:indent]
  end if node[@children]

  # Check if the node is actually a block expression
  buffer_eval 'end' if node[@value] =~ Settings::END_INSERTION
end

#filter(node, indent) ⇒ Object

Generate the code for a while control structure

Parameters:

  • node (Array)

    Node code generation data

  • indent (Fixnum)

    Size of the indentation to be added



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/compiler/filter.rb', line 10

def filter(node, indent)
  # Evaluate and generate node attributes, then process each one to
  # by generating the required attribute code
  attributes = {}
  node[@options].each do |key, attribute|
    attributes[key] = map_attribute key, attribute
  end

  # Get registered filter name
  name = node[@value]

  # Check if filter is registered
  self.error :filter_registered, name unless Filters.filters.has_key? name

  # Load the required filter
  Filters.filters[name].load_filter

  # Render output using the chosen engine
  output = Filters.filters[name].render node[@children]

  # Main output node which contains filter rendered value
  text_node = [:plain, :text, {value: output.rstrip, escaped: false, evaluate: false}, [], nil]

  # If we have a provided filter tag, wrap the text node in the wrapper
  # node tag and further indent
  if (wrapper_tag = Filters.filters[name].options[:tag])
    # Set wrapper tag attributes as evaluable expressions
    atts = {}
    Filters.filters[name].options[:attributes].each do |key, value|
      atts[key] = [:expression, value.inspect, {evaluate: false, escaped: false}]
    end

    # Create the wrapper node containing the output text node as a child
    wrapper_node = [:node, wrapper_tag, {attributes: atts}, [text_node], indent]

    # Begin code generation from the wrapper node
    root wrapper_node, indent
  else
    # Generate code for output text node
    root text_node, indent
  end
end

#if_node(node, indent) ⇒ Object

Generate the code for a if-elsif-else control structure

Parameters:

  • node (Array)

    Node code generation data

  • indent (Fixnum)

    Size of the indentation to be added



10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/opulent/compiler/control.rb', line 10

def if_node(node, indent)
  # Check if we have any condition met, or an else branch
  node[@value].each_with_index do |value, index|
    # If we have a branch that meets the condition, generate code for the
    # children related to that specific branch
    case value
    when node[@value].first then buffer_eval "if #{value}"
    when node[@value].last then buffer_eval "else"
    else buffer_eval "elsif #{value}"
    end

    # Evaluate child nodes
    node[@children][index].each do |child|
      root child, indent
    end
  end

  # End
  buffer_eval "end"
end

#indent_lines(text, indent) ⇒ Object

Indent all lines of the input text using give indentation

Parameters:

  • text (String)

    Input text to be indented

  • indent (String)

    Indentation string to be appended



109
110
111
112
# File 'lib/opulent/compiler.rb', line 109

def indent_lines(text, indent)
  text ||= ''
  text.lines.map { |line| indent + line }.join
end

#node(node, indent) ⇒ Object

Generate the code for a standard node element, with closing tags or self enclosing elements

Parameters:

  • node (Array)

    Node code generation data

  • indent (Fixnum)

    Size of the indentation to be added



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
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/opulent/compiler/node.rb', line 11

def node(node, indent)
  # Pretty print
  if @settings[:pretty]
    indentation = ' ' * indent
    inline = Settings::INLINE_NODE.include? node[@value]

    indentate = proc do
      if @in_definition
        buffer "' ' * (indent + #{indent})"
      else
        buffer_freeze indentation
      end
    end

    if inline
      inline_last_sibling = @sibling_stack[-1][-1] ? Settings::INLINE_NODE.include?(@sibling_stack[-1][-1][1]) : true

      if @sibling_stack[-1][-1] && @sibling_stack[-1][-1][0] == :plain
        buffer_remove_trailing_newline
      end

      if @in_definition || @sibling_stack[-1].length == 1 || !inline_last_sibling
        indentate[]
      end
    else
      indentate[]
    end

    @sibling_stack[-1] << [node[@type], node[@value]]
    @sibling_stack << [[node[@type], node[@value]]]
  end

  # Add the tag opening, with leading whitespace to the code buffer
  buffer_freeze ' ' if node[@options][:leading_whitespace]
  buffer_freeze "<#{node[@value]}"

  # Evaluate node extension in the current context
  if node[@options][:extension]
    extension_name = buffer_set_variable :extension,
                                         node[@options][:extension][@value]

    extension = {
      name: extension_name,
      escaped: node[@options][:extension][@options][:escaped]
    }
  end

  # Evaluate and generate node attributes, then process each one to
  # by generating the required attribute code
  buffer_attributes node[@options][:attributes],
                    extension


  # Check if the current node is self enclosing. Self enclosing nodes
  # do not have any child elements
  if node[@options][:self_enclosing]
    # If the tag is self enclosing, it cannot have any child elements.
    buffer_freeze '>'

    # Pretty print
    buffer_freeze "\n" if @settings[:pretty]

    # If we mistakenly add children to self enclosing nodes,
    # process each child element as if it was correctly indented
    # node[@children].each do |child|
    #   root child, indent + @settings[:indent]
    # end
  else
    # Set tag ending code
    buffer_freeze '>'

    # Pretty print
    if @settings[:pretty]
      if node[@children].length > 0
        buffer_freeze "\n" unless inline
      end
      # @sibling_stack << [[node[@type], node[@value]]]
    end

    # Process each child element recursively, increasing indentation
    node[@children].each do |child|
      root child, indent + @settings[:indent]
    end

    inline_last_child = @sibling_stack[-1][-2] &&
                        (@sibling_stack[-1][-2][0] == :plain ||
                          Settings::INLINE_NODE.include?(@sibling_stack[-1][-2][1]))

    # Pretty print
    if @settings[:pretty]
      if node[@children].length > 1 && inline_last_child
        buffer_freeze "\n"
      end

      if node[@children].size > 0 && !inline
        indentate[]
      end
    end

    # Set tag closing code
    buffer_freeze "</#{node[@value]}>"
    buffer_freeze ' ' if node[@options][:trailing_whitespace]

    # Pretty print
    if @settings[:pretty]
      buffer_freeze "\n" if !inline || @in_definition
    end
  end

  if @settings[:pretty]
    @sibling_stack.pop
  end
end

#plain(node, indent) ⇒ Object

Generate the code for a standard text node

Parameters:

  • node (Array)

    Node code generation data

  • indent (Fixnum)

    Size of the indentation to be added



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/compiler/text.rb', line 10

def plain(node, indent)
  # Text value
  value = node[@options][:value]

  # Pretty print
  if @settings[:pretty]
    indentation = ' ' * indent

    inline = @sibling_stack[-1][-1] &&
             @sibling_stack[-1][-1][0] == :node &&
             Settings::INLINE_NODE.include?(@sibling_stack[-1][-1][1])

    # Add current node to the siblings stack
    @sibling_stack[-1] << [node[@type], node[@value]]

    # If we have a text on multiple lines and the text isn't supposed to be
    # inline, indent all the lines of the text
    if node[@value] == :text 
      if !inline
        value.gsub!(/^(?!$)/, indentation)
      else
        buffer_remove_trailing_newline
        value.strip!
      end
    else
      buffer_freeze indentation
    end
  end

  # Leading whitespace
  buffer_freeze ' ' if node[@options][:leading_whitespace]

  # Evaluate text node if it's marked as such and print nodes in the
  # current context
  if node[@value] == :text
    buffer_split_by_interpolation value, node[@options][:escaped]
  else
    node[@options][:escaped] ? buffer_escape(value) : buffer(value)
  end

  # Trailing whitespace
  buffer_freeze ' ' if node[@options][:trailing_whitespace]

  # Pretty print
  if @settings[:pretty]
    buffer_freeze "\n" if !inline or node[@value] == :print
  end
end

#remove_trailing_newlineObject

Remove the last newline from the current code buffer



100
101
102
# File 'lib/opulent/compiler.rb', line 100

def remove_trailing_newline
  @code.chop! if @code[-1] == "\n"
end

#root(current, indent) ⇒ Object

Generate code for all nodes by calling the method with their type name

Parameters:

  • current (Array)

    Current node data with options

  • indent (Fixnum)

    Indentation size for current node



10
11
12
13
14
15
16
# File 'lib/opulent/compiler/root.rb', line 10

def root(current, indent)
  if KEYWORDS.include? current[@type]
    send :"#{current[@type]}_node", current, indent
  else
    send current[@type], current, indent
  end
end

#templatizeObject

Transform buffer array into a reusable template



311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
# File 'lib/opulent/compiler/buffer.rb', line 311

def templatize
  separator = DEBUG ? "\n" : '; ' # Readablity during development
  @template.map do |input|
    case input[0]
    when :preamble
      "#{BUFFER} = []#{separator}"
    when :buffer
      "#{BUFFER} << (#{input[1]})#{separator}"
    when :escape
      "#{BUFFER} << (::Opulent::Utils::escape(#{input[1]}))#{separator}"
    when :freeze
      "#{BUFFER} << (#{input[1].inspect}.freeze)#{separator}"
    when :eval
      "#{input[1]}#{separator}"
    when :postamble
      "#{BUFFER}.join"
    end
  end.join
end

#unless_node(node, indent) ⇒ Object

Generate the code for a unless-else control structure

Parameters:

  • node (Array)

    Node code generation data

  • indent (Fixnum)

    Size of the indentation to be added



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/opulent/compiler/control.rb', line 36

def unless_node(node, indent)
  # Check if we have any condition met, or an else branch
  node[@value].each_with_index do |value, index|
    # If we have a branch that meets the condition, generate code for the
    # children related to that specific branch
    case value
    when node[@value].first then buffer_eval "unless #{value}"
    else buffer_eval "else"
    end

    # Evaluate child nodes
    node[@children][index].each do |child|
      root child, indent
    end
  end

  # End
  buffer_eval "end"
end

#until_node(node, indent) ⇒ Object

Generate the code for a while control structure

Parameters:

  • node (Array)

    Node code generation data

  • indent (Fixnum)

    Size of the indentation to be added



108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/opulent/compiler/control.rb', line 108

def until_node(node, indent)
  # Until we have a branch that doesn't meet the condition, generate code for the
  # children related to that specific branch
  buffer_eval "until #{node[@value]}"

  # Evaluate child nodes
  node[@children].each do |child|
    root child, indent
  end

  # End
  buffer_eval 'end'
end

#while_node(node, indent) ⇒ Object

Generate the code for a while control structure

Parameters:

  • node (Array)

    Node code generation data

  • indent (Fixnum)

    Size of the indentation to be added



89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/opulent/compiler/control.rb', line 89

def while_node(node, indent)
  # While we have a branch that meets the condition, generate code for the
  # children related to that specific branch
  buffer_eval "while #{node[@value]}"

  # Evaluate child nodes
  node[@children].each do |child|
    root child, indent
  end

  #End
  buffer_eval 'end'
end

#yield_node(node, indent) ⇒ Object

Generate the code for a while control structure

Parameters:

  • node (Array)

    Node code generation data

  • indent (Fixnum)

    Size of the indentation to be added



10
11
12
# File 'lib/opulent/compiler/yield.rb', line 10

def yield_node(node, indent)
  buffer_eval 'yield if block_given?'
end