keisan 0.2.1 → 0.3.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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +92 -0
  3. data/lib/keisan.rb +12 -0
  4. data/lib/keisan/ast/bitwise_and.rb +1 -5
  5. data/lib/keisan/ast/bitwise_or.rb +1 -5
  6. data/lib/keisan/ast/bitwise_xor.rb +1 -5
  7. data/lib/keisan/ast/builder.rb +57 -22
  8. data/lib/keisan/ast/exponent.rb +1 -5
  9. data/lib/keisan/ast/function.rb +50 -1
  10. data/lib/keisan/ast/logical_and.rb +1 -5
  11. data/lib/keisan/ast/logical_equal.rb +18 -0
  12. data/lib/keisan/ast/logical_greater_than.rb +4 -4
  13. data/lib/keisan/ast/logical_greater_than_or_equal_to.rb +4 -4
  14. data/lib/keisan/ast/logical_less_than.rb +4 -4
  15. data/lib/keisan/ast/logical_less_than_or_equal_to.rb +4 -4
  16. data/lib/keisan/ast/logical_not_equal.rb +18 -0
  17. data/lib/keisan/ast/logical_or.rb +1 -5
  18. data/lib/keisan/ast/modulo.rb +18 -0
  19. data/lib/keisan/ast/node.rb +26 -0
  20. data/lib/keisan/ast/operator.rb +9 -2
  21. data/lib/keisan/ast/plus.rb +1 -5
  22. data/lib/keisan/ast/priorities.rb +27 -0
  23. data/lib/keisan/ast/times.rb +1 -5
  24. data/lib/keisan/ast/variable.rb +5 -0
  25. data/lib/keisan/calculator.rb +1 -12
  26. data/lib/keisan/context.rb +20 -5
  27. data/lib/keisan/evaluator.rb +79 -0
  28. data/lib/keisan/exceptions.rb +1 -0
  29. data/lib/keisan/functions/default_registry.rb +0 -5
  30. data/lib/keisan/functions/registry.rb +7 -0
  31. data/lib/keisan/parser.rb +42 -29
  32. data/lib/keisan/parsing/dot.rb +6 -0
  33. data/lib/keisan/parsing/dot_operator.rb +12 -0
  34. data/lib/keisan/parsing/dot_word.rb +14 -0
  35. data/lib/keisan/parsing/logical_equal.rb +9 -0
  36. data/lib/keisan/parsing/logical_not_equal.rb +9 -0
  37. data/lib/keisan/parsing/modulo.rb +9 -0
  38. data/lib/keisan/tokenizer.rb +2 -1
  39. data/lib/keisan/tokens/arithmetic_operator.rb +4 -1
  40. data/lib/keisan/tokens/dot.rb +11 -0
  41. data/lib/keisan/tokens/logical_operator.rb +7 -1
  42. data/lib/keisan/variables/registry.rb +7 -0
  43. data/lib/keisan/version.rb +1 -1
  44. metadata +14 -2
@@ -0,0 +1,18 @@
1
+ module Keisan
2
+ module AST
3
+ class LogicalEqual < LogicalOperator
4
+ def arity
5
+ 2..2
6
+ end
7
+
8
+ def self.symbol
9
+ :"=="
10
+ end
11
+
12
+ def value(context=nil)
13
+ context ||= Context.new
14
+ children[0].value(context) == children[1].value(context)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -1,14 +1,14 @@
1
1
  module Keisan
2
2
  module AST
3
3
  class LogicalGreaterThan < LogicalOperator
4
- def self.priority
5
- 82
6
- end
7
-
8
4
  def arity
9
5
  2..2
10
6
  end
11
7
 
8
+ def self.symbol
9
+ :">"
10
+ end
11
+
12
12
  def value(context = nil)
13
13
  children.first.value(context) > children.last.value(context)
14
14
  end
@@ -1,14 +1,14 @@
1
1
  module Keisan
