opal 0.7.2 → 0.8.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +6 -1
  3. data/CHANGELOG.md +29 -0
  4. data/CONTRIBUTING.md +51 -4
  5. data/Gemfile +3 -0
  6. data/README.md +5 -5
  7. data/config.ru +1 -1
  8. data/examples/sinatra/Gemfile +1 -0
  9. data/examples/sinatra/config.ru +13 -3
  10. data/lib/mspec/opal/rake_task.rb +21 -30
  11. data/lib/mspec/opal/runner.rb +37 -0
  12. data/lib/mspec/opal/special_calls.rb +6 -0
  13. data/lib/opal/builder.rb +1 -0
  14. data/lib/opal/builder_processors.rb +5 -2
  15. data/lib/opal/cli_runners/phantom.js +10 -1
  16. data/lib/opal/compiler.rb +6 -3
  17. data/lib/opal/config.rb +48 -0
  18. data/lib/opal/nodes/call.rb +3 -2
  19. data/lib/opal/nodes/literal.rb +19 -2
  20. data/lib/opal/parser/grammar.rb +2224 -2196
  21. data/lib/opal/parser/grammar.y +25 -7
  22. data/lib/opal/parser/lexer.rb +12 -9
  23. data/lib/opal/path_reader.rb +1 -1
  24. data/lib/opal/sprockets/erb.rb +6 -20
  25. data/lib/opal/sprockets/path_reader.rb +4 -2
  26. data/lib/opal/sprockets/processor.rb +135 -80
  27. data/lib/opal/sprockets/server.rb +49 -78
  28. data/lib/opal/sprockets/source_map_header_patch.rb +41 -0
  29. data/lib/opal/sprockets/source_map_server.rb +115 -0
  30. data/lib/opal/version.rb +1 -1
  31. data/lib/tilt/opal.rb +48 -0
  32. data/opal.gemspec +1 -1
  33. data/opal/corelib/array.rb +179 -51
  34. data/opal/corelib/array/inheritance.rb +14 -0
  35. data/opal/corelib/boolean.rb +5 -0
  36. data/opal/corelib/hash.rb +1 -1
  37. data/opal/corelib/kernel.rb +660 -164
  38. data/opal/corelib/match_data.rb +44 -21
  39. data/opal/corelib/module.rb +83 -53
  40. data/opal/corelib/numeric.rb +15 -1
  41. data/opal/corelib/regexp.rb +31 -75
  42. data/opal/corelib/runtime.js +20 -8
  43. data/opal/corelib/string.rb +754 -243
  44. data/opal/corelib/string/inheritance.rb +20 -3
  45. data/opal/corelib/struct.rb +30 -6
  46. data/opal/corelib/variables.rb +2 -2
  47. data/spec/filters/bugs/array.rb +0 -39
  48. data/spec/filters/bugs/kernel.rb +10 -7
  49. data/spec/filters/bugs/module.rb +21 -0
  50. data/spec/filters/bugs/opal.rb +0 -5
  51. data/spec/filters/bugs/singleton.rb +0 -2
  52. data/spec/filters/bugs/string.rb +69 -315
  53. data/spec/filters/bugs/struct.rb +0 -16
  54. data/spec/filters/unsupported/encoding.rb +7 -0
  55. data/spec/filters/unsupported/float.rb +3 -0
  56. data/spec/filters/unsupported/integer_size.rb +52 -0
  57. data/spec/filters/unsupported/marshal.rb +4 -0
  58. data/spec/filters/unsupported/mutable_strings.rb +37 -0
  59. data/spec/filters/unsupported/private_methods.rb +11 -0
  60. data/spec/filters/unsupported/rational_numbers.rb +4 -0
  61. data/spec/filters/unsupported/regular_expressions.rb +47 -0
  62. data/spec/filters/unsupported/symbols.rb +7 -0
  63. data/spec/filters/unsupported/tainted.rb +23 -1
  64. data/spec/filters/unsupported/trusted.rb +5 -0
  65. data/spec/lib/fixtures/complex_sprockets.js.rb.erb +4 -0
  66. data/spec/lib/fixtures/jst_file.js.jst +1 -0
  67. data/spec/lib/parser/call_spec.rb +19 -0
  68. data/spec/lib/parser/def_spec.rb +6 -0
  69. data/spec/lib/sprockets/erb_spec.rb +17 -4
  70. data/spec/lib/sprockets/processor_spec.rb +50 -18
  71. data/spec/lib/sprockets/server_spec.rb +39 -11
  72. data/spec/lib/tilt/opal_spec.rb +19 -0
  73. data/spec/opal/core/kernel/format_spec.rb +10 -10
  74. data/spec/opal/core/language/predefined_spec.rb +10 -0
  75. data/spec/opal/core/object_id_spec.rb +56 -0
  76. data/spec/opal/core/runtime/bridged_classes_spec.rb +4 -4
  77. data/spec/opal/core/runtime_spec.rb +7 -0
  78. data/spec/opal/stdlib/native/native_class_spec.rb +1 -1
  79. data/spec/opal/stdlib/promise/always_spec.rb +30 -0
  80. data/spec/opal/stdlib/promise/then_spec.rb +8 -0
  81. data/spec/opal/stdlib/promise/trace_spec.rb +8 -0
  82. data/spec/rubyspecs +15 -104
  83. data/spec/spec_helper.rb +4 -0
  84. data/stdlib/native.rb +7 -18
  85. data/stdlib/nodejs/file.rb +1 -1
  86. data/stdlib/opal-parser.rb +1 -0
  87. data/stdlib/pp.rb +7 -5
  88. data/stdlib/promise.rb +53 -41
  89. data/tasks/testing.rake +8 -6
  90. metadata +28 -14
  91. data/spec/filters/bugs/match_data.rb +0 -13
  92. data/spec/filters/bugs/numeric.rb +0 -22
  93. data/spec/filters/bugs/regexp.rb +0 -9
  94. data/spec/filters/bugs/unknown.rb +0 -11
