keisan 0.7.0 → 0.8.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|