Skip to content

Commit 6ceb215

Browse files
committed
Turn '::' into internal operator :deref, and add machinery to pre-build scope chain and traverse it to resolve class constant names at compile time where possible
1 parent 3a7e275 commit 6ceb215

File tree

5 files changed

+156
-47
lines changed

5 files changed

+156
-47
lines changed

classcope.rb

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class ClassScope < Scope
1010
# method v-table,
1111
# instance variables
1212
# and class variables
13-
attr_reader :name, :vtable, :instance_vars, :class_vars
13+
attr_reader :vtable, :instance_vars, :class_vars
1414

1515
# This is the number of instance variables allowed for the class
1616
# Class, and is used for bootstrapping. Note that it could be
@@ -32,6 +32,30 @@ def initialize(next_scope, name, offsets, superclass)
3232
@ivaroff = @superclass ? @superclass.instance_size : 0
3333
@instance_vars = !@superclass ? [:@__class__] : [] # FIXME: Do this properly
3434
@class_vars = {}
35+
36+
@constants = {}
37+
end
38+
39+
def find_constant(c)
40+
const = @constants[c]
41+
return const if const
42+
return @next.find_constant(@name+"__"+c.to_s) if @next
43+
return nil
44+
end
45+
46+
def prefix
47+
return "" if !@next
48+
n = @next.name
49+
return "" if n.empty?
50+
return n + "__"
51+
end
52+
53+
def local_name
54+
@name
55+
end
56+
57+
def name
58+
prefix + @name.to_s
3559
end
3660

3761
def class_scope
@@ -46,6 +70,10 @@ def add_ivar(a)
4670
@instance_vars << a.to_sym if !@instance_vars.include?(a.to_sym)
4771
end
4872

73+
def add_constant(c, v = true)
74+
@constants[c] = v
75+
end
76+
4977
def instance_size
5078
@instance_vars.size + @ivaroff
5179
end
@@ -58,7 +86,15 @@ def instance_size
5886
def get_arg(a)
5987
# Handle self
6088
if a.to_sym == :self
61-
return [:global,@name]
89+
return [:global,name]
90+
end
91+
92+
if (?A..?Z).member?(a.to_s[0])
93+
if @constants.member?(a.to_sym)
94+
return [:global,name + "__" + a.to_s]
95+
else
96+
return @next.get_arg(a)
97+
end
6298
end
6399

64100
# class variables.

compiler.rb

Lines changed: 27 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class Compiler
3636
:hash, :return,:sexp, :module, :rescue, :incr, :block,
3737
:required, :add, :sub, :mul, :div, :eq, :ne,
3838
:lt, :le, :gt, :ge,:saveregs, :and, :or,
39-
:preturn, :proc, :stackframe
39+
:preturn, :proc, :stackframe, :deref
4040
]
4141

4242
Keywords = @@keywords
@@ -51,6 +51,7 @@ def initialize emitter = Emitter.new
5151
@global_constants << :false
5252
@global_constants << :true
5353
@global_constants << :nil
54+
@global_constants << :__left
5455
@classes = {}
5556
@vtableoffsets = VTableOffsets.new
5657
@trace = false
@@ -245,6 +246,18 @@ def clean_method_name(name)
245246
return cleaned
246247
end
247248

249+
# Handle e.g. Tokens::Atom, which is parsed as (deref Tokens Atom)
250+
#
251+
# For now we are assuming statically resolvable chains, and not
252+
# tested multi-level dereference (e.g. Foo::Bar::Baz)
253+
#
254+
def compile_deref(scope, left, right)
255+
cscope = scope.find_constant(left)
256+
raise "Unable to resolve: #{left}::#{right} statically (FIXME)" if !cscope || !cscope.is_a?(ClassScope)
257+
get_arg(cscope,right)
258+
end
259+
260+
248261
# Compiles a function definition.
249262
# Takes the current scope, in which the function is defined,
250263
# the name of the function, its arguments as well as the body-expression that holds
@@ -358,6 +371,7 @@ def compile_and scope, left, right
358371

