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
@@ -1,15 +1,15 @@
1
1
  module Keisan
2
2
  module Functions
3
- class Filter < Keisan::Function
3
+ class Filter < Function
4
4
  # Filters (list, variable, expression)
5
5
  # e.g. filter([1,2,3,4], x, x % 2 == 0)
6
6
  # should give [2,4]
7
7
  def initialize
8
- @name = "filter"
8
+ super("filter", 3)
9
9
  end
10
10
 
11
11
  def value(ast_function, context = nil)
12
- evaluate(ast_function, context = nil)
12
+ evaluate(ast_function, context)
13
13
  end
14
14
 
15
15
  def evaluate(ast_function, context = nil)
@@ -18,21 +18,23 @@ module Keisan
18
18
  end
19
19
 
20
20
  def simplify(ast_function, context = nil)
21
+ validate_arguments!(ast_function.children.count)
22
+
23
+ context ||= Context.new
21
24
  list, variable, expression = list_variable_expression_for(ast_function, context)
22
25
 
23
- context ||= Keisan::Context.new
24
26
  local = context.spawn_child(transient: false, shadowed: [variable.name])
25
27
 
26
- Keisan::AST::List.new(
28
+ AST::List.new(
27
29
  list.children.select do |element|
28
30
  local.register_variable!(variable, element)
29
31
  result = expression.evaluate(local)
30
32
 
31
33
  case result
32
- when Keisan::AST::Boolean
34
+ when AST::Boolean
33
35
  result.value
34
36
  else
35
- raise Keisan::Exceptions::InvalidFunctionError.new("Filter requires expression to be a logical expression")
37
+ raise Exceptions::InvalidFunctionError.new("Filter requires expression to be a logical expression")
36
38
  end
37
39
  end
38
40
  )
@@ -41,20 +43,16 @@ module Keisan
41
43
  private
42
44
 
43
45
  def list_variable_expression_for(ast_function, context)
44
- unless ast_function.children.size == 3
45
- raise Keisan::Exceptions::InvalidFunctionError.new("Require 3 arguments to filter")
46
- end
47
-
48
46
  list = ast_function.children[0].simplify(context)
49
47
  variable = ast_function.children[1]
50
48
  expression = ast_function.children[2]
51
49
 
52
- unless list.is_a?(Keisan::AST::List)
53
- raise Keisan::Exceptions::InvalidFunctionError.new("First argument to filter must be a list")
50
+ unless list.is_a?(AST::List)
51
+ raise Exceptions::InvalidFunctionError.new("First argument to filter must be a list")
54
52
  end
55
53
 
56
- unless variable.is_a?(Keisan::AST::Variable)
57
- raise Keisan::Exceptions::InvalidFunctionError.new("Second argument to filter must be a variable")
54
+ unless variable.is_a?(AST::Variable)
55
+ raise Exceptions::InvalidFunctionError.new("Second argument to filter must be a variable")
58
56
  end
59
57
 
60
58
  [list, variable, expression]
@@ -1,32 +1,22 @@
1
1
  module Keisan
2
2
  module Functions
3
- class If < Keisan::Function
3
+ class If < Function
4
4
  def initialize
5
- @name = "if"
5
+ super("if", ::Range.new(2,3))
6
6
  end
7
7
 
8
8
  def value(ast_function, context = nil)
9
- context ||= Keisan::Context.new
10
-
11
- unless (2..3).cover? ast_function.children.size
12
- raise Keisan::Exceptions::InvalidFunctionError.new("Require 2 or 3 arguments to if")
13
- end
14
-
15
- bool = ast_function.children[0].value(context)
16
-
17
- if bool
18
- ast_function.children[1].value(context)
19
- else
20
- ast_function.children.size == 3 ? ast_function.children[2].value(context) : nil
21
- end
9
+ validate_arguments!(ast_function.children.count)
10
+ evaluate(ast_function, context)
22
11
  end
23
12
 
24
13
  def evaluate(ast_function, context = nil)
25
- context ||= Keisan::Context.new
14
+ validate_arguments!(ast_function.children.count)
15
+ context ||= Context.new
26
16
 
27
17
  bool = ast_function.children[0].evaluate(context)
28
18
 
29
- if bool.is_a?(Keisan::AST::Boolean)
19
+ if bool.is_a?(AST::Boolean)
30
20
  node = bool.value ? ast_function.children[1] : ast_function.children[2]
31
21
  node.to_node.evaluate(context)
32
22
  else
@@ -35,14 +25,17 @@ module Keisan
35
25
  end
36
26
 
37
27
  def simplify(ast_function, context = nil)
28
+ validate_arguments!(ast_function.children.count)
38
29
  context ||= Context.new
39
30
  bool = ast_function.children[0].simplify(context)
40
31
 
41
- if bool.is_a?(Keisan::AST::Boolean)
32
+ if bool.is_a?(AST::Boolean)
42
33
  if bool.value
43
34
  ast_function.children[1].to_node.simplify(context)
44
- else
35
+ elsif ast_function.children.size >= 2
45
36
  ast_function.children[2].to_node.simplify(context)
37
+ else
38
+ Keisan::AST::Null.new
46
39
  end
47
40
  else
48
41
  ast_function
@@ -50,8 +43,9 @@ module Keisan
50
43
  end
51
44
 
52
45
  def differentiate(ast_function, variable, context = nil)
46
+ validate_arguments!(ast_function.children.count)
53
47
  context ||= Context.new
54
- Keisan::AST::Function.new(
48
+ AST::Function.new(
55
49
  [
56
50
  ast_function.children[0],
57
51
  ast_function.children[1].differentiate(variable, context),
@@ -0,0 +1,36 @@
1
+ module Keisan
2
+ module Functions
3
+ class Let < Function
4
+ def initialize
5
+ super("let", ::Range.new(1,2))
6
+ end
7
+
8
+ def value(ast_function, context = nil)
9
+ evaluate(ast_function, context)
10
+ end
11
+
12
+ def evaluate(ast_function, context = nil)
13
+ validate_arguments!(ast_function.children.count)
14
+ assignment(ast_function).evaluate(context)
15
+ end
16
+
17
+ def simplify(ast_function, context = nil)
18
+ evaluate(ast_function, context)
19
+ end
20
+
21
+ private
22
+
23
+ def assignment(ast_function)
24
+ if ast_function.children.count == 1
25
+ unless ast_function.children.first.is_a?(AST::Assignment)
26
+ raise Exceptions::InvalidFunctionError.new("`let` must accept assignment if given one argument")
27
+ end
28
+
29
+ AST::Assignment.new(ast_function.children.first.children, local: true)
30
+ else
31
+ AST::Assignment.new(ast_function.children, local: true)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -1,15 +1,15 @@
1
1
  module Keisan
2
2
  module Functions
3
- class Map < Keisan::Function
3
+ class Map < Function
4
4
  # Maps (list, variable, expression)
5
5
  # e.g. map([1,2,3], x, 2*x)
6
6
  # should give [2,4,6]
7
7
  def initialize
8
- @name = "map"
8
+ super("map", 3)
9
9
  end
10
10
 
11
11
  def value(ast_function, context = nil)
12
- evaluate(ast_function, context = nil)
12
+ evaluate(ast_function, context)
13
13
  end
14
14
 
15
15
  def evaluate(ast_function, context = nil)
@@ -18,12 +18,14 @@ module Keisan
18
18
  end
19
19
 
20
20
  def simplify(ast_function, context = nil)
21
- context ||= Keisan::Context.new
21
+ validate_arguments!(ast_function.children.count)
22
+
23
+ context ||= Context.new
22
24
  list, variable, expression = list_variable_expression_for(ast_function, context)
23
25
 
24
26
  local = context.spawn_child(transient: false, shadowed: [variable.name])
25
27
 
26
- Keisan::AST::List.new(
28
+ AST::List.new(
27
29
  list.children.map do |element|
28
30
  local.register_variable!(variable, element)
29
31
  expression.simplified(local)
@@ -34,20 +36,16 @@ module Keisan
34
36
  private
35
37
 
36
38
  def list_variable_expression_for(ast_function, context)
37
- unless ast_function.children.size == 3
38
- raise Keisan::Exceptions::InvalidFunctionError.new("Require 3 arguments to map")
39
- end
40
-
41
39
  list = ast_function.children[0].simplify(context)
42
40
  variable = ast_function.children[1]
43
41
  expression = ast_function.children[2]
44
42
 
45
- unless list.is_a?(Keisan::AST::List)
46
- raise Keisan::Exceptions::InvalidFunctionError.new("First argument to map must be a list")
43
+ unless list.is_a?(AST::List)
44
+ raise Exceptions::InvalidFunctionError.new("First argument to map must be a list")
47
45
  end
48
46
 
49
- unless variable.is_a?(Keisan::AST::Variable)
50
- raise Keisan::Exceptions::InvalidFunctionError.new("Second argument to map must be a variable")
47
+ unless variable.is_a?(AST::Variable)
48
+ raise Exceptions::InvalidFunctionError.new("Second argument to map must be a variable")
51
49
  end
52
50
 
53
51
  [list, variable, expression]
@@ -11,7 +11,7 @@ module Keisan
11
11
  end
12
12
 
13
13
  def differentiate(ast_function, variable, context = nil)
14
- raise Keisan::Exceptions::InvalidFunctionError.new unless ast_function.children.count == 1
14
+ raise Exceptions::InvalidFunctionError.new unless ast_function.children.count == 1
15
15
  context ||= Context.new
16
16
 
17
17
  argument_simplified = ast_function.children.first.simplify(context)
@@ -23,7 +23,7 @@ module Keisan
23
23
  protected
24
24
 
25
25
  def self.derivative(argument)
26
- raise Keisan::Exceptions::NotImplementedError.new
26
+ raise Exceptions::NotImplementedError.new
27
27
  end
28
28
 
29
29
  def self.apply_simplifications(simplified)
@@ -1,27 +1,30 @@
1
1
  module Keisan
2
2
  module Functions
3
- class ProcFunction < Keisan::Function
3
+ class ProcFunction < Function
4
4
  attr_reader :function_proc
5
5
 
6
6
  def initialize(name, function_proc)
7
- raise Keisan::Exceptions::InvalidFunctionError.new unless function_proc.is_a?(Proc)
7
+ raise Exceptions::InvalidFunctionError.new unless function_proc.is_a?(Proc)
8
8
 
9
- super(name)
9
+ super(name, function_proc.arity)
10
10
  @function_proc = function_proc
11
11
  end
12
12
 
13
13
  def call(context, *args)
14
+ validate_arguments!(args.count)
14
15
  function_proc.call(*args).to_node
15
16
  end
16
17
 
17
18
  def value(ast_function, context = nil)
18
- context ||= Keisan::Context.new
19
+ validate_arguments!(ast_function.children.count)
20
+ context ||= Context.new
19
21
  argument_values = ast_function.children.map {|child| child.value(context)}
20
22
  call(context, *argument_values).value(context)
21
23
  end
22
24
 
23
25
  def evaluate(ast_function, context = nil)
24
- context ||= Keisan::Context.new
26
+ validate_arguments!(ast_function.children.count)
27
+ context ||= Context.new
25
28
 
26
29
  ast_function.instance_variable_set(
27
30
  :@children,
@@ -36,6 +39,7 @@ module Keisan
36
39
  end
37
40
 
38
41
  def simplify(ast_function, context = nil)
42
+ validate_arguments!(ast_function.children.count)
39
43
  context ||= Context.new
40
44
 
41
45
  ast_function.instance_variable_set(
@@ -43,7 +47,7 @@ module Keisan
43
47
  ast_function.children.map {|child| child.evaluate(context)}
44
48
  )
45
49
 
46
- if ast_function.children.all? {|child| child.is_a?(Keisan::AST::ConstantLiteral)}
50
+ if ast_function.children.all? {|child| child.is_a?(AST::ConstantLiteral)}
47
51
  value(ast_function, context).to_node.simplify(context)
48
52
  else
49
53
  ast_function
@@ -3,6 +3,7 @@ module Keisan
3
3
  class Rand < ProcFunction
4
4
  def initialize
5
5
  @name = "rand"
6
+ @arity = ::Range.new(1,2)
6
7
  end
7
8
 
8
9
  # Single argument: integer in range [0, max)
@@ -14,7 +15,7 @@ module Keisan
14
15
  when 2
15
16
  context.random.rand(args.first...args.last)
16
17
  else
17
- raise Keisan::Exceptions::InvalidFunctionError.new
18
+ raise Exceptions::InvalidFunctionError.new
18
19
  end
19
20
  end
20
21
  end
@@ -0,0 +1,74 @@
1
+ module Keisan
2
+ module Functions
3
+ class Range < Function
4
+ def initialize
5
+ super("range", ::Range.new(1,3))
6
+ end
7
+
8
+ def call(context, *args)
9
+ start, finish, shift = start_finish_shift_from_args(*args)
10
+
11
+ if shift == 1
12
+ start_finish_range(start, finish)
13
+ else
14
+ start_finish_shift_range(start, finish, shift)
15
+ end
16
+ end
17
+
18
+ def value(ast_function, context = nil)
19
+ validate_arguments!(ast_function.children.count)
20
+ evaluate(ast_function, context)
21
+ end
22
+
23
+ def evaluate(ast_function, context = nil)
24
+ validate_arguments!(ast_function.children.count)
25
+ context ||= Keisan::Context.new
26
+ simplify(ast_function, context)
27
+ end
28
+
29
+ def simplify(ast_function, context = nil)
30
+ validate_arguments!(ast_function.children.count)
31
+ context ||= Context.new
32
+
33
+ simplified_children = ast_function.children.map {|child| child.simplify(context)}
34
+
35
+ if simplified_children.all? {|child| child.is_a?(Keisan::AST::Number)}
36
+ Keisan::AST::List.new(call(context, *simplified_children.map(&:value)))
37
+ else
38
+ Keisan::AST::Function.new(simplified_children, "range")
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def start_finish_shift_from_args(*args)
45
+ case args.count
46
+ when 1
47
+ [0, args[0], 1]
48
+ when 2
49
+ [args[0], args[1], 1]
50
+ when 3
51
+ [args[0], args[1], args[2]]
52
+ else
53
+ [0, 0, 0]
54
+ end
55
+ end
56
+
57
+ def start_finish_range(start, finish)
58
+ (start...finish).to_a
59
+ end
60
+
61
+ def start_finish_shift_range(start, finish, shift)
62
+ if shift == 0 or !shift.is_a?(Integer)
63
+ raise Keisan::Exceptions::InvalidFunctionError.new("shift argument for Range must be non-zero integer")
64
+ end
65
+
66
+ if shift > 0
67
+ (start...finish).select {|i| (i - start) % shift == 0}
68
+ else
69
+ (finish+1...start+1).select {|i| (i - finish) % shift == 0}.reverse
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -1,15 +1,15 @@
1
1
  module Keisan
2
2
  module Functions
3
- class Reduce < Keisan::Function
3
+ class Reduce < Function
4
4
  # Reduces (list, initial, accumulator, variable, expression)
5
5
  # e.g. reduce([1,2,3,4], 0, total, x, total+x)
6
6
  # should give 10
7
7
  def initialize
8
- @name = "reduce"
8
+ super("reduce", 5)
9
9
  end
10
10
 
11
11
  def value(ast_function, context = nil)
12
- evaluate(ast_function, context = nil)
12
+ evaluate(ast_function, context)
13
13
  end
14
14
 
15
15
  def evaluate(ast_function, context = nil)
@@ -18,7 +18,9 @@ module Keisan
18
18
  end
19
19
 
20
20
  def simplify(ast_function, context = nil)
21
- context ||= Keisan::Context.new
21
+ validate_arguments!(ast_function.children.count)
22
+
23
+ context ||= Context.new
22
24
  list, initial, accumulator, variable, expression = list_initial_accumulator_variable_expression_for(ast_function, context)
23
25
 
24
26
  local = context.spawn_child(transient: false, shadowed: [accumulator.name, variable.name])
@@ -36,26 +38,22 @@ module Keisan
36
38
  private
37
39
 
38
40
  def list_initial_accumulator_variable_expression_for(ast_function, context)
39
- unless ast_function.children.size == 5
40
- raise Keisan::Exceptions::InvalidFunctionError.new("Require 5 arguments to reduce")
41
- end
42
-
43
41
  list = ast_function.children[0].simplify(context)
44
42
  initial = ast_function.children[1]
45
43
  accumulator = ast_function.children[2]
46
44
  variable = ast_function.children[3]
47
45
  expression = ast_function.children[4]
48
46
 
49
- unless list.is_a?(Keisan::AST::List)
50
- raise Keisan::Exceptions::InvalidFunctionError.new("First argument to reduce must be a list")
47
+ unless list.is_a?(AST::List)
48
+ raise Exceptions::InvalidFunctionError.new("First argument to reduce must be a list")
51
49
  end
52
50
 
53
- unless accumulator.is_a?(Keisan::AST::Variable)
54
- raise Keisan::Exceptions::InvalidFunctionError.new("Third argument to reduce is accumulator and must be a variable")
51
+ unless accumulator.is_a?(AST::Variable)
52
+ raise Exceptions::InvalidFunctionError.new("Third argument to reduce is accumulator and must be a variable")
55
53
  end
56
54
 
57
- unless variable.is_a?(Keisan::AST::Variable)
58
- raise Keisan::Exceptions::InvalidFunctionError.new("Fourth argument to reduce is variable and must be a variable")
55
+ unless variable.is_a?(AST::Variable)
56
+ raise Exceptions::InvalidFunctionError.new("Fourth argument to reduce is variable and must be a variable")
59
57
  end
60
58
 
61
59
  [list, initial, accumulator, variable, expression]