2
2
  module AST
3
3
  class LogicalGreaterThanOrEqualTo < LogicalOperator
4
- def self.priority
5
- 62
6
- end
7
-
8
4
  def arity
9
5
  2..2
10
6
  end
11
7
 
8
+ def self.symbol
9
+ :">="
10
+ end
11
+
12
12
  def value(context = nil)
13
13
  children.first.value(context) >= children.last.value(context)
14
14
  end
@@ -1,14 +1,14 @@
1
1
  module Keisan
2
2
  module AST
3
3
  class LogicalLessThan < LogicalOperator
4
- def self.priority
5
- 72
6
- end
7
-
8
4
  def arity
9
5
  2..2
10
6
  end
11
7
 
8
+ def self.symbol
9
+ :"<"
10
+ end
11
+
12
12
  def value(context = nil)
13
13
  children.first.value(context) < children.last.value(context)
14
14
  end
@@ -1,14 +1,14 @@
1
1
  module Keisan
2
2
  module AST
3
3
  class LogicalLessThanOrEqualTo < LogicalOperator
4
- def self.priority
5
- 52
6
- end
7
-
8
4
  def arity
9
5
  2..2
10
6
  end
11
7
 
8
+ def self.symbol
9
+ :"<="
10
+ end
11
+
12
12
  def value(context = nil)
13
13
  children.first.value(context) <= children.last.value(context)
14
14
  end
@@ -0,0 +1,18 @@
1
+ module Keisan
2
+ module AST
3
+ class LogicalNotEqual < LogicalOperator
4
+ def arity
5
+ 2..2
6
+ end
7
+
8
+ def self.symbol
9
+ :"!="
10
+ end
11
+
12
+ def value(context=nil)
13
+ context ||= Context.new
14
+ children[0].value(context) != children[1].value(context)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -1,15 +1,11 @@
1
1
  module Keisan
2
2
  module AST
3
3
  class LogicalOr < LogicalOperator
4
- def self.priority
5
- 12
6
- end
7
-
8
4
  def arity
9
5
  2..Float::INFINITY
10
6
  end
11
7
 
12
- def symbol
8
+ def self.symbol
13
9
  :"|"
14
10
  end
15
11
 
@@ -0,0 +1,18 @@
1
+ module Keisan
2
+ module AST
3
+ class Modulo < ArithmeticOperator
4
+ def arity
5
+ 2..Float::INFINITY
6
+ end
7
+
8
+ def self.symbol
9
+ :%
10
+ end
11
+
12
+ def value(context = nil)
13
+ children_values = children.map {|child| child.value(context)}
14
+ children_values[1..-1].inject(children_values[0], &:%)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -4,6 +4,32 @@ module Keisan
4
4
  def value(context = nil)
5
5
  raise Keisan::Exceptions::NotImplementedError.new
6
6
  end
7
+
8
+ def unbound_variables(context = nil)
9
+ context ||= Keisan::Context.new
10
+
11
+ case self
12
+ when Parent
13
+ children.inject(Set.new) do |vars, child|
14
+ vars | child.unbound_variables(context)
15
+ end
16
+ else
17
+ Set.new
18
+ end
19
+ end
20
+
21
+ def unbound_functions(context = nil)
22
+ context ||= Keisan::Context.new
23
+
24
+ case self
25
+ when Parent
26
+ children.inject(Set.new) do |fns, child|
27
+ fns | child.unbound_functions(context)
28
+ end
29
+ else
30
+ Set.new
31
+ end
32
+ end
7
33
  end
8
34
  end
9
35
  end
@@ -16,17 +16,24 @@ module Keisan
16
16
  @parsing_operators = parsing_operators
17
17
  end
18
18
 
19
-
20
19
  def arity
21
20
  raise Keisan::Exceptions::NotImplementedError.new
22
21
  end
23
22
 
