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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f0e1f5809295de94b29d997b98d712156654bc60621394d0476b5fc048ee70c0
4
- data.tar.gz: d35a2adf54dc40514dfa2f966b1ff9d992a11495c3c2c13f927e380be7616fda
3
+ metadata.gz: 45f6d0c347cfcf901dcd935e2b979098759f2c9b435ff5d900f77a830d5b2101
4
+ data.tar.gz: 88c0e8f7b79f3cb784f0dd0c85fc10b68427958893f3401998f9866fa9a9b50a
5
5
  SHA512:
6
- metadata.gz: fcb55360cc77db0bf05466a419f876274f210f3af0ce66af88210f08899e9f45b473efde8657914ea8eb0c1fc32e8816e3c0849b282b22807fce7b297319e5d0
7
- data.tar.gz: 9021a72e563ba1608d3fd5208929d619f4324b5274d8506e42e7a8a29d397178dd58254ba78b096fc29b0c16d4acb0ff92343e6e2e49e16c617c959298104080
6
+ metadata.gz: d6f959bc61a5f4f9548455244662a8b3d9482d9e4670fdb66fd9d5ea1101e062bb93d5fbaffabac5e24852c85058130f92680e76f7d01bbe1745914bee25640b
7
+ data.tar.gz: 30e4ecce3b01f850fd9515ab614e75d3cd915545b172bab7174872d0d5fab4875a96ade32dac2f1b7a95a3ca0f58ef0ef891544b926ba0e6c23fa38c4cb6fca2
@@ -1,3 +1,7 @@
1
+ ## 0.4.1
2
+ * Class attributes syntac now identical to plain Ruby
3
+ * Very preliminary version of Rlang String class
4
+
1
5
  ## 0.4.0
2
6
  * Object instances, instance methods and instance variables now supported
3
7
  * Test suite using parser directly
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
- What you'll find in the current version is a first implementation of Rlang. It will improve over time, with more facilities and probably more Ruby features but always with the goal to generate crisp and uncluttered WAT code.
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)
@@ -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 `wattr` directive. I actually defines a couple of things for you:
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
- wattr :side
79
+ attr_accessor :side
82
80
 
83
81
  def area
84
- self.side * self.side
82
+ @side * @side
85
83
  end
86
84
  end
