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.
Files changed (216) hide show
  1. checksums.yaml +4 -4
  2. data/.eslintrc.js +1 -0
  3. data/.github/workflows/build.yml +9 -9
  4. data/.rubocop/todo.yml +2 -2
  5. data/.rubocop.yml +17 -10
  6. data/.rubocop_todo.yml +311 -0
  7. data/CHANGELOG.md +16 -2
  8. data/UNRELEASED.md +78 -1
  9. data/benchmark-ips/bm_block_vs_yield.rb +3 -0
  10. data/benchmark-ips/bm_slice_or_not.rb +53 -0
  11. data/docs/bridging.md +112 -0
  12. data/docs/compiled_ruby.md +10 -10
  13. data/docs/getting_started.md +18 -22
  14. data/docs/index.md +1 -1
  15. data/lib/opal/builder_scheduler/prefork.rb +2 -2
  16. data/lib/opal/cache/file_cache.rb +7 -10
  17. data/lib/opal/cli_runners/chrome_cdp_interface.rb +5 -2
  18. data/lib/opal/cli_runners/firefox_cdp_interface.rb +5 -2
  19. data/lib/opal/compiler.rb +33 -1
  20. data/lib/opal/nodes/args/extract_kwoptarg.rb +2 -1
  21. data/lib/opal/nodes/call.rb +1 -1
  22. data/lib/opal/nodes/call_special.rb +71 -47
  23. data/lib/opal/nodes/hash.rb +14 -30
  24. data/lib/opal/nodes/if.rb +38 -30
  25. data/lib/opal/nodes/literal.rb +5 -1
  26. data/lib/opal/nodes/x_string.rb +13 -0
  27. data/lib/opal/parser/patch.rb +7 -0
  28. data/lib/opal/rewriters/for_rewriter.rb +36 -24
  29. data/lib/opal/source_map/file.rb +1 -1
  30. data/lib/opal/version.rb +1 -1
  31. data/opal/corelib/array/pack.rb +1 -0
  32. data/opal/corelib/array.rb +75 -47
  33. data/opal/corelib/basic_object.rb +1 -0
  34. data/opal/corelib/binding.rb +2 -0
  35. data/opal/corelib/boolean.rb +1 -0
  36. data/opal/corelib/class.rb +2 -0
  37. data/opal/corelib/comparable.rb +1 -0
  38. data/opal/corelib/complex.rb +2 -0
  39. data/opal/corelib/constants.rb +2 -2
  40. data/opal/corelib/dir.rb +2 -0
  41. data/opal/corelib/enumerable.rb +3 -2
  42. data/opal/corelib/enumerator/arithmetic_sequence.rb +2 -0
  43. data/opal/corelib/enumerator/chain.rb +1 -0
  44. data/opal/corelib/enumerator/generator.rb +1 -0
  45. data/opal/corelib/enumerator/lazy.rb +1 -0
  46. data/opal/corelib/enumerator/yielder.rb +2 -0
  47. data/opal/corelib/enumerator.rb +1 -0
  48. data/opal/corelib/error/errno.rb +2 -0
  49. data/opal/corelib/error.rb +12 -0
  50. data/opal/corelib/file.rb +1 -0
  51. data/opal/corelib/hash.rb +197 -504
  52. data/opal/corelib/helpers.rb +1 -0
  53. data/opal/corelib/io.rb +2 -0
  54. data/opal/corelib/irb.rb +2 -0
  55. data/opal/corelib/kernel/format.rb +1 -0
  56. data/opal/corelib/kernel.rb +70 -14
  57. data/opal/corelib/main.rb +2 -0
  58. data/opal/corelib/marshal/read_buffer.rb +2 -0
  59. data/opal/corelib/marshal/write_buffer.rb +2 -0
  60. data/opal/corelib/math/polyfills.rb +2 -0
  61. data/opal/corelib/math.rb +1 -0
  62. data/opal/corelib/method.rb +2 -0
  63. data/opal/corelib/module.rb +1 -0
  64. data/opal/corelib/nil.rb +3 -1
  65. data/opal/corelib/number.rb +2 -0
  66. data/opal/corelib/numeric.rb +2 -0
  67. data/opal/corelib/object_space.rb +1 -0
  68. data/opal/corelib/pack_unpack/format_string_parser.rb +2 -0
  69. data/opal/corelib/proc.rb +30 -28
  70. data/opal/corelib/process.rb +2 -0
  71. data/opal/corelib/random/formatter.rb +2 -0
  72. data/opal/corelib/random/math_random.js.rb +2 -0
  73. data/opal/corelib/random/mersenne_twister.rb +2 -0
  74. data/opal/corelib/random/seedrandom.js.rb +2 -0
  75. data/opal/corelib/random.rb +1 -0
  76. data/opal/corelib/range.rb +34 -12
  77. data/opal/corelib/rational.rb +2 -0
  78. data/opal/corelib/regexp.rb +5 -1
  79. data/opal/corelib/runtime.js +188 -224
  80. data/opal/corelib/set.rb +2 -0
  81. data/opal/corelib/string/encoding.rb +3 -0
  82. data/opal/corelib/string/unpack.rb +2 -0
  83. data/opal/corelib/string.rb +20 -12
  84. data/opal/corelib/struct.rb +3 -1
  85. data/opal/corelib/time.rb +1 -0
  86. data/opal/corelib/trace_point.rb +2 -0
  87. data/opal/corelib/unsupported.rb +2 -0
  88. data/opal/corelib/variables.rb +2 -0
  89. data/opal.gemspec +2 -2
  90. data/spec/filters/bugs/array.rb +0 -3
  91. data/spec/filters/bugs/enumerable.rb +0 -3
  92. data/spec/filters/bugs/hash.rb +0 -13
  93. data/spec/filters/bugs/integer.rb +0 -1
  94. data/spec/filters/bugs/kernel.rb +0 -39
  95. data/spec/filters/bugs/language.rb +0 -3
  96. data/spec/filters/bugs/range.rb +0 -1
  97. data/spec/filters/bugs/ruby-32.rb +0 -2
  98. data/spec/filters/bugs/string.rb +0 -1
  99. data/spec/filters/bugs/struct.rb +1 -5
  100. data/spec/filters/unsupported/hash.rb +1 -0
  101. data/spec/lib/compiler_spec.rb +24 -17
  102. data/spec/mspec-opal/formatters.rb +2 -0
  103. data/spec/mspec-opal/runner.rb +2 -0
  104. data/spec/opal/core/array/dup_spec.rb +2 -0
  105. data/spec/opal/core/exception_spec.rb +2 -0
  106. data/spec/opal/core/hash/internals_spec.rb +154 -206
  107. data/spec/opal/core/hash_spec.rb +2 -0
  108. data/spec/opal/core/iterable_props_spec.rb +2 -0
  109. data/spec/opal/core/kernel/at_exit_spec.rb +2 -0
  110. data/spec/opal/core/kernel/respond_to_spec.rb +2 -0
  111. data/spec/opal/core/language/arguments/mlhs_arg_spec.rb +2 -0
  112. data/spec/opal/core/language/case_spec.rb +13 -0
  113. data/spec/opal/core/language/safe_navigator_spec.rb +2 -0
  114. data/spec/opal/core/language/xstring_send_spec.rb +15 -0
  115. data/spec/opal/core/language/xstring_spec.rb +2 -0
  116. data/spec/opal/core/language_spec.rb +2 -0
  117. data/spec/opal/core/module_spec.rb +44 -0
  118. data/spec/opal/core/number/to_i_spec.rb +2 -0
  119. data/spec/opal/core/object_id_spec.rb +2 -0
  120. data/spec/opal/core/regexp/match_spec.rb +2 -0
  121. data/spec/opal/core/runtime/bridged_classes_spec.rb +38 -0
  122. data/spec/opal/core/runtime/constants_spec.rb +2 -0
  123. data/spec/opal/core/runtime/eval_spec.rb +2 -0
  124. data/spec/opal/core/runtime/exit_spec.rb +2 -0
  125. data/spec/opal/core/runtime/is_a_spec.rb +2 -0
  126. data/spec/opal/core/runtime/loaded_spec.rb +2 -0
  127. data/spec/opal/core/runtime/method_missing_spec.rb +2 -0
  128. data/spec/opal/core/runtime/rescue_spec.rb +2 -0
  129. data/spec/opal/core/runtime/string_spec.rb +2 -0
  130. data/spec/opal/core/runtime/truthy_spec.rb +2 -0
  131. data/spec/opal/core/runtime_spec.rb +2 -6
  132. data/spec/opal/core/string/to_sym_spec.rb +2 -0
  133. data/spec/opal/stdlib/js_spec.rb +2 -0
  134. data/spec/opal/stdlib/native/alias_native_spec.rb +2 -0
  135. data/spec/opal/stdlib/native/array_spec.rb +2 -0
  136. data/spec/opal/stdlib/native/date_spec.rb +2 -0
  137. data/spec/opal/stdlib/native/each_spec.rb +2 -0
  138. data/spec/opal/stdlib/native/element_reference_spec.rb +2 -0
  139. data/spec/opal/stdlib/native/exposure_spec.rb +2 -0
  140. data/spec/opal/stdlib/native/ext_spec.rb +2 -0
  141. data/spec/opal/stdlib/native/hash_spec.rb +30 -2
  142. data/spec/opal/stdlib/native/initialize_spec.rb +2 -0
  143. data/spec/opal/stdlib/native/method_missing_spec.rb +2 -0
  144. data/spec/opal/stdlib/native/native_alias_spec.rb +2 -0
  145. data/spec/opal/stdlib/native/native_class_spec.rb +2 -0
  146. data/spec/opal/stdlib/native/native_module_spec.rb +2 -0
  147. data/spec/opal/stdlib/native/native_reader_spec.rb +2 -0
  148. data/spec/opal/stdlib/native/native_writer_spec.rb +2 -0
  149. data/spec/opal/stdlib/native/new_spec.rb +2 -0
  150. data/spec/opal/stdlib/native/struct_spec.rb +2 -0
  151. data/spec/spec_helper.rb +2 -0
  152. data/stdlib/await.rb +1 -0
  153. data/stdlib/base64.rb +2 -0
  154. data/stdlib/bigdecimal/bignumber.js.rb +2 -0
  155. data/stdlib/bigdecimal/util.rb +1 -0
  156. data/stdlib/bigdecimal.rb +2 -0
  157. data/stdlib/buffer/array.rb +2 -0
  158. data/stdlib/buffer/view.rb +2 -0
  159. data/stdlib/buffer.rb +2 -0
  160. data/stdlib/cgi.rb +14 -0
  161. data/stdlib/console.rb +2 -0
  162. data/stdlib/date/date_time.rb +2 -0
  163. data/stdlib/date.rb +2 -0
  164. data/stdlib/delegate.rb +2 -0
  165. data/stdlib/deno/base.rb +2 -0
  166. data/stdlib/deno/file.rb +2 -0
  167. data/stdlib/erb.rb +2 -0
  168. data/stdlib/gjs/io.rb +2 -0
  169. data/stdlib/gjs/kernel.rb +2 -0
  170. data/stdlib/headless_browser/base.rb +2 -0
  171. data/stdlib/headless_browser/file.rb +1 -0
  172. data/stdlib/headless_browser.rb +1 -0
  173. data/stdlib/js.rb +2 -0
  174. data/stdlib/json.rb +9 -15
  175. data/stdlib/logger.rb +2 -0
  176. data/stdlib/nashorn/file.rb +2 -0
  177. data/stdlib/nashorn/io.rb +2 -0
  178. data/stdlib/native.rb +48 -48
  179. data/stdlib/nodejs/base.rb +2 -0
  180. data/stdlib/nodejs/dir.rb +2 -0
  181. data/stdlib/nodejs/env.rb +2 -0
  182. data/stdlib/nodejs/file.rb +2 -0
  183. data/stdlib/nodejs/fileutils.rb +2 -0
  184. data/stdlib/nodejs/io.rb +2 -0
  185. data/stdlib/nodejs/js-yaml-3-6-1.js +1 -1
  186. data/stdlib/nodejs/kernel.rb +2 -0
  187. data/stdlib/nodejs/open-uri.rb +2 -0
  188. data/stdlib/nodejs/pathname.rb +2 -0
  189. data/stdlib/nodejs/require.rb +2 -0
  190. data/stdlib/nodejs/yaml.rb +9 -3
  191. data/stdlib/opal/miniracer.rb +2 -0
  192. data/stdlib/opal-parser.rb +8 -1
  193. data/stdlib/opal-platform.rb +2 -0
  194. data/stdlib/opal-replutils.rb +2 -0
  195. data/stdlib/open-uri.rb +4 -1
  196. data/stdlib/ostruct.rb +4 -2
  197. data/stdlib/pathname.rb +2 -0
  198. data/stdlib/pp.rb +1 -0
  199. data/stdlib/promise/v2.rb +2 -0
  200. data/stdlib/quickjs/io.rb +2 -0
  201. data/stdlib/quickjs/kernel.rb +2 -0
  202. data/stdlib/quickjs.rb +2 -0
  203. data/stdlib/securerandom.rb +2 -0
  204. data/stdlib/strscan.rb +2 -0
  205. data/stdlib/time.rb +2 -0
  206. data/stdlib/uri.rb +1 -0
  207. data/tasks/performance/optimization_status.rb +2 -0
  208. data/tasks/testing.rake +1 -0
  209. data/test/nodejs/test_await.rb +1 -0
  210. data/test/nodejs/test_dir.rb +2 -0
  211. data/test/nodejs/test_error.rb +2 -0
  212. data/test/nodejs/test_file.rb +2 -0
  213. data/test/nodejs/test_string.rb +2 -0
  214. data/test/nodejs/test_yaml.rb +20 -0
  215. metadata +24 -14
  216. 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.