23
+ def priority
24
+ self.class.priority
25
+ end
26
+
27
+ def self.priority
28
+ Keisan::AST::Priorities.priorities[symbol]
29
+ end
30
+
24
31
  def associativity
25
32
  raise Keisan::Exceptions::NotImplementedError.new
26
33
  end
27
34
 
28
35
  def symbol
29
- raise Keisan::Exceptions::NotImplementedError.new
36
+ self.class.symbol
30
37
  end
31
38
 
32
39
  def blank_value
@@ -6,15 +6,11 @@ module Keisan
6
6
  convert_minus_to_plus!
7
7
  end
8
8
 
9
- def self.priority
10
- 10
11
- end
12
-
13
9
  def arity
14
10
  2..Float::INFINITY
15
11
  end
16
12
 
17
- def symbol
13
+ def self.symbol
18
14
  :+
19
15
  end
20
16
 
@@ -0,0 +1,27 @@
1
+ module Keisan
2
+ module AST
3
+ class Priorities
4
+ PRIORITIES = {
5
+ "**": 100,
6
+ "*": 95,
7
+ "%": 90,
8
+ "+": 85,
9
+ "&": 80,
10
+ "|": 75,
11
+ "^": 70,
12
+ ">": 65,
13
+ ">=": 60,
14
+ "<": 55,
15
+ "<=": 50,
16
+ "==": 45,
17
+ "!=": 40,
18
+ "&&": 35,
19
+ "||": 30
20
+ }.freeze
21
+
22
+ def self.priorities
23
+ PRIORITIES
24
+ end
25
+ end
26
+ end
27
+ end
@@ -6,15 +6,11 @@ module Keisan
6
6
  convert_divide_to_inverse!
7
7
  end
8
8
 
9
- def self.priority
10
- 20
11
- end
12
-
13
9
  def arity
14
10
  2..Float::INFINITY
15
11
  end
16
12
 
17
- def symbol
13
+ def self.symbol
18
14
  :*
19
15
  end
20
16
 
@@ -11,6 +11,11 @@ module Keisan
11
11
  context = Keisan::Context.new if context.nil?
12
12
  context.variable(name)
13
13
  end
14
+
15
+ def unbound_variables(context = nil)
16
+ context ||= Keisan::Context.new
17
+ context.has_variable?(name) ? Set.new : Set.new([name])
18
+ end
14
19
  end
15
20
  end
16
21
  end
@@ -7,18 +7,7 @@ module Keisan
7
7
  end
8
8
 
9
9
  def evaluate(expression, definitions = {})
10
- context.spawn_child do |local|
11
- definitions.each do |name, value|
12
- case value
13
- when Proc
14
- local.register_function!(name, value)
15
- else
16
- local.register_variable!(name, value)
17
- end
18
- end
19
-
20
- Keisan::AST::Builder.new(string: expression).ast.value(local)
21
- end
10
+ Evaluator.new(self).evaluate(expression, definitions)
22
11
  end
23
12
 
24
13
  def define_variable!(name, value)
@@ -9,18 +9,29 @@ module Keisan
9
9
  @random = random
10
10
  end
11
11
 
12
- def spawn_child
13
- if block_given?
14
- yield self.class.new(parent: self)
15
- else
16
- self.class.new(parent: self)
12
+ def spawn_child(definitions = {})
13
+ child = self.class.new(parent: self)
14
+
15
+ definitions.each do |name, value|
16
+ case value
17
+ when Proc
18
+ child.register_function!(name, value)
19
+ else
20
+ child.register_variable!(name, value)
21
+ end
17
22
  end
23
+
24
+ child
18
25
  end
19
26
 
20
27
  def function(name)
21
28
  @function_registry[name.to_s]
22
29
  end
23
30
 
31
+ def has_function?(name)
32
+ @function_registry.has?(name)
33
+ end
34
+
24
35
  def register_function!(name, function)
25
36
  @function_registry.register!(name.to_s, function)
26
37
  end
