-
Notifications
You must be signed in to change notification settings - Fork 23
/
Copy pathfunction.rb
136 lines (111 loc) · 3.26 KB
/
function.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# Represents a function argument.
# Can also be a list of arguments if :rest is specified in modifiers
# for variable argument functions.
class Arg
attr_reader :name, :rest, :default
# The local variable offset for this argument, if it
# has a default
attr_accessor :lvar
def initialize(name, *modifiers)
raise "Internal error: Arg.name must be Symbol; '#{name.inspect}'" if !name.is_a?(Symbol)
@name = name
# @rest indicates if we have
# a variable amount of parameters
@rest = modifiers.include?(:rest)
@default = modifiers[0] == :default ? modifiers[1] : nil
end
def rest?
@rest
end
def type
rest? ? :argaddr : :arg
end
end
# Represents a function.
# Takes arguments and a body of code.
class Function
attr_reader :args, :body, :scope, :name, :break_label, :arity_check
# Number of variables with defaults that we need to
# allocate local stack space for.
attr_reader :defaultvars
# Constructor for functions.
# Takes an argument list, a body of expressions as well as
# the scope the function was defined in. For methods this is a
# class scope.
def initialize(name, args, body, scope, break_label, arity_check = true)
@name = name
@body = body || []
@rest = false
@arity_check = arity_check
args ||= []
@defaultvars = 0
# If break should go to an outer function,
# break_label is not nil
@break_label = break_label
if args.last.kind_of?(Array)
@blockarg = args.pop[0] if args.last[1] == :block
end
@args = args.collect do |a|
arg = Arg.new(*[a].flatten(1))
if arg.default
arg.lvar = @defaultvars
@defaultvars += 1
end
@rest = true if arg.rest?
arg
end
# Default values have not yet been assigned for this.
@defaults_assigned = false
@scope = scope
end
def rest?
@rest
end
def minargs
@args.length - (rest? ? 1 : 0) - @defaultvars
end
def maxargs
rest? ? 99999 : @args.length
end
def process_defaults
self.args.each_with_index do |arg,index|
# FIXME: Should check that there are no "gaps" without defaults?
if (arg.default)
yield(arg,index)
end
end
@defaults_assigned = true
end
def lvaroffset
@defaultvars
end
# For arguments with defaults only, return the [:lvar, arg.lvar] value
def get_lvar_arg(a)
a = a.to_s[1..-1].to_sym if a[0] == ?#
args.each_with_index do |arg,i|
if arg.default && (arg.name == a)
raise "Expected to have a lvar assigned for #{arg.name}" if !arg.lvar
return [:lvar, arg.lvar]
end
end
nil
end
def get_arg(a)
# Previously, we made this a constant for non-variadic
# functions. The problem is that we need to be able
# to trigger exceptions for calls with the wrong number
# of arguments, so we need this always.
return [:lvar, -1] if a == :numargs
# FIXME
# @bug: r does not get set to nil if this line is not here.
r = nil
r = get_lvar_arg(a) if @defaults_assigned || a[0] == ?#
return r if r
raise "Expected lvar - #{a} / #{args.inspect}" if a[0] == ?#
a = :__closure__ if a == @blockarg
args.each_with_index do |arg,i|
return [arg.type, i] if arg.name == a
end
return @scope.get_arg(a)
end
end