Class: Interpreter

Inherits:
Visitor show all
Defined in:
lib/loxby/interpreter.rb,
lib/loxby/helpers/native_functions.rb

Overview

rubocop:disable Style/Documentation

Defined Under Namespace

Classes: NativeFunction

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from Visitor

define_types

Constructor Details

#initialize(process) ⇒ Interpreter

rubocop:disable Lint/MissingSuper



15
16
17
18
19
20
21
22
23
# File 'lib/loxby/interpreter.rb', line 15

def initialize(process) # rubocop:disable Lint/MissingSuper
  @process = process
  # `@globals` always refers to the same environment regardless of scope.
  @globals = Lox::Environment.new
  # `@environment` changes based on scope.
  @environment = @globals

  define_native_functions
end

Instance Attribute Details

#globalsObject (readonly)

Returns the value of attribute globals.



13
14
15
# File 'lib/loxby/interpreter.rb', line 13

def globals
  @globals
end

Instance Method Details

#define_native_functionsObject



43
44
45
46
47
# File 'lib/loxby/helpers/native_functions.rb', line 43

def define_native_functions
  Lox.config.native_functions.values.each do |name, func|
    @globals.set name.to_s, NativeFunction.new(func.arity, &func.block)
  end
end

#ensure_number(operator, *objs) ⇒ Object

Raises:



44
45
46
# File 'lib/loxby/interpreter.rb', line 44

def ensure_number(operator, *objs)
  raise Lox::RunError.new(operator, 'Operand must be a number.') unless objs.all? { _1.is_a?(Float) }
end

#execute_block(statements, environment) ⇒ Object



120
121
122
123
124
125
126
127
128
129
# File 'lib/loxby/interpreter.rb', line 120

def execute_block(statements, environment)
  previous = @environment
  @environment = environment
  statements.each { lox_eval _1 }
  nil
rescue Lox::RunError
  nil
ensure
  @environment = previous
end

#interpret(statements) ⇒ Object



25
26
27
28
29
30
31
32
# File 'lib/loxby/interpreter.rb', line 25

def interpret(statements)
  result = nil
  statements.each { result = lox_eval(_1) }
  result
rescue Lox::RunError => e
  @process.runtime_error e
  nil
end

#lox_eval(expr) ⇒ Object



34
35
36
# File 'lib/loxby/interpreter.rb', line 34

def lox_eval(expr)
  expr.accept self
end

#lox_obj_to_str(obj) ⇒ Object



48
49
50
51
52
53
54
55
56
57
# File 'lib/loxby/interpreter.rb', line 48

def lox_obj_to_str(obj)
  case obj
  when nil
    'nil'
  when Float
    obj.to_s[-2..] == '.0' ? obj.to_s[0...-2] : obj.to_s
  else
    obj.to_s
  end
end

#truthy?(obj) ⇒ Boolean

Lox’s definition of truthiness follows Ruby’s (for now), so this is a no-op (for now)

Returns:

  • (Boolean)


40
41
42
# File 'lib/loxby/interpreter.rb', line 40

def truthy?(obj)
  obj
end

#visit_assign_expression(expr) ⇒ Object



104
105
106
107
108
# File 'lib/loxby/interpreter.rb', line 104

def visit_assign_expression(expr)
  value = lox_eval expr.value
  @environment.assign expr.name, value
  value
end

#visit_binary_expression(expr) ⇒ Object

rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/AbcSize



164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/loxby/interpreter.rb', line 164

def visit_binary_expression(expr) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/AbcSize
  left = lox_eval expr.left
  right = lox_eval expr.right
  case expr.operator.type
  when :minus
    ensure_number(expr.operator, left, right)
    left.to_f - right.to_f
  when :slash
    raise Lox::DividedByZeroError.new(expr.operator, 'Cannot divide by zero.') if right == 0.0

    ensure_number(expr.operator, left, right)
    left.to_f / right
  when :star
    ensure_number(expr.operator, left, right)
    left.to_f * right.to_f
  when :plus
    unless (left.is_a?(Float) || left.is_a?(String)) && left.instance_of?(right.class)
      raise Lox::RunError.new(expr.operator, 'Operands must be two numbers or two strings.')
    end

    left + right
  when :greater
    ensure_number(expr.operator, left, right)
    left.to_f > right.to_f
  when :greater_equal
    ensure_number(expr.operator, left, right)
    left.to_f >= right.to_f
  when :less
    ensure_number(expr.operator, left, right)
    left.to_f < right.to_f
  when :less_equal
    ensure_number(expr.operator, left, right)
    left.to_f <= right.to_f
  when :bang_equal
    left != right
  when :equal_equal
    left == right
  when :comma
    right
  end
end

#visit_block_statement(statement) ⇒ Object



