opal 1.1.1 → 1.2.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +3 -2
  3. data/.github/ISSUE_TEMPLATE/bug-report.md +47 -0
  4. data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  5. data/.github/workflows/build.yml +11 -5
  6. data/.gitignore +1 -0
  7. data/.jshintrc +1 -1
  8. data/Gemfile +0 -4
  9. data/HACKING.md +1 -1
  10. data/README.md +19 -15
  11. data/UNRELEASED.md +41 -0
  12. data/benchmark-ips/bm_array_unshift.rb +7 -0
  13. data/bin/opal-mspec +2 -0
  14. data/docs/compiler.md +1 -1
  15. data/examples/rack/Gemfile +0 -1
  16. data/examples/rack/Gemfile.lock +0 -4
  17. data/lib/opal/cli.rb +1 -0
  18. data/lib/opal/cli_options.rb +4 -0
  19. data/lib/opal/cli_runners/nodejs.rb +4 -0
  20. data/lib/opal/cli_runners/source-map-support-browser.js +3 -1
  21. data/lib/opal/cli_runners/source-map-support-node.js +3 -1
  22. data/lib/opal/cli_runners/source-map-support.js +3 -1
  23. data/lib/opal/compiler.rb +2 -2
  24. data/lib/opal/nodes/args/arity_check.rb +1 -0
  25. data/lib/opal/nodes/args/parameters.rb +6 -0
  26. data/lib/opal/nodes/class.rb +1 -13
  27. data/lib/opal/nodes/literal.rb +14 -7
  28. data/lib/opal/nodes/module.rb +13 -9
  29. data/lib/opal/nodes/variables.rb +13 -4
  30. data/lib/opal/nodes/while.rb +54 -17
  31. data/lib/opal/parser.rb +1 -5
  32. data/lib/opal/parser/patch.rb +34 -0
  33. data/lib/opal/repl.rb +7 -0
  34. data/lib/opal/rewriter.rb +2 -0
  35. data/lib/opal/rewriters/arguments.rb +4 -1
  36. data/lib/opal/rewriters/forward_args.rb +54 -0
  37. data/lib/opal/rewriters/logical_operator_assignment.rb +5 -2
  38. data/lib/opal/rewriters/opal_engine_check.rb +5 -7
  39. data/lib/opal/version.rb +1 -1
  40. data/opal/corelib/array.rb +42 -20
  41. data/opal/corelib/array/pack.rb +6 -1
  42. data/opal/corelib/complex.rb +2 -0
  43. data/opal/corelib/constants.rb +3 -3
  44. data/opal/corelib/hash.rb +36 -38
  45. data/opal/corelib/module.rb +2 -7
  46. data/opal/corelib/number.rb +2 -180
  47. data/opal/corelib/numeric.rb +156 -0
  48. data/opal/corelib/object_space.rb +102 -0
  49. data/opal/corelib/random.rb +31 -66
  50. data/opal/corelib/random/formatter.rb +122 -0
  51. data/opal/corelib/range.rb +50 -19
  52. data/opal/corelib/runtime.js +82 -21
  53. data/opal/corelib/string.rb +86 -52
  54. data/opal/corelib/string/encoding.rb +140 -25
  55. data/opal/corelib/string/unpack.rb +26 -40
  56. data/opal/opal.rb +1 -0
  57. data/opal/opal/full.rb +1 -0
  58. data/spec/filters/bugs/array.rb +0 -22
  59. data/spec/filters/bugs/basicobject.rb +3 -0
  60. data/spec/filters/bugs/encoding.rb +0 -2
  61. data/spec/filters/bugs/exception.rb +1 -0
  62. data/spec/filters/bugs/float.rb +0 -2
  63. data/spec/filters/bugs/hash.rb +2 -7
  64. data/spec/filters/bugs/integer.rb +0 -2
  65. data/spec/filters/bugs/kernel.rb +16 -3
  66. data/spec/filters/bugs/language.rb +6 -14
  67. data/spec/filters/bugs/marshal.rb +1 -3
  68. data/spec/filters/bugs/module.rb +16 -1
  69. data/spec/filters/bugs/numeric.rb +4 -12
  70. data/spec/filters/bugs/objectspace.rb +67 -0
  71. data/spec/filters/bugs/pack_unpack.rb +0 -9
  72. data/spec/filters/bugs/pathname.rb +1 -0
  73. data/spec/filters/bugs/proc.rb +8 -0
  74. data/spec/filters/bugs/random.rb +3 -6
  75. data/spec/filters/bugs/range.rb +83 -113
  76. data/spec/filters/bugs/set.rb +2 -0
  77. data/spec/filters/bugs/string.rb +31 -70
  78. data/spec/filters/bugs/struct.rb +2 -0
  79. data/spec/filters/bugs/time.rb +8 -2
  80. data/spec/filters/unsupported/float.rb +3 -0
  81. data/spec/filters/unsupported/freeze.rb +1 -0
  82. data/spec/filters/unsupported/integer.rb +3 -0
  83. data/spec/filters/unsupported/refinements.rb +5 -0
  84. data/spec/filters/unsupported/string.rb +100 -95
  85. data/spec/filters/unsupported/time.rb +4 -0
  86. data/spec/lib/compiler_spec.rb +16 -0
  87. data/spec/lib/rewriters/forward_args_spec.rb +61 -0
  88. data/spec/lib/rewriters/logical_operator_assignment_spec.rb +1 -1
  89. data/spec/lib/rewriters/numblocks_spec.rb +44 -0
  90. data/spec/lib/rewriters/opal_engine_check_spec.rb +49 -4
  91. data/spec/opal/core/language/forward_args_spec.rb +53 -0
  92. data/spec/opal/core/language/infinite_range_spec.rb +13 -0
  93. data/spec/opal/core/language/memoization_spec.rb +16 -0
  94. data/spec/opal/core/module_spec.rb +38 -2
  95. data/spec/opal/core/number/to_i_spec.rb +28 -0
  96. data/spec/opal/core/runtime/bridged_classes_spec.rb +16 -0
  97. data/spec/opal/core/runtime/constants_spec.rb +20 -1
  98. data/spec/opal/core/string/subclassing_spec.rb +16 -0
  99. data/spec/opal/core/string/unpack_spec.rb +22 -0
  100. data/spec/opal/core/string_spec.rb +4 -4
  101. data/spec/ruby_specs +4 -1
  102. data/stdlib/json.rb +3 -1
  103. data/stdlib/securerandom.rb +55 -35
  104. data/tasks/testing.rake +6 -3
  105. data/test/nodejs/test_string.rb +25 -0
  106. data/vendored-minitest/minitest/assertions.rb +2 -0
  107. metadata +31 -10
  108. data/lib/opal/parser/with_c_lexer.rb +0 -15
