loxxy 0.0.16 → 0.0.21

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.
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'environment'
4
+
5
+ module Loxxy
6
+ module BackEnd
7
+ # A symbol table is basically a mapping from a name onto an object
8
+ # that holds information associated with that name. It is a data structure
9
+ # that keeps track of variables and their respective environment where they are
10
+ # declared. The key requirements for the symbol are:
11
+ # - To perform fast lookup operations: given a name, retrieve the corresponding
12
+ # object.
13
+ # - To allow the efficient insertion of names and related information
14
+ # - To support the nesting of environments
15
+ # - To handle the entry environment and exit environment events,
16
+ # - To cope with variable redefinition in nested environment
17
+ # The terminology 'symbol table' comes from the compiler design
18
+ # community.
19
+ class SymbolTable
20
+ # Mapping between a name and the environment(s) where it is defined
21
+ # @return [Hash{String => Array<Environment>}]
22
+ attr_reader :name2envs
23
+
24
+ # @return [Environment] The top-level environment (= root of environment tree)
25
+ attr_reader :root
26
+
27
+ # @return [Environment] The current environment.
28
+ attr_reader :current_env
29
+
30
+ # Build symbol table with given environment as root.
31
+ # @param anEnv [BackEnd::Environment,NilClass] The top-level Environment
32
+ def initialize(anEnv = nil)
33
+ @name2envs = {}
34
+ init_root(anEnv) # Set default (global) environment
35
+ end
36
+
37
+ # Returns iff there is no entry in the symbol table
38
+ # @return [Boolean]
39
+ def empty?
40
+ name2envs.empty?
41
+ end
42
+
43
+ # Use this method to signal the interpreter that a given environment
44
+ # to be a child of current environment and to be itself the new current environment.
45
+ # @param anEnv [BackEnd::Environment] the Environment that
46
+ def enter_environment(anEnv)
47
+ anEnv.parent = current_env
48
+ @current_env = anEnv
49
+ end
50
+
51
+ def leave_environment
52
+ current_env.defns.each_pair do |nm, _item|
53
+ environments = name2envs[nm]
54
+ if environments.size == 1
55
+ name2envs.delete(nm)
56
+ else
57
+ environments.pop
58
+ name2envs[nm] = environments
59
+ end
60
+ end
61
+ raise StandardError, 'Cannot remove root environment.' if current_env == root
62
+
63
+ @current_env = current_env.parent
64
+ end
65
+
66
+ # Add an entry with given name to current environment.
67
+ # @param anEntry [Variable]
68
+ # @return [String] Internal name of the entry
69
+ def insert(anEntry)
70
+ current_env.insert(anEntry)
71
+ name = anEntry.name
72
+ if name2envs.include?(name)
73
+ name2envs[name] << current_env
74
+ else
75
+ name2envs[name] = [current_env]
76
+ end
77
+
78
+ anEntry.name # anEntry.i_name
79
+ end
80
+
81
+ # Search for the object with the given name
82
+ # @param aName [String]
83
+ # @return [BackEnd::Variable]
84
+ def lookup(aName)
85
+ environments = name2envs.fetch(aName, nil)
86
+ return nil if environments.nil?
87
+
88
+ sc = environments.last
89
+ sc.defns[aName]
90
+ end
91
+
92
+ # Search for the object with the given i_name
93
+ # @param anIName [String]
94
+ # @return [BackEnd::Variable]
95
+ # def lookup_i_name(anIName)
96
+ # found = nil
97
+ # environment = current_env
98
+
99
+ # begin
100
+ # found = environment.defns.values.find { |e| e.i_name == anIName }
101
+ # break if found
102
+
103
+ # environment = environment.parent
104
+ # end while environment
105
+
106
+ # found
107
+ # end
108
+
109
+ # Return all variables defined in the current .. root chain.
110
+ # Variables are sorted top-down and left-to-right.
111
+ def all_variables
112
+ vars = []
113
+ skope = current_env
114
+ while skope
115
+ vars_of_environment = skope.defns.select { |_, item| item.kind_of?(Variable) }
116
+ vars = vars_of_environment.values.concat(vars)
117
+ skope = skope.parent
118
+ end
119
+
120
+ vars
121
+ end
122
+
123
+ private
124
+
125
+ def init_root(anEnv)
126
+ @root = valid_environment(anEnv)
127
+ @current_env = @root
128
+ end
129
+
130
+ def valid_environment(anEnv)
131
+ anEnv.nil? ? Environment.new : anEnv
132
+ end
133
+ end # class
134
+ end # module
135
+ end # module
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'entry'
4
+ require_relative '../datatype/all_datatypes'
5
+
6
+ module Loxxy
7
+ module BackEnd
8
+ # Representation of a Lox variable.
9
+ # It is a named slot that can be associated with a value at the time.
10
+ class Variable
11
+ include Entry # Add expected behaviour for symbol table entries
12
+
13
+ # @return [Datatype::BuiltinDatatype] the value assigned to the variable
14
+ attr_accessor :value
15
+
16
+ # Create a variable with given name and initial value
17
+ # @param aName [String] The name of the variable
18
+ # @param aValue [Datatype::BuiltinDatatype] the initial assigned value
19
+ def initialize(aName, aValue = Datatype::Nil.instance)
20
+ init_name(aName)
21
+ @value = aValue
22
+ end
23
+ end # class
24
+ end # module
25
+ end # module
@@ -14,6 +14,48 @@ module Loxxy
14
14
  @value = validated_value(aValue)
