keisan 0.7.0 → 0.8.4
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 +5 -5
- data/.travis.yml +6 -3
- data/README.md +47 -3
- data/keisan.gemspec +5 -5
- data/lib/keisan.rb +9 -3
- data/lib/keisan/ast.rb +25 -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/boolean.rb +1 -1
- data/lib/keisan/ast/builder.rb +2 -2
- data/lib/keisan/ast/cell.rb +10 -0
- data/lib/keisan/ast/date.rb +23 -0
- data/lib/keisan/ast/date_time_methods.rb +75 -0
- data/lib/keisan/ast/function.rb +9 -0
- data/lib/keisan/ast/function_assignment.rb +16 -6
- data/lib/keisan/ast/hash.rb +4 -0
- data/lib/keisan/ast/logical_and.rb +20 -3
- data/lib/keisan/ast/logical_equal.rb +6 -5
- data/lib/keisan/ast/logical_greater_than.rb +6 -4
- data/lib/keisan/ast/logical_greater_than_or_equal_to.rb +6 -4
- data/lib/keisan/ast/logical_less_than.rb +6 -4
- data/lib/keisan/ast/logical_less_than_or_equal_to.rb +6 -4
- data/lib/keisan/ast/logical_not_equal.rb +6 -5
- data/lib/keisan/ast/logical_operator.rb +24 -0
- data/lib/keisan/ast/logical_or.rb +18 -1
- data/lib/keisan/ast/node.rb +25 -0
- data/lib/keisan/ast/number.rb +24 -0
- data/lib/keisan/ast/operator.rb +3 -1
- data/lib/keisan/ast/parent.rb +5 -1
- data/lib/keisan/ast/plus.rb +10 -0
- data/lib/keisan/ast/time.rb +23 -0
- data/lib/keisan/ast/unary_inverse.rb +1 -1
- data/lib/keisan/ast/unary_operator.rb +1 -1
- data/lib/keisan/ast/variable.rb +10 -9
- data/lib/keisan/calculator.rb +17 -3
- data/lib/keisan/context.rb +27 -10
- data/lib/keisan/evaluator.rb +16 -4
- data/lib/keisan/exceptions.rb +3 -0
- data/lib/keisan/function.rb +6 -0
- data/lib/keisan/functions/break.rb +11 -0
- data/lib/keisan/functions/cmath_function.rb +3 -1
- data/lib/keisan/functions/continue.rb +11 -0
- data/lib/keisan/functions/default_registry.rb +39 -0
- data/lib/keisan/functions/enumerable_function.rb +10 -2
- data/lib/keisan/functions/expression_function.rb +16 -9
- data/lib/keisan/functions/filter.rb +6 -0
- data/lib/keisan/functions/loop_control_flow_function.rb +22 -0
- data/lib/keisan/functions/map.rb +6 -0
- data/lib/keisan/functions/proc_function.rb +2 -2
- data/lib/keisan/functions/reduce.rb +5 -0
- data/lib/keisan/functions/replace.rb +6 -6
- data/lib/keisan/functions/while.rb +7 -1
- data/lib/keisan/parser.rb +7 -5
- data/lib/keisan/parsing/bitwise_left_shift.rb +9 -0
- data/lib/keisan/parsing/bitwise_right_shift.rb +9 -0
- data/lib/keisan/parsing/function.rb +1 -1
- data/lib/keisan/parsing/hash.rb +2 -2
- data/lib/keisan/string_and_group_parser.rb +229 -0
- data/lib/keisan/token.rb +1 -1
- 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/util.rb +19 -0
- data/lib/keisan/variables/default_registry.rb +2 -1
- data/lib/keisan/version.rb +1 -1
- metadata +40 -28
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: de85515fc642eede46d24bdb2347a7f8bbe32c4d317fd305404c08367e742ecf
|
4
|
+
data.tar.gz: 0ce75e5555ca880e690e1cae4b76820594899706aa5608944fa5c9eedd0be9c8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cd6b7cee90edee2d0ba51eb6b4d0a223305eeaf75a85ec6c2c6dbd9c3310ecd035c00d4a86d16768303f28d61996291e6e4d3ac7e5323a3a4df0c22f6343ff58
|
7
|
+
data.tar.gz: '05873d8324f622a818f5cba2b9ef260e61f3500154db3ab0ecb9570bf308a686f4b32eddcbfb664afab76845fe71da7c82f47dd511f845f33498045011024a8a'
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -9,7 +9,7 @@
|
|
9
9
|
|
10
10
|
Keisan ([計算, to calculate](https://en.wiktionary.org/wiki/%E8%A8%88%E7%AE%97#Japanese)) is a Ruby library for parsing equations into an abstract syntax tree.
|
11
11
|
This allows for safe evaluation of string representations of mathematical/logical expressions.
|
12
|
-
It has support for variables, functions, conditionals, and loops, making it a Turing complete programming language.
|
12
|
+
It has support for variables, functions, conditionals, and loops, making it a [Turing complete](https://github.com/project-eutopia/keisan/blob/master/spec/keisan/turing_machine_spec.rb) programming language.
|
13
13
|
|
14
14
|
## Installation
|
15
15
|
|
@@ -119,6 +119,9 @@ calculator.evaluate("f(0-a)", a: 2)
|
|
119
119
|
#=> -20
|
120
120
|
calculator.evaluate("n") # n only exists in the definition of f(x)
|
121
121
|
#=> Keisan::Exceptions::UndefinedVariableError: n
|
122
|
+
calculator.evaluate("includes(a, element) = a.reduce(false, found, x, found || (x == element))")
|
123
|
+
calculator.evaluate("[3, 9].map(x, [1, 3, 5].includes(x))").value
|
124
|
+
#=> [true, false]
|
122
125
|
```
|
123
126
|
|
124
127
|
This form even supports recursion, but you must explicitly allow it.
|
@@ -248,6 +251,42 @@ calculator.evaluate("range(1, 6).map(x, [x, x**2]).to_h")
|
|
248
251
|
#=> {1 => 1, 2 => 4, 3 => 9, 4 => 16, 5 => 25}
|
249
252
|
```
|
250
253
|
|
254
|
+
##### Date and time objects
|
255
|
+
|
256
|
+
Keisan supports date and time objects like in Ruby.
|
257
|
+
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
|
+
They support methods `year`, `month`, `day`, `weekday`, `strftime`, and `to_time` to convert to a time object.
|
259
|
+
`epoch_days` computes the number of days since Unix epoch (Jan 1, 1970).
|
260
|
+
|
261
|
+
```ruby
|
262
|
+
calculator = Keisan::Calculator.new
|
263
|
+
calculator.evaluate("x = 11")
|
264
|
+
calculator.evaluate("(5 + date(2018, x, 2*x)).day")
|
265
|
+
#=> 27
|
266
|
+
calculator.evaluate("today() > date(2018, 11, 1)")
|
267
|
+
#=> true
|
268
|
+
calculator.evaluate("date('1999-12-31').to_time + 10")
|
269
|
+
#=> Time.new(1999, 12, 31, 0, 0, 10)
|
270
|
+
calculator.evaluate("date(1970, 1, 15).epoch_days")
|
271
|
+
#=> 14
|
272
|
+
```
|
273
|
+
|
274
|
+
Time objects are created using `time` (either a string to be parsed, or year, month, day, hour, minute, second arguments) or `now`.
|
275
|
+
They support methods `year`, `month`, `day`, `hour`, `minute`, `second`, `weekday`, `strftime`, and `to_date` to convert to a date object.
|
276
|
+
`epoch_seconds` computes the number of seconds since Unix epoch (00:00:00 on Jan 1, 1970).
|
277
|
+
|
278
|
+
```ruby
|
279
|
+
calculator = Keisan::Calculator.new
|
280
|
+
calculator.evaluate("time(2018, 11, 22, 12, 0, 0).to_date <= date(2018, 11, 22)")
|
281
|
+
#=> true
|
282
|
+
calculator.evaluate("time('2000-4-15 12:34:56').minute")
|
283
|
+
#=> 34
|
284
|
+
calculator.evaluate("time('5000-10-10 20:30:40').strftime('%b %d, %Y')")
|
285
|
+
#=> "Oct 10, 5000"
|
286
|
+
calculator.evaluate("time(1970, 1, 1, 2, 3, 4).epoch_seconds")
|
287
|
+
#=> 7384
|
288
|
+
```
|
289
|
+
|
251
290
|
##### Functional programming methods
|
252
291
|
|
253
292
|
Keisan also supports the basic functional programming operators `map` (or `collect`), `filter` (or `select`), and `reduce` (or `inject`).
|
@@ -293,17 +332,21 @@ calculator.evaluate("2 + if(1 > 0, 10, 29)")
|
|
293
332
|
```
|
294
333
|
|
295
334
|
For looping, you can use the basic `while` loop, which has an expression that evaluates to a boolean as the first argument, and any expression in the second argument.
|
335
|
+
One can use the keywords `break` and `continue` to control loop flow as well.
|
296
336
|
|
297
337
|
```ruby
|
298
338
|
calculator = Keisan::Calculator.new
|
299
339
|
calculator.evaluate("my_sum(a) = {let i = 0; let total = 0; while(i < a.size, {total += a[i]; i += 1}); total}")
|
300
340
|
calculator.evaluate("my_sum([1,3,5,7,9])")
|
301
341
|
#=> 25
|
342
|
+
calculator.evaluate("has_element(a, x) = {let i=0; let found=false; while(i<a.size, if(a[i] == x, found = true; break); i+=1); found}")
|
343
|
+
calculator.evaluate("[2, 3, 7, 11].has_element(11)")
|
344
|
+
#=> true
|
302
345
|
```
|
303
346
|
|
304
347
|
##### Bitwise operations
|
305
348
|
|
306
|
-
The basic bitwise operations, NOT `~`, OR `|`, XOR `^`, and
|
349
|
+
The basic bitwise operations, NOT `~`, OR `|`, XOR `^`, AND `&`, and left/right bitwise shifts (`<<` and `>>`) are also available for use
|
307
350
|
|
308
351
|
```ruby
|
309
352
|
calculator = Keisan::Calculator.new
|
@@ -368,7 +411,7 @@ calculator.evaluate("log10(1000)")
|
|
368
411
|
#=> 3.0
|
369
412
|
```
|
370
413
|
|
371
|
-
Furthermore, the constants `PI`, `E`, and `
|
414
|
+
Furthermore, the constants `PI`, `E`, `I`, and `INF` are included.
|
372
415
|
|
373
416
|
```ruby
|
374
417
|
calculator = Keisan::Calculator.new
|
@@ -448,6 +491,7 @@ calculator.evaluate("puts x**2") # prints "25\n" to STDOUT
|
|
448
491
|
|
449
492
|
#### Bitwise operators
|
450
493
|
- `&`, `|`, `^`: bitwise **and**, **or**, **xor** operators
|
494
|
+
- `<<`, `>>` bitwise shift operators
|
451
495
|
- `~`: unary bitwise not
|
452
496
|
|
453
497
|
#### Indexing of arrays/hashes
|
data/keisan.gemspec
CHANGED
@@ -19,14 +19,14 @@ Gem::Specification.new do |spec|
|
|
19
19
|
end
|
20
20
|
spec.require_paths = ["lib"]
|
21
21
|
|
22
|
-
spec.required_ruby_version = ">= 2.
|
22
|
+
spec.required_ruby_version = ">= 2.3.0"
|
23
23
|
|
24
|
-
spec.add_dependency "
|
24
|
+
spec.add_dependency "cmath", "~> 1.0"
|
25
25
|
|
26
|
-
spec.add_development_dependency "
|
27
|
-
spec.add_development_dependency "
|
28
|
-
spec.add_development_dependency "rake", "~> 10.0"
|
26
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
27
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
29
28
|
spec.add_development_dependency "rspec", "~> 3.0"
|
30
29
|
spec.add_development_dependency "pry"
|
31
30
|
spec.add_development_dependency "pry-stack_explorer"
|
31
|
+
spec.add_development_dependency "simplecov"
|
32
32
|
end
|
data/lib/keisan.rb
CHANGED
@@ -1,8 +1,6 @@
|
|
1
|
-
require "active_support"
|
2
|
-
require "active_support/core_ext"
|
3
|
-
|
4
1
|
require "keisan/version"
|
5
2
|
require "keisan/exceptions"
|
3
|
+
require "keisan/util"
|
6
4
|
|
7
5
|
require "keisan/ast/node"
|
8
6
|
require "keisan/ast/cell"
|
@@ -14,6 +12,8 @@ require "keisan/ast/number"
|
|
14
12
|
require "keisan/ast/string"
|
15
13
|
require "keisan/ast/null"
|
16
14
|
require "keisan/ast/boolean"
|
15
|
+
require "keisan/ast/date"
|
16
|
+
require "keisan/ast/time"
|
17
17
|
|
18
18
|
require "keisan/ast/block"
|
19
19
|
require "keisan/ast/parent"
|
@@ -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"
|
data/lib/keisan/ast.rb
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
require "date"
|
2
|
+
require "time"
|
3
|
+
|
1
4
|
module Keisan
|
2
5
|
module AST
|
3
6
|
def self.parse(expression)
|
@@ -76,6 +79,26 @@ module KeisanHash
|
|
76
79
|
end
|
77
80
|
end
|
78
81
|
|
82
|
+
module KeisanDate
|
83
|
+
def to_node
|
84
|
+
Keisan::AST::Date.new(self)
|
85
|
+
end
|
86
|
+
|
87
|
+
def value(context = nil)
|
88
|
+
self
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
module KeisanTime
|
93
|
+
def to_node
|
94
|
+
Keisan::AST::Time.new(self)
|
95
|
+
end
|
96
|
+
|
97
|
+
def value(context = nil)
|
98
|
+
self
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
79
102
|
class Numeric; prepend KeisanNumeric; end
|
80
103
|
class String; prepend KeisanString; end
|
81
104
|
class TrueClass; prepend KeisanTrueClass; end
|
@@ -83,3 +106,5 @@ class FalseClass; prepend KeisanFalseClass; end
|
|
83
106
|
class NilClass; prepend KeisanNilClass; end
|
84
107
|
class Array; prepend KeisanArray; end
|
85
108
|
class Hash; prepend KeisanHash; end
|
109
|
+
class Date; prepend KeisanDate; end
|
110
|
+
class Time; prepend KeisanTime; end
|
@@ -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/boolean.rb
CHANGED
data/lib/keisan/ast/builder.rb
CHANGED
@@ -12,10 +12,10 @@ module Keisan
|
|
12
12
|
elsif !parser.nil?
|
13
13
|
@components = parser.components
|
14
14
|
else
|
15
|
-
@components = Array
|
15
|
+
@components = Array(components)
|
16
16
|
end
|
17
17
|
|
18
|
-
@lines = @components
|
18
|
+
@lines = Util.array_split(@components) {|component|
|
19
19
|
component.is_a?(Parsing::LineSeparator)
|
20
20
|
}.reject(&:empty?)
|
21
21
|
|
data/lib/keisan/ast/cell.rb
CHANGED
@@ -15,6 +15,10 @@ module Keisan
|
|
15
15
|
node.unbound_functions(context)
|
16
16
|
end
|
17
17
|
|
18
|
+
def contains_a?(klass)
|
19
|
+
super || node.contains_a?(klass)
|
20
|
+
end
|
21
|
+
|
18
22
|
def deep_dup
|
19
23
|
dupped = dup
|
20
24
|
dupped.instance_variable_set(
|
@@ -72,6 +76,12 @@ module Keisan
|
|
72
76
|
def to_node
|
73
77
|
node
|
74
78
|
end
|
79
|
+
|
80
|
+
%i(< <= > >= equal not_equal).each do |sym|
|
81
|
+
define_method(sym) {|other|
|
82
|
+
node.send(sym, other.to_node)
|
83
|
+
}
|
84
|
+
end
|
75
85
|
end
|
76
86
|
end
|
77
87
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require_relative "date_time_methods"
|
2
|
+
|
3
|
+
module Keisan
|
4
|
+
module AST
|
5
|
+
class Date < ConstantLiteral
|
6
|
+
include DateTimeMethods
|
7
|
+
|
8
|
+
attr_reader :date
|
9
|
+
|
10
|
+
def initialize(date)
|
11
|
+
@date = date
|
12
|
+
end
|
13
|
+
|
14
|
+
def value(context = nil)
|
15
|
+
date
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
value.to_s
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Keisan
|
2
|
+
module AST
|
3
|
+
module DateTimeMethods
|
4
|
+
def +(other)
|
5
|
+
other = other.to_node
|
6
|
+
case other
|
7
|
+
when Number
|
8
|
+
self.class.new(value + other.value)
|
9
|
+
else
|
10
|
+
super
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def >(other)
|
15
|
+
other = other.to_node
|
16
|
+
case other
|
17
|
+
when self.class
|
18
|
+
Boolean.new(value.to_time > other.value.to_time)
|
19
|
+
else
|
20
|
+
super
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def <(other)
|
25
|
+
other = other.to_node
|
26
|
+
case other
|
27
|
+
when self.class
|
28
|
+
Boolean.new(value.to_time < other.value.to_time)
|
29
|
+
else
|
30
|
+
super
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def >=(other)
|
35
|
+
other = other.to_node
|
36
|
+
case other
|
37
|
+
when self.class
|
38
|
+
Boolean.new(value.to_time >= other.value.to_time)
|
39
|
+
else
|
40
|
+
super
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def <=(other)
|
45
|
+
other = other.to_node
|
46
|
+
case other
|
47
|
+
when self.class
|
48
|
+
Boolean.new(value.to_time <= other.value.to_time)
|
49
|
+
else
|
50
|
+
super
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def equal(other)
|
55
|
+
other = other.to_node
|
56
|
+
case other
|
57
|
+
when self.class
|
58
|
+
Boolean.new(value.to_time == other.value.to_time)
|
59
|
+
else
|
60
|
+
super
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def not_equal(other)
|
65
|
+
other = other.to_node
|
66
|
+
case other
|
67
|
+
when self.class
|
68
|
+
Boolean.new(value.to_time != other.value.to_time)
|
69
|
+
else
|
70
|
+
super
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/lib/keisan/ast/function.rb
CHANGED
@@ -13,6 +13,15 @@ module Keisan
|
|
13
13
|
function_from_context(context).value(self, context)
|
14
14
|
end
|
15
15
|
|
16
|
+
def unbound_variables(context = nil)
|
17
|
+
context ||= Context.new
|
18
|
+
if context.has_function?(name)
|
19
|
+
function_from_context(context).unbound_variables(children, context)
|
20
|
+
else
|
21
|
+
super
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
16
25
|
def unbound_functions(context = nil)
|
17
26
|
context ||= Context.new
|
18
27
|
|