keisan 0.4.0 → 0.5.0
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 +4 -4
- data/.travis.yml +3 -5
- data/README.md +112 -8
- data/bin/keisan +7 -0
- data/lib/keisan.rb +0 -1
- data/lib/keisan/ast.rb +24 -0
- data/lib/keisan/ast/assignment.rb +36 -10
- data/lib/keisan/ast/builder.rb +7 -1
- data/lib/keisan/ast/constant_literal.rb +6 -1
- data/lib/keisan/ast/exponent.rb +1 -1
- data/lib/keisan/ast/function.rb +7 -3
- data/lib/keisan/ast/list.rb +1 -1
- data/lib/keisan/ast/node.rb +1 -1
- data/lib/keisan/ast/parent.rb +1 -1
- data/lib/keisan/ast/plus.rb +11 -1
- data/lib/keisan/ast/string.rb +8 -0
- data/lib/keisan/ast/unary_operator.rb +1 -1
- data/lib/keisan/ast/variable.rb +8 -2
- data/lib/keisan/calculator.rb +16 -0
- data/lib/keisan/context.rb +39 -8
- data/lib/keisan/evaluator.rb +11 -1
- data/lib/keisan/function.rb +4 -0
- data/lib/keisan/functions/abs.rb +9 -0
- data/lib/keisan/functions/cbrt.rb +15 -0
- data/lib/keisan/functions/cmath_function.rb +11 -0
- data/lib/keisan/functions/cos.rb +15 -0
- data/lib/keisan/functions/cosh.rb +15 -0
- data/lib/keisan/functions/cot.rb +15 -0
- data/lib/keisan/functions/coth.rb +15 -0
- data/lib/keisan/functions/csc.rb +15 -0
- data/lib/keisan/functions/csch.rb +15 -0
- data/lib/keisan/functions/default_registry.rb +95 -1
- data/lib/keisan/functions/diff.rb +36 -14
- data/lib/keisan/functions/exp.rb +15 -0
- data/lib/keisan/functions/expression_function.rb +65 -15
- data/lib/keisan/functions/filter.rb +64 -0
- data/lib/keisan/functions/if.rb +15 -12
- data/lib/keisan/functions/imag.rb +9 -0
- data/lib/keisan/functions/log.rb +15 -0
- data/lib/keisan/functions/map.rb +57 -0
- data/lib/keisan/functions/math_function.rb +34 -0
- data/lib/keisan/functions/proc_function.rb +5 -3
- data/lib/keisan/functions/real.rb +9 -0
- data/lib/keisan/functions/reduce.rb +65 -0
- data/lib/keisan/functions/registry.rb +5 -1
- data/lib/keisan/functions/sec.rb +15 -0
- data/lib/keisan/functions/sech.rb +15 -0
- data/lib/keisan/functions/sin.rb +15 -0
- data/lib/keisan/functions/sinh.rb +15 -0
- data/lib/keisan/functions/sqrt.rb +15 -0
- data/lib/keisan/functions/tan.rb +15 -0
- data/lib/keisan/functions/tanh.rb +15 -0
- data/lib/keisan/repl.rb +105 -0
- data/lib/keisan/tokenizer.rb +5 -2
- data/lib/keisan/tokens/string.rb +1 -1
- data/lib/keisan/variables/registry.rb +14 -3
- data/lib/keisan/version.rb +1 -1
- data/screenshots/repl.png +0 -0
- metadata +30 -6
- data/lib/keisan/ast/functions/diff.rb +0 -57
- data/lib/keisan/ast/functions/if.rb +0 -47
- data/lib/keisan/function_definition_context.rb +0 -34
data/lib/keisan/tokenizer.rb
CHANGED
@@ -22,12 +22,15 @@ module Keisan
|
|
22
22
|
attr_reader :expression, :tokens
|
23
23
|
|
24
24
|
def initialize(expression)
|
25
|
-
@expression = self.class.
|
25
|
+
@expression = self.class.strip_whitespace_and_comments(expression)
|
26
26
|
@scan = @expression.scan(TOKEN_REGEX)
|
27
27
|
@tokens = tokenize!
|
28
28
|
end
|
29
29
|
|
30
|
-
def self.
|
30
|
+
def self.strip_whitespace_and_comments(expression)
|
31
|
+
# Remove comments
|
32
|
+
expression = expression.split("#").first || ""
|
33
|
+
|
31
34
|
# Do not allow whitespace between variables, numbers, and the like; they must be joined by operators
|
32
35
|
raise Keisan::Exceptions::TokenizingError.new if expression.gsub(Tokens::String.regex, "").match /\w\s+\w/
|
33
36
|
|
data/lib/keisan/tokens/string.rb
CHANGED
@@ -1,8 +1,11 @@
|
|
1
1
|
module Keisan
|
2
2
|
module Variables
|
3
3
|
class Registry
|
4
|
-
|
4
|
+
attr_reader :shadowed
|
5
|
+
|
6
|
+
def initialize(variables: {}, shadowed: [], parent: nil, use_defaults: true, force: false)
|
5
7
|
@hash = {}
|
8
|
+
@shadowed = Set.new(shadowed.map(&:to_s))
|
6
9
|
@parent = parent
|
7
10
|
@use_defaults = use_defaults
|
8
11
|
|
@@ -13,6 +16,7 @@ module Keisan
|
|
13
16
|
|
14
17
|
def [](name)
|
15
18
|
return @hash[name] if @hash.has_key?(name)
|
19
|
+
raise Keisan::Exceptions::UndefinedVariableError.new if @shadowed.include?(name)
|
16
20
|
|
17
21
|
if @parent && (parent_value = @parent[name])
|
18
22
|
return parent_value
|
@@ -22,18 +26,25 @@ module Keisan
|
|
22
26
|
raise Keisan::Exceptions::UndefinedVariableError.new name
|
23
27
|
end
|
24
28
|
|
29
|
+
def locals
|
30
|
+
@hash
|
31
|
+
end
|
32
|
+
|
25
33
|
def has?(name)
|
26
|
-
|
34
|
+
!self[name].nil?
|
27
35
|
rescue Keisan::Exceptions::UndefinedVariableError
|
28
36
|
false
|
29
37
|
end
|
30
38
|
|
31
39
|
def register!(name, value, force: false)
|
40
|
+
name = name.to_s
|
41
|
+
name = name.name if name.is_a?(Keisan::AST::Variable)
|
42
|
+
|
32
43
|
raise Keisan::Exceptions::UnmodifiableError.new("Cannot modify frozen variables registry") if frozen?
|
33
44
|
if !force && @use_defaults && default_registry.has_name?(name)
|
34
45
|
raise Keisan::Exceptions::UnmodifiableError.new("Cannot overwrite default variable")
|
35
46
|
end
|
36
|
-
self[name.to_s] = value
|
47
|
+
self[name.to_s] = value.to_node
|
37
48
|
end
|
38
49
|
|
39
50
|
protected
|
data/lib/keisan/version.rb
CHANGED
Binary file
|
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.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Christopher Locke
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-12-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -109,6 +109,7 @@ files:
|
|
109
109
|
- README.md
|
110
110
|
- Rakefile
|
111
111
|
- bin/console
|
112
|
+
- bin/keisan
|
112
113
|
- bin/setup
|
113
114
|
- keisan.gemspec
|
114
115
|
- lib/keisan.rb
|
@@ -124,8 +125,6 @@ files:
|
|
124
125
|
- lib/keisan/ast/constant_literal.rb
|
125
126
|
- lib/keisan/ast/exponent.rb
|
126
127
|
- lib/keisan/ast/function.rb
|
127
|
-
- lib/keisan/ast/functions/diff.rb
|
128
|
-
- lib/keisan/ast/functions/if.rb
|
129
128
|
- lib/keisan/ast/indexing.rb
|
130
129
|
- lib/keisan/ast/list.rb
|
131
130
|
- lib/keisan/ast/literal.rb
|
@@ -160,16 +159,39 @@ files:
|
|
160
159
|
- lib/keisan/evaluator.rb
|
161
160
|
- lib/keisan/exceptions.rb
|
162
161
|
- lib/keisan/function.rb
|
163
|
-
- lib/keisan/
|
162
|
+
- lib/keisan/functions/abs.rb
|
163
|
+
- lib/keisan/functions/cbrt.rb
|
164
|
+
- lib/keisan/functions/cmath_function.rb
|
165
|
+
- lib/keisan/functions/cos.rb
|
166
|
+
- lib/keisan/functions/cosh.rb
|
167
|
+
- lib/keisan/functions/cot.rb
|
168
|
+
- lib/keisan/functions/coth.rb
|
169
|
+
- lib/keisan/functions/csc.rb
|
170
|
+
- lib/keisan/functions/csch.rb
|
164
171
|
- lib/keisan/functions/default_registry.rb
|
165
172
|
- lib/keisan/functions/diff.rb
|
173
|
+
- lib/keisan/functions/exp.rb
|
166
174
|
- lib/keisan/functions/expression_function.rb
|
175
|
+
- lib/keisan/functions/filter.rb
|
167
176
|
- lib/keisan/functions/if.rb
|
177
|
+
- lib/keisan/functions/imag.rb
|
178
|
+
- lib/keisan/functions/log.rb
|
179
|
+
- lib/keisan/functions/map.rb
|
180
|
+
- lib/keisan/functions/math_function.rb
|
168
181
|
- lib/keisan/functions/proc_function.rb
|
169
182
|
- lib/keisan/functions/rand.rb
|
183
|
+
- lib/keisan/functions/real.rb
|
184
|
+
- lib/keisan/functions/reduce.rb
|
170
185
|
- lib/keisan/functions/registry.rb
|
171
186
|
- lib/keisan/functions/replace.rb
|
172
187
|
- lib/keisan/functions/sample.rb
|
188
|
+
- lib/keisan/functions/sec.rb
|
189
|
+
- lib/keisan/functions/sech.rb
|
190
|
+
- lib/keisan/functions/sin.rb
|
191
|
+
- lib/keisan/functions/sinh.rb
|
192
|
+
- lib/keisan/functions/sqrt.rb
|
193
|
+
- lib/keisan/functions/tan.rb
|
194
|
+
- lib/keisan/functions/tanh.rb
|
173
195
|
- lib/keisan/parser.rb
|
174
196
|
- lib/keisan/parsing/argument.rb
|
175
197
|
- lib/keisan/parsing/arithmetic_operator.rb
|
@@ -217,6 +239,7 @@ files:
|
|
217
239
|
- lib/keisan/parsing/unary_operator.rb
|
218
240
|
- lib/keisan/parsing/unary_plus.rb
|
219
241
|
- lib/keisan/parsing/variable.rb
|
242
|
+
- lib/keisan/repl.rb
|
220
243
|
- lib/keisan/token.rb
|
221
244
|
- lib/keisan/tokenizer.rb
|
222
245
|
- lib/keisan/tokens/arithmetic_operator.rb
|
@@ -235,6 +258,7 @@ files:
|
|
235
258
|
- lib/keisan/variables/default_registry.rb
|
236
259
|
- lib/keisan/variables/registry.rb
|
237
260
|
- lib/keisan/version.rb
|
261
|
+
- screenshots/repl.png
|
238
262
|
homepage: https://github.com/project-eutopia/keisan
|
239
263
|
licenses:
|
240
264
|
- MIT
|
@@ -255,7 +279,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
255
279
|
version: '0'
|
256
280
|
requirements: []
|
257
281
|
rubyforge_project:
|
258
|
-
rubygems_version: 2.6.
|
282
|
+
rubygems_version: 2.6.14
|
259
283
|
signing_key:
|
260
284
|
specification_version: 4
|
261
285
|
summary: An equation parser and evaluator
|
@@ -1,57 +0,0 @@
|
|
1
|
-
module Keisan
|
2
|
-
module AST
|
3
|
-
module Functions
|
4
|
-
class Diff < AST::Function
|
5
|
-
def initialize(arguments, name = "diff")
|
6
|
-
super(arguments, name)
|
7
|
-
end
|
8
|
-
|
9
|
-
def value(context = nil)
|
10
|
-
raise Keisan::Exceptions::InvalidFunctionError.new("Derivative not defined")
|
11
|
-
end
|
12
|
-
|
13
|
-
def unbound_functions(context = nil)
|
14
|
-
context ||= Keisan::Context.new
|
15
|
-
|
16
|
-
children.inject(Set.new) do |res, child|
|
17
|
-
res | child.unbound_functions(context)
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
def simplify(context = nil)
|
22
|
-
unless children.size > 0
|
23
|
-
raise Keisan::Exceptions::InvalidFunctionError.new("Diff requires at least one argument")
|
24
|
-
end
|
25
|
-
|
26
|
-
vars = children[1..-1]
|
27
|
-
|
28
|
-
unless vars.all? {|var| var.is_a?(AST::Variable)}
|
29
|
-
raise Keisan::Exceptions::InvalidFunctionError.new("Diff must differentiate with respect to variables")
|
30
|
-
end
|
31
|
-
|
32
|
-
result = children.first.simplify(context)
|
33
|
-
|
34
|
-
while vars.size > 0
|
35
|
-
begin
|
36
|
-
var = vars.first
|
37
|
-
if result.unbound_variables(context).include?(var.name)
|
38
|
-
result = result.differentiate(var, context)
|
39
|
-
else
|
40
|
-
return AST::Number.new(0)
|
41
|
-
end
|
42
|
-
rescue Keisan::Exceptions::NonDifferentiableError => e
|
43
|
-
return AST::Functions::Diff.new(
|
44
|
-
[result] + vars,
|
45
|
-
"diff"
|
46
|
-
)
|
47
|
-
end
|
48
|
-
|
49
|
-
vars.shift
|
50
|
-
end
|
51
|
-
|
52
|
-
result
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
@@ -1,47 +0,0 @@
|
|
1
|
-
module Keisan
|
2
|
-
module AST
|
3
|
-
module Functions
|
4
|
-
class If < AST::Function
|
5
|
-
def value(context = nil)
|
6
|
-
unless (2..3).cover? children.size
|
7
|
-
raise Keisan::Exceptions::InvalidFunctionError.new("Require 2 or 3 arguments to if")
|
8
|
-
end
|
9
|
-
|
10
|
-
context ||= Context.new
|
11
|
-
|
12
|
-
bool = children[0].value(context)
|
13
|
-
|
14
|
-
if bool
|
15
|
-
children[1].value(context)
|
16
|
-
else
|
17
|
-
children.size == 3 ? children[2].value(context) : nil
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
def simplify(context = nil)
|
22
|
-
context ||= Context.new
|
23
|
-
@children = children.map {|child| child.simplify(context)}
|
24
|
-
|
25
|
-
if children[0].is_a?(AST::Boolean)
|
26
|
-
if children[0].value
|
27
|
-
children[1]
|
28
|
-
else
|
29
|
-
# If no third argument, then children[2] gives nil, and to_node makes this AST::Null
|
30
|
-
children[2].to_node
|
31
|
-
end
|
32
|
-
else
|
33
|
-
self
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
def unbound_functions(context = nil)
|
38
|
-
context ||= Context.new
|
39
|
-
|
40
|
-
children.inject(Set.new) do |res, child|
|
41
|
-
res | child.unbound_functions(context)
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
@@ -1,34 +0,0 @@
|
|
1
|
-
module Keisan
|
2
|
-
class FunctionDefinitionContext < Context
|
3
|
-
def initialize(parent:, arguments:)
|
4
|
-
super(parent: parent)
|
5
|
-
@arguments = Set.new(arguments)
|
6
|
-
@arguments_context = Context.new
|
7
|
-
set_transient!
|
8
|
-
end
|
9
|
-
|
10
|
-
def variable(name)
|
11
|
-
if @arguments.member?(name)
|
12
|
-
@arguments_context.variable(name)
|
13
|
-
else
|
14
|
-
super
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
def has_variable?(name)
|
19
|
-
if @arguments.member?(name)
|
20
|
-
@arguments_context.has_variable?(name)
|
21
|
-
else
|
22
|
-
super
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
def register_variable!(name, value)
|
27
|
-
if @arguments.member?(name)
|
28
|
-
@arguments_context.register_variable!(name, value)
|
29
|
-
else
|
30
|
-
super
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|