keisan 0.8.8 → 0.8.13

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: d539ead6f33b2151b228dfe6c30a0e3f7d40bb7b1113c90b851d020e04106df5
4
- data.tar.gz: c36af3538e6f448c285b9857aa5ea14e3bc90853e5d3f4a74fd726d8b0c35068
3
+ metadata.gz: '05596336b8e5f1de3f7787b6a36120152f4e13c8caa822da225fe989f6af3c72'
4
+ data.tar.gz: 9a4d79fbc8ed709ec7d0f9b90d0e31f4f3e65c7e43b067800b66a0ea7c568ffb
5
5
  SHA512:
6
- metadata.gz: 67557029c43754d862406fc0491594b94dcc4dac5a925e518232accfd75c42582f8e5d11b5f99eec7f44dcb1ea29ea31f087668bf70a68c56fa25fc4ca8f2929
7
- data.tar.gz: 2451bd2dbe844af1bc3a4f2ec9d0819aeb4df52f96bd45dbbc5ada1993b2c6003245c8f6191ce3f5dc59de18b106f313e07dedd3aef9ba670525d4bacabfb43a
6
+ metadata.gz: d040a4d12325c74a8775d7a87a7d450fd0218de3e639b7ac43fe53bbb1310000cd53207e98727266f59535c5f7347d74392b941b66ad10359d97c515d5e02f75
7
+ data.tar.gz: 81ce4fe88b6d215a194bd8cf45388957689353c275cb8e60ec921a26eac8cb86d10c067ed34e09f6f303482f2b913005bc07c3448aed413d291b3beb66ecbf4f
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
@@ -66,7 +66,7 @@ module Keisan
66
66
  end
67
67
 
68
68
  def to_cell
69
- self.class.new(node.to_cell)
69
+ self.class.new(node.to_node)
70
70
  end
71
71
 
72
72
  def to_s
@@ -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
@@ -22,6 +22,170 @@ module Keisan
22
22
  value.to_s
23
23
  end
24
24
  end
