opal 0.3.19 → 0.3.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +3 -1
- data/Gemfile +3 -2
- data/README.md +304 -48
- data/Rakefile +1 -2
- data/core/alpha.rb +2 -1
- data/core/array.rb +92 -96
- data/core/basic_object.rb +1 -10
- data/core/boolean.rb +6 -18
- data/core/class.rb +9 -10
- data/core/comparable.rb +1 -1
- data/core/enumerable.rb +11 -11
- data/core/enumerator.rb +2 -10
- data/core/error.rb +16 -31
- data/core/hash.rb +32 -36
- data/core/json.rb +50 -0
- data/core/kernel.rb +48 -57
- data/core/load_order +3 -5
- data/core/module.rb +37 -35
- data/core/nil_class.rb +4 -0
- data/core/numeric.rb +10 -30
- data/core/proc.rb +1 -1
- data/core/range.rb +3 -4
- data/core/regexp.rb +21 -6
- data/core/runtime.js +278 -370
- data/core/string.rb +21 -37
- data/core/struct.rb +11 -3
- data/core/time.rb +44 -37
- data/lib/opal.rb +3 -3
- data/lib/opal/builder.rb +48 -27
- data/lib/opal/builder_task.rb +3 -20
- data/lib/opal/grammar.rb +18 -13
- data/lib/opal/grammar.y +7 -4
- data/lib/opal/parser.rb +290 -199
- data/lib/opal/scope.rb +187 -176
- data/lib/opal/version.rb +1 -1
- data/test/core/kernel/define_singleton_method_spec.rb +21 -0
- data/test/core/time/at_spec.rb +7 -0
- data/test/core/time/day_spec.rb +5 -0
- data/test/core/time/friday_spec.rb +9 -0
- data/test/core/time/hour_spec.rb +5 -0
- data/test/core/time/min_spec.rb +5 -0
- data/test/core/time/monday_spec.rb +9 -0
- data/test/core/time/month_spec.rb +5 -0
- data/test/core/time/now_spec.rb +5 -0
- data/test/core/time/saturday_spec.rb +9 -0
- data/test/index.html +2 -1
- data/test/language/singleton_class_spec.rb +0 -16
- data/test/opal/array/to_json_spec.rb +7 -0
- data/test/opal/boolean/singleton_class_spec.rb +9 -0
- data/test/opal/boolean/to_json_spec.rb +9 -0
- data/test/opal/hash/to_json_spec.rb +9 -0
- data/test/opal/json/parse_spec.rb +31 -0
- data/test/opal/kernel/to_json_spec.rb +5 -0
- data/test/opal/nil/to_json_spec.rb +5 -0
- data/test/opal/numeric/to_json_spec.rb +6 -0
- data/test/opal/runtime/call_spec.rb +16 -0
- data/test/opal/runtime/defined_spec.rb +11 -0
- data/test/opal/runtime/super_spec.rb +16 -0
- data/test/opal/string/to_json_spec.rb +6 -0
- data/test/spec_helper.rb +1 -3
- metadata +48 -15
- data/core/dir.rb +0 -89
- data/core/file.rb +0 -85
- data/core/match_data.rb +0 -35
- data/core/rational.rb +0 -16
- data/test/core/file/expand_path_spec.rb +0 -20
data/.gitignore
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -19,7 +19,7 @@ is a Freenode IRC channel at `#opal`.
|
|
19
19
|
The Opal runtime and corelib are distributed here, and are required to
|
20
20
|
run any code generated by opal.
|
21
21
|
|
22
|
-
[Opal version 0.3.
|
22
|
+
[Opal version 0.3.20](http://opalrb.org/opal.js) _(14.3kb Minified And Gzipped)_
|
23
23
|
|
24
24
|
## Installation
|
25
25
|
|
@@ -41,18 +41,22 @@ method which returns a string of javascript code:
|
|
41
41
|
```ruby
|
42
42
|
require 'opal'
|
43
43
|
|
44
|
-
Opal.parse("
|
44
|
+
Opal.parse("puts 'hello world'")
|
45
45
|
```
|
46
46
|
|
47
|
-
This will return a string of javascript
|
47
|
+
This will return a string of javascript:
|
48
48
|
|
49
49
|
```javascript
|
50
50
|
(function() {
|
51
|
-
|
52
|
-
|
51
|
+
var self = Opal.top;
|
52
|
+
self.$puts('hello world');
|
53
|
+
})();
|
53
54
|
```
|
54
55
|
|
55
|
-
This can then be written to a file and run in any browser.
|
56
|
+
This can then be written to a file and run in any browser. `Opal` is a
|
57
|
+
global javascript variable that holds all the ruby classes and methods.
|
58
|
+
`Opal.top` points to the top object in opal, so any file will have this
|
59
|
+
object as its top level `self` variable.
|
56
60
|
|
57
61
|
### Creating a rake task
|
58
62
|
|
@@ -98,7 +102,7 @@ opal runtime needs to be loaded first (you can download that above).
|
|
98
102
|
|
99
103
|
When using `Opal.parse()` as above, the generated code will be run
|
100
104
|
as soon as the page loads. Open the browsers console and you should
|
101
|
-
see the
|
105
|
+
see the message printed to the console.
|
102
106
|
|
103
107
|
## Builder and Dependency Builder
|
104
108
|
|
@@ -114,7 +118,7 @@ require 'opal'
|
|
114
118
|
|
115
119
|
Opal::BuilderTask.new do |t|
|
116
120
|
t.name = 'my-first-app'
|
117
|
-
t.dependencies = ['opal-
|
121
|
+
t.dependencies = ['opal-spec']
|
118
122
|
t.files = ['app.rb']
|
119
123
|
end
|
120
124
|
```
|
@@ -124,16 +128,16 @@ dependencies.
|
|
124
128
|
|
125
129
|
### Building dependencies
|
126
130
|
|
127
|
-
To build the opal runtime `opal.js`, as well as `opal-
|
131
|
+
To build the opal runtime `opal.js`, as well as `opal-spec` into
|
128
132
|
`build/`, run the simple rake task:
|
129
133
|
|
130
134
|
```ruby
|
131
135
|
rake dependencies
|
132
136
|
```
|
133
137
|
|
134
|
-
This will try and find the `opal-
|
135
|
-
install globally with `gem install opal-
|
136
|
-
Gemfile as `gem "opal-
|
138
|
+
This will try and find the `opal-spec` gem installed, so either
|
139
|
+
install globally with `gem install opal-spec`, or add it to your
|
140
|
+
Gemfile as `gem "opal-spec"` and run `bundle install`.
|
137
141
|
|
138
142
|
### Building app
|
139
143
|
|
@@ -158,28 +162,12 @@ You should now be able to run the built app using a standard HTML page.
|
|
158
162
|
</head>
|
159
163
|
<body>
|
160
164
|
<script src="build/opal.js"></script>
|
161
|
-
<script src="build/opal-
|
165
|
+
<script src="build/opal-spec.js"></script>
|
162
166
|
<script src="build/my-first-app.js"></script>
|
163
167
|
</body>
|
164
168
|
</html>
|
165
169
|
```
|
166
170
|
|
167
|
-
### Main file
|
168
|
-
|
169
|
-
When using the `BuilderTask`, the files generated will not be run
|
170
|
-
automatically on page load. The files are registered so they can be
|
171
|
-
loaded using `require()`. Builder is clever enough though that it will,
|
172
|
-
by default, automatically require the first file in the app to be
|
173
|
-
loaded. This will appear at the bottom of `my-first-app.js`:
|
174
|
-
|
175
|
-
```javascript
|
176
|
-
Opal.define('app', function() {
|
177
|
-
// app.rb code
|
178
|
-
});
|
179
|
-
|
180
|
-
Opal.require('app');
|
181
|
-
```
|
182
|
-
|
183
171
|
## Features And Implementation
|
184
172
|
|
185
173
|
Opal is a source-to-source compiler, so there is no VM as such and the
|
@@ -274,6 +262,49 @@ range instances.
|
|
274
262
|
3...7 # => __range(3, 7, false)
|
275
263
|
```
|
276
264
|
|
265
|
+
### Classes and Modules
|
266
|
+
|
267
|
+
A compiled class or module body is simply just an anonymous javascript
|
268
|
+
function that is used to keep any internal variables from escaping.
|
269
|
+
|
270
|
+
Classes and modules themselves are created as named functions (which is
|
271
|
+
used purely to aid debugging). Classes and modules may also be
|
272
|
+
re-opended, so they are put through a runtime function `__klass` for
|
273
|
+
classes, and `__module` for modules.
|
274
|
+
|
275
|
+
For example, the `Kernel` module is generated as:
|
276
|
+
|
277
|
+
```javascript
|
278
|
+
(function(__base) {
|
279
|
+
function Kernel(){};
|
280
|
+
Kernel = __module(__base, "Kernel", Kernel);
|
281
|
+
var Kernel_prototype = Kernel.prototype;
|
282
|
+
|
283
|
+
Kernel_prototype.$class = function() {
|
284
|
+
return this._real;
|
285
|
+
};
|
286
|
+
|
287
|
+
Kernel._donate('$class', ...);
|
288
|
+
})(__scope);
|
289
|
+
```
|
290
|
+
|
291
|
+
Firstly, the `__scope` is passed into the function which is used
|
292
|
+
for defining constants, which means that if `Kernel` gets defined
|
293
|
+
here then it can be set as a constant for the given scope. The
|
294
|
+
named function (Kernel) follows, as well as the `__module()` call
|
295
|
+
which simply sets the up the module if it doesn't already exist. If
|
296
|
+
it does exist, then it overwrites the previous `Kernel` local variable.
|
297
|
+
|
298
|
+
Next, `Kernel_prototype` is made a local variable to improve
|
299
|
+
minimization of the code, then all the Kernel methods are set on the
|
300
|
+
prototype. Modules cannot be instantiated in Opal (just like ruby), but
|
301
|
+
the use of prototypes is to improve performance.
|
302
|
+
|
303
|
+
Finally, the call to `Kernel._donate()` is to pass any defined methods
|
304
|
+
into classes that have included `Kernel`, which is just `Object`. This
|
305
|
+
method then adds all of Kernels methods into Objects prototype as this
|
306
|
+
is the most efficient way to try and emulate rubys method chain.
|
307
|
+
|
277
308
|
### Methods
|
278
309
|
|
279
310
|
A ruby method is just compiled directly into a function definition.
|
@@ -352,12 +383,12 @@ def to_s
|
|
352
383
|
end
|
353
384
|
```
|
354
385
|
|
355
|
-
This would generate the following javascript. (`
|
356
|
-
|
357
|
-
|
386
|
+
This would generate the following javascript. (`Array_prototype` is
|
387
|
+
an example and would assume this method definition is inside the
|
388
|
+
`Array` class body).
|
358
389
|
|
359
390
|
```javascript
|
360
|
-
|
391
|
+
Array_prototype.$to_s = function() {
|
361
392
|
return this.$inspect();
|
362
393
|
};
|
363
394
|
```
|
@@ -384,16 +415,16 @@ end
|
|
384
415
|
The generated code reads as expected:
|
385
416
|
|
386
417
|
```javascript
|
387
|
-
|
418
|
+
Array_prototype.$norm = function(a, b, c) {
|
388
419
|
return nil;
|
389
420
|
};
|
390
421
|
|
391
|
-
|
422
|
+
Array_prototype.$opt = function(a, b) {
|
392
423
|
if (b == null) b = 100;
|
393
424
|
return nil;
|
394
425
|
};
|
395
426
|
|
396
|
-
|
427
|
+
Array_prototype.$rest = function(a, b) {
|
397
428
|
b = __slice.call(arguments, 1);
|
398
429
|
return nil;
|
399
430
|
};
|
@@ -404,6 +435,144 @@ the correct number of arguments get passed to a function. This can be
|
|
404
435
|
enabled in debug mode, but is not included in production builds as it
|
405
436
|
adds a lot of overhead to **every** method call.
|
406
437
|
|
438
|
+
### Logic and conditionals
|
439
|
+
|
440
|
+
As per ruby, Opal treats only `false` and `nil` as falsy, everything
|
441
|
+
else is a truthy value including `""`, `0` and `[]`. This differs from
|
442
|
+
javascript as these values are also treated as false.
|
443
|
+
|
444
|
+
For this reason, most truthy tests must check if values are `false` or
|
445
|
+
`nil`.
|
446
|
+
|
447
|
+
Taking the following test:
|
448
|
+
|
449
|
+
```javascript
|
450
|
+
val = 42
|
451
|
+
|
452
|
+
if val
|
453
|
+
return 3.142;
|
454
|
+
end
|
455
|
+
```
|
456
|
+
|
457
|
+
This would be compiled into:
|
458
|
+
|
459
|
+
```ruby
|
460
|
+
val = 42;
|
461
|
+
|
462
|
+
if (val !== false && val !== nil) {
|
463
|
+
return 3.142;
|
464
|
+
}
|
465
|
+
```
|
466
|
+
|
467
|
+
This makes the generated truthy tests (`if` statements, `and` checks and
|
468
|
+
`or` statements) a litle more verbose in the generated code.
|
469
|
+
|
470
|
+
### Instance variables
|
471
|
+
|
472
|
+
Instance variables in Opal work just as expected. When ivars are set or
|
473
|
+
retrieved on an object, they are set natively without the `@` prefix.
|
474
|
+
This allows real javascript identifiers to be used which is more
|
475
|
+
efficient then accessing variables by string name.
|
476
|
+
|
477
|
+
```ruby
|
478
|
+
@foo = 200
|
479
|
+
@foo # => 200
|
480
|
+
|
481
|
+
@bar # => nil
|
482
|
+
```
|
483
|
+
|
484
|
+
This gets compiled into:
|
485
|
+
|
486
|
+
```javascript
|
487
|
+
this.foo = 200;
|
488
|
+
this.foo; // => 200
|
489
|
+
|
490
|
+
this.bar; // => nil
|
491
|
+
```
|
492
|
+
|
493
|
+
The only point of warning is that when variables are used for the
|
494
|
+
first time in ruby, they default to `nil`. In javascript, they default
|
495
|
+
to `undefined`/`null`.
|
496
|
+
|
497
|
+
To keep things working in opal, ivars must be preset to `nil` before
|
498
|
+
they can be used. In the top scope and other corner cases, this needs
|
499
|
+
to be done on a per scope basis, which can add overhead.
|
500
|
+
|
501
|
+
To improve performance, once a class body is compiled, all ivars used
|
502
|
+
within methods in that class are preset on the prototype of the class
|
503
|
+
to be `nil`. This means that all known ivars are already set to nil,
|
504
|
+
and this is done just once during the lifespan of the app.
|
505
|
+
|
506
|
+
```ruby
|
507
|
+
class Foo
|
508
|
+
def bar
|
509
|
+
@lol
|
510
|
+
end
|
511
|
+
|
512
|
+
def woosh
|
513
|
+
@kapow
|
514
|
+
end
|
515
|
+
end
|
516
|
+
```
|
517
|
+
|
518
|
+
This example gets compiled into something similar to:
|
519
|
+
|
520
|
+
```javascript
|
521
|
+
(function() {
|
522
|
+
function Foo(){}
|
523
|
+
// ...
|
524
|
+
|
525
|
+
Foo.prototype.lol = Foo.prototype.woosh = nil;
|
526
|
+
|
527
|
+
Foo.prototype.$bar = function() {
|
528
|
+
return this.lol;
|
529
|
+
};
|
530
|
+
|
531
|
+
// etc ...
|
532
|
+
})()
|
533
|
+
```
|
534
|
+
|
535
|
+
### Interacting with javascript
|
536
|
+
|
537
|
+
Opal tries to interact as cleanly with javascript and its api as much
|
538
|
+
as possible. Ruby arrays, strings, numbers, regexps, blocks and booleans
|
539
|
+
are just javascript native equivalents. The only boxed core features are
|
540
|
+
hashes and nil.
|
541
|
+
|
542
|
+
As most of the corelib deals with these low level details, opal provides
|
543
|
+
a special syntax for inlining javascript code. This is done with
|
544
|
+
x-strings or "backticks", as their ruby use has no useful translation
|
545
|
+
in the browser.
|
546
|
+
|
547
|
+
```ruby
|
548
|
+
`window.title`
|
549
|
+
# => "Opal: ruby to javascript compiler"
|
550
|
+
|
551
|
+
%x{
|
552
|
+
console.log("ruby version is:");
|
553
|
+
console.log(#{ OPAL_VERSION });
|
554
|
+
}
|
555
|
+
|
556
|
+
# => ruby version is:
|
557
|
+
# => 0.3.19
|
558
|
+
```
|
559
|
+
|
560
|
+
Even interpolations are supported, as seen here.
|
561
|
+
|
562
|
+
This feature of inlining code is used extensively, for example in
|
563
|
+
Array#length:
|
564
|
+
|
565
|
+
```ruby
|
566
|
+
class Array
|
567
|
+
def length
|
568
|
+
`this.length`
|
569
|
+
end
|
570
|
+
end
|
571
|
+
```
|
572
|
+
|
573
|
+
X-Strings also have the ability to automatically return their value,
|
574
|
+
as used by this example.
|
575
|
+
|
407
576
|
### Compiled Files
|
408
577
|
|
409
578
|
As described above, a compiled ruby source gets generated into a string
|
@@ -411,17 +580,16 @@ of javascript code that is wrapped inside an anonymous function. This
|
|
411
580
|
looks similar to the following:
|
412
581
|
|
413
582
|
```javascript
|
414
|
-
(function(
|
415
|
-
var nil = Opal.nil,
|
583
|
+
(function() {
|
584
|
+
var nil = Opal.nil, self = Opal.top;
|
416
585
|
// generated code
|
417
|
-
})
|
586
|
+
})();
|
418
587
|
```
|
419
588
|
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
from these functions as they are not used anywhere.
|
589
|
+
Inside the function, `nil` is assigned to ensure a local copy is
|
590
|
+
available, as well as all the helper methods used within the
|
591
|
+
generated file. There is no return value from these functions as they
|
592
|
+
are not used anywhere.
|
425
593
|
|
426
594
|
As a complete example, assuming the following code:
|
427
595
|
|
@@ -432,10 +600,10 @@ puts "foo"
|
|
432
600
|
This would compile directly into:
|
433
601
|
|
434
602
|
```javascript
|
435
|
-
(function(
|
436
|
-
var nil = Opal.nil;
|
437
|
-
|
438
|
-
})
|
603
|
+
(function() {
|
604
|
+
var nil = Opal.nil, self = Opal.top;
|
605
|
+
self.$puts("foo");
|
606
|
+
})();
|
439
607
|
```
|
440
608
|
|
441
609
|
Most of the helpers are no longer present as they are not used in this
|
@@ -447,12 +615,100 @@ If you write the generated code as above into a file `app.js` and add
|
|
447
615
|
that to your HTML page, then it is obvious that `"foo"` would be
|
448
616
|
written to the browser's console.
|
449
617
|
|
618
|
+
### JSON
|
619
|
+
|
620
|
+
The opal corelib includes JSON support instead of treating it as an
|
621
|
+
external lib. The `JSON` module provides the usual parsing methods.
|
622
|
+
|
623
|
+
```ruby
|
624
|
+
JSON.parse '{"a": 10, "b": [1, 2, 3], "c": null}'
|
625
|
+
# => { "a" => 10, "b" => [1, 2, 3], "c" => nil }
|
626
|
+
```
|
627
|
+
|
628
|
+
Opal expects `JSON` to be present in the browser, so older browsers
|
629
|
+
may require a shim (json2.js) to work with opal. Most mobile browsers
|
630
|
+
and modern desktop browsers include json support natively.
|
631
|
+
|
632
|
+
## Debugging and finding errors
|
633
|
+
|
634
|
+
Because Opal does not aim to be fully compatible with ruby, there are
|
635
|
+
some instances where things can break and it may not be entirely
|
636
|
+
obvious what went wrong.
|
637
|
+
|
638
|
+
### Undefined methods
|
639
|
+
|
640
|
+
By default, opal aims to be as fast as possible, so `method_missing` is
|
641
|
+
not turned on by default. Instead, when calling a method that doesn't
|
642
|
+
exist, a native error will be raised.
|
643
|
+
|
644
|
+
```ruby
|
645
|
+
self.do_something()
|
646
|
+
```
|
647
|
+
|
648
|
+
Might raise an error similar to:
|
649
|
+
|
650
|
+
```
|
651
|
+
Error: 'undefined' is not a function (evaluating 'this.$do_something()')
|
652
|
+
```
|
653
|
+
|
654
|
+
As described above, all ruby methods will have a `$` prefix which gives
|
655
|
+
a good indication that it is a opal method that doesnt exist, and most
|
656
|
+
js engines output the missing function name.
|
657
|
+
|
658
|
+
### Undefined constants
|
659
|
+
|
660
|
+
If trying to access a constant that doesn't exist, there is no runtime
|
661
|
+
error. Instead, the value of that expression is just `undefined` as
|
662
|
+
constants are retrieved from objects that hold all constants in the
|
663
|
+
scope. Trying to send a method to an undefined constant will therefore
|
664
|
+
just raise an ugly javascript `TypeError`.
|
665
|
+
|
666
|
+
If you are using the constant as a reference, it may not be until much
|
667
|
+
later that the error occurs.
|
668
|
+
|
669
|
+
### Using javascript debuggers
|
670
|
+
|
671
|
+
As opal just generates javascript, it is useful to use a native
|
672
|
+
debugger to work through javascript code. To use a debugger, simply
|
673
|
+
add an x-string similar to the following at the place you wish to
|
674
|
+
debug:
|
675
|
+
|
676
|
+
```ruby
|
677
|
+
# .. code
|
678
|
+
`debugger`
|
679
|
+
# .. more code
|
680
|
+
```
|
681
|
+
The x-strings just pass the debugger statement straight through to the
|
682
|
+
javascript output.
|
683
|
+
|
684
|
+
Inside methods and blocks, the current `self` value is always the
|
685
|
+
native `this` value. You will not see `self` inside debuggers as it is
|
686
|
+
never used to refer to the actual ruby self value.
|
687
|
+
|
688
|
+
All local variables and method/block arguments also keep their ruby
|
689
|
+
names except in the rare cases when the name is reserved in javascript.
|
690
|
+
In these cases, a `$` suffix is added to the name (e.g. `try` =>
|
691
|
+
`try$`).
|
692
|
+
|
450
693
|
## License
|
451
694
|
|
452
695
|
Opal is released under the MIT license.
|
453
696
|
|
454
697
|
## Change Log
|
455
698
|
|
699
|
+
**0.3.20** _(23 June 2012)_
|
700
|
+
|
701
|
+
* Merge JSON into core. JSON module and various #to_json methods are
|
702
|
+
now included as part of corelib
|
703
|
+
* Make `Time` class bridge to native `Date` constructor
|
704
|
+
* Use named functions as class constuctors to make debugging easier
|
705
|
+
* Classes are now real functions with prototypes. Bridged classes are
|
706
|
+
now directly corresponding to the ruby class (e.g. Array === Opal.Array)
|
707
|
+
* Set ivars used inside methods in class to `nil` inside class definition
|
708
|
+
to avoid doing it everytime method is called
|
709
|
+
* Add debug comments to output for def, class and module stating the file
|
710
|
+
and line number the given code was generated from
|
711
|
+
|
456
712
|
**0.3.19** _(30 May 2012)_
|
457
713
|
|
458
714
|
* Add BasicObject as the root class
|