15
15
  end
16
16
 
17
+ # Is the value considered falsey in Lox?
18
+ # Rule: false and nil are falsey and everything else is truthy.
19
+ # This test used in conditional statements (i.e. if, while)
20
+ def falsey?
21
+ false # Default implementation
22
+ end
23
+
24
+ # Is the value considered truthy in Lox?
25
+ # Rule: false and nil are falsey and everything else is truthy.
26
+ # This test used in conditional statements (i.e. if, while)
27
+ def truthy?
28
+ true # Default implementation
29
+ end
30
+
31
+ # Check for inequality of this object with another Lox object
32
+ # @param other [Datatype::BuiltinDatatype, Object]
33
+ # @return [Datatype::Boolean]
34
+ def !=(other)
35
+ !(self == other)
36
+ end
37
+
38
+ # Negation ('not')
39
+ # Returns a boolean with opposite truthiness value.
40
+ # @return [Datatype::Boolean]
41
+ def !
42
+ falsey? ? True.instance : False.instance
43
+ end
44
+
45
+ # Returns the first falsey argument (if any),
46
+ # otherwise returns the last truthy argument.
47
+ # @param operand2 [Loxxy::Datatype::BuiltinDatatype, Proc]
48
+ def and(operand2)
49
+ falsey? ? self : logical_2nd_arg(operand2)
50
+ end
51
+
52
+ # Returns the first truthy argument (if any),
53
+ # otherwise returns the last falsey argument.
54
+ # @param operand2 [Loxxy::Datatype::BuiltinDatatype, Proc]
55
+ def or(operand2)
56
+ truthy? ? self : logical_2nd_arg(operand2)
57
+ end
58
+
17
59
  # Method called from Lox to obtain the text representation of the boolean.
18
60
  # @return [String]
19
61
  def to_str
@@ -25,6 +67,19 @@ module Loxxy
25
67
  def validated_value(aValue)
26
68
  aValue
27
69
  end
70
+
71
+ def logical_2nd_arg(operand2)
72
+ case operand2
73
+ when false
74
+ False.instance # Convert to Lox equivalent
75
+ when nil
76
+ Nil.instance # Convert to Lox equivalent
77
+ when true
78
+ True.instance # Convert to Lox equivalent
79
+ else
80
+ operand2
81
+ end
82
+ end
28
83
  end # class
29
84
  end # module
30
85
  end # module
@@ -20,6 +20,20 @@ module Loxxy
20
20
  true
21
21
  end
22
22
 
23
+ # Is the value considered falsey in Lox?
24
+ # Rule: false and nil are falsey and everything else is truthy.
25
+ # This test used in conditional statements (i.e. if, while)
26
+ def falsey?
27
+ true
28
+ end
29
+
30
+ # Is the value considered truthy in Lox?
31
+ # Rule: false and nil are falsey and everything else is truthy.
32
+ # This test used in conditional statements (i.e. if, while)
33
+ def truthy?
34
+ false
35
+ end
36
+
23
37
  # Check for equality of a Lox False with another Lox object
24
38
  # @param other [Datatype::BuiltinDatatype, FalseClass, Object]
25
39
  # @return [Datatype::Boolean]
