Class: Verneuil::Compiler::Visitor

Inherits:
Object
  • Object
show all
Defined in:
lib/verneuil/compiler.rb

Overview

Compiler visitor visits sexps and transforms them into executable code by calling back on its code generator.

Constant Summary collapse

NOPOP =
[:return, :defn, :class]

Instance Method Summary collapse

Constructor Details

#initialize(generator, compiler) ⇒ Visitor

Returns a new instance of Visitor.



42
43
44
45
46
47
# File 'lib/verneuil/compiler.rb', line 42

def initialize(generator, compiler)
  @generator = generator
  @compiler = compiler
  
  @class_context = []
end

Instance Method Details

#accept_arglist(*args) ⇒ Object

s(:arglist, ARGUMENT, ARGUMENT) - Argument lists. Needs to return the actual number of arguments that we’ve compiled.



104
105
106
107
108
109
# File 'lib/verneuil/compiler.rb', line 104

def accept_arglist(*args)
  args.each do |arg|
    visit(arg)
  end
  return args.size
end

#accept_args(*arg_names) ⇒ Object

s(:args, ARGUMENT_NAMES)



268
269
270
271
272
273
274
275
276
277
278
# File 'lib/verneuil/compiler.rb', line 268

def accept_args(*arg_names)
  arg_names.each do |name|
    if name.to_s.start_with?('&')
      stripped_name = name.to_s[1..-1].to_sym
      @generator.load_block
      @generator.lvar_set stripped_name
    else
      @generator.lvar_set name
    end
  end
end

#accept_array(*elements) ⇒ Object

s(:array, ELEMENTS) - array literal.



134
135
136
137
138
# File 'lib/verneuil/compiler.rb', line 134

def accept_array(*elements)
  elements.each { |el| visit(el) }
  @generator.load Array
  @generator.ruby_call :"[]", elements.size
end

#accept_block(*statements) ⇒ Object

s(:block, STATEMENT, STATEMENT, …) - Blocks of code.



113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/verneuil/compiler.rb', line 113

def accept_block(*statements)
  statements.each_with_index do |statement, idx|
    type, *rest = statement
    
    visit(statement)
    
    unless idx+1 == statements.size ||
      NOPOP.include?(type)
      @generator.pop 1 
    end
  end
end

#accept_call(receiver, method_name, args) ⇒ Object

s(:call, RECEIVER, NAME, ARGUMENTS) - Method calls with or without receiver.



90
91
92
93
94
95
96
97
98
99
# File 'lib/verneuil/compiler.rb', line 90

def accept_call(receiver, method_name, args)
  argc = visit(args)
  
  if receiver
    visit(receiver)
    @generator.ruby_call method_name, argc
  else
    @generator.ruby_call_implicit method_name, argc
  end
end

#accept_class(name, _, scope) ⇒ Object

s(:class, :Fixnum, SUPER, s(:scope, s(:defn, :foo, s(:args), DEF))) - a method definition for a class.

NOTE that verneuils classes don’t work like Rubies classes at all. This is more or less just a method for masking methods on Ruby classes, not a way to define classes.



258
259
260
261
262
263
264
# File 'lib/verneuil/compiler.rb', line 258

def accept_class(name, _, scope)
  @class_context.push name
  
  visit(scope)
ensure
  @class_context.pop
end

#accept_colon2(left, right) ⇒ Object

s(:colon2, s(:const, :Verneuil), :Process) - access something inside a namespace. Constants are resolved at compile time!



75
76
77
78
79
# File 'lib/verneuil/compiler.rb', line 75

def accept_colon2(left, right)
  left_const = visit(left)
  const = left_const.const_get(right)
  @generator.load const
end

#accept_const(const) ⇒ Object

s(:const, :Verneuil) - Resolve a constant globally and return it.



83
84
85
# File 'lib/verneuil/compiler.rb', line 83

def accept_const(const)
  eval(const.to_s)
end

#accept_defined(exp) ⇒ Object

s(:defined, s(:lvar, :a)) - test if a local variable is defined.



192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/verneuil/compiler.rb', line 192

def accept_defined(exp)
  type, * = exp
  case type
    when :call
      # s(:call, nil, :a, s(:arglist))
      @generator.test_defined exp[2]
    when :lvar
      # s(:lvar, :a)
      @generator.test_defined exp.last
    when :lit
      @generator.load 'expression'
  else
    fail "Don't know how to implement defined? with #{variable.inspect}."
  end
end

#accept_defn(name, args, body) ⇒ Object

s(:defn, NAME, ARG_NAMES, BODY) - a method definition.



231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'lib/verneuil/compiler.rb', line 231

def accept_defn(name, args, body)
  # Jumping over functions so that definitions don't get executed.
  adr_end = @generator.fwd_adr
  @generator.jump adr_end
  
  method = Verneuil::Method.new(
    @class_context.last, 
    name, 
    @generator.current_adr)
  @generator.program.symbols.add(method)

  # Enters a new local scope and defines arguments
  visit(args)

  visit(body)
  @generator.return
  
  adr_end.resolve
end

#accept_falseObject

false.



331
332
333
# File 'lib/verneuil/compiler.rb', line 331

