ruby2cext 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Changelog +27 -0
- data/README +44 -0
- data/bin/rb2cx +178 -0
- data/doc/eval2c.html +281 -0
- data/doc/index.html +218 -0
- data/doc/limitations.html +581 -0
- data/doc/optimizations.html +222 -0
- data/doc/rb2cx.html +151 -0
- data/doc/style.css +27 -0
- data/lib/ruby2cext/c_function.rb +621 -0
- data/lib/ruby2cext/common_node_comp.rb +1409 -0
- data/lib/ruby2cext/compiler.rb +242 -0
- data/lib/ruby2cext/error.rb +15 -0
- data/lib/ruby2cext/eval2c.rb +129 -0
- data/lib/ruby2cext/parser.rb +36 -0
- data/lib/ruby2cext/plugin.rb +24 -0
- data/lib/ruby2cext/plugins/builtin_methods.rb +820 -0
- data/lib/ruby2cext/plugins/case_optimize.rb +105 -0
- data/lib/ruby2cext/plugins/const_cache.rb +38 -0
- data/lib/ruby2cext/plugins/inline_methods.rb +69 -0
- data/lib/ruby2cext/plugins/require_include.rb +71 -0
- data/lib/ruby2cext/plugins/warnings.rb +123 -0
- data/lib/ruby2cext/scopes.rb +227 -0
- data/lib/ruby2cext/str_to_c_strlit.rb +12 -0
- data/lib/ruby2cext/tools.rb +84 -0
- data/lib/ruby2cext/version.rb +22 -0
- data/testfiles/bench.rb +116 -0
- data/testfiles/eval2c/test_eval2c.rb +37 -0
- data/testfiles/test.rb +615 -0
- data/testfiles/vmode_test.rb +73 -0
- data/testfiles/warn_test.rb +35 -0
- metadata +87 -0
@@ -0,0 +1,222 @@
|
|
1
|
+
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Optimizations</title>
|
5
|
+
<link href="style.css" media="all" rel="Stylesheet" type="text/css">
|
6
|
+
</head>
|
7
|
+
<body>
|
8
|
+
<h1>Optimizations</h1>
|
9
|
+
|
10
|
+
|
11
|
+
<p>Without any optimizations enabled Ruby2CExtension tries to match Ruby’s
|
12
|
+
semantics as close as possible, but sometimes it might be desired to sacrifice
|
13
|
+
some compatibility in exchange for faster execution.</p>
|
14
|
+
|
15
|
+
|
16
|
+
<p>Ruby2CExtension offers different optimizations which can be enabled
|
17
|
+
indivdually (see <a href="rb2cx.html">rb2cx</a> and <a href="eval2c.html">Eval2C</a>). All these
|
18
|
+
optimizations should be safe, i.e. they won’t produce segfaults or other C
|
19
|
+
level failures, but, as mentioned above, they sacrifice compatibility for
|
20
|
+
faster execution and thus can result in “wrong” behavior of the compiled Ruby
|
21
|
+
code.</p>
|
22
|
+
|
23
|
+
|
24
|
+
<p>Sections: <a href="#section1">Constant Lookup Caching</a>, <a href="#section2">Optimization of Calls to Built-in Methods</a>, <a href="#section3">Inlining of Some Built-in Methods</a>, <a href="#section4">Case Optimization</a>.</p>
|
25
|
+
|
26
|
+
|
27
|
+
<h2 id="section1">Constant Lookup Caching</h2>
|
28
|
+
|
29
|
+
|
30
|
+
<p>This optimization simply assumes that constants really are constant. So the
|
31
|
+
constant lookup for each constant is only performed once and then the result
|
32
|
+
is cached for later use.</p>
|
33
|
+
|
34
|
+
|
35
|
+
<p>Because in Ruby constants can actually change their value, this optimization
|
36
|
+
can lead to wrong results. So if your code depends on changing constants, then
|
37
|
+
this optimization should not be enabled. Otherwise it can provide a small
|
38
|
+
performance improvement.</p>
|
39
|
+
|
40
|
+
|
41
|
+
<h2 id="section2">Optimization of Calls to Built-in Methods</h2>
|
42
|
+
|
43
|
+
|
44
|
+
<p>This optimization is by far the most important one, because it can produce
|
45
|
+
very significant speedups for most Ruby code. It replaces normal calls to many
|
46
|
+
(C-implemented) methods of the built-in classes with (almost) direct calls to
|
47
|
+
the C functions that implement these methods.</p>
|
48
|
+
|
49
|
+
|
50
|
+
<p>The motivation behind this optimization is that method calls are generally
|
51
|
+
considered to be one of the “slowest” parts of Ruby and since almost
|
52
|
+
everything is method calls in Ruby it helps to improve at least some of them.
|
53
|
+
A method call basically consists of two parts: first the method is looked up
|
54
|
+
in the receivers class (or its ancestors) and then Ruby sets up the
|
55
|
+
environment for the method and executes the code. For many of the
|
56
|
+
C-implemented methods of the built-in classes the setup is not necessary and
|
57
|
+
if we assume that they are generally not modified/redefined, we can also avoid
|
58
|
+
the lookup. So we basically can directly call the C function that implements
|
59
|
+
the method and thereby avoid most of the method call overhead in these cases.</p>
|
60
|
+
|
61
|
+
|
62
|
+
<p>Because this can not be done for every C-implemented method, there is a list
|
63
|
+
of method names (with arities and built-in types that have this method) for
|
64
|
+
which the optimization can be applied. When a call to such a method is
|
65
|
+
compiled, instead of doing a normal <code>rb_funcall()</code>, another C function is
|
66
|
+
called which checks if the type of the receiver matches one of the built-in
|
67
|
+
types that have this method and if yes, then the implementing C function is
|
68
|
+
called directly otherwise a normal Ruby call is performed.</p>
|
69
|
+
|
70
|
+
|
71
|
+
<p>To be able to directly call the C functions that implement the methods, the
|
72
|
+
pointers to these C functions have to be looked up at least once. So, when the
|
73
|
+
C extension is <code>require</code>d, for all methods that are used in this
|
74
|
+
extension the pointers to the C functions are looked up. This is also kind of
|
75
|
+
a sanity check: if that lookup fails or the lookup returns that the method is
|
76
|
+
not implemented by a C function (e.g. because it was modified/overridden by a
|
77
|
+
Ruby implementation), then this method will later be called by just using
|
78
|
+
<code>rb_funcall()</code>.</p>
|
79
|
+
|
80
|
+
|
81
|
+
<p>The built-in types which are included in this optimization are <code>Array</code>,
|
82
|
+
<code>Bignum</code>, <code>FalseClass</code>, <code>Fixnum</code>, <code>Float</code>, <code>Hash</code>, <code>NilClass</code>, <code>Regexp</code>,
|
83
|
+
<code>String</code>, <code>Symbol</code> and <code>TrueClass</code>. For the detailed method list please see
|
84
|
+
<code>lib/ruby2cext/plugins/builtin_methods.rb</code>.</p>
|
85
|
+
|
86
|
+
|
87
|
+
<p>If the type of the receiver is known at compile time, then it is possibly to
|
88
|
+
even further optimize by directly calling the implementing C function, instead
|
89
|
+
of doing the indirect call with the runtime type checking. An example for such
|
90
|
+
a case is the expression <code>1 == some_var</code>, in this expression the
|
91
|
+
receiver is a fixnum. The compile time type detection also works for string
|
92
|
+
literals, array literals, hash literals and regexp literals.</p>
|
93
|
+
|
94
|
+
|
95
|
+
<p>As mentioned above, this optimization can speedup most Ruby code, but it also
|
96
|
+
has downsides:</p>
|
97
|
+
|
98
|
+
|
99
|
+
<ul>
|
100
|
+
<li>If the type of the receiver of such a method call is not one of the
|
101
|
+
supported built-in types, then the call will actually be slightly slower
|
102
|
+
than it would have been without the optimization (because of the additional
|
103
|
+
type check). But the overhead is very small.</li>
|
104
|
+
<li>If one of the supported methods is modified after the compiled C extension
|
105
|
+
is <code>require</code>d, then this modification will not be used in the
|
106
|
+
extension, the extension will just continue to use the C implementation.</li>
|
107
|
+
</ul>
|
108
|
+
|
109
|
+
|
110
|
+
But in general this optimization can and should always be used, unless it is
|
111
|
+
necessary to modify built-in methods after the extension is
|
112
|
+
<code>require</code>d.
|
113
|
+
|
114
|
+
<h2 id="section3">Inlining of Some Built-in Methods</h2>
|
115
|
+
|
116
|
+
|
117
|
+
<p>This optimization goes one step further than the previous optimization by
|
118
|
+
avoiding the method calls completely and instead directly inlining the code
|
119
|
+
for the following methods: <code>nil?</code>, <code>equal?</code> and <code>__send__</code> (without a block).</p>
|
120
|
+
|
121
|
+
|
122
|
+
<p>Because these three methods should never be redefined in any class (according
|
123
|
+
to their documentation), this optimization can be applied to all calls of
|
124
|
+
these methods (with the correct arities) independent of the type of the
|
125
|
+
receiver.</p>
|
126
|
+
|
127
|
+
|
128
|
+
<p>Details:</p>
|
129
|
+
|
130
|
+
|
131
|
+
<ul>
|
132
|
+
<li>Any call to <code>nil?</code> with no arguments will be translated to a simple check
|
133
|
+
that determines if the receiver is <code>nil</code>.</li>
|
134
|
+
<li>Any call to <code>equal?</code> with one argument will be translated to a simple check
|
135
|
+
that determines if the receiver is equal to the first argument (i.e. they
|
136
|
+
have the same id).</li>
|
137
|
+
<li>Any call to <code>__send__</code> with one or more arguments and without a block will
|
138
|
+
be translated to code that directly performs the method call. As a result
|
139
|
+
<code>__send__(:foo, 1, 2, 3)</code> will perform practically as fast as
|
140
|
+
<code>foo(1, 2, 3)</code> in compiled code.</li>
|
141
|
+
</ul>
|
142
|
+
|
143
|
+
|
144
|
+
<p>In general this optimization can and should always be used, unless one of the
|
145
|
+
affected methods is redefined somewhere.</p>
|
146
|
+
|
147
|
+
|
148
|
+
<h2 id="section4">Case Optimization</h2>
|
149
|
+
|
150
|
+
|
151
|
+
<p>This optimization can in some cases transform Ruby <code>case</code>/<code>when</code> statements to
|
152
|
+
real C <code>switch</code>/<code>case</code> statements. This works for cases with Ruby immediate
|
153
|
+
values, for which the integral value is known at compile time, i.e. <code>nil</code>,
|
154
|
+
<code>true</code>, <code>false</code> and fixnums.</p>
|
155
|
+
|
156
|
+
|
157
|
+
<p>These cases have to be put at the beginning, all following non optimizable
|
158
|
+
whens will be handled normally. Example:</p>
|
159
|
+
|
160
|
+
|
161
|
+
<pre><code>case x
|
162
|
+
when true
|
163
|
+
# opt
|
164
|
+
when 2, 3, nil
|
165
|
+
# opt
|
166
|
+
when false, 26
|
167
|
+
# opt
|
168
|
+
when /foo/
|
169
|
+
# normal
|
170
|
+
when 23
|
171
|
+
# normal because, it is after another normal case
|
172
|
+
end
|
173
|
+
</code></pre>
|
174
|
+
|
175
|
+
<p>This basically gets converted to the following C code:</p>
|
176
|
+
|
177
|
+
|
178
|
+
<pre><code>switch (eval(x)) {
|
179
|
+
case Qtrue:
|
180
|
+
code;
|
181
|
+
break;
|
182
|
+
case LONG2FIX(2):
|
183
|
+
case LONG2FIX(3):
|
184
|
+
case Qnil:
|
185
|
+
code;
|
186
|
+
break;
|
187
|
+
case Qfalse:
|
188
|
+
case LONG2FIX(26):
|
189
|
+
code;
|
190
|
+
break;
|
191
|
+
default:
|
192
|
+
other cases ...
|
193
|
+
}
|
194
|
+
</code></pre>
|
195
|
+
|
196
|
+
<p>There also is a fallback that ensures that cases like</p>
|
197
|
+
|
198
|
+
|
199
|
+
<pre><code>case 2.0
|
200
|
+
when 2
|
201
|
+
...
|
202
|
+
end
|
203
|
+
</code></pre>
|
204
|
+
|
205
|
+
<p>still work.</p>
|
206
|
+
|
207
|
+
|
208
|
+
<p>This optimization can be useful for some kind of opcode dispatch in an
|
209
|
+
interpreter for example.</p>
|
210
|
+
|
211
|
+
|
212
|
+
<p>It should always produce the correct results, unless the <code>===</code> method of
|
213
|
+
<code>Fixnum</code>, <code>TrueClass</code>, <code>FalseClass</code> or <code>NilClass</code> is modified. So it is
|
214
|
+
generally okay to enable this optimization.</p>
|
215
|
+
|
216
|
+
|
217
|
+
<p>When this optimization is combined with the built-in methods optimization,
|
218
|
+
then the gained speedup is not really big (because the <code>===</code> calls for the
|
219
|
+
<code>when</code> cases are optimized by the built-in methods optimization anyway), but
|
220
|
+
it can be useful for larger <code>case</code>/<code>when</code> statements.</p>
|
221
|
+
</body>
|
222
|
+
</html>
|
data/doc/rb2cx.html
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>rb2cx</title>
|
5
|
+
<link href="style.css" media="all" rel="Stylesheet" type="text/css">
|
6
|
+
</head>
|
7
|
+
<body>
|
8
|
+
<h1>rb2cx</h1>
|
9
|
+
|
10
|
+
|
11
|
+
<p><code>rb2cx</code> is the command line interface to the functionality of Ruby2CExtension.
|
12
|
+
It takes one or more Ruby files, translates them to equivalent C extensions
|
13
|
+
and optionally compiles them with the C compiler.</p>
|
14
|
+
|
15
|
+
|
16
|
+
<p>Sections: <a href="#section1">Overview</a>, <a href="#section2">General Options</a>, <a href="#section3">Include Option</a>, <a href="#section4">Optimization Options</a>, <a href="#section5">Examples</a>.</p>
|
17
|
+
|
18
|
+
|
19
|
+
<h2 id="section1">Overview</h2>
|
20
|
+
|
21
|
+
|
22
|
+
<p>The general usage is very simple, just run <code>rb2cx</code> with the filenames of the
|
23
|
+
Ruby files as arguments:</p>
|
24
|
+
|
25
|
+
|
26
|
+
<pre><code>rb2cx file1.rb file2.rb
|
27
|
+
</code></pre>
|
28
|
+
|
29
|
+
<p>This will produce <code>file1.c</code>, <code>file2.c</code> and the compiled extensions <code>file1.so</code>,
|
30
|
+
<code>file2.so</code> (the file extension depends on the platform).</p>
|
31
|
+
|
32
|
+
|
33
|
+
<p>Additionally it is possible to specify some options before the filenames.</p>
|
34
|
+
|
35
|
+
|
36
|
+
<h2 id="section2">General Options</h2>
|
37
|
+
|
38
|
+
|
39
|
+
<ul>
|
40
|
+
<li><code>-h</code>/<code>--help</code>: a help message is printed and the program exits regardless of
|
41
|
+
other arguments.</li>
|
42
|
+
<li><code>-c</code>/<code>--only-c</code>: only the translation to C code is performed, the compilation
|
43
|
+
to native extensions is omited.</li>
|
44
|
+
<li><code>-v</code>/<code>--verbose</code>: some status messages are printed, e.g. which files are
|
45
|
+
currently translated or compiled.</li>
|
46
|
+
<li><code>-w</code>/<code>--warnings</code>: warnings are printed for some things that might not work
|
47
|
+
as expected. The warnings do not cover everything mentioned in the
|
48
|
+
<a href="limitations.html">limitations documentation</a>.</li>
|
49
|
+
<li><code>-V</code>/<code>--version</code>: the Ruby2CExtension version is printed.</li>
|
50
|
+
</ul>
|
51
|
+
|
52
|
+
|
53
|
+
<h2 id="section3">Include Option</h2>
|
54
|
+
|
55
|
+
|
56
|
+
<p>Ruby2CExtension has an experimental feature that allows dependencies of a
|
57
|
+
compiled Ruby file to be included in the same extension. This is best
|
58
|
+
described with an example. Let’s say we have 3 files: <code>a.rb</code>, <code>b.rb</code> and
|
59
|
+
<code>c.rb</code>:</p>
|
60
|
+
|
61
|
+
|
62
|
+
<pre><code># a.rb
|
63
|
+
puts "a"
|
64
|
+
class A; end
|
65
|
+
</code></pre>
|
66
|
+
|
67
|
+
<pre><code># b.rb
|
68
|
+
puts "b"
|
69
|
+
require "a"
|
70
|
+
class B; end
|
71
|
+
</code></pre>
|
72
|
+
|
73
|
+
<pre><code># c.rb
|
74
|
+
require "a"
|
75
|
+
require "b"
|
76
|
+
puts "c"
|
77
|
+
</code></pre>
|
78
|
+
|
79
|
+
<p>The <code>require</code>-include feature is enabled if the <code>-I</code>/<code>--include</code> option
|
80
|
+
followed by a search path is given (possibly multiple times). The search paths
|
81
|
+
can be absolute or relative paths, they are searched for <code>require</code>d
|
82
|
+
files. So, if the example is compiled to a C extension with</p>
|
83
|
+
|
84
|
+
|
85
|
+
<pre><code>rb2cx -I . c.rb
|
86
|
+
</code></pre>
|
87
|
+
|
88
|
+
<p>then the following will happen. For each call to <code>require</code> with no explicit
|
89
|
+
receiver and one argument that is a simple string (i.e. no interpolation) the
|
90
|
+
search paths are searched for a file that matches the argument to the
|
91
|
+
<code>require</code> call (with an algorithm similar to Ruby’s). If no matching file is
|
92
|
+
found, then the call to <code>require</code> is compiled as usual. But if a matching file
|
93
|
+
is found, then that file is read and it is translated to C and instead of
|
94
|
+
compiling the original <code>require</code> call that translated C code will be executed,
|
95
|
+
unless a <code>require</code> of that file was encountered before.</p>
|
96
|
+
|
97
|
+
|
98
|
+
<p>So in the example we will get one C extension, that contains the code of all
|
99
|
+
three files and the <code>require</code>s are performed at the right moment
|
100
|
+
and in correct order. The output will be (notice that the <code>require
|
101
|
+
"a"</code> in <code>b.rb</code> does not result in a second execution of <code>a.rb</code>, as
|
102
|
+
expected):</p>
|
103
|
+
|
104
|
+
|
105
|
+
<pre><code>a
|
106
|
+
b
|
107
|
+
c
|
108
|
+
</code></pre>
|
109
|
+
|
110
|
+
As stated above, this feature is experimental, it should work well for many
|
111
|
+
cases, e.g. for a library that has one main file which is always
|
112
|
+
<code>require</code>d by user code, but is split into multiple files for
|
113
|
+
maintenance. Such a library could be compiled into a single C extension. But
|
114
|
+
it can break for various reasons, e.g. Ruby will not be aware, that the
|
115
|
+
included files are already “<code>require</code>d” (so if a file is already
|
116
|
+
included in a C extension, but also <code>require</code>d by other normal Ruby
|
117
|
+
code, then that file will in effect execute twice).
|
118
|
+
|
119
|
+
<p>If the verbose mode is enabled (the <code>-v</code>/<code>--verbose</code> option), then each
|
120
|
+
inclusion of a file will be logged. This way one can easily check if the
|
121
|
+
expected files are actually included.</p>
|
122
|
+
|
123
|
+
|
124
|
+
<h2 id="section4">Optimization Options</h2>
|
125
|
+
|
126
|
+
|
127
|
+
<p>Ruby2CExtension can use various <a href="optimizations.html">optimizations</a> to improve
|
128
|
+
the performance of the resulting C extension. These optimizations are not
|
129
|
+
enabled by default, because they can all result in wrong behavior. The
|
130
|
+
optimizations are enabled by the <code>-O</code>/<code>--optimization</code> option followed by one
|
131
|
+
of the following optimization names:</p>
|
132
|
+
|
133
|
+
|
134
|
+
<ul>
|
135
|
+
<li><code>const_cache</code>: enables constant lookup caching</li>
|
136
|
+
<li><code>builtin_methods</code>: enables optimization of calls to built-in methods</li>
|
137
|
+
<li><code>inline_methods</code>: enables inlining of some built-in methods</li>
|
138
|
+
<li><code>case_optimize</code>: enables case optimization</li>
|
139
|
+
<li><code>all</code>: enables all of the above optimizations</li>
|
140
|
+
</ul>
|
141
|
+
|
142
|
+
|
143
|
+
<h2 id="section5">Examples</h2>
|
144
|
+
|
145
|
+
|
146
|
+
<pre><code>rb2cx -wv file.rb
|
147
|
+
rb2cx -I . -O all file.rb
|
148
|
+
rb2cx -I . -I ../libs -O const_cache -O builtin_methods -w file.rb
|
149
|
+
</code></pre>
|
150
|
+
</body>
|
151
|
+
</html>
|
data/doc/style.css
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
body {
|
2
|
+
color: #333;
|
3
|
+
background-color: white;
|
4
|
+
margin: 0 2em;
|
5
|
+
min-width: 41em;
|
6
|
+
}
|
7
|
+
h1 {
|
8
|
+
color: black;
|
9
|
+
border-bottom: 2px solid red;
|
10
|
+
}
|
11
|
+
h2 {
|
12
|
+
color: black;
|
13
|
+
}
|
14
|
+
h3 {
|
15
|
+
color: black;
|
16
|
+
}
|
17
|
+
pre {
|
18
|
+
color: black;
|
19
|
+
background-color: #eee;
|
20
|
+
padding: 0.7em 1em;
|
21
|
+
margin: 0.8em 0;
|
22
|
+
border: 1px dotted #999;
|
23
|
+
}
|
24
|
+
code {
|
25
|
+
color: black;
|
26
|
+
background-color: #eee;
|
27
|
+
}
|
@@ -0,0 +1,621 @@
|
|
1
|
+
|
2
|
+
require "ruby2cext/str_to_c_strlit"
|
3
|
+
require "ruby2cext/error"
|
4
|
+
require "ruby2cext/tools"
|
5
|
+
require "ruby2cext/common_node_comp"
|
6
|
+
require "ruby2cext/scopes"
|
7
|
+
|
8
|
+
module Ruby2CExtension
|
9
|
+
|
10
|
+
module CFunction
|
11
|
+
# contains all different Types of C functions that are compiled from ruby nodes
|
12
|
+
|
13
|
+
class Base
|
14
|
+
include CommonNodeComp
|
15
|
+
extend Tools::EnsureNodeTypeMixin
|
16
|
+
attr_reader :scope, :compiler, :closure_tbl
|
17
|
+
attr_accessor :need_res, :need_self, :need_cref, :need_class, :need_wrap
|
18
|
+
def initialize(compiler, scope)
|
19
|
+
@compiler = compiler
|
20
|
+
@scope = scope
|
21
|
+
@closure_tbl = []
|
22
|
+
@scope.closure_tbl = @closure_tbl
|
23
|
+
@lines = []
|
24
|
+
@while_stack = []
|
25
|
+
end
|
26
|
+
|
27
|
+
# some redirects to compiler
|
28
|
+
def un(str); compiler.un(str); end
|
29
|
+
def sym(sym); compiler.sym(sym); end
|
30
|
+
def global_const(str, register_gc = true)
|
31
|
+
compiler.global_const(str, register_gc)
|
32
|
+
end
|
33
|
+
def global_var(str)
|
34
|
+
compiler.global_var(str)
|
35
|
+
end
|
36
|
+
def add_helper(str); compiler.add_helper(str); end
|
37
|
+
|
38
|
+
def get_lines
|
39
|
+
@lines.join("\n")
|
40
|
+
end
|
41
|
+
def l(line) # add_line
|
42
|
+
# ignore lines with only whitespace or only alnum chars (variable name)
|
43
|
+
unless line =~ /\A\s*\z/ || (line =~ /\A(\w*);?\z/ && !(%w[break continue].include? $1))
|
44
|
+
@lines << line
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def push_while(redo_lbl, next_lbl, break_lbl)
|
49
|
+
@while_stack << [redo_lbl, next_lbl, break_lbl]
|
50
|
+
end
|
51
|
+
def pop_while
|
52
|
+
raise Ruby2CExtError::Bug, "popped from empty while stack" if @while_stack.empty?
|
53
|
+
@while_stack.pop
|
54
|
+
end
|
55
|
+
def in_while?(lbl_type = nil)
|
56
|
+
return false if @while_stack.empty?
|
57
|
+
case lbl_type
|
58
|
+
when nil
|
59
|
+
true
|
60
|
+
when :redo
|
61
|
+
@while_stack.last[0]
|
62
|
+
when :next
|
63
|
+
@while_stack.last[1]
|
64
|
+
when :break
|
65
|
+
@while_stack.last[2]
|
66
|
+
else
|
67
|
+
false
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def break_allowed?(with_value)
|
72
|
+
in_while?(:break)
|
73
|
+
end
|
74
|
+
def comp_break(hash)
|
75
|
+
if (lbl = in_while?(:break))
|
76
|
+
l "while_res = #{comp(hash[:stts])};"
|
77
|
+
l "goto #{lbl};"
|
78
|
+
"Qnil"
|
79
|
+
else
|
80
|
+
raise Ruby2CExtError::NotSupported, "break is not supported here"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def next_allowed?
|
85
|
+
in_while?(:next)
|
86
|
+
end
|
87
|
+
def comp_next(hash)
|
88
|
+
if (lbl = in_while?(:next))
|
89
|
+
# hash[:stts] is silently ignored (as ruby does)
|
90
|
+
l "goto #{lbl};"
|
91
|
+
"Qnil"
|
92
|
+
else
|
93
|
+
raise Ruby2CExtError::NotSupported, "next is not supported here"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def redo_allowed?
|
98
|
+
in_while?(:redo)
|
99
|
+
end
|
100
|
+
def comp_redo(hash)
|
101
|
+
if (lbl = in_while?(:redo))
|
102
|
+
l "goto #{lbl};"
|
103
|
+
"Qnil"
|
104
|
+
else
|
105
|
+
raise Ruby2CExtError::NotSupported, "redo is not supported here"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def return_allowed?
|
110
|
+
false
|
111
|
+
end
|
112
|
+
def comp_return(hash)
|
113
|
+
raise Ruby2CExtError::NotSupported, "return is not supported here"
|
114
|
+
end
|
115
|
+
|
116
|
+
def comp_retry(hash)
|
117
|
+
l "rb_jump_tag(0x4 /* TAG_RETRY */);"
|
118
|
+
"Qnil"
|
119
|
+
end
|
120
|
+
|
121
|
+
def need_closure_ptr
|
122
|
+
false # only needed in Block
|
123
|
+
end
|
124
|
+
|
125
|
+
def assign_res(str)
|
126
|
+
self.need_res = true
|
127
|
+
unless str.strip == "res"
|
128
|
+
l "res = #{str};"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
def get_self
|
132
|
+
self.need_self = true
|
133
|
+
"self"
|
134
|
+
end
|
135
|
+
def get_cref
|
136
|
+
self.need_cref = true
|
137
|
+
get_cref_impl # subclass
|
138
|
+
end
|
139
|
+
def get_class
|
140
|
+
self.need_class = true
|
141
|
+
"s_class"
|
142
|
+
end
|
143
|
+
def get_cbase
|
144
|
+
"(#{get_cref}->nd_clss)"
|
145
|
+
end
|
146
|
+
def get_cvar_cbase
|
147
|
+
# there is always at least one real class in the cref chain
|
148
|
+
add_helper <<-EOC
|
149
|
+
static VALUE cvar_cbase(NODE *cref) {
|
150
|
+
while (FL_TEST(cref->nd_clss, FL_SINGLETON)) { cref = cref->nd_next; }
|
151
|
+
return cref->nd_clss;
|
152
|
+
}
|
153
|
+
EOC
|
154
|
+
"cvar_cbase(#{get_cref})"
|
155
|
+
end
|
156
|
+
|
157
|
+
def get_closure_ary_var
|
158
|
+
"my_closure_ary"
|
159
|
+
end
|
160
|
+
|
161
|
+
def get_wrap_ptr
|
162
|
+
"(&the_wrap)"
|
163
|
+
end
|
164
|
+
|
165
|
+
def add_closure_need(sym)
|
166
|
+
closure_tbl << sym unless closure_tbl.include? sym
|
167
|
+
end
|
168
|
+
|
169
|
+
def closure_buid_c_code
|
170
|
+
if closure_tbl.empty?
|
171
|
+
nil
|
172
|
+
else
|
173
|
+
res = ["#{get_closure_ary_var} = rb_ary_new2(#{closure_tbl.size});"]
|
174
|
+
closure_tbl.each_with_index { |entry, idx|
|
175
|
+
case entry
|
176
|
+
when Integer
|
177
|
+
res << "RARRAY(#{get_closure_ary_var})->ptr[#{idx}] = #{scope.get_dvar_ary(entry)};"
|
178
|
+
when :lvar
|
179
|
+
res << "RARRAY(#{get_closure_ary_var})->ptr[#{idx}] = #{scope.get_lvar_ary};"
|
180
|
+
when :self
|
181
|
+
res << "RARRAY(#{get_closure_ary_var})->ptr[#{idx}] = #{get_self};"
|
182
|
+
when :class
|
183
|
+
res << "RARRAY(#{get_closure_ary_var})->ptr[#{idx}] = #{get_class};"
|
184
|
+
when :cref
|
185
|
+
add_helper <<-EOC
|
186
|
+
static void cref_data_mark(NODE *n) {
|
187
|
+
rb_gc_mark((VALUE)n);
|
188
|
+
}
|
189
|
+
EOC
|
190
|
+
res << "RARRAY(#{get_closure_ary_var})->ptr[#{idx}] = " +
|
191
|
+
"Data_Wrap_Struct(rb_cObject, cref_data_mark, 0, #{get_cref});"
|
192
|
+
else
|
193
|
+
raise Ruby2CExtError::Bug, "unexpected closure_tbl entry: #{entry.inspect}"
|
194
|
+
end
|
195
|
+
}
|
196
|
+
res << "RARRAY(#{get_closure_ary_var})->len = #{closure_tbl.size};"
|
197
|
+
res.join("\n")
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def wrap_buid_c_code
|
202
|
+
add_helper <<-EOC
|
203
|
+
struct wrap {
|
204
|
+
VALUE self;
|
205
|
+
VALUE s_class;
|
206
|
+
NODE *cref;
|
207
|
+
VALUE my_closure_ary;
|
208
|
+
VALUE *closure;
|
209
|
+
VALUE *var;
|
210
|
+
long state;
|
211
|
+
};
|
212
|
+
EOC
|
213
|
+
res = []
|
214
|
+
res << "the_wrap.self = #{get_self};" if need_self
|
215
|
+
res << "the_wrap.s_class = #{get_class};" if need_class
|
216
|
+
res << "the_wrap.cref = #{get_cref};" if need_cref
|
217
|
+
res << "the_wrap.my_closure_ary = #{get_closure_ary_var};" unless closure_tbl.empty?
|
218
|
+
res << "the_wrap.closure = closure;" if need_closure_ptr
|
219
|
+
res << "the_wrap.var = #{scope.var_ptr_for_wrap};" if scope.var_ptr_for_wrap
|
220
|
+
res.compact.join("\n")
|
221
|
+
end
|
222
|
+
|
223
|
+
def init_c_code
|
224
|
+
cb_c_code = closure_buid_c_code # must be called before the rest because it might change self or scope
|
225
|
+
res = []
|
226
|
+
res << "VALUE res;" if need_res
|
227
|
+
res << "VALUE s_class = (#{get_cref})->nd_clss;" if need_class
|
228
|
+
res << "VALUE #{get_closure_ary_var};" if cb_c_code
|
229
|
+
res << "struct wrap the_wrap;" if need_wrap
|
230
|
+
res << scope.init_c_code
|
231
|
+
res << cb_c_code if cb_c_code
|
232
|
+
res << wrap_buid_c_code if need_wrap
|
233
|
+
res.compact.join("\n")
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
class ClassModuleScope < Base
|
238
|
+
def self.compile(outer, scope_node, class_mod_var, is_class)
|
239
|
+
ensure_node_type(scope_node, :scope)
|
240
|
+
vmode_methods = Scopes::Scope::VMODES.dup
|
241
|
+
vmode_methods.delete :module_function if is_class
|
242
|
+
cf = self.new(outer.compiler, Scopes::Scope.new(scope_node.last[:tbl], vmode_methods))
|
243
|
+
cf.instance_eval {
|
244
|
+
block = make_block(scope_node.last[:next])
|
245
|
+
l "return #{comp(block)};"
|
246
|
+
}
|
247
|
+
body = "#{cf.init_c_code}\n#{cf.get_lines}"
|
248
|
+
args = []
|
249
|
+
args << "VALUE self" if cf.need_self
|
250
|
+
args << "NODE *cref" if cf.need_cref
|
251
|
+
sig = "static VALUE FUNNAME(#{args.join(", ")}) {"
|
252
|
+
fname = cf.compiler.add_fun("#{sig}\n#{body}\n}", "class_module_scope")
|
253
|
+
outer.instance_eval {
|
254
|
+
args = []
|
255
|
+
args << class_mod_var if cf.need_self
|
256
|
+
args << "NEW_NODE(NODE_CREF, #{class_mod_var}, 0, #{get_cref})" if cf.need_cref
|
257
|
+
assign_res("#{fname}(#{args.join(", ")})")
|
258
|
+
}
|
259
|
+
"res"
|
260
|
+
end
|
261
|
+
|
262
|
+
def get_cref_impl
|
263
|
+
"cref"
|
264
|
+
end
|
265
|
+
|
266
|
+
end
|
267
|
+
|
268
|
+
class ToplevelScope < ClassModuleScope
|
269
|
+
def self.compile(compiler, scope_node, private_vmode = true)
|
270
|
+
ensure_node_type(scope_node, :scope)
|
271
|
+
cf = self.new(compiler, Scopes::Scope.new(scope_node.last[:tbl], [:public, :private], private_vmode))
|
272
|
+
cf.instance_eval {
|
273
|
+
block = make_block(scope_node.last[:next])
|
274
|
+
l "#{comp(block)};"
|
275
|
+
}
|
276
|
+
body = "#{cf.init_c_code}\n#{cf.get_lines}"
|
277
|
+
sig = "static void FUNNAME(VALUE self, NODE *cref) {"
|
278
|
+
cf.compiler.add_fun("#{sig}\n#{body}\n}", "toplevel_scope") # and return the function name
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
class Method < Base
|
283
|
+
def self.compile(outer, scope_node, def_fun, class_var, mid)
|
284
|
+
ensure_node_type(scope_node, :scope)
|
285
|
+
cf = self.new(outer.compiler, Scopes::Scope.new(scope_node.last[:tbl], []))
|
286
|
+
cf.instance_eval {
|
287
|
+
block_array = make_block(scope_node.last[:next]).last.dup # dup the block_array to allow modification
|
288
|
+
arg = block_array.shift
|
289
|
+
ba = nil
|
290
|
+
unless block_array.empty? || block_array.first.first != :block_arg
|
291
|
+
ba = block_array.shift
|
292
|
+
end
|
293
|
+
handle_method_args(arg, ba)
|
294
|
+
l "return #{comp([:block, block_array])};"
|
295
|
+
}
|
296
|
+
body = "#{cf.init_c_code}\n#{cf.get_lines}"
|
297
|
+
sig = "static VALUE FUNNAME(int meth_argc, VALUE *meth_argv, VALUE self) {"
|
298
|
+
fname = cf.compiler.add_fun("#{sig}\n#{body}\n}", "method")
|
299
|
+
if cf.need_cref
|
300
|
+
outer.instance_eval {
|
301
|
+
add_helper <<-EOC
|
302
|
+
static void def_only_once(ID mid) {
|
303
|
+
rb_raise(rb_eTypeError, "def for \\"%s\\" can only be used once", rb_id2name(mid));
|
304
|
+
}
|
305
|
+
EOC
|
306
|
+
l "if (#{cf.cref_global_var}) def_only_once(#{sym(mid)});"
|
307
|
+
l "#{cf.cref_global_var} = (VALUE)(#{get_cref});"
|
308
|
+
}
|
309
|
+
end
|
310
|
+
outer.l "#{def_fun}(#{class_var}, #{mid.to_s.to_c_strlit}, #{fname}, -1);"
|
311
|
+
"Qnil"
|
312
|
+
end
|
313
|
+
|
314
|
+
def return_allowed?
|
315
|
+
true
|
316
|
+
end
|
317
|
+
def comp_return(hash)
|
318
|
+
l "return #{comp(hash[:stts])};"
|
319
|
+
"Qnil"
|
320
|
+
end
|
321
|
+
|
322
|
+
def get_cref_impl
|
323
|
+
@cref_global_var ||= global_var("Qfalse")
|
324
|
+
"(RNODE(#{@cref_global_var}))"
|
325
|
+
end
|
326
|
+
attr_reader :cref_global_var
|
327
|
+
end
|
328
|
+
|
329
|
+
class Block < Base
|
330
|
+
def self.compile(outer, block_node, var_node)
|
331
|
+
ensure_node_type(block_node, :block)
|
332
|
+
cf = self.new(outer.compiler, outer.scope.new_dyna_scope)
|
333
|
+
cf.instance_eval {
|
334
|
+
if Array === var_node
|
335
|
+
if var_node.first == :masgn
|
336
|
+
dup_hash = var_node.last.dup
|
337
|
+
c_if("ruby_current_node->nd_state != 1") { # 1 is YIELD_FUNC_AVALUE
|
338
|
+
# do "svalue_to_mrhs"
|
339
|
+
c_if("bl_val == Qundef") {
|
340
|
+
l "bl_val = rb_ary_new2(0);"
|
341
|
+
}
|
342
|
+
c_else {
|
343
|
+
#if dup_hash[:head] # TODO
|
344
|
+
l "VALUE tmp = rb_check_array_type(bl_val);"
|
345
|
+
l "bl_val = (NIL_P(tmp) ? rb_ary_new3(1, bl_val) : tmp);"
|
346
|
+
#else
|
347
|
+
# l "bl_val = rb_ary_new3(1, bl_val);"
|
348
|
+
#end
|
349
|
+
}
|
350
|
+
}
|
351
|
+
dup_hash[:value] = "bl_val"
|
352
|
+
comp_masgn(dup_hash)
|
353
|
+
else
|
354
|
+
c_if("ruby_current_node->nd_state == 1") { # 1 is YIELD_FUNC_AVALUE
|
355
|
+
# do "avalue_to_svalue"
|
356
|
+
l "if (RARRAY(bl_val)->len == 0) bl_val = Qnil;"
|
357
|
+
l "else if (RARRAY(bl_val)->len == 1) bl_val = RARRAY(bl_val)->ptr[0];"
|
358
|
+
}
|
359
|
+
handle_assign(var_node, "bl_val")
|
360
|
+
end
|
361
|
+
end
|
362
|
+
l "block_redo:"
|
363
|
+
l "return #{comp_block(block_node.last)};"
|
364
|
+
}
|
365
|
+
body = "#{cf.init_c_code(outer)}\n#{cf.get_lines}"
|
366
|
+
sig = "static VALUE FUNNAME(VALUE bl_val, VALUE closure_ary, VALUE bl_self) {"
|
367
|
+
fname = cf.compiler.add_fun("#{sig}\n#{body}\n}", "block")
|
368
|
+
[fname, cf.need_closure_ptr]
|
369
|
+
end
|
370
|
+
|
371
|
+
def init_c_code(outer)
|
372
|
+
cb_c_code = closure_buid_c_code # must be called before the rest because it might change self or scope
|
373
|
+
outer.add_closure_need(:self) if need_self
|
374
|
+
outer.add_closure_need(:class) if need_class
|
375
|
+
outer.add_closure_need(:cref) if need_cref
|
376
|
+
res = []
|
377
|
+
res << "VALUE res;" if need_res
|
378
|
+
if need_closure_ptr
|
379
|
+
res << "VALUE *closure = RARRAY(closure_ary)->ptr;"
|
380
|
+
end
|
381
|
+
res << "VALUE self = (bl_self == Qundef ? closure[#{outer.closure_tbl.index(:self)}] : bl_self);" if need_self
|
382
|
+
res << "VALUE s_class = (bl_self == Qundef ? closure[#{outer.closure_tbl.index(:class)}] : ruby_class);" if need_class
|
383
|
+
if need_cref
|
384
|
+
# see #define Data_Get_Struct
|
385
|
+
res << "NODE *cref = (Check_Type(closure[#{outer.closure_tbl.index(:cref)}]," +
|
386
|
+
" T_DATA), (NODE*)DATA_PTR(closure[#{outer.closure_tbl.index(:cref)}]));"
|
387
|
+
end
|
388
|
+
res << "VALUE #{get_closure_ary_var};" if cb_c_code
|
389
|
+
res << "struct wrap the_wrap;" if need_wrap
|
390
|
+
res << scope.init_c_code
|
391
|
+
res << cb_c_code if cb_c_code
|
392
|
+
res << wrap_buid_c_code if need_wrap
|
393
|
+
res.compact.join("\n")
|
394
|
+
end
|
395
|
+
|
396
|
+
def break_allowed?(with_value)
|
397
|
+
super || !with_value
|
398
|
+
end
|
399
|
+
def comp_break(hash)
|
400
|
+
if in_while?(:break)
|
401
|
+
super
|
402
|
+
else
|
403
|
+
raise Ruby2CExtError::NotSupported, "break with a value is not supported in a block" if hash[:stts]
|
404
|
+
l "rb_iter_break();"
|
405
|
+
"Qnil"
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
def next_allowed?
|
410
|
+
true
|
411
|
+
end
|
412
|
+
def comp_next(hash)
|
413
|
+
if in_while?(:next)
|
414
|
+
super
|
415
|
+
else
|
416
|
+
l "return #{comp(hash[:stts])};"
|
417
|
+
"Qnil"
|
418
|
+
end
|
419
|
+
end
|
420
|
+
|
421
|
+
def redo_allowed?
|
422
|
+
true
|
423
|
+
end
|
424
|
+
def comp_redo(hash)
|
425
|
+
if in_while?(:redo)
|
426
|
+
super
|
427
|
+
else
|
428
|
+
l "goto block_redo;"
|
429
|
+
"Qnil"
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
def need_closure_ptr
|
434
|
+
scope.need_closure || need_self || need_class || need_cref
|
435
|
+
end
|
436
|
+
|
437
|
+
def get_cref_impl
|
438
|
+
"cref"
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
442
|
+
class Wrap < Base
|
443
|
+
|
444
|
+
def self.compile(outer, base_name, cflow_hash = nil)
|
445
|
+
cf = self.new(outer, cflow_hash)
|
446
|
+
cf.instance_eval {
|
447
|
+
if cf.cflow_hash
|
448
|
+
l "#{get_wrap_ptr}->state &= (1<<20)-1;" # set bits 20+ to 0
|
449
|
+
end
|
450
|
+
l "return #{yield cf};"
|
451
|
+
}
|
452
|
+
body = "#{cf.init_c_code}\n#{cf.get_lines}"
|
453
|
+
sig = "static VALUE FUNNAME(struct wrap *wrap_ptr) {"
|
454
|
+
outer.need_wrap = true
|
455
|
+
cf.compiler.add_fun("#{sig}\n#{body}\n}", base_name) # and return the function name
|
456
|
+
end
|
457
|
+
|
458
|
+
attr_reader :base_cfun, :outer_cfun, :cflow_hash
|
459
|
+
|
460
|
+
# the following attr_accessors from Base are redefined, to redirect to base_cfun
|
461
|
+
[:need_self, :need_cref, :need_class, :need_wrap].each { |a|
|
462
|
+
define_method(a) { base_cfun.send(a) }
|
463
|
+
asgn_sym = :"#{a}="
|
464
|
+
define_method(asgn_sym) { |arg| base_cfun.send(asgn_sym, arg) }
|
465
|
+
}
|
466
|
+
|
467
|
+
# cflow_hash can either be nil or a hash, if it is nil then break,
|
468
|
+
# next, redo and return are not possible from the wrap to
|
469
|
+
# outer_cfun.
|
470
|
+
#
|
471
|
+
# If cflow_hash is a hash then those are possible if outer_cfun
|
472
|
+
# allows them. For this to work only the bits 0-19 of
|
473
|
+
# wrap_ptr->state may be modified by code inside this wrap and
|
474
|
+
# after the call the code generated by Wrap.handle_wrap_cflow has
|
475
|
+
# to be inserted.
|
476
|
+
def initialize(outer_cfun, cflow_hash = nil)
|
477
|
+
@outer_cfun = outer_cfun
|
478
|
+
if Wrap === outer_cfun
|
479
|
+
@base_cfun = outer_cfun.base_cfun
|
480
|
+
else
|
481
|
+
@base_cfun = outer_cfun
|
482
|
+
end
|
483
|
+
@cflow_hash = cflow_hash
|
484
|
+
@compiler = base_cfun.compiler
|
485
|
+
@scope = Scopes::WrappedScope.new(base_cfun.scope)
|
486
|
+
@closure_tbl = base_cfun.closure_tbl
|
487
|
+
@lines = []
|
488
|
+
@while_stack = []
|
489
|
+
end
|
490
|
+
|
491
|
+
BREAK_FLAG = 1 << 20
|
492
|
+
NEXT_FLAG = 1 << 21
|
493
|
+
REDO_FLAG = 1 << 22
|
494
|
+
RETURN_FLAG = 1 << 23
|
495
|
+
|
496
|
+
def break_allowed?(with_value)
|
497
|
+
super || (cflow_hash && outer_cfun.break_allowed?(with_value))
|
498
|
+
end
|
499
|
+
def comp_break(hash)
|
500
|
+
if in_while?(:break)
|
501
|
+
super
|
502
|
+
elsif break_allowed?(hash[:stts])
|
503
|
+
@cflow_hash[:break] ||= hash[:stts]
|
504
|
+
l "#{get_wrap_ptr}->state |= #{BREAK_FLAG};"
|
505
|
+
l "return #{comp(hash[:stts])};"
|
506
|
+
"Qnil"
|
507
|
+
else
|
508
|
+
raise Ruby2CExtError::NotSupported, "break #{hash[:stts] ? "with a value " : ""}is not supported here"
|
509
|
+
end
|
510
|
+
end
|
511
|
+
|
512
|
+
def next_allowed?
|
513
|
+
super || (cflow_hash && outer_cfun.next_allowed?)
|
514
|
+
end
|
515
|
+
def comp_next(hash)
|
516
|
+
if in_while?(:next)
|
517
|
+
super
|
518
|
+
elsif next_allowed?
|
519
|
+
@cflow_hash[:next] = true
|
520
|
+
l "#{get_wrap_ptr}->state |= #{NEXT_FLAG};"
|
521
|
+
# hash[:stts] might be evaluated unnecessarily (because it
|
522
|
+
# is ignored in while loops), but oh well ...
|
523
|
+
l "return #{comp(hash[:stts])};"
|
524
|
+
"Qnil"
|
525
|
+
else
|
526
|
+
raise Ruby2CExtError::NotSupported, "next is not supported here"
|
527
|
+
end
|
528
|
+
end
|
529
|
+
|
530
|
+
def redo_allowed?
|
531
|
+
super || (cflow_hash && outer_cfun.redo_allowed?)
|
532
|
+
end
|
533
|
+
def comp_redo(hash)
|
534
|
+
if in_while?(:redo)
|
535
|
+
super
|
536
|
+
elsif redo_allowed?
|
537
|
+
@cflow_hash[:redo] = true
|
538
|
+
l "#{get_wrap_ptr}->state |= #{REDO_FLAG};"
|
539
|
+
l "return Qnil;"
|
540
|
+
"Qnil"
|
541
|
+
else
|
542
|
+
raise Ruby2CExtError::NotSupported, "redo is not supported here"
|
543
|
+
end
|
544
|
+
end
|
545
|
+
|
546
|
+
def return_allowed?
|
547
|
+
cflow_hash && outer_cfun.return_allowed?
|
548
|
+
end
|
549
|
+
def comp_return(hash)
|
550
|
+
if return_allowed?
|
551
|
+
@cflow_hash[:return] = true
|
552
|
+
l "#{get_wrap_ptr}->state |= #{RETURN_FLAG};"
|
553
|
+
l "return #{comp(hash[:stts])};"
|
554
|
+
"Qnil"
|
555
|
+
else
|
556
|
+
raise Ruby2CExtError::NotSupported, "return is not supported here"
|
557
|
+
end
|
558
|
+
end
|
559
|
+
|
560
|
+
# the result of the call to the wrap cfun is expected in "res"
|
561
|
+
def self.handle_wrap_cflow(cfun, cflow_hash)
|
562
|
+
cfun.instance_eval {
|
563
|
+
if cflow_hash.has_key?(:break)
|
564
|
+
c_if("#{get_wrap_ptr}->state & #{BREAK_FLAG}") {
|
565
|
+
l comp_break(:stts => (cflow_hash[:break] ? "res" : false))
|
566
|
+
}
|
567
|
+
end
|
568
|
+
if cflow_hash.has_key?(:next)
|
569
|
+
c_if("#{get_wrap_ptr}->state & #{NEXT_FLAG}") {
|
570
|
+
l comp_next(:stts => "res")
|
571
|
+
}
|
572
|
+
end
|
573
|
+
if cflow_hash.has_key?(:redo)
|
574
|
+
c_if("#{get_wrap_ptr}->state & #{REDO_FLAG}") {
|
575
|
+
l comp_redo({})
|
576
|
+
}
|
577
|
+
end
|
578
|
+
if cflow_hash.has_key?(:return)
|
579
|
+
c_if("#{get_wrap_ptr}->state & #{RETURN_FLAG}") {
|
580
|
+
l comp_return(:stts => "res")
|
581
|
+
}
|
582
|
+
end
|
583
|
+
}
|
584
|
+
end
|
585
|
+
|
586
|
+
def get_self
|
587
|
+
self.need_self = true
|
588
|
+
"(wrap_ptr->self)"
|
589
|
+
end
|
590
|
+
def get_cref_impl
|
591
|
+
"(wrap_ptr->cref)"
|
592
|
+
end
|
593
|
+
def get_class
|
594
|
+
self.need_class = true
|
595
|
+
"(wrap_ptr->s_class)"
|
596
|
+
end
|
597
|
+
|
598
|
+
def get_closure_ary_var
|
599
|
+
"(wrap_ptr->my_closure_ary)"
|
600
|
+
end
|
601
|
+
|
602
|
+
def get_wrap_ptr
|
603
|
+
"wrap_ptr"
|
604
|
+
end
|
605
|
+
|
606
|
+
[:closure_buid_c_code, :wrap_buid_c_code, :need_closure_ptr].each { |m|
|
607
|
+
define_method(m) {
|
608
|
+
raise Ruby2CExtError::Bug, "the method #{m} may not be called for an instance of Wrap"
|
609
|
+
}
|
610
|
+
}
|
611
|
+
|
612
|
+
def init_c_code
|
613
|
+
res = []
|
614
|
+
res << "VALUE res;" if need_res
|
615
|
+
res.compact.join("\n")
|
616
|
+
end
|
617
|
+
end
|
618
|
+
|
619
|
+
end
|
620
|
+
|
621
|
+
end
|