359372
def compile_or scope, left, right
360373
@e.comment("compile_or: #{left.inspect} || #{right.inspect}")
374+
# FIXME: Eek. need to make sure variables are assigned for these. Turn it into a rewrite?
361375
compile_eval_arg(scope,[:assign, :__left, left])
362376
compile_if(scope, :__left, :__left, right)
363377
end
@@ -522,7 +536,7 @@ def compile_assign(scope, left, right)
522536
end
523537

524538
if atype == :addr
525-
@global_scope.globals << aparam
539+
scope.add_constant(aparam)
526540
@global_constants << aparam
527541
elsif atype == :ivar
528542
# FIXME: The register allocation here
@@ -821,52 +835,27 @@ def compile_module(scope,name, *exps)
821835
# Takes the current scope, the name of the class as well as a list of expressions
822836
# that belong to the class.
823837
def compile_class(scope, name,superclass, *exps)
824-
@e.comment("=== class #{name} ===")
838+
superc = name == :Class ? nil : @classes[superclass]
839+
cscope = scope.find_constant(name)
825840

826-
superc = @classes[superclass]
841+
@e.comment("=== class #{cscope.name} ===")
827842

828-
cscope = ClassScope.new(scope, name, @vtableoffsets, superc)
829843

830844
@e.evict_regs_for(:self)
831845

832-
# FIXME: Need to be able to handle re-opening of classes
833-
exps.each do |l2|
834-
l2.each do |e|
835-
if e.is_a?(Array)
836-
if e[0] == :defm
837-
cscope.add_vtable_entry(e[1]) # add method into vtable of class-scope to associate with class
838846

839-
e[3].depth_first do |exp|
840-
exp.each do |n|
841-
cscope.add_ivar(n) if n.is_a?(Symbol) and n.to_s[0] == ?@ && n.to_s[1] != ?@
842-
end
843-
end
847+
name = cscope.name.to_sym
848+
# The check for :Class and :Kernel is an "evil" temporary hack to work around the bootstrapping
849+
# issue of creating these class objects before Object is initialized. A better solution (to avoid
850+
# demanding an explicit order would be to clear the Object constant and make sure __new_class_object
851+
#does not try to deref a null pointer
852+
#
853+
sscope = (name == superclass or name == :Class or name == :Kernel) ? nil : @classes[superclass]
844854

845-
elsif e[0] == :call && e[1] == :attr_accessor
846-
# This is a bit presumptious, assuming noone are stupid enough to overload
847-
# attr_accessor, attr_reader without making them do more or less the same thing.
848-
# but the right thing to do is actually to call the method.
849-
#
850-
# In any case there is no actual harm in allocating the vtable
851-
# entry.`
852-
#
853-
# We may do a quick temporary hack to synthesize the methods,
854-
# though, as otherwise we need to implement the full define_method
855-
# etc.
856-
arr = e[1].is_a?(Array) ? e[2] : [e[2]]
857-
arr.each {|entry|
858-
cscope.add_vtable_entry(entry.to_s[1..-1].to_sym)
859-
}
860-
end
861-
end
862-
end
863-
end
864-
@classes[name] = cscope
865-
@global_scope.globals << name
866-
sscope = name == superclass ? nil : @classes[superclass]
867855
ssize = sscope ? sscope.klass_size : nil
868856
ssize = 0 if ssize.nil?
869857
compile_exp(scope, [:assign, name.to_sym, [:sexp,[:call, :__new_class_object, [cscope.klass_size,superclass,ssize]]]])
858+
870859
@global_constants << name
871860

872861
# In the context of "cscope", "self" refers to the Class object of the newly instantiated class.
@@ -931,7 +920,6 @@ def compile_main(exp)
931920
# We should allow arguments to main
932921
# so argc and argv get defined, but
933922
# that is for later.
934-
@global_scope = GlobalScope.new(@vtableoffsets)
935923
compile_eval_arg(@global_scope, exp)
936924
end
937925
end

globalscope.rb

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,27 @@
55
# (because their value is known at compile time), but some of them are
66
# not. For now, we'll treat all of them as global variables.
77
class GlobalScope < Scope
8-
attr_accessor :globals
98
attr_reader :class_scope
109

1110
def initialize(offsets)
1211
@vtableoffsets = offsets
13-
@globals = Set.new
12+
@globals = {}
1413
@class_scope = ClassScope.new(self,"Object",@vtableoffsets,nil)
1514

