keisan 0.8.0 → 0.8.5
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 +4 -4
- data/.travis.yml +6 -4
- data/README.md +24 -1
- data/keisan.gemspec +5 -3
- data/lib/keisan.rb +6 -0
- data/lib/keisan/ast/bitwise_left_shift.rb +17 -0
- data/lib/keisan/ast/bitwise_right_shift.rb +17 -0
- data/lib/keisan/ast/block.rb +4 -0
- data/lib/keisan/ast/cell.rb +4 -0
- data/lib/keisan/ast/function_assignment.rb +16 -6
- data/lib/keisan/ast/hash.rb +4 -0
- data/lib/keisan/ast/node.rb +25 -0
- data/lib/keisan/ast/number.rb +20 -0
- data/lib/keisan/ast/operator.rb +2 -0
- data/lib/keisan/ast/parent.rb +4 -0
- data/lib/keisan/ast/unary_inverse.rb +1 -1
- data/lib/keisan/ast/variable.rb +8 -4
- data/lib/keisan/calculator.rb +17 -3
- data/lib/keisan/context.rb +21 -4
- data/lib/keisan/evaluator.rb +16 -4
- data/lib/keisan/functions/cmath_function.rb +3 -1
- data/lib/keisan/functions/default_registry.rb +3 -0
- data/lib/keisan/functions/expression_function.rb +16 -9
- data/lib/keisan/functions/proc_function.rb +2 -2
- data/lib/keisan/functions/replace.rb +6 -6
- data/lib/keisan/parser.rb +2 -0
- data/lib/keisan/parsing/bitwise_left_shift.rb +9 -0
- data/lib/keisan/parsing/bitwise_right_shift.rb +9 -0
- data/lib/keisan/string_and_group_parser.rb +235 -0
- data/lib/keisan/tokenizer.rb +20 -18
- data/lib/keisan/tokens/assignment.rb +3 -1
- data/lib/keisan/tokens/bitwise_shift.rb +23 -0
- data/lib/keisan/tokens/group.rb +1 -7
- data/lib/keisan/tokens/string.rb +2 -4
- data/lib/keisan/version.rb +1 -1
- metadata +33 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a2a155be79ae75b3c67a097791c2af2bf67196cae16ef5372049b4eb54884117
|
4
|
+
data.tar.gz: b5d38c6111d1803a14c0229b0f54dc8733d581409254068ac9970af1321d9fca
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 550d595b64739c49e6fdcd12b149fa0f5706d3032f11c61f6f9b9bd3ac6ff7465398a5b04f58868dca803c9cef1b9fb14f9df029bc03c550d8616760751f3886
|
7
|
+
data.tar.gz: d2c25f61cdd3400c198cb6962cd31c0023ef5aa0081d3a0ea2699b7e7098620d8d39dc8af26bee5fe10197f84fb91218763fdc6ba830d2c97b30122a1b30fd3d
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -178,6 +178,22 @@ calculator.evaluate("x = 11; {let x = 12}; x")
|
|
178
178
|
#=> 11
|
179
179
|
```
|
180
180
|
|
181
|
+
##### Comments
|
182
|
+
|
183
|
+
When working with multi-line blocks of code, sometimes comments are useful to include.
|
184
|
+
Comments are parts of a string from the `#` character to the end of a line (indicated by a newline character `"\n"`).
|
185
|
+
|
186
|
+
```
|
187
|
+
calculator = Keisan::Calculator.new
|
188
|
+
calculator.evaluate("""
|
189
|
+
# This is a comment
|
190
|
+
x = 'foo'
|
191
|
+
x += '#bar' # Notice that `#` inside strings is not part of the comment
|
192
|
+
x # Should print 'foo#bar'
|
193
|
+
""")
|
194
|
+
#=> "foo#bar"
|
195
|
+
```
|
196
|
+
|
181
197
|
##### Lists
|
182
198
|
|
183
199
|
Just like in Ruby, lists can be defined using square brackets, and indexed using square brackets
|
@@ -256,6 +272,7 @@ calculator.evaluate("range(1, 6).map(x, [x, x**2]).to_h")
|
|
256
272
|
Keisan supports date and time objects like in Ruby.
|
257
273
|
You create a date object using either the method `date` (either a string to be parsed, or year, month, day numerical arguments) or `today`.
|
258
274
|
They support methods `year`, `month`, `day`, `weekday`, `strftime`, and `to_time` to convert to a time object.
|
275
|
+
`epoch_days` computes the number of days since Unix epoch (Jan 1, 1970).
|
259
276
|
|
260
277
|
```ruby
|
261
278
|
calculator = Keisan::Calculator.new
|
@@ -266,10 +283,13 @@ calculator.evaluate("today() > date(2018, 11, 1)")
|
|
266
283
|
#=> true
|
267
284
|
calculator.evaluate("date('1999-12-31').to_time + 10")
|
268
285
|
#=> Time.new(1999, 12, 31, 0, 0, 10)
|
286
|
+
calculator.evaluate("date(1970, 1, 15).epoch_days")
|
287
|
+
#=> 14
|
269
288
|
```
|
270
289
|
|
271
290
|
Time objects are created using `time` (either a string to be parsed, or year, month, day, hour, minute, second arguments) or `now`.
|
272
291
|
They support methods `year`, `month`, `day`, `hour`, `minute`, `second`, `weekday`, `strftime`, and `to_date` to convert to a date object.
|
292
|
+
`epoch_seconds` computes the number of seconds since Unix epoch (00:00:00 on Jan 1, 1970).
|
273
293
|
|
274
294
|
```ruby
|
275
295
|
calculator = Keisan::Calculator.new
|
@@ -279,6 +299,8 @@ calculator.evaluate("time('2000-4-15 12:34:56').minute")
|
|
279
299
|
#=> 34
|
280
300
|
calculator.evaluate("time('5000-10-10 20:30:40').strftime('%b %d, %Y')")
|
281
301
|
#=> "Oct 10, 5000"
|
302
|
+
calculator.evaluate("time(1970, 1, 1, 2, 3, 4).epoch_seconds")
|
303
|
+
#=> 7384
|
282
304
|
```
|
283
305
|
|
284
306
|
##### Functional programming methods
|
@@ -340,7 +362,7 @@ calculator.evaluate("[2, 3, 7, 11].has_element(11)")
|
|
340
362
|
|
341
363
|
##### Bitwise operations
|
342
364
|
|
343
|
-
The basic bitwise operations, NOT `~`, OR `|`, XOR `^`, and
|
365
|
+
The basic bitwise operations, NOT `~`, OR `|`, XOR `^`, AND `&`, and left/right bitwise shifts (`<<` and `>>`) are also available for use
|
344
366
|
|
345
367
|
```ruby
|
346
368
|
calculator = Keisan::Calculator.new
|
@@ -485,6 +507,7 @@ calculator.evaluate("puts x**2") # prints "25\n" to STDOUT
|
|
485
507
|
|
486
508
|
#### Bitwise operators
|
487
509
|
- `&`, `|`, `^`: bitwise **and**, **or**, **xor** operators
|
510
|
+
- `<<`, `>>` bitwise shift operators
|
488
511
|
- `~`: unary bitwise not
|
489
512
|
|
490
513
|
#### Indexing of arrays/hashes
|
data/keisan.gemspec
CHANGED
@@ -21,10 +21,12 @@ Gem::Specification.new do |spec|
|
|
21
21
|
|
22
22
|
spec.required_ruby_version = ">= 2.3.0"
|
23
23
|
|
24
|
-
spec.
|
25
|
-
|
26
|
-
spec.add_development_dependency "
|
24
|
+
spec.add_dependency "cmath", "~> 1.0"
|
25
|
+
|
26
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
27
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
27
28
|
spec.add_development_dependency "rspec", "~> 3.0"
|
28
29
|
spec.add_development_dependency "pry"
|
29
30
|
spec.add_development_dependency "pry-stack_explorer"
|
31
|
+
spec.add_development_dependency "simplecov"
|
30
32
|
end
|
data/lib/keisan.rb
CHANGED
@@ -37,6 +37,8 @@ require "keisan/ast/bitwise_operator"
|
|
37
37
|
require "keisan/ast/bitwise_and"
|
38
38
|
require "keisan/ast/bitwise_or"
|
39
39
|
require "keisan/ast/bitwise_xor"
|
40
|
+
require "keisan/ast/bitwise_left_shift"
|
41
|
+
require "keisan/ast/bitwise_right_shift"
|
40
42
|
require "keisan/ast/logical_operator"
|
41
43
|
require "keisan/ast/logical_and"
|
42
44
|
require "keisan/ast/logical_or"
|
@@ -78,10 +80,12 @@ require "keisan/tokens/assignment"
|
|
78
80
|
require "keisan/tokens/arithmetic_operator"
|
79
81
|
require "keisan/tokens/logical_operator"
|
80
82
|
require "keisan/tokens/bitwise_operator"
|
83
|
+
require "keisan/tokens/bitwise_shift"
|
81
84
|
require "keisan/tokens/word"
|
82
85
|
require "keisan/tokens/line_separator"
|
83
86
|
require "keisan/tokens/unknown"
|
84
87
|
|
88
|
+
require "keisan/string_and_group_parser"
|
85
89
|
require "keisan/tokenizer"
|
86
90
|
|
87
91
|
require "keisan/parsing/component"
|
@@ -128,6 +132,8 @@ require "keisan/parsing/bitwise_or"
|
|
128
132
|
require "keisan/parsing/bitwise_xor"
|
129
133
|
require "keisan/parsing/bitwise_not"
|
130
134
|
require "keisan/parsing/bitwise_not_not"
|
135
|
+
require "keisan/parsing/bitwise_left_shift"
|
136
|
+
require "keisan/parsing/bitwise_right_shift"
|
131
137
|
require "keisan/parsing/logical_operator"
|
132
138
|
require "keisan/parsing/logical_less_than"
|
133
139
|
require "keisan/parsing/logical_greater_than"
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Keisan
|
2
|
+
module AST
|
3
|
+
class BitwiseLeftShift < BitwiseOperator
|
4
|
+
def self.symbol
|
5
|
+
:<<
|
6
|
+
end
|
7
|
+
|
8
|
+
def blank_value
|
9
|
+
0
|
10
|
+
end
|
11
|
+
|
12
|
+
def evaluate(context = nil)
|
13
|
+
children[1..-1].inject(children.first.evaluate(context)) {|total, child| total << child.evaluate(context)}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Keisan
|
2
|
+
module AST
|
3
|
+
class BitwiseRightShift < BitwiseOperator
|
4
|
+
def self.symbol
|
5
|
+
:>>
|
6
|
+
end
|
7
|
+
|
8
|
+
def blank_value
|
9
|
+
0
|
10
|
+
end
|
11
|
+
|
12
|
+
def evaluate(context = nil)
|
13
|
+
children[1..-1].inject(children.first.evaluate(context)) {|total, child| total >> child.evaluate(context)}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/keisan/ast/block.rb
CHANGED
data/lib/keisan/ast/cell.rb
CHANGED
@@ -29,8 +29,7 @@ module Keisan
|
|
29
29
|
|
30
30
|
def evaluate
|
31
31
|
# Blocks might have local variable/function definitions, so skip check
|
32
|
-
verify_rhs_of_function_assignment_is_valid!
|
33
|
-
|
32
|
+
verify_rhs_of_function_assignment_is_valid!
|
34
33
|
context.register_function!(lhs.name, expression_function, local: local)
|
35
34
|
rhs
|
36
35
|
end
|
@@ -38,15 +37,26 @@ module Keisan
|
|
38
37
|
private
|
39
38
|
|
40
39
|
def verify_rhs_of_function_assignment_is_valid!
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
40
|
+
verify_unbound_functions!
|
41
|
+
verify_unbound_variables!
|
42
|
+
end
|
43
|
+
|
44
|
+
def verify_unbound_functions!
|
45
45
|
# Cannot have undefined functions unless allowed by context
|
46
46
|
unless context.allow_recursive || rhs.unbound_functions(context).empty?
|
47
47
|
raise Exceptions::InvalidExpression.new("Unbound function definitions are not allowed by current context")
|
48
48
|
end
|
49
49
|
end
|
50
|
+
|
51
|
+
def verify_unbound_variables!
|
52
|
+
# We allow unbound variables inside block statements, as they could be temporary
|
53
|
+
# variables assigned locally
|
54
|
+
return if rhs.is_a?(Block)
|
55
|
+
# Only variables that can appear are those that are arguments to the function
|
56
|
+
unless rhs.unbound_variables(context) <= Set.new(argument_names)
|
57
|
+
raise Exceptions::InvalidExpression.new("Unbound variables found in function definition")
|
58
|
+
end
|
59
|
+
end
|
50
60
|
end
|
51
61
|
end
|
52
62
|
end
|
data/lib/keisan/ast/hash.rb
CHANGED
data/lib/keisan/ast/node.rb
CHANGED
@@ -37,6 +37,15 @@ module Keisan
|
|
37
37
|
value(context)
|
38
38
|
end
|
39
39
|
|
40
|
+
def contains_a?(klass)
|
41
|
+
case klass
|
42
|
+
when Array
|
43
|
+
klass.any? {|k| is_a?(k) }
|
44
|
+
else
|
45
|
+
is_a?(klass)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
40
49
|
def evaluate_assignments(context = nil)
|
41
50
|
self
|
42
51
|
end
|
@@ -45,10 +54,18 @@ module Keisan
|
|
45
54
|
raise Exceptions::NonDifferentiableError.new
|
46
55
|
end
|
47
56
|
|
57
|
+
def differentiated(variable, context = nil)
|
58
|
+
deep_dup.differentiate(variable, context)
|
59
|
+
end
|
60
|
+
|
48
61
|
def replace(variable, replacement)
|
49
62
|
self
|
50
63
|
end
|
51
64
|
|
65
|
+
def replaced(variable, replacement)
|
66
|
+
deep_dup.replace(variable, replacement)
|
67
|
+
end
|
68
|
+
|
52
69
|
def coerce(other)
|
53
70
|
[other.to_node, self]
|
54
71
|
end
|
@@ -132,6 +149,14 @@ module Keisan
|
|
132
149
|
BitwiseOr.new([self, other.to_node])
|
133
150
|
end
|
134
151
|
|
152
|
+
def <<(other)
|
153
|
+
BitwiseLeftShift.new([self, other.to_node])
|
154
|
+
end
|
155
|
+
|
156
|
+
def >>(other)
|
157
|
+
BitwiseRightShift.new([self, other.to_node])
|
158
|
+
end
|
159
|
+
|
135
160
|
def >(other)
|
136
161
|
LogicalGreaterThan.new([self, other.to_node])
|
137
162
|
end
|
data/lib/keisan/ast/number.rb
CHANGED
@@ -111,6 +111,26 @@ module Keisan
|
|
111
111
|
end
|
112
112
|
end
|
113
113
|
|
114
|
+
def <<(other)
|
115
|
+
other = other.to_node
|
116
|
+
case other
|
117
|
+
when Number
|
118
|
+
Number.new(value << other.value)
|
119
|
+
else
|
120
|
+
super
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def >>(other)
|
125
|
+
other = other.to_node
|
126
|
+
case other
|
127
|
+
when Number
|
128
|
+
Number.new(value >> other.value)
|
129
|
+
else
|
130
|
+
super
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
114
134
|
def >(other)
|
115
135
|
other = other.to_node
|
116
136
|
case other
|
data/lib/keisan/ast/operator.rb
CHANGED
@@ -13,6 +13,8 @@ module Keisan
|
|
13
13
|
"%": [2, 85, :left], # Modulo
|
14
14
|
"+": [2, 80, :left], # Plus
|
15
15
|
# "-": [2, 80, :left], # Minus
|
16
|
+
"<<": [2, 75, :left], # Bitwise left shift
|
17
|
+
">>": [2, 75, :left], # Bitwise right shift
|
16
18
|
"&": [2, 70, :left], # Bitwise and
|
17
19
|
"^": [2, 65, :left], # Bitwise xor
|
18
20
|
"|": [2, 65, :left], # Bitwise or
|
data/lib/keisan/ast/parent.rb
CHANGED
data/lib/keisan/ast/variable.rb
CHANGED
@@ -13,7 +13,13 @@ module Keisan
|
|
13
13
|
|
14
14
|
def value(context = nil)
|
15
15
|
context ||= Context.new
|
16
|
-
variable_node_from_context(context)
|
16
|
+
node = variable_node_from_context(context)
|
17
|
+
case node
|
18
|
+
when Variable
|
19
|
+
node
|
20
|
+
else
|
21
|
+
node.value(context)
|
22
|
+
end
|
17
23
|
end
|
18
24
|
|
19
25
|
def unbound_variables(context = nil)
|
@@ -78,9 +84,7 @@ module Keisan
|
|
78
84
|
|
79
85
|
def variable_node_from_context(context)
|
80
86
|
variable = context.variable(name)
|
81
|
-
if variable.is_a?(Cell)
|
82
|
-
variable = variable.node
|
83
|
-
end
|
87
|
+
variable = variable.node if variable.is_a?(Cell)
|
84
88
|
variable
|
85
89
|
end
|
86
90
|
end
|
data/lib/keisan/calculator.rb
CHANGED
@@ -2,8 +2,14 @@ module Keisan
|
|
2
2
|
class Calculator
|
3
3
|
attr_reader :context
|
4
4
|
|
5
|
-
|
6
|
-
|
5
|
+
# Note, allow_recursive would be more appropriately named:
|
6
|
+
# allow_unbound_functions_in_function_definitions, but it is too late for that.
|
7
|
+
def initialize(context: nil, allow_recursive: false, allow_blocks: true, allow_multiline: true)
|
8
|
+
@context = context || Context.new(
|
9
|
+
allow_recursive: allow_recursive,
|
10
|
+
allow_blocks: allow_blocks,
|
11
|
+
allow_multiline: allow_multiline
|
12
|
+
)
|
7
13
|
end
|
8
14
|
|
9
15
|
def allow_recursive
|
@@ -14,6 +20,14 @@ module Keisan
|
|
14
20
|
context.allow_recursive!
|
15
21
|
end
|
16
22
|
|
23
|
+
def allow_blocks
|
24
|
+
context.allow_blocks
|
25
|
+
end
|
26
|
+
|
27
|
+
def allow_multiline
|
28
|
+
context.allow_multiline
|
29
|
+
end
|
30
|
+
|
17
31
|
def evaluate(expression, definitions = {})
|
18
32
|
Evaluator.new(self).evaluate(expression, definitions)
|
19
33
|
end
|
@@ -23,7 +37,7 @@ module Keisan
|
|
23
37
|
end
|
24
38
|
|
25
39
|
def ast(expression)
|
26
|
-
Evaluator.new(self).
|
40
|
+
Evaluator.new(self).parse_ast(expression)
|
27
41
|
end
|
28
42
|
|
29
43
|
def define_variable!(name, value)
|
data/lib/keisan/context.rb
CHANGED
@@ -1,13 +1,24 @@
|
|
1
1
|
module Keisan
|
2
2
|
class Context
|
3
|
-
attr_reader :function_registry,
|
4
|
-
|
5
|
-
|
3
|
+
attr_reader :function_registry,
|
4
|
+
:variable_registry,
|
5
|
+
:allow_recursive,
|
6
|
+
:allow_multiline,
|
7
|
+
:allow_blocks
|
8
|
+
|
9
|
+
def initialize(parent: nil,
|
10
|
+
random: nil,
|
11
|
+
allow_recursive: false,
|
12
|
+
allow_multiline: true,
|
13
|
+
allow_blocks: true,
|
14
|
+
shadowed: [])
|
6
15
|
@parent = parent
|
7
16
|
@function_registry = Functions::Registry.new(parent: @parent&.function_registry)
|
8
17
|
@variable_registry = Variables::Registry.new(parent: @parent&.variable_registry, shadowed: shadowed)
|
9
18
|
@random = random
|
10
19
|
@allow_recursive = allow_recursive
|
20
|
+
@allow_multiline = allow_multiline
|
21
|
+
@allow_blocks = allow_blocks
|
11
22
|
end
|
12
23
|
|
13
24
|
def allow_recursive!
|
@@ -110,7 +121,13 @@ module Keisan
|
|
110
121
|
end
|
111
122
|
|
112
123
|
def pure_child(shadowed: [])
|
113
|
-
self.class.new(
|
124
|
+
self.class.new(
|
125
|
+
parent: self,
|
126
|
+
shadowed: shadowed,
|
127
|
+
allow_recursive: allow_recursive,
|
128
|
+
allow_multiline: allow_multiline,
|
129
|
+
allow_blocks: allow_blocks
|
130
|
+
)
|
114
131
|
end
|
115
132
|
end
|
116
133
|
end
|
data/lib/keisan/evaluator.rb
CHANGED
@@ -8,7 +8,7 @@ module Keisan
|
|
8
8
|
|
9
9
|
def evaluate(expression, definitions = {})
|
10
10
|
context = calculator.context.spawn_child(definitions: definitions, transient: true)
|
11
|
-
ast =
|
11
|
+
ast = parse_ast(expression)
|
12
12
|
last_line = last_line(ast)
|
13
13
|
|
14
14
|
evaluation = ast.evaluated(context)
|
@@ -24,16 +24,28 @@ module Keisan
|
|
24
24
|
|
25
25
|
def simplify(expression, definitions = {})
|
26
26
|
context = calculator.context.spawn_child(definitions: definitions, transient: true)
|
27
|
-
ast =
|
27
|
+
ast = parse_ast(expression)
|
28
28
|
ast.simplify(context)
|
29
29
|
end
|
30
30
|
|
31
|
-
def
|
32
|
-
AST.parse(expression)
|
31
|
+
def parse_ast(expression)
|
32
|
+
AST.parse(expression).tap do |ast|
|
33
|
+
disallowed = disallowed_nodes
|
34
|
+
if !disallowed.empty? && ast.contains_a?(disallowed)
|
35
|
+
raise Keisan::Exceptions::InvalidExpression.new("Context does not permit expressions with #{disallowed}")
|
36
|
+
end
|
37
|
+
end
|
33
38
|
end
|
34
39
|
|
35
40
|
private
|
36
41
|
|
42
|
+
def disallowed_nodes
|
43
|
+
disallowed = []
|
44
|
+
disallowed << Keisan::AST::Block unless calculator.allow_blocks
|
45
|
+
disallowed << Keisan::AST::MultiLine unless calculator.allow_multiline
|
46
|
+
disallowed
|
47
|
+
end
|
48
|
+
|
37
49
|
def last_line(ast)
|
38
50
|
ast.is_a?(AST::MultiLine) ? ast.children.last : ast
|
39
51
|
end
|
@@ -4,7 +4,9 @@ module Keisan
|
|
4
4
|
module Functions
|
5
5
|
class CMathFunction < MathFunction
|
6
6
|
def initialize(name, proc_function = nil)
|
7
|
-
super(name, proc_function || Proc.new {|arg|
|
7
|
+
super(name, proc_function || Proc.new {|arg|
|
8
|
+
CMath.send(name, arg)
|
9
|
+
})
|
8
10
|
end
|
9
11
|
end
|
10
12
|
end
|
@@ -147,6 +147,9 @@ module Keisan
|
|
147
147
|
|
148
148
|
registry.register!(:to_time, Proc.new {|d| d.to_time }, force: true)
|
149
149
|
registry.register!(:to_date, Proc.new {|t| t.to_date }, force: true)
|
150
|
+
|
151
|
+
registry.register!(:epoch_seconds, Proc.new {|d| d.to_time - Time.new(1970, 1, 1, 0, 0, 0) }, force: true)
|
152
|
+
registry.register!(:epoch_days, Proc.new {|t| t.to_date - Date.new(1970, 1, 1) }, force: true)
|
150
153
|
end
|
151
154
|
|
152
155
|
def self.register_date_time!(registry)
|
@@ -5,7 +5,11 @@ module Keisan
|
|
5
5
|
|
6
6
|
def initialize(name, arguments, expression, transient_definitions)
|
7
7
|
super(name, arguments.count)
|
8
|
-
|
8
|
+
if expression.is_a?(::String)
|
9
|
+
@expression = AST::parse(expression)
|
10
|
+
else
|
11
|
+
@expression = expression.deep_dup
|
12
|
+
end
|
9
13
|
@arguments = arguments
|
10
14
|
@transient_definitions = transient_definitions
|
11
15
|
end
|
@@ -70,17 +74,20 @@ module Keisan
|
|
70
74
|
|
71
75
|
local = local_context_for(context)
|
72
76
|
|
73
|
-
|
74
|
-
|
75
|
-
argument_values = ast_function.children.map {|child| child.evaluate(local)}
|
77
|
+
argument_values = ast_function.children.map {|child| child.evaluated(local)}
|
76
78
|
|
77
79
|
argument_derivatives = ast_function.children.map do |child|
|
78
|
-
child.
|
80
|
+
child.differentiated(variable, context)
|
79
81
|
end
|
80
82
|
|
83
|
+
partial_derivatives = calculate_partial_derivatives(context)
|
84
|
+
|
81
85
|
AST::Plus.new(
|
82
86
|
argument_derivatives.map.with_index {|argument_derivative, i|
|
83
|
-
partial_derivative = partial_derivatives[i]
|
87
|
+
partial_derivative = partial_derivatives[i]
|
88
|
+
argument_variables.each.with_index {|argument_variable, j|
|
89
|
+
partial_derivative = partial_derivative.replaced(argument_variable, argument_values[j])
|
90
|
+
}
|
84
91
|
AST::Times.new([argument_derivative, partial_derivative])
|
85
92
|
}
|
86
93
|
)
|
@@ -92,9 +99,9 @@ module Keisan
|
|
92
99
|
@argument_variables ||= arguments.map {|argument| AST::Variable.new(argument)}
|
93
100
|
end
|
94
101
|
|
95
|
-
def
|
96
|
-
|
97
|
-
partial_derivative = expression.
|
102
|
+
def calculate_partial_derivatives(context)
|
103
|
+
argument_variables.map.with_index do |variable, i|
|
104
|
+
partial_derivative = expression.differentiated(variable, context)
|
98
105
|
end
|
99
106
|
end
|
100
107
|
|
@@ -28,7 +28,7 @@ module Keisan
|
|
28
28
|
|
29
29
|
ast_function.instance_variable_set(
|
30
30
|
:@children,
|
31
|
-
ast_function.children.map {|child| child.
|
31
|
+
ast_function.children.map {|child| child.simplify(context).to_node}
|
32
32
|
)
|
33
33
|
|
34
34
|
if ast_function.children.all? {|child| child.well_defined?(context)}
|
@@ -44,7 +44,7 @@ module Keisan
|
|
44
44
|
|
45
45
|
ast_function.instance_variable_set(
|
46
46
|
:@children,
|
47
|
-
ast_function.children.map {|child| child.
|
47
|
+
ast_function.children.map {|child| child.simplify(context)}
|
48
48
|
)
|
49
49
|
|
50
50
|
if ast_function.children.all? {|child| child.is_a?(AST::ConstantLiteral)}
|
@@ -10,19 +10,19 @@ module Keisan
|
|
10
10
|
evaluate(ast_function, context).value(context)
|
11
11
|
end
|
12
12
|
|
13
|
-
def
|
13
|
+
def simplify(ast_function, context = nil)
|
14
14
|
context ||= Context.new
|
15
15
|
expression, variable, replacement = expression_variable_replacement(ast_function)
|
16
16
|
|
17
|
-
expression = expression.
|
18
|
-
replacement = replacement.
|
17
|
+
expression = expression.simplify(context)
|
18
|
+
replacement = replacement.simplify(context)
|
19
19
|
|
20
|
-
expression.replace(variable, replacement).
|
20
|
+
expression.replace(variable, replacement).simplify(context)
|
21
21
|
end
|
22
22
|
|
23
|
-
def
|
23
|
+
def evaluate(ast_function, context = nil)
|
24
24
|
context ||= Context.new
|
25
|
-
|
25
|
+
simplify(ast_function, context).evaluate(context)
|
26
26
|
end
|
27
27
|
|
28
28
|
private
|
data/lib/keisan/parser.rb
CHANGED
@@ -241,6 +241,8 @@ module Keisan
|
|
241
241
|
:"&" => Parsing::BitwiseAnd,
|
242
242
|
:"|" => Parsing::BitwiseOr,
|
243
243
|
:"^" => Parsing::BitwiseXor,
|
244
|
+
:<< => Parsing::BitwiseLeftShift,
|
245
|
+
:>> => Parsing::BitwiseRightShift,
|
244
246
|
:"==" => Parsing::LogicalEqual,
|
245
247
|
:"!=" => Parsing::LogicalNotEqual,
|
246
248
|
:"&&" => Parsing::LogicalAnd,
|
@@ -0,0 +1,235 @@
|
|
1
|
+
module Keisan
|
2
|
+
class StringAndGroupParser
|
3
|
+
class Portion
|
4
|
+
attr_reader :start_index, :end_index
|
5
|
+
|
6
|
+
def initialize(start_index)
|
7
|
+
@start_index = start_index
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class StringPortion < Portion
|
12
|
+
attr_reader :string, :escaped_string
|
13
|
+
|
14
|
+
def initialize(expression, start_index)
|
15
|
+
super(start_index)
|
16
|
+
|
17
|
+
@string = expression[start_index]
|
18
|
+
@escaped_string = expression[start_index]
|
19
|
+
@end_index = start_index + 1
|
20
|
+
|
21
|
+
while @end_index < expression.size
|
22
|
+
if expression[@end_index] == quote_type
|
23
|
+
@string << quote_type
|
24
|
+
@escaped_string << quote_type
|
25
|
+
@end_index += 1
|
26
|
+
# Successfully parsed the string
|
27
|
+
return
|
28
|
+
end
|
29
|
+
|
30
|
+
n, c = get_potentially_escaped_next_character(expression, @end_index)
|
31
|
+
@escaped_string << c
|
32
|
+
@end_index += n
|
33
|
+
end
|
34
|
+
|
35
|
+
raise Keisan::Exceptions::TokenizingError.new("Tokenizing error, no closing quote #{quote_type}")
|
36
|
+
end
|
37
|
+
|
38
|
+
def size
|
39
|
+
string.size
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_s
|
43
|
+
string
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
# Returns number of processed input characters, and the output character
|
49
|
+
# If a sequence like '\"' is encountered, the first backslash escapes the
|
50
|
+
# second double-quote, and the two characters will act as a one double-quote
|
51
|
+
# character.
|
52
|
+
def get_potentially_escaped_next_character(expression, index)
|
53
|
+
@string << expression[index]
|
54
|
+
if expression[index] == "\\" && index + 1 < expression.size
|
55
|
+
@string << expression[index + 1]
|
56
|
+
return [2, escaped_character(expression[index + 1])]
|
57
|
+
else
|
58
|
+
return [1, expression[index]]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def quote_type
|
63
|
+
@string[0]
|
64
|
+
end
|
65
|
+
|
66
|
+
def escaped_character(character)
|
67
|
+
case character
|
68
|
+
when "\\", '"', "'"
|
69
|
+
character
|
70
|
+
when "a"
|
71
|
+
"\a"
|
72
|
+
when "b"
|
73
|
+
"\b"
|
74
|
+
when "r"
|
75
|
+
"\r"
|
76
|
+
when "n"
|
77
|
+
"\n"
|
78
|
+
when "s"
|
79
|
+
"\s"
|
80
|
+
when "t"
|
81
|
+
"\t"
|
82
|
+
else
|
83
|
+
raise Keisan::Exceptions::TokenizingError.new("Tokenizing error, unknown escape character: \"\\#{character}\"")
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
class GroupPortion < Portion
|
89
|
+
attr_reader :opening_brace, :closing_brace ,:portions, :size
|
90
|
+
|
91
|
+
OPENING_TO_CLOSING_BRACE = {
|
92
|
+
"(" => ")",
|
93
|
+
"{" => "}",
|
94
|
+
"[" => "]",
|
95
|
+
}
|
96
|
+
|
97
|
+
def initialize(expression, start_index)
|
98
|
+
super(start_index)
|
99
|
+
|
100
|
+
case expression[start_index]
|
101
|
+
when OPEN_GROUP_REGEX
|
102
|
+
@opening_brace = expression[start_index]
|
103
|
+
else
|
104
|
+
raise Keisan::Exceptions::TokenizingError.new("Internal error, GroupPortion did not start with brace")
|
105
|
+
end
|
106
|
+
|
107
|
+
@closing_brace = OPENING_TO_CLOSING_BRACE[opening_brace]
|
108
|
+
|
109
|
+
parser = StringAndGroupParser.new(expression, start_index: start_index + 1, ending_character: closing_brace)
|
110
|
+
@portions = parser.portions
|
111
|
+
@size = parser.size + 2
|
112
|
+
|
113
|
+
if start_index + size > expression.size || expression[start_index + size - 1] != closing_brace
|
114
|
+
raise Keisan::Exceptions::TokenizingError.new("Tokenizing error, group with opening brace #{opening_brace} did not have closing brace")
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def to_s
|
119
|
+
opening_brace + portions.map(&:to_s).join + closing_brace
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
class OtherPortion < Portion
|
124
|
+
attr_reader :string
|
125
|
+
|
126
|
+
def initialize(expression, start_index)
|
127
|
+
super(start_index)
|
128
|
+
|
129
|
+
case expression[start_index]
|
130
|
+
when STRING_CHARACTER_REGEX, OPEN_GROUP_REGEX, CLOSED_GROUP_REGEX
|
131
|
+
raise Keisan::Exceptions::TokenizingError.new("Internal error, OtherPortion should not have string/braces at start")
|
132
|
+
else
|
133
|
+
index = start_index + 1
|
134
|
+
end
|
135
|
+
|
136
|
+
while index < expression.size
|
137
|
+
case expression[index]
|
138
|
+
when STRING_CHARACTER_REGEX, OPEN_GROUP_REGEX, CLOSED_GROUP_REGEX, COMMENT_CHARACTER_REGEX
|
139
|
+
break
|
140
|
+
else
|
141
|
+
index += 1
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
@end_index = index
|
146
|
+
@string = expression[start_index...end_index]
|
147
|
+
end
|
148
|
+
|
149
|
+
def size
|
150
|
+
string.size
|
151
|
+
end
|
152
|
+
|
153
|
+
def to_s
|
154
|
+
string
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
class CommentPortion < Portion
|
159
|
+
attr_reader :string
|
160
|
+
|
161
|
+
def initialize(expression, start_index)
|
162
|
+
super(start_index)
|
163
|
+
|
164
|
+
if expression[start_index] != '#'
|
165
|
+
raise Keisan::Exceptions::TokenizingError.new("Comment should start with '#'")
|
166
|
+
else
|
167
|
+
index = start_index + 1
|
168
|
+
end
|
169
|
+
|
170
|
+
while index < expression.size
|
171
|
+
break if expression[index] == "\n"
|
172
|
+
index += 1
|
173
|
+
end
|
174
|
+
|
175
|
+
@end_index = index
|
176
|
+
@string = expression[start_index...end_index]
|
177
|
+
end
|
178
|
+
|
179
|
+
def size
|
180
|
+
string.size
|
181
|
+
end
|
182
|
+
|
183
|
+
def to_s
|
184
|
+
string
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# An ordered array of "portions", which
|
189
|
+
attr_reader :portions, :size
|
190
|
+
|
191
|
+
COMMENT_CHARACTER_REGEX = /[#]/
|
192
|
+
STRING_CHARACTER_REGEX = /["']/
|
193
|
+
OPEN_GROUP_REGEX = /[\(\{\[]/
|
194
|
+
CLOSED_GROUP_REGEX = /[\)\}\]]/
|
195
|
+
|
196
|
+
# Ending character is used as a second ending condition besides expression size
|
197
|
+
def initialize(expression, start_index: 0, ending_character: nil)
|
198
|
+
index = start_index
|
199
|
+
@portions = []
|
200
|
+
|
201
|
+
while index < expression.size && (ending_character.nil? || expression[index] != ending_character)
|
202
|
+
case expression[index]
|
203
|
+
when STRING_CHARACTER_REGEX
|
204
|
+
portion = StringPortion.new(expression, index)
|
205
|
+
index = portion.end_index
|
206
|
+
@portions << portion
|
207
|
+
|
208
|
+
when OPEN_GROUP_REGEX
|
209
|
+
portion = GroupPortion.new(expression, index)
|
210
|
+
index += portion.size
|
211
|
+
@portions << portion
|
212
|
+
|
213
|
+
when CLOSED_GROUP_REGEX
|
214
|
+
raise Keisan::Exceptions::TokenizingError.new("Tokenizing error, unexpected closing brace #{expression[start_index]}")
|
215
|
+
|
216
|
+
when COMMENT_CHARACTER_REGEX
|
217
|
+
portion = CommentPortion.new(expression, index)
|
218
|
+
index += portion.size
|
219
|
+
@portions << portion
|
220
|
+
|
221
|
+
else
|
222
|
+
portion = OtherPortion.new(expression, index)
|
223
|
+
index += portion.size
|
224
|
+
@portions << portion
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
@size = index - start_index
|
229
|
+
end
|
230
|
+
|
231
|
+
def to_s
|
232
|
+
portions.map(&:to_s).join
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
data/lib/keisan/tokenizer.rb
CHANGED
@@ -1,13 +1,12 @@
|
|
1
1
|
module Keisan
|
2
2
|
class Tokenizer
|
3
3
|
TOKEN_CLASSES = [
|
4
|
-
Tokens::Group,
|
5
|
-
Tokens::String,
|
6
4
|
Tokens::Null,
|
7
5
|
Tokens::Boolean,
|
8
6
|
Tokens::Word,
|
9
7
|
Tokens::Number,
|
10
8
|
Tokens::Assignment,
|
9
|
+
Tokens::BitwiseShift,
|
11
10
|
Tokens::LogicalOperator,
|
12
11
|
Tokens::ArithmeticOperator,
|
13
12
|
Tokens::BitwiseOperator,
|
@@ -25,28 +24,31 @@ module Keisan
|
|
25
24
|
attr_reader :expression, :tokens
|
26
25
|
|
27
26
|
def initialize(expression)
|
28
|
-
@expression =
|
29
|
-
@scan = @expression.scan(TOKEN_REGEX)
|
30
|
-
@tokens = tokenize!
|
31
|
-
end
|
27
|
+
@expression = expression
|
32
28
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
end
|
29
|
+
portions = StringAndGroupParser.new(expression).portions.reject do |portion|
|
30
|
+
portion.is_a? StringAndGroupParser::CommentPortion
|
31
|
+
end
|
37
32
|
|
38
|
-
|
33
|
+
@tokens = portions.inject([]) do |tokens, portion|
|
34
|
+
case portion
|
35
|
+
when StringAndGroupParser::StringPortion
|
36
|
+
tokens << Tokens::String.new(portion.escaped_string)
|
37
|
+
when StringAndGroupParser::GroupPortion
|
38
|
+
tokens << Tokens::Group.new(portion.to_s)
|
39
|
+
when StringAndGroupParser::OtherPortion
|
40
|
+
scan = portion.to_s.scan(TOKEN_REGEX)
|
41
|
+
tokens += tokenize!(scan)
|
42
|
+
end
|
39
43
|
|
40
|
-
|
41
|
-
|
44
|
+
tokens
|
45
|
+
end
|
42
46
|
end
|
43
47
|
|
44
|
-
|
45
|
-
expression.gsub(/#[^;]*/, "")
|
46
|
-
end
|
48
|
+
private
|
47
49
|
|
48
|
-
def tokenize!
|
49
|
-
|
50
|
+
def tokenize!(scan)
|
51
|
+
scan.map do |scan_result|
|
50
52
|
i = scan_result.find_index {|token| !token.nil?}
|
51
53
|
token_string = scan_result[i]
|
52
54
|
token = TOKEN_CLASSES[i].new(token_string)
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Keisan
|
2
|
+
module Tokens
|
3
|
+
class BitwiseShift < Operator
|
4
|
+
LEFT_SHIFT = /(?:<<)/
|
5
|
+
RIGHT_SHIFT = /(?:>>)/
|
6
|
+
|
7
|
+
REGEX = /(#{LEFT_SHIFT}|#{RIGHT_SHIFT})/
|
8
|
+
|
9
|
+
def self.regex
|
10
|
+
REGEX
|
11
|
+
end
|
12
|
+
|
13
|
+
def operator_type
|
14
|
+
case string
|
15
|
+
when LEFT_SHIFT
|
16
|
+
:<<
|
17
|
+
when RIGHT_SHIFT
|
18
|
+
:>>
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/keisan/tokens/group.rb
CHANGED
@@ -1,19 +1,13 @@
|
|
1
1
|
module Keisan
|
2
2
|
module Tokens
|
3
3
|
class Group < Token
|
4
|
-
REGEX = /(\((?:[^\[\]\(\)\{\}]*+\g<1>*+)*+\)|\[(?:[^\[\]\(\)\{\}]*+\g<1>*+)*+\]|\{(?:[^\[\]\(\)\{\}]*+\g<1>*+)*+\})/
|
5
|
-
|
6
4
|
attr_reader :sub_tokens
|
7
5
|
|
8
6
|
def initialize(string)
|
9
|
-
|
7
|
+
@string = string
|
10
8
|
@sub_tokens = Tokenizer.new(string[1...-1]).tokens
|
11
9
|
end
|
12
10
|
|
13
|
-
def self.regex
|
14
|
-
REGEX
|
15
|
-
end
|
16
|
-
|
17
11
|
# Either :round, :square
|
18
12
|
def group_type
|
19
13
|
case string[0]
|
data/lib/keisan/tokens/string.rb
CHANGED
data/lib/keisan/version.rb
CHANGED
metadata
CHANGED
@@ -1,57 +1,57 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: keisan
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.8.
|
4
|
+
version: 0.8.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Christopher Locke
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-09-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: cmath
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '0'
|
20
|
-
type: :
|
19
|
+
version: '1.0'
|
20
|
+
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '0'
|
26
|
+
version: '1.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: bundler
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '2.0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '2.0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rake
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
47
|
+
version: '13.0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
54
|
+
version: '13.0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: rspec
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -94,6 +94,20 @@ dependencies:
|
|
94
94
|
- - ">="
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: simplecov
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
97
111
|
description: A library for parsing equations into an abstract syntax tree for evaluation
|
98
112
|
email:
|
99
113
|
- project.eutopia@gmail.com
|
@@ -117,8 +131,10 @@ files:
|
|
117
131
|
- lib/keisan/ast/arithmetic_operator.rb
|
118
132
|
- lib/keisan/ast/assignment.rb
|
119
133
|
- lib/keisan/ast/bitwise_and.rb
|
134
|
+
- lib/keisan/ast/bitwise_left_shift.rb
|
120
135
|
- lib/keisan/ast/bitwise_operator.rb
|
121
136
|
- lib/keisan/ast/bitwise_or.rb
|
137
|
+
- lib/keisan/ast/bitwise_right_shift.rb
|
122
138
|
- lib/keisan/ast/bitwise_xor.rb
|
123
139
|
- lib/keisan/ast/block.rb
|
124
140
|
- lib/keisan/ast/boolean.rb
|
@@ -219,10 +235,12 @@ files:
|
|
219
235
|
- lib/keisan/parsing/arithmetic_operator.rb
|
220
236
|
- lib/keisan/parsing/assignment.rb
|
221
237
|
- lib/keisan/parsing/bitwise_and.rb
|
238
|
+
- lib/keisan/parsing/bitwise_left_shift.rb
|
222
239
|
- lib/keisan/parsing/bitwise_not.rb
|
223
240
|
- lib/keisan/parsing/bitwise_not_not.rb
|
224
241
|
- lib/keisan/parsing/bitwise_operator.rb
|
225
242
|
- lib/keisan/parsing/bitwise_or.rb
|
243
|
+
- lib/keisan/parsing/bitwise_right_shift.rb
|
226
244
|
- lib/keisan/parsing/bitwise_xor.rb
|
227
245
|
- lib/keisan/parsing/boolean.rb
|
228
246
|
- lib/keisan/parsing/component.rb
|
@@ -266,11 +284,13 @@ files:
|
|
266
284
|
- lib/keisan/parsing/unary_plus.rb
|
267
285
|
- lib/keisan/parsing/variable.rb
|
268
286
|
- lib/keisan/repl.rb
|
287
|
+
- lib/keisan/string_and_group_parser.rb
|
269
288
|
- lib/keisan/token.rb
|
270
289
|
- lib/keisan/tokenizer.rb
|
271
290
|
- lib/keisan/tokens/arithmetic_operator.rb
|
272
291
|
- lib/keisan/tokens/assignment.rb
|
273
292
|
- lib/keisan/tokens/bitwise_operator.rb
|
293
|
+
- lib/keisan/tokens/bitwise_shift.rb
|
274
294
|
- lib/keisan/tokens/boolean.rb
|
275
295
|
- lib/keisan/tokens/colon.rb
|
276
296
|
- lib/keisan/tokens/comma.rb
|
@@ -308,8 +328,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
308
328
|
- !ruby/object:Gem::Version
|
309
329
|
version: '0'
|
310
330
|
requirements: []
|
311
|
-
|
312
|
-
rubygems_version: 2.7.7
|
331
|
+
rubygems_version: 3.0.3
|
313
332
|
signing_key:
|
314
333
|
specification_version: 4
|
315
334
|
summary: An equation parser and evaluator
|