opal 1.7.4 → 1.8.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (260) 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 +1 -15
  8. data/README.md +7 -7
  9. data/UNRELEASED.md +92 -1
  10. data/benchmark-ips/bm_block_vs_yield.rb +3 -0
  11. data/benchmark-ips/bm_slice_or_not.rb +53 -0
  12. data/docs/bridging.md +112 -0
  13. data/docs/compiled_ruby.md +10 -10
  14. data/docs/getting_started.md +18 -22
  15. data/docs/releasing.md +8 -16
  16. data/lib/opal/cli_runners/chrome_cdp_interface.rb +1 -0
  17. data/lib/opal/cli_runners/firefox_cdp_interface.rb +1 -0
  18. data/lib/opal/compiler.rb +33 -1
  19. data/lib/opal/nodes/args/extract_kwoptarg.rb +2 -1
  20. data/lib/opal/nodes/call.rb +1 -1
  21. data/lib/opal/nodes/call_special.rb +71 -47
  22. data/lib/opal/nodes/closure.rb +15 -7
  23. data/lib/opal/nodes/defined.rb +1 -1
  24. data/lib/opal/nodes/hash.rb +14 -30
  25. data/lib/opal/nodes/if.rb +37 -29
  26. data/lib/opal/nodes/literal.rb +15 -7
  27. data/lib/opal/nodes/rescue.rb +1 -1
  28. data/lib/opal/nodes/x_string.rb +13 -0
  29. data/lib/opal/parser/patch.rb +1 -0
  30. data/lib/opal/rewriters/for_rewriter.rb +36 -24
  31. data/lib/opal/source_map/file.rb +1 -1
  32. data/lib/opal/version.rb +1 -1
  33. data/opal/corelib/array/pack.rb +1 -0
  34. data/opal/corelib/array.rb +110 -64
  35. data/opal/corelib/basic_object.rb +1 -0
  36. data/opal/corelib/binding.rb +2 -0
  37. data/opal/corelib/boolean.rb +1 -0
  38. data/opal/corelib/class.rb +28 -8
  39. data/opal/corelib/comparable.rb +1 -0
  40. data/opal/corelib/complex.rb +3 -1
  41. data/opal/corelib/constants.rb +2 -2
  42. data/opal/corelib/dir.rb +2 -0
  43. data/opal/corelib/enumerable.rb +3 -2
  44. data/opal/corelib/enumerator/arithmetic_sequence.rb +3 -1
  45. data/opal/corelib/enumerator/chain.rb +1 -0
  46. data/opal/corelib/enumerator/generator.rb +1 -0
  47. data/opal/corelib/enumerator/lazy.rb +1 -0
  48. data/opal/corelib/enumerator/yielder.rb +2 -0
  49. data/opal/corelib/enumerator.rb +1 -0
  50. data/opal/corelib/error/errno.rb +2 -0
  51. data/opal/corelib/error.rb +22 -0
  52. data/opal/corelib/file.rb +1 -0
  53. data/opal/corelib/hash.rb +224 -519
  54. data/opal/corelib/helpers.rb +1 -0
  55. data/opal/corelib/io.rb +2 -0
  56. data/opal/corelib/irb.rb +2 -0
  57. data/opal/corelib/kernel/format.rb +1 -0
  58. data/opal/corelib/kernel.rb +70 -15
  59. data/opal/corelib/main.rb +2 -0
  60. data/opal/corelib/marshal/read_buffer.rb +2 -0
  61. data/opal/corelib/marshal/write_buffer.rb +2 -0
  62. data/opal/corelib/math/polyfills.rb +2 -0
  63. data/opal/corelib/math.rb +1 -0
  64. data/opal/corelib/method.rb +2 -0
  65. data/opal/corelib/module.rb +45 -5
  66. data/opal/corelib/nil.rb +3 -1
  67. data/opal/corelib/number.rb +30 -1
  68. data/opal/corelib/numeric.rb +2 -0
  69. data/opal/corelib/object_space.rb +1 -0
  70. data/opal/corelib/pack_unpack/format_string_parser.rb +2 -0
  71. data/opal/corelib/proc.rb +30 -28
  72. data/opal/corelib/process.rb +2 -0
  73. data/opal/corelib/random/formatter.rb +2 -0
  74. data/opal/corelib/random/math_random.js.rb +2 -0
  75. data/opal/corelib/random/mersenne_twister.rb +2 -0
  76. data/opal/corelib/random/seedrandom.js.rb +2 -0
  77. data/opal/corelib/random.rb +1 -0
  78. data/opal/corelib/range.rb +35 -13
  79. data/opal/corelib/rational.rb +3 -1
  80. data/opal/corelib/regexp.rb +1 -0
  81. data/opal/corelib/runtime.js +297 -259
  82. data/opal/corelib/set.rb +2 -0
  83. data/opal/corelib/string/encoding.rb +3 -0
  84. data/opal/corelib/string/unpack.rb +2 -0
  85. data/opal/corelib/string.rb +56 -20
  86. data/opal/corelib/struct.rb +4 -2
  87. data/opal/corelib/time.rb +2 -1
  88. data/opal/corelib/trace_point.rb +2 -0
  89. data/opal/corelib/unsupported.rb +2 -0
  90. data/opal/corelib/variables.rb +2 -0
  91. data/opal.gemspec +2 -2
  92. data/spec/filters/bugs/array.rb +58 -2
  93. data/spec/filters/bugs/basicobject.rb +7 -0
  94. data/spec/filters/bugs/bigdecimal.rb +1 -2
  95. data/spec/filters/bugs/binding.rb +1 -0
  96. data/spec/filters/bugs/class.rb +2 -3
  97. data/spec/filters/bugs/complex.rb +3 -0
  98. data/spec/filters/bugs/date.rb +5 -2
  99. data/spec/filters/bugs/datetime.rb +1 -0
  100. data/spec/filters/bugs/delegate.rb +1 -2
  101. data/spec/filters/bugs/encoding.rb +1 -1
  102. data/spec/filters/bugs/enumerable.rb +11 -3
  103. data/spec/filters/bugs/enumerator.rb +15 -2
  104. data/spec/filters/bugs/exception.rb +9 -4
  105. data/spec/filters/bugs/file.rb +2 -0
  106. data/spec/filters/bugs/float.rb +1 -0
  107. data/spec/filters/bugs/freeze.rb +5 -49
  108. data/spec/filters/bugs/hash.rb +1 -13
  109. data/spec/filters/bugs/integer.rb +5 -6
  110. data/spec/filters/bugs/kernel.rb +12 -43
  111. data/spec/filters/bugs/language.rb +33 -15
  112. data/spec/filters/bugs/marshal.rb +63 -4
  113. data/spec/filters/bugs/method.rb +2 -10
  114. data/spec/filters/bugs/module.rb +18 -7
  115. data/spec/filters/bugs/objectspace.rb +2 -0
  116. data/spec/filters/bugs/pathname.rb +1 -0
  117. data/spec/filters/bugs/proc.rb +4 -2
  118. data/spec/filters/bugs/random.rb +0 -3
  119. data/spec/filters/bugs/range.rb +1 -2
  120. data/spec/filters/bugs/rational.rb +2 -0
  121. data/spec/filters/bugs/refinement.rb +19 -0
  122. data/spec/filters/bugs/regexp.rb +27 -5
  123. data/spec/filters/bugs/ruby-32.rb +0 -6
  124. data/spec/filters/bugs/set.rb +10 -2
  125. data/spec/filters/bugs/singleton.rb +0 -2
  126. data/spec/filters/bugs/string.rb +140 -2
  127. data/spec/filters/bugs/struct.rb +16 -10
  128. data/spec/filters/bugs/time.rb +56 -2
  129. data/spec/filters/bugs/trace_point.rb +1 -0
  130. data/spec/filters/bugs/unboundmethod.rb +4 -9
  131. data/spec/filters/bugs/warnings.rb +0 -1
  132. data/spec/filters/platform/firefox/exception.rb +3 -3
  133. data/spec/filters/platform/firefox/kernel.rb +1 -0
  134. data/spec/filters/platform/safari/exception.rb +2 -2
  135. data/spec/filters/platform/safari/float.rb +1 -0
  136. data/spec/filters/platform/safari/kernel.rb +1 -0
  137. data/spec/filters/platform/safari/literal_regexp.rb +2 -2
  138. data/spec/filters/unsupported/hash.rb +1 -0
  139. data/spec/lib/compiler_spec.rb +28 -17
  140. data/spec/mspec-opal/formatters.rb +2 -0
  141. data/spec/mspec-opal/runner.rb +2 -0
  142. data/spec/opal/core/array/dup_spec.rb +2 -0
  143. data/spec/opal/core/class/clone_spec.rb +36 -0
  144. data/spec/opal/core/exception_spec.rb +2 -0
  145. data/spec/opal/core/hash/internals_spec.rb +154 -206
  146. data/spec/opal/core/hash_spec.rb +2 -0
  147. data/spec/opal/core/iterable_props_spec.rb +2 -0
  148. data/spec/opal/core/kernel/at_exit_spec.rb +2 -0
  149. data/spec/opal/core/kernel/respond_to_spec.rb +2 -0
  150. data/spec/opal/core/language/arguments/mlhs_arg_spec.rb +2 -0
  151. data/spec/opal/core/language/safe_navigator_spec.rb +2 -0
  152. data/spec/opal/core/language/xstring_send_spec.rb +15 -0
  153. data/spec/opal/core/language/xstring_spec.rb +2 -0
  154. data/spec/opal/core/language_spec.rb +2 -0
  155. data/spec/opal/core/module_spec.rb +44 -0
  156. data/spec/opal/core/number/to_i_spec.rb +2 -0
  157. data/spec/opal/core/object_id_spec.rb +2 -6
  158. data/spec/opal/core/regexp/match_spec.rb +2 -0
  159. data/spec/opal/core/runtime/bridged_classes_spec.rb +38 -0
  160. data/spec/opal/core/runtime/constants_spec.rb +2 -0
  161. data/spec/opal/core/runtime/eval_spec.rb +2 -0
  162. data/spec/opal/core/runtime/exit_spec.rb +2 -0
  163. data/spec/opal/core/runtime/is_a_spec.rb +2 -0
  164. data/spec/opal/core/runtime/loaded_spec.rb +2 -0
  165. data/spec/opal/core/runtime/method_missing_spec.rb +2 -0
  166. data/spec/opal/core/runtime/rescue_spec.rb +2 -0
  167. data/spec/opal/core/runtime/string_spec.rb +2 -0
  168. data/spec/opal/core/runtime/truthy_spec.rb +2 -0
  169. data/spec/opal/core/runtime_spec.rb +2 -6
  170. data/spec/opal/core/string/to_sym_spec.rb +2 -0
  171. data/spec/opal/language/predefined_spec.rb +20 -0
  172. data/spec/opal/language/yield_spec.rb +43 -0
  173. data/spec/opal/stdlib/js_spec.rb +2 -0
  174. data/spec/opal/stdlib/native/alias_native_spec.rb +2 -0
  175. data/spec/opal/stdlib/native/array_spec.rb +2 -0
  176. data/spec/opal/stdlib/native/date_spec.rb +2 -0
  177. data/spec/opal/stdlib/native/each_spec.rb +2 -0
  178. data/spec/opal/stdlib/native/element_reference_spec.rb +2 -0
  179. data/spec/opal/stdlib/native/exposure_spec.rb +2 -0
  180. data/spec/opal/stdlib/native/ext_spec.rb +2 -0
  181. data/spec/opal/stdlib/native/hash_spec.rb +30 -2
  182. data/spec/opal/stdlib/native/initialize_spec.rb +2 -0
  183. data/spec/opal/stdlib/native/method_missing_spec.rb +2 -0
  184. data/spec/opal/stdlib/native/native_alias_spec.rb +2 -0
  185. data/spec/opal/stdlib/native/native_class_spec.rb +2 -0
  186. data/spec/opal/stdlib/native/native_module_spec.rb +2 -0
  187. data/spec/opal/stdlib/native/native_reader_spec.rb +2 -0
  188. data/spec/opal/stdlib/native/native_writer_spec.rb +2 -0
  189. data/spec/opal/stdlib/native/new_spec.rb +2 -0
  190. data/spec/opal/stdlib/native/struct_spec.rb +2 -0
  191. data/spec/ruby_specs +0 -2
  192. data/spec/spec_helper.rb +2 -0
  193. data/stdlib/await.rb +1 -0
  194. data/stdlib/base64.rb +2 -0
  195. data/stdlib/bigdecimal/bignumber.js.rb +2 -0
  196. data/stdlib/bigdecimal/util.rb +1 -0
  197. data/stdlib/bigdecimal.rb +4 -0
  198. data/stdlib/buffer/array.rb +2 -0
  199. data/stdlib/buffer/view.rb +2 -0
  200. data/stdlib/buffer.rb +2 -0
  201. data/stdlib/cgi.rb +14 -0
  202. data/stdlib/console.rb +2 -0
  203. data/stdlib/date/date_time.rb +2 -0
  204. data/stdlib/date.rb +2 -0
  205. data/stdlib/delegate.rb +5 -4
  206. data/stdlib/deno/base.rb +2 -0
  207. data/stdlib/deno/file.rb +2 -0
  208. data/stdlib/erb.rb +2 -0
  209. data/stdlib/gjs/io.rb +2 -0
  210. data/stdlib/gjs/kernel.rb +2 -0
  211. data/stdlib/headless_browser/base.rb +2 -0
  212. data/stdlib/headless_browser/file.rb +1 -0
  213. data/stdlib/headless_browser.rb +1 -0
  214. data/stdlib/js.rb +2 -0
  215. data/stdlib/json.rb +9 -15
  216. data/stdlib/logger.rb +2 -0
  217. data/stdlib/nashorn/file.rb +2 -0
  218. data/stdlib/nashorn/io.rb +2 -0
  219. data/stdlib/native.rb +48 -48
  220. data/stdlib/nodejs/base.rb +2 -0
  221. data/stdlib/nodejs/dir.rb +2 -0
  222. data/stdlib/nodejs/env.rb +2 -0
  223. data/stdlib/nodejs/file.rb +2 -0
  224. data/stdlib/nodejs/fileutils.rb +2 -0
  225. data/stdlib/nodejs/io.rb +2 -0
  226. data/stdlib/nodejs/js-yaml-3-6-1.js +1 -1
  227. data/stdlib/nodejs/kernel.rb +2 -0
  228. data/stdlib/nodejs/open-uri.rb +2 -0
  229. data/stdlib/nodejs/pathname.rb +2 -0
  230. data/stdlib/nodejs/require.rb +2 -0
  231. data/stdlib/nodejs/yaml.rb +9 -3
  232. data/stdlib/opal/miniracer.rb +2 -0
  233. data/stdlib/opal-parser.rb +8 -1
  234. data/stdlib/opal-platform.rb +2 -0
  235. data/stdlib/opal-replutils.rb +2 -0
  236. data/stdlib/open-uri.rb +4 -1
  237. data/stdlib/ostruct.rb +4 -2
  238. data/stdlib/pathname.rb +3 -1
  239. data/stdlib/pp.rb +1 -0
  240. data/stdlib/promise/v2.rb +24 -7
  241. data/stdlib/quickjs/io.rb +2 -0
  242. data/stdlib/quickjs/kernel.rb +2 -0
  243. data/stdlib/quickjs.rb +2 -0
  244. data/stdlib/securerandom.rb +2 -0
  245. data/stdlib/stringio.rb +2 -0
  246. data/stdlib/strscan.rb +2 -0
  247. data/stdlib/time.rb +2 -0
  248. data/stdlib/uri.rb +1 -0
  249. data/tasks/performance/optimization_status.rb +2 -0
  250. data/tasks/testing.rake +16 -11
  251. data/test/nodejs/test_await.rb +1 -0
  252. data/test/nodejs/test_dir.rb +2 -0
  253. data/test/nodejs/test_error.rb +2 -0
  254. data/test/nodejs/test_file.rb +2 -0
  255. data/test/nodejs/test_string.rb +2 -0
  256. data/test/nodejs/test_yaml.rb +20 -0
  257. data/test/opal/promisev2/test_always.rb +14 -0
  258. data/test/opal/unsupported_and_bugs.rb +0 -8
  259. metadata +26 -13
  260. data/spec/filters/bugs/openstruct.rb +0 -8