110
111
112
113
114
# File 'lib/loxby/interpreter.rb', line 110

def visit_block_statement(statement)
  # Pull out a copy of the environment
  # so that blocks are closures
  execute_block(statement.statements, Lox::Environment.new(@environment))
end

#visit_break_statement(_) ⇒ Object



116
117
118
# File 'lib/loxby/interpreter.rb', line 116

def visit_break_statement(_)
  throw :break
end

#visit_call_expression(expr) ⇒ Object

rubocop:disable Metrics/AbcSize



212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/loxby/interpreter.rb', line 212

def visit_call_expression(expr) # rubocop:disable Metrics/AbcSize
  callee = lox_eval expr.callee
  arguments = expr.arguments.map { lox_eval _1 }

  unless callee.class.include? Lox::Callable
    raise Lox::RunError.new(expr.paren, 'Can only call functions and classes.')
  end

  unless arguments.size == callee.arity
    raise Lox::RunError.new(expr.paren, "Expected #{callee.arity} arguments but got #{arguments.size}.")
  end

  callee.call(self, arguments)
end

#visit_expression_statement(statement) ⇒ Object



59
60
61
# File 'lib/loxby/interpreter.rb', line 59

def visit_expression_statement(statement)
  lox_eval statement.expression
end

#visit_function_statement(statement) ⇒ Object



63
64
65
66
67
# File 'lib/loxby/interpreter.rb', line 63

def visit_function_statement(statement)
  function = Lox::Function.new(statement, @environment)
  @environment[statement.name] = function if statement.name
  function
end

#visit_grouping_expression(expr) ⇒ Object



148
149
150
# File 'lib/loxby/interpreter.rb', line 148

def visit_grouping_expression(expr)
  lox_eval expr.expression
end

#visit_if_statement(statement) ⇒ Object



69
70
71
72
73
74
75
# File 'lib/loxby/interpreter.rb', line 69

def visit_if_statement(statement)
  if truthy? lox_eval(statement.condition)
    lox_eval statement.then_branch
  elsif !statement.else_branch.nil?
    lox_eval statement.else_branch
  end
end

#visit_literal_expression(expr) ⇒ Object

Leaves of the AST. The scanner picks out these values for us beforehand.



133
134
135
# File 'lib/loxby/interpreter.rb', line 133

def visit_literal_expression(expr)
  expr.value
end

#visit_logical_expression(expr) ⇒ Object



137
138
139
140
141
142
143
144
145
146
# File 'lib/loxby/interpreter.rb', line 137

def visit_logical_expression(expr)
  left = lox_eval expr.left

  case expr.operator.type
  when :or
    left if truthy? left
  else # Just and, for now
    truthy?(left) ? lox_eval(expr.right) : left
  end
end

#visit_print_statement(statement) ⇒ Object



77
78
79
80
# File 'lib/loxby/interpreter.rb', line 77

def visit_print_statement(statement)
  value = lox_eval statement.expression
  puts lox_obj_to_str(value)
end

#visit_return_statement(statement) ⇒ Object



82
83
84
85
# File 'lib/loxby/interpreter.rb', line 82

def visit_return_statement(statement)
  value = statement.value.nil? ? nil : lox_eval(statement.value)
  throw :return, value # This is not an error, just sending a message up the callstack
end

#visit_ternary_expression(expr) ⇒ Object



206
207
208
209
210
# File 'lib/loxby/interpreter.rb', line 206

def visit_ternary_expression(expr)
  left = lox_eval expr.left

  left ? lox_eval(expr.center) : lox_eval(expr.right)
end

#visit_unary_expression(expr) ⇒ Object



152
153
154
155
156
157
158
159
160
161
162
# File 'lib/loxby/interpreter.rb', line 152

def visit_unary_expression(expr)
  right = lox_eval(expr.right)

  case expr.operator.type
  when :minus
    ensure_number(expr.operator, right)
    -right.to_f
  when :bang
    truthy? right
  end
end

#visit_var_statement(statement) ⇒ Object



87
88
89
90
# File 'lib/loxby/interpreter.rb', line 87

def visit_var_statement(statement)
  value = statement.initializer ? lox_eval(statement.initializer) : nil
  @environment[statement.name] = value
end

#visit_variable_expression(expr) ⇒ Object



100
101
102
# File 'lib/loxby/interpreter.rb', line 100

def visit_variable_expression(expr)
  @environment[expr.name]
end

#visit_while_statement(statement) ⇒ Object



92
93
94
95
96
97
98
# File 'lib/loxby/interpreter.rb', line 92

def visit_while_statement(statement)
  catch :break do # Jump beacon for break statements
    value = nil
    (value = lox_eval statement.body) while truthy?(lox_eval(statement.condition))
    value
  end
end