Class: Baba::Interpreter

Inherits:
Object
  • Object
show all
Defined in:
lib/baba/interpreter.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(environment = Environment.new, loaded_files = []) ⇒ Interpreter

Returns a new instance of Interpreter.



31
32
33
34
35
36
37
38
# File 'lib/baba/interpreter.rb', line 31

def initialize(environment = Environment.new, loaded_files = [])
  @globals = environment
  @environment = @globals
  @locals = {}
  @loaded_files = loaded_files

  @globals.define("RubyObject", RubyClass.new)
end

Instance Attribute Details

#globalsObject (readonly)

Returns the value of attribute globals.



29
30
31
# File 'lib/baba/interpreter.rb', line 29

def globals
  @globals
end

Instance Method Details

#equal?(a, b) ⇒ Boolean

Returns:

  • (Boolean)


345
346
347
# File 'lib/baba/interpreter.rb', line 345

def equal?(a, b)
  a == b
end

#evaluate(expr) ⇒ Object



52
53
54
# File 'lib/baba/interpreter.rb', line 52

def evaluate(expr)
  expr.accept(self)
end

#execute(stmt) ⇒ Object



56
57
58
59
60
61
62
# File 'lib/baba/interpreter.rb', line 56

def execute(stmt)
  begin
    stmt.accept(self)
  rescue SystemStackError
    raise BabaRuntimeError.new(nil, "Stack level too deep (wild recursive method?)")
  end
end

#execute_block(statements, environment) ⇒ Object



64
65
66
67
68
69
70
71
72
73
# File 'lib/baba/interpreter.rb', line 64

def execute_block(statements, environment)
  previous = @environment
  begin
    @environment = environment

    statements.each { |s| execute(s) }
  ensure
    @environment = previous
  end
end

#interpret(statements) ⇒ Object



40
41
42
43
44
45
46
# File 'lib/baba/interpreter.rb', line 40

def interpret(statements)
  begin
    statements.each { |s| execute(s) }
  rescue BabaRuntimeError => error
    Baba.runtime_error(error)
  end
end

#look_up_variable(name, expr) ⇒ Object



332
333
334
335
336
337
338
339
# File 'lib/baba/interpreter.rb', line 332

def look_up_variable(name, expr)
  distance = @locals[expr]
  unless distance.nil?
    return @environment.get_at(distance, name.lexeme)
  else
    return @globals[name]
  end
end

#resolve(expr, depth) ⇒ Object



48
49
50
# File 'lib/baba/interpreter.rb', line 48

def resolve(expr, depth)
  @locals[expr] = depth
end

#truthy?(object) ⇒ Boolean

Returns:

  • (Boolean)


341
342
343
# File 'lib/baba/interpreter.rb', line 341

def truthy?(object)
  !!object
end

#visit_assign_expr(expr) ⇒ Object



202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/baba/interpreter.rb', line 202

def visit_assign_expr(expr)
  value = evaluate(expr.value)

  distance = @locals[expr]
  unless distance.nil?
    @environment.assign_at(distance, expr.name, value)
  else
    @globals[expr_name] = value
  end

  value
end

#visit_binary_expr(expr) ⇒ Object



215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# File 'lib/baba/interpreter.rb', line 215

def visit_binary_expr(expr)
  left = evaluate(expr.left)
  right = evaluate(expr.right)

  case expr.operator.type
  when MINUS
    return left - right
  when SLASH
    return left / right
  when STAR
    return left * right
  when PLUS
    return left + right
  when GREATER
    return left > right
  when GREATER_EQUAL
    return left >= right
  when LESS
    return left < right
  when LESS_EQUAL
    return left <= right
  when NOT_EQUAL
    return !equal?(left, right)
  when EQUAL_EQUAL
    return equal?(left, right)
  end

  return nil
end

#visit_block_stmt(stmt) ⇒ Object



75
76
77
# File 'lib/baba/interpreter.rb', line 75