@@ -9,60 +9,12 @@ require 'opal/source_map'
9
9
  require 'sprockets'
10
10
  require 'sourcemap'
11
11
  require 'erb'
12
+ require 'opal/sprockets/source_map_server'
13
+ require 'opal/sprockets/source_map_header_patch'
12
14
 
13
15
  module Opal
14
-
15
- class SourceMapServer
16
- def initialize sprockets, prefix = '/'
17
- @sprockets = sprockets
18
- @prefix = prefix
19
- end
20
-
21
- attr_reader :sprockets, :prefix
22
-
23
- def inspect
24
- "#<#{self.class}:#{object_id}>"
25
- end
26
-
27
- def call(env)
28
- prefix_regex = %r{^(?:#{prefix}/|/)}
29
- path_info = env['PATH_INFO'].to_s.sub(prefix_regex, '')
30
-
31
- case path_info
32
- when %r{^(.*)\.map$}
33
- path = $1
34
- asset = sprockets[path]
35
- return not_found(path) if asset.nil?
36
-
37
- # "logical_name" of a BundledAsset keeps the .js extension
38
- source = register[asset.logical_path.sub(/\.js$/, '')]
39
- return not_found(asset) if source.nil?
40
-
41
- map = JSON.parse(source)
42
- map['sources'] = map['sources'].map {|s| "#{prefix}/#{s}"}
43
- source = map.to_json
44
- return not_found(asset) if source.nil?
45
-
46
- return [200, {"Content-Type" => "text/json"}, [source.to_s]]
47
- when %r{^(.*)\.rb$}
48
- source = File.read(sprockets.resolve(path_info))
49
- return not_found(path_info) if source.nil?
50
- return [200, {"Content-Type" => "text/ruby"}, [source]]
51
- else
52
- not_found(path_info)
53
- end
54
- end
55
-
56
- def not_found(*messages)
57
- not_found = [404, {}, [{not_found: messages, keys: register.keys}.inspect]]
58
- end
59
-
60
- def register
61
- Opal::Processor.source_map_register
62
- end
63
- end
64
-
65
16
  class Server
17
+ SOURCE_MAPS_PREFIX_PATH = '/__OPAL_SOURCE_MAPS__'
66
18
 
67
19
  attr_accessor :debug, :use_index, :index_path, :main, :public_root,
68
20
  :public_urls, :sprockets, :prefix
@@ -113,16 +65,29 @@ module Opal
113
65
  def create_app
114
66
  server, sprockets, prefix = self, @sprockets, self.prefix
115
67
  sprockets.logger.level ||= Logger::DEBUG
68
+ source_map_enabled = self.source_map_enabled
69
+ if source_map_enabled
70
+ maps_prefix = SOURCE_MAPS_PREFIX_PATH
71
+ maps_app = SourceMapServer.new(sprockets, maps_prefix)
72
+ ::Opal::Sprockets::SourceMapHeaderPatch.inject!(maps_prefix)
73
+ end
74
+
116
75
  @app = Rack::Builder.app do
117
76
  not_found = lambda { |env| [404, {}, []] }
118
77
  use Rack::Deflater
119
78
  use Rack::ShowExceptions
120
79
  use Index, server if server.use_index
121
- assets = []
122
- assets << SourceMapServer.new(sprockets, prefix) if server.source_map_enabled
123
- assets << sprockets
124
- map(prefix) { run Rack::Cascade.new(assets) }
125
- run Rack::Static.new(not_found, :root => server.public_root, :urls => server.public_urls)
80
+ if source_map_enabled
81
+ map(maps_prefix) do
82
+ require 'rack/conditionalget'
83
+ require 'rack/etag'
84
+ use Rack::ConditionalGet
85
+ use Rack::ETag
86
+ run maps_app
87
+ end
88
+ end
89
+ map(prefix) { run sprockets }
90
+ run Rack::Static.new(not_found, root: server.public_root, urls: server.public_urls)
126
91
  end
127
92
  end
128
93
 
@@ -152,37 +117,43 @@ module Opal
152
117
  raise "index does not exist: #{@index_path}" unless File.exist?(@index_path)
153
118
  Tilt.new(@index_path).render(self)
154
119
  else
155
- ::ERB.new(SOURCE).result binding
120
+ source
156
121
  end
157
122
  end
158
123
 
159
- def javascript_include_tag source
160
- if @server.debug
161
- assets = @server.sprockets[source].to_a
124
+ def javascript_include_tag name
125
+ sprockets = @server.sprockets
126
+ prefix = @server.prefix
127
+ asset = sprockets[name]
128
+ raise "Cannot find asset: #{name}" if asset.nil?
129
+ scripts = []
162
130
 
163
- raise "Cannot find asset: #{source}" if assets.empty?
164
-
165
- scripts = assets.map do |a|
166
- %Q{<script src="/assets/#{ a.logical_path }?body=1"></script>}
131
+ if @server.debug
132
+ asset.to_a.map do |dependency|
133
+ scripts << %{<script src="#{prefix}/#{dependency.logical_path}?body=1"></script>}
167
134
  end
168
-
169
- scripts.join "\n"
170
135
  else
171
- "<script src=\"/assets/#{source}.js\"></script>"
136
+ scripts << %{<script src="#{prefix}/#{name}.js"></script>}
172
137
  end
138
+
139
+ scripts << %{<script>#{Opal::Processor.load_asset_code(sprockets, name)}</script>}
140
+
141
+ scripts.join "\n"
173
142
  end
174
143
 
175
- SOURCE = <<-HTML
176
- <!DOCTYPE html>
177
- <html>
178
- <head>
179
- <title>Opal Server</title>
180
- </head>
181
- <body>
182
- <%= javascript_include_tag @server.main %>
183
- </body>
184
- </html>
185
- HTML
144
+ def source
145
+ <<-HTML
146
+ <!DOCTYPE html>
147
+ <html>
148
+ <head>
149
+ <title>Opal Server</title>
150
+ </head>
151
+ <body>
152
+ #{javascript_include_tag @server.main}
153
+ </body>
154
+ </html>
155
+ HTML
156
+ end
186
157
  end
187
158
  end
188
159
  end
@@ -0,0 +1,41 @@
1
+ require 'sprockets/server'
2
+
3
+ module Opal
4
+ module Sprockets
5
+ module SourceMapHeaderPatch
6
+ # Adds the source map header to all sprocket responses for assets
7
+ # with a .rb or .opal extension in the extension chain.
8
+ def headers_with_opal_source_maps(env, asset, length)
9
+ headers_without_opal_source_maps(env, asset, length).tap do |current_headers|
10
+ if asset.pathname.to_s =~ /\.(rb|opal)\b/
11
+ base_path = asset.logical_path.gsub('.js', '')
12
+ current_headers['X-SourceMap'] = "#{::Opal::Sprockets::SourceMapHeaderPatch.prefix}/#{base_path}.map"
13
+ end
14
+ end
15
+ end
16
+
17
+ def self.included(base)
18
+ # Poor man's alias_method_chain :)
19
+ base.send(:alias_method, :headers_without_opal_source_maps, :headers)
20
+ base.send(:alias_method, :headers, :headers_with_opal_source_maps)
21
+ end
22
+
23
+ def self.inject!(prefix)
24
+ self.prefix = prefix
25
+ unless ::Sprockets::Server.ancestors.include?(self)
26
+ ::Sprockets::Server.send :include, self
27
+ end
28
+ end
29
+
30
+ def self.prefix
31
+ @prefix
32
+ end
33
+
34
+ def self.prefix= val
35
+ @prefix = val
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+
@@ -0,0 +1,115 @@
1
+ module Opal
2
+ class SourceMapServer
3
+ # Carelessly taken from Sprockets::Caching (Sprockets v2)
4
+ class Cache
5
+ def initialize
6
+ @cache = {}
7
+ end
8
+
9
+ attr_reader :cache
10
+
11
+ def cache_get(key)
12
+ # `Cache#get(key)` for Memcache
13
+ if cache.respond_to?(:get)
14
+ cache.get(key)
15
+
16
+ # `Cache#[key]` so `Hash` can be used
17
+ elsif cache.respond_to?(:[])
18
+ cache[key]
19
+
20
+ # `Cache#read(key)` for `ActiveSupport::Cache` support
21
+ elsif cache.respond_to?(:read)
22
+ cache.read(key)
23
+
24
+ else
25
+ nil
26
+ end
27
+ end
28
+
29
+ def cache_set(key, value)
30
+ # `Cache#set(key, value)` for Memcache
31
+ if cache.respond_to?(:set)
32
+ cache.set(key, value)
33
+
34
+ # `Cache#[key]=value` so `Hash` can be used
35
+ elsif cache.respond_to?(:[]=)
36
+ cache[key] = value
37
+
38
+ # `Cache#write(key, value)` for `ActiveSupport::Cache` support
39
+ elsif cache.respond_to?(:write)
40
+ cache.write(key, value)
41
+ end
42
+
43
+ value
44
+ end
45
+ end
46
+
47
+ def self.get_map_cache(sprockets, logical_path)
48
+ logical_path = logical_path.gsub(/\.js$/, '')
49
+ cache_key = cache_key_for_path(logical_path+'.map')
50
+ cache(sprockets).cache_get(cache_key)
51
+ end
52
+
53
+ def self.set_map_cache(sprockets, logical_path, map_contents)
54
+ logical_path = logical_path.gsub(/\.js$/, '')
55
+ cache_key = cache_key_for_path(logical_path+'.map')
56
+ cache(sprockets).cache_set(cache_key, map_contents)
57
+ end
58
+
59
+ def self.cache(sprockets)
60
+ @cache ||= sprockets.cache ? sprockets : Cache.new
61
+ end
62
+
63
+ def self.cache_key_for_path(logical_path)
64
+ base_name = logical_path.gsub(/\.js$/, '')
65
+ File.join('opal', 'source_maps', base_name)
66
+ end
67
+
68
+
69
+ def initialize sprockets, prefix = '/'
70
+ @sprockets = sprockets
71
+ @prefix = prefix
72
+ end
73
+
74
+ attr_reader :sprockets, :prefix
75
+
76
+ def inspect
77
+ "#<#{self.class}:#{object_id}>"
78
+ end
79
+
80
+ def call(env)
81
+ prefix_regex = %r{^(?:#{prefix}/|/)}
82
+ path_info = env['PATH_INFO'].to_s.sub(prefix_regex, '')
83
+
84
+ case path_info
85
+ when %r{^(.*)\.map$}
86
+ path = $1
87
+ asset = sprockets[path]
88
+ return not_found(path) if asset.nil?
89
+
90
+ # "logical_name" of a BundledAsset keeps the .js extension
91
+ source = SourceMapServer.get_map_cache(sprockets, asset.logical_path)
92
+ return not_found(asset) if source.nil?
93
+
94
+ map = JSON.parse(source)
95
+ map['sources'] = map['sources'].map {|s| "#{prefix}/#{s}"}
96
+ source = map.to_json
97
+
98
+ return [200, {"Content-Type" => "text/json"}, [source.to_s]]
99
+ when %r{^(.*)\.rb$}
100
+ begin
101
+ asset = sprockets.resolve(path_info.sub(/\.rb$/,''))
102
+ rescue Sprockets::FileNotFound
103
+ return not_found(path_info)
104
+ end
105
+ return [200, {"Content-Type" => "text/ruby"}, [Pathname(asset).read]]
106
+ else
107
+ not_found(path_info)
108
+ end
109
+ end
110
+
111
+ def not_found(*messages)
112
+ not_found = [404, {}, [{not_found: messages}.inspect]]
113
+ end
114
+ end
115
+ end
data/lib/opal/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Opal
2
- VERSION = '0.7.2'
2
+ VERSION = '0.8.0.beta1'
3
3
  end
data/lib/tilt/opal.rb ADDED
@@ -0,0 +1,48 @@
1
+ require 'tilt'
2
+ require 'opal/compiler'
3
+ require 'opal/config'
4
+ require 'opal/version'
5
+
6
+ $OPAL_SOURCE_MAPS = {}
7
+
8
+ module Opal
9
+ class TiltTemplate < Tilt::Template
10
+ self.default_mime_type = 'application/javascript'
11
+
12
+ def self.inherited(subclass)
13
+ subclass.default_mime_type = 'application/javascript'
14
+ end
15
+
16
+ def self.engine_initialized?
17
+ true
18
+ end
19
+
20
+ def self.version
21
+ ::Opal::VERSION
22
+ end
23
+
24
+ def self.compiler_options
25
+ Opal::Config.compiler_options.merge(requirable: true)
26
+ end
27
+
28
+ def initialize_engine
29
+ require_template_library 'opal'
30
+ end
31
+
32
+ def prepare
33
+ end
34
+
35
+ def evaluate(context, locals, &block)
36
+ compiler_options = Opal::Config.compiler_options.merge(file: file)
37
+ compiler = Compiler.new(data, compiler_options)
38
+ compiler.compile.to_s
39
+ end
40
+
41
+ def compiler_options
42
+ self.class.compiler_options
43
+ end
44
+ end
45
+ end
46
+
47
+ Tilt.register 'rb', Opal::TiltTemplate
48
+ Tilt.register 'opal', Opal::TiltTemplate
data/opal.gemspec CHANGED
@@ -21,7 +21,7 @@ Gem::Specification.new do |s|
21
21
  required_ruby_version = '>= 1.9.3'
22
22
 
23
23
  s.add_dependency 'sourcemap', '~> 0.1.0'
24
- s.add_dependency 'sprockets', '>= 2.2.3', '< 3.0.0'
24
+ s.add_dependency 'sprockets', '>= 2.2.3', '< 4.0.0'
25
25
  s.add_dependency 'hike', '~> 1.2'
26
26
  s.add_dependency 'tilt', '~> 1.4'
27
27
 
@@ -228,33 +228,52 @@ class Array
228
228
  end
229
229
 
230
230
  def ==(other)
231
- return true if `self === other`
232
-
233
- unless Array === other
234
- return false unless other.respond_to? :to_ary
235
- return other == self
236
- end
231
+ %x{
232
+ var recursed = {};
237
233
 
238
- other = other.to_a
234
+ function _eqeq(array, other) {
235
+ var i, length, a, b;
239
236
 
240
- return false unless `self.length === other.length`
237
+ if (!other.$$is_array) {
238
+ if (#{Opal.respond_to? `other`, :to_ary}) {
239
+ return #{`other` == `array`};
240
+ } else {
241
+ return false;
242
+ }
243
+ }
241
244
 
242
- %x{
243
- for (var i = 0, length = self.length; i < length; i++) {
244
- var a = self[i],
245
- b = other[i];
245
+ other = #{other.to_a};
246
246
 
247
- if (a.$$is_array && b.$$is_array && (a === self)) {
248
- continue;
247
+ if (array.length !== other.length) {
248
+ return false;
249
249
  }
250
250
 
251
- if (!#{`a` == `b`}) {
252
- return false;
251
+ recursed[#{`array`.object_id}] = true;
252
+
253
+ for (i = 0, length = array.length; i < length; i++) {
254
+ a = array[i];
255
+ b = other[i];
256
+ if (a.$$is_array) {
257
+ if (b.$$is_array && b.length !== a.length) {
258
+ return false;
259
+ }
260
+ if (!recursed.hasOwnProperty(#{`a`.object_id})) {
261
+ if (!_eqeq(a, b)) {
262
+ return false;
263
+ }
264
+ }
265
+ } else {
266
+ if (!#{`a` == `b`}) {
267
+ return false;
268
+ }
269
+ }
253
270
  }
271
+
272
+ return true;
254
273
  }
255
- }
256
274
 
257
- true
275
+ return _eqeq(self, other);
276
+ }
258
277
  end
259
278
 
260
279
  def [](index, length = undefined)
@@ -615,7 +634,13 @@ class Array
615
634
  }
616
635
  }
617
636
 
618
- return self.length === original ? nil : object;
637
+ if (self.length === original) {
638
+ if (#{block_given?}) {
639
+ return #{yield};
640
+ }
641
+ return nil;
642
+ }
643
+ return object;
619
644
  }
620
645
  end
621
646
 
@@ -709,29 +734,48 @@ class Array
709
734
  end
710
735
 
711
736
  def eql?(other)
712
- return true if `self === other`
713
- return false unless Array === other
737
+ %x{
738
+ var recursed = {};
714
739
 
715
- other = other.to_a
740
+ function _eql(array, other) {
741
+ var i, length, a, b;
716
742
 
717
- return false unless `self.length === other.length`
743
+ if (!other.$$is_array) {
744
+ return false;
745
+ }
718
746
 
719
- %x{
720
- for (var i = 0, length = self.length; i < length; i++) {
721
- var a = self[i],
722
- b = other[i];
747
+ other = #{other.to_a};
723
748
 
724
- if (a.$$is_array && b.$$is_array && (a === self)) {
725
- continue;
749
+ if (array.length !== other.length) {
750
+ return false;
726
751
  }
727
752
 
728
- if (!#{`a`.eql? `b`}) {
729
- return false;
753
+ recursed[#{`array`.object_id}] = true;
754
+
755
+ for (i = 0, length = array.length; i < length; i++) {
756
+ a = array[i];
757
+ b = other[i];
758
+ if (a.$$is_array) {
759
+ if (b.$$is_array && b.length !== a.length) {
760
+ return false;
761
+ }
762
+ if (!recursed.hasOwnProperty(#{`a`.object_id})) {
763
+ if (!_eql(a, b)) {
764
+ return false;
765
+ }
766
+ }
767
+ } else {
768
+ if (!#{`a`.eql?(`b`)}) {
769
+ return false;
770
+ }
771
+ }
730
772
  }
773
+
774
+ return true;
731
775
  }
732
- }
733
776
 
734
- true
777
+ return _eql(self, other);
778
+ }
735
779
  end
736
780
 
737
781
  def fetch(index, defaults = undefined, &block)
@@ -866,30 +910,56 @@ class Array
866
910
 
867
911
  def flatten(level = undefined)
868
912
  %x{
869
- var result = [];
913
+ var object_id = #{`self`.object_id};
870
914
 
871
- for (var i = 0, length = self.length; i < length; i++) {
872
- var item = self[i];
915
+ function _flatten(array, level) {
916
+ var array = #{`array`.to_a},
917
+ result = [],
918
+ i, length,
919
+ item, ary;
873
920
 
874
- if (#{Opal.respond_to? `item`, :to_ary}) {
875
- item = #{`item`.to_ary};
921
+ for (i = 0, length = array.length; i < length; i++) {
922
+ item = array[i];
876
923
 
877
- if (level == null) {
878
- result.push.apply(result, #{`item`.flatten.to_a});
924
+ if (!#{Opal.respond_to? `item`, :to_ary}) {
925
+ result.push(item);
926
+ continue;
879
927
  }
880
- else if (level == 0) {
928
+
929
+ ary = #{`item`.to_ary};
930
+
931
+ if (ary === nil) {
881
932
  result.push(item);
933
+ continue;
882
934
  }
883
- else {
884
- result.push.apply(result, #{`item`.flatten(`level - 1`).to_a});
935
+
936
+ if (!ary.$$is_array) {
937
+ #{raise TypeError};
938
+ }
939
+
940
+ if (object_id === #{`ary`.object_id}) {
941
+ #{raise ArgumentError};
942
+ }
943
+
944
+ switch (level) {
945
+ case undefined:
946
+ result.push.apply(result, _flatten(ary));
947
+ break;
948
+ case 0:
949
+ result.push(ary);
950
+ break;
951
+ default:
952
+ result.push.apply(result, _flatten(ary, level - 1));
885
953
  }
886
954
  }
887
- else {
888
- result.push(item);
889
- }
955
+ return result;
890
956
  }
891
957
 
892
- return result;
958
+ if (level !== undefined) {
959
+ level = #{Opal.coerce_to(`level`, Integer, :to_int)};
960
+ }
961
+
962
+ return _flatten(self, level);
893
963
  }
894
964
  end
895
965
 
@@ -917,12 +987,15 @@ class Array
917
987
 
918
988
  def hash
919
989
  %x{
920
- var hash = ['A'], item, item_hash;
990
+ var hash = ['A'],
991
+ item;
921
992
  for (var i = 0, length = self.length; i < length; i++) {
922
993
  item = self[i];
923
- // Guard against recursion
924
- item_hash = self === item ? 'self' : item.$hash();
925
- hash.push(item_hash);
994
+ if (item.$$is_array && #{`self`.eql?(`item`)}) {
995
+ hash.push('self');
996
+ } else {
997
+ hash.push(item.$hash());
998
+ }
926
999
  }
927
1000
  return hash.join(',');
928
1001
  }
@@ -1022,6 +1095,7 @@ class Array
1022
1095
 
1023
1096
  %x{
1024
1097
  var result = [];
1098
+ var object_id = #{`self`.object_id};
1025
1099
 
1026
1100
  for (var i = 0, length = self.length; i < length; i++) {
1027
1101
  var item = self[i];
@@ -1039,6 +1113,10 @@ class Array
1039
1113
  if (#{Opal.respond_to? `item`, :to_ary}) {
1040
1114
  var tmp = #{`item`.to_ary};
1041
1115
 
1116
+ if (object_id === #{`tmp`.object_id}) {
1117
+ #{raise ArgumentError};
1118
+ }
1119
+
1042
1120
  if (tmp !== nil) {
1043
1121
  result.push(#{`tmp`.join(sep)});
1044
1122
 
@@ -1138,6 +1216,56 @@ class Array
1138
1216
  end
1139
1217
  end
1140
1218
 
1219
+ def product(*args, &block)
1220
+ %x{
1221
+ var result = #{block_given?} ? null : [],
1222
+ n = args.length + 1,
1223
+ counters = new Array(n),
1224
+ lengths = new Array(n),
1225
+ arrays = new Array(n),
1226
+ i, m, subarray, len, resultlen = 1;
1227
+
1228
+ arrays[0] = self;
1229
+ for (i = 1; i < n; i++) {
1230
+ arrays[i] = #{Opal.coerce_to(`args[i - 1]`, Array, :to_ary)};
1231
+ }
1232
+
1233
+ for (i = 0; i < n; i++) {
1234
+ len = arrays[i].length;
1235
+ if (len === 0) {
1236
+ return result || self;
1237
+ }
1238
+ resultlen *= len;
1239
+ if (resultlen > 2147483647) {
1240
+ #{raise RangeError, "too big to product"}
1241
+ }
1242
+ lengths[i] = len;
1243
+ counters[i] = 0;
1244
+ }
1245
+
1246
+ outer_loop: for (;;) {
1247
+ subarray = [];
1248
+ for (i = 0; i < n; i++) {
1249
+ subarray.push(arrays[i][counters[i]]);
1250
+ }
1251
+ if (result) {
1252
+ result.push(subarray);
1253
+ } else {
1254
+ #{yield `subarray`}
1255
+ }
1256
+ m = n - 1;
1257
+ counters[m]++;
1258
+ while (counters[m] === lengths[m]) {
1259
+ counters[m] = 0;
1260
+ if (--m < 0) break outer_loop;
1261
+ counters[m]++;
1262
+ }
1263
+ }
1264
+
1265
+ return result || self;
1266
+ }
1267
+ end
1268
+
1141
1269
  def push(*objects)
1142
1270
  %x{
1143
1271
  for (var i = 0, length = objects.length; i < length; i++) {