@@ -145,6 +145,162 @@ class Numeric
145
145
  to_f.round(digits)
146
146
  end
147
147
 
148
+ def step(limit = undefined, step = undefined, to: undefined, by: undefined, &block)
149
+ %x{
150
+ if (limit !== undefined && to !== undefined) {
151
+ #{raise ArgumentError, 'to is given twice'}
152
+ }
153
+
154
+ if (step !== undefined && by !== undefined) {
155
+ #{raise ArgumentError, 'step is given twice'}
156
+ }
157
+
158
+ if (to !== undefined) {
159
+ limit = to;
160
+ }
161
+
162
+ if (by !== undefined) {
163
+ step = by;
164
+ }
165
+
166
+ if (limit === undefined) {
167
+ limit = nil;
168
+ }
169
+
170
+ function validateParameters() {
171
+ if (step === nil) {
172
+ #{raise TypeError, 'step must be numeric'}
173
+ }
174
+
175
+ if (step != null && #{step == 0}) {
176
+ #{raise ArgumentError, "step can't be 0"}
177
+ }
178
+
179
+ if (step === nil || step == null) {
180
+ step = 1;
181
+ }
182
+
183
+ var sign = #{step <=> 0};
184
+
185
+ if (sign === nil) {
186
+ #{raise ArgumentError, "0 can't be coerced into #{step.class}"}
187
+ }
188
+
189
+ if (limit === nil || limit == null) {
190
+ limit = sign > 0 ? #{Float::INFINITY} : #{-Float::INFINITY};
191
+ }
192
+
193
+ #{Opal.compare(self, limit)}
194
+ }
195
+
196
+ function stepFloatSize() {
197
+ if ((step > 0 && self > limit) || (step < 0 && self < limit)) {
198
+ return 0;
199
+ } else if (step === Infinity || step === -Infinity) {
200
+ return 1;
201
+ } else {
202
+ var abs = Math.abs, floor = Math.floor,
203
+ err = (abs(self) + abs(limit) + abs(limit - self)) / abs(step) * #{Float::EPSILON};
204
+
205
+ if (err === Infinity || err === -Infinity) {
206
+ return 0;
207
+ } else {
208
+ if (err > 0.5) {
209
+ err = 0.5;
210
+ }
211
+
212
+ return floor((limit - self) / step + err) + 1
213
+ }
214
+ }
215
+ }
216
+
217
+ function stepSize() {
218
+ validateParameters();
219
+
220
+ if (step === 0) {
221
+ return Infinity;
222
+ }
223
+
224
+ if (step % 1 !== 0) {
225
+ return stepFloatSize();
226
+ } else if ((step > 0 && self > limit) || (step < 0 && self < limit)) {
227
+ return 0;
228
+ } else {
229
+ var ceil = Math.ceil, abs = Math.abs,
230
+ lhs = abs(self - limit) + 1,
231
+ rhs = abs(step);
232
+
233
+ return ceil(lhs / rhs);
234
+ }
235
+ }
236
+
237
+ }
238
+
239
+ return enum_for(:step, limit, step, &`stepSize`) unless block_given?
240
+
241
+ %x{
242
+ validateParameters();
243
+
244
+ var isDesc = #{step.negative?},
245
+ isInf = #{step == 0} ||
246
+ (limit === Infinity && !isDesc) ||
247
+ (limit === -Infinity && isDesc);
248
+
249
+ if (self.$$is_number && step.$$is_number && limit.$$is_number) {
250
+ if (self % 1 === 0 && (isInf || limit % 1 === 0) && step % 1 === 0) {
251
+ var value = self;
252
+
253
+ if (isInf) {
254
+ for (;; value += step) {
255
+ block(value);
256
+ }
257
+ } else if (isDesc) {
258
+ for (; value >= limit; value += step) {
259
+ block(value);
260
+ }
261
+ } else {
262
+ for (; value <= limit; value += step) {
263
+ block(value);
264
+ }
265
+ }
266
+
267
+ return self;
268
+ } else {
269
+ var begin = #{to_f}.valueOf();
270
+ step = #{step.to_f}.valueOf();
271
+ limit = #{limit.to_f}.valueOf();
272
+
273
+ var n = stepFloatSize();
274
+
275
+ if (!isFinite(step)) {
276
+ if (n !== 0) block(begin);
277
+ } else if (step === 0) {
278
+ while (true) {
279
+ block(begin);
280
+ }
281
+ } else {
282
+ for (var i = 0; i < n; i++) {
283
+ var d = i * step + self;
284
+ if (step >= 0 ? limit < d : limit > d) {
285
+ d = limit;
286
+ }
287
+ block(d);
288
+ }
289
+ }
290
+
291
+ return self;
292
+ }
293
+ }
294
+ }
295
+
296
+ counter = self
297
+
298
+ while `isDesc ? #{counter >= limit} : #{counter <= limit}`
299
+ yield counter
300
+ counter += step
301
+ end
302
+ end
303
+
148
304
  def to_c