@@ -0,0 +1,53 @@
1
+ %x{
2
+ // Minify common function calls
3
+ var $call = Function.prototype.call;
4
+ var $bind = Function.prototype.bind;
5
+ var $has_own = Object.hasOwn || $call.bind(Object.prototype.hasOwnProperty);
6
+ var $set_proto = Object.setPrototypeOf;
7
+ var $slice = $call.bind(Array.prototype.slice);
8
+ var $splice = $call.bind(Array.prototype.splice);
9
+
10
+ var cnt = 0;
11
+ var fun = function(a,b,c) {
12
+ cnt += a + b + c;
13
+ }
14
+ }
15
+
16
+ Benchmark.ips do |x|
17
+ ary = [1,2,3]
18
+ obj = `{0: 1, 1: 2, 2: 3, length: 3}`
19
+
20
+ x.report('Array.from(array)') do
21
+ `fun.apply(null, Array.from(ary))`
22
+ end
23
+
24
+ x.report('Array.from(obj)') do
25
+ `fun.apply(null, Array.from(obj))`
26
+ end
27
+
28
+ x.report('$slice(array)') do
29
+ `fun.apply(null, $slice(ary))`
30
+ end
31
+
32
+ x.report('$slice(obj)') do
33
+ `fun.apply(null, $slice(obj))`
34
+ end
35
+
36
+ x.report('array') do
37
+ `fun.apply(null, ary)`
38
+ end
39
+
40
+ x.report('obj') do
41
+ `fun.apply(null, obj)`
42
+ end
43
+
44
+ x.report('$slice?(array)') do
45
+ `fun.apply(null, ary.$$is_array ? ary : $slice(ary))`
46
+ end
47
+
48
+ x.report('$slice?(obj)') do
49
+ `fun.apply(null, obj.$$is_array ? obj : $slice(obj))`
50
+ end
51
+
52
+ x.compare!
53
+ end
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/releasing.md CHANGED
@@ -8,21 +8,14 @@ _This guide is a work-in-progress._
8
8
 
