opal 0.3.19 → 0.3.20

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. data/.gitignore +3 -1
  2. data/Gemfile +3 -2
  3. data/README.md +304 -48
  4. data/Rakefile +1 -2
  5. data/core/alpha.rb +2 -1
  6. data/core/array.rb +92 -96
  7. data/core/basic_object.rb +1 -10
  8. data/core/boolean.rb +6 -18
  9. data/core/class.rb +9 -10
  10. data/core/comparable.rb +1 -1
  11. data/core/enumerable.rb +11 -11
  12. data/core/enumerator.rb +2 -10
  13. data/core/error.rb +16 -31
  14. data/core/hash.rb +32 -36
  15. data/core/json.rb +50 -0
  16. data/core/kernel.rb +48 -57
  17. data/core/load_order +3 -5
  18. data/core/module.rb +37 -35
  19. data/core/nil_class.rb +4 -0
  20. data/core/numeric.rb +10 -30
  21. data/core/proc.rb +1 -1
  22. data/core/range.rb +3 -4
  23. data/core/regexp.rb +21 -6
  24. data/core/runtime.js +278 -370
  25. data/core/string.rb +21 -37
  26. data/core/struct.rb +11 -3
  27. data/core/time.rb +44 -37
  28. data/lib/opal.rb +3 -3
  29. data/lib/opal/builder.rb +48 -27
  30. data/lib/opal/builder_task.rb +3 -20
  31. data/lib/opal/grammar.rb +18 -13
  32. data/lib/opal/grammar.y +7 -4
  33. data/lib/opal/parser.rb +290 -199
  34. data/lib/opal/scope.rb +187 -176
  35. data/lib/opal/version.rb +1 -1
  36. data/test/core/kernel/define_singleton_method_spec.rb +21 -0
  37. data/test/core/time/at_spec.rb +7 -0
  38. data/test/core/time/day_spec.rb +5 -0
  39. data/test/core/time/friday_spec.rb +9 -0
  40. data/test/core/time/hour_spec.rb +5 -0
  41. data/test/core/time/min_spec.rb +5 -0
  42. data/test/core/time/monday_spec.rb +9 -0
  43. data/test/core/time/month_spec.rb +5 -0
  44. data/test/core/time/now_spec.rb +5 -0
  45. data/test/core/time/saturday_spec.rb +9 -0
  46. data/test/index.html +2 -1
  47. data/test/language/singleton_class_spec.rb +0 -16
  48. data/test/opal/array/to_json_spec.rb +7 -0
  49. data/test/opal/boolean/singleton_class_spec.rb +9 -0
  50. data/test/opal/boolean/to_json_spec.rb +9 -0
  51. data/test/opal/hash/to_json_spec.rb +9 -0
  52. data/test/opal/json/parse_spec.rb +31 -0
  53. data/test/opal/kernel/to_json_spec.rb +5 -0
  54. data/test/opal/nil/to_json_spec.rb +5 -0
  55. data/test/opal/numeric/to_json_spec.rb +6 -0
  56. data/test/opal/runtime/call_spec.rb +16 -0
  57. data/test/opal/runtime/defined_spec.rb +11 -0
  58. data/test/opal/runtime/super_spec.rb +16 -0
  59. data/test/opal/string/to_json_spec.rb +6 -0
  60. data/test/spec_helper.rb +1 -3
  61. metadata +48 -15
  62. data/core/dir.rb +0 -89
  63. data/core/file.rb +0 -85
  64. data/core/match_data.rb +0 -35
  65. data/core/rational.rb +0 -16
  66. data/test/core/file/expand_path_spec.rb +0 -20
data/.gitignore CHANGED
@@ -11,4 +11,6 @@ pkg/*
11
11
  /*.js
12
12
  /docs/gh-pages
13
13
  /examples/**/*.js
14
- /build
14
+ /build
15
+ Gemfile.local
16
+ Gemfile.local.lock
data/Gemfile CHANGED
@@ -3,8 +3,9 @@ source :rubygems
3
3
  gemspec
4
4
 
5
5
  gem "rake"
6
- # gem "racc"
6
+ gem "racc"
7
7
 
8
8
  group :browser do
9
- gem "opal-spec", "0.1.6"
9
+ gem "opal-spec"
10
+ gem 'opal-dom'
10
11
  end
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.19](http://opalrb.org/opal.js) _(14.9kb Minified And Gzipped)_
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("[1, 2, 3, 4].each { |a| puts a }")
44
+ Opal.parse("puts 'hello world'")
45
45
  ```
46
46
 
47
- This will return a string of javascript similar to the following:
47
+ This will return a string of javascript:
48
48
 
49
49
  ```javascript
50
50
  (function() {
51
- // compiled ruby
52
- }).call(Opal.top);
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 4 numbers printed to the console.
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-json']
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-json` into
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-json` gem installed, so either
135
- install globally with `gem install opal-json`, or add it to your
136
- Gemfile as `gem "opal-json"` and run `bundle install`.
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-json.js"></script>
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. (`def.` is a local
356
- variable set to be the class's instance prototype. It is used
357
- for minimization of code as well as trying to be readable).
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
- def.$to_s = function() {
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
- def.$norm = function(a, b, c) {
418
+ Array_prototype.$norm = function(a, b, c) {
388
419
  return nil;
389
420
  };
390
421
 
391
- def.$opt = function(a, b) {
422
+ Array_prototype.$opt = function(a, b) {
392
423
  if (b == null) b = 100;
393
424
  return nil;
394
425
  };
395
426
 
396
- def.$rest = function(a, b) {
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(undefined) {
415
- var nil = Opal.nil, __slice = Opal.slice, __klass = Opal.klass;
583
+ (function() {
584
+ var nil = Opal.nil, self = Opal.top;
416
585
  // generated code
417
- }).call(Opal.top);
586
+ })();
418
587
  ```
419
588
 
420
- This function gets called with `Opal.top` as the context, which is the
421
- top level object available to ruby (`main`). Inside the function, `nil`
422
- is assigned to ensure a local copy is available, as well as all the
423
- helper methods used within the generated file. There is no return value
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(undefined) {
436
- var nil = Opal.nil;
437
- this.$puts("foo");
438
- }).call(Opal.top);
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