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.
Files changed (69) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +6 -3
  3. data/README.md +47 -3
  4. data/keisan.gemspec +5 -5
  5. data/lib/keisan.rb +9 -3
  6. data/lib/keisan/ast.rb +25 -0
  7. data/lib/keisan/ast/bitwise_left_shift.rb +17 -0
  8. data/lib/keisan/ast/bitwise_right_shift.rb +17 -0
  9. data/lib/keisan/ast/block.rb +4 -0
  10. data/lib/keisan/ast/boolean.rb +1 -1
  11. data/lib/keisan/ast/builder.rb +2 -2
  12. data/lib/keisan/ast/cell.rb +10 -0
  13. data/lib/keisan/ast/date.rb +23 -0
  14. data/lib/keisan/ast/date_time_methods.rb +75 -0
  15. data/lib/keisan/ast/function.rb +9 -0
  16. data/lib/keisan/ast/function_assignment.rb +16 -6
  17. data/lib/keisan/ast/hash.rb +4 -0
  18. data/lib/keisan/ast/logical_and.rb +20 -3
  19. data/lib/keisan/ast/logical_equal.rb +6 -5
  20. data/lib/keisan/ast/logical_greater_than.rb +6 -4
  21. data/lib/keisan/ast/logical_greater_than_or_equal_to.rb +6 -4
  22. data/lib/keisan/ast/logical_less_than.rb +6 -4
  23. data/lib/keisan/ast/logical_less_than_or_equal_to.rb +6 -4
  24. data/lib/keisan/ast/logical_not_equal.rb +6 -5
  25. data/lib/keisan/ast/logical_operator.rb +24 -0
  26. data/lib/keisan/ast/logical_or.rb +18 -1
  27. data/lib/keisan/ast/node.rb +25 -0
  28. data/lib/keisan/ast/number.rb +24 -0
  29. data/lib/keisan/ast/operator.rb +3 -1
  30. data/lib/keisan/ast/parent.rb +5 -1
  31. data/lib/keisan/ast/plus.rb +10 -0
  32. data/lib/keisan/ast/time.rb +23 -0
  33. data/lib/keisan/ast/unary_inverse.rb +1 -1
  34. data/lib/keisan/ast/unary_operator.rb +1 -1
  35. data/lib/keisan/ast/variable.rb +10 -9
  36. data/lib/keisan/calculator.rb +17 -3
  37. data/lib/keisan/context.rb +27 -10
  38. data/lib/keisan/evaluator.rb +16 -4
  39. data/lib/keisan/exceptions.rb +3 -0
  40. data/lib/keisan/function.rb +6 -0
  41. data/lib/keisan/functions/break.rb +11 -0
  42. data/lib/keisan/functions/cmath_function.rb +3 -1
  43. data/lib/keisan/functions/continue.rb +11 -0
  44. data/lib/keisan/functions/default_registry.rb +39 -0
  45. data/lib/keisan/functions/enumerable_function.rb +10 -2
  46. data/lib/keisan/functions/expression_function.rb +16 -9
  47. data/lib/keisan/functions/filter.rb +6 -0
  48. data/lib/keisan/functions/loop_control_flow_function.rb +22 -0
  49. data/lib/keisan/functions/map.rb +6 -0
  50. data/lib/keisan/functions/proc_function.rb +2 -2
  51. data/lib/keisan/functions/reduce.rb +5 -0
  52. data/lib/keisan/functions/replace.rb +6 -6
  53. data/lib/keisan/functions/while.rb +7 -1
  54. data/lib/keisan/parser.rb +7 -5
  55. data/lib/keisan/parsing/bitwise_left_shift.rb +9 -0
  56. data/lib/keisan/parsing/bitwise_right_shift.rb +9 -0
  57. data/lib/keisan/parsing/function.rb +1 -1
  58. data/lib/keisan/parsing/hash.rb +2 -2
  59. data/lib/keisan/string_and_group_parser.rb +229 -0
  60. data/lib/keisan/token.rb +1 -1
  61. data/lib/keisan/tokenizer.rb +20 -18
  62. data/lib/keisan/tokens/assignment.rb +3 -1
  63. data/lib/keisan/tokens/bitwise_shift.rb +23 -0
  64. data/lib/keisan/tokens/group.rb +1 -7
  65. data/lib/keisan/tokens/string.rb +2 -4
  66. data/lib/keisan/util.rb +19 -0
  67. data/lib/keisan/variables/default_registry.rb +2 -1
  68. data/lib/keisan/version.rb +1 -1
  69. metadata +40 -28
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 900d86c9daad1cab75e6140a21785f6de67ead1c
4
- data.tar.gz: f2c5f2abc85d46ce71aa651f0cc860363a8790bf
2
+ SHA256:
3
+ metadata.gz: de85515fc642eede46d24bdb2347a7f8bbe32c4d317fd305404c08367e742ecf
4
+ data.tar.gz: 0ce75e5555ca880e690e1cae4b76820594899706aa5608944fa5c9eedd0be9c8
5
5
  SHA512:
