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
@@ -22,7 +22,13 @@ module Keisan
22
22
 
23
23
  consume_operators!
24
24
 
25
- unless @nodes.count == 1
25
+ case @nodes.count
26
+ when 0
27
+ # Empty string, set to just Null
28
+ @nodes = [Keisan::AST::Null.new]
29
+ when 1
30
+ # Good
31
+ else
26
32
  raise Keisan::Exceptions::ASTError.new("Should end up with a single node")
27
33
  end
28
34
  end
@@ -6,7 +6,12 @@ module Keisan
6
6
  end
7
7
 
8
8
  def to_s
9
- value.to_s
9
+ case value
10
+ when Rational
11
+ "(#{value.to_s})"
12
+ else
13
+ value.to_s
14
+ end
10
15
  end
11
16
  end
12
17
  end
@@ -58,7 +58,7 @@ module Keisan
58
58
 
59
59
  # Special simple case where exponent is a pure number
60
60
  if exponent.is_a?(AST::Number)
61
- return (exponent * base ** (exponent -1)).simplify(context)
61
+ return (exponent * base.differentiate(variable, context) * base ** (exponent -1)).simplify(context)
62
62
  end
63
63
 
64
64
  base_diff = base.differentiate(variable, context)
@@ -47,7 +47,7 @@ module Keisan
47
47
  if function_defined?(context)
48
48
  function_from_context(context).evaluate(self, context)
49
49
  else
50
- @children = children.map {|child| child.evaluate(context)}
50
+ @children = children.map {|child| child.evaluate(context).to_node}
51
51
  self
52
52
  end
53
53
  end
@@ -58,7 +58,7 @@ module Keisan
58
58
  if function_defined?(context)
59
59
  function_from_context(context).simplify(self, context)
60
60
  else
61
- @children = children.map {|child| child.simplify(context)}
61
+ @children = children.map {|child| child.simplify(context).to_node}
62
62
  self
63
63
  end
64
64
  end
@@ -68,10 +68,14 @@ module Keisan
68
68
  end
69
69
 
70
70
  def differentiate(variable, context = nil)
71
+ function = function_from_context(context)
72
+ function.differentiate(self, variable, context)
73
+
74
+ rescue Keisan::Exceptions::UndefinedFunctionError, Keisan::Exceptions::NotImplementedError
71
75
  unless unbound_variables(context).include?(variable.name)
72
76
  return AST::Number.new(0)
73
77
  end
74
- # Do not know how to differentiate a function in general, so leave as derivative
78
+
75
79
  AST::Function.new([self, variable], "diff")
76
80
  end
77
81
  end
@@ -2,7 +2,7 @@ module Keisan
2
2
  module AST
3
3
  class List < Parent
4
4
  def value(context = nil)
5
- context = Keisan::Context.new if context.nil?
5
+ context ||= Keisan::Context.new
6
6
  children.map {|child| child.value(context)}
7
7
  end
8
8
 
@@ -80,7 +80,7 @@ module Keisan
80
80
  end
81
81
 
82
82
  def !
83
- AST::LogicalNot.new(self)
83
+ AST::UnaryLogicalNot.new(self)
84
84
  end
85
85
 
86
86
  def ~
@@ -4,7 +4,7 @@ module Keisan
4
4
  attr_reader :children
5
5
 
6
6
  def initialize(children = [])
7
- children = Array.wrap(children)
7
+ children = Array.wrap(children).map(&:to_node)
8
8
  unless children.is_a?(Array) && children.all? {|children| children.is_a?(Node)}
9
9
  raise Keisan::Exceptions::InternalError.new
10
10
  end
@@ -24,7 +24,7 @@ module Keisan
24
24
  children_values.inject([], &:+)
25
25
  else
26
26
  children_values.inject(0, &:+)
27
- end
27
+ end.to_node.value(context)
28
28
  end
29
29
 
30
30
  def evaluate(context = nil)
@@ -46,6 +46,16 @@ module Keisan
46
46
  end
47
47
  end
48
48
 
49
+ if children.all? {|child| child.is_a?(Keisan::AST::String)}
50
+ return Keisan::AST::String.new(children.inject("") do |result, child|
51
+ result + child.value
52
+ end)
53
+ elsif children.all? {|child| child.is_a?(Keisan::AST::List)}
54
+ return Keisan::AST::List.new(children.inject([]) do |result, child|
55
+ result + child.value
56
+ end)
57
+ end
58
+
49
59
  constants, non_constants = *children.partition {|child| child.is_a?(AST::Number)}
50
60
  constant = constants.inject(AST::Number.new(0), &:+).simplify(context)
51
61
 
@@ -19,6 +19,14 @@ module Keisan
19
19
  raise Keisan::Exceptions::TypeError.new("#{other}'s type is invalid, #{other.class}")
20
20
  end
21
21
  end
