Class: Loxxy::BackEnd::Resolver

Inherits:
Object
  • Object
show all
Defined in:
lib/loxxy/back_end/resolver.rb

Overview

A class aimed to perform variable resolution when it visits the parse tree. Resolving means retrieve the declaration of a variable/function everywhere it is referenced.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeResolver

Returns a new instance of Resolver.



33
34
35
36
37
38
# File 'lib/loxxy/back_end/resolver.rb', line 33

def initialize
  @scopes = []
  @locals = {}
  @current_function = :none
  @current_class = :none
end

Instance Attribute Details

#current_classSymbol (readonly)

An indicator that tells we're in the middle of a class declaration

Returns:

  • (Symbol)

    must be one of: :none, :class



31
32
33
# File 'lib/loxxy/back_end/resolver.rb', line 31

def current_class
  @current_class
end

#current_functionSymbol (readonly)

An indicator that tells we're in the middle of a function declaration

Returns:

  • (Symbol)

    must be one of: :none, :function



27
28
29
# File 'lib/loxxy/back_end/resolver.rb', line 27

def current_function
  @current_function
end

#localsHash {LoxNode => Integer} (readonly)

A map from a LoxNode involving a variable and the number of enclosing scopes where it is declared.

Returns:

  • (Hash {LoxNode => Integer})


23
24
25
# File 'lib/loxxy/back_end/resolver.rb', line 23

def locals
  @locals
end

#scopesArray<Hash{String => Boolean}> (readonly)

A stack of Hashes of the form String => Boolean

Returns:

  • (Array<Hash{String => Boolean}>)


18
19
20
# File 'lib/loxxy/back_end/resolver.rb', line 18

def scopes
  @scopes
end

Instance Method Details

#after_assign_expr(anAssignExpr, aVisitor) ⇒ Object

Assignment expressions require their variables resolved



131
132
133
# File 'lib/loxxy/back_end/resolver.rb', line 131

def after_assign_expr(anAssignExpr, aVisitor)
  resolve_local(anAssignExpr, aVisitor)
end

#after_block_stmt(_aBlockStmt) ⇒ Object



57
58
59
# File 'lib/loxxy/back_end/resolver.rb', line 57

def after_block_stmt(_aBlockStmt)
  end_scope
end

#after_call_expr(aCallExpr, aVisitor) ⇒ Object



158
159
160
161
162
# File 'lib/loxxy/back_end/resolver.rb', line 158

def after_call_expr(aCallExpr, aVisitor)
  # Evaluate callee part
  aCallExpr.callee.accept(aVisitor)
  aCallExpr.arguments.reverse_each { |arg| arg.accept(aVisitor) }
end

#after_class_stmt(aClassStmt, aVisitor) ⇒ Object



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/loxxy/back_end/resolver.rb', line 66

def after_class_stmt(aClassStmt, aVisitor)
  previous_class = current_class
  @current_class = :class
  define(aClassStmt.name)
  if aClassStmt.superclass
    if aClassStmt.name == aClassStmt.superclass.name
      raise Loxxy::RuntimeError, "'A class can't inherit from itself."
    end

    @current_class = :subclass
    aClassStmt.superclass.accept(aVisitor)
    begin_scope
    define('super')
  end
  begin_scope
  define('this')
  aClassStmt.body.each do |fun_stmt|
    mth_type = fun_stmt.name == 'init' ? :initializer : :method
    resolve_function(fun_stmt, mth_type, aVisitor)
  end
  end_scope
  end_scope if aClassStmt.superclass
  @current_class = previous_class
end

#after_get_expr(aGetExpr, aVisitor) ⇒ Object



164
165
166
167
# File 'lib/loxxy/back_end/resolver.rb', line 164

def after_get_expr(aGetExpr, aVisitor)
  # Evaluate object part
  aGetExpr.object.accept(aVisitor)
end

#after_if_stmt(anIfStmt, aVisitor) ⇒ Object



91
92
93
94
# File 'lib/loxxy/back_end/resolver.rb', line 91

def after_if_stmt(anIfStmt, aVisitor)
  anIfStmt.then_stmt.accept(aVisitor)
  anIfStmt.else_stmt&.accept(aVisitor)
end

#after_logical_expr(aLogicalExpr, aVisitor) ⇒ Object



141
142
143
144
# File 'lib/loxxy/back_end/resolver.rb', line 141

def after_logical_expr(aLogicalExpr, aVisitor)
  # Force the visit of second operand (resolver should ignore shortcuts)
  aLogicalExpr.operands.last.accept(aVisitor)
end

#after_set_expr(aSetExpr, aVisitor) ⇒ Object



135
136
137
138
139
# File 'lib/loxxy/back_end/resolver.rb', line 135

def after_set_expr(aSetExpr, aVisitor)
  aSetExpr.value.accept(aVisitor)
  # Evaluate object part
  aSetExpr.object.accept(aVisitor)
end

#after_super_expr(aSuperExpr, aVisitor) ⇒ Object

rubocop: disable Style/CaseLikeIf rubocop: disable Style/StringConcatenation