25
+
26
+ def is_constant?
27
+ true
28
+ end
29
+
30
+ def +(other)
31
+ if other.is_constant?
32
+ raise Keisan::Exceptions::InvalidExpression.new("Cannot add #{self.class} to #{other.class}")
33
+ else
34
+ super
35
+ end
36
+ end
37
+
38
+ def -(other)
39
+ if other.is_constant?
40
+ raise Keisan::Exceptions::InvalidExpression.new("Cannot subtract #{self.class} from #{other.class}")
41
+ else
42
+ super
43
+ end
44
+ end
45
+
46
+ def *(other)
47
+ if other.is_constant?
48
+ raise Keisan::Exceptions::InvalidExpression.new("Cannot multiply #{self.class} and #{other.class}")
49
+ else
50
+ super
51
+ end
52
+ end
53
+
54
+ def /(other)
55
+ if other.is_constant?
56
+ raise Keisan::Exceptions::InvalidExpression.new("Cannot divide #{self.class} and #{other.class}")
57
+ else
58
+ super
59
+ end
60
+ end
61
+
62
+ def %(other)
63
+ if other.is_constant?
64
+ raise Keisan::Exceptions::InvalidExpression.new("Cannot modulo #{self.class} and #{other.class}")
65
+ else
66
+ super
67
+ end
68
+ end
69
+
70
+ def !
71
+ raise Keisan::Exceptions::InvalidExpression.new("Cannot take logical not of #{self.class}")
72
+ end
73
+
74
+ def ~
75
+ raise Keisan::Exceptions::InvalidExpression.new("Cannot take bitwise not of #{self.class}")
76
+ end
77
+
78
+ def +@
79
+ raise Keisan::Exceptions::InvalidExpression.new("Cannot take unary plus of #{self.class}")
80
+ end
81
+
82
+ def -@
83
+ raise Keisan::Exceptions::InvalidExpression.new("Cannot take unary minus of #{self.class}")
84
+ end
85
+
86
+ def **(other)
87
+ if other.is_constant?
88
+ raise Keisan::Exceptions::InvalidExpression.new("Cannot exponentiate #{self.class} and #{other.class}")
89
+ else
90
+ super
91
+ end
92
+ end
93
+
94
+ def &(other)
95
+ if other.is_constant?
96
+ raise Keisan::Exceptions::InvalidExpression.new("Cannot bitwise and #{self.class} and #{other.class}")
97
+ else
98
+ super
99
+ end
100
+ end
101
+
102
+ def ^(other)
103
+ if other.is_constant?
104
+ raise Keisan::Exceptions::InvalidExpression.new("Cannot bitwise xor #{self.class} and #{other.class}")
105
+ else
106
+ super
107
+ end
108
+ end
109
+
110
+ def |(other)
111
+ if other.is_constant?
112
+ raise Keisan::Exceptions::InvalidExpression.new("Cannot bitwise or #{self.class} and #{other.class}")
113
+ else
114
+ super
115
+ end
116
+ end
117
+
118
+ def <<(other)
119
+ if other.is_constant?
120
+ raise Keisan::Exceptions::InvalidExpression.new("Cannot bitwise left shift #{self.class} and #{other.class}")
121
+ else
122
+ super
123
+ end
124
+ end
125
+
126
+ def >>(other)
127
+ if other.is_constant?
128
+ raise Keisan::Exceptions::InvalidExpression.new("Cannot bitwise right shift #{self.class} and #{other.class}")
129
+ else
130
+ super
131
+ end
132
+ end
133
+
134
+ def >(other)
135
+ if other.is_constant?
136
+ raise Keisan::Exceptions::InvalidExpression.new("Cannot compute #{self.class} > #{other.class}")
137
+ else
138
+ super
139
+ end
140
+ end
141
+
142
+ def >=(other)
143
+ if other.is_constant?
144
+ raise Keisan::Exceptions::InvalidExpression.new("Cannot compute #{self.class} >= #{other.class}")
145
+ else
146
+ super
147
+ end
148
+ end
149
+
150
+ def <(other)
151
+ if other.is_constant?
152
+ raise Keisan::Exceptions::InvalidExpression.new("Cannot compute #{self.class} < #{other.class}")
153
+ else
154
+ super
155
+ end
156
+ end
157
+
158
+ def <=(other)
159
+ if other.is_constant?
160
+ raise Keisan::Exceptions::InvalidExpression.new("Cannot compute #{self.class} <= #{other.class}")
161
+ else
162
+ super
163
+ end
164
+ end
165
+
166
+ def equal(other)
167
+ other.is_constant? ? Boolean.new(false) : super
168
+ end
169
+
170
+ def not_equal(other)
171
+ other.is_constant? ? Boolean.new(true) : super
172
+ end
173
+
174
+ def and(other)
175
+ if other.is_constant?
176
+ raise Keisan::Exceptions::InvalidExpression.new("Cannot logical and #{self.class} and #{other.class}")
177
+ else
178
+ super
179
+ end
180
+ end
181
+
182
+ def or(other)
183
+ if other.is_constant?
184
+ raise Keisan::Exceptions::InvalidExpression.new("Cannot logical or #{self.class} and #{other.class}")
185
+ else
186
+ super
187
+ end
188
+ end
25
189
  end
26
190
  end
27
191
  end
@@ -91,6 +91,13 @@ module Keisan
91
91
 
92
92
  self.class.new([self, variable], "diff")
93
93
  end
94
+
95
+ # Functions cannot be guaranteed to be constant even if the arguments
96
+ # are constants, because there might be randomness involved in the
97
+ # outputs.
98
+ def is_constant?
99
+ false
100
+ end
94
101
  end
95
102
  end
96
103
  end
@@ -34,6 +34,7 @@ module Keisan
34
34
  end
35
35
 
36
36
  def evaluate(context = nil)
37
+ return self if frozen?
37
38
  context ||= Context.new
38
39
 
39
40
  @hash = ::Hash[
@@ -89,6 +90,10 @@ module Keisan
89
90
  ])
