opal 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (125) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +4 -4
  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/.rubocop.yml +2 -1
  9. data/CHANGELOG.md +95 -1
  10. data/Gemfile +0 -4
  11. data/HACKING.md +1 -1
  12. data/README.md +19 -15
  13. data/UNRELEASED.md +37 -96
  14. data/benchmark-ips/bm_array_unshift.rb +7 -0
  15. data/bin/build-browser-source-map-support +2 -3
  16. data/bin/opal-mspec +2 -0
  17. data/docs/compiler.md +1 -1
  18. data/examples/rack/Gemfile +0 -1
  19. data/examples/rack/Gemfile.lock +0 -4
  20. data/lib/opal/cli.rb +1 -0
  21. data/lib/opal/cli_options.rb +4 -0
  22. data/lib/opal/cli_runners/nodejs.rb +5 -1
  23. data/lib/opal/cli_runners/source-map-support-browser.js +8 -2
  24. data/lib/opal/cli_runners/source-map-support-node.js +3706 -0
  25. data/lib/opal/cli_runners/source-map-support.js +3 -1
  26. data/lib/opal/compiler.rb +2 -2
  27. data/lib/opal/nodes/args/arity_check.rb +1 -0
  28. data/lib/opal/nodes/args/parameters.rb +6 -0
  29. data/lib/opal/nodes/class.rb +1 -13
  30. data/lib/opal/nodes/literal.rb +14 -7
  31. data/lib/opal/nodes/module.rb +13 -9
  32. data/lib/opal/nodes/variables.rb +13 -4
  33. data/lib/opal/nodes/while.rb +54 -17
  34. data/lib/opal/parser.rb +1 -5
  35. data/lib/opal/parser/patch.rb +44 -0
  36. data/lib/opal/repl.rb +7 -0
  37. data/lib/opal/rewriter.rb +4 -0
  38. data/lib/opal/rewriters/arguments.rb +4 -1
  39. data/lib/opal/rewriters/forward_args.rb +54 -0
  40. data/lib/opal/rewriters/logical_operator_assignment.rb +5 -2
  41. data/lib/opal/rewriters/opal_engine_check.rb +5 -7
  42. data/lib/opal/rewriters/pattern_matching.rb +287 -0
  43. data/lib/opal/version.rb +1 -1
  44. data/opal/corelib/array.rb +42 -20
  45. data/opal/corelib/array/pack.rb +6 -1
  46. data/opal/corelib/complex.rb +2 -0
  47. data/opal/corelib/constants.rb +3 -3
  48. data/opal/corelib/hash.rb +45 -38
  49. data/opal/corelib/module.rb +2 -7
  50. data/opal/corelib/number.rb +2 -180
  51. data/opal/corelib/numeric.rb +156 -0
  52. data/opal/corelib/object_space.rb +102 -0
  53. data/opal/corelib/pattern_matching.rb +159 -0
  54. data/opal/corelib/random.rb +31 -66
  55. data/opal/corelib/random/formatter.rb +122 -0
  56. data/opal/corelib/range.rb +50 -19
  57. data/opal/corelib/runtime.js +82 -21
  58. data/opal/corelib/string.rb +86 -52
  59. data/opal/corelib/string/encoding.rb +140 -25
  60. data/opal/corelib/string/unpack.rb +26 -40
  61. data/opal/opal.rb +1 -0
  62. data/opal/opal/full.rb +2 -0
  63. data/package.json +1 -1
  64. data/spec/filters/bugs/array.rb +0 -23
  65. data/spec/filters/bugs/basicobject.rb +3 -0
  66. data/spec/filters/bugs/encoding.rb +0 -2
  67. data/spec/filters/bugs/exception.rb +1 -0
  68. data/spec/filters/bugs/float.rb +0 -2
  69. data/spec/filters/bugs/hash.rb +3 -13
  70. data/spec/filters/bugs/integer.rb +0 -2
  71. data/spec/filters/bugs/kernel.rb +16 -3
  72. data/spec/filters/bugs/language.rb +27 -90
  73. data/spec/filters/bugs/marshal.rb +1 -3
  74. data/spec/filters/bugs/module.rb +16 -1
  75. data/spec/filters/bugs/numeric.rb +4 -12
  76. data/spec/filters/bugs/objectspace.rb +67 -0
  77. data/spec/filters/bugs/pack_unpack.rb +0 -9
  78. data/spec/filters/bugs/pathname.rb +1 -0
  79. data/spec/filters/bugs/proc.rb +8 -0
  80. data/spec/filters/bugs/random.rb +3 -6
  81. data/spec/filters/bugs/range.rb +83 -113
  82. data/spec/filters/bugs/set.rb +2 -0
  83. data/spec/filters/bugs/string.rb +32 -70
  84. data/spec/filters/bugs/struct.rb +2 -10
  85. data/spec/filters/bugs/time.rb +8 -2
  86. data/spec/filters/unsupported/float.rb +3 -0
  87. data/spec/filters/unsupported/freeze.rb +1 -0
  88. data/spec/filters/unsupported/integer.rb +3 -0
  89. data/spec/filters/unsupported/refinements.rb +8 -0
  90. data/spec/filters/unsupported/string.rb +100 -95
  91. data/spec/filters/unsupported/time.rb +4 -0
  92. data/spec/lib/compiler_spec.rb +16 -0
  93. data/spec/lib/rewriters/forward_args_spec.rb +61 -0
  94. data/spec/lib/rewriters/logical_operator_assignment_spec.rb +1 -1
  95. data/spec/lib/rewriters/numblocks_spec.rb +44 -0
  96. data/spec/lib/rewriters/opal_engine_check_spec.rb +49 -4
  97. data/spec/opal/core/language/forward_args_spec.rb +53 -0
  98. data/spec/opal/core/language/infinite_range_spec.rb +13 -0
  99. data/spec/opal/core/language/memoization_spec.rb +16 -0
  100. data/spec/opal/core/language/pattern_matching_spec.rb +124 -0
  101. data/spec/opal/core/module_spec.rb +38 -2
  102. data/spec/opal/core/number/to_i_spec.rb +28 -0
  103. data/spec/opal/core/runtime/bridged_classes_spec.rb +16 -0
  104. data/spec/opal/core/runtime/constants_spec.rb +20 -1
  105. data/spec/opal/core/string/subclassing_spec.rb +16 -0
  106. data/spec/opal/core/string/unpack_spec.rb +22 -0
  107. data/spec/opal/core/string_spec.rb +4 -4
  108. data/spec/ruby_specs +4 -1
  109. data/stdlib/json.rb +3 -1
  110. data/stdlib/promise/v1.rb +1 -0
  111. data/stdlib/promise/v2.rb +386 -0
  112. data/stdlib/securerandom.rb +55 -35
  113. data/tasks/releasing.rake +1 -1
  114. data/tasks/testing.rake +6 -4
  115. data/test/nodejs/test_string.rb +25 -0
  116. data/test/opal/promisev2/test_always.rb +63 -0
  117. data/test/opal/promisev2/test_error.rb +16 -0
  118. data/test/opal/promisev2/test_rescue.rb +59 -0
  119. data/test/opal/promisev2/test_then.rb +90 -0
  120. data/test/opal/promisev2/test_trace.rb +52 -0
  121. data/test/opal/promisev2/test_value.rb +16 -0
  122. data/test/opal/promisev2/test_when.rb +35 -0
  123. data/vendored-minitest/minitest/assertions.rb +2 -0
  124. metadata +47 -8
  125. data/lib/opal/parser/with_c_lexer.rb +0 -15
