rlang 0.4.0 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +12 -2
- data/docs/RlangManual.md +43 -30
- data/lib/rlang/lib.rb +2 -1
- data/lib/rlang/lib/malloc.rb +2 -2
- data/lib/rlang/lib/memory.rb +18 -0
- data/lib/rlang/lib/string.rb +30 -0
- data/lib/rlang/parser.rb +263 -206
- data/lib/rlang/parser/{wattr.rb → attr.rb} +41 -33
- data/lib/rlang/parser/data.rb +3 -0
- data/lib/rlang/parser/ivar.rb +16 -7
- data/lib/rlang/parser/klass.rb +6 -3
- data/lib/rlang/parser/method.rb +17 -9
- data/lib/rlang/parser/wgenerator.rb +168 -47
- data/lib/rlang/parser/wnode.rb +96 -65
- data/lib/rlang/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 45f6d0c347cfcf901dcd935e2b979098759f2c9b435ff5d900f77a830d5b2101
|
4
|
+
data.tar.gz: 88c0e8f7b79f3cb784f0dd0c85fc10b68427958893f3401998f9866fa9a9b50a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d6f959bc61a5f4f9548455244662a8b3d9482d9e4670fdb66fd9d5ea1101e062bb93d5fbaffabac5e24852c85058130f92680e76f7d01bbe1745914bee25640b
|
7
|
+
data.tar.gz: 30e4ecce3b01f850fd9515ab614e75d3cd915545b172bab7174872d0d5fab4875a96ade32dac2f1b7a95a3ca0f58ef0ef891544b926ba0e6c23fa38c4cb6fca2
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -2,11 +2,13 @@
|
|
2
2
|
|
3
3
|
Rlang is meant to generate fast and uncluttered [WebAssembly](https://webassembly.org) code from the comfort of the Ruby language.
|
4
4
|
|
5
|
-
Rlang is two things: 1) a subset of the Ruby language and 2) a **compiler** transforming this Ruby subset in a valid, fully runnable and native WebAssembly module.
|
5
|
+
Rlang is actually two things: 1) a supported subset of the Ruby language and 2) a **compiler** transforming this Ruby subset in a valid, fully runnable and native WebAssembly module.
|
6
6
|
|
7
7
|
Rlang can be seen as a foundational language that can help you quickly develop and debug high performance WebAssembly modules. For the rationale behind the creation of Rlang see [below](#why-rlang).
|
8
8
|
|
9
|
-
|
9
|
+
This is still a young project but Rlang has already been successfully tested with some real code such as the dynamic memory allocator provided with the Rlang library. It will keep mproving over time, always with the goal of generating crisp and uncluttered WAT code.
|
10
|
+
|
11
|
+
If you want to help with Rlang see [How you can help](#how-you-can-help).
|
10
12
|
|
11
13
|
## Dependencies
|
12
14
|
|
@@ -77,6 +79,14 @@ Yes I hear you: Rlang is already the name of the R language so why use that name
|
|
77
79
|
|
78
80
|
The name **Rlang** itself is a tribute to [Slang](http://wiki.squeak.org/squeak/slang), a subset of the Smalltalk language that can directly translate to C. It was created in 1995 to bootstrap the development of the virtual machine of Squeak, an open-source Smalltalk programming system. I highly encourage anyone interested in the history and the technology of virtual machines to read both the [Back to the future](http://www.vpri.org/pdf/tr1997001_backto.pdf) article as well as the now legendary [Blue Book](http://stephane.ducasse.free.fr/FreeBooks/BlueBook/Bluebook.pdf) explaining how the Smalltalk-80 Virtual Machine and Language were designed in the 80s. I would actually go as far as saying that you don't really know what (virtual) machines are until you have read this book :-)
|
79
81
|
|
82
|
+
## How you can help
|
83
|
+
* Test Rlang installation on different platforms (Linux, Mac, Windows) and different Ruby versions (Ruby 2.2 and above)
|
84
|
+
* Write some real Rlang application code, compile it and test it.
|
85
|
+
* Improve the documentation if you find it unclear or want to enrich it with further examples and tips
|
86
|
+
* Growing the Rlang library that is still quite basic. For instance you can write the String class or the Array class,...). Just start with the the most popular methods in those classes.
|
87
|
+
* If you feel like digging into the Rlang parser and Wasm code generator you can extend it with the support of Floats for instance
|
88
|
+
* And of course writing tests for the parts of the code not yet covered is always welcome (running "rake test" also generates code coverage information)
|
89
|
+
|
80
90
|
## Credits
|
81
91
|
A big thanks to:
|
82
92
|
* [@whitequark](https://github.com/whitequark) for a fantastic [Ruby parser](https://github.com/whitequark/parser)
|
data/docs/RlangManual.md
CHANGED
@@ -71,46 +71,45 @@ This short piece of code shows several interesting points:
|
|
71
71
|
1. In `MyClass::take_one` you can see that Rlang also supports convenient syntactic sugar like `if` as a modifier or combined operation and assignment as in Ruby (here the `-=` operator)
|
72
72
|
|
73
73
|
### Class attributes and instance variables
|
74
|
-
Rlang support both the use of class attributes and instance variables. Class attribute declaration is happening through the `
|
75
|
-
1. Define the corresponding accessors both getter and setter (like `attr_accessor` does in Ruby)
|
76
|
-
2. Define the corresponding instance variable
|
74
|
+
Rlang support both the use of class attributes and instance variables. Class attribute declaration is happening through the `attr_accessor`, `attr_reader` or `attr_writer` directives as in plain Ruby. It actually defines a couple of things for you:
|
77
75
|
|
78
|
-
Here is an example showing the definition of a single attribute
|
76
|
+
Here is an example showing the definition of a single attribute both in read and write mode.
|
79
77
|
```ruby
|
80
78
|
class Square
|
81
|
-
|
79
|
+
attr_accessor :side
|
82
80
|
|
83
81
|
def area
|
84
|
-
|
82
|
+
@side * @side
|
85
83
|
end
|
86
84
|
end
|
87
85
|
```
|
88
|
-
Later in your code you could
|
86
|
+
Later in your code you could
|
89
87
|
|
90
88
|
```ruby
|
91
89
|
class Test
|
92
|
-
@@square = Square.new
|
93
90
|
|
94
91
|
def self.my_method
|
95
|
-
|
96
|
-
|
92
|
+
s = Square.new
|
93
|
+
s.side = 10
|
94
|
+
area = square.area
|
95
|
+
perimeter = s.side * 4
|
97
96
|
end
|
98
97
|
end
|
99
98
|
```
|
100
99
|
|
101
|
-
The code is pretty straightforward: a new square instance is created
|
100
|
+
The code is pretty straightforward: a new square instance is created, its side attribute is set to 10. As you would expect, the call to the Square#area method returns 100 and the perimeter is 40.
|
102
101
|
|
103
102
|
### Class attribute type
|
104
103
|
In the example above the `side` attribute is implicitely using the `:I32` (long integer) WebAssembly type. It's the default Rlang type. Assuming you want to manage big squares, you'd have to use `:I64` (double integer) like this for the `side` attribute and also instruct Rlang that the return value of area is also `:I64` (more on this later).
|
105
104
|
|
106
105
|
```ruby
|
107
106
|
class Square
|
108
|
-
|
109
|
-
|
107
|
+
attr_accessor :side
|
108
|
+
attr_type side: :I64
|
110
109
|
|
111
110
|
def area
|
112
111
|
result :I64
|
113
|
-
|
112
|
+
@side * @side
|
114
113
|
end
|
115
114
|
end
|
116
115
|
```
|
@@ -119,19 +118,26 @@ end
|
|
119
118
|
Starting with version 0.4.0, Rlang is equipped with a dynamic memory allocator (see [Rlang library](#the-rlang-library) section). It is therefore capable of allocating objects in a dynamic way at *runtime*. Prior versions were only capable of allocating objects statically at *compile* time.
|
120
119
|
|
121
120
|
### Static objects
|
122
|
-
|
121
|
+
A statement like `@@square = Square.new` appearing in the body of a class definition result in a portion of the WebAssembly memory being statically allocated at compile time by Rlang. The `@@square` class variable points to that particular memory location. Similarly you can also statically instantiate and store an object in a global variable or in a constant like this:
|
123
122
|
|
124
123
|
```ruby
|
125
|
-
|
126
|
-
|
124
|
+
class Test
|
125
|
+
@@square = Square.new
|
126
|
+
SQUARE = Square.new
|
127
|
+
$SQUARE = Square.new
|
128
|
+
|
129
|
+
# Your methods below...
|
130
|
+
# ...
|
131
|
+
end
|
127
132
|
```
|
128
133
|
|
134
|
+
**IMPORTANT NOTE**: in the current version of Rlang the new method call used to allocate the object sape doesn't do any initialization. That's why the new method in this context (class body or top level) doesn't accept any parameter
|
135
|
+
|
129
136
|
### Dynamic objects
|
130
137
|
At any point in the body of method you can dynamically instantiate a new object. Here is an exemple:
|
131
138
|
|
132
139
|
```ruby
|
133
140
|
class Cube
|
134
|
-
wattr :x, :y, :z
|
135
141
|
|
136
142
|
def initialize(x, y, z)
|
137
143
|
@x = x; @y = y; @z = z
|
@@ -141,20 +147,26 @@ class Cube
|
|
141
147
|
@x * @y * @z
|
142
148
|
end
|
143
149
|
end
|
150
|
+
```
|
151
|
+
In this example the `Cube` method uses 3 instance variables `@x`, `@y`, `@z`.
|
152
|
+
|
153
|
+
Whenever you define a class, Rlang automatically generate the MyClass._size_ class method. Calling this method will tell you how many bytes MyClass objects uses in memory. As an example, a call to `Cube._size_` would return 12 as the 3 instance variables of Cube are of type `I32` using 4 bytes each in memory.
|
144
154
|
|
155
|
+
### Garbage collection
|
156
|
+
In its current version, Rlang doesn't come with a garbage collector. All dynamically allocated objects must eventually be freed explicitely using the `Object.free` method in your code when objects are no longer needed.
|
157
|
+
|
158
|
+
Here is an example building on the Cube class that we just defined:
|
159
|
+
```ruby
|
145
160
|
class Main
|
146
161
|
def self.run
|
162
|
+
# Dynamic allocation of a new Cube
|
147
163
|
cube = Cube.new(10, 20, 30)
|
148
|
-
v = cube.volume
|
149
|
-
# ... Do what
|
164
|
+
v = cube.volume
|
165
|
+
# ... Do what ever you have to do...
|
150
166
|
Object.free(cube)
|
151
167
|
end
|
152
168
|
end
|
153
169
|
```
|
154
|
-
In this example the `Cube` method uses 3 instance variables `@x, @y, @z`. Those instance variables can be accessed through the usual attribute accessors like `Cube#x` and `Cube#x=`. In your code you can also use the `_size_` class method to know how much bytes an object consumes in meory. Here a call to `Cube._size_` would return 12 as each instance variable is an `I32` using 4 bytes each.
|
155
|
-
|
156
|
-
### (Lack of) Garbage collection
|
157
|
-
In its current version, Rlang doesn't come with a garbage collector. So all dynamically allocated objects must be eventually free'd explicitely using the `Object.free` method in your code when objects are no longer needed.
|
158
170
|
|
159
171
|
## Methods
|
160
172
|
Methods in Rlang are defined as you would normally do in Ruby by using the `def` reserved keyword. They can be either class or instance methods.
|
@@ -199,7 +211,7 @@ Note: in this directive `:method_name` symbol starts with a `#` characters if it
|
|
199
211
|
For an example see the [test_def_result_type_declaration.rb](https://github.com/ljulliar/rlang/blob/master/test/rlang_test_files/test_def_result_type_declaration.rb), a Rlang file that is part of the Rlang test suite.
|
200
212
|
|
201
213
|
### Local variables
|
202
|
-
Local variable used in a method body doesn't have to be declared. They are auto-vivified the first time you assign a value to it. In some cases though, you may have to use the `local` directive as in the example below to explicitely state the type of a local variable.
|
214
|
+
Local variable used in a method body doesn't have to be explicitely declared. They are auto-vivified the first time you assign a value to it. In some cases though, you may have to use the `local` directive as in the example below to explicitely state the type of a local variable.
|
203
215
|
|
204
216
|
```ruby
|
205
217
|
def self.m_local_var(arg1)
|
@@ -209,7 +221,7 @@ def self.m_local_var(arg1)
|
|
209
221
|
# ....
|
210
222
|
end
|
211
223
|
```
|
212
|
-
|
224
|
+
The `local` directive above instructs the compiler that `lvar` is of type `:I64` and the local variable mysquare is of type `Square`. Without it `lvar` would have been auto-vivified with the Wasm default type or `:I32`.
|
213
225
|
|
214
226
|
### Exporting a method
|
215
227
|
In WebAssembly, you can make functions visible to the outside world by declaring them in the export section. To achieve a similar result in Rlang, you can use the `export` keyword right before a method definition.
|
@@ -271,7 +283,7 @@ end
|
|
271
283
|
|
272
284
|
The first line will auto-vivify the `@@cvar` class variable as type `:I64`.
|
273
285
|
|
274
|
-
The second example turns the value `123876` into a pointer to a `Square` object.
|
286
|
+
The second example turns the value `123876` into a pointer to a `Square` object. It's pretty much like turning an integer into a pointer to a memory address in C.
|
275
287
|
|
276
288
|
For `:I32` and `:I64` type cast you can also use the following shortcuts `100.to_I64` or `100.to_I32`
|
277
289
|
|
@@ -424,10 +436,10 @@ Here is an example:
|
|
424
436
|
class MyOtherClass
|
425
437
|
def self.x10_square(arg1)
|
426
438
|
arg1 *= 10
|
427
|
-
|
439
|
+
wasm wat: '(i32.mul
|
428
440
|
(local.get $arg1)
|
429
441
|
(local.get $arg1))',
|
430
|
-
|
442
|
+
ruby: 'arg1 ** 2'
|
431
443
|
end
|
432
444
|
end
|
433
445
|
```
|
@@ -449,7 +461,8 @@ For now, the Rlang library is very modest and containoffers the following classe
|
|
449
461
|
* Malloc.malloc(nbytes) : dynamically allocates nbytes of memory and return address of the allocated memory space or -1 if it fails
|
450
462
|
* Malloc.free(address) : frees the block of memory at the given address
|
451
463
|
* **Object** class: provides a couple of object management method. Use `Object.free` to free an object.
|
464
|
+
* **String** class: string are initialized in Rlang by using a string literal like `"This is my string"`. String methods supported are very minimal for the moment. See [rlang/lib/String.rb](https://github.com/ljulliar/rlang/blob/master/lib/rlang/lib/string.rb). Feel free to improve.
|
452
465
|
|
453
|
-
As a side note, the dynamic memory allocator
|
466
|
+
As a side note, the dynamic memory allocator currently used in Rlang is shamelessly adapted from the example provided in the famous Kernigan & Richie C book 2nd edition. Take a look at the [C version](https://github.com/ljulliar/rlang/blob/master/lib/rlang/lib/malloc.c) and see how easy it is to rewrite it in [Rlang](https://github.com/ljulliar/rlang/blob/master/lib/rlang/lib/malloc.rb).
|
454
467
|
|
455
468
|
That's it! Enjoy Rlang and, as always, feedback and contributions are welcome.
|
data/lib/rlang/lib.rb
CHANGED
data/lib/rlang/lib/malloc.rb
CHANGED
data/lib/rlang/lib/memory.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'rlang/lib/type'
|
2
|
+
|
1
3
|
class Memory
|
2
4
|
|
3
5
|
def self.size
|
@@ -7,5 +9,21 @@ class Memory
|
|
7
9
|
def self.grow(delta)
|
8
10
|
inline wat: '(memory.grow (local.get $delta))'
|
9
11
|
end
|
12
|
+
|
13
|
+
# Copy memory from source to destination address
|
14
|
+
# TODO: optimize this method using 64 bits copy
|
15
|
+
# first then 32 bits, then 16, then 8 bits
|
16
|
+
def self.copy(src, dest, size)
|
17
|
+
arg src: :I32, dest: :I32
|
18
|
+
result :none
|
19
|
+
idx = 0
|
20
|
+
while idx < size
|
21
|
+
inline wat: '(i32.store8
|
22
|
+
(i32.add (local.get $dest) (local.get $idx))
|
23
|
+
(i32.load8_u (i32.add (local.get $src) (local.get $idx)))
|
24
|
+
)', wtype: :none
|
25
|
+
idx += 1
|
26
|
+
end
|
27
|
+
end
|
10
28
|
|
11
29
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require_relative './malloc'
|
2
|
+
|
3
|
+
class String
|
4
|
+
attr_reader :length, :ptr
|
5
|
+
|
6
|
+
# str is a pointer to a memory
|
7
|
+
# location of type :String allocated either
|
8
|
+
# statically through a string litteral or
|
9
|
+
# through a dynamic allocation
|
10
|
+
# ptr is a simple memory address of type
|
11
|
+
# :I32 (see it as the equivalent of a
|
12
|
+
# char * in C)
|
13
|
+
def initialize(ptr, length)
|
14
|
+
@ptr = ptr
|
15
|
+
@length = length
|
16
|
+
end
|
17
|
+
|
18
|
+
def +(stg)
|
19
|
+
arg stg: :String
|
20
|
+
result :String
|
21
|
+
new_length = self.length + stg.length
|
22
|
+
# allocate space for concatenated string
|
23
|
+
new_ptr = Malloc.malloc(new_length)
|
24
|
+
Memory.copy(self.ptr, new_ptr, self.length)
|
25
|
+
Memory.copy(stg.ptr, new_ptr + self.length, stg.length)
|
26
|
+
# Create new object string
|
27
|
+
String.new(new_ptr, new_length)
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
data/lib/rlang/parser.rb
CHANGED
@@ -213,6 +213,9 @@ module Rlang::Parser
|
|
213
213
|
when :false
|
214
214
|
wn = parse_false(node, wnode, keep_eval)
|
215
215
|
|
216
|
+
when :str
|
217
|
+
wn = parse_string(node, wnode, keep_eval)
|
218
|
+
|
216
219
|
else
|
217
220
|
raise "Unknown node type: #{node.type} => #{node}"
|
218
221
|
end
|
@@ -262,13 +265,14 @@ module Rlang::Parser
|
|
262
265
|
parse_node(body_node, wn_class) if body_node
|
263
266
|
|
264
267
|
# We finished parsing the class body so
|
265
|
-
# 1)
|
266
|
-
# 2) generate
|
268
|
+
# 1) postprocess instance variables
|
269
|
+
# 2) generate wnodes for the new/initialize function
|
270
|
+
# 2) generate wnodes for attribute accessors
|
271
|
+
@wgenerator.ivars_setup(wn_class)
|
267
272
|
@wgenerator.def_initialize(wn_class) # generate **BEFORE** new
|
268
273
|
@wgenerator.def_new(wn_class)
|
269
|
-
@wgenerator.
|
270
|
-
|
271
|
-
return wn_class
|
274
|
+
@wgenerator.def_attr(wn_class)
|
275
|
+
wn_class
|
272
276
|
end
|
273
277
|
|
274
278
|
# TODO: the code for op_asgn is quite murky but I thought
|
@@ -320,9 +324,10 @@ module Rlang::Parser
|
|
320
324
|
# (int 1)))
|
321
325
|
#
|
322
326
|
def parse_op_asgn(node, wnode, keep_eval)
|
323
|
-
|
327
|
+
op_asgn_type = node.children.first.type
|
328
|
+
logger.debug "op_asgn on #{op_asgn_type} / wnode: #{wnode}, keep_eval: #{keep_eval}"
|
324
329
|
|
325
|
-
case
|
330
|
+
case op_asgn_type
|
326
331
|
# Global variable case
|
327
332
|
when :gvasgn
|
328
333
|
var_asgn_node, op, exp_node = *node.children
|
@@ -336,7 +341,7 @@ module Rlang::Parser
|
|
336
341
|
raise "Unknown global variable #{var_name}" unless gvar
|
337
342
|
|
338
343
|
# Create the operator node (infer operator type from variable)
|
339
|
-
wn_op = @wgenerator.
|
344
|
+
wn_op = @wgenerator.send_method(wn_var_set, op, gvar.wtype)
|
340
345
|
# Create the var getter node as a child of operator node
|
341
346
|
wn_var_get = @wgenerator.gvar(wn_op, gvar)
|
342
347
|
|
@@ -353,7 +358,7 @@ module Rlang::Parser
|
|
353
358
|
raise "Unknown class variable #{var_name}" unless cvar
|
354
359
|
|
355
360
|
# Create the operator node (infer operator type from variable)
|
356
|
-
wn_op = @wgenerator.
|
361
|
+
wn_op = @wgenerator.send_method(wn_var_set, op, cvar.wtype)
|
357
362
|
# Create the var getter node as a child of operator node
|
358
363
|
wn_var_get = @wgenerator.cvar(wn_op, cvar)
|
359
364
|
|
@@ -370,7 +375,7 @@ module Rlang::Parser
|
|
370
375
|
raise "Unknown local variable #{var_name}" unless lvar
|
371
376
|
|
372
377
|
# Create the operator node (infer operator type from variable)
|
373
|
-
wn_op = @wgenerator.
|
378
|
+
wn_op = @wgenerator.send_method(wn_var_set, op, lvar.wtype)
|
374
379
|
# Create the var getter node as a child of operator node
|
375
380
|
wn_var_get = @wgenerator.lvar(wn_op, lvar)
|
376
381
|
|
@@ -381,36 +386,31 @@ module Rlang::Parser
|
|
381
386
|
# s(:op_asgn,
|
382
387
|
# s(:ivasgn, :@stack_ptr), :-, s(:lvar, :nbytes))
|
383
388
|
when :ivasgn
|
384
|
-
|
389
|
+
raise "Instance variable can only be accessed in instance method scope" \
|
390
|
+
unless wnode.in_instance_method_scope?
|
391
|
+
var_asgn_node, operator, exp_node = *node.children
|
385
392
|
var_name = var_asgn_node.children.last
|
386
393
|
|
387
394
|
# To op_asgn to work, ivar must already be declared
|
388
|
-
|
389
|
-
raise "Unknown instance variable #{var_name}" unless
|
390
|
-
|
391
|
-
# Instance variable op_asgn case is actually like
|
392
|
-
# the getter/setter case below where the receiver
|
393
|
-
# wnode is self
|
394
|
-
wn_recv = parse_self(node, wnode)
|
395
|
+
ivar = wnode.find_ivar(var_name)
|
396
|
+
raise "Unknown instance variable #{var_name}" unless ivar
|
395
397
|
|
396
398
|
# Create the top level variable setter node
|
397
|
-
wn_var_set = @wgenerator.ivasgn(wnode,
|
399
|
+
wn_var_set = @wgenerator.ivasgn(wnode, ivar)
|
398
400
|
|
399
401
|
# Second argument of the setter is the operator wnode
|
400
|
-
# Create it with wtype
|
401
|
-
# with the operands call later on
|
402
|
-
wn_op = @wgenerator.
|
402
|
+
# Create it with wtype of receiver by default. We may
|
403
|
+
# change that wtype with the operands call later on
|
404
|
+
wn_op = @wgenerator.send_method(wn_var_set, operator, ivar.wtype)
|
403
405
|
|
404
|
-
# now create the getter node as
|
406
|
+
# now create the getter node as a child of the
|
405
407
|
# operator
|
406
|
-
wn_var_get = @wgenerator.ivar(
|
408
|
+
wn_var_get = @wgenerator.ivar(wn_op, ivar)
|
407
409
|
|
408
|
-
#
|
409
|
-
#
|
410
|
-
|
411
|
-
|
412
|
-
#@wgenerator.call(wnode, wn_recv.wtype.name, "#{method_name}", :instance)
|
413
|
-
end
|
410
|
+
# The wasm code for the ivar setter wnode doesn't leave
|
411
|
+
# any value on stack (store instruction). So if the last
|
412
|
+
# evaluated value must be kept then load the ivar again
|
413
|
+
@wgenerator.ivar(wnode, ivar) if keep_eval
|
414
414
|
|
415
415
|
# setter/getter case
|
416
416
|
# Example (setter/getter)
|
@@ -437,9 +437,9 @@ module Rlang::Parser
|
|
437
437
|
wn_recv.reparent_to(wn_var_set)
|
438
438
|
|
439
439
|
# Second argument of the setter is the operator wnode
|
440
|
-
# Create it with wtype
|
441
|
-
# with the operands call later on
|
442
|
-
wn_op = @wgenerator.
|
440
|
+
# Create it with wtype of receiver by default. We may
|
441
|
+
# change that wtype with the operands call later on
|
442
|
+
wn_op = @wgenerator.send_method(wn_var_set, op, wn_recv.wtype)
|
443
443
|
|
444
444
|
# Parsing the send node will create the getter wnode
|
445
445
|
# this is the first argument of the operator wnode,
|
@@ -450,7 +450,7 @@ module Rlang::Parser
|
|
450
450
|
|
451
451
|
# If the setter returns something and last evaluated value
|
452
452
|
# must be ignored then drop it
|
453
|
-
unless keep_eval
|
453
|
+
unless (keep_eval || wn_var_set.wtype.blank?)
|
454
454
|
@wgenerator.drop(wnode)
|
455
455
|
#@wgenerator.call(wnode, wn_recv.wtype.name, "#{method_name}", :instance)
|
456
456
|
end
|
@@ -476,23 +476,54 @@ module Rlang::Parser
|
|
476
476
|
# (int 2000))
|
477
477
|
def parse_casgn(node, wnode, keep_eval)
|
478
478
|
class_name_node, constant_name, exp_node = *node.children
|
479
|
-
raise "dynamic constant assignment" unless wnode.in_class_scope?
|
480
|
-
raise "constant initialization can only take a number" unless exp_node.type == :int
|
481
|
-
|
479
|
+
# raise "dynamic constant assignment" unless wnode.in_class_scope?
|
482
480
|
unless class_name_node.nil?
|
483
481
|
raise "constant assignment with class path not supported (got #{class_name_node})"
|
484
482
|
end
|
485
|
-
if wnode.find_const(constant_name)
|
486
|
-
raise "constant #{constant_name} already initialized"
|
487
|
-
end
|
488
483
|
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
484
|
+
if wnode.in_method_scope?
|
485
|
+
# if exp_node is nil then this is the form of
|
486
|
+
# :casgn that comes from op_asgn
|
487
|
+
if exp_node
|
488
|
+
if const.nil?
|
489
|
+
# first constant occurence
|
490
|
+
# type cast the constant to the wtype of the expression
|
491
|
+
const = wnode.create_const(constant_name, nil, 0, WType::DEFAULT)
|
492
|
+
wn_casgn = @wgenerator.casgn(wnode, const)
|
493
|
+
wn_exp = parse_node(exp_node, wn_casgn)
|
494
|
+
const.wtype = wn_exp.wtype
|
495
|
+
else
|
496
|
+
# if const already exists then type cast the
|
497
|
+
# expression to the wtype of the existing const
|
498
|
+
wn_casgn = @wgenerator.casgn(wnode, const)
|
499
|
+
wn_exp = parse_node(exp_node, wn_casgn)
|
500
|
+
@wgenerator.cast(wn_exp, const.wtype, false)
|
501
|
+
logger.warning "Already initialized constant #{const.name}"
|
502
|
+
end
|
503
|
+
else
|
504
|
+
raise "Constant #{const_name} not declared before" unless const
|
505
|
+
wn_casgn = @wgenerator.casgn(wnode, const)
|
506
|
+
end
|
507
|
+
# to mimic Ruby push the constant value on stack if needed
|
508
|
+
@wgenerator.const(wnode, const) if keep_eval
|
509
|
+
return wn_casgn
|
493
510
|
|
494
|
-
|
495
|
-
|
511
|
+
elsif wnode.in_class_scope? || wnode.in_root_scope?
|
512
|
+
# If we are in class scope
|
513
|
+
# then it is a class variable initialization
|
514
|
+
# Parse the expression node to see if it's a ixx.const
|
515
|
+
# in the end but get rid of it then because we are not
|
516
|
+
# executing this code. Just statically initiliazing the
|
517
|
+
# const with the value
|
518
|
+
wn_exp = parse_node(exp_node, wnode)
|
519
|
+
raise "Constant initializer initializer can only be an int or a constant/class (got #{wn_exp}" \
|
520
|
+
unless wn_exp.const?
|
521
|
+
const = wnode.create_const(constant_name, nil, wn_exp.wargs[:value], wn_exp.wtype)
|
522
|
+
wnode.remove_child(wn_exp)
|
523
|
+
logger.debug "Constant #{constant_name} initialized with value #{const.value} and wtype #{const.wtype}"
|
524
|
+
else
|
525
|
+
raise "Constant can only be defined in method or class scope"
|
526
|
+
end
|
496
527
|
end
|
497
528
|
|
498
529
|
# Example
|
@@ -537,7 +568,7 @@ module Rlang::Parser
|
|
537
568
|
# to mimic Ruby push the variable value on stack if needed
|
538
569
|
@wgenerator.gvar(wnode, gvar) if keep_eval
|
539
570
|
return wn_gvasgn
|
540
|
-
|
571
|
+
elsif true #wnode.in_class_scope?
|
541
572
|
# If we are at root or in class scope
|
542
573
|
# then it is a global variable initialization
|
543
574
|
raise "Global op_asgn can only happen in method scope" unless exp_node
|
@@ -550,7 +581,7 @@ module Rlang::Parser
|
|
550
581
|
# Then remove the generated wnode because it is not for
|
551
582
|
# execution. It is just to get the init value
|
552
583
|
wn_exp = parse_node(exp_node, wnode)
|
553
|
-
raise "Global initializer can only be a
|
584
|
+
raise "Global initializer can only be a int or a constant/class (got #{wn_exp})" \
|
554
585
|
unless wn_exp.const?
|
555
586
|
wnode.remove_child(wn_exp)
|
556
587
|
if gvar
|
@@ -560,6 +591,8 @@ module Rlang::Parser
|
|
560
591
|
end
|
561
592
|
# Do not export global for now
|
562
593
|
#gvar.export! if self.config[:export_all]
|
594
|
+
else
|
595
|
+
raise "Global can only be defined in method or class scope"
|
563
596
|
end
|
564
597
|
end
|
565
598
|
|
@@ -571,41 +604,35 @@ module Rlang::Parser
|
|
571
604
|
def parse_ivasgn(node, wnode, keep_eval)
|
572
605
|
iv_name, exp_node = *node.children
|
573
606
|
|
574
|
-
raise "Instance variable #{iv_name} can only
|
575
|
-
unless wnode.
|
576
|
-
|
577
|
-
unless (wattr = wnode.find_ivar(iv_name))
|
578
|
-
# first ivar occurence, create it
|
579
|
-
wattr = wnode.create_ivar(iv_name)
|
580
|
-
new_ivar = true
|
581
|
-
end
|
582
|
-
|
583
|
-
# ivar assignment is like calling the corresponding
|
584
|
-
# setter on self. So create self wnode first
|
585
|
-
wn_recv = parse_self(node, wnode)
|
586
|
-
wn_ivasgn = @wgenerator.ivasgn(wnode, wn_recv, wattr)
|
607
|
+
raise "Instance variable #{iv_name} can only used in instance method scope" \
|
608
|
+
unless wnode.in_instance_method_scope?
|
587
609
|
|
588
|
-
|
589
|
-
wn_exp = parse_node(exp_node, wn_ivasgn)
|
590
|
-
|
591
|
-
if new_ivar
|
592
|
-
# type cast the wattr and its ivar to the wtype
|
593
|
-
# of the expression as this its first occurence
|
594
|
-
logger.debug "Setting new ivar #{wattr.name} wtype to #{wn_exp.wtype.name}"
|
595
|
-
wattr.wtype = wn_exp.wtype
|
596
|
-
else
|
610
|
+
if (ivar = wnode.find_ivar(iv_name))
|
597
611
|
# if ivar already exists then type cast the
|
598
612
|
# expression to the wtype of the existing ivar
|
599
|
-
|
600
|
-
|
613
|
+
wn_ivasgn = @wgenerator.ivasgn(wnode, ivar)
|
614
|
+
wn_exp = parse_node(exp_node, wn_ivasgn)
|
615
|
+
logger.debug "Casting exp. wtype #{wn_exp.wtype} to existing ivar #{ivar.name} wtype #{ivar.wtype}"
|
616
|
+
@wgenerator.cast(wn_exp, ivar.wtype, false)
|
617
|
+
else
|
618
|
+
# first ivar occurence, create it
|
619
|
+
ivar = wnode.create_ivar(iv_name)
|
620
|
+
# parse the expression to determine its wtype
|
621
|
+
wn_phony = @wgenerator.phony(wnode)
|
622
|
+
wn_exp = parse_node(exp_node, wn_phony)
|
623
|
+
# the ivar wtype is defined by the
|
624
|
+
# wtype of the expression
|
625
|
+
ivar.wtype = wn_exp.wtype
|
626
|
+
wn_ivasgn = @wgenerator.ivasgn(wnode, ivar)
|
627
|
+
wn_phony.reparent_children_to(wn_ivasgn)
|
628
|
+
logger.debug "Setting new ivar #{ivar.name} wtype to #{wn_exp.wtype.name}"
|
601
629
|
end
|
602
630
|
|
603
|
-
#
|
604
|
-
#
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
return wn_ivasgn
|
631
|
+
# The wasm code for the ivar setter wnode doesn't leave
|
632
|
+
# any value on stack (store instruction). So if the last
|
633
|
+
# evaluated value must be kept then load the ivar again
|
634
|
+
@wgenerator.ivar(wnode, ivar) if keep_eval
|
635
|
+
wn_ivasgn
|
609
636
|
end
|
610
637
|
|
611
638
|
# Example
|
@@ -650,13 +677,13 @@ module Rlang::Parser
|
|
650
677
|
# Parse the expression node to see if it's a ixx.const
|
651
678
|
# in the end but get rid of it then because we are not
|
652
679
|
# executing this code. Just statically initiliazing the
|
653
|
-
#cvar with the value
|
680
|
+
# cvar with the value
|
654
681
|
wn_exp = parse_node(exp_node, wnode)
|
655
|
-
raise "Class variable initializer can only be a
|
682
|
+
raise "Class variable initializer can only be an int or a constant/class (got #{wn_exp}" \
|
656
683
|
unless wn_exp.const?
|
657
684
|
cvar = wnode.create_cvar(cv_name, wn_exp.wargs[:value], wn_exp.wtype)
|
658
685
|
wnode.remove_child(wn_exp)
|
659
|
-
logger.debug "Class variable #{cv_name}
|
686
|
+
logger.debug "Class variable #{cv_name} initialized with value #{cvar.value} and wtype #{cvar.wtype}"
|
660
687
|
else
|
661
688
|
raise "Class variable can only be defined in method or class scope"
|
662
689
|
end
|
@@ -723,18 +750,17 @@ module Rlang::Parser
|
|
723
750
|
# ---
|
724
751
|
# ... s(:ivar, :@stack_ptr)
|
725
752
|
def parse_ivar(node, wnode, keep_eval)
|
726
|
-
raise "Instance variable can only be accessed in method scope" \
|
727
|
-
unless wnode.
|
753
|
+
raise "Instance variable can only be accessed in instance method scope" \
|
754
|
+
unless wnode.in_instance_method_scope?
|
728
755
|
iv_name, = *node.children
|
729
|
-
if (
|
730
|
-
|
731
|
-
wn_ivar = @wgenerator.ivar(wnode, wn_recv, wattr)
|
756
|
+
if (ivar = wnode.find_ivar(iv_name))
|
757
|
+
wn_ivar = @wgenerator.ivar(wnode, ivar)
|
732
758
|
else
|
733
|
-
raise "unknown instance variable #{
|
759
|
+
raise "unknown instance variable #{ivar_name}"
|
734
760
|
end
|
735
761
|
# Drop last evaluated result if asked to
|
736
762
|
@wgenerator.drop(wnode) unless keep_eval
|
737
|
-
|
763
|
+
wn_ivar
|
738
764
|
end
|
739
765
|
|
740
766
|
# Example
|
@@ -803,6 +829,29 @@ module Rlang::Parser
|
|
803
829
|
return wn_false
|
804
830
|
end
|
805
831
|
|
832
|
+
# Whenever a string literal is used in Rlang
|
833
|
+
# in whatever scope (root, class or method scope)
|
834
|
+
# the string literal must be allocated
|
835
|
+
# statically.
|
836
|
+
# Then if the literal is used in a method scope
|
837
|
+
# we must instantiate a dynamic string object
|
838
|
+
# and copy the initial static value in it
|
839
|
+
def parse_string(node, wnode, keep_eval)
|
840
|
+
string = node.children.last
|
841
|
+
if wnode.in_method_scope?
|
842
|
+
# allocate string dynamically
|
843
|
+
wn_string = @wgenerator.string_dynamic_new(wnode, string)
|
844
|
+
else
|
845
|
+
# allocate string statically
|
846
|
+
wn_string = @wgenerator.string_static_new(wnode, string)
|
847
|
+
end
|
848
|
+
# Drop last evaluated result if asked to
|
849
|
+
@wgenerator.drop(wnode) unless keep_eval
|
850
|
+
|
851
|
+
logger.debug "wn_string:#{wn_string} wtype:#{wn_string.wtype} keep_eval:#{keep_eval}"
|
852
|
+
return wn_string
|
853
|
+
end
|
854
|
+
|
806
855
|
# Example
|
807
856
|
# TestA::C::MYCONST
|
808
857
|
# -------
|
@@ -1173,15 +1222,8 @@ module Rlang::Parser
|
|
1173
1222
|
|
1174
1223
|
# A that stage it's a method call of some sort
|
1175
1224
|
# (call on class or instance)
|
1176
|
-
|
1177
|
-
RELATIONAL_OPS.include?(method_name) ||
|
1178
|
-
UNARY_OPS.include?(method_name)
|
1179
|
-
return parse_operator(node, wnode, keep_eval)
|
1180
|
-
else
|
1181
|
-
return parse_send_method_lookup(node, wnode, keep_eval)
|
1182
|
-
end
|
1225
|
+
return parse_send_method_lookup(node, wnode, keep_eval)
|
1183
1226
|
|
1184
|
-
raise "FATAL ERROR!! Unreachable point at end of parse_send (node: #{node})"
|
1185
1227
|
end
|
1186
1228
|
|
1187
1229
|
def parse_send_nil_receiver(node, wnode, keep_eval)
|
@@ -1214,12 +1256,12 @@ module Rlang::Parser
|
|
1214
1256
|
return parse_send_result(node, wnode, keep_eval)
|
1215
1257
|
end
|
1216
1258
|
|
1217
|
-
if recv_node.nil? && method_name
|
1218
|
-
return
|
1259
|
+
if recv_node.nil? && method_name.to_s =~ /^attr_(reader|writer|accessor)/
|
1260
|
+
return parse_send_attr(node, wnode, keep_eval)
|
1219
1261
|
end
|
1220
1262
|
|
1221
|
-
if recv_node.nil? && method_name == :
|
1222
|
-
return
|
1263
|
+
if recv_node.nil? && method_name == :attr_type
|
1264
|
+
return parse_send_attr_type(node, wnode, keep_eval)
|
1223
1265
|
end
|
1224
1266
|
|
1225
1267
|
if recv_node.nil? && method_name == :inline
|
@@ -1366,9 +1408,15 @@ module Rlang::Parser
|
|
1366
1408
|
# Create class and method objects as we known we'll
|
1367
1409
|
# be calling them later on
|
1368
1410
|
WNode.root.find_or_create_class(cn_name)
|
1369
|
-
|
1370
|
-
|
1371
|
-
|
1411
|
+
if mn_name[0] == '#'
|
1412
|
+
method_type = :instance
|
1413
|
+
mth_name = mn_name[1..-1].to_sym
|
1414
|
+
else
|
1415
|
+
method_type = :class
|
1416
|
+
mth_name = mn_name.to_sym
|
1417
|
+
end
|
1418
|
+
(mth = wnode.find_or_create_method(mth_name, cn_name, nil, method_type)).wtype = WType.new(result_type)
|
1419
|
+
logger.debug "Declared #{method_type} method #{mth.name} in class #{mth.class_name} with wtype #{mth.wtype.name}"
|
1372
1420
|
else
|
1373
1421
|
raise "result declaration not supported #{wn.scope} scope"
|
1374
1422
|
end
|
@@ -1380,38 +1428,46 @@ module Rlang::Parser
|
|
1380
1428
|
# memory with an offset from the base address given as
|
1381
1429
|
# an argument.
|
1382
1430
|
#
|
1431
|
+
# xxxxx below can be reader, writer, accessor
|
1432
|
+
#
|
1383
1433
|
# Example
|
1384
|
-
#
|
1434
|
+
# attr_xxxxx :ptr, :size
|
1385
1435
|
# ---------
|
1386
|
-
# s(:send, nil, :
|
1436
|
+
# s(:send, nil, :attr,
|
1387
1437
|
# s(:sym, :ptr),
|
1388
1438
|
# s(:sym, :size))
|
1389
|
-
def
|
1390
|
-
raise "
|
1439
|
+
def parse_send_attr(node, wnode, keep_eval)
|
1440
|
+
raise "attr directives can only happen in class scope" \
|
1391
1441
|
unless wnode.in_class_scope?
|
1392
|
-
|
1393
|
-
|
1394
|
-
|
1395
|
-
|
1396
|
-
|
1397
|
-
|
1398
|
-
|
1442
|
+
|
1443
|
+
# check accessor directive is valid
|
1444
|
+
attr_access = node.children[1].to_s
|
1445
|
+
raise "Unknown kind of attribute accessor: #{attr_access}" \
|
1446
|
+
unless ['attr_reader', 'attr_writer', 'attr_accessor'].include? attr_access
|
1447
|
+
# scan through all attributes
|
1448
|
+
attr_nodes = node.children[2..-1]
|
1449
|
+
attr_nodes.each do |an|
|
1450
|
+
logger.debug "processing attr node #{an}"
|
1451
|
+
raise "attribute name must be a symbol (got #{an})" unless an.type == :sym
|
1452
|
+
attr_name = an.children.last
|
1453
|
+
if (attr = wnode.find_attr(attr_name))
|
1454
|
+
raise "attribute #{attr_name} already declared" if attr
|
1399
1455
|
else
|
1400
|
-
|
1401
|
-
|
1402
|
-
wattr.setter.export! if (@@export || self.config[:export_all])
|
1456
|
+
attr = wnode.create_attr(attr_name)
|
1457
|
+
attr.export!
|
1403
1458
|
end
|
1459
|
+
attr.send(attr_access)
|
1404
1460
|
end
|
1405
|
-
|
1461
|
+
nil
|
1406
1462
|
end
|
1407
1463
|
|
1408
1464
|
# Directive to specify wasm type of class attributes
|
1409
1465
|
# in case it's not the default type
|
1410
1466
|
#
|
1411
1467
|
# Example
|
1412
|
-
#
|
1468
|
+
# attr_type ptr: :I64, size: :I32
|
1413
1469
|
# ---------
|
1414
|
-
# s(:send, nil, :
|
1470
|
+
# s(:send, nil, :attr_type,
|
1415
1471
|
# (hash
|
1416
1472
|
# (pair
|
1417
1473
|
# s(:sym, :ptr)
|
@@ -1420,16 +1476,16 @@ module Rlang::Parser
|
|
1420
1476
|
# s(:sym, :size)
|
1421
1477
|
# s(:sym, :I32)) ))
|
1422
1478
|
#
|
1423
|
-
def
|
1424
|
-
raise "
|
1479
|
+
def parse_send_attr_type(node, wnode, keep_eval)
|
1480
|
+
raise "attr directives can only happen in class scope" \
|
1425
1481
|
unless wnode.in_class_scope?
|
1426
1482
|
hash_node = node.children.last
|
1427
|
-
|
1428
|
-
|
1429
|
-
if (
|
1430
|
-
logger.debug "Setting
|
1483
|
+
attr_types = parse_type_args(hash_node, :attribute)
|
1484
|
+
attr_types.each do |name, wtype|
|
1485
|
+
if (attr = wnode.find_attr(name))
|
1486
|
+
logger.debug "Setting attr #{name} type to #{wtype}"
|
1431
1487
|
# TODO find a way to update both wtype at once
|
1432
|
-
|
1488
|
+
attr.wtype = WType.new(wtype)
|
1433
1489
|
else
|
1434
1490
|
raise "Unknown class attribute #{name} in #{wnode}"
|
1435
1491
|
end
|
@@ -1477,18 +1533,21 @@ module Rlang::Parser
|
|
1477
1533
|
|
1478
1534
|
# Find the :wat entry in hash
|
1479
1535
|
logger.debug "Hash node: #{hash_node} "
|
1480
|
-
wat_node = hash_node.children
|
1536
|
+
wat_node = hash_node.children.\
|
1481
1537
|
find {|pair| sym_node, = *pair.children; sym_node.children.last == :wat}
|
1482
1538
|
raise "inline has no wat: hash entry" unless wat_node
|
1539
|
+
logger.debug "inline wat entry: #{wat_node}"
|
1483
1540
|
|
1484
1541
|
# Find the :wtype entry in hash if any
|
1485
|
-
wtype_node = hash_node.children
|
1542
|
+
wtype_node = hash_node.children.\
|
1486
1543
|
find {|pair| sym_node, = *pair.children; sym_node.children.last == :wtype}
|
1487
1544
|
if wtype_node
|
1488
1545
|
wtype = WType.new(wtype_node.children.last.children.last)
|
1546
|
+
logger.debug "inline wtype entry: #{wtype_node}"
|
1489
1547
|
else
|
1490
1548
|
wtype = WType::DEFAULT
|
1491
1549
|
end
|
1550
|
+
logger.debug "wtype: #{wtype} "
|
1492
1551
|
|
1493
1552
|
# Now extract the WAT code itself
|
1494
1553
|
raise "inline has no wat: hash entry" unless wat_node
|
@@ -1503,72 +1562,15 @@ module Rlang::Parser
|
|
1503
1562
|
end
|
1504
1563
|
wn_inline = @wgenerator.inline(wnode, wat_code, wtype)
|
1505
1564
|
# Drop last evaluated result if asked to
|
1506
|
-
@wgenerator.drop(wnode) unless keep_eval
|
1565
|
+
@wgenerator.drop(wnode) unless (keep_eval || wtype.blank?)
|
1507
1566
|
return wn_inline
|
1508
1567
|
end
|
1509
1568
|
|
1510
|
-
#
|
1511
|
-
# be an arithmetic or relational expression
|
1512
|
-
# a method call on class "instance"
|
1513
|
-
#
|
1514
|
-
# Example for binary op
|
1515
|
-
# 1 + 2
|
1516
|
-
# ----------
|
1517
|
-
# (send
|
1518
|
-
# (int 1) :+
|
1519
|
-
# (int 2)
|
1520
|
-
# )
|
1521
|
-
#
|
1522
|
-
# Example unary op
|
1523
|
-
# !(n==1)
|
1524
|
-
# ----------
|
1525
|
-
# (send
|
1526
|
-
# (begin
|
1527
|
-
# (send (lvar :n) :== (int 1))
|
1528
|
-
# ) :!)
|
1529
|
-
#
|
1530
|
-
def parse_operator(node, wnode, keep_eval)
|
1531
|
-
recv_node = node.children[0]
|
1532
|
-
operator = node.children[1]
|
1533
|
-
wn_op = @wgenerator.operator(wnode, operator)
|
1534
|
-
wn_recv = parse_node(recv_node, wn_op, true)
|
1535
|
-
# now process the 2nd op arguments (there should
|
1536
|
-
# be only one but do as if we had several
|
1537
|
-
arg_nodes = node.children[2..-1]
|
1538
|
-
raise "method #{operator} got #{arg_nodes.count} arguments (expected 0 or 1)" \
|
1539
|
-
unless arg_nodes.count <= 1
|
1540
|
-
wn_args = arg_nodes.collect {|n| parse_node(n, wn_op, true)}
|
1541
|
-
@wgenerator.operands(wn_op, wn_recv, wn_args)
|
1542
|
-
logger.debug " After type cast: #{wn_op} wtype: #{wn_op.wtype}, op children types: #{wn_op.children.map(&:wtype)}"
|
1543
|
-
# Drop last evaluated result if asked to
|
1544
|
-
@wgenerator.drop(wnode) unless keep_eval
|
1545
|
-
return wn_op
|
1546
|
-
end
|
1547
|
-
|
1548
|
-
# Method lookup
|
1549
|
-
# In the example below mem_size wasn' t
|
1550
|
-
# recognized as a local var because it was not
|
1551
|
-
# assigned a value before. It's recognized as
|
1552
|
-
# a tentative method call
|
1553
|
-
# Example
|
1554
|
-
# some_var = mem_size + 10
|
1555
|
-
# ------
|
1556
|
-
# (send
|
1557
|
-
# (send nil :mem_size) :+
|
1558
|
-
# (int 10))
|
1559
|
-
#
|
1560
|
-
#
|
1561
|
-
# Example for method call on class instance
|
1562
|
-
# @@cvar.x = 100
|
1563
|
-
# ----------
|
1564
|
-
# (send
|
1565
|
-
# (cvar :@@cvar) :x= (int 100)
|
1566
|
-
# )
|
1567
|
-
# TODO must guess type arg from operator type
|
1569
|
+
# Determine whether it's an instance or class method call
|
1568
1570
|
def parse_send_method_lookup(node, wnode, keep_eval)
|
1569
1571
|
recv_node = node.children[0]
|
1570
1572
|
#method_name = node.children[1]
|
1571
|
-
if wnode.in_class_scope? || wnode.in_class_method_scope?
|
1573
|
+
if wnode.in_class_scope? || wnode.in_class_method_scope? || wnode.in_root_scope?
|
1572
1574
|
if recv_node.nil? || recv_node.type == :self
|
1573
1575
|
return parse_send_class_method_call(node, wnode, keep_eval)
|
1574
1576
|
elsif recv_node.type == :const
|
@@ -1585,10 +1587,10 @@ module Rlang::Parser
|
|
1585
1587
|
end
|
1586
1588
|
elsif wnode.in_instance_method_scope?
|
1587
1589
|
if recv_node&.type == :const
|
1588
|
-
|
1590
|
+
recv_name = recv_node.children.last
|
1589
1591
|
# if this is a Constant, not a class
|
1590
1592
|
# then it's actually an instance method call
|
1591
|
-
if wnode.find_const(
|
1593
|
+
if wnode.find_const(recv_name)
|
1592
1594
|
return parse_send_instance_method_call(node, wnode, keep_eval)
|
1593
1595
|
else
|
1594
1596
|
return parse_send_class_method_call(node, wnode, keep_eval)
|
@@ -1647,7 +1649,7 @@ module Rlang::Parser
|
|
1647
1649
|
raise "Can only call method class on self or class objects (got #{recv_node} in node #{node})"
|
1648
1650
|
end
|
1649
1651
|
logger.debug "...#{class_name}::#{method_name}"
|
1650
|
-
if method_name == :new && wnode.in_class_scope?
|
1652
|
+
if method_name == :new && (wnode.in_class_scope? || wnode.in_root_scope?)
|
1651
1653
|
# This is class object instantiation. Statically
|
1652
1654
|
# allocated though. So it can only happen in the
|
1653
1655
|
# class scope for a class variable or a constant
|
@@ -1666,33 +1668,88 @@ module Rlang::Parser
|
|
1666
1668
|
raise "FATAL ERROR!! Unreachable point at end of parse_send_class_method_call (node: #{node})"
|
1667
1669
|
end
|
1668
1670
|
|
1671
|
+
# Instance Method lookup and native operator
|
1672
|
+
#
|
1673
|
+
# In the example below mem_size would be
|
1674
|
+
# recognized as a local var because it was not
|
1675
|
+
# assigned a value before. It's recognized as
|
1676
|
+
# a tentative method call
|
1677
|
+
# Example
|
1678
|
+
# some_var = mem_size + 10
|
1679
|
+
# ------
|
1680
|
+
# (send
|
1681
|
+
# (send nil :mem_size) :+
|
1682
|
+
# (int 10))
|
1683
|
+
#
|
1684
|
+
# Example for method call on class instance
|
1685
|
+
# @@cvar.x = 100
|
1686
|
+
# ----------
|
1687
|
+
# (send
|
1688
|
+
# (cvar :@@cvar) :x= (int 100)
|
1689
|
+
# )
|
1690
|
+
#
|
1691
|
+
# If receiver not self or const then it could
|
1692
|
+
# be an arithmetic or relational operator or
|
1693
|
+
# an operato overloaded in the related class
|
1694
|
+
#
|
1695
|
+
# Example for binary op
|
1696
|
+
# 1 + 2
|
1697
|
+
# ----------
|
1698
|
+
# (send
|
1699
|
+
# (int 1) :+
|
1700
|
+
# (int 2)
|
1701
|
+
# )
|
1702
|
+
#
|
1703
|
+
# Example unary op
|
1704
|
+
# !(n==1)
|
1705
|
+
# ----------
|
1706
|
+
# (send
|
1707
|
+
# (begin
|
1708
|
+
# (send (lvar :n) :== (int 1))
|
1709
|
+
# ) :!)
|
1710
|
+
#
|
1669
1711
|
def parse_send_instance_method_call(node, wnode, keep_eval)
|
1670
1712
|
recv_node = node.children[0]
|
1671
1713
|
method_name = node.children[1]
|
1672
1714
|
# Parse receiver node and temporarily attach it
|
1673
1715
|
# to parent wnode. It will later become the first
|
1674
1716
|
# argument of the method call by reparenting it
|
1675
|
-
logger.debug "Parsing instance method call #{method_name}..."
|
1676
|
-
|
1717
|
+
logger.debug "Parsing instance method call #{method_name}, keep_eval: #{keep_eval}..."
|
1718
|
+
logger.debug "... on receiver #{recv_node}..."
|
1719
|
+
|
1720
|
+
# parse the receiver node just to know its wtype
|
1677
1721
|
# if nil it means self
|
1678
|
-
|
1679
|
-
|
1680
|
-
|
1722
|
+
wn_phony = @wgenerator.phony(wnode)
|
1723
|
+
|
1724
|
+
wn_recv = recv_node.nil? ? parse_self(recv_node, wn_phony) : parse_node(recv_node, wn_phony)
|
1725
|
+
logger.debug "Parsed receiver : #{wn_recv} / wtype: #{wn_recv.wtype}"
|
1681
1726
|
|
1682
|
-
|
1683
|
-
|
1727
|
+
# If it's a Wasm natively supported operator and it
|
1728
|
+
# is not overloaded by the receiver's class
|
1729
|
+
# then generate native operator Wasm code instead of
|
1730
|
+
# a method call
|
1731
|
+
wn_op = @wgenerator.send_method(wnode, method_name, wn_recv.wtype)
|
1732
|
+
|
1733
|
+
# reparent the receiver wnode(s) to operator wnode
|
1734
|
+
wn_phony.reparent_children_to(wn_op)
|
1735
|
+
wnode.remove_child(wn_phony)
|
1684
1736
|
|
1685
1737
|
# Grab all arguments and add them as child of the call node
|
1686
1738
|
arg_nodes = node.children[2..-1]
|
1687
|
-
arg_nodes.
|
1688
|
-
|
1689
|
-
|
1739
|
+
wn_args = arg_nodes.collect do |n|
|
1740
|
+
logger.debug "...with arg #{n}"
|
1741
|
+
parse_node(n, wn_op, true)
|
1690
1742
|
end
|
1691
|
-
|
1743
|
+
|
1744
|
+
# now cast operands (will do nothing if it's a method call)
|
1745
|
+
@wgenerator.operands(wn_op, wn_recv, wn_args)
|
1746
|
+
logger.debug "After operands, call wnode: #{wn_op} wtype: #{wn_op.wtype}, wn_op children types: #{wn_op.children.map(&:wtype)}"
|
1747
|
+
|
1692
1748
|
# Drop last evaluated result if asked to or if
|
1693
1749
|
# the method called doesn't return any value
|
1694
|
-
@wgenerator.drop(wnode) unless (keep_eval ||
|
1695
|
-
|
1750
|
+
@wgenerator.drop(wnode) unless (keep_eval || wn_op.wtype.blank?)
|
1751
|
+
|
1752
|
+
return wn_op
|
1696
1753
|
end
|
1697
1754
|
|
1698
1755
|
def parse_type_args(hash_node, entity)
|
@@ -1966,7 +2023,7 @@ module Rlang::Parser
|
|
1966
2023
|
logger.debug "logical operator section : #{node.type}"
|
1967
2024
|
cond1_node, cond2_node = *node.children
|
1968
2025
|
# Prepare the operator wnode
|
1969
|
-
wn_op = @wgenerator.
|
2026
|
+
wn_op = @wgenerator.native_operator(wnode, node.type)
|
1970
2027
|
# Parse operand nodes and attach them to the
|
1971
2028
|
# operator wnode
|
1972
2029
|
wn_cond1 = parse_node(cond1_node, wn_op)
|