9
9
  All of the following is now covered by `bin/rake release:prepare VERSION=v1.2.3`
10
10
 
11
- ### Updating the version
12
-
13
- - Update `lib/opal/version.rb`
14
- - Update `opal/corelib/constants.rb` with the same version number along with release dates
15
-
16
- ### Updating the changelog
17
-
18
- - Ensure all the unreleased changes are documented in UNRELEASED.md
19
- - [skip for pre-releases] Run `bin/rake changelog VERSION=v1.2.3` specifying the version number you're about to release
20
- - [skip for pre-releases] Empty UNRELEASED.md
21
-
22
- ### The commit
23
-
24
- - Commit the updated changelog along with the version bump using this commit message:
25
- "Release v1.2.3"
11
+ 1. Update the version
12
+ - Update `lib/opal/version.rb`
13
+ - Update `opal/corelib/constants.rb` with the same version number along with release dates
14
+ 2. Update the changelog
15
+ - Ensure all the unreleased changes are documented in UNRELEASED.md
16
+ - [skip for pre-releases] Run `bin/rake changelog VERSION=v1.2.3` specifying the version number you're about to release
17
+ - [skip for pre-releases] Empty UNRELEASED.md
18
+ 3. Commit the updated changelog along with the version bump using this commit message: "Release v1.2.3"
26
19
 
