opal 1.7.2 → 1.8.0.alpha1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.eslintrc.js +1 -0
- data/.github/workflows/build.yml +9 -9
- data/.rubocop/todo.yml +2 -2
- data/.rubocop.yml +17 -10
- data/.rubocop_todo.yml +311 -0
- data/CHANGELOG.md +16 -2
- data/UNRELEASED.md +78 -1
- data/benchmark-ips/bm_block_vs_yield.rb +3 -0
- data/benchmark-ips/bm_slice_or_not.rb +53 -0
- data/docs/bridging.md +112 -0
- data/docs/compiled_ruby.md +10 -10
- data/docs/getting_started.md +18 -22
- data/docs/index.md +1 -1
- data/lib/opal/builder_scheduler/prefork.rb +2 -2
- data/lib/opal/cache/file_cache.rb +7 -10
- data/lib/opal/cli_runners/chrome_cdp_interface.rb +5 -2
- data/lib/opal/cli_runners/firefox_cdp_interface.rb +5 -2
- data/lib/opal/compiler.rb +33 -1
- data/lib/opal/nodes/args/extract_kwoptarg.rb +2 -1
- data/lib/opal/nodes/call.rb +1 -1
- data/lib/opal/nodes/call_special.rb +71 -47
- data/lib/opal/nodes/hash.rb +14 -30
- data/lib/opal/nodes/if.rb +38 -30
- data/lib/opal/nodes/literal.rb +5 -1
- data/lib/opal/nodes/x_string.rb +13 -0
- data/lib/opal/parser/patch.rb +7 -0
- data/lib/opal/rewriters/for_rewriter.rb +36 -24
- data/lib/opal/source_map/file.rb +1 -1
- data/lib/opal/version.rb +1 -1
- data/opal/corelib/array/pack.rb +1 -0
- data/opal/corelib/array.rb +75 -47
- data/opal/corelib/basic_object.rb +1 -0
- data/opal/corelib/binding.rb +2 -0
- data/opal/corelib/boolean.rb +1 -0
- data/opal/corelib/class.rb +2 -0
- data/opal/corelib/comparable.rb +1 -0
- data/opal/corelib/complex.rb +2 -0
- data/opal/corelib/constants.rb +2 -2
- data/opal/corelib/dir.rb +2 -0
- data/opal/corelib/enumerable.rb +3 -2
- data/opal/corelib/enumerator/arithmetic_sequence.rb +2 -0
- data/opal/corelib/enumerator/chain.rb +1 -0
- data/opal/corelib/enumerator/generator.rb +1 -0
- data/opal/corelib/enumerator/lazy.rb +1 -0
- data/opal/corelib/enumerator/yielder.rb +2 -0
- data/opal/corelib/enumerator.rb +1 -0
- data/opal/corelib/error/errno.rb +2 -0
- data/opal/corelib/error.rb +12 -0
- data/opal/corelib/file.rb +1 -0
- data/opal/corelib/hash.rb +197 -504
- data/opal/corelib/helpers.rb +1 -0
- data/opal/corelib/io.rb +2 -0
- data/opal/corelib/irb.rb +2 -0
- data/opal/corelib/kernel/format.rb +1 -0
- data/opal/corelib/kernel.rb +70 -14
- data/opal/corelib/main.rb +2 -0
- data/opal/corelib/marshal/read_buffer.rb +2 -0
- data/opal/corelib/marshal/write_buffer.rb +2 -0
- data/opal/corelib/math/polyfills.rb +2 -0
- data/opal/corelib/math.rb +1 -0
- data/opal/corelib/method.rb +2 -0
- data/opal/corelib/module.rb +1 -0
- data/opal/corelib/nil.rb +3 -1
- data/opal/corelib/number.rb +2 -0
- data/opal/corelib/numeric.rb +2 -0
- data/opal/corelib/object_space.rb +1 -0
- data/opal/corelib/pack_unpack/format_string_parser.rb +2 -0
- data/opal/corelib/proc.rb +30 -28
- data/opal/corelib/process.rb +2 -0
- data/opal/corelib/random/formatter.rb +2 -0
- data/opal/corelib/random/math_random.js.rb +2 -0
- data/opal/corelib/random/mersenne_twister.rb +2 -0
- data/opal/corelib/random/seedrandom.js.rb +2 -0
- data/opal/corelib/random.rb +1 -0
- data/opal/corelib/range.rb +34 -12
- data/opal/corelib/rational.rb +2 -0
- data/opal/corelib/regexp.rb +5 -1
- data/opal/corelib/runtime.js +188 -224
- data/opal/corelib/set.rb +2 -0
- data/opal/corelib/string/encoding.rb +3 -0
- data/opal/corelib/string/unpack.rb +2 -0
- data/opal/corelib/string.rb +20 -12
- data/opal/corelib/struct.rb +3 -1
- data/opal/corelib/time.rb +1 -0
- data/opal/corelib/trace_point.rb +2 -0
- data/opal/corelib/unsupported.rb +2 -0
- data/opal/corelib/variables.rb +2 -0
- data/opal.gemspec +2 -2
- data/spec/filters/bugs/array.rb +0 -3
- data/spec/filters/bugs/enumerable.rb +0 -3
- data/spec/filters/bugs/hash.rb +0 -13
- data/spec/filters/bugs/integer.rb +0 -1
- data/spec/filters/bugs/kernel.rb +0 -39
- data/spec/filters/bugs/language.rb +0 -3
- data/spec/filters/bugs/range.rb +0 -1
- data/spec/filters/bugs/ruby-32.rb +0 -2
- data/spec/filters/bugs/string.rb +0 -1
- data/spec/filters/bugs/struct.rb +1 -5
- data/spec/filters/unsupported/hash.rb +1 -0
- data/spec/lib/compiler_spec.rb +24 -17
- data/spec/mspec-opal/formatters.rb +2 -0
- data/spec/mspec-opal/runner.rb +2 -0
- data/spec/opal/core/array/dup_spec.rb +2 -0
- data/spec/opal/core/exception_spec.rb +2 -0
- data/spec/opal/core/hash/internals_spec.rb +154 -206
- data/spec/opal/core/hash_spec.rb +2 -0
- data/spec/opal/core/iterable_props_spec.rb +2 -0
- data/spec/opal/core/kernel/at_exit_spec.rb +2 -0
- data/spec/opal/core/kernel/respond_to_spec.rb +2 -0
- data/spec/opal/core/language/arguments/mlhs_arg_spec.rb +2 -0
- data/spec/opal/core/language/case_spec.rb +13 -0
- data/spec/opal/core/language/safe_navigator_spec.rb +2 -0
- data/spec/opal/core/language/xstring_send_spec.rb +15 -0
- data/spec/opal/core/language/xstring_spec.rb +2 -0
- data/spec/opal/core/language_spec.rb +2 -0
- data/spec/opal/core/module_spec.rb +44 -0
- data/spec/opal/core/number/to_i_spec.rb +2 -0
- data/spec/opal/core/object_id_spec.rb +2 -0
- data/spec/opal/core/regexp/match_spec.rb +2 -0
- data/spec/opal/core/runtime/bridged_classes_spec.rb +38 -0
- data/spec/opal/core/runtime/constants_spec.rb +2 -0
- data/spec/opal/core/runtime/eval_spec.rb +2 -0
- data/spec/opal/core/runtime/exit_spec.rb +2 -0
- data/spec/opal/core/runtime/is_a_spec.rb +2 -0
- data/spec/opal/core/runtime/loaded_spec.rb +2 -0
- data/spec/opal/core/runtime/method_missing_spec.rb +2 -0
- data/spec/opal/core/runtime/rescue_spec.rb +2 -0
- data/spec/opal/core/runtime/string_spec.rb +2 -0
- data/spec/opal/core/runtime/truthy_spec.rb +2 -0
- data/spec/opal/core/runtime_spec.rb +2 -6
- data/spec/opal/core/string/to_sym_spec.rb +2 -0
- data/spec/opal/stdlib/js_spec.rb +2 -0
- data/spec/opal/stdlib/native/alias_native_spec.rb +2 -0
- data/spec/opal/stdlib/native/array_spec.rb +2 -0
- data/spec/opal/stdlib/native/date_spec.rb +2 -0
- data/spec/opal/stdlib/native/each_spec.rb +2 -0
- data/spec/opal/stdlib/native/element_reference_spec.rb +2 -0
- data/spec/opal/stdlib/native/exposure_spec.rb +2 -0
- data/spec/opal/stdlib/native/ext_spec.rb +2 -0
- data/spec/opal/stdlib/native/hash_spec.rb +30 -2
- data/spec/opal/stdlib/native/initialize_spec.rb +2 -0
- data/spec/opal/stdlib/native/method_missing_spec.rb +2 -0
- data/spec/opal/stdlib/native/native_alias_spec.rb +2 -0
- data/spec/opal/stdlib/native/native_class_spec.rb +2 -0
- data/spec/opal/stdlib/native/native_module_spec.rb +2 -0
- data/spec/opal/stdlib/native/native_reader_spec.rb +2 -0
- data/spec/opal/stdlib/native/native_writer_spec.rb +2 -0
- data/spec/opal/stdlib/native/new_spec.rb +2 -0
- data/spec/opal/stdlib/native/struct_spec.rb +2 -0
- data/spec/spec_helper.rb +2 -0
- data/stdlib/await.rb +1 -0
- data/stdlib/base64.rb +2 -0
- data/stdlib/bigdecimal/bignumber.js.rb +2 -0
- data/stdlib/bigdecimal/util.rb +1 -0
- data/stdlib/bigdecimal.rb +2 -0
- data/stdlib/buffer/array.rb +2 -0
- data/stdlib/buffer/view.rb +2 -0
- data/stdlib/buffer.rb +2 -0
- data/stdlib/cgi.rb +14 -0
- data/stdlib/console.rb +2 -0
- data/stdlib/date/date_time.rb +2 -0
- data/stdlib/date.rb +2 -0
- data/stdlib/delegate.rb +2 -0
- data/stdlib/deno/base.rb +2 -0
- data/stdlib/deno/file.rb +2 -0
- data/stdlib/erb.rb +2 -0
- data/stdlib/gjs/io.rb +2 -0
- data/stdlib/gjs/kernel.rb +2 -0
- data/stdlib/headless_browser/base.rb +2 -0
- data/stdlib/headless_browser/file.rb +1 -0
- data/stdlib/headless_browser.rb +1 -0
- data/stdlib/js.rb +2 -0
- data/stdlib/json.rb +9 -15
- data/stdlib/logger.rb +2 -0
- data/stdlib/nashorn/file.rb +2 -0
- data/stdlib/nashorn/io.rb +2 -0
- data/stdlib/native.rb +48 -48
- data/stdlib/nodejs/base.rb +2 -0
- data/stdlib/nodejs/dir.rb +2 -0
- data/stdlib/nodejs/env.rb +2 -0
- data/stdlib/nodejs/file.rb +2 -0
- data/stdlib/nodejs/fileutils.rb +2 -0
- data/stdlib/nodejs/io.rb +2 -0
- data/stdlib/nodejs/js-yaml-3-6-1.js +1 -1
- data/stdlib/nodejs/kernel.rb +2 -0
- data/stdlib/nodejs/open-uri.rb +2 -0
- data/stdlib/nodejs/pathname.rb +2 -0
- data/stdlib/nodejs/require.rb +2 -0
- data/stdlib/nodejs/yaml.rb +9 -3
- data/stdlib/opal/miniracer.rb +2 -0
- data/stdlib/opal-parser.rb +8 -1
- data/stdlib/opal-platform.rb +2 -0
- data/stdlib/opal-replutils.rb +2 -0
- data/stdlib/open-uri.rb +4 -1
- data/stdlib/ostruct.rb +4 -2
- data/stdlib/pathname.rb +2 -0
- data/stdlib/pp.rb +1 -0
- data/stdlib/promise/v2.rb +2 -0
- data/stdlib/quickjs/io.rb +2 -0
- data/stdlib/quickjs/kernel.rb +2 -0
- data/stdlib/quickjs.rb +2 -0
- data/stdlib/securerandom.rb +2 -0
- data/stdlib/strscan.rb +2 -0
- data/stdlib/time.rb +2 -0
- data/stdlib/uri.rb +1 -0
- data/tasks/performance/optimization_status.rb +2 -0
- data/tasks/testing.rake +1 -0
- data/test/nodejs/test_await.rb +1 -0
- data/test/nodejs/test_dir.rb +2 -0
- data/test/nodejs/test_error.rb +2 -0
- data/test/nodejs/test_file.rb +2 -0
- data/test/nodejs/test_string.rb +2 -0
- data/test/nodejs/test_yaml.rb +20 -0
- metadata +24 -14
- data/spec/filters/bugs/openstruct.rb +0 -8
data/docs/bridging.md
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
# Opal Bridging
|
2
|
+
|
3
|
+
## Intro
|
4
|
+
|
5
|
+
Opal's "bridging" makes JavaScript objects become Ruby object in Opal, boosting language interoperability and performance by using native JavaScript versions of certain Ruby classes.
|
6
|
+
|
7
|
+
## Bridging Basics
|
8
|
+
|
9
|
+
Opal modifies the prototype chain of a JavaScript constructor function by injecting a Ruby class and its superclasses, enabling bridged JavaScript classes to inherit Ruby behaviors.
|
10
|
+
|
11
|
+
Prototype chain pre-bridging:
|
12
|
+
|
13
|
+
- constructor (window.String)
|
14
|
+
- JavaScript prototype chain (window.Object)
|
15
|
+
- null
|
16
|
+
|
17
|
+
After bridging with `::Object`:
|
18
|
+
|
19
|
+
- constructor (window.String)
|
20
|
+
- Ruby superclass chain (Opal.Object, Opal.Kernel, Opal.BasicObject)
|
21
|
+
- JavaScript prototype chain (window.Object)
|
22
|
+
- null
|
23
|
+
|
24
|
+
The `Opal.bridge` function is used for this, allowing the JavaScript class to become a Ruby class. You can also use a syntax like: ```class MyCar < `Car` ``` which counterintuitively doesn't mean inheritance - `MyCar`'s superclass will be Object, but `MyCar` will be bridged into native JavaScript `Car` class.
|
25
|
+
|
26
|
+
The bridged JavaScript classes are usable in Ruby, but with an adapted interface: JavaScript methods accessed from Ruby use x-strings and Ruby methods accessed from JavaScript use $-prefixed method names (e.g., reduce->$reduce).
|
27
|
+
|
28
|
+
This strategy avoids type casting for bridged objects and boosts performance by utilizing native JavaScript objects.
|
29
|
+
|
30
|
+
Example:
|
31
|
+
|
32
|
+
JavaScript `Car` class:
|
33
|
+
|
34
|
+
```javascript
|
35
|
+
class Car {
|
36
|
+
constructor(make, model) {
|
37
|
+
this.make = make;
|
38
|
+
this.model = model;
|
39
|
+
}
|
40
|
+
|
41
|
+
getCarInfo() {
|
42
|
+
return `${this.make} ${this.model}`;
|
43
|
+
}
|
44
|
+
}
|
45
|
+
```
|
46
|
+
|
47
|
+
Bridged Ruby `MyCar` class:
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
class MyCar < `Car`
|
51
|
+
def self.new(make, model)
|
52
|
+
`new Car(make, model)`
|
53
|
+
end
|
54
|
+
|
55
|
+
def car_info
|
56
|
+
`self.getCarInfo()`
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
car = MyCar.new('Toyota', 'Corolla')
|
61
|
+
puts car.car_info # Outputs: "Toyota Corolla"
|
62
|
+
```
|
63
|
+
|
64
|
+
This bridges `Car` to `MyCar`, creating a `Car` instance with `new Car(make, model)`. We call `getCarInfo()` via `self`, referencing the same JavaScript and Ruby object due to bridging. Thus, creating a `MyCar` instance makes a `Car` instance, and `car_info` on the `car` object outputs `"Toyota Corolla"`. This shows seamless Ruby-JavaScript interaction via bridging.
|
65
|
+
|
66
|
+
## Inheritance & Bridging
|
67
|
+
|
68
|
+
Creating a subclass of a native JavaScript class is possible. Bridging doesn't affect JavaScript's behavior or inheritance hierarchy.
|
69
|
+
|
70
|
+
## Bridging Considerations
|
71
|
+
|
72
|
+
### Bridged Class Methods
|
73
|
+
|
74
|
+
Access JavaScript class methods as properties on the constructor with `class.$$constructor`, where `class` is a Ruby class. You can access it using `self`.
|
75
|
+
|
76
|
+
### Performance
|
77
|
+
|
78
|
+
Bridging is quicker than wrapping as no wrapper object is used, but altering the prototype chain could affect JavaScript engine performance.
|
79
|
+
|
80
|
+
### Exception Handling
|
81
|
+
|
82
|
+
JavaScript `Error` is bridged to Ruby `Exception` for unified error handling.
|
83
|
+
|
84
|
+
### Native JavaScript Interactions
|
85
|
+
|
86
|
+
Use x-strings for interacting with native JavaScript. The `Native` class wraps JavaScript objects and offers a Ruby-like API.
|
87
|
+
|
88
|
+
### Type Conversions
|
89
|
+
|
90
|
+
With bridging creating equivalent Ruby and JavaScript objects, no explicit type conversion is needed. The `Native` library's `#to_n` is how you convert non-bridged objects.
|
91
|
+
|
92
|
+
## Bridged Classes in Corelib
|
93
|
+
|
94
|
+
Opal's core library bridges several commonly used JavaScript classes, including `Array`, `Boolean`, `Number` (think: `Float` which also may act as an `Integer`), `Proc`, `Time`, `RegExp`, and `String`, allowing interaction with these JavaScript instances as their Ruby equivalents. Also `Hash` bridges the Javascript `Map` class and works very well between Javascript and Opal if primitives of the Javascript types "string", "number" and "symbol" (which must be global, generated with `Symbol.for()`) are used as keys. When other types or objects are used as keys, there may be issues retrieving the Map entries from each language, because the original references of the objects that have been used as keys are required for retrieval. When using a `Map` with object keys passed from Javascript as Hash in Opal, calling `Hash#rehash` may resolve issues and make entries accessible.
|
95
|
+
|
96
|
+
However, Opal doesn't bridge common JavaScript classes like `Set` due to significant differences in behavior and interfaces between Ruby and JavaScript.
|
97
|
+
|
98
|
+
## Drawbacks
|
99
|
+
|
100
|
+
Bridging effectively utilizes JavaScript objects within Ruby, but it does have its downsides.
|
101
|
+
|
102
|
+
The main drawback of Opal bridging is that it modifies the prototypes of JavaScript classes, potentially leading to prototype pollution. This can cause conflicts with other JavaScript code expecting the prototype in its original state, potentially causing hard-to-trace bugs or slower code execution due to changes impacting the JavaScript engine's performance.
|
103
|
+
|
104
|
+
Additionally, bridging requires comprehensive knowledge of both Ruby and JavaScript, and understanding how Opal implements the bridge. Incorrect usage can lead to complex, hard-to-debug issues.
|
105
|
+
|
106
|
+
An alternative to bridging is using `Native::Wrapper`. This module in Opal's standard library allows interaction with JavaScript objects without modifying their prototypes. It offers a Ruby-friendly API for accessing and calling JavaScript methods, as well as handling JavaScript properties, thus avoiding the prototype pollution issue.
|
107
|
+
|
108
|
+
In conclusion, bridging provides powerful functionality for Ruby-JavaScript code interaction, but it should be used cautiously and with a deep understanding of its implications. Depending on your needs, `Native::Wrapper` may be a safer and more intuitive alternative.
|
109
|
+
|
110
|
+
## Conclusion
|
111
|
+
|
112
|
+
Opal's bridging mechanism offers an enticing method for combining Ruby and JavaScript's strengths into a single environment. Despite requiring caution due to potential drawbacks, bridging enables developers to create Ruby idiomatic APIs for accessing JavaScript code. By leveraging this mechanism, we can redefine conventional web programming paradigms and create more vibrant, dynamic applications.
|
data/docs/compiled_ruby.md
CHANGED
@@ -78,18 +78,17 @@ Ruby syntaxes for word arrays etc are also supported.
|
|
78
78
|
|
79
79
|
#### Hash
|
80
80
|
|
81
|
-
Inside
|
82
|
-
creates a new
|
83
|
-
and simply returns a new instance of the `Hash` class.
|
81
|
+
Inside Javascript `new Map()` can be used, which
|
82
|
+
creates simply a new instance of the `Hash` class.
|
84
83
|
|
85
84
|
```ruby
|
86
|
-
{ :foo => 100, :baz => 700 } # =>
|
87
|
-
{ foo: 42, bar: [1, 2, 3] } # =>
|
85
|
+
{ :foo => 100, :baz => 700 } # => new Map([["foo", 100], ["baz", 700]])
|
86
|
+
{ foo: 42, bar: [1, 2, 3] } # => new Map([["foo", 42], ["bar", [1, 2, 3]]])
|
88
87
|
```
|
89
88
|
|
90
89
|
#### Range
|
91
90
|
|
92
|
-
|
91
|
+
There is a function `Opal.range` available to create
|
93
92
|
range instances.
|
94
93
|
|
95
94
|
```ruby
|
@@ -598,10 +597,10 @@ array.$map(); // => [2,4,6]
|
|
598
597
|
|
599
598
|
### Hash
|
600
599
|
|
601
|
-
Since Ruby hashes are
|
600
|
+
Since Ruby hashes are bridged to `Map`. It's quite possible to interact with hashes from JavaScript:
|
602
601
|
|
603
602
|
```javascript
|
604
|
-
var myHash =
|
603
|
+
var myHash = new Map([['a', 1], ['b', 2]]);
|
605
604
|
// output of $inspect: {"a"=>1, "b"=>2}
|
606
605
|
myHash.$store('a', 10);
|
607
606
|
// output of $inspect: {"a"=>10, "b"=>2}
|
@@ -609,10 +608,11 @@ myHash.$fetch('b','');
|
|
609
608
|
// 2
|
610
609
|
myHash.$fetch('z','');
|
611
610
|
// ""
|
612
|
-
myHash.$update(
|
611
|
+
myHash.$update(new Map([['b', 20], ['c', 30]]));
|
613
612
|
// output of $inspect: {"a"=>10, "b"=>20, "c"=>30}
|
614
613
|
myHash.$to_n(); // provided by the Native module
|
615
|
-
// output: {
|
614
|
+
// output: Map(2) {'b' => 20, 'c' => 30}
|
615
|
+
// aka a standard JavaScript Map
|
616
616
|
```
|
617
617
|
|
618
618
|
NOTE: Be aware `Hash#to_n` produces a duplicate copy of the hash.
|
data/docs/getting_started.md
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
# Getting Started
|
2
2
|
|
3
|
-
Opal is a
|
3
|
+
Opal is a Ruby to JavaScript compiler, an implementation of the Ruby corelib and stdlib, and associated gems for building fast client-side web applications in Ruby.
|
4
4
|
|
5
5
|
## Installation
|
6
6
|
|
7
|
-
Opal is available as a gem
|
7
|
+
Opal is available as a gem and can be installed via:
|
8
8
|
|
9
9
|
```
|
10
10
|
$ gem install opal
|
@@ -16,49 +16,45 @@ Or added to your Gemfile as:
|
|
16
16
|
gem 'opal'
|
17
17
|
```
|
18
18
|
|
19
|
-
## Getting
|
19
|
+
## Getting Started with Opal
|
20
20
|
|
21
|
-
At its
|
21
|
+
At its core, Opal provides a simple method of compiling a string of Ruby into JavaScript that can run on top of the Opal runtime:
|
22
22
|
|
23
23
|
```ruby
|
24
24
|
Opal.compile("[1, 2, 3].each { |a| puts a }")
|
25
25
|
# => "(function() { ... })()"
|
26
26
|
```
|
27
27
|
|
28
|
-
|
28
|
+
Opal allows for Ruby (and ERB) assets to be compiled and treated as first-class JavaScript citizens. Ruby sources can require JavaScript and other Ruby files, working similar to CoffeeScript.
|
29
29
|
|
30
|
-
This relies on the Opal load path. Any gem containing
|
30
|
+
This relies on the Opal load path. Any gem containing Opal code registers that directory to the Opal load path. Opal will then use all Opal load paths when running instances.
|
31
31
|
|
32
|
+
### Adding Lookup Paths
|
32
33
|
|
33
|
-
|
34
|
+
Opal uses a load path to create a set of locations from which Opal can require files. If you want to add a directory to this load path, you can add it to the global environment.
|
34
35
|
|
35
|
-
Opal
|
36
|
-
from. If you want to add a directory to this load path, you can add it to the global environment.
|
37
|
-
|
38
|
-
In the `Opal` module, a property `paths` is used to hold the load paths which
|
39
|
-
`Opal` uses to require files from. You can add a directory to this:
|
36
|
+
In the `Opal` module, a property `paths` is used to hold the load paths which `Opal` uses to require files from. You can add a directory to this:
|
40
37
|
|
41
38
|
```ruby
|
42
39
|
Opal.append_path '../my_lib'
|
43
40
|
```
|
44
41
|
|
45
|
-
Now, any
|
46
|
-
|
42
|
+
Now, any Ruby files in this directory can be discovered.
|
47
43
|
|
48
44
|
## FAQ
|
49
45
|
|
50
|
-
### Why
|
46
|
+
### Why Does Opal Exist?
|
51
47
|
|
52
|
-
|
48
|
+
Opal aims to keep Ruby relevant in a world where client-side applications are making JavaScript the primary development platform.
|
53
49
|
|
54
|
-
### How
|
50
|
+
### How Compatible is Opal?
|
55
51
|
|
56
|
-
|
52
|
+
Opal is tested against the [Ruby spec](https://github.com/ruby/spec) as our primary testing setup. The goal is to make Opal as compatible as possible while also considering the restrictions of JavaScript when applicable. Opal supports the majority of Ruby syntax features, as well as a very large part of the corelib implementation. Opal can compile and run RSpec unmodified, as well as self-host the compiler at runtime.
|
57
53
|
|
58
|
-
### What
|
54
|
+
### What Version of Ruby Does Opal Target?
|
59
55
|
|
60
|
-
|
56
|
+
Opal's tests are run under Ruby 3.2.0 conditions, but it remains mostly compatible with 2.6 level features.
|
61
57
|
|
62
|
-
### Why
|
58
|
+
### Why Doesn't Opal Support Mutable Strings?
|
63
59
|
|
64
|
-
All strings in Opal are immutable because
|
60
|
+
All strings in Opal are immutable because Ruby strings are compiled directly into JavaScript strings, which are immutable. Wrapping Ruby strings as a custom JavaScript object would add a lot of overhead and complicate interaction between Ruby and JavaScript libraries.
|
data/docs/index.md
CHANGED
@@ -8,7 +8,7 @@ The guides for earlier releases are [available here](/docs).
|
|
8
8
|
|
9
9
|
## Start here
|
10
10
|
|
11
|
-
#### [Getting Started with Opal](
|
11
|
+
#### [Getting Started with Opal](getting_started.html)
|
12
12
|
|
13
13
|
Everything you need to know to install Opal and create your first application.
|
14
14
|
|
@@ -131,8 +131,8 @@ module Opal
|
|
131
131
|
|
132
132
|
class Fork
|
133
133
|
def initialize(forkset)
|
134
|
-
@parent_read, @child_write = IO.pipe
|
135
|
-
@child_read, @parent_write = IO.pipe
|
134
|
+
@parent_read, @child_write = IO.pipe(binmode: true)
|
135
|
+
@child_read, @parent_write = IO.pipe(binmode: true)
|
136
136
|
@forkset = forkset
|
137
137
|
@in_fork = false
|
138
138
|
|
@@ -78,19 +78,16 @@ module Opal
|
|
78
78
|
nil
|
79
79
|
end
|
80
80
|
|
81
|
-
#
|
81
|
+
# Check if we can robustly mkdir_p a directory.
|
82
82
|
def self.dir_writable?(*paths)
|
83
|
-
|
84
|
-
paths = paths.reduce([]) do |a, b|
|
85
|
-
[*a, dir = a.last ? File.expand_path(b, a.last) : b]
|
86
|
-
end
|
83
|
+
return false unless File.exist?(paths.first)
|
87
84
|
|
88
|
-
|
89
|
-
paths.
|
90
|
-
|
91
|
-
|
85
|
+
until paths.empty?
|
86
|
+
dir = File.expand_path(paths.shift, dir)
|
87
|
+
ok = File.directory?(dir) && File.writable?(dir) if File.exist?(dir)
|
88
|
+
end
|
92
89
|
|
93
|
-
dir
|
90
|
+
dir if ok
|
94
91
|
end
|
95
92
|
|
96
93
|
def self.find_dir
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# backtick_javascript: true
|
1
2
|
# frozen_string_literal: true
|
2
3
|
|
3
4
|
# This script I converted into Opal, so that I don't have to write
|
@@ -121,14 +122,16 @@ var server = http.createServer(function(req, res) {
|
|
121
122
|
|
122
123
|
// actual CDP code
|
123
124
|
|
124
|
-
CDP.List(options, function(err, targets) {
|
125
|
+
CDP.List(options, async function(err, targets) {
|
125
126
|
// default CDP port is 9222, Firefox runner is at 9333
|
126
127
|
// Lets collect clients for
|
127
128
|
// Chrome CDP starting at 9273 ...
|
128
129
|
// Firefox CDP starting 9334 ...
|
129
130
|
port_offset = targets ? targets.length + 51 : 51; // default CDP port is 9222, Node CDP port 9229, Firefox is at 9333
|
130
131
|
|
131
|
-
|
132
|
+
const {webSocketDebuggerUrl} = await CDP.Version(options);
|
133
|
+
|
134
|
+
return await CDP({target: webSocketDebuggerUrl}, function(browser_client) {
|
132
135
|
|
133
136
|
server.listen({ port: port_offset + options.port, host: options.host });
|
134
137
|
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# backtick_javascript: true
|
1
2
|
# frozen_string_literal: true
|
2
3
|
|
3
4
|
# This script I converted into Opal, so that I don't have to write
|
@@ -94,10 +95,12 @@ var server = http.createServer(function(req, res) {
|
|
94
95
|
|
95
96
|
// actual CDP code
|
96
97
|
|
97
|
-
CDP.List(options, function(err, targets) {
|
98
|
+
CDP.List(options, async function(err, targets) {
|
98
99
|
offset = targets ? targets.length + 1 : 1;
|
99
100
|
|
100
|
-
|
101
|
+
const {webSocketDebuggerUrl} = await CDP.Version(options);
|
102
|
+
|
103
|
+
return await CDP({target: webSocketDebuggerUrl}, function(browser_client) {
|
101
104
|
|
102
105
|
server.listen({port: offset + options.port, host: options.host });
|
103
106
|
|
data/lib/opal/compiler.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
if RUBY_ENGINE == 'opal'
|
4
|
+
require 'corelib/string/unpack'
|
5
|
+
end
|
3
6
|
require 'set'
|
4
7
|
require 'opal/parser'
|
5
8
|
require 'opal/fragment'
|
@@ -8,6 +11,7 @@ require 'opal/eof_content'
|
|
8
11
|
require 'opal/errors'
|
9
12
|
require 'opal/magic_comments'
|
10
13
|
require 'opal/nodes/closure'
|
14
|
+
require 'opal/source_map'
|
11
15
|
|
12
16
|
module Opal
|
13
17
|
# Compile a string of ruby code into javascript.
|
@@ -184,6 +188,30 @@ module Opal
|
|
184
188
|
# Adds comments for every method definition
|
185
189
|
compiler_option :parse_comments, default: false, as: :parse_comments?
|
186
190
|
|
191
|
+
# @!method backtick_javascript?
|
192
|
+
#
|
193
|
+
# Allows use of a backtick operator (and `%x{}``) to embed verbatim JavaScript.
|
194
|
+
# If false, backtick operator will
|
195
|
+
compiler_option :backtick_javascript, default: nil, as: :backtick_javascript?, magic_comment: true
|
196
|
+
|
197
|
+
# Warn about impending compatibility break
|
198
|
+
def backtick_javascript_or_warn?
|
199
|
+
case backtick_javascript?
|
200
|
+
when true
|
201
|
+
true
|
202
|
+
when nil
|
203
|
+
@backtick_javascript_warned ||= begin
|
204
|
+
warning 'Backtick operator usage interpreted as intent to embed JavaScript; this code will ' \
|
205
|
+
'break in Opal 2.0; add a magic comment: `# backtick_javascript: true`'
|
206
|
+
true
|
207
|
+
end
|
208
|
+
|
209
|
+
true
|
210
|
+
when false
|
211
|
+
false
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
187
215
|
compiler_option :scope_variables, default: []
|
188
216
|
|
189
217
|
# @!method async_await
|
@@ -572,7 +600,11 @@ module Opal
|
|
572
600
|
when :return, :js_return, :returnable_yield
|
573
601
|
sexp
|
574
602
|
when :xstr
|
575
|
-
|
603
|
+
if backtick_javascript_or_warn?
|
604
|
+
sexp.updated(nil, [s(:js_return, *sexp.children)])
|
605
|
+
else
|
606
|
+
sexp
|
607
|
+
end
|
576
608
|
when :if
|
577
609
|
cond, true_body, false_body = *sexp
|
578
610
|
sexp.updated(
|
@@ -16,12 +16,13 @@ module Opal
|
|
16
16
|
children :lvar_name, :default_value
|
17
17
|
|
18
18
|
def compile
|
19
|
+
helper :hash_get
|
19
20
|
key_name = @sexp.meta[:arg_name]
|
20
21
|
scope.used_kwargs << key_name
|
21
22
|
|
22
23
|
add_temp lvar_name
|
23
24
|
|
24
|
-
line "#{lvar_name} = $kwargs
|
25
|
+
line "#{lvar_name} = $hash_get($kwargs, #{key_name.to_s.inspect});"
|
25
26
|
|
26
27
|
return if default_value.children[1] == :undefined
|
27
28
|
|
data/lib/opal/nodes/call.rb
CHANGED
@@ -366,7 +366,7 @@ module Opal
|
|
366
366
|
end
|
367
367
|
|
368
368
|
add_special :__OPAL_COMPILER_CONFIG__ do
|
369
|
-
push fragment "
|
369
|
+
push fragment "(new Map([['arity_check', #{compiler.arity_check?}]]))"
|
370
370
|
end
|
371
371
|
|
372
372
|
add_special :lambda do |compile_default|
|
@@ -60,8 +60,8 @@ module Opal
|
|
60
60
|
end
|
61
61
|
end
|
62
62
|
|
63
|
-
#
|
64
|
-
#
|
63
|
+
# Handles match_with_lvasgn nodes which represent matching a regular expression
|
64
|
+
# with a right-hand side value and assigning the match result to a left-hand side variable.
|
65
65
|
class Match3Node < Base
|
66
66
|
handle :match_with_lvasgn
|
67
67
|
|
@@ -69,58 +69,82 @@ module Opal
|
|
69
69
|
|
70
70
|
def compile
|
71
71
|
sexp = s(:send, lhs, :=~, rhs)
|
72
|
+
|
72
73
|
# Handle named matches like: /(?<abc>b)/ =~ 'b'
|
73
74
|
if lhs.type == :regexp && lhs.children.first.type == :str
|
74
|
-
|
75
|
-
names = re.scan(/\(\?<([^>]*)>/).flatten.map(&:to_sym)
|
76
|
-
unless names.empty?
|
77
|
-
# $m3names = $~ ? $~.named_captures : {}
|
78
|
-
names_def = s(:lvasgn, :$m3names,
|
79
|
-
s(:if,
|
80
|
-
s(:gvar, :$~),
|
81
|
-
s(:send, s(:gvar, :$~), :named_captures),
|
82
|
-
s(:hash)
|
83
|
-
)
|
84
|
-
)
|
75
|
+
names = extract_names(lhs)
|
85
76
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
end
|
96
|
-
|
97
|
-
if stmt?
|
98
|
-
# We don't care about a return value of this one, so we
|
99
|
-
# ignore it and just assign the local variables.
|
100
|
-
#
|
101
|
-
# (/(?<abc>b)/ =~ 'f')
|
102
|
-
# $m3names = $~ ? $~.named_captures : {}
|
103
|
-
# abc = $m3names[:abc]
|
104
|
-
sexp = s(:begin, sexp, names_def, *names)
|
105
|
-
else
|
106
|
-
# We actually do care about a return value, so we must
|
107
|
-
# keep it saved.
|
108
|
-
#
|
109
|
-
# $m3tmp = (/(?<abc>b)/ =~ 'f')
|
110
|
-
# $m3names = $~ ? $~.named_captures : {}
|
111
|
-
# abc = $m3names[:abc]
|
112
|
-
# $m3tmp
|
113
|
-
sexp = s(:begin,
|
114
|
-
s(:lvasgn, :$m3tmp, sexp),
|
115
|
-
names_def,
|
116
|
-
*names,
|
117
|
-
s(:lvar, :$m3tmp)
|
118
|
-
)
|
119
|
-
end
|
77
|
+
unless names.empty?
|
78
|
+
names_def = generate_names_definition
|
79
|
+
names_assignments = generate_names_assignments(names)
|
80
|
+
|
81
|
+
sexp = if stmt?
|
82
|
+
handle_statement(sexp, names_def, names_assignments)
|
83
|
+
else
|
84
|
+
handle_non_statement(sexp, names_def, names_assignments)
|
85
|
+
end
|
120
86
|
end
|
121
87
|
end
|
88
|
+
|
122
89
|
push process(sexp, @level)
|
123
90
|
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def extract_names(regexp_node)
|
95
|
+
re = regexp_node.children.first.children.first
|
96
|
+
re.scan(/\(\?<([^>]*)>/).flatten.map(&:to_sym)
|
97
|
+
end
|
98
|
+
|
99
|
+
def generate_names_definition
|
100
|
+
# Generate names definition: $m3names = $~ ? $~.named_captures : {}
|
101
|
+
s(:lvasgn, :$m3names,
|
102
|
+
s(:if,
|
103
|
+
s(:gvar, :$~),
|
104
|
+
s(:send, s(:gvar, :$~), :named_captures),
|
105
|
+
s(:hash)
|
106
|
+
)
|
107
|
+
)
|
108
|
+
end
|
109
|
+
|
110
|
+
def generate_names_assignments(names)
|
111
|
+
# Generate names assignments: abc = $m3names[:abc]
|
112
|
+
names.map do |name|
|
113
|
+
s(:lvasgn, name,
|
114
|
+
s(:send,
|
115
|
+
s(:lvar, :$m3names),
|
116
|
+
:[],
|
117
|
+
s(:sym, name)
|
118
|
+
)
|
119
|
+
)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def handle_statement(sexp, names_def, names_assignments)
|
124
|
+
# We don't care about a return value of this one, so we
|
125
|
+
# ignore it and just assign the local variables.
|
126
|
+
#
|
127
|
+
# (/(?<abc>b)/ =~ 'f')
|
128
|
+
# $m3names = $~ ? $~.named_captures : {}
|
129
|
+
# abc = $m3names[:abc]
|
130
|
+
s(:begin, sexp, names_def, *names_assignments)
|
131
|
+
end
|
132
|
+
|
133
|
+
def handle_non_statement(sexp, names_def, names_assignments)
|
134
|
+
# We actually do care about a return value, so we must
|
135
|
+
# keep it saved.
|
136
|
+
#
|
137
|
+
# $m3tmp = (/(?<abc>b)/ =~ 'f')
|
138
|
+
# $m3names = $~ ? $~.named_captures : {}
|
139
|
+
# abc = $m3names[:abc]
|
140
|
+
# $m3tmp
|
141
|
+
s(:begin,
|
142
|
+
s(:lvasgn, :$m3tmp, sexp),
|
143
|
+
names_def,
|
144
|
+
*names_assignments,
|
145
|
+
s(:lvar, :$m3tmp)
|
146
|
+
)
|
147
|
+
end
|
124
148
|
end
|
125
149
|
end
|
126
150
|
end
|
data/lib/opal/nodes/hash.rb
CHANGED
@@ -27,14 +27,12 @@ module Opal
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def simple_keys?
|
30
|
-
keys.all? { |key| %i[sym str].include?(key.type) }
|
30
|
+
keys.all? { |key| %i[sym str int].include?(key.type) }
|
31
31
|
end
|
32
32
|
|
33
33
|
def compile
|
34
34
|
if has_kwsplat
|
35
35
|
compile_merge
|
36
|
-
elsif simple_keys?
|
37
|
-
compile_hash2
|
38
36
|
else
|
39
37
|
compile_hash
|
40
38
|
end
|
@@ -47,8 +45,6 @@ module Opal
|
|
47
45
|
# Each kwsplat overrides previosly defined keys
|
48
46
|
# Hash k/v pairs override previously defined kwsplat values
|
49
47
|
def compile_merge
|
50
|
-
helper :hash
|
51
|
-
|
52
48
|
result, seq = [], []
|
53
49
|
|
54
50
|
children.each do |child|
|
@@ -76,38 +72,26 @@ module Opal
|
|
76
72
|
end
|
77
73
|
|
78
74
|
# Compiles a hash without kwsplats
|
79
|
-
# with complex keys.
|
75
|
+
# with simple or complex keys.
|
80
76
|
def compile_hash
|
81
|
-
helper :hash
|
82
|
-
|
83
77
|
children.each_with_index do |pair, idx|
|
84
78
|
key, value = pair.children
|
85
79
|
push ', ' unless idx == 0
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
# Compiles a hash without kwsplats
|
93
|
-
# and containing **only** string/symbols as keys.
|
94
|
-
def compile_hash2
|
95
|
-
hash_obj, hash_keys = {}, []
|
96
|
-
helper :hash2
|
97
|
-
|
98
|
-
keys.size.times do |idx|
|
99
|
-
key = keys[idx].children[0].to_s.inspect
|
100
|
-
hash_keys << key unless hash_obj.include? key
|
101
|
-
hash_obj[key] = expr(values[idx])
|
80
|
+
if %i[sym str].include?(key.type)
|
81
|
+
push "[#{key.children[0].to_s.inspect}", ', ', expr(value), ']'
|
82
|
+
else
|
83
|
+
push '[', expr(key), ', ', expr(value), ']'
|
84
|
+
end
|
102
85
|
end
|
103
86
|
|
104
|
-
|
105
|
-
push '
|
106
|
-
|
107
|
-
|
87
|
+
if keys.empty?
|
88
|
+
push '(new Map())'
|
89
|
+
elsif simple_keys?
|
90
|
+
wrap '(new Map([', ']))'
|
91
|
+
else
|
92
|
+
helper :hash_rehash
|
93
|
+
wrap '$hash_rehash(new Map([', ']))'
|
108
94
|
end
|
109
|
-
|
110
|
-
wrap "$hash2([#{hash_keys.join ', '}], {", '})'
|
111
95
|
end
|
112
96
|
end
|
113
97
|
|