87
85
  ```
88
- Later in your code you could use this class in your code as follows
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
- @@square.side = 10
96
- @@square.area
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 (here it is created statically at compile time), its side attribute is set to 10 and, as you would expect, the call to the Square#area method returns 100.
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
- wattr :side
109
- wattr_type side: :I64
107
+ attr_accessor :side
108
+ attr_type side: :I64
110
109
 
111
110
  def area
112
111
  result :I64
113
- self.side * self.side
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
- You have already seen how to perform static objects allocation in one of our previous example. 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 and initialized at compile time by Rlang and the `@@square` 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:
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
- SQUARE = Square.new
126
- $SQUARE = Square.new
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 # would be 6000
149
- # ... Do what eveer you have to do...
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
- In this example, the `local` directive instructs the compiler that `lvar` is of type `:I64` and the local variable mysquare is of type `Square`.
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. In the absence of dynamic object instantiation this allows you to create your own object at runtime by allocating WebAssembly memory and pointing to it as if it was an object of you choice (see Rlang library below for memory management)
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
- inline wat: '(i32.mul
439
+ wasm wat: '(i32.mul
428
440
  (local.get $arg1)
429
441
  (local.get $arg1))',
430
- ruby: 'arg1 ** 2'
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 provided with Rlang is quite simple. It is shamelessly adapted from 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).
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.
@@ -7,4 +7,5 @@ require_relative './lib/object'
7
7
  require_relative './lib/type'
8
8
  require_relative './lib/memory'
9
9
  require_relative './lib/unistd'
10
- require_relative './lib/malloc'
10
+ require_relative './lib/malloc'
11
+ require_relative './lib/string'
@@ -35,8 +35,8 @@ $NALLOC = 1024
35
35
  DAta[:dummy_malloc_data] = [0, 0, 0, 0, 0]
36
36
 
37
37
  class Header
38
- wattr :ptr, :size
39
- wattr_type ptr: :Header, size: :I32
38
+ attr_accessor :ptr, :size
39
+ attr_type ptr: :Header, size: :I32
40
40
  end
41
41
 
42
42
  class Malloc
@@ -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
@@ -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) generate the wnodes for the new/initialize function
266
- # 2) generate the wnodes for accessing attributes
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.def_wattr(wn_class)
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
- logger.debug "wnode: #{wnode}, keep_eval: #{keep_eval}"
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 node.children.first.type
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.operator(wn_var_set, op, gvar.wtype)
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.operator(wn_var_set, op, cvar.wtype)
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.operator(wn_var_set, op, lvar.wtype)
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
- var_asgn_node, op, exp_node = *node.children
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
- wattr = wnode.find_ivar(var_name)
389
- raise "Unknown instance variable #{var_name}" unless wattr
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, wn_recv, wattr)
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 :none for now. We'll fix that
401
- # with the operands call later on
402
- wn_op = @wgenerator.operator(wn_var_set, op)
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 the first child of the
406
+ # now create the getter node as a child of the
405
407
  # operator
406
- wn_var_get = @wgenerator.ivar(wnode, wn_recv, wattr)
408
+ wn_var_get = @wgenerator.ivar(wn_op, ivar)
407
409
 
408
- # If the setter returns something and last evaluated value
409
- # must be ignored then drop it
410
- unless keep_eval && !wn_var_set.wtype.blank?
411
- @wgenerator.drop(wnode)
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 :none for now. We'll fix that
441
- # with the operands call later on
442
- wn_op = @wgenerator.operator(wn_var_set, op)
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 && !wn_var_set.wtype.blank?
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
- # TODO: const are I32 hardcoded for now. Must find a way to
490
- # initialize I64 constant too
491
- value = exp_node.children.last
492
- const = wnode.create_const(constant_name, nil, value, WType::DEFAULT)
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
- wn_casgn = @wgenerator.casgn(wnode, const)
495
- return wn_casgn
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
- else
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 straight number" \
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 be used in method scope" \
575
- unless wnode.in_method_scope?
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
- # Second argument is the expression
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
- logger.debug "Casting exp. wtype #{wn_exp.wtype} to existing ivar #{wattr.name} wtype #{wattr.wtype}"
600
- @wgenerator.cast(wn_exp, wattr.wtype, false)
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
- # If the setter returns something and last evaluated value
604
- # must be ignored then drop it
605
- unless keep_eval && !wn_ivasgn.wtype.blank?
606
- @wgenerator.drop(wnode)
607
- end
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 straight number (got #{wn_exp}" \
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} initialzed with value #{cvar.value} and wtype #{cvar.wtype}"
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.in_method_scope?
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 (wattr = wnode.find_ivar(iv_name))
730
- wn_recv = parse_self(node, wnode)
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 #{cv_name}"
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
- return wn_ivar
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
- if ARITHMETIC_OPS.include?(method_name) ||
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 == :wattr
1218
- return parse_send_wattr(node, wnode, keep_eval)
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 == :wattr_type
1222
- return parse_send_wattr_type(node, wnode, keep_eval)
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
- method_type = (mn_name[0] == '#' ? :instance : :class)
1370
- (mwn = wnode.find_or_create_method(mn_name, cn_name, nil, method_type)).wtype = WType.new(result_type)
1371
- logger.debug "result_type #{mwn.wtype} for method #{mwn.name}"
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
- # wattr :ptr, :size
1434
+ # attr_xxxxx :ptr, :size
1385
1435
  # ---------
1386
- # s(:send, nil, :wattr,
1436
+ # s(:send, nil, :attr,
1387
1437
  # s(:sym, :ptr),
1388
1438
  # s(:sym, :size))
1389
- def parse_send_wattr(node, wnode, keep_eval)
1390
- raise "wattr directives can only happen in class scope" \
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
- wattr_nodes = node.children[2..-1]
1393
- wattr_nodes.each do |wan|
1394
- logger.debug "processing wattr node #{wan}"
1395
- raise "attribute name must be a symbol (got #{wan})" unless wan.type == :sym
1396
- wattr_name = wan.children.last
1397
- if (wattr = wnode.find_wattr(wattr_name))
1398
- raise "attribute #{wattr_name} already declared" if wattr
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
- wattr = wnode.create_wattr(wattr_name)
1401
- wattr.getter.export! if (@@export || self.config[:export_all])
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
- return
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
- # wattr_type ptr: :I64, size: :I32
1468
+ # attr_type ptr: :I64, size: :I32
1413
1469
  # ---------
1414
- # s(:send, nil, :wattr_type,
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 parse_send_wattr_type(node, wnode, keep_eval)
1424
- raise "wattr directives can only happen in class scope" \
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
- wattr_types = parse_type_args(hash_node, :attribute)
1428
- wattr_types.each do |name, wtype|
1429
- if (wattr = wnode.find_wattr(name))
1430
- logger.debug "Setting wattr #{name} type to #{wtype}"
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
- wattr.wtype = wattr.ivar.wtype = WType.new(wtype)
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
- # If receiver not self or const then it could
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
- const_name = recv_node.children.last
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(const_name)
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
- # parse the receiver node
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
- wn_recv = recv_node.nil? ? parse_self(recv_node, wnode) : parse_node(recv_node, wnode)
1679
- logger.debug "... on #{wn_recv}"
1680
- class_name = wn_recv.wtype.name
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
- wn_call = @wgenerator.call(wnode, class_name, method_name, :instance)
1683
- wn_recv.reparent_to(wn_call) if wn_recv
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.each do |node|
1688
- wn = parse_node(node, wn_call)
1689
- logger.debug "...with arg #{wn}"
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
- logger.debug "Resulting in call node #{wn_call}"
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 || wn_call.wtype.blank?)
1695
- return wn_call
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.operator(wnode, node.type)
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)