27
20
  ---
28
21
 
@@ -56,4 +49,3 @@ All of the following is now covered by `bin/rake release:prepare VERSION=v1.2.3`
56
49
  - Create a new pull request that:
57
50
  - Updates a version to `v1.x.0.dev` in both `lib/opal/version.rb` and `opal/corelib/constants.rb`
58
51
  - Remember to merge that PR before merging anything else next once we decide to not release any more point releases from `master`.
59
-
@@ -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
@@ -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
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
@@ -102,7 +102,7 @@ module Opal
102
102
  def generate_thrower(type, closure, value)
103
103
  id = closure.register_catcher(type)
104
104
  closure.register_thrower(type, id)
105
- push id, '.$throw(', expr_or_empty(value), ')'
105
+ push id, '.$throw(', expr_or_nil(value), ', ', scope.identify!, '.$$is_lambda)'
106
106
  id
107
107
  end
108
108
 
@@ -113,11 +113,11 @@ module Opal
113
113
  id = closure.throwers[type]
114
114
  else
115
115
  id = compiler.unique_temp('t_')
116
- scope = closure.node.scope&.parent || top_scope
117
- scope.add_scope_temp("#{id} = $thrower('#{type}')")
116
+ parent_scope = closure.node.scope&.parent || top_scope
117
+ parent_scope.add_scope_temp("#{id} = $thrower('#{type}')")
118
118
  closure.register_thrower(type, id)