22
+
23
+ def to_s
24
+ if value =~ /\"/
25
+ "\"#{value.gsub("\"", "\\\"")}\""
26
+ else
27
+ "\"#{value}\""
28
+ end
29
+ end
22
30
  end
23
31
  end
24
32
  end
@@ -3,7 +3,7 @@ module Keisan
3
3
  class UnaryOperator < Operator
4
4
  def initialize(children = [])
5
5
  children = Array.wrap(children)
6
- super
6
+ super(children)
7
7
  if children.count != 1
8
8
  raise Keisan::Exceptions::ASTError.new("Unary operator takes has a single child")
9
9
  end
@@ -9,7 +9,7 @@ module Keisan
9
9
 
10
10
  def value(context = nil)
11
11
  context = Keisan::Context.new if context.nil?
12
- context.variable(name)
12
+ context.variable(name).value(context)
13
13
  end
14
14
 
15
15
  def unbound_variables(context = nil)
@@ -28,7 +28,13 @@ module Keisan
28
28
  def evaluate(context = nil)
29
29
  context ||= Keisan::Context.new
30
30
  if context.has_variable?(name)
31
- context.variable(name).to_node.evaluate(context)
31
+ variable = context.variable(name)
32
+ # The variable might just be a variable, i.e. probably in function definition
33
+ if variable.is_a?(AST::Node)
34
+ variable.is_a?(AST::Variable) ? variable : variable.evaluate(context)
35
+ else
36
+ variable
37
+ end
32
38
  else
33
39
  self
34
40
  end
@@ -6,10 +6,26 @@ module Keisan
6
6
  @context = context || Context.new(allow_recursive: allow_recursive)
7
7
  end
8
8
 
9
+ def allow_recursive
10
+ context.allow_recursive
11
+ end
12
+
13
+ def allow_recursive!
14
+ context.allow_recursive!
15
+ end
16
+
9
17
  def evaluate(expression, definitions = {})
10
18
  Evaluator.new(self).evaluate(expression, definitions)
11
19
  end
12
20
 
21
+ def simplify(expression, definitions = {})
22
+ Evaluator.new(self).simplify(expression, definitions)
23
+ end
24
+
25
+ def ast(expression)
26
+ Evaluator.new(self).ast(expression)
27
+ end
28
+
13
29
  def define_variable!(name, value)
14
30
  context.register_variable!(name, value)
15
31
  end
@@ -2,30 +2,57 @@ module Keisan
2
2
  class Context
3
3
  attr_reader :function_registry, :variable_registry, :allow_recursive
4
4
 
5
- def initialize(parent: nil, random: nil, allow_recursive: false)
5
+ def initialize(parent: nil, random: nil, allow_recursive: false, shadowed: [])
6
6
  @parent = parent
7
7
  @function_registry = Functions::Registry.new(parent: @parent.try(:function_registry))
8
- @variable_registry = Variables::Registry.new(parent: @parent.try(:variable_registry))
8
+ @variable_registry = Variables::Registry.new(parent: @parent.try(:variable_registry), shadowed: shadowed)
9
9
  @random = random
10
10
  @allow_recursive = allow_recursive
11
11
  end
12
12
 
13
- def spawn_child(definitions: {}, transient: false)
14
- child = Context.new(parent: self, allow_recursive: allow_recursive)
13
+ def allow_recursive!
14
+ @allow_recursive = true
15
+ end
16
+
17
+ # A transient context does not persist variables and functions in this context, but
18
+ # rather store them one level higher in the parent context. When evaluating a string,
19
+ # the entire operation is done in a transient context that is unique from the calculators
20
+ # current context, but such that variable/function definitions can be persisted in
21
+ # the calculator.
22
+ def spawn_child(definitions: {}, shadowed: [], transient: nil)
23
+ child = pure_child(shadowed: shadowed)
15
24
 
16
25
  definitions.each do |name, value|
17
26
  case value
18
27
  when Proc
19
28
  child.register_function!(name, value)
29
+ when Keisan::Functions::ProcFunction
30
+ child.register_function!(name, value.function_proc)
20
31
  else
21
32
  child.register_variable!(name, value)
22
33
  end
23
34
  end
24
35
 
25
- child.set_transient! if transient
36
+ if transient.nil? && self.transient? || transient == true
37
+ child.set_transient!
38
+ end
26
39
  child
27
40
  end
28
41
 
42
+ def transient_definitions
43
+ return {} unless @transient
44
+ parent_definitions = @parent.present? ? @parent.transient_definitions : {}
45
+ parent_definitions.merge(
46
+ @variable_registry.locals
47
+ ).merge(
48
+ @function_registry.locals
49
+ )
50
+ end
51
+
52
+ def transient?
53
+ !!@transient
54
+ end
55
+
29
56
  def variable(name)
30
57
  @variable_registry[name.to_s]
31
58
  end
@@ -35,10 +62,10 @@ module Keisan
35
62
  end
