hotcell 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +19 -0
- data/.rspec +2 -0
- data/.rvmrc +1 -0
- data/Gemfile +15 -0
- data/Guardfile +24 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +17 -0
- data/hotcell.gemspec +22 -0
- data/lib/hotcell/.DS_Store +0 -0
- data/lib/hotcell/config.rb +31 -0
- data/lib/hotcell/context.rb +36 -0
- data/lib/hotcell/errors.rb +43 -0
- data/lib/hotcell/extensions.rb +42 -0
- data/lib/hotcell/lexer.rb +783 -0
- data/lib/hotcell/lexer.rl +299 -0
- data/lib/hotcell/manipulator.rb +31 -0
- data/lib/hotcell/node/arrayer.rb +7 -0
- data/lib/hotcell/node/assigner.rb +11 -0
- data/lib/hotcell/node/block.rb +58 -0
- data/lib/hotcell/node/calculator.rb +35 -0
- data/lib/hotcell/node/command.rb +41 -0
- data/lib/hotcell/node/hasher.rb +7 -0
- data/lib/hotcell/node/joiner.rb +7 -0
- data/lib/hotcell/node/sequencer.rb +7 -0
- data/lib/hotcell/node/summoner.rb +11 -0
- data/lib/hotcell/node/tag.rb +26 -0
- data/lib/hotcell/node.rb +55 -0
- data/lib/hotcell/parser.rb +1186 -0
- data/lib/hotcell/parser.y +231 -0
- data/lib/hotcell/scope.rb +57 -0
- data/lib/hotcell/template.rb +29 -0
- data/lib/hotcell/version.rb +3 -0
- data/lib/hotcell.rb +19 -0
- data/misc/rage.rl +1999 -0
- data/misc/unicode2ragel.rb +305 -0
- data/spec/data/dstrings +8 -0
- data/spec/data/sstrings +6 -0
- data/spec/lib/hotcell/config_spec.rb +57 -0
- data/spec/lib/hotcell/context_spec.rb +53 -0
- data/spec/lib/hotcell/lexer_spec.rb +340 -0
- data/spec/lib/hotcell/manipulator_spec.rb +64 -0
- data/spec/lib/hotcell/node/block_spec.rb +188 -0
- data/spec/lib/hotcell/node/command_spec.rb +71 -0
- data/spec/lib/hotcell/parser_spec.rb +382 -0
- data/spec/lib/hotcell/scope_spec.rb +160 -0
- data/spec/lib/hotcell/template_spec.rb +41 -0
- data/spec/lib/hotcell_spec.rb +8 -0
- data/spec/spec_helper.rb +44 -0
- metadata +139 -0
@@ -0,0 +1,299 @@
|
|
1
|
+
%%{
|
2
|
+
#%
|
3
|
+
machine puffer_lexer;
|
4
|
+
|
5
|
+
variable data @data;
|
6
|
+
variable te @te;
|
7
|
+
variable ts @ts;
|
8
|
+
|
9
|
+
plus = '+';
|
10
|
+
minus = '-';
|
11
|
+
multiply = '*';
|
12
|
+
power = '**';
|
13
|
+
divide = '/';
|
14
|
+
modulo = '%';
|
15
|
+
arithmetic = plus | minus | multiply | power | divide | modulo;
|
16
|
+
|
17
|
+
and = '&&';
|
18
|
+
or = '||';
|
19
|
+
not = '!';
|
20
|
+
equal = '==';
|
21
|
+
inequal = '!=';
|
22
|
+
gt = '>';
|
23
|
+
gte = '>=';
|
24
|
+
lt = '<';
|
25
|
+
lte = '<=';
|
26
|
+
logic = and | or | not | equal | inequal | gt | gte | lt | lte;
|
27
|
+
|
28
|
+
assign = '=';
|
29
|
+
comma = ',';
|
30
|
+
period = '.';
|
31
|
+
colon = ':';
|
32
|
+
question = '?';
|
33
|
+
semicolon = ';';
|
34
|
+
newline = '\n';
|
35
|
+
flow = assign | comma | period | colon | question | semicolon | newline;
|
36
|
+
|
37
|
+
array_open = '[';
|
38
|
+
array_close = ']';
|
39
|
+
hash_open = '{';
|
40
|
+
hash_close = '}';
|
41
|
+
bracket_open = '(';
|
42
|
+
bracket_close = ')';
|
43
|
+
structure = array_open | array_close | hash_open | hash_close | bracket_open | bracket_close;
|
44
|
+
|
45
|
+
|
46
|
+
escaped_symbol = '\\' any;
|
47
|
+
|
48
|
+
squote = "'";
|
49
|
+
snon_quote = [^\\'];
|
50
|
+
sstring = squote (snon_quote | escaped_symbol)* squote @lerr{ raise_unterminated_string };
|
51
|
+
|
52
|
+
dquote = '"';
|
53
|
+
dnon_quote = [^\\"];
|
54
|
+
dstring = dquote (dnon_quote | escaped_symbol)* dquote @lerr{ raise_unterminated_string };
|
55
|
+
|
56
|
+
rquote = '/';
|
57
|
+
rnon_quote = [^\\/];
|
58
|
+
regexp = rquote @{ regexp_ambiguity { fgoto expression; } }
|
59
|
+
(rnon_quote | escaped_symbol)* rquote alpha* @lerr{ raise_unterminated_regexp };
|
60
|
+
|
61
|
+
|
62
|
+
numeric = digit* ('.' digit+)?;
|
63
|
+
identifer = (alpha | '_') (alnum | '_')* [?!]?;
|
64
|
+
operator = arithmetic | logic | flow | structure;
|
65
|
+
comment = '#' ([^\n}]+ | '}' [^}])*;
|
66
|
+
blank = [\t\v\f\r ];
|
67
|
+
|
68
|
+
tag_open = '{{' [!\#]?;
|
69
|
+
tag_close = '}}';
|
70
|
+
template = [^{]+ | '{';
|
71
|
+
|
72
|
+
template_comment_close = '#}}';
|
73
|
+
template_comment_body = [^\#]+ | '#';
|
74
|
+
|
75
|
+
|
76
|
+
expression := |*
|
77
|
+
tag_close => { emit_tag; fret; };
|
78
|
+
operator => { emit_operator };
|
79
|
+
numeric => { emit_numeric };
|
80
|
+
identifer => { emit_identifer };
|
81
|
+
sstring => { emit_sstring };
|
82
|
+
dstring => { emit_dstring };
|
83
|
+
regexp => { emit_regexp };
|
84
|
+
comment => { emit_comment };
|
85
|
+
blank;
|
86
|
+
*|;
|
87
|
+
|
88
|
+
template_comment := |*
|
89
|
+
template_comment_close => { emit_comment; fret; };
|
90
|
+
template_comment_body => { emit_comment };
|
91
|
+
*|;
|
92
|
+
|
93
|
+
main := |*
|
94
|
+
tag_open => { emit_tag_or_comment ->{ fcall expression; }, ->{ fcall template_comment; } };
|
95
|
+
template => { emit_template };
|
96
|
+
*|;
|
97
|
+
}%%
|
98
|
+
#%
|
99
|
+
|
100
|
+
module Hotcell
|
101
|
+
class Lexer
|
102
|
+
OPERATIONS = {
|
103
|
+
'+' => :PLUS, '-' => :MINUS, '*' => :MULTIPLY, '**' => :POWER, '/' => :DIVIDE, '%' => :MODULO,
|
104
|
+
|
105
|
+
'&&' => :AND, '||' => :OR, '!' => :NOT, '==' => :EQUAL, '!=' => :INEQUAL,
|
106
|
+
'>' => :GT, '>=' => :GTE, '<' => :LT, '<=' => :LTE,
|
107
|
+
|
108
|
+
'=' => :ASSIGN, ',' => :COMMA, '.' => :PERIOD, ':' => :COLON, '?' => :QUESTION,
|
109
|
+
';' => :SEMICOLON
|
110
|
+
}
|
111
|
+
|
112
|
+
BOPEN = { '[' => :AOPEN, '{' => :HOPEN, '(' => :POPEN }
|
113
|
+
BCLOSE = { ']' => :ACLOSE, '}' => :HCLOSE, ')' => :PCLOSE }
|
114
|
+
BRACKETS = BOPEN.merge(BCLOSE)
|
115
|
+
|
116
|
+
OPERATORS = OPERATIONS.merge(BRACKETS).merge("\n" => :NEWLINE)
|
117
|
+
|
118
|
+
CONSTANTS = {
|
119
|
+
'nil' => [:NIL, nil], 'null' => [:NIL, nil],
|
120
|
+
'false' => [:FALSE, false], 'true' => [:TRUE, true]
|
121
|
+
}
|
122
|
+
|
123
|
+
SSTRING_ESCAPE_REGEXP = /\\\'|\\\\/
|
124
|
+
SSTRING_ESCAPE_MAP = { "\\'" => "'", "\\\\" => "\\" }
|
125
|
+
|
126
|
+
DSTRING_ESCAPE_REGEXP = /\\./
|
127
|
+
DSTRING_ESCAPE_MAP = {
|
128
|
+
'\\"' => '"', "\\\\" => "\\", '\n' => "\n",
|
129
|
+
'\s' => "\s", '\r' => "\r", '\t' => "\t"
|
130
|
+
}
|
131
|
+
|
132
|
+
TAGS = {
|
133
|
+
'{{' => :TOPEN, '{{!' => :TOPEN,
|
134
|
+
'}}' => :TCLOSE
|
135
|
+
}
|
136
|
+
|
137
|
+
PREREGEXP = Set.new [
|
138
|
+
:TOPEN, :NEWLINE, :SEMICOLON,
|
139
|
+
:COLON, :COMMA, :PERIOD,
|
140
|
+
:POPEN, :AOPEN, :HOPEN
|
141
|
+
]
|
142
|
+
|
143
|
+
def initialize source
|
144
|
+
@source = source
|
145
|
+
@data = @source.unpack 'c*'
|
146
|
+
|
147
|
+
%% write data;
|
148
|
+
#%
|
149
|
+
end
|
150
|
+
|
151
|
+
def emit symbol, value
|
152
|
+
@token_array << [symbol, value]
|
153
|
+
end
|
154
|
+
|
155
|
+
def current_value
|
156
|
+
@data[@ts...@te].pack('c*').force_encoding('UTF-8')
|
157
|
+
end
|
158
|
+
|
159
|
+
def emit_operator
|
160
|
+
value = current_value
|
161
|
+
emit OPERATORS[value], value
|
162
|
+
end
|
163
|
+
|
164
|
+
def emit_numeric
|
165
|
+
# last = @token_array[-1]
|
166
|
+
# pre_last = @token_array[-2]
|
167
|
+
# # This need to give unary minus with numeric higher precedence then unari minus with
|
168
|
+
# last[0] = :NEGATIVE if last && last[0] == :MINUS &&
|
169
|
+
# (!pre_last || pre_last[0].in?())
|
170
|
+
|
171
|
+
value = current_value
|
172
|
+
if value =~ /\./
|
173
|
+
emit :FLOAT, Float(value)
|
174
|
+
else
|
175
|
+
emit :INTEGER, Integer(value)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def emit_identifer
|
180
|
+
value = current_value
|
181
|
+
if args = CONSTANTS[value]
|
182
|
+
emit *args
|
183
|
+
else
|
184
|
+
emit :IDENTIFER, value
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def emit_sstring
|
189
|
+
emit :STRING, current_value[1..-2].gsub(SSTRING_ESCAPE_REGEXP) { |match|
|
190
|
+
SSTRING_ESCAPE_MAP[match] }.force_encoding('UTF-8')
|
191
|
+
end
|
192
|
+
|
193
|
+
def emit_dstring
|
194
|
+
emit :STRING, current_value[1..-2].gsub(DSTRING_ESCAPE_REGEXP) { |match|
|
195
|
+
DSTRING_ESCAPE_MAP[match] || match[1] }
|
196
|
+
end
|
197
|
+
|
198
|
+
def regexp_ambiguity
|
199
|
+
unless regexp_possible?
|
200
|
+
emit_operator
|
201
|
+
yield
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def regexp_possible?
|
206
|
+
last = @token_array[-1]
|
207
|
+
# Need more rules!
|
208
|
+
!last || PREREGEXP.include?(last[0])
|
209
|
+
end
|
210
|
+
|
211
|
+
def emit_regexp
|
212
|
+
value = current_value
|
213
|
+
finish = value.rindex('/')
|
214
|
+
|
215
|
+
options_string = value[finish+1..-1]
|
216
|
+
options = 0
|
217
|
+
options |= Regexp::EXTENDED if options_string.include?('x')
|
218
|
+
options |= Regexp::IGNORECASE if options_string.include?('i')
|
219
|
+
options |= Regexp::MULTILINE if options_string.include?('m')
|
220
|
+
|
221
|
+
emit :REGEXP, Regexp.new(value[1..finish-1], options)
|
222
|
+
end
|
223
|
+
|
224
|
+
def emit_template
|
225
|
+
# Hack this to glue templates going straight
|
226
|
+
last = @token_array[-1]
|
227
|
+
if last && last[0] == :TEMPLATE
|
228
|
+
last[1] += current_value
|
229
|
+
else
|
230
|
+
emit :TEMPLATE, current_value
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
def emit_tag_or_comment if_tag, if_comment
|
235
|
+
value = current_value
|
236
|
+
if value == '{{#'
|
237
|
+
emit_comment
|
238
|
+
if_comment.call
|
239
|
+
else
|
240
|
+
emit_tag
|
241
|
+
if_tag.call
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
def emit_tag
|
246
|
+
value = current_value
|
247
|
+
emit TAGS[value], value
|
248
|
+
end
|
249
|
+
|
250
|
+
def emit_comment
|
251
|
+
last = @token_array[-1]
|
252
|
+
if last && last[0] == :COMMENT
|
253
|
+
last[1] += current_value
|
254
|
+
else
|
255
|
+
emit :COMMENT, current_value
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
def current_position
|
260
|
+
parsed = @data[0..@ts].pack('c*').force_encoding('UTF-8')
|
261
|
+
line = parsed.count("\n") + 1
|
262
|
+
column = parsed.size - 1 - (parsed.rindex("\n") || -1)
|
263
|
+
[line, column]
|
264
|
+
end
|
265
|
+
|
266
|
+
def raise_unexpected_symbol
|
267
|
+
raise Hotcell::Errors::UnexpectedSymbol.new *current_position
|
268
|
+
end
|
269
|
+
|
270
|
+
def raise_unterminated_string
|
271
|
+
raise Hotcell::Errors::UnterminatedString.new *current_position
|
272
|
+
end
|
273
|
+
|
274
|
+
def raise_unterminated_regexp
|
275
|
+
raise Hotcell::Errors::UnterminatedRegexp.new *current_position
|
276
|
+
end
|
277
|
+
|
278
|
+
def tokens
|
279
|
+
@tokens ||= tokenize
|
280
|
+
end
|
281
|
+
|
282
|
+
def tokenize
|
283
|
+
@token_array = []
|
284
|
+
|
285
|
+
%% write init;
|
286
|
+
#%
|
287
|
+
|
288
|
+
eof = pe
|
289
|
+
stack = []
|
290
|
+
|
291
|
+
%% write exec;
|
292
|
+
#%
|
293
|
+
|
294
|
+
raise_unexpected_symbol unless @ts.nil?
|
295
|
+
|
296
|
+
@token_array
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Hotcell
|
2
|
+
class Manipulator
|
3
|
+
module Mixin
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
class_attribute :manipulator_methods, instance_writter: false
|
8
|
+
self.manipulator_methods = Set.new
|
9
|
+
|
10
|
+
def self.manipulator *methods
|
11
|
+
self.manipulator_methods = Set.new(manipulator_methods.to_a + methods.flatten.map(&:to_s))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_manipulator
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
def manipulator_invoke method, *arguments
|
20
|
+
send(method, *arguments) if manipulator_methods.include? method
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
include Mixin
|
25
|
+
|
26
|
+
def manipulator_methods
|
27
|
+
@manipulator_methods ||= Set.new((self.class.public_instance_methods -
|
28
|
+
Hotcell::Manipulator.public_instance_methods).map(&:to_s))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Hotcell
|
2
|
+
class Block < Hotcell::Command
|
3
|
+
class_attribute :_subcommands, instance_writter: false
|
4
|
+
self._subcommands = []
|
5
|
+
|
6
|
+
def self.subcommands *names
|
7
|
+
if names.any?
|
8
|
+
self._subcommands = _subcommands + names.flatten.map(&:to_s)
|
9
|
+
else
|
10
|
+
_subcommands
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def optimize
|
15
|
+
if klass = Hotcell.blocks[name]
|
16
|
+
klass.new name, *children, options
|
17
|
+
else
|
18
|
+
self
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def subnodes
|
23
|
+
options[:subnodes] || []
|
24
|
+
end
|
25
|
+
|
26
|
+
def subcommands
|
27
|
+
subnodes.select { |node| node.is_a?(Hash) }
|
28
|
+
end
|
29
|
+
|
30
|
+
def validate!
|
31
|
+
valid = subcommands.map { |subcommand| subcommand[:name] } - _subcommands == []
|
32
|
+
raise Hotcell::Errors::BlockError.new 'Invalid block syntax' unless valid
|
33
|
+
end
|
34
|
+
|
35
|
+
def process context, subnodes, *args
|
36
|
+
end
|
37
|
+
|
38
|
+
def render context
|
39
|
+
context.safe do
|
40
|
+
subnodes = render_subnodes(context)
|
41
|
+
values = render_nodes(context, children)
|
42
|
+
concat context, process(context, subnodes, *values)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def render_subnodes context
|
47
|
+
subnodes.map do |node|
|
48
|
+
if node.is_a?(Hash)
|
49
|
+
subcommand = { name: node[:name] }
|
50
|
+
subcommand.merge!(args: node[:args].render(context)) if node[:args]
|
51
|
+
subcommand
|
52
|
+
else
|
53
|
+
node.render(context)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Hotcell
|
2
|
+
class Calculator < Hotcell::Node
|
3
|
+
HANDLERS = {
|
4
|
+
:PLUS => ->(value1, value2) { value1 + value2 },
|
5
|
+
:MINUS => ->(value1, value2) { value1 - value2 },
|
6
|
+
:UMINUS => ->(value) { -value },
|
7
|
+
:UPLUS => ->(value) { value },
|
8
|
+
:MULTIPLY => ->(value1, value2) { value1 * value2 },
|
9
|
+
:POWER => ->(value1, value2) { value1 ** value2 },
|
10
|
+
:DIVIDE => ->(value1, value2) { value1 / value2 },
|
11
|
+
:MODULO => ->(value1, value2) { value1 % value2 },
|
12
|
+
:AND => ->(value1, value2) { value1 && value2 },
|
13
|
+
:OR => ->(value1, value2) { value1 || value2 },
|
14
|
+
:NOT => ->(value) { !value },
|
15
|
+
:EQUAL => ->(value1, value2) { value1 == value2 },
|
16
|
+
:INEQUAL => ->(value1, value2) { value1 != value2 },
|
17
|
+
:GT => ->(value1, value2) { value1 > value2 },
|
18
|
+
:GTE => ->(value1, value2) { value1 >= value2 },
|
19
|
+
:LT => ->(value1, value2) { value1 < value2 },
|
20
|
+
:LTE => ->(value1, value2) { value1 <= value2 }
|
21
|
+
}
|
22
|
+
|
23
|
+
def optimize
|
24
|
+
handler = HANDLERS[name]
|
25
|
+
reducible = handler && children.none? { |child| child.is_a? Hotcell::Node }
|
26
|
+
reducible ? handler.call(*children) : super
|
27
|
+
end
|
28
|
+
|
29
|
+
def process context, *values
|
30
|
+
handler = HANDLERS[name]
|
31
|
+
raise "Could not find handler for `#{name}`" unless handler
|
32
|
+
handler.call *values
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Hotcell
|
2
|
+
class Command < Hotcell::Node
|
3
|
+
attr_reader :mode, :assign
|
4
|
+
|
5
|
+
def initialize *_
|
6
|
+
super
|
7
|
+
@mode = options[:mode] || :normal
|
8
|
+
@assign = options[:assign]
|
9
|
+
end
|
10
|
+
|
11
|
+
def optimize
|
12
|
+
if klass = Hotcell.commands[name]
|
13
|
+
klass.new name, *children, options
|
14
|
+
else
|
15
|
+
self
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def validate!
|
20
|
+
end
|
21
|
+
|
22
|
+
def process context, *args
|
23
|
+
end
|
24
|
+
|
25
|
+
def render context
|
26
|
+
context.safe do
|
27
|
+
concat context, process(context, *render_nodes(context, children))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def concat context, result
|
32
|
+
context[assign] = result if assign
|
33
|
+
case mode
|
34
|
+
when :normal
|
35
|
+
result
|
36
|
+
else
|
37
|
+
''
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Hotcell
|
2
|
+
class Tag < Hotcell::Node
|
3
|
+
attr_reader :mode
|
4
|
+
|
5
|
+
def initialize *_
|
6
|
+
super
|
7
|
+
@mode = options[:mode] || :normal
|
8
|
+
end
|
9
|
+
|
10
|
+
def process context, *values
|
11
|
+
values.last
|
12
|
+
end
|
13
|
+
|
14
|
+
def render context
|
15
|
+
context.safe do
|
16
|
+
values = render_nodes(context, children)
|
17
|
+
case mode
|
18
|
+
when :normal
|
19
|
+
process context, *values
|
20
|
+
else
|
21
|
+
''
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/hotcell/node.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
module Hotcell
|
2
|
+
class Node
|
3
|
+
attr_accessor :name, :children, :options
|
4
|
+
|
5
|
+
def self.build *args
|
6
|
+
new(*args).optimize
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize name, *args
|
10
|
+
@name = name
|
11
|
+
@options = args.extract_options!
|
12
|
+
@children = args
|
13
|
+
end
|
14
|
+
|
15
|
+
def optimize
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
def [] key
|
20
|
+
children[key]
|
21
|
+
end
|
22
|
+
|
23
|
+
def render context
|
24
|
+
process context, *render_nodes(context, children)
|
25
|
+
end
|
26
|
+
|
27
|
+
def process context, *values
|
28
|
+
raise NotImplementedError
|
29
|
+
end
|
30
|
+
|
31
|
+
def render_nodes context, *values
|
32
|
+
values.flatten.map do |node|
|
33
|
+
node.is_a?(Hotcell::Node) ? node.render(context) : node
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def == other
|
38
|
+
other.is_a?(self.class) &&
|
39
|
+
name == other.name &&
|
40
|
+
options == other.options &&
|
41
|
+
children == other.children
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
require 'hotcell/node/calculator'
|
47
|
+
require 'hotcell/node/assigner'
|
48
|
+
require 'hotcell/node/summoner'
|
49
|
+
require 'hotcell/node/arrayer'
|
50
|
+
require 'hotcell/node/hasher'
|
51
|
+
require 'hotcell/node/sequencer'
|
52
|
+
require 'hotcell/node/joiner'
|
53
|
+
require 'hotcell/node/tag'
|
54
|
+
require 'hotcell/node/command'
|
55
|
+
require 'hotcell/node/block'
|