keisan 0.6.0 → 0.7.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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +90 -132
  3. data/bin/keisan +13 -2
  4. data/lib/keisan.rb +5 -0
  5. data/lib/keisan/ast.rb +11 -0
  6. data/lib/keisan/ast/assignment.rb +20 -55
  7. data/lib/keisan/ast/boolean.rb +16 -12
  8. data/lib/keisan/ast/cell.rb +17 -0
  9. data/lib/keisan/ast/cell_assignment.rb +70 -0
  10. data/lib/keisan/ast/function_assignment.rb +52 -0
  11. data/lib/keisan/ast/hash.rb +82 -0
  12. data/lib/keisan/ast/indexing.rb +26 -15
  13. data/lib/keisan/ast/line_builder.rb +22 -4
  14. data/lib/keisan/ast/list.rb +14 -7
  15. data/lib/keisan/ast/node.rb +13 -0
  16. data/lib/keisan/ast/null.rb +14 -0
  17. data/lib/keisan/ast/parent.rb +8 -3
  18. data/lib/keisan/ast/string.rb +10 -0
  19. data/lib/keisan/ast/variable.rb +4 -0
  20. data/lib/keisan/ast/variable_assignment.rb +62 -0
  21. data/lib/keisan/context.rb +16 -2
  22. data/lib/keisan/functions/default_registry.rb +4 -0
  23. data/lib/keisan/functions/enumerable_function.rb +56 -0
  24. data/lib/keisan/functions/filter.rb +34 -32
  25. data/lib/keisan/functions/map.rb +25 -31
  26. data/lib/keisan/functions/puts.rb +23 -0
  27. data/lib/keisan/functions/reduce.rb +29 -29
  28. data/lib/keisan/functions/registry.rb +4 -4
  29. data/lib/keisan/functions/sample.rb +5 -3
  30. data/lib/keisan/functions/to_h.rb +34 -0
  31. data/lib/keisan/interpreter.rb +42 -0
  32. data/lib/keisan/parser.rb +59 -50
  33. data/lib/keisan/parsing/compound_assignment.rb +15 -0
  34. data/lib/keisan/parsing/hash.rb +36 -0
  35. data/lib/keisan/repl.rb +1 -1
  36. data/lib/keisan/token.rb +1 -0
  37. data/lib/keisan/tokenizer.rb +23 -19
  38. data/lib/keisan/tokens/assignment.rb +21 -1
  39. data/lib/keisan/tokens/colon.rb +11 -0
  40. data/lib/keisan/tokens/unknown.rb +11 -0
  41. data/lib/keisan/variables/registry.rb +11 -6
  42. data/lib/keisan/version.rb +1 -1
  43. metadata +14 -2
@@ -66,9 +66,20 @@ module KeisanArray
66
66
  end
67
67
  end
68
68
 
69
+ module KeisanHash
70
+ def to_node
71
+ Keisan::AST::Hash.new(map {|k,v| [k.to_node, v.to_node]})
72
+ end
73
+
74
+ def value(context = nil)
75
+ self
76
+ end
77
+ end
78
+
69
79
  class Numeric; prepend KeisanNumeric; end
70
80
  class String; prepend KeisanString; end
71
81
  class TrueClass; prepend KeisanTrueClass; end
72
82
  class FalseClass; prepend KeisanFalseClass; end
73
83
  class NilClass; prepend KeisanNilClass; end
74
84
  class Array; prepend KeisanArray; end
85
+ class Hash; prepend KeisanHash; end
@@ -1,17 +1,26 @@
1
+ require_relative "variable_assignment"
2
+ require_relative "function_assignment"
3
+ require_relative "cell_assignment"
4
+
1
5
  module Keisan
2
6
  module AST
3
7
  class Assignment < Operator
4
- attr_reader :local
8
+ attr_reader :local, :compound_operator
5
9
 
6
- def initialize(children = [], parsing_operators = [], local: false)
10
+ def initialize(children = [], parsing_operators = [], local: false, compound_operator: nil)
7
11
  super(children, parsing_operators)
8
12
  @local = local
13
+ @compound_operator = compound_operator
9
14
  end
10
15
 
11
16
  def self.symbol
12
17
  :"="
13
18
  end
14
19
 
20
+ def symbol
21
+ :"#{compound_operator}="
22
+ end
23
+
15
24
  def evaluate(context = nil)
16
25
  context ||= Context.new
17
26
 
@@ -19,9 +28,9 @@ module Keisan
19
28
  rhs = children.last
20
29
 
21
30
  if is_variable_definition?
