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,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