@@ -78,18 +78,17 @@ Ruby syntaxes for word arrays etc are also supported.
78
78
 
79
79
  #### Hash
80
80
 
81
- Inside a generated Ruby script, a function `Opal.hash` is available which
82
- creates a new hash. This is also available in JavaScript as `Opal.hash`
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 } # => Opal.hash("foo", 100, "baz", 700)
87
- { foo: 42, bar: [1, 2, 3] } # => Opal.hash("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
- Similar to hash, there is a function `Opal.range` available to create
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 implemented directly with an Opal class, there's no "toll-free" bridging available (unlike with strings and arrays, for example). However, it's quite possible to interact with hashes from JavaScript:
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 = Opal.hash({a: 1, b: 2});
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(Opal.hash({b: 20, c: 30}));
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: {"a": 10, "b": 20, "c": 30} aka a standard JavaScript object
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.
@@ -1,10 +1,10 @@
1
1
  # Getting Started
2
2
 
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.
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, and can be installed via:
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 started with Opal
19
+ ## Getting Started with Opal
20
20
 
21
- At its very core, opal provides a simple method of compiling a string of ruby into javascript that can run on top of the opal runtime, provided by `opal.js`:
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
- `opal` includes sprockets support for compiling Ruby (and ERB) assets, and treating them as first class JavaScript citizens. It works in a similar way to CoffeeScript, where JavaScript files can simply require Ruby sources, and Ruby sources can require JavaScript and other Ruby files.
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 opal code registers that directory to the Opal load path. Opal will then use all Opal load paths when running sprockets instances. For rails applications, `opal-rails` does this automatically. For building a simple application, we have to do this manually.
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
- ### Adding lookup paths
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 uses a load path which works with sprockets to create a set of locations which opal can require files
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 ruby files in this directory can be discovered.
46
-
42
+ Now, any Ruby files in this directory can be discovered.
47
43
 