1615
# Despite not following "$name" syntax, these are really global constants.
17-
@globals << :false
18-
@globals << :true
19-
@globals << :nil
16+
@globals[:false] = true
17+
@globals[:true] = true
18+
@globals[:nil] = true
19+
20+
@globals[:__left] = true
21+
end
22+
23+
def add_constant(c,v = true)
24+
@globals[c] = v
25+
end
26+
27+
def find_constant(c)
28+
@globals[c]
2029
end
2130

2231
def rest?

operators.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ def self.expect(s)
111111
# "Fake" operator for [] following a name
112112
"#index#" => Oper.new(100, :index, :infix),
113113
"." => Oper.new(100, :callm, :infix, 2,2,:left),
114-
"::" => Oper.new(100, :callm, :infix, 2,2,:left),
114+
"::" => Oper.new(100, :deref, :infix, 2,2,:left),
115115
".." => Oper.new(100, :range, :infix), # FIXME: Check pri - probably not right.
116116

117117

transform.rb

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,83 @@ def rewrite_concat(exp)
308308
end
309309
end
310310

311+
# build_class_scopes
312+
#
313+
# Consider the case where I open a class, define a method that refers to an as yet undefined
314+
# class. Then later I re-open the class and defines the earlier class as an inner class:
315+
#
316+
# class Foo
317+
# def hello
318+
# Bar.new
319+
# end
320+
# end
321+
#
322+
# class Foo
323+
# class Bar
324+
# end
325+
# end
326+
#
327+
# To handle this case, <tt>ClassScope</tt> objects must persist across open/close of a class,
328+
# and they do. However, to compile this to static references, I also must identify any references
329+
# and resolve them, to be able to distinguish a possible ::Bar from ::Foo::Bar
330+
#
331+
# (we still need to be able to fall back to dynamic constant lookup)
332+
#
333+
def build_class_scopes(exps, scope = nil)
334+
scope = @global_scope if !scope
335+
336+
return if !exps.is_a?(Array)
337+
338+
exps.each do |e|
339+
if e.is_a?(Array)
340+
if e[0] == :defm && scope.is_a?(ClassScope)
341+
scope.add_vtable_entry(e[1]) # add method into vtable of class-scope to associate with class
342+
343+
e[3].depth_first do |exp|
344+
exp.each do |n|
345+
scope.add_ivar(n) if n.is_a?(Symbol) and n.to_s[0] == ?@ && n.to_s[1] != ?@
346+
end
347+
end
348+
349+
elsif e[0] == :call && e[1] == :attr_accessor
350+
# This is a bit presumptious, assuming noone are stupid enough to overload
351+
# attr_accessor, attr_reader without making them do more or less the same thing.
352+
# but the right thing to do is actually to call the method.
353+
#
354+
# In any case there is no actual harm in allocating the vtable
355+
# entry.`
356+
#
357+
# We may do a quick temporary hack to synthesize the methods,
358+
# though, as otherwise we need to implement the full define_method
359+
# etc.
360+
arr = e[1].is_a?(Array) ? e[2] : [e[2]]
361+
arr.each {|entry|
362+
scope.add_vtable_entry(entry.to_s[1..-1].to_sym)
363+
}
364+
elsif e[0] == :class || e[0] == :module
365+
superclass = e[2]
366+
superc = @classes[superclass.to_sym]
367+
cscope = @classes[e[1].to_sym]
368+
cscope = ClassScope.new(scope, e[1], @vtableoffsets, superc) if !cscope
369+
@classes[cscope.name.to_sym] = cscope
370+
@global_scope.add_constant(cscope.name.to_sym,cscope)
371+
scope.add_constant(e[1].to_sym,cscope)
372+
build_class_scopes(e[3], cscope)
373+
elsif e[0] == :sexp
374+
else
375+
e[1..-1].each do |x|
376+
build_class_scopes(x,scope)
377+
end
378+
end
379+
end
380+
end
381+
end
382+
311383
def preprocess exp
384+
# The global scope is needed for some rewrites
385+
@global_scope = GlobalScope.new(@vtableoffsets)
386+
build_class_scopes(exp)
387+
312388
rewrite_concat(exp)
313389
rewrite_range(exp)
314390
rewrite_strconst(exp)

0 commit comments

Comments
 (0)