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
@@ -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