hotcell 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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'