90
91
  AST::Cell.new(h)
91
92
  end
93
+
94
+ def is_constant?
95
+ @hash.all? {|k,v| v.is_constant?}
96
+ end
92
97
  end
93
98
  end
94
99
  end
@@ -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
@@ -26,7 +26,7 @@ module Keisan
26
26
 
27
27
  def short_circuit_do(method, context)
28
28
  context ||= Context.new
29
- lhs = children[0].send(method, context)
29
+ lhs = children[0].send(method, context).to_node
30
30
  case lhs
31
31
  when AST::Boolean
32
32
  lhs.false? ? AST::Boolean.new(false) : children[1].send(method, context)
@@ -26,7 +26,7 @@ module Keisan
26
26
 
27
27
  def short_circuit_do(method, context)
28
28
  context ||= Context.new
29
- lhs = children[0].send(method, context)
29
+ lhs = children[0].send(method, context).to_node
30
30
  case lhs
31
31
  when AST::Boolean
32
32
  lhs.true? ? AST::Boolean.new(true) : children[1].send(method, context)
@@ -202,6 +202,10 @@ module Keisan
202
202
  def or(other)
203
203
  LogicalOr.new([self, other.to_node])
204
204
  end
205
+
206
+ def is_constant?
207
+ false
208
+ end
205
209
  end
206
210
  end
207
211
  end
@@ -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)
@@ -75,6 +75,10 @@ module Keisan
75
75
  @children = children.map {|child| child.replace(variable, replacement)}
76
76
  self
77
77
  end
78
+
79
+ def is_constant?
80
+ @children.all?(&:is_constant?)
81
+ end
78
82
  end
79
83
  end
80
84
  end
@@ -21,7 +21,7 @@ module Keisan
21
21
  context ||= Context.new
22
22
 
23
23
  operand, arguments, expression = operand_arguments_expression_for(ast_function, context)
24
-
24
+
25
25
  # Extract underlying operand for cells
26
26
  real_operand = operand.is_a?(AST::Cell) ? operand.node : operand
27
27
 
