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,231 @@
1
+ class Hotcell::Parser
2
+
3
+ # Yes [ ] [ ]= Element reference, element set
4
+ # Yes ** Exponentiation (raise to the power)
5
+ # Yes ! ~ + - Not, complement, unary plus and minus (method names for the last two are +@ and -@)
6
+ # Yes * / % Multiply, divide, and modulo
7
+ # Yes + - Addition and subtraction
8
+ # Yes <= < > >= Comparison operators
9
+ # Yes <=> == === != =~ !~ Equality and pattern match operators (!= and !~ may not be defined as methods)
10
+ # && Logical `AND'
11
+ # || Logical `AND'
12
+ # .. ... Range (inclusive and exclusive)
13
+ # ? : Ternary if-then-else
14
+ # = %= { /= -= += |= &= >>= <<= *= &&= ||= **= Assignment
15
+
16
+ # . :: [] (method) left to right
17
+ # ! ~ + (unary) right to left
18
+ # ** right to left
19
+ # - (unary) right to left
20
+ # * / % left to right
21
+ # + - (binary) left to right
22
+ # & left to right
23
+ # | ^ left to right
24
+ # > >= < <= left to right
25
+ # <=> == === != =~ !~ not associative
26
+ # && left to right
27
+ # || left to right
28
+ # .. ... not associative
29
+ # ?: right to left
30
+ # = **= *= <<= >>= &&= &= ||= |= += -= /= ^= %= right to left
31
+ # , : not associative
32
+ # not right to left
33
+ # or and left to right
34
+ # ; left to right
35
+
36
+ prechigh
37
+ nonassoc NEGATIVE
38
+ left PERIOD AOPEN
39
+ right UPLUS NOT
40
+ right POWER
41
+ right UMINUS
42
+ left MULTIPLY DIVIDE MODULO
43
+ left PLUS MINUS
44
+ left GT GTE LT LTE
45
+ nonassoc EQUAL INEQUAL
46
+ left AND
47
+ left OR
48
+ right TERNARY
49
+ right ASSIGN
50
+ nonassoc COMMA COLON
51
+ left SEMICOLON NEWLINE
52
+ preclow
53
+ start document
54
+ rule
55
+ document: document document_unit { val[0].children.push(val[1]) }
56
+ | document_unit { result = Joiner.build :JOINER, val[0] }
57
+ document_unit: template | command_tag | block_tag | tag
58
+
59
+ template: TEMPLATE { result = val[0] }
60
+ tag: TOPEN TCLOSE { result = Tag.build :TAG, mode: TAG_MODES[val[0]] }
61
+ | TOPEN sequence TCLOSE { result = Tag.build :TAG, *val[1].flatten, mode: TAG_MODES[val[0]] }
62
+
63
+ command_tag: TOPEN assigned_command TCLOSE {
64
+ command = val[1][:command]
65
+ assign = val[1][:assign]
66
+ command.options[:mode] = TAG_MODES[val[0]]
67
+ command.options[:assign] = assign if assign
68
+ command.validate!
69
+ result = command
70
+ }
71
+ block_open: TOPEN assigned_block TCLOSE {
72
+ block = val[1][:block]
73
+ assign = val[1][:assign]
74
+ block.options[:mode] = TAG_MODES[val[0]]
75
+ block.options[:assign] = assign if assign
76
+ result = block
77
+ }
78
+ block_close: TOPEN endblock TCLOSE
79
+ block_body: block_body document_unit {
80
+ val[0][-1].is_a?(Joiner) ?
81
+ val[0][-1].children.push(val[1]) :
82
+ val[0].push(Joiner.build(:JOINER, val[1]))
83
+ }
84
+ | block_body subcommand_tag { val[0].push(val[1]) }
85
+ | document_unit { result = [Joiner.build(:JOINER, val[0])] }
86
+ | subcommand_tag { result = [val[0]] }
87
+ block_tag: block_open block_close
88
+ | block_open block_body block_close { val[0].options[:subnodes] = val[1]; val[0].validate! }
89
+ subcommand_tag: TOPEN subcommand TCLOSE { result = val[1] }
90
+
91
+ command: COMMAND { result = Command.build val[0] }
92
+ | COMMAND arguments { result = Command.build val[0], *val[1] }
93
+ assigned_command: command { result = { command: val[0] } }
94
+ | IDENTIFER ASSIGN command { result = { command: val[2], assign: val[0] } }
95
+ block: BLOCK { result = Block.build val[0] }
96
+ | BLOCK arguments { result = Block.build val[0], *val[1] }
97
+ assigned_block: block { result = { block: val[0] } }
98
+ | IDENTIFER ASSIGN block { result = { block: val[2], assign: val[0] } }
99
+ endblock: ENDBLOCK
100
+ | END BLOCK
101
+ subcommand: SUBCOMMAND { result = { name: val[0] } }
102
+ | SUBCOMMAND arguments { result = { name: val[0], args: Arrayer.build(:ARRAY, *val[1]) } }
103
+
104
+ sequence: sequence SEMICOLON sequence { result = val[0].push(val[2]) }
105
+ | sequence SEMICOLON
106
+ | SEMICOLON sequence { result = val[1] }
107
+ | SEMICOLON { result = [] }
108
+ | sequence NEWLINE sequence { result = val[0].push(val[2]) }
109
+ | sequence NEWLINE
110
+ | NEWLINE sequence { result = val[1] }
111
+ | NEWLINE { result = [] }
112
+ | expr { result = [val[0]] }
113
+
114
+ expr: expr MULTIPLY expr { result = Calculator.build :MULTIPLY, val[0], val[2] }
115
+ | expr POWER expr { result = Calculator.build :POWER, val[0], val[2] }
116
+ | expr DIVIDE expr { result = Calculator.build :DIVIDE, val[0], val[2] }
117
+ | expr PLUS expr { result = Calculator.build :PLUS, val[0], val[2] }
118
+ | expr MINUS expr { result = Calculator.build :MINUS, val[0], val[2] }
119
+ | expr MODULO expr { result = Calculator.build :MODULO, val[0], val[2] }
120
+ | MINUS expr =UMINUS { result = Calculator.build :UMINUS, val[1] }
121
+ | PLUS expr =UPLUS { result = Calculator.build :UPLUS, val[1] }
122
+ | expr AND expr { result = Calculator.build :AND, val[0], val[2] }
123
+ | expr OR expr { result = Calculator.build :OR, val[0], val[2] }
124
+ | expr GT expr { result = Calculator.build :GT, val[0], val[2] }
125
+ | expr GTE expr { result = Calculator.build :GTE, val[0], val[2] }
126
+ | expr LT expr { result = Calculator.build :LT, val[0], val[2] }
127
+ | expr LTE expr { result = Calculator.build :LTE, val[0], val[2] }
128
+ | expr EQUAL expr { result = Calculator.build :EQUAL, val[0], val[2] }
129
+ | expr INEQUAL expr { result = Calculator.build :INEQUAL, val[0], val[2] }
130
+ | NOT expr { result = Calculator.build :NOT, val[1] }
131
+ | IDENTIFER ASSIGN expr { result = Assigner.build val[0], val[2] }
132
+ | expr PERIOD method { val[2].children[0] = val[0]; result = val[2] }
133
+ | expr AOPEN arguments ACLOSE { result = Summoner.build val[0], '[]', *val[2] }
134
+ | POPEN PCLOSE { result = nil }
135
+ | POPEN sequence PCLOSE {
136
+ result = case val[1].size
137
+ when 0
138
+ nil
139
+ when 1
140
+ val[1][0]
141
+ else
142
+ Sequencer.build :SEQUENCE, *val[1].flatten
143
+ end
144
+ }
145
+ | value
146
+
147
+ value: const | number | string | array | hash | method# | ternary
148
+
149
+ const: NIL | TRUE | FALSE
150
+ number: INTEGER | FLOAT
151
+ string: STRING | REGEXP
152
+
153
+ # ternary: expr QUESTION expr COLON expr =TERNARY { result = Node.build :TERNARY, val[0], val[2], val[4] }
154
+
155
+ array: AOPEN ACLOSE { result = Arrayer.build :ARRAY }
156
+ | AOPEN params ACLOSE { result = Arrayer.build :ARRAY, *val[1] }
157
+ params: params COMMA expr { val[0].push(val[2]) }
158
+ | expr { result = [val[0]] }
159
+
160
+ hash: HOPEN HCLOSE { result = Hasher.build :HASH }
161
+ | HOPEN pairs HCLOSE { result = Hasher.build :HASH, *val[1] }
162
+ pairs: pairs COMMA pair { val[0].push(val[2]) }
163
+ | pair { result = [val[0]] }
164
+ pair: IDENTIFER COLON expr { result = Arrayer.build :PAIR, val[0], val[2] }
165
+
166
+ arguments: params COMMA pairs { result = [*val[0], Hasher.build(:HASH, *val[2])] }
167
+ | params
168
+ | pairs { result = Hasher.build(:HASH, *val[0]) }
169
+ method: IDENTIFER { result = Summoner.build nil, val[0] }
170
+ | IDENTIFER POPEN PCLOSE { result = Summoner.build nil, val[0] }
171
+ | IDENTIFER POPEN arguments PCLOSE { result = Summoner.build nil, val[0], *val[2] }
172
+
173
+ ---- header
174
+ require 'hotcell/lexer'
175
+ ---- inner
176
+ NEWLINE_PRED = Set.new(Lexer::BOPEN.values + Lexer::OPERATIONS.values)
177
+ NEWLINE_NEXT = Set.new(Lexer::BCLOSE.values + [:NEWLINE])
178
+
179
+ TAG_MODES = { '{{' => :normal, '{{!' => :silence }
180
+
181
+ def initialize string, options = {}
182
+ @lexer = Lexer.new(string)
183
+ @tokens = @lexer.tokens
184
+ @position = -1
185
+
186
+ @commands = Set.new(Array.wrap(options[:commands]).map(&:to_s))
187
+ @blocks = Set.new(Array.wrap(options[:blocks]).map(&:to_s))
188
+ @endblocks = Set.new(Array.wrap(options[:blocks]).map { |identifer| "end#{identifer}" })
189
+ @subcommands = Set.new(Array.wrap(options[:subcommands]).map(&:to_s))
190
+ end
191
+
192
+ def parse
193
+ if @tokens.size == 0
194
+ Joiner.build :JOINER
195
+ else
196
+ do_parse
197
+ end
198
+ end
199
+
200
+ def next_token
201
+ @position = @position + 1
202
+
203
+ tcurr = @tokens[@position]
204
+ tnext = @tokens[@position.next]
205
+ tpred = @tokens[@position.pred]
206
+
207
+ if tcurr && (tcurr[0] == :COMMENT || tcurr[0] == :NEWLINE && (
208
+ (tpred && NEWLINE_PRED.include?(tpred[0])) ||
209
+ (tnext && NEWLINE_NEXT.include?(tnext[0]))
210
+ ))
211
+ next_token
212
+ else
213
+ if tcurr && tcurr[0] == :IDENTIFER
214
+ if @commands.include?(tcurr[1])
215
+ [:COMMAND, tcurr[1]]
216
+ elsif @blocks.include?(tcurr[1])
217
+ [:BLOCK, tcurr[1]]
218
+ elsif @endblocks.include?(tcurr[1])
219
+ [:ENDBLOCK, tcurr[1]]
220
+ elsif @subcommands.include?(tcurr[1])
221
+ [:SUBCOMMAND, tcurr[1]]
222
+ elsif tcurr[1] == 'end'
223
+ [:END, tcurr[1]]
224
+ else
225
+ tcurr
226
+ end
227
+ else
228
+ tcurr || [false, false]
229
+ end
230
+ end
231
+ end
@@ -0,0 +1,57 @@
1
+ module Hotcell
2
+ class Scope
3
+ def initialize scope = {}
4
+ @scope = [scope]
5
+ end
6
+
7
+ def [] key
8
+ cache[key]
9
+ end
10
+
11
+ def []= key, value
12
+ clear_cache
13
+ hash = scope.reverse_each.detect { |hash| hash.key? key }
14
+ hash ||= scope.last
15
+ hash[key] = value
16
+ end
17
+
18
+ def key? key
19
+ cache.key?(key)
20
+ end
21
+
22
+ def push data
23
+ cache.merge!(data)
24
+ scope.push(data)
25
+ end
26
+
27
+ def pop
28
+ clear_cache
29
+ scope.pop if scope.size > 1
30
+ end
31
+
32
+ def scoped data
33
+ push(data)
34
+ yield
35
+ ensure
36
+ pop
37
+ end
38
+
39
+ private
40
+
41
+ def scope
42
+ @scope
43
+ end
44
+
45
+ def reduce
46
+ scope.inject({}, &:merge)
47
+ end
48
+
49
+ def cache
50
+ @cache ||= reduce
51
+ end
52
+
53
+ def clear_cache
54
+ @cache = nil
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,29 @@
1
+ module Hotcell
2
+ class Template
3
+ attr_reader :source, :options
4
+
5
+ def self.parse source
6
+ new source,
7
+ commands: Hotcell.commands.keys,
8
+ blocks: Hotcell.blocks.keys,
9
+ subcommands: Hotcell.subcommands.keys
10
+ end
11
+
12
+ def initialize source, options = {}
13
+ @source = source
14
+ @options = options
15
+ end
16
+
17
+ def syntax
18
+ @syntax ||= Parser.new(source, options.slice(:commands, :blocks, :subcommands)).parse
19
+ end
20
+
21
+ def render context = {}
22
+ if context.is_a?(Context)
23
+ syntax.render(context)
24
+ else
25
+ syntax.render(Context.new(context))
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,3 @@
1
+ module Hotcell
2
+ VERSION = "0.0.1"
3
+ end
data/lib/hotcell.rb ADDED
@@ -0,0 +1,19 @@
1
+ require 'active_support/all'
2
+ require 'hotcell/version'
3
+ require 'hotcell/config'
4
+ require 'hotcell/errors'
5
+ require 'hotcell/lexer'
6
+ require 'hotcell/parser'
7
+ require 'hotcell/node'
8
+ require 'hotcell/context'
9
+ require 'hotcell/manipulator'
10
+ require 'hotcell/extensions'
11
+ require 'hotcell/template'
12
+
13
+ module Hotcell
14
+ [:commands, :blocks, :subcommands, :register_command].each do |method|
15
+ define_singleton_method method do
16
+ Config.instance.send(method)
17
+ end
18
+ end
19
+ end