keisan 0.2.1 → 0.3.0

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