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