hayadentaku 3.5.7

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 (132) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/rspec.yml +26 -0
  3. data/.github/workflows/rubocop.yml +14 -0
  4. data/.gitignore +14 -0
  5. data/.pryrc +2 -0
  6. data/.rubocop.yml +114 -0
  7. data/.travis.yml +10 -0
  8. data/CHANGELOG.md +328 -0
  9. data/Gemfile +4 -0
  10. data/LICENSE +21 -0
  11. data/README.md +352 -0
  12. data/Rakefile +31 -0
  13. data/hayadentaku.gemspec +35 -0
  14. data/lib/dentaku/ast/access.rb +44 -0
  15. data/lib/dentaku/ast/arithmetic.rb +292 -0
  16. data/lib/dentaku/ast/array.rb +38 -0
  17. data/lib/dentaku/ast/bitwise.rb +42 -0
  18. data/lib/dentaku/ast/case/case_conditional.rb +38 -0
  19. data/lib/dentaku/ast/case/case_else.rb +35 -0
  20. data/lib/dentaku/ast/case/case_switch_variable.rb +35 -0
  21. data/lib/dentaku/ast/case/case_then.rb +35 -0
  22. data/lib/dentaku/ast/case/case_when.rb +39 -0
  23. data/lib/dentaku/ast/case.rb +93 -0
  24. data/lib/dentaku/ast/combinators.rb +50 -0
  25. data/lib/dentaku/ast/comparators.rb +88 -0
  26. data/lib/dentaku/ast/datetime.rb +8 -0
  27. data/lib/dentaku/ast/function.rb +56 -0
  28. data/lib/dentaku/ast/function_registry.rb +107 -0
  29. data/lib/dentaku/ast/functions/abs.rb +5 -0
  30. data/lib/dentaku/ast/functions/all.rb +19 -0
  31. data/lib/dentaku/ast/functions/and.rb +25 -0
  32. data/lib/dentaku/ast/functions/any.rb +19 -0
  33. data/lib/dentaku/ast/functions/avg.rb +13 -0
  34. data/lib/dentaku/ast/functions/count.rb +26 -0
  35. data/lib/dentaku/ast/functions/duration.rb +51 -0
  36. data/lib/dentaku/ast/functions/enum.rb +54 -0
  37. data/lib/dentaku/ast/functions/filter.rb +21 -0
  38. data/lib/dentaku/ast/functions/if.rb +47 -0
  39. data/lib/dentaku/ast/functions/intercept.rb +33 -0
  40. data/lib/dentaku/ast/functions/map.rb +19 -0
  41. data/lib/dentaku/ast/functions/max.rb +5 -0
  42. data/lib/dentaku/ast/functions/min.rb +5 -0
  43. data/lib/dentaku/ast/functions/mul.rb +12 -0
  44. data/lib/dentaku/ast/functions/not.rb +5 -0
  45. data/lib/dentaku/ast/functions/or.rb +25 -0
  46. data/lib/dentaku/ast/functions/pluck.rb +34 -0
  47. data/lib/dentaku/ast/functions/reduce.rb +60 -0
  48. data/lib/dentaku/ast/functions/round.rb +5 -0
  49. data/lib/dentaku/ast/functions/rounddown.rb +8 -0
  50. data/lib/dentaku/ast/functions/roundup.rb +8 -0
  51. data/lib/dentaku/ast/functions/ruby_math.rb +57 -0
  52. data/lib/dentaku/ast/functions/string_functions.rb +212 -0
  53. data/lib/dentaku/ast/functions/sum.rb +12 -0
  54. data/lib/dentaku/ast/functions/switch.rb +8 -0
  55. data/lib/dentaku/ast/functions/xor.rb +44 -0
  56. data/lib/dentaku/ast/grouping.rb +23 -0
  57. data/lib/dentaku/ast/identifier.rb +52 -0
  58. data/lib/dentaku/ast/literal.rb +30 -0
  59. data/lib/dentaku/ast/logical.rb +8 -0
  60. data/lib/dentaku/ast/negation.rb +54 -0
  61. data/lib/dentaku/ast/nil.rb +13 -0
  62. data/lib/dentaku/ast/node.rb +29 -0
  63. data/lib/dentaku/ast/numeric.rb +8 -0
  64. data/lib/dentaku/ast/operation.rb +44 -0
  65. data/lib/dentaku/ast/string.rb +15 -0
  66. data/lib/dentaku/ast.rb +42 -0
  67. data/lib/dentaku/bulk_expression_solver.rb +158 -0
  68. data/lib/dentaku/calculator.rb +192 -0
  69. data/lib/dentaku/date_arithmetic.rb +60 -0
  70. data/lib/dentaku/dependency_resolver.rb +29 -0
  71. data/lib/dentaku/exceptions.rb +116 -0
  72. data/lib/dentaku/flat_hash.rb +161 -0
  73. data/lib/dentaku/parser.rb +318 -0
  74. data/lib/dentaku/print_visitor.rb +112 -0
  75. data/lib/dentaku/string_casing.rb +7 -0
  76. data/lib/dentaku/token.rb +48 -0
  77. data/lib/dentaku/token_matcher.rb +138 -0
  78. data/lib/dentaku/token_matchers.rb +29 -0
  79. data/lib/dentaku/token_scanner.rb +240 -0
  80. data/lib/dentaku/tokenizer.rb +127 -0
  81. data/lib/dentaku/version.rb +3 -0
  82. data/lib/dentaku/visitor/infix.rb +86 -0
  83. data/lib/dentaku.rb +69 -0
  84. data/spec/ast/abs_spec.rb +26 -0
  85. data/spec/ast/addition_spec.rb +67 -0
  86. data/spec/ast/all_spec.rb +38 -0
  87. data/spec/ast/and_function_spec.rb +35 -0
  88. data/spec/ast/and_spec.rb +32 -0
  89. data/spec/ast/any_spec.rb +36 -0
  90. data/spec/ast/arithmetic_spec.rb +147 -0
  91. data/spec/ast/avg_spec.rb +42 -0
  92. data/spec/ast/case_spec.rb +84 -0
  93. data/spec/ast/comparator_spec.rb +87 -0
  94. data/spec/ast/count_spec.rb +40 -0
  95. data/spec/ast/division_spec.rb +64 -0
  96. data/spec/ast/filter_spec.rb +25 -0
  97. data/spec/ast/function_spec.rb +69 -0
  98. data/spec/ast/intercept_spec.rb +30 -0
  99. data/spec/ast/map_spec.rb +40 -0
  100. data/spec/ast/max_spec.rb +33 -0
  101. data/spec/ast/min_spec.rb +33 -0
  102. data/spec/ast/mul_spec.rb +43 -0
  103. data/spec/ast/negation_spec.rb +48 -0
  104. data/spec/ast/node_spec.rb +43 -0
  105. data/spec/ast/numeric_spec.rb +16 -0
  106. data/spec/ast/or_spec.rb +35 -0
  107. data/spec/ast/pluck_spec.rb +49 -0
  108. data/spec/ast/reduce_spec.rb +22 -0
  109. data/spec/ast/round_spec.rb +35 -0
  110. data/spec/ast/rounddown_spec.rb +35 -0
  111. data/spec/ast/roundup_spec.rb +35 -0
  112. data/spec/ast/string_functions_spec.rb +217 -0
  113. data/spec/ast/sum_spec.rb +43 -0
  114. data/spec/ast/switch_spec.rb +30 -0
  115. data/spec/ast/xor_spec.rb +35 -0
  116. data/spec/benchmark.rb +70 -0
  117. data/spec/bulk_expression_solver_spec.rb +241 -0
  118. data/spec/calculator_spec.rb +1003 -0
  119. data/spec/dentaku_spec.rb +52 -0
  120. data/spec/dependency_resolver_spec.rb +18 -0
  121. data/spec/exceptions_spec.rb +9 -0
  122. data/spec/external_function_spec.rb +177 -0
  123. data/spec/parser_spec.rb +183 -0
  124. data/spec/print_visitor_spec.rb +77 -0
  125. data/spec/spec_helper.rb +69 -0
  126. data/spec/token_matcher_spec.rb +134 -0
  127. data/spec/token_scanner_spec.rb +49 -0
  128. data/spec/token_spec.rb +16 -0
  129. data/spec/tokenizer_spec.rb +375 -0
  130. data/spec/visitor/infix_spec.rb +52 -0
  131. data/spec/visitor_spec.rb +139 -0
  132. metadata +353 -0