6
- metadata.gz: 4f5e7ea1fd844e7927af6eb5b66edacc53dac30541cb670d6208e43d0a9edb4611417943be232ba8188c275fa8890ee3c3e7f43a874ae32085f80b05092ecef6
7
- data.tar.gz: 588daa93a53120d0456f38e66f5fa27d50b5e517516c3e0747519e51aeb5ca49dc317187e85ec420e5eec5bc722732138499e16188bf7271afbc4481ba1980bd
6
+ metadata.gz: cd6b7cee90edee2d0ba51eb6b4d0a223305eeaf75a85ec6c2c6dbd9c3310ecd035c00d4a86d16768303f28d61996291e6e4d3ac7e5323a3a4df0c22f6343ff58
7
+ data.tar.gz: '05873d8324f622a818f5cba2b9ef260e61f3500154db3ab0ecb9570bf308a686f4b32eddcbfb664afab76845fe71da7c82f47dd511f845f33498045011024a8a'
@@ -1,6 +1,9 @@
1
1
  sudo: false
2
2
  language: ruby
3
+ before_install:
4
+ - gem install bundler
3
5
  rvm:
4
- - 2.3.5
5
- - 2.4.2
6
- before_install: gem install bundler -v 1.16.0
6
+ - 2.4.9
7
+ - 2.5.7
8
+ - 2.6.5
9
+ - 2.7.0
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 AND `&` are also available for use
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 `I` are included.
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
@@ -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.0.0"
22
+ spec.required_ruby_version = ">= 2.3.0"
23
23
 
24
- spec.add_dependency "activesupport", ">= 4.2.2"
24
+ spec.add_dependency "cmath", "~> 1.0"
25
25
 
26
- spec.add_development_dependency "coveralls"
27
- spec.add_development_dependency "bundler", "~> 1.14"
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
@@ -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"
@@ -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
@@ -17,6 +17,10 @@ module Keisan
17
17
  child.unbound_functions(local)
18
18
  end
19
19
 
20
+ def contains_a?(klass)
21
+ super || child.contains_a?(klass)
22
+ end
23
+
20
24
  def deep_dup
21
25
  dupped = dup
22
26
  dupped.instance_variable_set(
@@ -12,7 +12,7 @@ module Keisan
12
12
  end
13
13
 
14
14
  def true?
15
- false
15
+ bool
16
16
  end
17
17
 
18
18
  def !
@@ -12,10 +12,10 @@ module Keisan
12
12
  elsif !parser.nil?
13
13
  @components = parser.components
14
14
  else
15
- @components = Array.wrap(components)
15
+ @components = Array(components)
16
16
  end
17
17
 
18
- @lines = @components.split {|component|
18
+ @lines = Util.array_split(@components) {|component|
19
19
  component.is_a?(Parsing::LineSeparator)
20
20
  }.reject(&:empty?)
21
21
 
@@ -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
@@ -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