methic 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.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/.travis.yml +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +56 -0
- data/Rakefile +17 -0
- data/lib/methic.rb +727 -0
- data/lib/methic.tt +101 -0
- data/lib/methic/version.rb +3 -0
- data/lib/methic_node_classes.rb +9 -0
- data/methic.gemspec +24 -0
- data/test/methic_test.rb +54 -0
- data/test/test_helper.rb +18 -0
- metadata +102 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a28f40b0c1a99dc37830175725f8319468a46ab4
|
4
|
+
data.tar.gz: 7a96c2312722b4f3498aaaf6fbfe4142b8bb76f9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6908e75f68ea08980bda60ccf9814093095773c6f8cd20193de3a9c442d494e04117c1d9dcda0ea7725906efb474d06528db8e1f7248eff16da9061265a00372
|
7
|
+
data.tar.gz: 2e2b9d8f6550734dc81d2e6372b59275596d1faeafd44515e45c27872f34741f77b38e05f7638ed314f6da7ad05b41fec10cedd82cd68e3cd7c064e6e0362dde
|
data/.gitignore
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
data/.travis.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
language: ruby
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Josef Šimánek
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
# Methic [](https://travis-ci.org/zizkovrb/methic)
|
2
|
+
|
3
|
+
Simple arithmetic implemented by Treetop.
|
4
|
+
|
5
|
+
Heavily based on [treetop arithmetic example](https://github.com/cjheath/treetop/blob/master/examples/lambda_calculus/arithmetic.treetop).
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'methic'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install methic
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
parser = MethicParser.new
|
27
|
+
|
28
|
+
parser.parse('0').eval #=> 0
|
29
|
+
parser.parse('1').eval #=> 1
|
30
|
+
parser.parse('123').eval #=> 123
|
31
|
+
|
32
|
+
parser.parse('x').eval('x' => 0) #=> 0
|
33
|
+
parser.parse('x').eval('x' => 3) #=> 3
|
34
|
+
parser.parse('y').eval('y' => 10) #=> 10
|
35
|
+
|
36
|
+
parser.parse('x + 5').eval('x' => 5) #=> 10
|
37
|
+
parser.parse('x - 5').eval('x' => 5) #=> 0
|
38
|
+
parser.parse('x * 2').eval('x' => 3) #=> 6
|
39
|
+
parser.parse('x / 2').eval('x' => 6) #=> 3
|
40
|
+
|
41
|
+
parser.parse('1 + 2 * 3 + 4').eval #=> 11
|
42
|
+
parser.parse('5 - 2 - 1').eval #=> 2
|
43
|
+
|
44
|
+
parser.parse('(5 + x) * (10 - y)').eval('x' => 0, 'y' => 5) #=> 25
|
45
|
+
|
46
|
+
parser.parse('4 == 4').eval #=> true
|
47
|
+
parser.parse('4 == 3').eval #=> false
|
48
|
+
```
|
49
|
+
|
50
|
+
## Contributing
|
51
|
+
|
52
|
+
1. Fork it ( https://github.com/zizkovrb/methic/fork )
|
53
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
54
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
55
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
56
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require 'treetop'
|
3
|
+
require 'rake/testtask'
|
4
|
+
|
5
|
+
Rake::TestTask.new(:test) do |t|
|
6
|
+
t.libs << 'test'
|
7
|
+
t.pattern = 'test/**/*_test.rb'
|
8
|
+
end
|
9
|
+
|
10
|
+
desc 'Compile treetop to ruby'
|
11
|
+
task :compile do
|
12
|
+
compiler = Treetop::Compiler::GrammarCompiler.new
|
13
|
+
treetop_file = File.expand_path('../lib/methic.tt', __FILE__)
|
14
|
+
compiler.compile(treetop_file)
|
15
|
+
end
|
16
|
+
|
17
|
+
task :default => [:compile, :test]
|
data/lib/methic.rb
ADDED
@@ -0,0 +1,727 @@
|
|
1
|
+
# Autogenerated from a Treetop grammar. Edits may be lost.
|
2
|
+
|
3
|
+
|
4
|
+
require 'treetop'
|
5
|
+
require 'methic_node_classes'
|
6
|
+
|
7
|
+
module Methic
|
8
|
+
include Treetop::Runtime
|
9
|
+
|
10
|
+
def root
|
11
|
+
@root ||= :expression
|
12
|
+
end
|
13
|
+
|
14
|
+
def _nt_expression
|
15
|
+
start_index = index
|
16
|
+
if node_cache[:expression].has_key?(index)
|
17
|
+
cached = node_cache[:expression][index]
|
18
|
+
if cached
|
19
|
+
node_cache[:expression][index] = cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
|
20
|
+
@index = cached.interval.end
|
21
|
+
end
|
22
|
+
return cached
|
23
|
+
end
|
24
|
+
|
25
|
+
i0 = index
|
26
|
+
r1 = _nt_comparative
|
27
|
+
if r1
|
28
|
+
r1 = SyntaxNode.new(input, (index-1)...index) if r1 == true
|
29
|
+
r0 = r1
|
30
|
+
else
|
31
|
+
r2 = _nt_additive
|
32
|
+
if r2
|
33
|
+
r2 = SyntaxNode.new(input, (index-1)...index) if r2 == true
|
34
|
+
r0 = r2
|
35
|
+
else
|
36
|
+
@index = i0
|
37
|
+
r0 = nil
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
node_cache[:expression][start_index] = r0
|
42
|
+
|
43
|
+
r0
|
44
|
+
end
|
45
|
+
|
46
|
+
module Comparative0
|
47
|
+
def space1
|
48
|
+
elements[0]
|
49
|
+
end
|
50
|
+
|
51
|
+
def operator
|
52
|
+
elements[1]
|
53
|
+
end
|
54
|
+
|
55
|
+
def space2
|
56
|
+
elements[2]
|
57
|
+
end
|
58
|
+
|
59
|
+
def operand
|
60
|
+
elements[3]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
module Comparative1
|
65
|
+
def head
|
66
|
+
elements[0]
|
67
|
+
end
|
68
|
+
|
69
|
+
def tail
|
70
|
+
elements[1]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def _nt_comparative
|
75
|
+
start_index = index
|
76
|
+
if node_cache[:comparative].has_key?(index)
|
77
|
+
cached = node_cache[:comparative][index]
|
78
|
+
if cached
|
79
|
+
node_cache[:comparative][index] = cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
|
80
|
+
@index = cached.interval.end
|
81
|
+
end
|
82
|
+
return cached
|
83
|
+
end
|
84
|
+
|
85
|
+
i0, s0 = index, []
|
86
|
+
r1 = _nt_additive
|
87
|
+
s0 << r1
|
88
|
+
if r1
|
89
|
+
s2, i2 = [], index
|
90
|
+
loop do
|
91
|
+
i3, s3 = index, []
|
92
|
+
r4 = _nt_space
|
93
|
+
s3 << r4
|
94
|
+
if r4
|
95
|
+
r5 = _nt_equality_op
|
96
|
+
s3 << r5
|
97
|
+
if r5
|
98
|
+
r6 = _nt_space
|
99
|
+
s3 << r6
|
100
|
+
if r6
|
101
|
+
r7 = _nt_additive
|
102
|
+
s3 << r7
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
if s3.last
|
107
|
+
r3 = instantiate_node(SyntaxNode,input, i3...index, s3)
|
108
|
+
r3.extend(Comparative0)
|
109
|
+
else
|
110
|
+
@index = i3
|
111
|
+
r3 = nil
|
112
|
+
end
|
113
|
+
if r3
|
114
|
+
s2 << r3
|
115
|
+
else
|
116
|
+
break
|
117
|
+
end
|
118
|
+
end
|
119
|
+
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
|
120
|
+
s0 << r2
|
121
|
+
end
|
122
|
+
if s0.last
|
123
|
+
r0 = instantiate_node(BinaryOperation,input, i0...index, s0)
|
124
|
+
r0.extend(Comparative1)
|
125
|
+
else
|
126
|
+
@index = i0
|
127
|
+
r0 = nil
|
128
|
+
end
|
129
|
+
|
130
|
+
node_cache[:comparative][start_index] = r0
|
131
|
+
|
132
|
+
r0
|
133
|
+
end
|
134
|
+
|
135
|
+
module EqualityOp0
|
136
|
+
def apply(a, b)
|
137
|
+
a == b
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def _nt_equality_op
|
142
|
+
start_index = index
|
143
|
+
if node_cache[:equality_op].has_key?(index)
|
144
|
+
cached = node_cache[:equality_op][index]
|
145
|
+
if cached
|
146
|
+
node_cache[:equality_op][index] = cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
|
147
|
+
@index = cached.interval.end
|
148
|
+
end
|
149
|
+
return cached
|
150
|
+
end
|
151
|
+
|
152
|
+
if (match_len = has_terminal?('==', false, index))
|
153
|
+
r0 = instantiate_node(SyntaxNode,input, index...(index + match_len))
|
154
|
+
r0.extend(EqualityOp0)
|
155
|
+
@index += match_len
|
156
|
+
else
|
157
|
+
terminal_parse_failure('==')
|
158
|
+
r0 = nil
|
159
|
+
end
|
160
|
+
|
161
|
+
node_cache[:equality_op][start_index] = r0
|
162
|
+
|
163
|
+
r0
|
164
|
+
end
|
165
|
+
|
166
|
+
module Additive0
|
167
|
+
def space1
|
168
|
+
elements[0]
|
169
|
+
end
|
170
|
+
|
171
|
+
def operator
|
172
|
+
elements[1]
|
173
|
+
end
|
174
|
+
|
175
|
+
def space2
|
176
|
+
elements[2]
|
177
|
+
end
|
178
|
+
|
179
|
+
def operand
|
180
|
+
elements[3]
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
module Additive1
|
185
|
+
def head
|
186
|
+
elements[0]
|
187
|
+
end
|
188
|
+
|
189
|
+
def tail
|
190
|
+
elements[1]
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def _nt_additive
|
195
|
+
start_index = index
|
196
|
+
if node_cache[:additive].has_key?(index)
|
197
|
+
cached = node_cache[:additive][index]
|
198
|
+
if cached
|
199
|
+
node_cache[:additive][index] = cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
|
200
|
+
@index = cached.interval.end
|
201
|
+
end
|
202
|
+
return cached
|
203
|
+
end
|
204
|
+
|
205
|
+
i0, s0 = index, []
|
206
|
+
r1 = _nt_multitive
|
207
|
+
s0 << r1
|
208
|
+
if r1
|
209
|
+
s2, i2 = [], index
|
210
|
+
loop do
|
211
|
+
i3, s3 = index, []
|
212
|
+
r4 = _nt_space
|
213
|
+
s3 << r4
|
214
|
+
if r4
|
215
|
+
r5 = _nt_additive_op
|
216
|
+
s3 << r5
|
217
|
+
if r5
|
218
|
+
r6 = _nt_space
|
219
|
+
s3 << r6
|
220
|
+
if r6
|
221
|
+
r7 = _nt_multitive
|
222
|
+
s3 << r7
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
if s3.last
|
227
|
+
r3 = instantiate_node(SyntaxNode,input, i3...index, s3)
|
228
|
+
r3.extend(Additive0)
|
229
|
+
else
|
230
|
+
@index = i3
|
231
|
+
r3 = nil
|
232
|
+
end
|
233
|
+
if r3
|
234
|
+
s2 << r3
|
235
|
+
else
|
236
|
+
break
|
237
|
+
end
|
238
|
+
end
|
239
|
+
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
|
240
|
+
s0 << r2
|
241
|
+
end
|
242
|
+
if s0.last
|
243
|
+
r0 = instantiate_node(BinaryOperation,input, i0...index, s0)
|
244
|
+
r0.extend(Additive1)
|
245
|
+
else
|
246
|
+
@index = i0
|
247
|
+
r0 = nil
|
248
|
+
end
|
249
|
+
|
250
|
+
node_cache[:additive][start_index] = r0
|
251
|
+
|
252
|
+
r0
|
253
|
+
end
|
254
|
+
|
255
|
+
module AdditiveOp0
|
256
|
+
def apply(a, b)
|
257
|
+
a + b
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
module AdditiveOp1
|
262
|
+
def apply(a, b)
|
263
|
+
a - b
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
def _nt_additive_op
|
268
|
+
start_index = index
|
269
|
+
if node_cache[:additive_op].has_key?(index)
|
270
|
+
cached = node_cache[:additive_op][index]
|
271
|
+
if cached
|
272
|
+
node_cache[:additive_op][index] = cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
|
273
|
+
@index = cached.interval.end
|
274
|
+
end
|
275
|
+
return cached
|
276
|
+
end
|
277
|
+
|
278
|
+
i0 = index
|
279
|
+
if (match_len = has_terminal?('+', false, index))
|
280
|
+
r1 = instantiate_node(SyntaxNode,input, index...(index + match_len))
|
281
|
+
r1.extend(AdditiveOp0)
|
282
|
+
@index += match_len
|
283
|
+
else
|
284
|
+
terminal_parse_failure('+')
|
285
|
+
r1 = nil
|
286
|
+
end
|
287
|
+
if r1
|
288
|
+
r1 = SyntaxNode.new(input, (index-1)...index) if r1 == true
|
289
|
+
r0 = r1
|
290
|
+
else
|
291
|
+
if (match_len = has_terminal?('-', false, index))
|
292
|
+
r2 = instantiate_node(SyntaxNode,input, index...(index + match_len))
|
293
|
+
r2.extend(AdditiveOp1)
|
294
|
+
@index += match_len
|
295
|
+
else
|
296
|
+
terminal_parse_failure('-')
|
297
|
+
r2 = nil
|
298
|
+
end
|
299
|
+
if r2
|
300
|
+
r2 = SyntaxNode.new(input, (index-1)...index) if r2 == true
|
301
|
+
r0 = r2
|
302
|
+
else
|
303
|
+
@index = i0
|
304
|
+
r0 = nil
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
node_cache[:additive_op][start_index] = r0
|
309
|
+
|
310
|
+
r0
|
311
|
+
end
|
312
|
+
|
313
|
+
module Multitive0
|
314
|
+
def space1
|
315
|
+
elements[0]
|
316
|
+
end
|
317
|
+
|
318
|
+
def operator
|
319
|
+
elements[1]
|
320
|
+
end
|
321
|
+
|
322
|
+
def space2
|
323
|
+
elements[2]
|
324
|
+
end
|
325
|
+
|
326
|
+
def operand
|
327
|
+
elements[3]
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
module Multitive1
|
332
|
+
def head
|
333
|
+
elements[0]
|
334
|
+
end
|
335
|
+
|
336
|
+
def tail
|
337
|
+
elements[1]
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
def _nt_multitive
|
342
|
+
start_index = index
|
343
|
+
if node_cache[:multitive].has_key?(index)
|
344
|
+
cached = node_cache[:multitive][index]
|
345
|
+
if cached
|
346
|
+
node_cache[:multitive][index] = cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
|
347
|
+
@index = cached.interval.end
|
348
|
+
end
|
349
|
+
return cached
|
350
|
+
end
|
351
|
+
|
352
|
+
i0, s0 = index, []
|
353
|
+
r1 = _nt_primary
|
354
|
+
s0 << r1
|
355
|
+
if r1
|
356
|
+
s2, i2 = [], index
|
357
|
+
loop do
|
358
|
+
i3, s3 = index, []
|
359
|
+
r4 = _nt_space
|
360
|
+
s3 << r4
|
361
|
+
if r4
|
362
|
+
r5 = _nt_multitive_op
|
363
|
+
s3 << r5
|
364
|
+
if r5
|
365
|
+
r6 = _nt_space
|
366
|
+
s3 << r6
|
367
|
+
if r6
|
368
|
+
r7 = _nt_primary
|
369
|
+
s3 << r7
|
370
|
+
end
|
371
|
+
end
|
372
|
+
end
|
373
|
+
if s3.last
|
374
|
+
r3 = instantiate_node(SyntaxNode,input, i3...index, s3)
|
375
|
+
r3.extend(Multitive0)
|
376
|
+
else
|
377
|
+
@index = i3
|
378
|
+
r3 = nil
|
379
|
+
end
|
380
|
+
if r3
|
381
|
+
s2 << r3
|
382
|
+
else
|
383
|
+
break
|
384
|
+
end
|
385
|
+
end
|
386
|
+
r2 = instantiate_node(SyntaxNode,input, i2...index, s2)
|
387
|
+
s0 << r2
|
388
|
+
end
|
389
|
+
if s0.last
|
390
|
+
r0 = instantiate_node(BinaryOperation,input, i0...index, s0)
|
391
|
+
r0.extend(Multitive1)
|
392
|
+
else
|
393
|
+
@index = i0
|
394
|
+
r0 = nil
|
395
|
+
end
|
396
|
+
|
397
|
+
node_cache[:multitive][start_index] = r0
|
398
|
+
|
399
|
+
r0
|
400
|
+
end
|
401
|
+
|
402
|
+
module MultitiveOp0
|
403
|
+
def apply(a, b)
|
404
|
+
a * b
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
module MultitiveOp1
|
409
|
+
def apply(a, b)
|
410
|
+
a / b
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
414
|
+
def _nt_multitive_op
|
415
|
+
start_index = index
|
416
|
+
if node_cache[:multitive_op].has_key?(index)
|
417
|
+
cached = node_cache[:multitive_op][index]
|
418
|
+
if cached
|
419
|
+
node_cache[:multitive_op][index] = cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
|
420
|
+
@index = cached.interval.end
|
421
|
+
end
|
422
|
+
return cached
|
423
|
+
end
|
424
|
+
|
425
|
+
i0 = index
|
426
|
+
if (match_len = has_terminal?('*', false, index))
|
427
|
+
r1 = instantiate_node(SyntaxNode,input, index...(index + match_len))
|
428
|
+
r1.extend(MultitiveOp0)
|
429
|
+
@index += match_len
|
430
|
+
else
|
431
|
+
terminal_parse_failure('*')
|
432
|
+
r1 = nil
|
433
|
+
end
|
434
|
+
if r1
|
435
|
+
r1 = SyntaxNode.new(input, (index-1)...index) if r1 == true
|
436
|
+
r0 = r1
|
437
|
+
else
|
438
|
+
if (match_len = has_terminal?('/', false, index))
|
439
|
+
r2 = instantiate_node(SyntaxNode,input, index...(index + match_len))
|
440
|
+
r2.extend(MultitiveOp1)
|
441
|
+
@index += match_len
|
442
|
+
else
|
443
|
+
terminal_parse_failure('/')
|
444
|
+
r2 = nil
|
445
|
+
end
|
446
|
+
if r2
|
447
|
+
r2 = SyntaxNode.new(input, (index-1)...index) if r2 == true
|
448
|
+
r0 = r2
|
449
|
+
else
|
450
|
+
@index = i0
|
451
|
+
r0 = nil
|
452
|
+
end
|
453
|
+
end
|
454
|
+
|
455
|
+
node_cache[:multitive_op][start_index] = r0
|
456
|
+
|
457
|
+
r0
|
458
|
+
end
|
459
|
+
|
460
|
+
module Primary0
|
461
|
+
def space1
|
462
|
+
elements[1]
|
463
|
+
end
|
464
|
+
|
465
|
+
def expression
|
466
|
+
elements[2]
|
467
|
+
end
|
468
|
+
|
469
|
+
def space2
|
470
|
+
elements[3]
|
471
|
+
end
|
472
|
+
|
473
|
+
end
|
474
|
+
|
475
|
+
module Primary1
|
476
|
+
def eval(env={})
|
477
|
+
expression.eval(env)
|
478
|
+
end
|
479
|
+
end
|
480
|
+
|
481
|
+
def _nt_primary
|
482
|
+
start_index = index
|
483
|
+
if node_cache[:primary].has_key?(index)
|
484
|
+
cached = node_cache[:primary][index]
|
485
|
+
if cached
|
486
|
+
node_cache[:primary][index] = cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
|
487
|
+
@index = cached.interval.end
|
488
|
+
end
|
489
|
+
return cached
|
490
|
+
end
|
491
|
+
|
492
|
+
i0 = index
|
493
|
+
r1 = _nt_variable
|
494
|
+
if r1
|
495
|
+
r1 = SyntaxNode.new(input, (index-1)...index) if r1 == true
|
496
|
+
r0 = r1
|
497
|
+
else
|
498
|
+
r2 = _nt_number
|
499
|
+
if r2
|
500
|
+
r2 = SyntaxNode.new(input, (index-1)...index) if r2 == true
|
501
|
+
r0 = r2
|
502
|
+
else
|
503
|
+
i3, s3 = index, []
|
504
|
+
if (match_len = has_terminal?('(', false, index))
|
505
|
+
r4 = true
|
506
|
+
@index += match_len
|
507
|
+
else
|
508
|
+
terminal_parse_failure('(')
|
509
|
+
r4 = nil
|
510
|
+
end
|
511
|
+
s3 << r4
|
512
|
+
if r4
|
513
|
+
r5 = _nt_space
|
514
|
+
s3 << r5
|
515
|
+
if r5
|
516
|
+
r6 = _nt_expression
|
517
|
+
s3 << r6
|
518
|
+
if r6
|
519
|
+
r7 = _nt_space
|
520
|
+
s3 << r7
|
521
|
+
if r7
|
522
|
+
if (match_len = has_terminal?(')', false, index))
|
523
|
+
r8 = true
|
524
|
+
@index += match_len
|
525
|
+
else
|
526
|
+
terminal_parse_failure(')')
|
527
|
+
r8 = nil
|
528
|
+
end
|
529
|
+
s3 << r8
|
530
|
+
end
|
531
|
+
end
|
532
|
+
end
|
533
|
+
end
|
534
|
+
if s3.last
|
535
|
+
r3 = instantiate_node(SyntaxNode,input, i3...index, s3)
|
536
|
+
r3.extend(Primary0)
|
537
|
+
r3.extend(Primary1)
|
538
|
+
else
|
539
|
+
@index = i3
|
540
|
+
r3 = nil
|
541
|
+
end
|
542
|
+
if r3
|
543
|
+
r3 = SyntaxNode.new(input, (index-1)...index) if r3 == true
|
544
|
+
r0 = r3
|
545
|
+
else
|
546
|
+
@index = i0
|
547
|
+
r0 = nil
|
548
|
+
end
|
549
|
+
end
|
550
|
+
end
|
551
|
+
|
552
|
+
node_cache[:primary][start_index] = r0
|
553
|
+
|
554
|
+
r0
|
555
|
+
end
|
556
|
+
|
557
|
+
module Variable0
|
558
|
+
def eval(env={})
|
559
|
+
env[name]
|
560
|
+
end
|
561
|
+
|
562
|
+
def name
|
563
|
+
text_value
|
564
|
+
end
|
565
|
+
end
|
566
|
+
|
567
|
+
def _nt_variable
|
568
|
+
start_index = index
|
569
|
+
if node_cache[:variable].has_key?(index)
|
570
|
+
cached = node_cache[:variable][index]
|
571
|
+
if cached
|
572
|
+
node_cache[:variable][index] = cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
|
573
|
+
@index = cached.interval.end
|
574
|
+
end
|
575
|
+
return cached
|
576
|
+
end
|
577
|
+
|
578
|
+
s0, i0 = [], index
|
579
|
+
loop do
|
580
|
+
if has_terminal?(@regexps[gr = '\A[a-z]'] ||= Regexp.new(gr), :regexp, index)
|
581
|
+
r1 = true
|
582
|
+
@index += 1
|
583
|
+
else
|
584
|
+
terminal_parse_failure('[a-z]')
|
585
|
+
r1 = nil
|
586
|
+
end
|
587
|
+
if r1
|
588
|
+
s0 << r1
|
589
|
+
else
|
590
|
+
break
|
591
|
+
end
|
592
|
+
end
|
593
|
+
if s0.empty?
|
594
|
+
@index = i0
|
595
|
+
r0 = nil
|
596
|
+
else
|
597
|
+
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
|
598
|
+
r0.extend(Variable0)
|
599
|
+
end
|
600
|
+
|
601
|
+
node_cache[:variable][start_index] = r0
|
602
|
+
|
603
|
+
r0
|
604
|
+
end
|
605
|
+
|
606
|
+
module Number0
|
607
|
+
end
|
608
|
+
|
609
|
+
module Number1
|
610
|
+
def eval(env={})
|
611
|
+
text_value.to_i
|
612
|
+
end
|
613
|
+
end
|
614
|
+
|
615
|
+
def _nt_number
|
616
|
+
start_index = index
|
617
|
+
if node_cache[:number].has_key?(index)
|
618
|
+
cached = node_cache[:number][index]
|
619
|
+
if cached
|
620
|
+
node_cache[:number][index] = cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
|
621
|
+
@index = cached.interval.end
|
622
|
+
end
|
623
|
+
return cached
|
624
|
+
end
|
625
|
+
|
626
|
+
i0 = index
|
627
|
+
i1, s1 = index, []
|
628
|
+
if has_terminal?(@regexps[gr = '\A[1-9]'] ||= Regexp.new(gr), :regexp, index)
|
629
|
+
r2 = true
|
630
|
+
@index += 1
|
631
|
+
else
|
632
|
+
terminal_parse_failure('[1-9]')
|
633
|
+
r2 = nil
|
634
|
+
end
|
635
|
+
s1 << r2
|
636
|
+
if r2
|
637
|
+
s3, i3 = [], index
|
638
|
+
loop do
|
639
|
+
if has_terminal?(@regexps[gr = '\A[0-9]'] ||= Regexp.new(gr), :regexp, index)
|
640
|
+
r4 = true
|
641
|
+
@index += 1
|
642
|
+
else
|
643
|
+
terminal_parse_failure('[0-9]')
|
644
|
+
r4 = nil
|
645
|
+
end
|
646
|
+
if r4
|
647
|
+
s3 << r4
|
648
|
+
else
|
649
|
+
break
|
650
|
+
end
|
651
|
+
end
|
652
|
+
r3 = instantiate_node(SyntaxNode,input, i3...index, s3)
|
653
|
+
s1 << r3
|
654
|
+
end
|
655
|
+
if s1.last
|
656
|
+
r1 = instantiate_node(SyntaxNode,input, i1...index, s1)
|
657
|
+
r1.extend(Number0)
|
658
|
+
else
|
659
|
+
@index = i1
|
660
|
+
r1 = nil
|
661
|
+
end
|
662
|
+
if r1
|
663
|
+
r1 = SyntaxNode.new(input, (index-1)...index) if r1 == true
|
664
|
+
r0 = r1
|
665
|
+
r0.extend(Number1)
|
666
|
+
else
|
667
|
+
if (match_len = has_terminal?('0', false, index))
|
668
|
+
r5 = true
|
669
|
+
@index += match_len
|
670
|
+
else
|
671
|
+
terminal_parse_failure('0')
|
672
|
+
r5 = nil
|
673
|
+
end
|
674
|
+
if r5
|
675
|
+
r5 = SyntaxNode.new(input, (index-1)...index) if r5 == true
|
676
|
+
r0 = r5
|
677
|
+
r0.extend(Number1)
|
678
|
+
else
|
679
|
+
@index = i0
|
680
|
+
r0 = nil
|
681
|
+
end
|
682
|
+
end
|
683
|
+
|
684
|
+
node_cache[:number][start_index] = r0
|
685
|
+
|
686
|
+
r0
|
687
|
+
end
|
688
|
+
|
689
|
+
def _nt_space
|
690
|
+
start_index = index
|
691
|
+
if node_cache[:space].has_key?(index)
|
692
|
+
cached = node_cache[:space][index]
|
693
|
+
if cached
|
694
|
+
node_cache[:space][index] = cached = SyntaxNode.new(input, index...(index + 1)) if cached == true
|
695
|
+
@index = cached.interval.end
|
696
|
+
end
|
697
|
+
return cached
|
698
|
+
end
|
699
|
+
|
700
|
+
s0, i0 = [], index
|
701
|
+
loop do
|
702
|
+
if (match_len = has_terminal?(' ', false, index))
|
703
|
+
r1 = true
|
704
|
+
@index += match_len
|
705
|
+
else
|
706
|
+
terminal_parse_failure(' ')
|
707
|
+
r1 = nil
|
708
|
+
end
|
709
|
+
if r1
|
710
|
+
s0 << r1
|
711
|
+
else
|
712
|
+
break
|
713
|
+
end
|
714
|
+
end
|
715
|
+
r0 = instantiate_node(SyntaxNode,input, i0...index, s0)
|
716
|
+
|
717
|
+
node_cache[:space][start_index] = r0
|
718
|
+
|
719
|
+
r0
|
720
|
+
end
|
721
|
+
|
722
|
+
end
|
723
|
+
|
724
|
+
class MethicParser < Treetop::Runtime::CompiledParser
|
725
|
+
include Methic
|
726
|
+
end
|
727
|
+
|
data/lib/methic.tt
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'treetop'
|
2
|
+
require 'methic_node_classes'
|
3
|
+
|
4
|
+
grammar Methic
|
5
|
+
rule expression
|
6
|
+
comparative / additive
|
7
|
+
end
|
8
|
+
|
9
|
+
rule comparative
|
10
|
+
head:additive
|
11
|
+
tail:(
|
12
|
+
space operator:equality_op
|
13
|
+
space operand:additive)* <BinaryOperation>
|
14
|
+
end
|
15
|
+
|
16
|
+
rule equality_op
|
17
|
+
'==' {
|
18
|
+
def apply(a, b)
|
19
|
+
a == b
|
20
|
+
end
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
rule additive
|
25
|
+
head:multitive
|
26
|
+
tail:(
|
27
|
+
space operator:additive_op
|
28
|
+
space operand:multitive)* <BinaryOperation>
|
29
|
+
end
|
30
|
+
|
31
|
+
rule additive_op
|
32
|
+
'+' {
|
33
|
+
def apply(a, b)
|
34
|
+
a + b
|
35
|
+
end
|
36
|
+
}
|
37
|
+
/
|
38
|
+
'-' {
|
39
|
+
def apply(a, b)
|
40
|
+
a - b
|
41
|
+
end
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
rule multitive
|
46
|
+
head:primary
|
47
|
+
tail:(
|
48
|
+
space operator:multitive_op
|
49
|
+
space operand:primary)* <BinaryOperation>
|
50
|
+
end
|
51
|
+
|
52
|
+
rule multitive_op
|
53
|
+
'*' {
|
54
|
+
def apply(a, b)
|
55
|
+
a * b
|
56
|
+
end
|
57
|
+
}
|
58
|
+
/
|
59
|
+
'/' {
|
60
|
+
def apply(a, b)
|
61
|
+
a / b
|
62
|
+
end
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
rule primary
|
67
|
+
variable
|
68
|
+
/
|
69
|
+
number
|
70
|
+
/
|
71
|
+
'(' space expression space ')' {
|
72
|
+
def eval(env={})
|
73
|
+
expression.eval(env)
|
74
|
+
end
|
75
|
+
}
|
76
|
+
end
|
77
|
+
|
78
|
+
rule variable
|
79
|
+
[a-z]+ {
|
80
|
+
def eval(env={})
|
81
|
+
env[name]
|
82
|
+
end
|
83
|
+
|
84
|
+
def name
|
85
|
+
text_value
|
86
|
+
end
|
87
|
+
}
|
88
|
+
end
|
89
|
+
|
90
|
+
rule number
|
91
|
+
([1-9] [0-9]* / '0') {
|
92
|
+
def eval(env={})
|
93
|
+
text_value.to_i
|
94
|
+
end
|
95
|
+
}
|
96
|
+
end
|
97
|
+
|
98
|
+
rule space
|
99
|
+
' '*
|
100
|
+
end
|
101
|
+
end
|
data/methic.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'methic/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "methic"
|
8
|
+
spec.version = Methic::VERSION
|
9
|
+
spec.authors = ["Josef Šimánek"]
|
10
|
+
spec.email = ["josef.simanek@gmail.com"]
|
11
|
+
spec.summary = %q{Simple arithmetic implemented by Treetop.}
|
12
|
+
spec.homepage = "https://github.com/zizkovrb/methic"
|
13
|
+
spec.license = "MIT"
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0")
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_dependency "treetop"
|
21
|
+
|
22
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
23
|
+
spec.add_development_dependency "rake"
|
24
|
+
end
|
data/test/methic_test.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class MethicParserTest < Minitest::Test
|
4
|
+
include ParserTestHelper
|
5
|
+
|
6
|
+
def setup
|
7
|
+
@parser = MethicParser.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_number
|
11
|
+
assert_equal 0, parse('0').eval
|
12
|
+
assert_equal 1, parse('1').eval
|
13
|
+
assert_equal 123, parse('123').eval
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_variable
|
17
|
+
assert_equal 0, parse('x').eval('x' => 0)
|
18
|
+
assert_equal 3, parse('x').eval('x' => 3)
|
19
|
+
assert_equal 10, parse('y').eval('y' => 10)
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_addition
|
23
|
+
assert_equal 10, parse('x + 5').eval('x' => 5)
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_subtraction
|
27
|
+
assert_equal 0, parse('x - 5').eval('x' => 5)
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_multiplication
|
31
|
+
assert_equal 6, parse('x * 2').eval('x' => 3)
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_division
|
35
|
+
assert_equal 3, parse('x / 2').eval('x' => 6)
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_order_of_operations
|
39
|
+
assert_equal 11, parse('1 + 2 * 3 + 4').eval
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_left_to_right
|
43
|
+
assert_equal 2, parse('5 - 2 - 1').eval
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_parentheses
|
47
|
+
assert_equal 25, parse('(5 + x) * (10 - y)').eval('x' => 0, 'y' => 5)
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_equality
|
51
|
+
assert parse('4 == 4').eval
|
52
|
+
assert !parse('4 == 3').eval
|
53
|
+
end
|
54
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
require 'methic'
|
4
|
+
|
5
|
+
module ParserTestHelper
|
6
|
+
def assert_evals_to_self(input)
|
7
|
+
assert_evals_to(input, input)
|
8
|
+
end
|
9
|
+
|
10
|
+
def parse(input)
|
11
|
+
result = @parser.parse(input)
|
12
|
+
unless result
|
13
|
+
puts @parser.terminal_failures.join("\n")
|
14
|
+
end
|
15
|
+
assert !result.nil?
|
16
|
+
result
|
17
|
+
end
|
18
|
+
end
|
metadata
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: methic
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Josef Šimánek
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-05-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: treetop
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: minitest
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '5.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '5.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description:
|
56
|
+
email:
|
57
|
+
- josef.simanek@gmail.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- .gitignore
|
63
|
+
- .travis.yml
|
64
|
+
- Gemfile
|
65
|
+
- LICENSE.txt
|
66
|
+
- README.md
|
67
|
+
- Rakefile
|
68
|
+
- lib/methic.rb
|
69
|
+
- lib/methic.tt
|
70
|
+
- lib/methic/version.rb
|
71
|
+
- lib/methic_node_classes.rb
|
72
|
+
- methic.gemspec
|
73
|
+
- test/methic_test.rb
|
74
|
+
- test/test_helper.rb
|
75
|
+
homepage: https://github.com/zizkovrb/methic
|
76
|
+
licenses:
|
77
|
+
- MIT
|
78
|
+
metadata: {}
|
79
|
+
post_install_message:
|
80
|
+
rdoc_options: []
|
81
|
+
require_paths:
|
82
|
+
- lib
|
83
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - '>='
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
requirements: []
|
94
|
+
rubyforge_project:
|
95
|
+
rubygems_version: 2.2.1
|
96
|
+
signing_key:
|
97
|
+
specification_version: 4
|
98
|
+
summary: Simple arithmetic implemented by Treetop.
|
99
|
+
test_files:
|
100
|
+
- test/methic_test.rb
|
101
|
+
- test/test_helper.rb
|
102
|
+
has_rdoc:
|