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
@@ -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]