def visit_block_stmt(stmt)
  execute_block(stmt.statements, Environment.new(@environment))
end

#visit_break_expr(expr) ⇒ Object

Raises:



79
80
81
# File 'lib/baba/interpreter.rb', line 79

def visit_break_expr(expr)
  raise Break.new
end

#visit_call_expr(expr) ⇒ Object



245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/baba/interpreter.rb', line 245

def visit_call_expr(expr)
  callee = evaluate(expr.callee)

  arguments = expr.arguments.map { |a| evaluate(a) }

  unless callee.kind_of?(Callable)
    raise BabaRuntimeError.new(expr.paren, "Can only call callable objects.")
  end
  if !callee.is_a?(RubyFunction) && (arguments.size != callee.arity) # Don't check arity on ruby functions
    raise BabaRuntimeError.new(expr.paren, "Expected #{callee.arity} arguments, got #{arguments.size}.")
  end
  callee.call(self, arguments)
end

#visit_class_stmt(stmt) ⇒ Object



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/baba/interpreter.rb', line 83

def visit_class_stmt(stmt)
  superclass = nil
  unless stmt.superclass.nil?
    superclass = evaluate(stmt.superclass)
    unless superclass.is_a?(BabaClass)
      raise BabaRuntimeError.new(stmt.superclass.name, "Super thing must be a class.")
    end
  end

  @environment.define(stmt.name.lexeme, nil)

  unless stmt.superclass.nil?
    @environment = Environment.new(@environment)
    @environment.define("super", superclass)
  end

  methods = {}
  stmt.methods.each do |method|
    function = Function.new(method, @environment)
    methods[method.name.lexeme] = function
  end

  klass = BabaClass.new(stmt.name.lexeme, superclass, methods)

  unless superclass.nil?
    @environment = @environment.enclosing
  end

  @environment[stmt.name] = klass
end

#visit_expression_stmt(stmt) ⇒ Object



114
115
116
# File 'lib/baba/interpreter.rb', line 114

def visit_expression_stmt(stmt)
  evaluate(stmt.expression)
end

#visit_function_stmt(stmt) ⇒ Object



118
119
120
121
# File 'lib/baba/interpreter.rb', line 118

def visit_function_stmt(stmt)
  function = Function.new(stmt, @environment)
  @environment.define(stmt.name.lexeme, function)
end

#visit_get_expr(expr) ⇒ Object

Raises:



259
260
261
262
263
264
265
266
# File 'lib/baba/interpreter.rb', line 259

def visit_get_expr(expr)
  object = evaluate(expr.object)
  if object.is_a?(Instance)
    return object[expr.name]
  end

  raise BabaRuntimeError.new(expr.name, "Only instances have properties.")
end

#visit_grouping_expr(expr) ⇒ Object



349
350
351
# File 'lib/baba/interpreter.rb', line 349

def visit_grouping_expr(expr)
  evaluate(expr.expression)
end

#visit_if_stmt(stmt) ⇒ Object



123
124
125
126
127
128
129
130
# File 'lib/baba/interpreter.rb', line 123

def visit_if_stmt(stmt)
  if truthy?(evaluate(stmt.condition))
    execute(stmt.then_branch)
  elsif !stmt.else_branch.nil?
    execute(stmt.else_branch)
  end
  nil
end

#visit_include_stmt(stmt) ⇒ Object



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/baba/interpreter.rb', line 132

def visit_include_stmt(stmt)
  string = evaluate(stmt.expression)

  string += ".baba" unless File.exist?(string)
  string = __FILE__ + "../../" + string unless File.exist?(string) # Standard library
  unless File.exist?(string)
    raise BabaRuntimeError.new(stmt.expression, "Unable to find #{stmt.expression}.")
  end

  return if @loaded_files.include?(string) # Don't load the same file multiple times! We could get weird stack level wackiness
  @loaded_files << string

  contents = File.read(string)

  scanner = Scanner.new(contents)
  tokens = scanner.scan_tokens
  parser = Parser.new(tokens)

  statements = parser.parse

  if Baba.had_error
    @loaded_files.pop
    return
  end

  resolver = Resolver.new(self)
  resolver.resolve(statements)

  if Baba.had_error
    @loaded_files.pop
    return
  end

  interpret(statements)