22
- evaluate_variable(context, lhs, rhs)
31
+ evaluate_variable_assignment(context, lhs, rhs)
23
32
  elsif is_function_definition?
24
- evaluate_function(context, lhs, rhs)
33
+ evaluate_function_assignment(context, lhs, rhs)
25
34
  else
26
35
  # Try cell assignment
27
36
  evaluate_cell_assignment(context, lhs, rhs)
@@ -64,61 +73,17 @@ module Keisan
64
73
 
65
74
  private
66
75
 
67
- def evaluate_cell_assignment(context, lhs, rhs)
68
- lhs = lhs.evaluate(context)
69
- unless lhs.is_a?(Cell)
70
- raise Exceptions::InvalidExpression.new("Unhandled left hand side #{lhs} in assignment")
71
- end
72
-
73
- rhs = rhs.evaluate(context)
74
-
75
- lhs.node = rhs
76
- rhs
76
+ def evaluate_variable_assignment(context, lhs, rhs)
77
+ VariableAssignment.new(self, context, lhs, rhs).evaluate
77
78
  end
78
79
 
79
- def evaluate_variable(context, lhs, rhs)
80
- rhs = rhs.evaluate(context)
81
-
82
- unless rhs.well_defined?
83
- raise Exceptions::InvalidExpression.new("Right hand side of assignment to variable must be well defined")
84
- end
85
-
86
- rhs_value = rhs.value(context)
87
- context.register_variable!(lhs.name, rhs_value, local: local)
88
- # Return the variable assigned value
89
- rhs
80
+ def evaluate_function_assignment(context, lhs, rhs)
81
+ raise Exceptions::InvalidExpression.new("Cannot do compound assignment on functions") if compound_operator
82
+ FunctionAssignment.new(context, lhs, rhs, local).evaluate
90
83
  end
91
84
 
92
- def evaluate_function(context, lhs, rhs)
93
- unless lhs.children.all? {|arg| arg.is_a?(Variable)}
94
- raise Exceptions::InvalidExpression.new("Left hand side function must have variables as arguments")
95
- end
96
-
97
- argument_names = lhs.children.map(&:name)
98
- function_definition_context = context.spawn_child(shadowed: argument_names, transient: true)
99
-
100
- # Blocks might have local variable/function definitions
101
- if !rhs.is_a?(Block)
102
- unless rhs.unbound_variables(context) <= Set.new(argument_names)
103
- raise Exceptions::InvalidExpression.new("Unbound variables found in function definition")
104
- end
105
- unless context.allow_recursive || rhs.unbound_functions(context).empty?
106
- raise Exceptions::InvalidExpression.new("Unbound function definitions are not allowed by current context")
107
- end
108
- end
109
-
110
- context.register_function!(
111
- lhs.name,
112
- Functions::ExpressionFunction.new(
113
- lhs.name,
114
- argument_names,
115
- rhs.evaluate_assignments(function_definition_context),
116
- context.transient_definitions
117
- ),
118
- local: local
119
- )
120
-
121
- rhs
85
+ def evaluate_cell_assignment(context, lhs, rhs)
86
+ CellAssignment.new(self, context, lhs, rhs).evaluate
122
87
  end
123
88
  end
124
89
  end
@@ -11,28 +11,32 @@ module Keisan
11
11
  bool
12
12
  end
13
13
 
14
+ def true?
15
+ false
16
+ end
17
+
14
18
  def !
15
19
  Boolean.new(!bool)
16
20
  end
17
21
 
18
22
  def and(other)
19
23
  other = other.to_node
20
- case other
21
- when Boolean
22
- Boolean.new(bool && other.bool)
23
- else
24
- super
25
- end
24
+ other.is_a?(Boolean) ? Boolean.new(bool && other.bool) : super
26
25
  end
27
26
 
28
27
  def or(other)
29
28
  other = other.to_node
30
- case other
31
- when Boolean
32
- Boolean.new(bool || other.bool)
33
- else
34
- super
35
- end
29
+ other.is_a?(Boolean) ? Boolean.new(bool || other.bool) : super
30
+ end
31
+
32
+ def equal(other)
33
+ other = other.to_node
34
+ other.is_a?(Boolean) ? Boolean.new(value == other.value) : super
35
+ end
36
+
37
+ def not_equal(other)
38
+ other = other.to_node
39
+ other.is_a?(Boolean) ? Boolean.new(value != other.value) : super
36
40
  end
37
41
  end
38
42
  end
@@ -24,10 +24,23 @@ module Keisan
24
24
  dupped
25
25
  end
26
26
 
