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