keisan 0.8.5 → 0.8.10

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a2a155be79ae75b3c67a097791c2af2bf67196cae16ef5372049b4eb54884117
4
- data.tar.gz: b5d38c6111d1803a14c0229b0f54dc8733d581409254068ac9970af1321d9fca
3
+ metadata.gz: 9f97e020e01a4789ca6f181ac1cd2556d4338297d0d59ed89e4c6e29cf90fe27
4
+ data.tar.gz: 3b06e9483c5f7ac0f38b32ce925a27b06c0de2e8df8cbe5b45b5262c341bdf80
5
5
  SHA512:
6
- metadata.gz: 550d595b64739c49e6fdcd12b149fa0f5706d3032f11c61f6f9b9bd3ac6ff7465398a5b04f58868dca803c9cef1b9fb14f9df029bc03c550d8616760751f3886
7
- data.tar.gz: d2c25f61cdd3400c198cb6962cd31c0023ef5aa0081d3a0ea2699b7e7098620d8d39dc8af26bee5fe10197f84fb91218763fdc6ba830d2c97b30122a1b30fd3d
6
+ metadata.gz: d2814acec39cba71faa5c9e3ce924ff17361b826fd092c01a82a54097992be7d28238ee127ada33f5577496ce573cd3da9947c65a6a09956ece8f66a524dd58a
7
+ data.tar.gz: a64666d0b022ca0352054b2a8c2c9d1e7cd0b01b4a569e52f28d86fd034103360a854d7eef60da0d0f8452c959b6859a051446762059457f6bf155b2ac6868f5
@@ -0,0 +1,28 @@
1
+ name: Ruby
2
+
3
+ on:
4
+ push:
5
+ branches: [ master ]
6
+ pull_request:
7
+ branches: [ master ]
8
+
9
+ jobs:
10
+ test:
11
+
12
+ runs-on: ubuntu-latest
13
+
14
+ strategy:
15
+ matrix:
16
+ ruby-version: [2.3, 2.4, 2.5, 2.6, 2.7, 3.0]
17
+
18
+ steps:
19
+ - uses: actions/checkout@v2
20
+ - name: Set up Ruby ${{ matrix.ruby-version }}
21
+ uses: ruby/setup-ruby@v1
22
+ with:
23
+ ruby-version: ${{ matrix.ruby-version }}
24
+ bundler-cache: true
25
+ - name: Install dependencies
26
+ run: bundle install
27
+ - name: Run tests
28
+ run: bundle exec rspec
@@ -17,8 +17,8 @@ module Keisan
17
17
  child.unbound_functions(local)
18
18
  end
19
19
 
20
- def contains_a?(klass)
21
- super || child.contains_a?(klass)
20
+ def traverse(&block)
21
+ super(&block) || child.traverse(&block)
22
22
  end
23
23
 
24
24
  def deep_dup
@@ -15,8 +15,8 @@ module Keisan
15
15
  node.unbound_functions(context)
16
16
  end
17
17
 
18
- def contains_a?(klass)
19
- super || node.contains_a?(klass)
18
+ def traverse(&block)
19
+ super(&block) || node.traverse(&block)
20
20
  end
21
21
 
22
22
  def deep_dup
@@ -30,7 +30,11 @@ module Keisan
30
30
  private
31
31
 
32
32
  def lhs_evaluate_and_check_modifiable
33
- lhs.evaluate(context)
33
+ res = lhs.evaluate(context)
34
+ if res.frozen?
35
+ raise Exceptions::UnmodifiableError.new("Cannot modify frozen variables")
36
+ end
37
+ res
34
38
  rescue RuntimeError => e
35
39
  raise Exceptions::UnmodifiableError.new("Cannot modify frozen variables") if e.message =~ /can't modify frozen/
36
40
  raise
@@ -21,11 +21,20 @@ module Keisan
21
21
  end
22
22
  end
23
23
 
24
- def contains_a?(klass)
25
- super || @hash.any? {|k, v| k.to_node.contains_a?(klass) || v.contains_a?(klass) }
24
+ def traverse(&block)
25
+ value = super(&block)
26
+ return value if value
27
+ @hash.each do |k, v|
28
+ value = k.to_node.traverse(&block)
29
+ return value if value
30
+ value = v.traverse(&block)
31
+ return value if value
32
+ end
33
+ false
26
34
  end
27
35
 
28
36
  def evaluate(context = nil)
37
+ return self if frozen?
29
38
  context ||= Context.new
30
39
 
