keisan 0.6.0 → 0.7.0

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