27
+ def freeze
28
+ node.freeze
29
+ super
30
+ end
31
+
27
32
  def value(context = nil)
28
33
  node.value(context)
29
34
  end
30
35
 
36
+ def true?
37
+ node.true?
38
+ end
39
+
40
+ def false?
41
+ node.false?
42
+ end
43
+
31
44
  def evaluate(context = nil)
32
45
  node.evaluate(context)
33
46
  end
@@ -48,6 +61,10 @@ module Keisan
48
61
  node.replace(variable, replacement)
49
62
  end
50
63
 
64
+ def to_cell
65
+ self.class.new(node.to_cell)
66
+ end
67
+
51
68
  def to_s
52
69
  node.to_s
53
70
  end
@@ -0,0 +1,70 @@
1
+ module Keisan
2
+ module AST
3
+ class CellAssignment
4
+ attr_reader :assignment, :context, :lhs, :rhs
5
+
6
+ def initialize(assignment, context, lhs, rhs)
7
+ @assignment = assignment
8
+ @context = context
9
+ @lhs = lhs
10
+ @rhs = rhs
11
+ end
12
+
13
+ def evaluate
14
+ lhs = lhs_evaluate_and_check_modifiable
15
+
16
+ unless lhs.is_a?(Cell)
17
+ raise Exceptions::InvalidExpression.new("Unhandled left hand side #{lhs} in assignment")
18
+ end
19
+
20
+ case assignment.compound_operator
21
+ when :"||"
22
+ evaluate_cell_or_assignment(context, lhs, rhs)
23
+ when :"&&"
24
+ evaluate_cell_and_assignment(context, lhs, rhs)
25
+ else
26
+ evaluate_cell_non_logical_assignment(context, lhs, rhs)
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def lhs_evaluate_and_check_modifiable
33
+ lhs.evaluate(context)
34
+ rescue RuntimeError => e
35
+ raise Exceptions::UnmodifiableError.new("Cannot modify frozen variables") if e.message =~ /can't modify frozen/
36
+ raise
37
+ end
38
+
39
+ def evaluate_cell_or_assignment(context, lhs, rhs)
40
+ if lhs.false?
41
+ rhs = rhs.evaluate(context)
42
+ lhs.node = rhs.is_a?(Cell) ? rhs.node.deep_dup : rhs
43
+ rhs
44
+ else
45
+ lhs
46
+ end
47
+ end
48
+
49
+ def evaluate_cell_and_assignment(context, lhs, rhs)
50
+ if lhs.true?
51
+ rhs = rhs.evaluate(context)
52
+ lhs.node = rhs.is_a?(Cell) ? rhs.node.deep_dup : rhs
53
+ rhs
54
+ else
55
+ lhs
56
+ end
57
+ end
58
+
59
+ def evaluate_cell_non_logical_assignment(context, lhs, rhs)
60
+ rhs = rhs.evaluate(context)
61
+ if assignment.compound_operator
62
+ rhs = rhs.send(assignment.compound_operator, lhs.node).evaluate(context)
63
+ end
64
+
65
+ lhs.node = rhs.is_a?(Cell) ? rhs.node.deep_dup : rhs
66
+ rhs
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,52 @@
1
+ module Keisan
2
+ module AST
3
+ class FunctionAssignment
4
+ attr_reader :context, :lhs, :rhs, :local
5
+
6
+ def initialize(context, lhs, rhs, local)
7
+ @context = context
8
+ @lhs = lhs
9
+ @rhs = rhs
10
+ @local = local
11
+
12
+ unless lhs.children.all? {|arg| arg.is_a?(Variable)}
13
+ raise Exceptions::InvalidExpression.new("Left hand side function must have variables as arguments")
14
+ end
15
+ end
16
+
17
+ def argument_names
18
+ @argument_names ||= lhs.children.map(&:name)
19
+ end
20
+
21
+ def expression_function
22
+ Functions::ExpressionFunction.new(
23
+ lhs.name,
24
+ argument_names,
25
+ rhs.evaluate_assignments(context.spawn_child(shadowed: argument_names, transient: true)),
26
+ context.transient_definitions
27
+ )
28
+ end
29
+
30
+ def evaluate
31
+ # Blocks might have local variable/function definitions, so skip check
32
+ verify_rhs_of_function_assignment_is_valid! unless rhs.is_a?(Block)
33
+
34
+ context.register_function!(lhs.name, expression_function, local: local)
35
+ rhs
36
+ end
37
+
38
+ private
39
+
40
+ def verify_rhs_of_function_assignment_is_valid!
41
+ # Only variables that can appear are those that are arguments to the function
42
+ unless rhs.unbound_variables(context) <= Set.new(argument_names)
43
+ raise Exceptions::InvalidExpression.new("Unbound variables found in function definition")
44
+ end
45
+ # Cannot have undefined functions unless allowed by context
46
+ unless context.allow_recursive || rhs.unbound_functions(context).empty?
47
+ raise Exceptions::InvalidExpression.new("Unbound function definitions are not allowed by current context")
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,82 @@
1
+ module Keisan
2
+ module AST
3
+ class Hash < Node
4
+ include Enumerable
5
+
6
+ def initialize(key_value_pairs)
7
+ @hash = ::Hash[key_value_pairs.map(&:to_a).map {|k,v| [k.value, v.to_node]}]
8
+ end
9
+
10
+ def freeze
11
+ values.each(&:freeze)
12
+ super
13
+ end
14
+
15
+ def [](key)
16
+ key = key.to_node
17
+ return nil unless key.is_a?(AST::ConstantLiteral)
18
+
19
+ @hash[key.value] || Cell.new(Null.new).tap do |cell|
20
+ @hash[key.value] = cell
21
+ end
22
+ end
23
+
24
+ def evaluate(context = nil)
25
+ context ||= Context.new
26
+
27
+ @hash = ::Hash[
28
+ @hash.map do |key, val|
29
+ if val.is_a?(Cell)
30
+ [key, val]
31
+ else
32
+ [key, val.evaluate(context)]
33
+ end
34
+ end
35
+ ]
36
+
37
+ self
38
+ end
39
+
40
+ def simplify(context = nil)
41
+ evaluate(context)
42
+ end
43
+
44
+ def each(&block)
45
+ @hash.each(&block)
46
+ end
47
+
48
+ def keys
49
+ @hash.keys
50
+ end
51
+
52
+ def values
53
+ @hash.values
54
+ end
55
+
56
+ def value(context = nil)
57
+ context ||= Context.new
58
+ evaluate(context)
59
+
60
+ ::Hash[
61
+ @hash.map {|key, val|
62
+ [key, val.value(context)]
63
+ }
64
+ ]
65
+ end
66
+
67
+ def to_s
68
+ "{#{@hash.map {|k,v| "#{k.is_a?(::String) ? "'#{k}'" : k}: #{v}"}.join(', ')}}"
69
+ end
70
+
71
+ def to_cell
72
+ h = self.class.new([])
73
+ h.instance_variable_set(:@hash, ::Hash[
74
+ @hash.map do |key, value|
75
+ [key, value.to_cell]
76
+ end
77
+ ])
78
+ AST::Cell.new(h)
79
+ end
80
+ end
81
+ end
82
+ end
@@ -25,24 +25,11 @@ module Keisan
25
25
  @children = children.map {|child| child.evaluate(context)}