def accept_false
  @generator.load false
end

#accept_if(cond, _then, _else) ⇒ Object

s(:if, COND, THEN, ELSE) - an if statement



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/verneuil/compiler.rb', line 142

def accept_if(cond, _then, _else)
  adr_else = @generator.fwd_adr
  adr_end  = @generator.fwd_adr
  
  visit(cond)
  @generator.jump_if_false adr_else
  
  visit(_then)
  @generator.jump adr_end
  
  adr_else.resolve
  if _else
    visit(_else)
  else
    @generator.load nil
  end
  adr_end.resolve
end

#accept_iter(call, assigns, block = nil) ⇒ Object

s(:iter, s(:call, RECEIVER, METHOD, ARGUMENTS), ASSIGNS, BLOCK) - call a method with a block.



298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
# File 'lib/verneuil/compiler.rb', line 298

def accept_iter(call, assigns, block=nil)
  # Jump over the block code
  adr_end_of_block = @generator.fwd_adr
  @generator.jump adr_end_of_block
  
  adr_start_of_block = @generator.current_adr
  
  if assigns
    type, *names = assigns
    fail "BUG: Unsupported type of block arguments: #{type}" \
      unless type == :lasgn
    accept_args(*names)
  end
  visit(block) if block
  @generator.return 

  adr_end_of_block.resolve
  
  # Compile the call as we would normally, adding a push_block/pop_block
  # around it. 
  @generator.push_block adr_start_of_block
  visit(call)
  @generator.pop_block
end

#accept_lasgn(*args) ⇒ Object

s(:lasgn, VARIABLE, VALUE) - assignment of local variables. s(:lasgn, VARIABLE) - implicit assignment of local vars.



211
212
213
214
215
216
217
218
219
220
# File 'lib/verneuil/compiler.rb', line 211

def accept_lasgn(*args)
  if args.size == 2
    val = args.last
    visit(val)
  end

  name = args.first
  @generator.dup 0
  @generator.lvar_set name
end

#accept_lit(value) ⇒ Object

s(:lit, LITERAL) - A literal value.



128
129
130
# File 'lib/verneuil/compiler.rb', line 128

def accept_lit(value)
  @generator.load value
end

#accept_lvar(name) ⇒ Object

s(:lvar, VARIABLE) - local variable access.



224
225
226
227
# File 'lib/verneuil/compiler.rb', line 224

def accept_lvar(name)
  visit(
    s(:call, nil, name, s(:arglist)))
end

#accept_nilObject

nil



343
344
345
# File 'lib/verneuil/compiler.rb', line 343

def accept_nil
  @generator.load nil
end

#accept_op_asgn_or(variable, assign) ⇒ Object

s(:op_asgn_or, s(:lvar, :a), s(:lasgn, :a, s(:lit, 1))) - var ||= value.



180
181
182
183
184
185
186
187
188
# File 'lib/verneuil/compiler.rb', line 180

def accept_op_asgn_or(variable, assign)
  (*), var, val = assign
  visit(
    s(:lasgn, var, 
      s(:if, 
        s(:defined, variable), 
        variable, 
        val)))
end

#accept_return(val) ⇒ Object

s(:return, RETVAL) - return from the current method.



290
291
292
293
# File 'lib/verneuil/compiler.rb', line 290

def accept_return(val)
  visit(val)
  @generator.return 
end

#accept_scope(body) ⇒ Object

s(:scope, BODY) - a new scope.



282
283
284
285
286
# File 'lib/verneuil/compiler.rb', line 282

def accept_scope(body)
  # For now, we don't know what we'll eventually do with scopes. So here
  # is this very basic idea...
  visit(body)
end

#accept_selfObject

self



337
338
339
# File 'lib/verneuil/compiler.rb', line 337

def accept_self
  @generator.load_self
end

#accept_trueObject

true.



325
326
327
# File 'lib/verneuil/compiler.rb', line 325

def accept_true
  @generator.load true
end

#accept_while(cond, body, t) ⇒ Object

s(:while, test, body, true) - a while statement.



163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/verneuil/compiler.rb', line 163

def accept_while(cond, body, t)
  fail "What is t for?" unless t

  adr_end = @generator.fwd_adr

  adr_test = @generator.current_adr
  visit(cond)
  @generator.jump_if_false adr_end
  
  visit(body)
  @generator.jump adr_test
  
  adr_end.resolve
end

#visit(sexp) ⇒ Object

Raises:

  • (ArgumentError)


49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/verneuil/compiler.rb', line 49

def visit(sexp)
  type, *args = sexp
  
  sym = "accept_#{type}".to_sym
  
  raise ArgumentError, "No sexp given?" unless sexp && type
  
  raise NotImplementedError, "No acceptor for #{sexp}." \
    unless respond_to? sym

  begin
    self.send(sym, *args)
  rescue ArgumentError => ex
    if md=ex.message.match(/wrong number of arguments \(([^)]+)\)/)
      sexp_skeleton = s(sexp[0], *sexp[1..-1].map { |e| e && s(e.first, '...') })
      raise ArgumentError, "wrong number of elements in #{sexp_skeleton} (#{md[1]})."
    end
    raise
  end
end