@@ -29,6 +40,10 @@ module Keisan
29
40
  @variable_registry[name.to_s]
30
41
  end
31
42
 
43
+ def has_variable?(name)
44
+ @variable_registry.has?(name)
45
+ end
46
+
32
47
  def register_variable!(name, value)
33
48
  @variable_registry.register!(name.to_s, value)
34
49
  end
@@ -0,0 +1,79 @@
1
+ module Keisan
2
+ class Evaluator
3
+ WORD = /[a-zA-Z_]\w*/
4
+ VARIABLE_DEFINITION = /\A\s*(#{WORD})\s*=\s*(.+)\z/
5
+ FUNCTION_DEFINITION = /\A\s*(#{WORD})\s*\(((?:\s*#{WORD}\s*)(?:,\s*#{WORD}\s*)*)\)\s*=\s*(.+)\z/
6
+
7
+ attr_reader :calculator
8
+
9
+ def initialize(calculator)
10
+ @calculator = calculator
11
+ end
12
+
13
+ def evaluate(expression, definitions = {})
14
+ case expression
15
+ when VARIABLE_DEFINITION
16
+ variable_evaluate(expression, definitions)
17
+ when FUNCTION_DEFINITION
18
+ function_evaluate(expression, definitions)
19
+ else
20
+ pure_evaluate(expression, definitions)
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def variable_evaluate(expression, definitions = {})
27
+ match = expression.match VARIABLE_DEFINITION
28
+ name = match[1]
29
+ expression = match[2]
30
+ ast = Keisan::AST::Builder.new(string: expression).ast
31
+
32
+ context = calculator.context.spawn_child(definitions)
33
+ unbound_variables = ast.unbound_variables(context)
34
+ unless unbound_variables <= Set.new([name])
35
+ raise Keisan::Exceptions::InvalidExpression.new("Unbound variables found in variable definition")
36
+ end
37
+
38
+ calculator.context.register_variable!(name, ast.value(context))
39
+ end
40
+
41
+ def function_evaluate(expression, definitions = {})
42
+ match = expression.match FUNCTION_DEFINITION
43
+ name = match[1]
44
+ args = match[2].split(",").map(&:strip)
45
+ expression = match[3]
46
+ ast = Keisan::AST::Builder.new(string: expression).ast
47
+
48
+ context = calculator.context.spawn_child(definitions)
49
+ unbound_variables = ast.unbound_variables(context)
50
+ unbound_functions = ast.unbound_functions(context)
51
+ # Ensure the variables are contained within the arguments
52
+ unless unbound_variables <= Set.new(args) && unbound_functions <= Set.new([name])
53
+ raise Keisan::Exceptions::InvalidExpression.new("Unbound variables found in function definition")
54
+ end
55
+
56
+ function = lambda do |*received_args|
57
+ unless args.count == received_args.count
58
+ raise Keisan::Exceptions::InvalidFunctionError.new("Invalid number of arguments for #{name} function")
59
+ end
60
+
61
+ local = calculator.context.spawn_child(definitions)
62
+ args.each.with_index do |arg, i|
63
+ local.register_variable!(arg, received_args[i])
64
+ end
65
+
66
+ ast.value(local)
67
+ end
68
+
69
+ calculator.context.register_function!(
70
+ name,
71
+ Keisan::Function.new(name, function)
72
+ )
73
+ end
74
+
75
+ def pure_evaluate(expression, definitions = {})
76
+ Keisan::AST::Builder.new(string: expression).ast.value(calculator.context.spawn_child(definitions))
77
+ end
78
+ end
79
+ end
@@ -15,5 +15,6 @@ module Keisan
15
15
  class UndefinedFunctionError < StandardError; end
16
16
  class UndefinedVariableError < StandardError; end
17
17
  class UnmodifiableError < StandardError; end
18
+ class InvalidExpression < StandardError; end
18
19
  end
19
20
  end