31
40
  @hash = ::Hash[
@@ -6,6 +6,7 @@ module Keisan
6
6
  end
7
7
 
8
8
  def evaluate(context = nil)
9
+ return self if frozen?
9
10
  context ||= Context.new
10
11
  @children = children.map {|child| child.is_a?(Cell) ? child : child.evaluate(context)}
11
12
  self
@@ -37,12 +37,26 @@ module Keisan
37
37
  value(context)
38
38
  end
39
39
 
40
+ # Takes a block, and does a DFS down the AST, evaluating the received block
41
+ # at each node, passing in the node as the single argument. If the block
42
+ # returns a truthy value at any point, the DFS ends and the return value is
43
+ # percolated up the tree.
44
+ def traverse(&block)
45
+ block.call(self)
46
+ end
47
+
40
48
  def contains_a?(klass)
41
49
  case klass
42
50
  when Array
43
- klass.any? {|k| is_a?(k) }
51
+ klass.any? do |k|
52
+ traverse do |node|
53
+ node.is_a?(k)
54
+ end
55
+ end
44
56
  else
45
- is_a?(klass)
57
+ traverse do |node|
58
+ node.is_a?(klass)
59
+ end
46
60
  end
47
61
  end
48
62
 
@@ -5,6 +5,11 @@ module Keisan
5
5
 
6
6
  def initialize(number)
7
7
  @number = number
8
+ # Reduce the number if possible
9
+ case @number
10
+ when Rational
11
+ @number = @number.numerator if @number.denominator == 1
12
+ end
8
13
  end
9
14
 
10
15
  def value(context = nil)
@@ -25,8 +25,14 @@ module Keisan
25
25
  end
26
26
  end
27
27
 
28
- def contains_a?(klass)
29
- super || children.any? {|child| child.contains_a?(klass) }
28
+ def traverse(&block)
29
+ value = super(&block)
30
+ return value if value
31
+ children.each do |child|
32
+ value = child.traverse(&block)
33
+ return value if value
34
+ end
35
+ false
30
36
  end
31
37
 
32
38
  def freeze
@@ -4,11 +4,12 @@ module Keisan
4
4
 
5
5
  # Note, allow_recursive would be more appropriately named:
6
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)
7
+ def initialize(context: nil, allow_recursive: false, allow_blocks: true, allow_multiline: true, allow_random: true)
8
8
  @context = context || Context.new(
9
9
  allow_recursive: allow_recursive,
10
10
  allow_blocks: allow_blocks,
11
- allow_multiline: allow_multiline
11
+ allow_multiline: allow_multiline,
12
+ allow_random: allow_random
12
13
  )
13
14
  end
14
15
 
@@ -28,6 +29,10 @@ module Keisan
28
29
  context.allow_multiline
29
30
  end
30
31
 
32
+ def allow_random
33
+ context.allow_random
34
+ end
35
+
31
36
  def evaluate(expression, definitions = {})
32
37
  Evaluator.new(self).evaluate(expression, definitions)
33
38
  end
@@ -4,13 +4,15 @@ module Keisan
4
4
  :variable_registry,
5
5
  :allow_recursive,
6
6
  :allow_multiline,
7
- :allow_blocks
7
+ :allow_blocks,
8
+ :allow_random
8
9
 
9
10
  def initialize(parent: nil,
10
11
  random: nil,
11
12
  allow_recursive: false,
12
13
  allow_multiline: true,
13
14
  allow_blocks: true,
15
+ allow_random: true,
14
16
  shadowed: [])
15
17
  @parent = parent
16
18
  @function_registry = Functions::Registry.new(parent: @parent&.function_registry)
@@ -19,6 +21,7 @@ module Keisan
19
21
  @allow_recursive = allow_recursive
20
22
  @allow_multiline = allow_multiline
21
23
  @allow_blocks = allow_blocks
24
+ @allow_random = allow_random
22
25
  end
23
26
 
24
27
  def allow_recursive!
@@ -111,7 +114,13 @@ module Keisan
111
114
  end
112
115
 
113
116
  def random
114
- @random || @parent&.random || Random.new
117
+ raise Keisan::Exceptions::InvalidExpression.new("Context does not permit expressions with randomness") unless allow_random
118
+ @random ||= @parent&.random || Random.new
119
+ end
120
+
121
+ def set_random(random)
122
+ raise Keisan::Exceptions::InvalidExpression.new("Context does not permit expressions with randomness") unless allow_random
123
+ @random = random
115
124
  end
116
125
 
117
126
  protected
@@ -21,14 +21,17 @@ module Keisan
21
21
  context ||= Context.new
22
22
 
23
23
  operand, arguments, expression = operand_arguments_expression_for(ast_function, context)
24
+
25
+ # Extract underlying operand for cells
26
+ real_operand = operand.is_a?(AST::Cell) ? operand.node : operand
24
27
 
25
- case operand
28
+ case real_operand
26
29
  when AST::List
27
- evaluate_list(operand, arguments, expression, context).evaluate(context)
30
+ evaluate_list(real_operand, arguments, expression, context).evaluate(context)
28
31
  when AST::Hash
29
- evaluate_hash(operand, arguments, expression, context).evaluate(context)
32
+ evaluate_hash(real_operand, arguments, expression, context).evaluate(context)
30
33
  else
31
- raise Exceptions::InvalidFunctionError.new("Unhandled first argument to #{name}: #{operand}")
34
+ raise Exceptions::InvalidFunctionError.new("Unhandled first argument to #{name}: #{real_operand}")
32
35
  end
33
36
  end
34
37
 
@@ -1,3 +1,3 @@
1
1
  module Keisan
2
- VERSION = "0.8.5"
2
+ VERSION = "0.8.10"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: keisan
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.5
4
+ version: 0.8.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Christopher Locke
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-09-01 00:00:00.000000000 Z
11
+ date: 2021-05-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cmath
@@ -115,9 +115,9 @@ executables: []
115
115
  extensions: []
116
116
  extra_rdoc_files: []
117
117
  files:
118
+ - ".github/workflows/ruby.yml"
118
119
  - ".gitignore"
119
120
  - ".rspec"
120
- - ".travis.yml"
121
121
  - Gemfile
122
122
  - MIT-LICENSE
123
123
  - README.md
@@ -328,7 +328,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
328
328
  - !ruby/object:Gem::Version
329
329
  version: '0'
330
330
  requirements: []
331
- rubygems_version: 3.0.3
331
+ rubygems_version: 3.2.15
332
332
  signing_key:
333
333
  specification_version: 4
334
334
  summary: An equation parser and evaluator
data/.travis.yml DELETED
@@ -1,9 +0,0 @@
1
- sudo: false
2
- language: ruby
3
- before_install:
4
- - gem install bundler
5
- rvm:
6
- - 2.4.9
7
- - 2.5.7
8
- - 2.6.5
9
- - 2.7.0