opal 0.3.19 → 0.3.20
Sign up to get free protection for your applications and to get access to all the features.
- 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
|