48
44
  ## FAQ
49
45
 
50
- ### Why does Opal exist?
46
+ ### Why Does Opal Exist?
51
47
 
52
- To try and keep ruby relevant in a world where client-side applications are making javascript the primary development platform.
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 compatible is Opal?
50
+ ### How Compatible is Opal?
55
51
 
56
- We run opal against the [ruby spec](https://github.com/ruby/spec) as our primary testing setup. We try to make Opal as compatible as possible, whilst also taking into account restrictions of JavaScript when applicable. Opal supports the majority of ruby syntax features, as well as a very large part of the corelib implementation. We support method\_missing, modules, classes, instance\_exec, blocks, procs and lots lots more. Opal can compile and run RSpec unmodified, as well as self hosting the compiler at runtime.
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 version of ruby does Opal target?
54
+ ### What Version of Ruby Does Opal Target?
59
55
 
60
- We are running tests under ruby 3.0.0 conditions, but are mostly compatible with 2.6 level features.
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 doesn't Opal support mutable strings?
58
+ ### Why Doesn't Opal Support Mutable Strings?
63
59
 
64
- All strings in Opal are immutable because ruby strings just get compiled directly into javascript strings, which are immutable. Wrapping ruby strings as a custom JavaScript object would add a lot of overhead as well as making interaction between ruby and javascript libraries more difficult.
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](etting_started.html)
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
- # This complex piece of code tries to check if we can robustly mkdir_p a directory.
81
+ # Check if we can robustly mkdir_p a directory.
82
82
  def self.dir_writable?(*paths)