149
305
  Complex(self, 0)
150
306
  end
@@ -0,0 +1,102 @@
1
+ # helpers: respond_to, falsy, truthy
2
+
3
+ module ObjectSpace
4
+ module_function
5
+
6
+ %x{
7
+ var callers = {}, registry, add_caller, delete_callers;
8
+ if (typeof FinalizationRegistry === "function") {
9
+ registry = new FinalizationRegistry(function(id) {
10
+ if (typeof callers[id] !== "undefined") {
11
+ for (var i = 0; i < callers[id].length; i++) {
12
+ #{`callers[id][i]`.call(`id`)};
13
+ }
14
+ delete callers[id];
15
+ }
16
+ });
17
+ add_caller = function(id, value) {
18
+ callers[id] = callers[id] || [];
19
+ callers[id].push(value);
20
+ }
21
+ delete_callers = function(id) {
22
+ delete callers[id];
23
+ }
24
+ }
25
+ else {
26
+ // A weak polyfill for FinalizationRegistry
27
+ registry = {
28
+ register: function(){},
29
+ unregister: function(){}
30
+ };
31
+ add_caller = function(){};
32
+ delete_callers = function(){};
33
+ }
34
+ }
35
+
36
+ def define_finalizer(obj, aproc = undefined, &block)
37
+ %x{
38
+ if ($truthy(block)) aproc = block;
39
+ if ($falsy(aproc)) aproc = #{proc};
40
+ if (!$respond_to(aproc, '$call')) {
41
+ #{raise ArgumentError, "Wrong type argument #{aproc.class} (should be callable)"};
42
+ }
43
+ var id = #{obj.__id__};
44
+ add_caller(id, aproc);
45
+ try {
46
+ registry.register(obj, id, obj);
47
+ }
48
+ catch (e) {
49
+ delete_callers(id);
50
+ #{raise ArgumentError, "cannot define finalizer for #{obj.class}"};
51
+ }
52
+ return [0, aproc];
53
+ }
54
+ end
55
+
56
+ def undefine_finalizer(obj)
57
+ %{
58
+ var id = #{obj.__id__};
59
+ registry.unregister(obj);
60
+ delete_callers(id);
61
+ return obj;
62
+ }
63
+ end
64
+
65
+ class WeakMap
66
+ include Enumerable
67
+
68
+ def initialize
69
+ @weak_map = `new WeakMap()`
70
+ @primitive_map = {}
71
+ end
72
+
73
+ def [](p1)
74
+ %x{
75
+ if (typeof p1 !== "function" && typeof p1 !== "object") return #{@primitive_map[p1]};
76
+ return #{@weak_map}.get(p1);
77
+ }
78
+ end
79
+
80
+ def []=(p1, p2)
81
+ %x{
82
+ if (typeof p1 !== "function" && typeof p1 !== "object") return #{@primitive_map[p1] = p2};
83
+ return #{@weak_map}.set(p1, p2);
84
+ }
85
+ end
86
+
87
+ def include?(p1)
88
+ %x{
89
+ if (typeof p1 !== "function" && typeof p1 !== "object") return #{@primitive_map.key? p1};
90
+ return #{@weak_map}.has(p1);
91
+ }
92
+ end
93
+ alias member? include?
94
+ alias key? include?
95
+
96
+ %i[each each_key each_value each_pair keys values size length].each do |i|
97
+ define_method i do |*|
98
+ raise NotImplementedError, "##{i} can't be implemented on top of JS interfaces"
99
+ end
100
+ end
101
+ end
102
+ end
@@ -1,6 +1,18 @@
1
+ # helpers: falsy
2
+
1
3
  class Random
