keisan 0.5.0 → 0.6.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 (107) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +49 -1
  3. data/keisan.gemspec +1 -0
  4. data/lib/keisan.rb +30 -0
  5. data/lib/keisan/ast/assignment.rb +44 -17
  6. data/lib/keisan/ast/block.rb +60 -0
  7. data/lib/keisan/ast/boolean.rb +5 -5
  8. data/lib/keisan/ast/builder.rb +10 -207
  9. data/lib/keisan/ast/cell.rb +60 -0
  10. data/lib/keisan/ast/constant_literal.rb +9 -0
  11. data/lib/keisan/ast/exponent.rb +6 -6
  12. data/lib/keisan/ast/function.rb +12 -8
  13. data/lib/keisan/ast/indexing.rb +25 -15
  14. data/lib/keisan/ast/line_builder.rb +230 -0
  15. data/lib/keisan/ast/list.rb +28 -1
  16. data/lib/keisan/ast/literal.rb +0 -8
  17. data/lib/keisan/ast/logical_and.rb +1 -1
  18. data/lib/keisan/ast/logical_or.rb +1 -1
  19. data/lib/keisan/ast/multi_line.rb +28 -0
  20. data/lib/keisan/ast/node.rb +32 -24
  21. data/lib/keisan/ast/number.rb +31 -31
  22. data/lib/keisan/ast/operator.rb +12 -4
  23. data/lib/keisan/ast/parent.rb +4 -4
  24. data/lib/keisan/ast/plus.rb +10 -10
  25. data/lib/keisan/ast/string.rb +3 -3
  26. data/lib/keisan/ast/times.rb +8 -8
  27. data/lib/keisan/ast/unary_identity.rb +1 -1
  28. data/lib/keisan/ast/unary_inverse.rb +7 -7
  29. data/lib/keisan/ast/unary_minus.rb +5 -5
  30. data/lib/keisan/ast/unary_operator.rb +2 -2
  31. data/lib/keisan/ast/unary_plus.rb +2 -2
  32. data/lib/keisan/ast/variable.rb +26 -10
  33. data/lib/keisan/context.rb +5 -5
  34. data/lib/keisan/evaluator.rb +15 -8
  35. data/lib/keisan/function.rb +24 -6
  36. data/lib/keisan/functions/cbrt.rb +1 -1
  37. data/lib/keisan/functions/cos.rb +1 -1
  38. data/lib/keisan/functions/cosh.rb +1 -1
  39. data/lib/keisan/functions/cot.rb +1 -1
  40. data/lib/keisan/functions/coth.rb +1 -1
  41. data/lib/keisan/functions/csc.rb +1 -1
  42. data/lib/keisan/functions/csch.rb +1 -1
  43. data/lib/keisan/functions/default_registry.rb +53 -74
  44. data/lib/keisan/functions/diff.rb +18 -14
  45. data/lib/keisan/functions/erf.rb +15 -0
  46. data/lib/keisan/functions/exp.rb +1 -1
  47. data/lib/keisan/functions/expression_function.rb +15 -21
  48. data/lib/keisan/functions/filter.rb +13 -15
  49. data/lib/keisan/functions/if.rb +14 -20
  50. data/lib/keisan/functions/let.rb +36 -0
  51. data/lib/keisan/functions/map.rb +11 -13
  52. data/lib/keisan/functions/math_function.rb +2 -2
  53. data/lib/keisan/functions/proc_function.rb +10 -6
  54. data/lib/keisan/functions/rand.rb +2 -1
  55. data/lib/keisan/functions/range.rb +74 -0
  56. data/lib/keisan/functions/reduce.rb +12 -14
  57. data/lib/keisan/functions/registry.rb +7 -7
  58. data/lib/keisan/functions/replace.rb +8 -8
  59. data/lib/keisan/functions/sample.rb +2 -1
  60. data/lib/keisan/functions/sec.rb +1 -1
  61. data/lib/keisan/functions/sech.rb +1 -1
  62. data/lib/keisan/functions/sin.rb +1 -1
  63. data/lib/keisan/functions/sinh.rb +1 -1
  64. data/lib/keisan/functions/sqrt.rb +1 -1
  65. data/lib/keisan/functions/tan.rb +1 -1
  66. data/lib/keisan/functions/tanh.rb +1 -1
  67. data/lib/keisan/functions/while.rb +46 -0
  68. data/lib/keisan/parser.rb +121 -79
  69. data/lib/keisan/parsing/assignment.rb +1 -1
  70. data/lib/keisan/parsing/bitwise_and.rb +1 -1
  71. data/lib/keisan/parsing/bitwise_not.rb +1 -1
  72. data/lib/keisan/parsing/bitwise_not_not.rb +1 -1
  73. data/lib/keisan/parsing/bitwise_or.rb +1 -1
  74. data/lib/keisan/parsing/bitwise_xor.rb +1 -1
  75. data/lib/keisan/parsing/curly_group.rb +6 -0
  76. data/lib/keisan/parsing/divide.rb +1 -1
  77. data/lib/keisan/parsing/exponent.rb +1 -1
  78. data/lib/keisan/parsing/function.rb +1 -1
  79. data/lib/keisan/parsing/group.rb +1 -1
  80. data/lib/keisan/parsing/indexing.rb +1 -1
  81. data/lib/keisan/parsing/line_separator.rb +6 -0
  82. data/lib/keisan/parsing/logical_and.rb +1 -1
  83. data/lib/keisan/parsing/logical_equal.rb +1 -1
  84. data/lib/keisan/parsing/logical_greater_than.rb +1 -1
  85. data/lib/keisan/parsing/logical_greater_than_or_equal_to.rb +1 -1
  86. data/lib/keisan/parsing/logical_less_than.rb +1 -1
  87. data/lib/keisan/parsing/logical_less_than_or_equal_to.rb +1 -1
  88. data/lib/keisan/parsing/logical_not.rb +1 -1
  89. data/lib/keisan/parsing/logical_not_equal.rb +1 -1
  90. data/lib/keisan/parsing/logical_not_not.rb +1 -1
  91. data/lib/keisan/parsing/logical_or.rb +1 -1
  92. data/lib/keisan/parsing/minus.rb +1 -1
  93. data/lib/keisan/parsing/modulo.rb +1 -1
  94. data/lib/keisan/parsing/operator.rb +1 -1
  95. data/lib/keisan/parsing/plus.rb +1 -1
  96. data/lib/keisan/parsing/times.rb +1 -1
  97. data/lib/keisan/parsing/unary_minus.rb +1 -1
  98. data/lib/keisan/parsing/unary_operator.rb +1 -1
  99. data/lib/keisan/parsing/unary_plus.rb +1 -1
  100. data/lib/keisan/repl.rb +1 -1
  101. data/lib/keisan/tokenizer.rb +4 -9
  102. data/lib/keisan/tokens/group.rb +3 -1
  103. data/lib/keisan/tokens/line_separator.rb +11 -0
  104. data/lib/keisan/variables/default_registry.rb +0 -5
  105. data/lib/keisan/variables/registry.rb +7 -7
  106. data/lib/keisan/version.rb +1 -1
  107. metadata +27 -2