83
- dir = nil
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
- File.exist?(paths.first) &&
89
- paths.reverse.all? do |i|
90
- !File.exist?(i) || (File.directory?(i) && File.writable?(i))
91
- end
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
- return CDP(options, function(browser_client) {
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
- return CDP(options, function(browser_client) {
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
- sexp.updated(nil, [s(:js_return, *sexp.children)])
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.$$smap[#{key_name.to_s.inspect}];"
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
 
@@ -366,7 +366,7 @@ module Opal
366
366
  end
367
367
 
368
368
  add_special :__OPAL_COMPILER_CONFIG__ do
369
- push fragment "Opal.hash({ arity_check: #{compiler.arity_check?} })"
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
- # /regexp/ =~ rhs
64
- # s(:match_with_lvasgn, lhs, rhs)
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
- re = lhs.children.first.children.first
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
- names = names.map do |name|
87
- # abc = $m3names[:abc]
88
- s(:lvasgn, name,
89
- s(:send,
90
- s(:lvar, :$m3names),
91
- :[],
92
- s(:sym, name)
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
@@ -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
- push expr(key), ', ', expr(value)
87
- end
88
-
89
- wrap '$hash(', ')'
90
- end
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
- hash_keys.each_with_index do |key, idx|
105
- push ', ' unless idx == 0
106
- push "#{key}: "
107
- push hash_obj[key]
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