2
4
  attr_reader :seed, :state
3
5
 
6
+ def self._verify_count(count)
7
+ %x{
8
+ if ($falsy(count)) count = 16;
9
+ if (typeof count !== "number") count = #{`count`.to_int};
10
+ if (count < 0) #{raise ArgumentError, 'negative string size (or size too big)'};
11
+ count = Math.floor(count);
12
+ return count;
13
+ }
14
+ end
15
+
4
16
  def initialize(seed = Random.new_seed)
5
17
  seed = Opal.coerce_to!(seed, Integer, :to_int)
6
18
  @state = seed
@@ -29,13 +41,7 @@ class Random
29
41
  end
30
42
 
31
43
  def self.urandom(size)
32
- size = Opal.coerce_to!(size, Integer, :to_int)
33
-
34
- if size < 0
35
- raise ArgumentError, 'negative string size (or size too big)'
36
- end
37
-
38
- Array.new(size) { rand(255).chr }.join.encode('ASCII-8BIT')
44
+ ::SecureRandom.bytes(size)
39
45
  end
40
46
 
41
47
  def ==(other)
@@ -45,74 +51,33 @@ class Random
45
51
  end
46
52
 
47
53
  def bytes(length)
48
- length = Opal.coerce_to!(length, Integer, :to_int)
54
+ length = Random._verify_count(length)
49
55
 
50
56
  Array.new(length) { rand(255).chr }.join.encode('ASCII-8BIT')
51
57
  end
52
58
 
59
+ def self.bytes(length)
60
+ DEFAULT.bytes(length)
61
+ end
62
+
53
63
  def rand(limit = undefined)
64
+ random_number(limit)
65
+ end
66
+
67
+ # Not part of the Ruby interface (use #random_number for portability), but
68
+ # used by Random::Formatter as a shortcut, as for Random interface the float
69
+ # RNG is primary.
70
+ def random_float
54
71
  %x{
55
- function randomFloat() {
56
- self.state++;
57
- return Opal.$$rand.rand(self.$rng);
58
- }
59
-
60
- function randomInt() {
61
- return Math.floor(randomFloat() * limit);
62
- }
63
-
64
- function randomRange() {
65
- var min = limit.begin,
66
- max = limit.end;
67
-
68
- if (min === nil || max === nil) {
69
- return nil;
70
- }
71
-
72
- var length = max - min;
73
-
74
- if (length < 0) {
75
- return nil;
76
- }
77
-
78
- if (length === 0) {
79
- return min;
80
- }
81
-
82
- if (max % 1 === 0 && min % 1 === 0 && !limit.excl) {
83
- length++;
84
- }
85
-
86
- return self.$rand(length) + min;
87
- }
88
-
89
- if (limit == null) {
90
- return randomFloat();
91
- } else if (limit.$$is_range) {
92
- return randomRange();
93
- } else if (limit.$$is_number) {
94
- if (limit <= 0) {
95
- #{raise ArgumentError, "invalid argument - #{limit}"}
96
- }
97
-
98
- if (limit % 1 === 0) {
99
- // integer
100
- return randomInt();
101
- } else {
102
- return randomFloat() * limit;
103
- }
104
- } else {
105
- limit = #{Opal.coerce_to!(limit, Integer, :to_int)};
106
-
107
- if (limit <= 0) {
108
- #{raise ArgumentError, "invalid argument - #{limit}"}
109
- }
110
-
111
- return randomInt();
112
- }
72
+ self.state++;
73
+ return Opal.$$rand.rand(self.$rng);
113
74
  }