183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/loxxy/back_end/resolver.rb', line 183

def after_super_expr(aSuperExpr, aVisitor)
  msg_prefix = "Error at 'super': Can't use 'super' "
  if current_class == :none
    err_msg = msg_prefix + 'outside of a class.'
    raise Loxxy::RuntimeError, err_msg

  elsif current_class == :class
    err_msg = msg_prefix + 'in a class without superclass.'
    raise Loxxy::RuntimeError, err_msg

  end
  # 'super' behaves closely to a local variable
  resolve_local(aSuperExpr, aVisitor)
end

#after_this_expr(aThisExpr, aVisitor) ⇒ Object



176
177
178
179
# File 'lib/loxxy/back_end/resolver.rb', line 176

def after_this_expr(aThisExpr, aVisitor)
  # 'this' behaves closely to a local variable
  resolve_local(aThisExpr, aVisitor)
end

#after_var_stmt(aVarStmt) ⇒ Object



126
127
128
# File 'lib/loxxy/back_end/resolver.rb', line 126

def after_var_stmt(aVarStmt)
  define(aVarStmt.name)
end

#after_variable_expr(aVarExpr, aVisitor) ⇒ Object



154
155
156
# File 'lib/loxxy/back_end/resolver.rb', line 154

def after_variable_expr(aVarExpr, aVisitor)
  resolve_local(aVarExpr, aVisitor)
end

#after_while_stmt(aWhileStmt, aVisitor) ⇒ Object



113
114
115
116
# File 'lib/loxxy/back_end/resolver.rb', line 113

def after_while_stmt(aWhileStmt, aVisitor)
  aWhileStmt.body.accept(aVisitor)
  aWhileStmt.condition&.accept(aVisitor)
end

#analyze(aVisitor) ⇒ Loxxy::Datatype::BuiltinDatatype

Given an abstract syntax parse tree visitor, launch the visit and execute the visit events in the output stream.

Parameters:

  • aVisitor (AST::ASTVisitor)

Returns:



44
45
46
47
48
49
50
# File 'lib/loxxy/back_end/resolver.rb', line 44

def analyze(aVisitor)
  begin_scope
  aVisitor.subscribe(self)
  aVisitor.start
  aVisitor.unsubscribe(self)
  end_scope
end

#before_block_stmt(_aBlockStmt) ⇒ Object

block statement introduces a new scope



53
54
55
# File 'lib/loxxy/back_end/resolver.rb', line 53

def before_block_stmt(_aBlockStmt)
  begin_scope
end

#before_class_stmt(aClassStmt) ⇒ Object

A class declaration adds a new variable to current scope



62
63
64
# File 'lib/loxxy/back_end/resolver.rb', line 62

def before_class_stmt(aClassStmt)
  declare(aClassStmt.name)
end

#before_fun_stmt(aFunStmt, aVisitor) ⇒ Object

function declaration creates a new scope for its body & binds its parameters for that scope



201
202
203
204
205
# File 'lib/loxxy/back_end/resolver.rb', line 201

def before_fun_stmt(aFunStmt, aVisitor)
  declare(aFunStmt.name)
  define(aFunStmt.name)
  resolve_function(aFunStmt, :function, aVisitor)
end

#before_return_stmt(returnStmt) ⇒ Object



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/loxxy/back_end/resolver.rb', line 96

def before_return_stmt(returnStmt)
  if scopes.size < 2
    msg = "Error at 'return': Can't return from top-level code."
    raise Loxxy::RuntimeError, msg
  end

  if current_function == :none
    msg = "Error at 'return': Can't return from outside a function."
    raise Loxxy::RuntimeError, msg
  end

  if current_function == :initializer
    msg = "Error at 'return': Can't return a value from an initializer."
    raise Loxxy::RuntimeError, msg unless returnStmt.subnodes[0].kind_of?(Datatype::Nil)
  end
end

#before_this_expr(_thisExpr) ⇒ Object



169
170
171
172
173
174
# File 'lib/loxxy/back_end/resolver.rb', line 169

def before_this_expr(_thisExpr)
  if current_class == :none
    msg = "Error at 'this': Can't use 'this' outside of a class."
    raise Loxxy::RuntimeError, msg
  end
end

#before_var_stmt(aVarStmt) ⇒ Object

A variable declaration adds a new variable to current scope



119
120
121
122
123
124
# File 'lib/loxxy/back_end/resolver.rb', line 119

def before_var_stmt(aVarStmt)
  # Oddly enough, Lox allows the re-definition of a variable at top-level scope
  return if scopes.size == 1 && scopes.last[aVarStmt.name]

  declare(aVarStmt.name)
end

#before_variable_expr(aVarExpr) ⇒ Object

Variable expressions require their variables resolved



147
148
149
150
151
152
# File 'lib/loxxy/back_end/resolver.rb', line 147

def before_variable_expr(aVarExpr)
  var_name = aVarExpr.name
  if !scopes.empty? && (scopes.last[var_name] == false)
    raise Loxxy::RuntimeError, "Can't read variable #{var_name} in its own initializer"
  end
end