rlang 0.4.1 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -3
- data/Gemfile.lock +4 -6
- data/README +11 -0
- data/README.md +18 -10
- data/bin/rlang +17 -5
- data/docs/RlangCompiler.md +5 -1
- data/docs/RlangManual.md +98 -20
- data/examples/fib/fib.rb +5 -1
- data/lib/builder/rlang/compiler.rb +2 -21
- data/lib/rlang/lib/array/array32.rb +59 -0
- data/lib/rlang/lib/array/array64.rb +56 -0
- data/lib/rlang/lib/array.rb +6 -0
- data/lib/rlang/lib/base64.rb +223 -0
- data/lib/rlang/lib/io.rb +75 -0
- data/lib/rlang/lib/kernel.rb +23 -0
- data/lib/rlang/lib/malloc.rb +12 -8
- data/lib/rlang/lib/memory.rb +102 -2
- data/lib/rlang/lib/object.rb +40 -4
- data/lib/rlang/lib/rlang.rb +29 -0
- data/lib/rlang/lib/rlang_core.rb +15 -0
- data/lib/rlang/lib/string.rb +106 -8
- data/lib/rlang/lib/type/i32.rb +43 -0
- data/lib/rlang/lib/type/i64.rb +2 -0
- data/lib/rlang/lib/unistd.rb +1 -2
- data/lib/rlang/lib/wasi.rb +184 -0
- data/lib/rlang/parser/attr.rb +9 -13
- data/lib/rlang/parser/const.rb +105 -1
- data/lib/rlang/parser/cvar.rb +11 -7
- data/lib/rlang/parser/data.rb +17 -6
- data/lib/rlang/parser/export.rb +4 -3
- data/lib/rlang/parser/ext/integer.rb +3 -1
- data/lib/rlang/parser/ext/type.rb +30 -1
- data/lib/rlang/parser/global.rb +10 -2
- data/lib/rlang/parser/ivar.rb +3 -7
- data/lib/rlang/parser/klass.rb +8 -35
- data/lib/rlang/parser/method.rb +36 -12
- data/lib/rlang/parser/module.rb +143 -0
- data/lib/rlang/parser/wgenerator.rb +462 -168
- data/lib/rlang/parser/wnode.rb +387 -142
- data/lib/rlang/parser/wtype.rb +30 -7
- data/lib/rlang/parser.rb +506 -231
- data/lib/rlang/version.rb +1 -1
- data/lib/rlang.rb +3 -0
- data/lib/ruby/mirror/rstring.rb +16 -0
- data/lib/utils/exceptions.rb +12 -0
- data/rlang.gemspec +4 -4
- metadata +25 -13
- data/lib/rlang/lib.rb +0 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 853a10a09ac23cc4cedb2c2b5bc966258cfc858673bbecc7a7bb625c8ddf4b92
|
4
|
+
data.tar.gz: f27458788eb2cccaa49bb319436979a91e93de4e54fb3fd83c28fd67b481a631
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 78a5e6da19f10887854dd40fb85e4a48d2073c10ece4acc01c07651041110eceb4c13cb67fd7251b1e0abbc310bc25134ef32c27bd2bb4e6af3488eec3a2bdf5
|
7
|
+
data.tar.gz: 3bfc1ffe8b133f4b8b2621c810af522f51fa88d44ab5dcc00a679dd0bf3eab857321d311c9b156dcd63428e62222669d87a6c1f1a501203ba0e448b44300a60f
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,22 @@
|
|
1
|
-
## 0.
|
2
|
-
*
|
3
|
-
*
|
1
|
+
## 0.6.0
|
2
|
+
* Support for signed/unsigned integers (arithmetics, relational operatos, explicit and implicit type cast)
|
3
|
+
* More methods in String ([], []=, +, *, ==, !=, chr,...)
|
4
|
+
* More methods in Array ([], []=, ==, !=,...)
|
5
|
+
* More methods in I32 and I64 classes (to_s,...)
|
6
|
+
* Base 64 encoding and decidong module (same methods as in Ruby)
|
7
|
+
* Array literal ([]) supported as initializer
|
8
|
+
* Automatically adjust HEAP base address at compile time
|
9
|
+
* Various bug fixes and code cleanup
|
10
|
+
|
11
|
+
## 0.5.1
|
12
|
+
* Imported WASM methods can now be defined
|
13
|
+
* Preliminary version of WASI interface and IO class added to Rlang library
|
14
|
+
|
15
|
+
## 0.5.0
|
16
|
+
* Class attributes syntax now identical to plain Ruby
|
17
|
+
* Class in class definition and module supported
|
18
|
+
* Class inheritance
|
19
|
+
* Basic Array and String class in Rlang library
|
4
20
|
|
5
21
|
## 0.4.0
|
6
22
|
* Object instances, instance methods and instance variables now supported
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
rlang (0.
|
4
|
+
rlang (0.5.1)
|
5
5
|
parser
|
6
6
|
|
7
7
|
GEM
|
@@ -13,13 +13,11 @@ GEM
|
|
13
13
|
parser (2.7.0.2)
|
14
14
|
ast (~> 2.4.0)
|
15
15
|
rake (12.3.3)
|
16
|
-
rutie (0.0.4)
|
17
16
|
simplecov (0.18.1)
|
18
17
|
docile (~> 1.1)
|
19
18
|
simplecov-html (~> 0.11.0)
|
20
19
|
simplecov-html (0.11.0)
|
21
|
-
wasmer (0.
|
22
|
-
rutie (~> 0.0.3)
|
20
|
+
wasmer (1.0.0)
|
23
21
|
|
24
22
|
PLATFORMS
|
25
23
|
ruby
|
@@ -30,7 +28,7 @@ DEPENDENCIES
|
|
30
28
|
rake (~> 12.0)
|
31
29
|
rlang!
|
32
30
|
simplecov (~> 0.18)
|
33
|
-
wasmer (~> 0
|
31
|
+
wasmer (~> 1.0)
|
34
32
|
|
35
33
|
BUNDLED WITH
|
36
|
-
2.
|
34
|
+
2.3.13
|
data/README
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
All contents here come from http://www.wolczko.com/st80/
|
2
|
+
|
3
|
+
image.tar.gz:original Xerox virtual image (as provided on tape by Xerox PARC)
|
4
|
+
manual.pdf: the manual that came with the tape (incl. errata for the Blue Book implementation section)
|
5
|
+
st.tar.gz: C implementation of the Smalltalk VM from Mario Wolczko
|
6
|
+
VMsrc.shar: Smalltalk VM sourcesas re-keyed from the Blue Book by Mario Wolczko
|
7
|
+
|
8
|
+
Xerox Star
|
9
|
+
----------
|
10
|
+
8010_hd_images.zip: Xerox Star images. hard disk images for Viewpoint 2.0, XDE 5.0, and The Harmony release of Interlisp-D (Not Smalltalk)
|
11
|
+
See article at https://engblg.livingcomputers.org/index.php/2019/01/19/introducing-darkstar-a-xerox-star-emulator/
|
data/README.md
CHANGED
@@ -1,12 +1,17 @@
|
|
1
|
-
# Rlang : a
|
1
|
+
# Rlang : a Ruby-like language compiled to WebAssembly
|
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 actually two things: 1) a
|
5
|
+
Rlang is actually two things: 1) a subset of the Ruby language and 2) a **compiler** transforming this Ruby subset in a native, fully runnable WebAssembly module.
|
6
|
+
|
7
|
+
So in summary what Rlang does is:
|
8
|
+
<p align="center"><b>
|
9
|
+
Rlang source code → Rlang compiler → WebAssembly bytecode
|
10
|
+
</b></p>
|
6
11
|
|
7
12
|
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
13
|
|
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
|
14
|
+
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 improving over time, always with the goal of generating crisp and uncluttered WebAssembly code.
|
10
15
|
|
11
16
|
If you want to help with Rlang see [How you can help](#how-you-can-help).
|
12
17
|
|
@@ -15,6 +20,8 @@ If you want to help with Rlang see [How you can help](#how-you-can-help).
|
|
15
20
|
* **WABT toolkit**: the rlang compiler can generate both WebAssembly source code (WAT file) and WebAssembly bytecode (WASM file). To generate WASM bytecode the rlang compiler uses wat2wasm. This utility is part of the [WABT toolkit](https://github.com/WebAssembly/wabt)
|
16
21
|
* **wasmer runtime** (optional): [Wasmer](https://wasmer.io/) is a fast WebAssembly runtime. You'll need it if you want to run the test suite from the source repo. You can also use it to run the compiled WASM code generated by the rlang compiler. You can get Wasmer at [wasmer.io](https://wasmer.io/)
|
17
22
|
|
23
|
+
Rlang has also been successfully tested with the [Wasmtime](https://wasmtime.dev/) WebAssembly runtime.
|
24
|
+
|
18
25
|
|
19
26
|
## Installing Rlang
|
20
27
|
Rlang is available as a gem from rubygems.org. So simply run the following command to install it:
|
@@ -22,7 +29,7 @@ Rlang is available as a gem from rubygems.org. So simply run the following comma
|
|
22
29
|
```
|
23
30
|
$ gem install rlang
|
24
31
|
```
|
25
|
-
Alternatively, if you clone this git repo and play with the Rlang source code you can generate your own local gem and install it
|
32
|
+
Alternatively, if you clone this git repo and play with the Rlang source code you can generate your own local gem and install it as follows:
|
26
33
|
|
27
34
|
```
|
28
35
|
$ bundle install
|
@@ -33,7 +40,7 @@ $ gem install --local rlang-x.y.z.gem
|
|
33
40
|
To check that the installation went well, run `rlang --help` and see if the help message displays correctly.
|
34
41
|
|
35
42
|
## A Quick example
|
36
|
-
An example is always worth a thousand words, so here is a quick one
|
43
|
+
An example is always worth a thousand words, so here is a quick one showing you how to compile some Rang code and run it from your browser. Since Chrome, Chromium, Firefox and Safari embed a WebAssembly runtime it is the easiest way to test your compiled Rlang code.
|
37
44
|
|
38
45
|
Navigate to the Fibonacci directory in [examples/fib](https://github.com/ljulliar/rlang/blob/master/examples/fib/) and download the three files from this directory: fib.rb, index.html, server.rb. Alternatively you can git clone the repo and go directly in that directory.
|
39
46
|
|
@@ -50,15 +57,14 @@ Point your browser to [http://localhost:8080](http://localhost:8080), a simple p
|
|
50
57
|
|
51
58
|
That was easy, right ?
|
52
59
|
|
53
|
-
|
54
60
|
## The Rlang language
|
55
61
|
Ruby features supported by Rlang are detailed in the [Rlang Manual](https://github.com/ljulliar/rlang/blob/master/docs/RlangManual.md)
|
56
62
|
|
57
63
|
You can look at the rlang test suite in [test/rlang_test_files](https://github.com/ljulliar/rlang/blob/master/test/rlang_test_files/) to get a flavor of the subset of Ruby currently supported.
|
58
64
|
|
59
|
-
For a more involved example of
|
65
|
+
For a more involved example of Rlang code, I invite you to look at the [dynamic memory allocator](https://github.com/ljulliar/rlang/blob/master/lib/rlang/lib/malloc.rb) provided with the Rlang library.
|
60
66
|
|
61
|
-
## rlang compiler
|
67
|
+
## The rlang compiler
|
62
68
|
The Rlang compiler can be invoked through the `rlang` command. See the [Rlang Compiler Documentation](https://github.com/ljulliar/rlang/blob/master/docs/RlangCompiler.md) for more details about the command line options.
|
63
69
|
|
64
70
|
Keep in mind that Rlang is **NOT** a Ruby interpreter or a Ruby VM executing some kind of bytecode. It does actually statically **compile** the Rlang language to WebAssembly code pretty much like gcc or llvm compiles C/C++ code to machine assembly language.
|
@@ -70,9 +76,11 @@ One of the big benefits of Rlang being a subset of the Ruby language is that you
|
|
70
76
|
## Why Rlang?
|
71
77
|
This project was created out of the need to develop a Virtual Machine written in WebAssembly capable of interpreting the [Rubinius](https://github.com/rubinius/rubinius) bytecode. And yes, ultimately run a native Ruby VM in a browser :-)
|
72
78
|
|
73
|
-
After a first proof of concept written directly by hand in WebAssembly (WAT code) it became clear that writing a full fledged VM directly in WebAssembly was going to be tedious, complex and unnecessarily painful.
|
79
|
+
After a first proof of concept written directly by hand in WebAssembly (WAT source code) it became clear that writing a full fledged VM directly in WebAssembly was going to be tedious, complex and unnecessarily painful.
|
80
|
+
|
81
|
+
Sure I could have written this VM in any of the language that can already be compiled directly to WebAssembly (C, C++, Rust, Go,...) but being fond of Ruby since 2000 I decided that I would go for a compiler capable of transforming a subset of the Ruby language directly into WebAssembly with a minimum overhead.
|
74
82
|
|
75
|
-
|
83
|
+
So in a nutshell: the goal of Rlang is to let you develop efficient WebAssembly code with a reasonably high level of abstraction while keeping the generated WebAssembly code straightforward, human readable and slim.
|
76
84
|
|
77
85
|
## Why the name Rlang?
|
78
86
|
Yes I hear you: Rlang is already the name of the R language so why use that name and aren't you introducing some confusion? Well for one I couldn't resist using that name to honor software engineering history (see below) and because, after all, the intersection between the Ruby/WebAssembly community and the R language community focused on data processing and machine learning must be quite small to say the least.
|
data/bin/rlang
CHANGED
@@ -13,13 +13,16 @@
|
|
13
13
|
|
14
14
|
require 'optparse'
|
15
15
|
require 'fileutils'
|
16
|
-
require 'rlang'
|
16
|
+
require 'rlang' # setup RLANG_BASE_DIR
|
17
17
|
require 'builder'
|
18
18
|
|
19
|
+
RLANG_LIB_DIR = File.expand_path('./rlang/lib', RLANG_BASE_DIR)
|
20
|
+
|
19
21
|
include Log
|
20
22
|
logger.level = Logger::INFO
|
21
23
|
|
22
24
|
options = {}
|
25
|
+
custom_load_path = []
|
23
26
|
OptionParser.new do |opts|
|
24
27
|
opts.banner = %q{Usage: rlang [options] filename
|
25
28
|
read a Rlang file, check it for errors, and convert it
|
@@ -39,8 +42,8 @@ options:
|
|
39
42
|
}
|
40
43
|
|
41
44
|
opts.on("-I DIR", "--load_path DIRECTORY", "specify $LOAD_PATH directory (may be used more than once)") do |dir|
|
42
|
-
|
43
|
-
|
45
|
+
custom_load_path ||= []
|
46
|
+
custom_load_path << dir
|
44
47
|
end
|
45
48
|
|
46
49
|
opts.on("-M", "--module NAME", "WASM module name") do |name|
|
@@ -67,6 +70,14 @@ options:
|
|
67
70
|
options[:wasm] = v
|
68
71
|
end
|
69
72
|
|
73
|
+
opts.on("-c", "--comments", "Include comments in WAT source file") do |v|
|
74
|
+
options[:comments] = v
|
75
|
+
end
|
76
|
+
|
77
|
+
opts.on("-S", "--start FUNCTION", "Function name where execution starts (default '_start')") do |function|
|
78
|
+
options[:start] = function
|
79
|
+
end
|
80
|
+
|
70
81
|
opts.on("-o", "--output FILE", "Write output to file") do |file|
|
71
82
|
options[:output] = file
|
72
83
|
end
|
@@ -87,9 +98,10 @@ options:
|
|
87
98
|
end
|
88
99
|
end.parse!(ARGV)
|
89
100
|
|
90
|
-
options[:module]
|
101
|
+
options[:module] ||= ''
|
91
102
|
options[:memory_min] ||= 4
|
92
|
-
options[:LOAD_PATH]
|
103
|
+
options[:LOAD_PATH] = custom_load_path << RLANG_LIB_DIR
|
104
|
+
options[:start] ||= '_start'
|
93
105
|
|
94
106
|
fh_out = options[:output] ? File.open(options[:output], 'w') : STDOUT
|
95
107
|
|
data/docs/RlangCompiler.md
CHANGED
@@ -19,6 +19,8 @@ Usage: rlang [options] rlang_file.rb
|
|
19
19
|
-w, --wat Generate WAT source file
|
20
20
|
-a, --ast Generate Ruby AST file
|
21
21
|
-s, --wasm Generate WASM bytecode file
|
22
|
+
-c, --comments Include comments in WAT source file
|
23
|
+
-S, --start FUNCTION Function name where execution starts (default '_start')
|
22
24
|
-o, --output FILE Write output to file
|
23
25
|
-v, --verbose [LEVEL] Verbosity level (fatal, error, warn, info, debug)
|
24
26
|
-V, --version Displays Rlang version
|
@@ -30,7 +32,9 @@ Usage: rlang [options] rlang_file.rb
|
|
30
32
|
* **-m, --memory MIN[,MAX]**: size of the WASM memory allocated at run time. The first argument is the initial amount of memory allocated and the second one (optional) is the maximum memory that your WASM module can allocate while running. The unit of both arguments is in number of WASM pages (4 KBytes)
|
31
33
|
* **-w, --wat**: parse Rlang file and generate WAT source code
|
32
34
|
* **-a, --ast**: parse Rlang file and generate the Ruby abstract syntax tree
|
33
|
-
* **-
|
35
|
+
* **-s, --wasm**: parse Rlang file, generate WAT source code and compile it to WASM bytecode
|
36
|
+
* **-c, --comments**: include the Rlang comments at the corresponding location in the generated WAT source file.
|
37
|
+
* **-S, --start**: function to use as the execution starting point of the WASM module (default is `_start`)
|
34
38
|
* **-o, --output FILE**: send rlang output to FILE
|
35
39
|
* **-v, --verbose [LEVEL]**: verbosity level (fatal, error, warn, info, debug). Default is warn
|
36
40
|
* **-V, --version**: Displays Rlang version
|
data/docs/RlangManual.md
CHANGED
@@ -1,19 +1,16 @@
|
|
1
1
|
# The Rlang language
|
2
2
|
|
3
|
-
Rlang is a subset of the Ruby language that is meant to provide a
|
3
|
+
Rlang is a subset of the Ruby language that is meant to provide a good level of abstraction and expressiveness while keeping its compilation to WebAssembly straightforward.
|
4
4
|
|
5
5
|
Ruby programmers will feel at home with Rlang and non Ruby programmers will find it useful to generate efficient WebAssembly code from a language that is much easier to use.
|
6
6
|
|
7
7
|
Still, to make this Ruby to WebAssembly compilation possible a number of trade-offs had to be made. The goal of this document is to explain the features of Rlang are how it differs from plain Ruby.
|
8
8
|
|
9
|
-
## The Rlang object model
|
10
|
-
|
11
|
-
In Rlang you can define classes and those classes can be instantiated either statically at compile time or dynamically at runtime. There is no inheritance mechanism today between classes in the current version. Classes cannot be nested.
|
12
|
-
|
13
9
|
## What Rlang does
|
14
10
|
|
15
11
|
Rlang provides:
|
16
12
|
* Classes, class attributes and class variables
|
13
|
+
* Modules
|
17
14
|
* Object instantiation, attribute accessors and instance variables
|
18
15
|
* Method definition and method calls
|
19
16
|
* Integers and booleans
|
@@ -24,6 +21,7 @@ Rlang provides:
|
|
24
21
|
* Arithmetic, relational and logical operators
|
25
22
|
* WebAssembly source code (WAT) inlining
|
26
23
|
* Requiring other Rlang or WAT files
|
24
|
+
* Importing and exporting WASM functions
|
27
25
|
* A Rlang library (written in Rlang of course) that you can reuse in your own WebAssembly module
|
28
26
|
|
29
27
|
Here is a sample of Rlang code to whet your appetite:
|
@@ -46,8 +44,6 @@ calling that method later in your code is as simple as invoking `Math.fib(20)`
|
|
46
44
|
## Classes
|
47
45
|
Classes are core constructs of Rlang and are very similar to Ruby classes.
|
48
46
|
|
49
|
-
In Rlang, all methods must be defined within the scope of a class. In other words you can not define a method at the top level of your Rlang code (not a very good practice anyway, even in plain Ruby). Also there is no inheritance mechanism in Rlang in the current version.
|
50
|
-
|
51
47
|
Here is an example of a class definition and the initialization and use of a class variable written in Rlang (note: this example uses only class methods on purpose. Instance methods are covered later in this document):
|
52
48
|
|
53
49
|
```ruby
|
@@ -70,6 +66,28 @@ This short piece of code shows several interesting points:
|
|
70
66
|
1. Methods in this example are class methods, hence the use of `self.take_one` and `self.refill` in method definitions but instance methods are also supported (more on this later)
|
71
67
|
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
68
|
|
69
|
+
### Class scope and inheritance
|
70
|
+
Rlang supports the definition of classes in class at any depth and class naming follows the exact same convention as in Ruby. In the example below the `B` class can be referred to as `A::B`.
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
class A
|
74
|
+
class B
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def main
|
79
|
+
new_object = A::B.new
|
80
|
+
end
|
81
|
+
```
|
82
|
+
A Class in RLang can also inherit from another class as in Ruby. Whan a superclass is not specified, a newly defined class automatically inherits from the Object class.
|
83
|
+
|
84
|
+
If you need to use a class name in your code before the class is actually processed by the compiler you can use the following empty class notation to declare that the corresponding constant is actually a class. You can later reopen the class definition and define methods.
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
class String; end
|
88
|
+
```
|
89
|
+
|
90
|
+
|
73
91
|
### Class attributes and instance variables
|
74
92
|
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:
|
75
93
|
|
@@ -100,7 +118,7 @@ end
|
|
100
118
|
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.
|
101
119
|
|
102
120
|
### Class attribute type
|
103
|
-
In the example above the `side` attribute is implicitely using the `:I32` (
|
121
|
+
In the example above the `side` attribute is implicitely using the `:I32` (signed 32-bit 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).
|
104
122
|
|
105
123
|
```ruby
|
106
124
|
class Square
|
@@ -113,6 +131,8 @@ class Square
|
|
113
131
|
end
|
114
132
|
end
|
115
133
|
```
|
134
|
+
## Modules
|
135
|
+
Modules in Rlang behaves exactly like modules in Ruby. Modules can be included, extended or prepended in other classes and modules. .
|
116
136
|
|
117
137
|
## Object instantiation
|
118
138
|
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.
|
@@ -130,8 +150,9 @@ class Test
|
|
130
150
|
# ...
|
131
151
|
end
|
132
152
|
```
|
153
|
+
The address in memory of both class variables and constants can be accessed by using the `addr` method as in `SQUARE.addr` for instance.
|
133
154
|
|
134
|
-
**IMPORTANT NOTE**: in the current version of Rlang the new method call used to allocate
|
155
|
+
**IMPORTANT NOTE**: in the current version of Rlang the new method call used to allocate static objects 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
156
|
|
136
157
|
### Dynamic objects
|
137
158
|
At any point in the body of method you can dynamically instantiate a new object. Here is an exemple:
|
@@ -163,7 +184,7 @@ class Main
|
|
163
184
|
cube = Cube.new(10, 20, 30)
|
164
185
|
v = cube.volume
|
165
186
|
# ... Do what ever you have to do...
|
166
|
-
|
187
|
+
cube.free
|
167
188
|
end
|
168
189
|
end
|
169
190
|
```
|
@@ -171,6 +192,11 @@ end
|
|
171
192
|
## Methods
|
172
193
|
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.
|
173
194
|
|
195
|
+
### Method definition
|
196
|
+
A method in Rlang can either be defined in a class, a module or at the top level. In that case the method is implicitely defined within the context of the Object class as Ruby does.
|
197
|
+
|
198
|
+
Class methods must be defined using the `def self.mymethod` syntax. The `class << self ... end` form is not supported.
|
199
|
+
|
174
200
|
### Method arguments
|
175
201
|
Rlang method definition supports fixed name arguments in any number. The *args and **args notation are not supported.
|
176
202
|
|
@@ -204,9 +230,9 @@ Rlang also gives you the ability to declare the return type of a method like thi
|
|
204
230
|
result :class_name, :method_name, :your_type_here
|
205
231
|
```
|
206
232
|
|
207
|
-
This result directive must be used to instruct the compiler about the return type of a method if
|
233
|
+
This result directive must be used to instruct the compiler about the return type of a method if the compiler has not yet compiled this method (e.g. the method definition is coming later in your source code) or when calling a method from another class defined in a different file not yet compiled. Keep in mind that this is only needed if the method returns something different than the default type (`:I32`).
|
208
234
|
|
209
|
-
Note: in this directive `:method_name` symbol starts with a `#` characters if it refers to an instance method. Without
|
235
|
+
Note: in this directive `:method_name` symbol starts with a `#` characters if it refers to an instance method. Without the `#` it refers to a class method.
|
210
236
|
|
211
237
|
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.
|
212
238
|
|
@@ -224,7 +250,9 @@ end
|
|
224
250
|
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`.
|
225
251
|
|
226
252
|
### Exporting a method
|
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.
|
253
|
+
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 with an optional export name of your choice.
|
254
|
+
|
255
|
+
If no function name is specified, Rlang will build it for you. WASM exported functions are named after the class name (in lower case), the method type (class or instance) and the method name. As an example the exported method in the example above will be known to the WASM runtime as the `myclass_c_visible` function (where the `_c_` means it's a class function and `_i_` an instance method).
|
228
256
|
|
229
257
|
```ruby
|
230
258
|
class MyClass
|
@@ -234,31 +262,53 @@ class MyClass
|
|
234
262
|
# ...
|
235
263
|
end
|
236
264
|
|
265
|
+
export :seeable
|
266
|
+
def self.visible_too(arg1)
|
267
|
+
# ...
|
268
|
+
end
|
269
|
+
|
237
270
|
def self.not_visible
|
238
271
|
# ...
|
239
272
|
end
|
240
273
|
end
|
241
274
|
```
|
242
275
|
|
243
|
-
Note that the `export` keyword only applies to the method definition that immediately follows. In the example above `MyClass::
|
276
|
+
Note that the `export` keyword only applies to the method definition that immediately follows. In the example above `MyClass::visible` and `MyClass::visible_too` will be exported by the generated WASM module whereas `MyClass::not_visible` will not.
|
277
|
+
|
278
|
+
### Importing a method
|
279
|
+
An import statement in WebAssembly is a way to declare the signature of a method defined outside of the current WebAssembly module and then call it from your code.
|
280
|
+
|
281
|
+
Rlang has an equivalent import statement as shown in the example below:
|
282
|
+
```ruby
|
283
|
+
import :wasi_unstable, :proc_exit
|
284
|
+
def self.proc_exit(exitcode)
|
285
|
+
arg exitcode: :I32
|
286
|
+
result :none
|
287
|
+
end
|
288
|
+
```
|
289
|
+
|
290
|
+
The first 2 arguments of import are the imported module name and function name as in WebAssembly and then follows a regular method definition with arguments and possibly the `arg` directive to specify argument types and a `result` directive to indicate the nature of the returned value. Two points worth highlighting here:
|
291
|
+
1. The Rlang method name doesn't have to be the same as the function name in the import statement.
|
292
|
+
2. As imported function are external to your Rlang module they must be declared as class methods. This is bacause defining them as instance method would automatically pass `self` as the first argument in the method call therefore changing the signature of the method.
|
293
|
+
|
294
|
+
The example above is taken from the Rlang WASI class that defines the interface with [WASI (WebAssembly System Interface)](https::wasi.dev).
|
244
295
|
|
245
|
-
WASM exported functions are named after the class name (in lower case) followed by an underscore and the method name. So the exported method in the example above is known to the WASM runtime as the `myclass_c_visible` function (where the `_c_` means it's a class function and `_i_` an instance method)
|
246
296
|
|
247
297
|
## Rlang types
|
248
|
-
The types currently supported by Rlang are integers either long (`:I32`)
|
298
|
+
The types currently supported by Rlang are integers either signed long (`:I32`), unsigned long (`:UI32`), signed double (`:I64`), unsigned double (`:UI64`) or a class type. Float types (`:F32`, `:F64`) may follow in a future version. By default Rlang assumes that any integer literal, variable, argument,... is of type `:I32`. If you need it to be of a different type you must declare it explicitely in the method body (see above) or cast it explicitely (see below).
|
249
299
|
|
250
300
|
### Implicit type cast
|
251
|
-
Only in rare cases will you use the `local` directive in methods as Rlang does its best to infer the type of a variable from its first assigned value. As an example, in the code below, the fact that `arg1` is known to be an `:I64` type of argument is enough to auto-magically create lvar as
|
301
|
+
Only in rare cases will you use the `local` directive in methods as Rlang does its best to infer the type of a variable from its first assigned value. As an example, in the code below, the fact that `arg1` is known to be an `:I64` type of argument is enough to auto-magically create lvar as an `:I64` local variable too.
|
252
302
|
|
253
303
|
```ruby
|
254
|
-
def
|
304
|
+
def m_local_var(arg1)
|
255
305
|
arg :arg1, :I64
|
256
306
|
lvar = arg1 * 100
|
257
307
|
# ....
|
258
308
|
end
|
259
309
|
```
|
260
310
|
|
261
|
-
Conversely in the method below the first statement `lvar = 10` auto-vivifies `lvar` as a variable of type `:I32` (the default Rlang type). On the next line, Rlang evaluates `arg1 * 100` as an `:I64` result because `arg1` is declared as being of type `:I64`. Similarly as the type of `lvar` local variable was auto-vivified as `:I32`, the result of the expression `arg1 * 100` will be type cast from `:I64` to `:I32`. Note that
|
311
|
+
Conversely in the method below the first statement `lvar = 10` auto-vivifies `lvar` as a variable of type `:I32` (the default Rlang type). On the next line, Rlang evaluates `arg1 * 100` as an `:I64` result because `arg1` is declared as being of type `:I64`. Similarly as the type of `lvar` local variable was auto-vivified as `:I32`, the result of the expression `arg1 * 100` will be type cast from `:I64` to `:I32`. Note that such a type cast may of course result in the value being truncated and the Rlang compiler will emit a warning accordingly.
|
262
312
|
|
263
313
|
```ruby
|
264
314
|
def self.m_local_var(arg1)
|
@@ -269,6 +319,33 @@ def self.m_local_var(arg1)
|
|
269
319
|
end
|
270
320
|
```
|
271
321
|
|
322
|
+
### Implicit type cast precedence
|
323
|
+
|
324
|
+
Rlang follows the C IEC/ISO Standard to implicitely convert types. When operands of different types are mixed in arithmetic or relational operators, Rlang will conver them according to the following priority order from the highest priority to the lowest:
|
325
|
+
|
326
|
+
`:F64`, `:F32`, `:UI64`, `:I64`, `:Class`, `:UI32`, `:I32`
|
327
|
+
|
328
|
+
Let's take a few examples:
|
329
|
+
* If you use 32 bit floating point numbers and 64 bit floating point numbers in the same expression all `:F32` values will be converted to `:F64`
|
330
|
+
* Similarly if you mix `:F32` values and `:I64` values, all 64-bit integers will be converted to 32-bit floating point values before evaluating the expression
|
331
|
+
* **IMPORTANT** note that unsigned integers `:UI64` and `:UI32`have a higher priority than their signed counter part `:I64` and `:I32`. This means that when mixing signed and unsigned integer values in the same expression, the signed integers will be reinterpreted as its unsigned equivalent bitwise. As an example in this context an `:I32` with a value of -6 will be reinterpreted as an`:UI32` with a value of 4294967290. So be very cautious when mixing signed and unsigned integers as this may be give somewhat suprising results especially when using relational operators such >, <, >=,...
|
332
|
+
|
333
|
+
### Pointer arithmetics
|
334
|
+
|
335
|
+
In the types priority order mentioned above, the `:Class` type designate any value pointing to a class instance, in other word an object. This means that object pointers are values that you can process through arithmetic and relational operators like in C. A pointer to an object is a 32 bit memory address that has type casting precedence over `:UI32` and `:I32` types.
|
336
|
+
|
337
|
+
Consequently when doing some arithmetics on object pointers like an addition or a substraction the integer vale will be interpreted as a multiple of the object size as if you were moving an index over objects of the same type in memory. Again this behavior is similar to the C language.
|
338
|
+
|
339
|
+
|
340
|
+
```ruby
|
341
|
+
class MyClass
|
342
|
+
up = Header.new
|
343
|
+
next_hdr = up + 1
|
344
|
+
#...
|
345
|
+
end
|
346
|
+
```
|
347
|
+
In the example above, a new header object is created and it meory location is stored in the up variable. When adding 1 to the up variable, Rlang knows that up is of type Header and will therefore increase up not by 1 but by the size in byted of the Header Object in memory. You can see examples of pointer arithmetics in the memory allocator of Rlang in lib/malloc.rb.
|
348
|
+
|
272
349
|
### Explicit type cast
|
273
350
|
If Rlang is not capable of guessing the proper type of an expression or variable, you can explicitely cast it to any known type. Look at this example:
|
274
351
|
|
@@ -285,7 +362,7 @@ The first line will auto-vivify the `@@cvar` class variable as type `:I64`.
|
|
285
362
|
|
286
363
|
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.
|
287
364
|
|
288
|
-
For
|
365
|
+
For type cast a number or an expression to native type you can also use shortcuts like `100.to_I64` or `100.to_UI32`, etc...
|
289
366
|
|
290
367
|
Note that type cast can be used anywhere in the code whether in class body or method definition.
|
291
368
|
|
@@ -461,6 +538,7 @@ For now, the Rlang library is very modest and containoffers the following classe
|
|
461
538
|
* Malloc.malloc(nbytes) : dynamically allocates nbytes of memory and return address of the allocated memory space or -1 if it fails
|
462
539
|
* Malloc.free(address) : frees the block of memory at the given address
|
463
540
|
* **Object** class: provides a couple of object management method. Use `Object.free` to free an object.
|
541
|
+
* **Array32**, **Array64** : Arrays can be initialized with the usual bracket operator '[]'. When the array is initialized at compile time (statically) such as for constants or class variables you can only use integers in the array initializers (e.g.`[0, 100, 200]`). When the array is initialized at runtime (dynamically) such as in the body of a method definition you can either use integers or any Rlang object (e.g. `[ 1, 2, "A string", my_object]`)
|
464
542
|
* **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.
|
465
543
|
|
466
544
|
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).
|
data/examples/fib/fib.rb
CHANGED
@@ -17,23 +17,10 @@ module Builder::Rlang
|
|
17
17
|
# WAT source frame
|
18
18
|
WAT_FRAME = %q{
|
19
19
|
;; Generated by Rlang compiler version %{version} on %{time}\n"
|
20
|
-
(module %{module}
|
21
|
-
(memory $0 %{memory_min} %{memory_max})
|
22
20
|
|
23
|
-
|
24
|
-
(export "memory" (memory $0))
|
25
|
-
%{exports}
|
21
|
+
%{code}
|
26
22
|
|
27
|
-
|
28
|
-
%{globals}
|
29
|
-
|
30
|
-
;; ======= STATIC DATA =======
|
31
|
-
%{data}
|
32
|
-
|
33
|
-
;; ======= CODE =======
|
34
|
-
%{code}
|
35
|
-
)
|
36
|
-
}
|
23
|
+
}
|
37
24
|
|
38
25
|
# source: Path to Rlang file (.rb)
|
39
26
|
# target: Path to Wat file (.wat)
|
@@ -64,12 +51,6 @@ module Builder::Rlang
|
|
64
51
|
end
|
65
52
|
@tf.write(WAT_FRAME % {version: Rlang::VERSION,
|
66
53
|
time: Time.now,
|
67
|
-
module: @options[:module],
|
68
|
-
memory_min: @options[:memory_min],
|
69
|
-
memory_max: @options[:memory_max],
|
70
|
-
exports: Rlang::Parser::Export.transpile,
|
71
|
-
globals: Rlang::Parser::Global.transpile,
|
72
|
-
data: Rlang::Parser::DAta.transpile,
|
73
54
|
code: @wgenerator.root.transpile
|
74
55
|
})
|
75
56
|
@tf.close
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# Rubinius WebAssembly VM
|
2
|
+
# Copyright (c) 2019-2020, Laurent Julliard and contributors
|
3
|
+
# All rights reserved.
|
4
|
+
#
|
5
|
+
# 4 bytes object array class
|
6
|
+
require_relative '../memory'
|
7
|
+
require_relative '../kernel'
|
8
|
+
require_relative '../type'
|
9
|
+
#require_relative '../string'
|
10
|
+
|
11
|
+
|
12
|
+
class Array32
|
13
|
+
attr_reader :count, :ptr
|
14
|
+
|
15
|
+
result :Kernel, :raise, :none
|
16
|
+
|
17
|
+
# count: number of elements in Array
|
18
|
+
# Array elements are native types or
|
19
|
+
# pointers to objects
|
20
|
+
# Arrays are fixed size for now
|
21
|
+
def initialize(count)
|
22
|
+
if count == 0
|
23
|
+
@ptr = 0
|
24
|
+
else
|
25
|
+
# Memory size is count * 4 bytes
|
26
|
+
@ptr = Object.allocate(count << 2)
|
27
|
+
end
|
28
|
+
@count = count
|
29
|
+
end
|
30
|
+
|
31
|
+
def size; @count; end
|
32
|
+
def length; @count; end
|
33
|
+
def empty?; @count == 0; end
|
34
|
+
|
35
|
+
def [](idx)
|
36
|
+
result :I32
|
37
|
+
raise "Index out of bound" if idx >= @count || idx < -@count
|
38
|
+
idx = @count + idx if idx <0
|
39
|
+
# offset in memory for elt #idx is idx * 4
|
40
|
+
Memory.load32(@ptr + (idx << 2))
|
41
|
+
end
|
42
|
+
|
43
|
+
def []=(idx, value)
|
44
|
+
arg value: :I32
|
45
|
+
result :I32
|
46
|
+
raise "Index out of bound" if idx >= @count || idx < -@count
|
47
|
+
idx = @count + idx if idx <0
|
48
|
+
# offset in memory for elt #idx is idx * 4
|
49
|
+
Memory.store32(@ptr + (idx << 2), value)
|
50
|
+
value
|
51
|
+
end
|
52
|
+
|
53
|
+
def free
|
54
|
+
result :none
|
55
|
+
Object.free(@ptr)
|
56
|
+
Object.free(self)
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|