@@ -6,20 +6,20 @@ module Keisan
6
6
  def initialize(children = [])
7
7
  children = Array.wrap(children).map(&:to_node)
8
8
  unless children.is_a?(Array) && children.all? {|children| children.is_a?(Node)}
9
- raise Keisan::Exceptions::InternalError.new
9
+ raise Exceptions::InternalError.new
10
10
  end
11
11
  @children = children
12
12
  end
13
13
 
14
14
  def unbound_variables(context = nil)
15
- context ||= Keisan::Context.new
15
+ context ||= Context.new
16
16
  children.inject(Set.new) do |vars, child|
17
17
  vars | child.unbound_variables(context)
18
18
  end
19
19
  end
20
20
 
21
21
  def unbound_functions(context = nil)
22
- context ||= Keisan::Context.new
22
+ context ||= Context.new
23
23
  children.inject(Set.new) do |fns, child|
24
24
  fns | child.unbound_functions(context)
25
25
  end
@@ -45,7 +45,7 @@ module Keisan
45
45
  end
46
46
 
47
47
  def evaluate(context = nil)
48
- context ||= Keisan::Context.new
48
+ context ||= Context.new
49
49
  @children = children.map {|child| child.evaluate(context)}
50
50
  self
51
51
  end
@@ -39,25 +39,25 @@ module Keisan
39
39
  # Commutative, so pull in operands of any `Plus` operators