119
119
  end
120
- push id, '.$throw(', expr_or_empty(value), ')'
120
+ push id, '.$throw(', expr_or_nil(value), ', ', scope.identify!, '.$$is_lambda)'
121
121
  id
122
122
  end
123
123
 
@@ -132,7 +132,7 @@ module Opal
132
132
  if iter_closure
133
133
  generate_thrower_without_catcher(:return, iter_closure, value)
134
134
  elsif compiler.eval?
135
- push 'Opal.t_eval_return.$throw(', expr_or_empty(value), ')'
135
+ push 'Opal.t_eval_return.$throw(', expr_or_nil(value), ', false)'
136
136
  else
137
137
  error 'Invalid return'
138
138
  end
@@ -211,9 +211,11 @@ module Opal
211
211
 
212
212
  helper :thrower
213
213
 
214
+ catchers_without_eval_return = catchers.grep_v(:eval_return)
215
+
214
216
  push "} catch($e) {"
215
217
  indent do
216
- @closure.catchers.each do |type|
218
+ catchers.each do |type|
217
219
  case type
218
220
  when :eval_return
219
221
  line "if ($e === Opal.t_eval_return) return $e.$v;"
@@ -225,9 +227,15 @@ module Opal
225
227
  end
226
228
  line "}"
227
229
 
230
+ unless catchers_without_eval_return.empty?
231
+ push " finally {", *catchers_without_eval_return.map { |type| "$t_#{type}.is_orphan = true;" }, "}"
232
+ end
233
+
228
234
  unshift "return " if closure_is? SEND
229
235
 
230
- unshift "var ", catchers.map { |type| "$t_#{type} = $thrower('#{type}')" }.join(", "), "; "
236
+ unless catchers_without_eval_return.empty?
237
+ unshift "var ", catchers_without_eval_return.map { |type| "$t_#{type} = $thrower('#{type}')" }.join(", "), "; "
238
+ end
231
239
  unshift "try { "
232
240
 
233
241
  unless closure_is? JS_FUNCTION
@@ -80,7 +80,7 @@ module Opal
80
80
  push ' if (Opal.rescue($err, [Opal.Exception])) {'
81
81
  push ' try {'
82
82
  push ' return false;'
83
- push ' } finally { Opal.pop_exception() }'
83
+ push ' } finally { Opal.pop_exception($err); }'
84
84
  push ' } else { throw $err; }'
85
85
  push '}})())'
86
86