@@ -27,14 +41,6 @@ module Loxxy
27
41
  falsey = other.kind_of?(False) || other.kind_of?(FalseClass)
28
42
  falsey ? True.instance : False.instance
29
43
  end
30
-
31
- # Check for inequality of a Lox False with another Lox object
32
- # @param other [Datatype::BuiltinDatatype, FalseClass, Object]
33
- # @return [Datatype::Boolean]
34
- def !=(other)
35
- falsey = other.kind_of?(False) || other.kind_of?(FalseClass)
36
- falsey ? False.instance : True.instance
37
- end
38
44
  end # class
39
45
 
40
46
  False.instance.freeze # Make the sole instance immutable
@@ -21,20 +21,6 @@ module Loxxy
21
21
  end
22
22
  end
23
23
 
24
- # Check the inequality of a Lox String with another Lox object.
25
- # @param other [Datatype::LxString, String, Object]
26
- # @return [Datatype::Boolean]
27
- def !=(other)
28
- case other
29
- when LXString
30
- (value != other.value) ? True.instance : False.instance
31
- when String
32
- (value != other) ? True.instance : False.instance
33
- else
34
- True.instance
35
- end
36
- end
37
-
38
24
  # Perform the concatenation of two Lox stings or
39
25
  # one Lox string and a Ruby String
40
26
  # @param other [Loxxy::Datatype::LXString, String]
@@ -22,12 +22,18 @@ module Loxxy
22
22
  is_nil ? True.instance : False.instance
23
23
  end
24
24
 
25
- # Check the inequality with another object.
26
- # @param other [Datatype::BuiltinDatatype, NilClass, Object]
27
- # @return [Datatype::Boolean]
28
- def !=(other)
29
- is_nil = other.kind_of?(Nil) || other.kind_of?(NilClass)
30
- is_nil ? False.instance : True.instance
25
+ # Is the value considered falsey in Lox?
26
+ # Rule: false and nil are falsey and everything else is truthy.
27
+ # This test used in conditional statements (i.e. if, while)
28
+ def falsey?
29
+ true
30
+ end
31
+
32
+ # Is the value considered truthy in Lox?
33
+ # Rule: false and nil are falsey and everything else is truthy.
34
+ # This test used in conditional statements (i.e. if, while)
35
+ def truthy?
36
+ false
31
37
  end
32
38
 
33
39
  # Method called from Lox to obtain the text representation of nil.
@@ -71,6 +71,12 @@ module Loxxy
71
71
  end
72
72
  end
73
73
 
74
+ # Unary minus (return value with changed sign)
75
+ # @return [Loxxy::Datatype::Number]
76
+ def -@
77
+ self.class.new(-value)
78
+ end
79
+
74
80
  # Check the equality of a Lox number object with another object
75
81
  # @param other [Datatype::BuiltinDatatype, Numeric, Object]
76
82
  # @return [Datatype::Boolean]
@@ -85,20 +91,52 @@ module Loxxy
85
91
  end
86
92
  end
87
93
 
88
- # Check the inequality of a Lox number object with another object
89
- # @param other [Datatype::BuiltinDatatype, Numeric, Object]
94
+ # Check whether this Lox number has a greater value than given argument.
95
+ # @param other [Datatype::Number, Numeric]
96
+ # @return [Datatype::Boolean]
97
+ def >(other)
98
+ case other
99
+ when Number
100
+ (value > other.value) ? True.instance : False.instance
101
+ when Numeric
102
+ (value > other) ? True.instance : False.instance
103
+ else
104
+ msg = "'>': Operands must be numbers."
105
+ raise StandardError, msg
106
+ end
107
+ end
108
+
109
+ # Check whether this Lox number has a greater or equal value
110
+ # than given argument.
111
+ # @param other [Datatype::Number, Numeric]
90
112
  # @return [Datatype::Boolean]
91
- def !=(other)
113
+ def >=(other)
92
114
  case other
93
115
  when Number
94
- (value != other.value) ? True.instance : False.instance
116
+ (value >= other.value) ? True.instance : False.instance
95
117
  when Numeric
96
- (value != other) ? True.instance : False.instance
118
+ (value >= other) ? True.instance : False.instance
97
119
  else
98
- True.instance
120
+ msg = "'>': Operands must be numbers."
121
+ raise StandardError, msg
99
122
  end
100
123
  end
101
124
 