36
63
 
37
64
  def register_variable!(name, value)
38
- if @transient
65
+ if !@variable_registry.shadowed.member?(name) && transient?
39
66
  @parent.register_variable!(name, value)
40
67
  else
41
- @variable_registry.register!(name.to_s, value)
68
+ @variable_registry.register!(name, value)
42
69
  end
43
70
  end
44
71
 
@@ -51,7 +78,7 @@ module Keisan
51
78
  end
52
79
 
53
80
  def register_function!(name, function)
54
- if @transient
81
+ if transient?
55
82
  @parent.register_function!(name, function)
56
83
  else
57
84
  @function_registry.register!(name.to_s, function)
@@ -67,5 +94,9 @@ module Keisan
67
94
  def set_transient!
68
95
  @transient = true
69
96
  end
97
+
98
+ def pure_child(shadowed: [])
99
+ self.class.new(parent: self, shadowed: shadowed, allow_recursive: allow_recursive)
100
+ end
70
101
  end
71
102
  end
@@ -14,11 +14,21 @@ module Keisan
14
14
  case ast
15
15
  when Keisan::AST::Assignment
16
16
  if ast.children.first.is_a?(Keisan::AST::Variable)
17
- context.variable(ast.children.first.name)
17
+ context.variable(ast.children.first.name).value(context)
18
18
  end
19
19
  else
20
20
  evaluation.value(context)
21
21
  end
22
22
  end
23
+
24
+ def simplify(expression, definitions = {})
25
+ context = calculator.context.spawn_child(definitions: definitions, transient: true)
26
+ ast = Keisan::AST.parse(expression)
27
+ ast.simplify(context)
28
+ end
29
+
30
+ def ast(expression)
31
+ Keisan::AST.parse(expression)
32
+ end
23
33
  end
24
34
  end
@@ -17,5 +17,9 @@ module Keisan
17
17
  def simplify(ast_function, context = nil)
18
18
  raise Keisan::Exceptions::NotImplementedError.new
19
19
  end
20
+
21
+ def differentiate(ast_function, variable, context = nil)
22
+ raise Keisan::Exceptions::NotImplementedError.new
23
+ end
20
24
  end
21
25
  end
@@ -0,0 +1,9 @@
1
+ module Keisan
2
+ module Functions
3
+ class Abs < CMathFunction
4
+ def initialize
5
+ super("abs", Proc.new {|arg| arg.abs})
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,15 @@
1
+ module Keisan
2
+ module Functions
3
+ class Cbrt < CMathFunction
4
+ def initialize
5
+ super("cbrt")
6
+ end
7
+
8
+ protected
9
+
10
+ def self.derivative(argument)
11
+ Keisan::AST::Exponent.new([argument, Rational(-2,3)]) / 3
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ require "cmath"
2
+
3
+ module Keisan
4
+ module Functions
5
+ class CMathFunction < MathFunction
6
+ def initialize(name, proc_function = nil)
7
+ super(name, proc_function || Proc.new {|arg| CMath.send(name, arg)})
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ module Keisan
2
+ module Functions
3
+ class Cos < CMathFunction
4
+ def initialize
5
+ super("cos")
6
+ end
7
+
8
+ protected
9
+
10
+ def self.derivative(argument)
11
+ -Keisan::AST::Function.new([argument], "sin")
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module Keisan
2
+ module Functions
3
+ class Cosh < CMathFunction
4
+ def initialize
5
+ super("cosh")
6
+ end
7
+
8
+ protected
9
+
10
+ def self.derivative(argument)
11
+ Keisan::AST::Function.new([argument], "sinh")
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module Keisan
2
+ module Functions
3
+ class Cot < CMathFunction
4
+ def initialize
5
+ super("cot", Proc.new {|arg| 1 / CMath::tan(arg)})
6
+ end
7
+
8
+ protected
9
+
10
+ def self.derivative(argument)
11
+ -Keisan::AST::Exponent.new([Keisan::AST::Function.new([argument], "sin"), -2])
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module Keisan
2
+ module Functions
3
+ class Coth < CMathFunction
4
+ def initialize
5
+ super("coth", Proc.new {|arg| 1 / CMath::tanh(arg)})
6
+ end
7
+
8
+ protected
9
+
10
+ def self.derivative(argument)
11
+ -Keisan::AST::Exponent.new([Keisan::AST::Function.new([argument], "sinh"), -2])
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module Keisan
2
+ module Functions
3
+ class Csc < CMathFunction
4
+ def initialize
5
+ super("csc", Proc.new {|arg| 1 / CMath::sin(arg)})
6
+ end
7
+
8
+ protected
9
+
10
+ def self.derivative(argument)
11
+ -Keisan::AST::Function.new([argument], "cos") * Keisan::AST::Exponent.new([Keisan::AST::Function.new([argument], "sin"), -2])
12
+ end
13
+ end
14
+ end
15
+ end