data/README.md ADDED
@@ -0,0 +1,352 @@
1
+ Hayadentaku
2
+ ===========
3
+
4
+ [![Gem Version](https://badge.fury.io/rb/hayadentaku.svg)](https://rubygems.org/gems/hayadentaku)
5
+
6
+ > 速電卓 — *haya* (fast) + *dentaku* (calculator). A speed-focused,
7
+ > drop-in compatible fork of [Dentaku](https://github.com/rubysolo/dentaku).
8
+
9
+ DESCRIPTION
10
+ -----------
11
+
12
+ Hayadentaku is a parser and evaluator for a mathematical and logical formula
13
+ language that allows run-time binding of values to variables referenced in the
14
+ formulas. It is intended to safely evaluate untrusted expressions without
15
+ opening security holes.
16
+
17
+ It is a fork of [Dentaku](https://github.com/rubysolo/dentaku) with
18
+ optimizations on the hot path. The Ruby module name is still `Dentaku`, so
19
+ adopting Hayadentaku is as simple as swapping the gem in your `Gemfile`:
20
+
21
+ ```ruby
22
+ # Gemfile
23
+ gem 'hayadentaku'
24
+ ```
25
+
26
+ No code changes required — `Dentaku::Calculator` and friends continue to work
27
+ exactly as before.
28
+
29
+ EXAMPLE
30
+ -------
31
+
32
+ This is probably simplest to illustrate in code:
33
+
34
+ ```ruby
35
+ calculator = Dentaku::Calculator.new
36
+ calculator.evaluate('10 * 2')
37
+ #=> 20
38
+ ```
39
+
40
+ Okay, not terribly exciting. But what if you want to have a reference to a
41
+ variable, and evaluate it at run-time? Here's how that would look:
42
+
43
+ ```ruby
44
+ calculator.evaluate('kiwi + 5', kiwi: 2)
45
+ #=> 7
46
+ ```
47
+
48
+ To enter a case sensitive mode, just pass an option to the calculator instance:
49
+
50
+ ```ruby
51
+ calculator.evaluate('Kiwi + 5', Kiwi: -2, kiwi: 2)
52
+ #=> 7
53
+ calculator = Dentaku::Calculator.new(case_sensitive: true)
54
+ calculator.evaluate('Kiwi + 5', Kiwi: -2, kiwi: 2)
55
+ #=> 3
56
+ ```
57
+
58
+ You can also store the variable values in the calculator's memory and then
59
+ evaluate expressions against those stored values:
60
+
61
+ ```ruby
62
+ calculator.store(peaches: 15)
63
+ calculator.evaluate('peaches - 5')
64
+ #=> 10
65
+ calculator.evaluate('peaches >= 15')
66
+ #=> true
67
+ ```
68
+
69
+ For maximum CS geekery, `bind` is an alias of `store`.
70
+
71
+ Dentaku understands precedence order and using parentheses to group expressions
72
+ to ensure proper evaluation:
73
+
74
+ ```ruby
75
+ calculator.evaluate('5 + 3 * 2')
76
+ #=> 11
77
+ calculator.evaluate('(5 + 3) * 2')
78
+ #=> 16
79
+ ```
80
+
81
+ The `evaluate` method will return `nil` if there is an error in the formula.
82
+ If this is not the desired behavior, use `evaluate!`, which will raise an
83
+ exception.
84
+
85
+ ```ruby
86
+ calculator.evaluate('10 * x')
87
+ #=> nil
88
+ calculator.evaluate!('10 * x')
89
+ Dentaku::UnboundVariableError: Dentaku::UnboundVariableError
90
+ ```
91
+
92
+ Dentaku has built-in functions (including `if`, `not`, `min`, `max`, `sum`, and
93
+ `round`) and the ability to define custom functions (see below). Functions
94
+ generally work like their counterparts in Excel:
95
+
96
+ ```ruby
97
+ calculator.evaluate('SUM(1, 1, 2, 3, 5, 8)')
98
+ #=> 20
99
+
100
+ calculator.evaluate('if (pears < 10, 10, 20)', pears: 5)
101
+ #=> 10
102
+ calculator.evaluate('if (pears < 10, 10, 20)', pears: 15)
103
+ #=> 20
104
+ ```
105
+
106
+ `round` can be called with or without the number of decimal places:
107
+
108
+ ```ruby
109
+ calculator.evaluate('round(8.2)')
110
+ #=> 8
111
+ calculator.evaluate('round(8.2759, 2)')
112
+ #=> 8.28
113
+ ```
114
+
115
+ `round` follows rounding rules, while `roundup` and `rounddown` are `ceil` and
116
+ `floor`, respectively.
117
+
118
+ If you're too lazy to be building calculator objects, there's a shortcut just
119
+ for you:
120
+
121
+ ```ruby
122
+ Dentaku('plums * 1.5', plums: 2)
123
+ #=> 3.0
124
+ ```
125
+
126
+ PERFORMANCE
127
+ -----------
128
+
129
+ The flexibility and safety of Dentaku don't come without a price. Tokenizing a
130
+ string, parsing to an AST, and then evaluating that AST are about 2 orders of
131
+ magnitude slower than doing the same math in pure Ruby!
132
+
133
+ The good news is that most of the time is spent in the tokenization and parsing
134
+ phases, so if performance is a concern, you can enable AST caching:
135
+
136
+ ```ruby
137
+ Dentaku.enable_ast_cache!
138
+ ```
139
+
140
+ After this, Dentaku will cache the AST of each formula that it evaluates, so
141
+ subsequent evaluations (even with different values for variables) will be much
142
+ faster -- closer to 4x native Ruby speed. As usual, these benchmarks should be
143
+ considered rough estimates, and you should measure with representative formulas
144
+ from your application. Also, if new formulas are constantly introduced to your
145
+ application, AST caching will consume more memory with each new formula.
146
+
147
+ BUILT-IN OPERATORS AND FUNCTIONS
148
+ ---------------------------------
149
+
150
+ Math: `+`, `-`, `*`, `/`, `%`, `^`, `|`, `&`, `<<`, `>>`
151
+
152
+ Also, all functions from Ruby's Math module, including `SIN`, `COS`, `TAN`, etc.
153
+
154
+ Comparison: `<`, `>`, `<=`, `>=`, `<>`, `!=`, `=`,
155
+
156
+ Logic: `IF`, `AND`, `OR`, `XOR`, `NOT`, `SWITCH`
157
+
158
+ Numeric: `MIN`, `MAX`, `SUM`, `AVG`, `COUNT`, `ROUND`, `ROUNDDOWN`, `ROUNDUP`, `ABS`, `INTERCEPT`
159
+
160
+ Selections: `CASE` (syntax see [spec](https://github.com/rubysolo/dentaku/blob/main/lib/dentaku/ast/case.rb))
161
+
162
+ String: `LEFT`, `RIGHT`, `MID`, `LEN`, `FIND`, `SUBSTITUTE`, `CONCAT`, `CONTAINS`
163
+
164
+ Collection: `MAP`, `FILTER`, `ALL`, `ANY`, `PLUCK`
165
+
166
+ RESOLVING DEPENDENCIES
167
+ ----------------------
168
+
169
+ If your formulas rely on one another, they may need to be resolved in a
170
+ particular order. For example:
171
+
172
+ ```ruby
173
+ calc = Dentaku::Calculator.new
174
+ calc.store(monthly_income: 50)
175
+ need_to_compute = {
176
+ income_taxes: "annual_income / 5",
177
+ annual_income: "monthly_income * 12"
178
+ }
179
+ ```
180
+
181
+ In the example, `annual_income` needs to be computed (and stored) before
182
+ `income_taxes`.
183
+
184
+ Dentaku provides two methods to help resolve formulas in order:
185
+
186
+ #### Calculator.dependencies
187
+ Pass a (string) expression to Dependencies and get back a list of variables (as
188
+ `:symbols`) that are required for the expression. `Dependencies` also takes
189
+ into account variables already (explicitly) stored into the calculator.
190
+
191
+ ```ruby
192
+ calc.dependencies("monthly_income * 12")
193
+ #=> []
194
+ # (since monthly_income is in memory)
195
+
196
+ calc.dependencies("annual_income / 5")
197
+ #=> [:annual_income]
198
+ ```
199
+
200
+ #### Calculator.solve! / Calculator.solve
201
+ Have Dentaku figure out the order in which your formulas need to be evaluated.
202
+
203
+ Pass in a hash of `{eventual_variable_name: "expression"}` to `solve!` and
204
+ have Dentaku resolve dependencies (using `TSort`) for you.
205
+
206
+ Raises `TSort::Cyclic` when a valid expression order cannot be found.
207
+
208
+ ```ruby
209
+ calc = Dentaku::Calculator.new
210
+ calc.store(monthly_income: 50)
211
+ need_to_compute = {
212
+ income_taxes: "annual_income / 5",
213
+ annual_income: "monthly_income * 12"
214
+ }
215
+ calc.solve!(need_to_compute)
216
+ #=> {annual_income: 600, income_taxes: 120}
217
+
218
+ calc.solve!(
219
+ make_money: "have_money",
220
+ have_money: "make_money"
221
+ }
222
+ #=> raises TSort::Cyclic
223
+ ```
224
+
225
+ `solve!` will also raise an exception if any of the formulas in the set cannot
226
+ be evaluated (e.g. raise `ZeroDivisionError`). The non-bang `solve` method will
227
+ find as many solutions as possible and return the symbol `:undefined` for the
228
+ problem formulas.
229
+
230
+ INLINE COMMENTS
231
+ ---------------------------------
232
+
233
+ If your expressions grow long or complex, you may add inline comments for future
234
+ reference. This is particularly useful if you save your expressions in a model.
235
+
236
+ ```ruby
237
+ calculator.evaluate('kiwi + 5 /* This is a comment */', kiwi: 2)
238
+ #=> 7
239
+ ```
240
+
241
+ Comments can be single or multi-line. The following are also valid.
242
+
243
+ ```
244
+ /*
245
+ * This is a multi-line comment
246
+ */
247
+
248
+ /*
249
+ This is another type of multi-line comment
250
+ */
251
+ ```
252
+
253
+ EXTERNAL FUNCTIONS
254
+ ------------------
255
+
256
+ I don't know everything, so I might not have implemented all the functions you
257
+ need. Please implement your favorites and send a pull request! Okay, so maybe
258
+ that's not feasible because:
259
+
260
+ 1. You can't be bothered to share
261
+ 1. You can't wait for me to respond to a pull request, you need it `NOW()`
262
+ 1. The formula is the secret sauce for your startup
263
+
264
+ Whatever your reasons, Dentaku supports adding functions at runtime. To add a
265
+ function, you'll need to specify a name, a return type, and a lambda that
266
+ accepts all function arguments and returns the result value.
267
+
268
+ Here's an example of adding a function named `POW` that implements
269
+ exponentiation.
270
+
271
+ ```ruby
272
+ > c = Dentaku::Calculator.new
273
+ > c.add_function(:pow, :numeric, ->(mantissa, exponent) { mantissa ** exponent })
274
+ > c.evaluate('POW(3,2)')
275
+ #=> 9
276
+ > c.evaluate('POW(2,3)')
277
+ #=> 8
278
+ ```
279
+
280
+ Here's an example of adding a variadic function:
281
+
282
+ ```ruby
283
+ > c = Dentaku::Calculator.new
284
+ > c.add_function(:max, :numeric, ->(*args) { args.max })
285
+ > c.evaluate 'MAX(8,6,7,5,3,0,9)'
286
+ #=> 9
287
+ ```
288
+
289
+ (However both of these are already built-in -- the `^` operator and the `MAX`
290
+ function)
291
+
292
+ Functions can be added individually using Calculator#add_function, or en masse
293
+ using Calculator#add_functions.
294
+
295
+ FUNCTION ALIASES
296
+ ----------------
297
+
298
+ Every function can be aliased by synonyms. For example, it can be useful if
299
+ your application is multilingual.
300
+
301
+ ```ruby
302
+ Dentaku.aliases = {
303
+ round: ['rrrrround!', 'округлить']
304
+ }
305
+
306
+ Dentaku('rrrrround!(8.2) + округлить(8.4)') # the same as round(8.2) + round(8.4)
307
+ # 16
308
+ ```
309
+
310
+ Also, if you need thread-safe aliases you can pass them to `Dentaku::Calculator`
311
+ initializer:
312
+
313
+ ```ruby
314
+ aliases = {
315
+ round: ['rrrrround!', 'округлить']
316
+ }
317
+ c = Dentaku::Calculator.new(aliases: aliases)
318
+ c.evaluate('rrrrround!(8.2) + округлить(8.4)')
319
+ # 16
320
+ ```
321
+
322
+ THANKS
323
+ ------
324
+
325
+ Big thanks to [ElkStone Basements](http://www.elkstonebasements.com/) for
326
+ allowing me to extract and open source this code. Thanks also to all the
327
+ [contributors](https://github.com/rubysolo/dentaku/graphs/contributors)!
328
+
329
+
330
+ LICENSE
331
+ -------
332
+
333
+ (The MIT License)
334
+
335
+ Copyright © 2012-2022 Solomon White
336
+
337
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
338
+ this software and associated documentation files (the ‘Software’), to deal in
339
+ the Software without restriction, including without limitation the rights to
340
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
341
+ the Software, and to permit persons to whom the Software is furnished to do so,
342
+ subject to the following conditions:
343
+
344
+ The above copyright notice and this permission notice shall be included in all
345
+ copies or substantial portions of the Software.
346
+
347
+ THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
348
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
349
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
350
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
351
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
352
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,31 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ require 'rubocop/rake_task'
4
+
5
+ RuboCop::RakeTask.new
6
+
7
+ desc "Run specs"
8
+ task :spec do
9
+ RSpec::Core::RakeTask.new(:spec) do |t|
10
+ t.rspec_opts = %w{--colour --format progress}
11
+ t.pattern = 'spec/**/*_spec.rb'
12
+ end
13
+ end
14
+
15
+ desc "Default: run specs."
16
+ task(:default).clear.enhance [:spec, :rubocop]
17
+
18
+ task :console do
19
+ begin
20
+ require 'pry'
21
+ console = Pry
22
+ rescue LoadError
23
+ require 'irb'
24
+ require 'irb/completion'
25
+ console = IRB
26
+ end
27
+
28
+ require 'dentaku'
29
+ ARGV.clear
30
+ console.start
31
+ end
@@ -0,0 +1,35 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "dentaku/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "hayadentaku"
7
+ s.version = Dentaku::VERSION
8
+ s.authors = ["Siarhei Padlozny", "Solomon White"]
9
+ s.email = ["barmidrol@gmail.com", "rubysolo@gmail.com"]
10
+ s.homepage = "https://github.com/barmidrol/dentaku"
11
+ s.licenses = %w(MIT)
12
+ s.summary = 'A fast formula language parser and evaluator (a speedy fork of Dentaku)'
13
+ s.description = <<-DESC
14
+ Hayadentaku ("haya" 速 = fast + "dentaku" 電卓 = calculator) is a speed-focused,
15
+ drop-in compatible fork of Dentaku, a parser and evaluator for mathematical and
16
+ logical formulas with run-time variable binding. The Ruby module name remains
17
+ `Dentaku`, so you can swap the gem in your Gemfile without code changes.
18
+ DESC
19
+
20
+ s.add_dependency('bigdecimal')
21
+ s.add_dependency('concurrent-ruby')
22
+
23
+ s.add_development_dependency('pry')
24
+ s.add_development_dependency('pry-byebug')
25
+ s.add_development_dependency('pry-stack_explorer')
26
+ s.add_development_dependency('rake')
27
+ s.add_development_dependency('rspec')
28
+ s.add_development_dependency('rubocop')
29
+ s.add_development_dependency('simplecov')
30
+
31
+ s.files = `git ls-files`.split("\n")
32
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
33
+ s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
34
+ s.require_paths = ["lib"]
35
+ end
@@ -0,0 +1,44 @@
1
+ require_relative "./node"
2
+
3
+ module Dentaku
4
+ module AST
5
+ class Access < Node
6
+ attr_reader :structure, :index
7
+
8
+ def self.arity
9
+ 2
10
+ end
11
+
12
+ def self.min_param_count
13
+ arity
14
+ end
15
+
16
+ def self.max_param_count
17
+ arity
18
+ end
19
+
20
+ def initialize(data_structure, index)
21
+ @structure = data_structure
22
+ @index = index
23
+ end
24
+
25
+ def value(context = {})
26
+ structure = @structure.value(context)
27
+ index = @index.value(context)
28
+ structure[index]
29
+ end
30
+
31
+ def dependencies(context = {})
32
+ @structure.dependencies(context) + @index.dependencies(context)
33
+ end
34
+
35
+ def type
36
+ nil
37
+ end
38
+
39
+ def accept(visitor)
40
+ visitor.visit_access(self)
41
+ end
42
+ end
43
+ end
44
+ end