hotcell 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. data/.gitignore +19 -0
  2. data/.rspec +2 -0
  3. data/.rvmrc +1 -0
  4. data/Gemfile +15 -0
  5. data/Guardfile +24 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +29 -0
  8. data/Rakefile +17 -0
  9. data/hotcell.gemspec +22 -0
  10. data/lib/hotcell/.DS_Store +0 -0
  11. data/lib/hotcell/config.rb +31 -0
  12. data/lib/hotcell/context.rb +36 -0
  13. data/lib/hotcell/errors.rb +43 -0
  14. data/lib/hotcell/extensions.rb +42 -0
  15. data/lib/hotcell/lexer.rb +783 -0
  16. data/lib/hotcell/lexer.rl +299 -0
  17. data/lib/hotcell/manipulator.rb +31 -0
  18. data/lib/hotcell/node/arrayer.rb +7 -0
  19. data/lib/hotcell/node/assigner.rb +11 -0
  20. data/lib/hotcell/node/block.rb +58 -0
  21. data/lib/hotcell/node/calculator.rb +35 -0
  22. data/lib/hotcell/node/command.rb +41 -0
  23. data/lib/hotcell/node/hasher.rb +7 -0
  24. data/lib/hotcell/node/joiner.rb +7 -0
  25. data/lib/hotcell/node/sequencer.rb +7 -0
  26. data/lib/hotcell/node/summoner.rb +11 -0
  27. data/lib/hotcell/node/tag.rb +26 -0
  28. data/lib/hotcell/node.rb +55 -0
  29. data/lib/hotcell/parser.rb +1186 -0
  30. data/lib/hotcell/parser.y +231 -0
  31. data/lib/hotcell/scope.rb +57 -0
  32. data/lib/hotcell/template.rb +29 -0
  33. data/lib/hotcell/version.rb +3 -0
  34. data/lib/hotcell.rb +19 -0
  35. data/misc/rage.rl +1999 -0
  36. data/misc/unicode2ragel.rb +305 -0
  37. data/spec/data/dstrings +8 -0
  38. data/spec/data/sstrings +6 -0
  39. data/spec/lib/hotcell/config_spec.rb +57 -0
  40. data/spec/lib/hotcell/context_spec.rb +53 -0
  41. data/spec/lib/hotcell/lexer_spec.rb +340 -0
  42. data/spec/lib/hotcell/manipulator_spec.rb +64 -0
  43. data/spec/lib/hotcell/node/block_spec.rb +188 -0
  44. data/spec/lib/hotcell/node/command_spec.rb +71 -0
  45. data/spec/lib/hotcell/parser_spec.rb +382 -0
  46. data/spec/lib/hotcell/scope_spec.rb +160 -0
  47. data/spec/lib/hotcell/template_spec.rb +41 -0
  48. data/spec/lib/hotcell_spec.rb +8 -0
  49. data/spec/spec_helper.rb +44 -0
  50. 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,7 @@
1
+ module Hotcell
2
+ class Arrayer < Hotcell::Node
3
+ def process context, *values
4
+ values
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ module Hotcell
2
+ class Assigner < Hotcell::Node
3
+ def initialize *attrs
4
+ super :ASSIGN, *attrs
5
+ end
6
+
7
+ def process context, name, value
8
+ context[name] = value
9
+ end
10
+ end
11
+ 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,7 @@
1
+ module Hotcell
2
+ class Hasher < Hotcell::Node
3
+ def render context, *values
4
+ Hash[values]
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Hotcell
2
+ class Joiner < Hotcell::Node
3
+ def process context, *values
4
+ values.join
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Hotcell
2
+ class Sequencer < Hotcell::Node
3
+ def process context, *values
4
+ values.last
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ module Hotcell
2
+ class Summoner < Hotcell::Node
3
+ def initialize *attrs
4
+ super :METHOD, *attrs
5
+ end
6
+
7
+ def process context, object, method, *arguments
8
+ (object.to_manipulator || context).manipulator_invoke(method, *arguments)
9
+ end
10
+ end
11
+ 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
@@ -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'