125
+ # Check whether this Lox number has a lesser value than given argument.
126
+ # @param other [Datatype::Number, Numeric]
127
+ # @return [Datatype::Boolean]
128
+ def <(other)
129
+ !(self >= other)
130
+ end
131
+
132
+ # Check whether this Lox number has a lesser or equal value
133
+ # than given argument.
134
+ # @param other [Datatype::Number, Numeric]
135
+ # @return [Datatype::Boolean]
136
+ def <=(other)
137
+ !(self > other)
138
+ end
139
+
102
140
  protected
103
141
 
104
142
  def validated_value(aValue)
@@ -20,21 +20,13 @@ module Loxxy
20
20
  true
21
21
  end
22
22
 
23
- # Check for equality of a Lox True with another Lox object
23
+ # Check for equality of a Lox True with another Lox object / Ruby true
24
24
  # @param other [Datatype::True, TrueClass, Object]
25
25
  # @return [Datatype::Boolean]
26
26
  def ==(other)
27
27
  thruthy = other.kind_of?(True) || other.kind_of?(TrueClass)
28
28
  thruthy ? True.instance : False.instance
29
29
  end
30
-
31
- # Check for inequality of a Lox True with another Lox object
32
- # @param other [Datatype::BuiltinDatatype, TrueClass, Object]
33
- # @return [Datatype::Boolean]
34
- def !=(other)
35
- thruthy = other.kind_of?(True) || other.kind_of?(TrueClass)
36
- thruthy ? False.instance : True.instance
37
- end
38
30
  end # class
39
31
 
40
32
  True.instance.freeze # Make the sole instance immutable
@@ -46,8 +46,8 @@ module Loxxy
46
46
 
47
47
  rule('funDecl' => 'FUN function')
48
48
 
49
- rule('varDecl' => 'VAR IDENTIFIER SEMICOLON')
50
- rule('varDecl' => 'VAR IDENTIFIER EQUAL expression SEMICOLON')
49
+ rule('varDecl' => 'VAR IDENTIFIER SEMICOLON').as 'var_declaration'
50
+ rule('varDecl' => 'VAR IDENTIFIER EQUAL expression SEMICOLON').as 'var_initialization'
51
51
 
52
52
  # Statements: produce side effects, but don't introduce bindings
53
53
  rule('statement' => 'exprStmt')
@@ -68,9 +68,9 @@ module Loxxy
68
68
  rule('forTest' => 'expression_opt SEMICOLON')
69
69
  rule('forUpdate' => 'expression_opt')
70
70
 
71
- rule('ifStmt' => 'IF ifCondition statement elsePart_opt')
72
- rule('ifCondition' => 'LEFT_PAREN expression RIGHT_PAREN')
73
- rule('elsePart_opt' => 'ELSE statement')
71
+ rule('ifStmt' => 'IF ifCondition statement elsePart_opt').as 'if_stmt'
72
+ rule('ifCondition' => 'LEFT_PAREN expression RIGHT_PAREN').as 'keep_symbol2'
73
+ rule('elsePart_opt' => 'ELSE statement').as 'keep_symbol2'
74
74
  rule('elsePart_opt' => [])
75
75
 
76
76
  rule('printStmt' => 'PRINT expression SEMICOLON').as 'print_stmt'
@@ -121,7 +121,7 @@ module Loxxy
121
121
  rule('multiplicative_plus' => 'multOp unary').as 'multiplicative_plus_end'
122
122
  rule('multOp' => 'SLASH')
123
123
  rule('multOp' => 'STAR')
124
- rule('unary' => 'unaryOp unary')
124
+ rule('unary' => 'unaryOp unary').as 'unary_expr'
125
125
  rule('unary' => 'call')
126
126
  rule('unaryOp' => 'BANG')
127
127
  rule('unaryOp' => 'MINUS')
@@ -138,7 +138,7 @@ module Loxxy
138
138
  rule('primary' => 'NUMBER').as 'literal_expr'
139
139
  rule('primary' => 'STRING').as 'literal_expr'
140
140
  rule('primary' => 'IDENTIFIER')
141
- rule('primary' => 'LEFT_PAREN expression RIGHT_PAREN')
141
+ rule('primary' => 'LEFT_PAREN expression RIGHT_PAREN').as 'grouping_expr'
142
142
  rule('primary' => 'SUPER DOT IDENTIFIER')
143
143
 
144
144
  # Utility rules