keisan 0.7.0 → 0.8.4

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