keisan 0.4.0 → 0.5.0

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