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
@@ -0,0 +1,57 @@
|
|
1
|
+
module Keisan
|
2
|
+
module Functions
|
3
|
+
class Map < Keisan::Function
|
4
|
+
# Maps (list, variable, expression)
|
5
|
+
# e.g. map([1,2,3], x, 2*x)
|
6
|
+
# should give [2,4,6]
|
7
|
+
def initialize
|
8
|
+
@name = "map"
|
9
|
+
end
|
10
|
+
|
11
|
+
def value(ast_function, context = nil)
|
12
|
+
evaluate(ast_function, context = nil)
|
13
|
+
end
|
14
|
+
|
15
|
+
def evaluate(ast_function, context = nil)
|
16
|
+
context ||= Context.new
|
17
|
+
simplify(ast_function, context).evaluate(context)
|
18
|
+
end
|
19
|
+
|
20
|
+
def simplify(ast_function, context = nil)
|
21
|
+
context ||= Keisan::Context.new
|
22
|
+
list, variable, expression = list_variable_expression_for(ast_function, context)
|
23
|
+
|
24
|
+
local = context.spawn_child(transient: false, shadowed: [variable.name])
|
25
|
+
|
26
|
+
Keisan::AST::List.new(
|
27
|
+
list.children.map do |element|
|
28
|
+
local.register_variable!(variable, element)
|
29
|
+
expression.simplified(local)
|
30
|
+
end
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def list_variable_expression_for(ast_function, context)
|
37
|
+
unless ast_function.children.size == 3
|
38
|
+
raise Keisan::Exceptions::InvalidFunctionError.new("Require 3 arguments to map")
|
39
|
+
end
|
40
|
+
|
41
|
+
list = ast_function.children[0].simplify(context)
|
42
|
+
variable = ast_function.children[1]
|
43
|
+
expression = ast_function.children[2]
|
44
|
+
|
45
|
+
unless list.is_a?(Keisan::AST::List)
|
46
|
+
raise Keisan::Exceptions::InvalidFunctionError.new("First argument to map must be a list")
|
47
|
+
end
|
48
|
+
|
49
|
+
unless variable.is_a?(Keisan::AST::Variable)
|
50
|
+
raise Keisan::Exceptions::InvalidFunctionError.new("Second argument to map must be a variable")
|
51
|
+
end
|
52
|
+
|
53
|
+
[list, variable, expression]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Keisan
|
2
|
+
module Functions
|
3
|
+
class MathFunction < ProcFunction
|
4
|
+
def initialize(name, proc_function = nil)
|
5
|
+
super(name, proc_function || Proc.new {|arg| Math.send(name, arg)})
|
6
|
+
end
|
7
|
+
|
8
|
+
def simplify(ast_function, context = nil)
|
9
|
+
simplified = super
|
10
|
+
self.class.apply_simplifications(simplified)
|
11
|
+
end
|
12
|
+
|
13
|
+
def differentiate(ast_function, variable, context = nil)
|
14
|
+
raise Keisan::Exceptions::InvalidFunctionError.new unless ast_function.children.count == 1
|
15
|
+
context ||= Context.new
|
16
|
+
|
17
|
+
argument_simplified = ast_function.children.first.simplify(context)
|
18
|
+
argument_differentiated = argument_simplified.differentiate(variable, context)
|
19
|
+
|
20
|
+
(argument_differentiated * self.class.derivative(argument_simplified)).simplify(context)
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
|
25
|
+
def self.derivative(argument)
|
26
|
+
raise Keisan::Exceptions::NotImplementedError.new
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.apply_simplifications(simplified)
|
30
|
+
simplified
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
module Keisan
|
2
2
|
module Functions
|
3
3
|
class ProcFunction < Keisan::Function
|
4
|
+
attr_reader :function_proc
|
5
|
+
|
4
6
|
def initialize(name, function_proc)
|
5
7
|
raise Keisan::Exceptions::InvalidFunctionError.new unless function_proc.is_a?(Proc)
|
6
8
|
|
@@ -9,13 +11,13 @@ module Keisan
|
|
9
11
|
end
|
10
12
|
|
11
13
|
def call(context, *args)
|
12
|
-
|
14
|
+
function_proc.call(*args).to_node
|
13
15
|
end
|
14
16
|
|
15
17
|
def value(ast_function, context = nil)
|
16
18
|
context ||= Keisan::Context.new
|
17
19
|
argument_values = ast_function.children.map {|child| child.value(context)}
|
18
|
-
call(context, *argument_values)
|
20
|
+
call(context, *argument_values).value(context)
|
19
21
|
end
|
20
22
|
|
21
23
|
def evaluate(ast_function, context = nil)
|
@@ -23,7 +25,7 @@ module Keisan
|
|
23
25
|
|
24
26
|
ast_function.instance_variable_set(
|
25
27
|
:@children,
|
26
|
-
ast_function.children.map {|child| child.evaluate(context)}
|
28
|
+
ast_function.children.map {|child| child.evaluate(context).to_node}
|
27
29
|
)
|
28
30
|
|
29
31
|
if ast_function.children.all? {|child| child.well_defined?(context)}
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Keisan
|
2
|
+
module Functions
|
3
|
+
class Reduce < Keisan::Function
|
4
|
+
# Reduces (list, initial, accumulator, variable, expression)
|
5
|
+
# e.g. reduce([1,2,3,4], 0, total, x, total+x)
|
6
|
+
# should give 10
|
7
|
+
def initialize
|
8
|
+
@name = "reduce"
|
9
|
+
end
|
10
|
+
|
11
|
+
def value(ast_function, context = nil)
|
12
|
+
evaluate(ast_function, context = nil)
|
13
|
+
end
|
14
|
+
|
15
|
+
def evaluate(ast_function, context = nil)
|
16
|
+
context ||= Context.new
|
17
|
+
simplify(ast_function, context).evaluate(context)
|
18
|
+
end
|
19
|
+
|
20
|
+
def simplify(ast_function, context = nil)
|
21
|
+
context ||= Keisan::Context.new
|
22
|
+
list, initial, accumulator, variable, expression = list_initial_accumulator_variable_expression_for(ast_function, context)
|
23
|
+
|
24
|
+
local = context.spawn_child(transient: false, shadowed: [accumulator.name, variable.name])
|
25
|
+
local.register_variable!(accumulator, initial.simplify(context))
|
26
|
+
|
27
|
+
list.children.each do |element|
|
28
|
+
local.register_variable!(variable, element)
|
29
|
+
result = expression.simplified(local)
|
30
|
+
local.register_variable!(accumulator, result)
|
31
|
+
end
|
32
|
+
|
33
|
+
local.variable(accumulator.name)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def list_initial_accumulator_variable_expression_for(ast_function, context)
|
39
|
+
unless ast_function.children.size == 5
|
40
|
+
raise Keisan::Exceptions::InvalidFunctionError.new("Require 5 arguments to reduce")
|
41
|
+
end
|
42
|
+
|
43
|
+
list = ast_function.children[0].simplify(context)
|
44
|
+
initial = ast_function.children[1]
|
45
|
+
accumulator = ast_function.children[2]
|
46
|
+
variable = ast_function.children[3]
|
47
|
+
expression = ast_function.children[4]
|
48
|
+
|
49
|
+
unless list.is_a?(Keisan::AST::List)
|
50
|
+
raise Keisan::Exceptions::InvalidFunctionError.new("First argument to reduce must be a list")
|
51
|
+
end
|
52
|
+
|
53
|
+
unless accumulator.is_a?(Keisan::AST::Variable)
|
54
|
+
raise Keisan::Exceptions::InvalidFunctionError.new("Third argument to reduce is accumulator and must be a variable")
|
55
|
+
end
|
56
|
+
|
57
|
+
unless variable.is_a?(Keisan::AST::Variable)
|
58
|
+
raise Keisan::Exceptions::InvalidFunctionError.new("Fourth argument to reduce is variable and must be a variable")
|
59
|
+
end
|
60
|
+
|
61
|
+
[list, initial, accumulator, variable, expression]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -22,6 +22,10 @@ module Keisan
|
|
22
22
|
raise Keisan::Exceptions::UndefinedFunctionError.new name
|
23
23
|
end
|
24
24
|
|
25
|
+
def locals
|
26
|
+
@hash
|
27
|
+
end
|
28
|
+
|
25
29
|
def has?(name)
|
26
30
|
!!self[name]
|
27
31
|
rescue Keisan::Exceptions::UndefinedFunctionError
|
@@ -40,7 +44,7 @@ module Keisan
|
|
40
44
|
when Proc
|
41
45
|
self[name] = Keisan::Functions::ProcFunction.new(name, function)
|
42
46
|
when Keisan::Function
|
43
|
-
self[
|
47
|
+
self[name] = function
|
44
48
|
else
|
45
49
|
raise Keisan::Exceptions::InvalidFunctionError.new
|
46
50
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Keisan
|
2
|
+
module Functions
|
3
|
+
class Sec < CMathFunction
|
4
|
+
def initialize
|
5
|
+
super("sec", Proc.new {|arg| 1 / CMath::cos(arg)})
|
6
|
+
end
|
7
|
+
|
8
|
+
protected
|
9
|
+
|
10
|
+
def self.derivative(argument)
|
11
|
+
Keisan::AST::Function.new([argument], "sin") * Keisan::AST::Exponent.new([Keisan::AST::Function.new([argument], "cos"), -2])
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Keisan
|
2
|
+
module Functions
|
3
|
+
class Sech < CMathFunction
|
4
|
+
def initialize
|
5
|
+
super("sech", Proc.new {|arg| 1 / CMath::cosh(arg)})
|
6
|
+
end
|
7
|
+
|
8
|
+
protected
|
9
|
+
|
10
|
+
def self.derivative(argument)
|
11
|
+
-Keisan::AST::Function.new([argument], "sinh") * Keisan::AST::Exponent.new([Keisan::AST::Function.new([argument], "cosh"), -2])
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Keisan
|
2
|
+
module Functions
|
3
|
+
class Tan < CMathFunction
|
4
|
+
def initialize
|
5
|
+
super("tan")
|
6
|
+
end
|
7
|
+
|
8
|
+
protected
|
9
|
+
|
10
|
+
def self.derivative(argument)
|
11
|
+
Keisan::AST::Exponent.new([Keisan::AST::Function.new([argument], "cos"), -2])
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Keisan
|
2
|
+
module Functions
|
3
|
+
class Tanh < CMathFunction
|
4
|
+
def initialize
|
5
|
+
super("tanh")
|
6
|
+
end
|
7
|
+
|
8
|
+
protected
|
9
|
+
|
10
|
+
def self.derivative(argument)
|
11
|
+
Keisan::AST::Exponent.new([Keisan::AST::Function.new([argument], "cosh"), -2])
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/keisan/repl.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
require "pry"
|
2
|
+
require "readline"
|
3
|
+
|
4
|
+
module Keisan
|
5
|
+
class Repl
|
6
|
+
COMMANDS = %w(
|
7
|
+
reset
|
8
|
+
quit
|
9
|
+
variables
|
10
|
+
functions
|
11
|
+
allow_recursive
|
12
|
+
).freeze
|
13
|
+
|
14
|
+
attr_reader :calculator
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@running = true
|
18
|
+
initialize_completion_commands
|
19
|
+
reset
|
20
|
+
end
|
21
|
+
|
22
|
+
def reset
|
23
|
+
@calculator = Keisan::Calculator.new
|
24
|
+
end
|
25
|
+
|
26
|
+
def start
|
27
|
+
while @running
|
28
|
+
command = get_command
|
29
|
+
|
30
|
+
# ctrl-d should break out
|
31
|
+
break if command.nil?
|
32
|
+
process_command command
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def get_command
|
37
|
+
Readline.readline("keisan> ", true)
|
38
|
+
end
|
39
|
+
|
40
|
+
def process_command(command)
|
41
|
+
command = command.strip
|
42
|
+
return if command.empty?
|
43
|
+
|
44
|
+
case command
|
45
|
+
when /\Areset\z/i
|
46
|
+
reset
|
47
|
+
when /\Aquit\z/i
|
48
|
+
@running = false
|
49
|
+
when /\Avariables\z/i
|
50
|
+
output_variables
|
51
|
+
when /\Afunctions\z/i
|
52
|
+
output_functions
|
53
|
+
when /\Aallow_recursive\z/i
|
54
|
+
calculator.allow_recursive!
|
55
|
+
else
|
56
|
+
begin
|
57
|
+
output_result calculator.simplify(command)
|
58
|
+
rescue StandardError => error
|
59
|
+
output_error error
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def output_result(result)
|
65
|
+
puts "=> " + CodeRay.encode(result.to_s, :ruby, :terminal)
|
66
|
+
end
|
67
|
+
|
68
|
+
def output_error(error)
|
69
|
+
puts CodeRay.encode(error.class.to_s, :ruby, :terminal) + ": " + CodeRay.encode("\"#{error.message}\"", :ruby, :terminal)
|
70
|
+
end
|
71
|
+
|
72
|
+
def output_variables
|
73
|
+
variable_registry.locals.each do |name, variable|
|
74
|
+
puts CodeRay.encode("#{name} = #{variable.value}", :ruby, :terminal)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def output_functions
|
79
|
+
function_registry.locals.each do |name, function|
|
80
|
+
puts CodeRay.encode("#{name}(#{function.arguments.join(', ')}) = #{function.expression.to_s}", :ruby, :terminal)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def context
|
87
|
+
calculator.context
|
88
|
+
end
|
89
|
+
|
90
|
+
def variable_registry
|
91
|
+
context.variable_registry
|
92
|
+
end
|
93
|
+
|
94
|
+
def function_registry
|
95
|
+
context.function_registry
|
96
|
+
end
|
97
|
+
|
98
|
+
def initialize_completion_commands
|
99
|
+
completion_proc = Proc.new { |s| COMMANDS.grep(/^#{Regexp.escape(s)}/) }
|
100
|
+
|
101
|
+
Readline.completion_append_character = " "
|
102
|
+
Readline.completion_proc = completion_proc
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|