keisan 0.8.6 → 0.8.11

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 496551eafa9e9774866f8322a750ec0e0b88c463c9cb0f9174cd3461003d95a5
4
- data.tar.gz: 9d509cb07d53c12758bf3cbc1ddb45c4e75809432267cbf3215ab0ce9451404f
3
+ metadata.gz: 982db21218a4096daa15314fb7de88f1710e1a82be5b122c1b1c4a76323fb44a
4
+ data.tar.gz: 4df1eec9b4afd5da69db4a6d40d6900dc4bd9bd249dd1b67646766ba8468fd36
5
5
  SHA512:
6
- metadata.gz: a0047d2ffeea473809ce46b8e7a0c5458012a5ee27182384d28f9ce5706eeaf0385a7c6aecf4619b637760ec235d8c55f8815c537665f03af58ed388180d5fe4
7
- data.tar.gz: 29d2bf8124706bc44129abec0839bba5809678e71c50ac6b2b6d56e2584f4d7cf3f094005db1e8f6e2606334a637f6df90ab95f1a80ac8e6f9e844f67aaf032d
6
+ metadata.gz: bcc26f352e4f13139a9f0e2c0e1e97be8b3801ca6c2590349aad746fb1283776bf14d4f8ff4eb98049dfd624e7d1b71ce5be5770080f3187a4349c01a5fba4a6
7
+ data.tar.gz: 70e240222845d98e231ceb577939a37020b03ba1e3a5b9b88165a3c62ce4ffb6d5aa16cff0d4d2eed3078746b4595d3e104a2545b09f55f6d5b784dba1307bf1
@@ -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
data/README.md CHANGED
@@ -83,6 +83,18 @@ calculator.evaluate("3*x + 1")
83
83
  #=> 61
84
84
  ```
85
85
 
86
+ To perform multiple assignments, lists can be used
87
+
88
+ ```ruby
89
+ calculator = Keisan::Calculator.new
90
+ calculator.evaluate("x = [1, 2]")
91
+ calculator.evaluate("[x[1], y] = [11, 22]")
92
+ calculator.evaluate("x")
93
+ #=> [1, 11]
94
+ calculator.evaluate("y")
95
+ #=> 22
96
+ ```
97
+
86
98
 
87
99
  ##### Specifying functions
88
100
 
@@ -1,5 +1,6 @@
1
1
  require_relative "variable_assignment"
2
2
  require_relative "function_assignment"
3
+ require_relative "list_assignment"
3
4
  require_relative "cell_assignment"
4
5
 
5
6
  module Keisan
@@ -31,6 +32,8 @@ module Keisan
31
32
  evaluate_variable_assignment(context, lhs, rhs)
32
33
  elsif is_function_definition?
33
34
  evaluate_function_assignment(context, lhs, rhs)
35
+ elsif is_list_assignment?
36
+ evaluate_list_assignment(context, lhs, rhs)
34
37
  else
35
38
  # Try cell assignment
36
39
  evaluate_cell_assignment(context, lhs, rhs)
@@ -71,6 +74,10 @@ module Keisan
71
74
  children.first.is_a?(Function)
72
75
  end
73
76
 
77
+ def is_list_assignment?
78
+ children.first.is_a?(List)
79
+ end
80
+
74
81
  private
75
82
 
76
83
  def evaluate_variable_assignment(context, lhs, rhs)
@@ -82,6 +89,10 @@ module Keisan
82
89
  FunctionAssignment.new(context, lhs, rhs, local).evaluate
83
90
  end
84
91
 
92
+ def evaluate_list_assignment(context, lhs, rhs)
93
+ ListAssignment.new(self, context, lhs, rhs).evaluate
94
+ end
95
+
85
96
  def evaluate_cell_assignment(context, lhs, rhs)
86
97
  CellAssignment.new(self, context, lhs, rhs).evaluate
87
98
  end
@@ -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
@@ -0,0 +1,38 @@
1
+ module Keisan
2
+ module AST
3
+ class ListAssignment
4
+ attr_reader :assignment, :context, :lhs, :rhs
5
+
6
+ def initialize(assignment, context, lhs, rhs)
7
+ @assignment = assignment
8
+ @context = context
9
+ @lhs = lhs
10
+ @rhs = rhs
11
+ end
12
+
13
+ def evaluate
14
+ rhs = @rhs.evaluate(context)
15
+
16
+ if !rhs.is_a?(List)
17
+ raise Exceptions::InvalidExpression.new("To do multiple assignment, RHS must be a list")
18
+ end
19
+ if lhs.children.size != rhs.children.size
20
+ raise Exceptions::InvalidExpression.new("To do multiple assignment, RHS list must have same length as LHS list")
21
+ end
22
+
23
+ i = 0
24
+ while i < lhs.children.size
25
+ lhs_variable = lhs.children[i]
26
+ rhs_assignment = rhs.children[i]
27
+ individual_assignment = Assignment.new(
28
+ children = [lhs_variable, rhs_assignment],
29
+ local: assignment.local,
30
+ compound_operator: assignment.compound_operator
31
+ )
32
+ individual_assignment.evaluate(context)
33
+ i += 1
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -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
@@ -1,3 +1,5 @@
1
+ require "set"
2
+
1
3
  module Keisan
2
4
  module Variables
3
5
  class Registry
@@ -5,7 +7,7 @@ module Keisan
5
7
 
6
8
  def initialize(variables: {}, shadowed: [], parent: nil, use_defaults: true, force: false)
7
9
  @hash = {}
8
- @shadowed = Set.new(shadowed.map(&:to_s))
10
+ @shadowed = ::Set.new(shadowed.map(&:to_s))
9
11
  @parent = parent
10
12
  @use_defaults = use_defaults
11
13
 
@@ -1,3 +1,3 @@
1
1
  module Keisan
2
- VERSION = "0.8.6"
2
+ VERSION = "0.8.11"
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.6
4
+ version: 0.8.11
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-10-26 00:00:00.000000000 Z
11
+ date: 2021-05-20 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
@@ -151,6 +151,7 @@ files:
151
151
  - lib/keisan/ast/indexing.rb
152
152
  - lib/keisan/ast/line_builder.rb
153
153
  - lib/keisan/ast/list.rb
154
+ - lib/keisan/ast/list_assignment.rb
154
155
  - lib/keisan/ast/literal.rb
155
156
  - lib/keisan/ast/logical_and.rb
156
157
  - lib/keisan/ast/logical_equal.rb
@@ -328,7 +329,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
328
329
  - !ruby/object:Gem::Version
329
330
  version: '0'
330
331
  requirements: []
331
- rubygems_version: 3.0.3
332
+ rubygems_version: 3.2.15
332
333
  signing_key:
333
334
  specification_version: 4
334
335
  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