keisan 0.5.0 → 0.6.0

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