26
26
  @indexes = indexes.map {|index| index.evaluate(context)}
27
27
 
28
- if list = extract_list
29
- list.children[@indexes.first.value(context)]
30
- else
31
- self
32
- end
28
+ evaluate_list(context) || evaluate_hash(context) || self
33
29
  end
34
30
 
35
31
  def simplify(context = nil)
36
- context ||= Context.new
37
-
38
- @indexes = indexes.map {|index| index.simplify(context)}
39
- @children = [child.simplify(context)]
40
-
41
- if list = extract_list
42
- Cell.new(list.children[@indexes.first.value(context)].simplify(context))
43
- else
44
- self
45
- end
32
+ evaluate(context)
46
33
  end
47
34
 
48
35
  def replace(variable, replacement)
@@ -52,6 +39,20 @@ module Keisan
52
39
 
53
40
  private
54
41
 
42
+ def evaluate_list(context)
43
+ if list = extract_list
44
+ element = list.children[@indexes.first.value(context)]
45
+ element.nil? ? AST::Null.new : element
46
+ end
47
+ end
48
+
49
+ def evaluate_hash(context)
50
+ if hash = extract_hash
51
+ element = hash[@indexes.first.value(context)]
52
+ element.nil? ? AST::Null.new : element
53
+ end
54
+ end
55
+
55
56
  def extract_list
56
57
  if child.is_a?(List)
57
58
  child
@@ -61,6 +62,16 @@ module Keisan
61
62
  nil
62
63
  end
63
64
  end
65
+
66
+ def extract_hash
67
+ if child.is_a?(AST::Hash)
68
+ child
69
+ elsif child.is_a?(Cell) && child.node.is_a?(AST::Hash)
70
+ child.node
71
+ else
72
+ nil
73
+ end
74
+ end
64
75
  end
65
76
  end
66
77
  end