@@ -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
@@ -0,0 +1,159 @@
1
+ # A "userland" implementation of pattern matching for Opal
2
+
3
+ class PatternMatching
4
+ def self.call(from, pattern)
5
+ pm = new(from, pattern)
6
+ pm.match || (return nil)
7
+ pm.returns
8
+ end
9
+
10
+ def initialize(from, pattern)
11
+ @from, @pattern = from, pattern
12
+ @returns = []
13
+ end
14
+
15
+ attr_reader :returns
16
+
17
+ def match(from = @from, pattern = @pattern)
18
+ if pattern == :var
19
+ @returns << from
20
+ true
21
+ else # Pattern is otherwise an Array
22
+ type, *args = *pattern
23
+
24
+ case type
25
+ when :save # from =>
26
+ @returns << from
27
+ match(from, args[0])
28
+ when :lit # 3, 4, :a, (1..), ... (but also ^a)
29
+ args[0] === from
30
+ when :any # a | b
31
+ args.any? { |arg| match(from, arg) }
32
+ when :all # Array(1) which works as Array & [1] (& doesn't exist though...)
33
+ args.all? { |arg| match(from, arg) }
34
+ when :array # [...]
35
+ fixed_size, array_size, array_match = *args
36
+ return false unless from.respond_to? :deconstruct
37
+ a = from.deconstruct
38
+ return false if fixed_size && a.length != array_size
39
+ return false if a.length < array_size
40
+
41
+ skip_elems = 0
42
+ skip_rests = 0
43
+
44
+ array_match.each_with_index.all? do |elem, i|
45
+ type, *args = elem
46
+ case type
47
+ when :rest
48
+ skip_elems = a.size - array_size
49
+ skip_rests = 1
50
+ match(a[i...i + skip_elems], args[0]) if args[0] # :save?
51
+ true
52
+ else
53
+ match(a[i + skip_elems - skip_rests], elem)
54
+ end
55
+ end
56
+ when :find # [*, a, b, *]
57
+ find_match, = *args
58
+ first, *find_match, last = *find_match
59
+ pattern_length = find_match.length
60
+
61
+ return false unless from.respond_to? :deconstruct
62
+ a = from.deconstruct
63
+ a_length = a.length
64
+ return false if a_length < pattern_length
65
+
66
+ # We will save the backup of returns, to be restored
67
+ # on each iteration to try again.
68
+ returns_backup = @returns.dup
69
+
70
+ # Extract the capture info from first and last.
71
+ # Both are of a form [:rest], or [:rest, :var].
72
+ # So our new variables will be either :var, or nil.
73
+ first, last = first[1], last[1]
74
+
75
+ # Let's try to match each possibility...
76
+ # [A, B, c, d], [a, B, C, d], [a, b, C, D]
77
+ iterations = a_length - pattern_length + 1
78
+
79
+ iterations.times.any? do |skip|
80
+ first_part = a[0, skip]
81
+ content = a[skip, pattern_length]
82
+ last_part = a[skip + pattern_length..-1]
83
+
84
+ match(first_part, first) if first
85
+ success = content.each_with_index.all? do |e, i|
86
+ match(e, find_match[i])
87
+ end
88
+ match(last_part, last) if last
89
+
90
+ # Match failed. Let's not return anything.
91
+ @returns = returns_backup.dup unless success
92
+
93
+ success
94
+ end
95
+ when :hash # {...}
96
+ any_size, hash_match = *args
97
+
98
+ hash_match = hash_match.to_h
99
+
100
+ return false unless from.respond_to? :deconstruct_keys
101
+
102
+ if any_size && any_size != true # a => {a:, **other}
103
+ a = from.deconstruct_keys(nil) # ^^^^^^^
104
+ else
105
+ a = from.deconstruct_keys(hash_match.keys)
106
+ end
107
+
108
+ hash_match.all? do |k, v|
109
+ return false unless a.key? k
110
+ match(a[k], v)
111
+ end || (return false)
112
+
113
+ if any_size && any_size != true
114
+ match(a.except(*hash_match.keys), args[0])
115
+ elsif !any_size
116
+ return false unless a.except(*hash_match.keys).empty?
117
+ end
118
+
119
+ true
120
+ end
121
+ end
122
+ end
123
+ end
124
+
125
+ class Array
126
+ def deconstruct
127
+ self
128
+ end
129
+ end
130
+
131
+ class Hash
132
+ def deconstruct_keys(_)
133
+ self
134
+ end
135
+ end
136
+
137
+ class Struct
138
+ alias deconstruct to_a
139
+ # This function is specified in a very weird way...
140
+ def deconstruct_keys(keys)
141
+ return to_h if keys.nil?
142
+ raise TypeError, 'expected Array or nil' unless Array === keys
143
+ return {} if keys.length > values.length
144
+ out = {}
145
+ keys.each do |key|
146
+ should_break = case key
147
+ when Integer
148
+ values.length < key
149
+ when Symbol # Or String? Doesn't matter, we're in Opal.
150
+ !members.include?(key)
151
+ end
152
+ break if should_break
153
+ out[key] = self[key]
154
+ end
155
+ out
156
+ end
157
+ end
158
+
159
+ class NoMatchingPatternError < StandardError; 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