114
75
  end
115
76
 
77
+ def self.random_float
78
+ DEFAULT.random_float
79
+ end
80
+
116
81
  def self.generator=(generator)
117
82
  `Opal.$$rand = #{generator}`
118
83
 
@@ -0,0 +1,122 @@
1
+ class Random
2
+ module Formatter
3
+ def hex(count = nil)
4
+ count = Random._verify_count(count)
5
+ %x{
6
+ var bytes = #{bytes(count)};
7
+ var out = "";
8
+ for (var i = 0; i < #{count}; i++) {
9
+ out += bytes.charCodeAt(i).toString(16).padStart(2, '0');
10
+ }
11
+ return #{`out`.encode('US-ASCII')};
12
+ }
13
+ end
14
+
15
+ def random_bytes(count = nil)
16
+ bytes(count)
17
+ end
18
+
19
+ def base64(count = nil)
20
+ Base64.strict_encode64(random_bytes(count)).encode('US-ASCII')
21
+ end
22
+
23
+ def urlsafe_base64(count = nil, padding = false)
24
+ Base64.urlsafe_encode64(random_bytes(count), padding).encode('US-ASCII')
25
+ end
26
+
27
+ def uuid
28
+ str = hex(16).split('')
29
+ str[12] = '4'
30
+ str[16] = `(parseInt(#{str[16]}, 16) & 3 | 8).toString(16)`
31
+ str = [str[0...8], str[8...12], str[12...16], str[16...20], str[20...32]]
32
+ str = str.map(&:join)
33
+ str.join('-')
34
+ end
35
+
36
+ # Implemented in terms of `#bytes` for SecureRandom, but Random overrides this
37
+ # method to implement `#bytes` in terms of `#random_float`. Not part of standard
38
+ # Ruby interface - use random_number for portability.
39
+ def random_float
40
+ bs = bytes(4)
41
+ num = 0
42
+ 4.times do |i|
43
+ num <<= 8
44
+ num |= bs[i].ord
45
+ end
46
+ num.abs / 0x7fffffff
47
+ end
48
+
49
+ def random_number(limit = undefined)
50
+ %x{
51
+ function randomFloat() {
52
+ return #{random_float};
53
+ }
54
+
55
+ function randomInt(max) {
56
+ return Math.floor(randomFloat() * max);
57
+ }
58
+
59
+ function randomRange() {
60
+ var min = limit.begin,
61
+ max = limit.end;
62
+
63
+ if (min === nil || max === nil) {
64
+ return nil;
65
+ }
66
+
67
+ var length = max - min;
68
+
69
+ if (length < 0) {
70
+ return nil;
71
+ }
72
+
73
+ if (length === 0) {
74
+ return min;
75
+ }
76
+
77
+ if (max % 1 === 0 && min % 1 === 0 && !limit.excl) {
78
+ length++;
79
+ }
80
+
81
+ return randomInt(length) + min;
82
+ }
83
+
84
+ if (limit == null) {
85
+ return randomFloat();
86
+ } else if (limit.$$is_range) {
87
+ return randomRange();
88
+ } else if (limit.$$is_number) {
89
+ if (limit <= 0) {
90
+ #{raise ArgumentError, "invalid argument - #{limit}"}
91
+ }
92
+
93
+ if (limit % 1 === 0) {
94
+ // integer
95
+ return randomInt(limit);
96
+ } else {
97
+ return randomFloat() * limit;
98
+ }
99
+ } else {
100
+ limit = #{Opal.coerce_to!(limit, Integer, :to_int)};
101
+
102
+ if (limit <= 0) {
103
+ #{raise ArgumentError, "invalid argument - #{limit}"}
104
+ }
105
+
106
+ return randomInt(limit);
107
+ }
108
+ }
109
+ end
110
+
111
+ def alphanumeric(count = nil)
112
+ count = Random._verify_count(count)
113
+ map = ['0'..'9', 'a'..'z', 'A'..'Z'].map(&:to_a).flatten
114
+ Array.new(count) do |i|
115
+ map[random_number(map.length)]
116
+ end.join
117
+ end
118
+ end
119
+
120
+ include Random::Formatter
121
+ extend Random::Formatter
122
+ end