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.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +3 -5
  3. data/README.md +112 -8
  4. data/bin/keisan +7 -0
  5. data/lib/keisan.rb +0 -1
  6. data/lib/keisan/ast.rb +24 -0
  7. data/lib/keisan/ast/assignment.rb +36 -10
  8. data/lib/keisan/ast/builder.rb +7 -1
  9. data/lib/keisan/ast/constant_literal.rb +6 -1
  10. data/lib/keisan/ast/exponent.rb +1 -1
  11. data/lib/keisan/ast/function.rb +7 -3
  12. data/lib/keisan/ast/list.rb +1 -1
  13. data/lib/keisan/ast/node.rb +1 -1
  14. data/lib/keisan/ast/parent.rb +1 -1
  15. data/lib/keisan/ast/plus.rb +11 -1
  16. data/lib/keisan/ast/string.rb +8 -0
  17. data/lib/keisan/ast/unary_operator.rb +1 -1
  18. data/lib/keisan/ast/variable.rb +8 -2
  19. data/lib/keisan/calculator.rb +16 -0
  20. data/lib/keisan/context.rb +39 -8
  21. data/lib/keisan/evaluator.rb +11 -1
  22. data/lib/keisan/function.rb +4 -0
  23. data/lib/keisan/functions/abs.rb +9 -0
  24. data/lib/keisan/functions/cbrt.rb +15 -0
  25. data/lib/keisan/functions/cmath_function.rb +11 -0
  26. data/lib/keisan/functions/cos.rb +15 -0
  27. data/lib/keisan/functions/cosh.rb +15 -0
  28. data/lib/keisan/functions/cot.rb +15 -0
  29. data/lib/keisan/functions/coth.rb +15 -0
  30. data/lib/keisan/functions/csc.rb +15 -0
  31. data/lib/keisan/functions/csch.rb +15 -0
  32. data/lib/keisan/functions/default_registry.rb +95 -1
  33. data/lib/keisan/functions/diff.rb +36 -14
  34. data/lib/keisan/functions/exp.rb +15 -0
  35. data/lib/keisan/functions/expression_function.rb +65 -15
  36. data/lib/keisan/functions/filter.rb +64 -0
  37. data/lib/keisan/functions/if.rb +15 -12
  38. data/lib/keisan/functions/imag.rb +9 -0
  39. data/lib/keisan/functions/log.rb +15 -0
  40. data/lib/keisan/functions/map.rb +57 -0
  41. data/lib/keisan/functions/math_function.rb +34 -0
  42. data/lib/keisan/functions/proc_function.rb +5 -3
  43. data/lib/keisan/functions/real.rb +9 -0
  44. data/lib/keisan/functions/reduce.rb +65 -0
  45. data/lib/keisan/functions/registry.rb +5 -1
  46. data/lib/keisan/functions/sec.rb +15 -0
  47. data/lib/keisan/functions/sech.rb +15 -0
  48. data/lib/keisan/functions/sin.rb +15 -0
  49. data/lib/keisan/functions/sinh.rb +15 -0
  50. data/lib/keisan/functions/sqrt.rb +15 -0
  51. data/lib/keisan/functions/tan.rb +15 -0
  52. data/lib/keisan/functions/tanh.rb +15 -0
  53. data/lib/keisan/repl.rb +105 -0
  54. data/lib/keisan/tokenizer.rb +5 -2
  55. data/lib/keisan/tokens/string.rb +1 -1
  56. data/lib/keisan/variables/registry.rb +14 -3
  57. data/lib/keisan/version.rb +1 -1
  58. data/screenshots/repl.png +0 -0
  59. metadata +30 -6
  60. data/lib/keisan/ast/functions/diff.rb +0 -57
  61. data/lib/keisan/ast/functions/if.rb +0 -47
  62. data/lib/keisan/function_definition_context.rb +0 -34
@@ -0,0 +1,9 @@
1
+ module Keisan
2
+ module Functions
3
+ class Imag < CMathFunction
4
+ def initialize
5
+ super("imag", Proc.new {|arg| arg.imag})
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,15 @@
1
+ module Keisan
2
+ module Functions
3
+ class Log < CMathFunction
4
+ def initialize
5
+ super("log")
6
+ end
7
+
8
+ protected
9
+
10
+ def self.derivative(argument)
11
+ 1 / argument
12
+ end
13
+ end
14
+ end
15
+ end
@@ -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
- @function_proc.call(*args)
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,9 @@
1
+ module Keisan
2
+ module Functions
3
+ class Real < CMathFunction
4
+ def initialize
5
+ super("real", Proc.new {|arg| arg.real})
6
+ end
7
+ end
8
+ end
9
+ end
@@ -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[function.name] = function
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 Sin < CMathFunction
4
+ def initialize
5
+ super("sin")
6
+ end
7
+
8
+ protected
9
+
10
+ def self.derivative(argument)
11
+ Keisan::AST::Function.new([argument], "cos")
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module Keisan
2
+ module Functions
3
+ class Sinh < CMathFunction
4
+ def initialize
5
+ super("sinh")
6
+ end
7
+
8
+ protected
9
+
10
+ def self.derivative(argument)
11
+ Keisan::AST::Function.new([argument], "cosh")
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module Keisan
2
+ module Functions
3
+ class Sqrt < CMathFunction
4
+ def initialize
5
+ super("sqrt")
6
+ end
7
+
8
+ protected
9
+
10
+ def self.derivative(argument)
11
+ Keisan::AST::Exponent.new([argument, Rational(-1,2)]) / 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
@@ -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