40
40
  @children = children.inject([]) do |new_children, cur_child|
41
41
  case cur_child
42
- when AST::Plus
42
+ when Plus
43
43
  new_children + cur_child.children
44
44
  else
45
45
  new_children << cur_child
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|
49
+ if children.all? {|child| child.is_a?(String)}
50
+ return String.new(children.inject("") do |result, child|
51
51
  result + child.value
52
52
  end)
53
- elsif children.all? {|child| child.is_a?(Keisan::AST::List)}
54
- return Keisan::AST::List.new(children.inject([]) do |result, child|
53
+ elsif children.all? {|child| child.is_a?(List)}
54
+ return List.new(children.inject([]) do |result, child|
55
55
  result + child.value
56
56
  end)
57
57
  end
58
58
 
59
- constants, non_constants = *children.partition {|child| child.is_a?(AST::Number)}
60
- constant = constants.inject(AST::Number.new(0), &:+).simplify(context)
59
+ constants, non_constants = *children.partition {|child| child.is_a?(Number)}
60
+ constant = constants.inject(Number.new(0), &:+).simplify(context)
61
61
 
62
62
  if non_constants.empty?
63
63
  constant
@@ -72,15 +72,15 @@ module Keisan
72
72
  end
73
73
 
74
74
  def differentiate(variable, context = nil)
75
- AST::Plus.new(children.map {|child| child.differentiate(variable, context)}).simplify(context)
75
+ Plus.new(children.map {|child| child.differentiate(variable, context)}).simplify(context)
76
76
  end
77
77
 
78
78
  private
79
79
 
80
80
  def convert_minus_to_plus!
81
81
  @parsing_operators.each.with_index do |parsing_operator, index|
82
- if parsing_operator.is_a?(Keisan::Parsing::Minus)
83
- @children[index+1] = Keisan::AST::UnaryMinus.new(@children[index+1])
82
+ if parsing_operator.is_a?(Parsing::Minus)
83
+ @children[index+1] = UnaryMinus.new(@children[index+1])
84
84
  end
85
85
  end
86
86
  end
@@ -13,10 +13,10 @@ module Keisan
13
13
 
14
14
  def +(other)
15
15
  case other
16
- when AST::String
17
- AST::String.new(value + other.value)
16
+ when String
17
+ String.new(value + other.value)
18
18
  else
19
- raise Keisan::Exceptions::TypeError.new("#{other}'s type is invalid, #{other.class}")
19
+ raise Exceptions::TypeError.new("#{other}'s type is invalid, #{other.class}")
20
20
  end
21
21
  end
22
22
 
@@ -26,17 +26,17 @@ module Keisan
26
26
  # Commutative, so pull in operands of any `Times` operators
27
27
  @children = children.inject([]) do |new_children, cur_child|
28
28
  case cur_child
29
- when AST::Times
29
+ when Times
30
30
  new_children + cur_child.children
31
31
  else
32
32
  new_children << cur_child
33
33
  end
34
34
  end
35
35
 
36
- constants, non_constants = *children.partition {|child| child.is_a?(AST::Number)}
37
- constant = constants.inject(AST::Number.new(1), &:*).simplify(context)
36
+ constants, non_constants = *children.partition {|child| child.is_a?(Number)}
37
+ constant = constants.inject(Number.new(1), &:*).simplify(context)
38
38
 
39
- return Keisan::AST::Number.new(0) if constant.value(context) == 0
39
+ return Number.new(0) if constant.value(context) == 0
40
40
 
41
41
  if non_constants.empty?
42
42
  constant
@@ -52,9 +52,9 @@ module Keisan
52
52
 
53
53
  def differentiate(variable, context = nil)
54
54
  # Product rule
55
- AST::Plus.new(
55
+ Plus.new(
56
56
  children.map.with_index do |child,i|
57
- AST::Times.new(
57
+ Times.new(
58
58
  children.slice(0,i) + [child.differentiate(variable, context)] + children.slice(i+1,children.size)
59
59
  )
60
60
  end
@@ -65,8 +65,8 @@ module Keisan
65
65
 
66
66
  def convert_divide_to_inverse!
67
67
  @parsing_operators.each.with_index do |parsing_operator, index|
68
- if parsing_operator.is_a?(Keisan::Parsing::Divide)
69
- @children[index+1] = Keisan::AST::UnaryInverse.new(@children[index+1])
68
+ if parsing_operator.is_a?(Parsing::Divide)
69
+ @children[index+1] = UnaryInverse.new(@children[index+1])
70
70
  end
71
71
  end
72
72
  end
@@ -2,7 +2,7 @@ module Keisan
2
2
  module AST
3
3
  class UnaryIdentity < UnaryOperator
4
4
  def value(context = nil)
5
- return child.value(context)
5
+ child.value(context)
6
6
  end
7
7
 
8
8
  def evaluate(context = nil)
@@ -18,8 +18,8 @@ module Keisan
18
18
 
19
19
  @children = [child.simplify(context)]
20
20
  case child
21
- when AST::Number
22
- AST::Number.new(Rational(1,child.value(context))).simplify(context)
21
+ when Number
22
+ Number.new(Rational(1,child.value(context))).simplify(context)
23
23
  else
24
24
  (child ** -1).simplify(context)
25
25
  end
@@ -27,12 +27,12 @@ module Keisan
27
27
 
28
28
  def differentiate(variable, context = nil)
29
29
  context ||= Context.new
30
- AST::Times.new(
30
+ Times.new(
31
31
  [
32
- AST::UnaryMinus.new(child.differentiate(variable, context)),
33
- AST::UnaryInverse.new(
34
- AST::Exponent.new([
35
- child.deep_dup, AST::Number.new(2)
32
+ UnaryMinus.new(child.differentiate(variable, context)),
33
+ UnaryInverse.new(
34
+ Exponent.new([
35
+ child.deep_dup, Number.new(2)
36
36
  ])
37
37
  )
38
38
  ]
@@ -17,11 +17,11 @@ module Keisan
17
17
  context ||= Context.new
18
18
 
19
19
  case child
20
- when AST::Number
21
- AST::Number.new(-child.value(context)).simplify(context)
20
+ when Number
21
+ Number.new(-child.value(context)).simplify(context)
22
22
  else
23
- AST::Times.new([
24
- AST::Number.new(-1),
23
+ Times.new([
24
+ Number.new(-1),
25
25
  child
26
26
  ]).simplify(context)
27
27
  end
@@ -29,7 +29,7 @@ module Keisan
29
29
 
30
30
  def differentiate(variable, context = nil)
31
31
  context ||= Context.new
32
- AST::Times.new([
32
+ Times.new([
33
33
  -1.to_node,
34
34
  child.differentiate(variable, context)
35
35
  ]).simplify(context)
@@ -5,7 +5,7 @@ module Keisan
5
5
  children = Array.wrap(children)
6
6
  super(children)
7
7
  if children.count != 1
8
- raise Keisan::Exceptions::ASTError.new("Unary operator takes has a single child")
8
+ raise Exceptions::ASTError.new("Unary operator takes has a single child")
9
9
  end
10
10
  end
11
11
 
@@ -26,7 +26,7 @@ module Keisan
26
26
  end
27
27
 
28
28
  def to_s
29
- if child.is_a?(AST::Operator)
29
+ if child.is_a?(Operator)
30
30
  "#{symbol.to_s}(#{child.to_s})"
31
31
  else
32
32
  "#{symbol.to_s}#{child.to_s}"
@@ -11,8 +11,8 @@ module Keisan
11
11
 
12
12
  def simplify(context = nil)
13
13
  case child
14
- when AST::Number
15
- AST::Number.new(child.value(context)).simplify(context)
14
+ when Number
15
+ Number.new(child.value(context)).simplify(context)
16
16
  else
17
17
  super
18
18
  end
@@ -8,17 +8,22 @@ module Keisan
8
8
  end
9
9
 
10
10
  def value(context = nil)
11
- context = Keisan::Context.new if context.nil?
12
- context.variable(name).value(context)
11
+ context ||= Context.new
12
+ variable_node_from_context(context).value(context)
13
13
  end
14
14
 
15
15
  def unbound_variables(context = nil)
16
- context ||= Keisan::Context.new
16
+ context ||= Context.new
17
17
  context.has_variable?(name) ? Set.new : Set.new([name])
18
18
  end
19
19
 
20
20
  def ==(other)
21
- name == other.name
21
+ case other
22
+ when Variable
23
+ name == other.name
24
+ else
25
+ false
26
+ end
22
27
  end
23
28
 
24
29
  def to_s
@@ -26,12 +31,13 @@ module Keisan
26
31
  end
27
32
 
28
33
  def evaluate(context = nil)
29
- context ||= Keisan::Context.new
34
+ context ||= Context.new
30
35
  if context.has_variable?(name)
31
- variable = context.variable(name)
36
+ variable = variable_node_from_context(context)
37
+
32
38
  # 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)
39
+ if variable.is_a?(Node)
40
+ variable.is_a?(Variable) ? variable : variable.evaluate(context)
35
41
  else
36
42
  variable
37
43
  end
@@ -41,7 +47,7 @@ module Keisan
41
47
  end
42
48
 
43
49
  def simplify(context = nil)
44
- context ||= Keisan::Context.new
50
+ context ||= Context.new
45
51
  if context.has_variable?(name)
46
52
  context.variable(name).to_node.simplify(context)
47
53
  else
@@ -58,7 +64,7 @@ module Keisan
58
64
  end
59
65
 
60
66
  def differentiate(variable, context = nil)
61
- context ||= Keisan::Context.new
67
+ context ||= Context.new
62
68
 
63
69
  if name == variable.name && !context.has_variable?(name)
64
70
  1.to_node
@@ -66,6 +72,16 @@ module Keisan
66
72
  0.to_node
67
73
  end
68
74
  end
75
+
76
+ private
77
+
78
+ def variable_node_from_context(context)
79
+ variable = context.variable(name)
80
+ if variable.is_a?(Cell)
81
+ variable = variable.node
82
+ end
83
+ variable
84
+ end
69
85
  end
70
86
  end
71
87
  end
@@ -26,7 +26,7 @@ module Keisan
26
26
  case value
27
27
  when Proc
28
28
  child.register_function!(name, value)
29
- when Keisan::Functions::ProcFunction
29
+ when Functions::ProcFunction
30
30
  child.register_function!(name, value.function_proc)
31
31
  else
32
32
  child.register_variable!(name, value)
@@ -61,8 +61,8 @@ module Keisan
61
61
  @variable_registry.has?(name)
62
62
  end
63
63
 
64
- def register_variable!(name, value)
65
- if !@variable_registry.shadowed.member?(name) && transient?
64
+ def register_variable!(name, value, local: false)
65
+ if !@variable_registry.shadowed.member?(name) && (transient? || !local && @parent&.has_variable?(name))
66
66
  @parent.register_variable!(name, value)
67
67
  else
68
68
  @variable_registry.register!(name, value)
@@ -77,8 +77,8 @@ module Keisan
77
77
  @function_registry.has?(name)
78
78
  end
79
79
 
80
- def register_function!(name, function)
81
- if transient?
80
+ def register_function!(name, function, local: false)
81
+ if transient? || !local && @parent&.has_function?(name)
82
82
  @parent.register_function!(name, function)
83
83
  else
84
84
  @function_registry.register!(name.to_s, function)
@@ -8,13 +8,14 @@ module Keisan
8
8
 
9
9
  def evaluate(expression, definitions = {})
10
10
  context = calculator.context.spawn_child(definitions: definitions, transient: true)
11
- ast = Keisan::AST.parse(expression)
12
- evaluation = ast.evaluate(context)
11
+ ast = ast(expression)
12
+ last_line = last_line(ast)
13
13
 
14
- case ast
15
- when Keisan::AST::Assignment
16
- if ast.children.first.is_a?(Keisan::AST::Variable)
17
- context.variable(ast.children.first.name).value(context)
14
+ evaluation = ast.evaluated(context)
15
+
16
+ if last_line.is_a?(AST::Assignment)
17
+ if last_line.children.first.is_a?(AST::Variable)
18
+ context.variable(last_line.children.first.name).value(context)
18
19
  end
19
20
  else
20
21
  evaluation.value(context)
@@ -23,12 +24,18 @@ module Keisan
23
24
 
24
25
  def simplify(expression, definitions = {})
25
26
  context = calculator.context.spawn_child(definitions: definitions, transient: true)
26
- ast = Keisan::AST.parse(expression)
27
+ ast = AST.parse(expression)
27
28
  ast.simplify(context)
28
29
  end
29
30
 
30
31
  def ast(expression)
31
- Keisan::AST.parse(expression)
32
+ AST.parse(expression)
33
+ end
34
+
35
+ private
36
+
37
+ def last_line(ast)
38
+ ast.is_a?(AST::MultiLine) ? ast.children.last : ast
32
39
  end
33
40
  end
34
41
  end
@@ -1,25 +1,43 @@
1
1
  module Keisan
2
2
  class Function
3
- attr_reader :name
3
+ attr_reader :name, :arity
4
4
 
5
- def initialize(name)
5
+ def initialize(name, arity = 1)
6
6
  @name = name
7
+ @arity = arity
7
8
  end
8
9
 
9
10
  def value(ast_function, context = nil)
10
- raise Keisan::Exceptions::NotImplementedError.new
11
+ raise Exceptions::NotImplementedError.new
11
12
  end
12
13
 
13
14
  def evaluate(ast_function, context = nil)
14
- raise Keisan::Exceptions::NotImplementedError.new
15
+ raise Exceptions::NotImplementedError.new
15
16
  end
16
17
 
17
18
  def simplify(ast_function, context = nil)
18
- raise Keisan::Exceptions::NotImplementedError.new
19
+ raise Exceptions::NotImplementedError.new
19
20
  end
20
21
 
21
22
  def differentiate(ast_function, variable, context = nil)
22
- raise Keisan::Exceptions::NotImplementedError.new
23
+ raise Exceptions::NotImplementedError.new
24
+ end
25
+
26
+ protected
27
+
28
+ def validate_arguments!(count)
29
+ case arity
30
+ when Integer
31
+ if arity < 0 && count < arity.abs || arity >= 0 && count != arity
32
+ raise Keisan::Exceptions::InvalidFunctionError.new("Require #{arity} arguments to #{name}")
33
+ end
34
+ when Range
35
+ unless arity.include? count
36
+ raise Keisan::Exceptions::InvalidFunctionError.new("Require #{arity} arguments to #{name}")
37
+ end
38
+ else
39
+ raise Keisan::Exceptions::InternalError.new("Invalid arity: #{arity}")
40
+ end
23
41
  end
24
42
  end
25
43
  end
@@ -8,7 +8,7 @@ module Keisan
8
8
  protected
9
9
 
10
10
  def self.derivative(argument)
11
- Keisan::AST::Exponent.new([argument, Rational(-2,3)]) / 3
11
+ AST::Exponent.new([argument, Rational(-2,3)]) / 3
12
12
  end
13
13
  end
14
14
  end