@@ -29,7 +29,7 @@ module Keisan
29
29
  AST::List.new(
30
30
  list.children.select do |element|
31
31
  local.register_variable!(variable, element)
32
- result = expression.evaluated(local)
32
+ result = expression.evaluated(local).to_node
33
33
 
34
34
  case result
35
35
  when AST::Boolean
@@ -54,7 +54,7 @@ module Keisan
54
54
  hash.select do |cur_key, cur_value|
55
55
  local.register_variable!(key, cur_key)
56
56
  local.register_variable!(value, cur_value)
57
- result = expression.evaluated(local)
57
+ result = expression.evaluated(local).to_node
58
58
 
59
59
  case result
60
60
  when AST::Boolean
@@ -13,12 +13,13 @@ module Keisan
13
13
  def evaluate(ast_function, context = nil)
14
14
  validate_arguments!(ast_function.children.count)
15
15
  context ||= Context.new
16
-
17
- bool = ast_function.children[0].evaluate(context)
16
+ bool = ast_function.children[0].evaluate(context).to_node
18
17
 
19
18
  if bool.is_a?(AST::Boolean)
20
19
  node = bool.value ? ast_function.children[1] : ast_function.children[2]
21
20
  node.to_node.evaluate(context)
21
+ elsif bool.is_constant?
22
+ raise Keisan::Exceptions::InvalidFunctionError.new("if statement must work on booleans, other constants are not supported")
22
23
  else
23
24
  ast_function
24
25
  end
@@ -27,7 +28,7 @@ module Keisan
27
28
  def simplify(ast_function, context = nil)
28
29
  validate_arguments!(ast_function.children.count)
29
30
  context ||= Context.new
30
- bool = ast_function.children[0].simplify(context)
31
+ bool = ast_function.children[0].simplify(context).to_node
31
32
 
32
33
  if bool.is_a?(AST::Boolean)
33
34
  if bool.value
@@ -37,6 +38,8 @@ module Keisan
37
38
  else
38
39
  Keisan::AST::Null.new
39
40
  end
41
+ elsif bool.is_constant?
42
+ raise Keisan::Exceptions::InvalidFunctionError.new("if statement must work on booleans, other constants are not supported")
40
43
  else
41
44
  ast_function
42
45
  end
@@ -1,6 +1,9 @@
1
1
  module Keisan
2
2
  module Functions
3
3
  class While < Keisan::Function
4
+ class WhileLogicalNodeIsNotConstant < Keisan::Exceptions::StandardError; end
5
+ class WhileLogicalNodeIsNonBoolConstant < Keisan::Exceptions::StandardError; end
6
+
4
7
  def initialize
5
8
  super("while", 2)
6
9
  end
@@ -13,27 +16,43 @@ module Keisan
13
16
  def evaluate(ast_function, context = nil)
14
17
  validate_arguments!(ast_function.children.count)
15
18
  context ||= Keisan::Context.new
16
- simplify(ast_function, context)
19
+ while_loop(ast_function, context, simplify: false)
17
20
  end
18
21
 
19
22
  def simplify(ast_function, context = nil)
20
23
  validate_arguments!(ast_function.children.count)
21
24
  context ||= Context.new
22
- while_loop(ast_function.children[0], ast_function.children[1], context)
25
+ while_loop(ast_function, context, simplify: true)
23
26
  end
24
27
 
25
28
  private
26
29
 
27
- def while_loop(logical_node, body_node, context)
30
+ def while_loop(ast_function, context, simplify: true)
31
+ logical_node, body_node = ast_function.children[0], ast_function.children[1]
28
32
  current = Keisan::AST::Null.new
29
33
 
30
- while logical_node_evaluates_to_true(logical_node, context)
31
- begin
32
- current = body_node.evaluated(context)
33
- rescue Exceptions::BreakError
34
- break
35
- rescue Exceptions::ContinueError
36
- next
34
+ begin
35
+ while logical_node_evaluates_to_true(logical_node, context)
36
+ begin
37
+ current = body_node.evaluated(context)
38
+ rescue Exceptions::BreakError
39
+ break
40
+ rescue Exceptions::ContinueError
41
+ next
42
+ end
43
+ end
44
+
45
+ # While loops should work on booleans, not other types of constants
46
+ rescue WhileLogicalNodeIsNonBoolConstant
47
+ raise Keisan::Exceptions::InvalidFunctionError.new("while condition must evaluate to a boolean")
48
+
49
+ # If the logical expression is not constant (e.g. boolean), then we
50
+ # cannot simplify the while loop, and an evaluate should raise an error.
51
+ rescue WhileLogicalNodeIsNotConstant
52
+ if simplify
53
+ return ast_function
54
+ else
55
+ raise Keisan::Exceptions::InvalidFunctionError.new("while condition must evaluate to a boolean")
37
56
  end
38
57
  end
39
58
 
@@ -41,11 +60,15 @@ module Keisan
41
60
  end
42
61
 
43
62
  def logical_node_evaluates_to_true(logical_node, context)
44
- bool = logical_node.evaluated(context)
45
- unless bool.is_a?(AST::Boolean)
46
- raise Keisan::Exceptions::InvalidFunctionError.new("while condition must evaluate to a boolean")
63
+ bool = logical_node.evaluated(context).to_node
64
+
65
+ if bool.is_a?(AST::Boolean)
66
+ bool.value(context)
67
+ elsif bool.is_constant?
68
+ raise WhileLogicalNodeIsNonBoolConstant.new
69
+ else
70
+ raise WhileLogicalNodeIsNotConstant.new
47
71
  end
48
- bool.value(context)
49
72
  end
50
73
  end
51
74
  end
@@ -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.8"
2
+ VERSION = "0.8.13"
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.8
4
+ version: 0.8.13
5
5
  platform: ruby
6
6
  authors:
7
7
  - Christopher Locke
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-04-30 00:00:00.000000000 Z
11
+ date: 2021-05-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cmath
@@ -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