end

#visit_literal_expr(expr) ⇒ Object



268
269
270
# File 'lib/baba/interpreter.rb', line 268

def visit_literal_expr(expr)
  expr.value
end

#visit_logical_expr(expr) ⇒ Object



272
273
274
275
276
277
278
279
280
281
282
# File 'lib/baba/interpreter.rb', line 272

def visit_logical_expr(expr)
  left = evaluate(expr.left)

  if expr.operator.type == OR
    return left if truthy?(left)
  else
    return left if !truthy?(left)
  end

  return evaluate(expr.right)
end

#visit_rbeval_stmt(stmt) ⇒ Object



168
169
170
171
# File 'lib/baba/interpreter.rb', line 168

def visit_rbeval_stmt(stmt)
  value = evaluate(stmt.expression)
  eval(value)
end

#visit_return_stmt(stmt) ⇒ Object

Raises:



173
174
175
176
177
178
179
# File 'lib/baba/interpreter.rb', line 173

def visit_return_stmt(stmt)
  value = unless stmt.value.nil?
      evaluate(stmt.value)
    end

  raise Return.new(value)
end

#visit_self_expr(expr) ⇒ Object



284
285
286
# File 'lib/baba/interpreter.rb', line 284

def visit_self_expr(expr)
  look_up_variable(expr.keyword, expr)
end

#visit_set_expr(expr) ⇒ Object



288
289
290
291
292
293
294
295
296
297
298
# File 'lib/baba/interpreter.rb', line 288

def visit_set_expr(expr)
  object = evaluate(expr.object)

  unless object.is_a?(Instance)
    raise BabaRuntimeError.new(expr.name, "Only instances have fields.")
  end

  value = evaluate(expr.value)
  object[expr.name] = value
  return value
end

#visit_super_expr(expr) ⇒ Object



300
301
302
303
304
305
306
307
308
309
310
311
312
313
# File 'lib/baba/interpreter.rb', line 300

def visit_super_expr(expr)
  distance = @locals[expr]
  superclass = @environment.get_at(distance, "super")

  object = @environment.get_at(distance - 1, "self")

  method = superclass.find_method(expr.method.lexeme)

  if method.nil?
    raise BabaRuntimeError.new(expr.method, "Undefined property #{expr.method.lexeme}.")
  end

  method.bind(object)
end

#visit_unary_expr(expr) ⇒ Object



315
316
317
318
319
320
321
322
323
324
325
326
# File 'lib/baba/interpreter.rb', line 315

def visit_unary_expr(expr)
  right = evaluate(expr.right)

  case expr.operator.type
  when MINUS
    return -(right.to_f)
  when NOT
    return !truthy?(right)
  end

  return nil
end

#visit_var_stmt(stmt) ⇒ Object



181
182
183
184
185
186
187
188
189
# File 'lib/baba/interpreter.rb', line 181

def visit_var_stmt(stmt)
  value = nil
  if (stmt.initializer != nil)
    value = evaluate(stmt.initializer)
  end

  @environment.define(stmt.name.lexeme, value)
  value
end

#visit_variable_expr(expr) ⇒ Object



328
329
330
# File 'lib/baba/interpreter.rb', line 328

def visit_variable_expr(expr)
  look_up_variable(expr.name, expr)
end

#visit_while_stmt(stmt) ⇒ Object



191
192
193
194
195
196
197
198
199
200
# File 'lib/baba/interpreter.rb', line 191

def visit_while_stmt(stmt)
  while truthy?(evaluate(stmt.condition))
    begin
      execute(stmt.body)
    rescue Break
      break
    end
  end
  nil
end