opal 0.7.0.beta3 → 0.7.0.rc1

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/.gitmodules +4 -0
  3. data/.travis.yml +7 -3
  4. data/.yardopts +6 -0
  5. data/CHANGELOG.md +28 -0
  6. data/Gemfile +1 -1
  7. data/README.md +3 -12
  8. data/Rakefile +4 -150
  9. data/bin/opal-mspec +1 -1
  10. data/docs/compiler_directives.md +127 -0
  11. data/examples/rack/.gitignore +1 -0
  12. data/examples/rack/app/user.rb +1 -0
  13. data/lib/mspec/opal/special_calls.rb +15 -2
  14. data/lib/opal/builder.rb +15 -8
  15. data/lib/opal/compiler.rb +75 -4
  16. data/lib/opal/erb.rb +22 -2
  17. data/lib/opal/fragment.rb +17 -5
  18. data/lib/opal/nodes/def.rb +174 -53
  19. data/lib/opal/nodes/if.rb +14 -0
  20. data/lib/opal/nodes/module.rb +0 -1
  21. data/lib/opal/nodes/rescue.rb +10 -2
  22. data/lib/opal/nodes/scope.rb +0 -17
  23. data/lib/opal/parser.rb +83 -19
  24. data/lib/opal/parser/grammar.rb +2511 -2414
  25. data/lib/opal/parser/grammar.y +71 -9
  26. data/lib/opal/parser/lexer.rb +44 -12
  27. data/lib/opal/parser/parser_scope.rb +3 -0
  28. data/lib/opal/parser/sexp.rb +7 -1
  29. data/lib/opal/paths.rb +5 -5
  30. data/lib/opal/sprockets/environment.rb +2 -10
  31. data/lib/opal/sprockets/path_reader.rb +1 -1
  32. data/lib/opal/sprockets/processor.rb +1 -0
  33. data/lib/opal/sprockets/server.rb +2 -0
  34. data/lib/opal/util.rb +7 -2
  35. data/lib/opal/version.rb +1 -1
  36. data/opal.gemspec +1 -0
  37. data/opal/README.md +1 -1
  38. data/opal/corelib/dir.rb +1 -1
  39. data/opal/corelib/enumerable.rb +3 -1
  40. data/opal/corelib/error.rb +3 -0
  41. data/opal/corelib/file.rb +2 -0
  42. data/opal/corelib/hash.rb +3 -0
  43. data/opal/corelib/io.rb +15 -1
  44. data/opal/corelib/kernel.rb +8 -0
  45. data/opal/corelib/module.rb +42 -17
  46. data/opal/corelib/runtime.js +223 -49
  47. data/opal/corelib/string.rb +1 -1
  48. data/opal/corelib/struct.rb +1 -7
  49. data/spec/README.md +8 -0
  50. data/spec/filters/bugs/language.rb +1 -0
  51. data/spec/filters/bugs/module.rb +4 -0
  52. data/spec/filters/unsupported/frozen.rb +2 -0
  53. data/spec/lib/compiler/pre_processed_conditionals_spec.rb +87 -0
  54. data/spec/lib/compiler_spec.rb +1 -67
  55. data/spec/lib/fixtures/file_with_directives.js +2 -0
  56. data/spec/lib/fixtures/required_file.js +1 -0
  57. data/spec/lib/parser/def_spec.rb +29 -16
  58. data/spec/lib/parser/variables_spec.rb +5 -5
  59. data/spec/lib/sprockets/path_reader_spec.rb +24 -8
  60. data/spec/lib/sprockets/server_spec.rb +10 -3
  61. data/spec/opal/core/date_spec.rb +14 -0
  62. data/spec/opal/core/language/versions/def_2_0_spec.rb +62 -0
  63. data/spec/opal/core/language_spec.rb +23 -0
  64. data/spec/opal/core/runtime/donate_spec.rb +53 -0
  65. data/spec/opal/stdlib/native/native_alias_spec.rb +19 -0
  66. data/spec/opal/stdlib/native/native_class_spec.rb +18 -0
  67. data/spec/opal/stdlib/native/native_module_spec.rb +13 -0
  68. data/spec/rubyspecs +2 -0
  69. data/stdlib/buffer.rb +1 -0
  70. data/stdlib/date.rb +18 -0
  71. data/stdlib/encoding.rb +3 -2
  72. data/stdlib/minitest.rb +780 -0
  73. data/stdlib/minitest/assertions.rb +662 -0
  74. data/stdlib/minitest/autorun.rb +12 -0
  75. data/stdlib/minitest/benchmark.rb +426 -0
  76. data/stdlib/minitest/expectations.rb +281 -0
  77. data/stdlib/minitest/hell.rb +11 -0
  78. data/stdlib/minitest/mock.rb +220 -0
  79. data/stdlib/minitest/parallel.rb +65 -0
  80. data/stdlib/minitest/pride.rb +4 -0
  81. data/stdlib/minitest/pride_plugin.rb +142 -0
  82. data/stdlib/minitest/spec.rb +310 -0
  83. data/stdlib/minitest/test.rb +293 -0
  84. data/stdlib/minitest/unit.rb +45 -0
  85. data/stdlib/native.rb +12 -3
  86. data/stdlib/nodejs/process.rb +16 -2
  87. data/stdlib/promise.rb +99 -0
  88. data/stdlib/test/unit.rb +10 -0
  89. data/stdlib/thread.rb +4 -0
  90. data/tasks/building.rake +58 -0
  91. data/tasks/documentation.rake +38 -0
  92. data/tasks/documenting.rake +37 -0
  93. data/tasks/testing.rake +102 -0
  94. metadata +57 -2
data/lib/opal/builder.rb CHANGED
@@ -6,18 +6,21 @@ module Opal
6
6
  class Builder
7
7
  include BuilderProcessors
8
8
 
9
+ class MissingRequire < LoadError
10
+ end
11
+
9
12
  def initialize(options = nil)
10
13
  (options || {}).each_pair do |k,v|
11
14
  public_send("#{k}=", v)
12
15
  end
13
16
 
17
+ @stubs ||= []
18
+ @preload ||= []
19
+ @processors ||= DEFAULT_PROCESSORS
20
+ @path_reader ||= PathReader.new
21
+ @prerequired ||= []
14
22
  @compiler_options ||= {}
15
23
  @default_processor ||= RubyProcessor
16
- @processors ||= DEFAULT_PROCESSORS
17
- @stubs ||= []
18
- @preload ||= []
19
- @prerequired ||= []
20
- @path_reader ||= PathReader.new
21
24
 
22
25
  @processed = []
23
26
  end
@@ -38,6 +41,8 @@ module Opal
38
41
  requires.map { |r| process_require(r, options) }
39
42
  processed << asset
40
43
  self
44
+ rescue MissingRequire => error
45
+ raise error, "A file required by #{filename.inspect} wasn't found.\n#{error.message}"
41
46
  end
42
47
 
43
48
  def build_require(path, options = {})
@@ -92,7 +97,7 @@ module Opal
92
97
 
93
98
  def read(path)
94
99
  path_reader.read(path) or
95
- raise ArgumentError, "can't find file: #{path.inspect} in #{path_reader.paths.inspect}"
100
+ raise MissingRequire, "can't find file: #{path.inspect} in #{path_reader.paths.inspect}"
96
101
  end
97
102
 
98
103
  def process_require(filename, options)
@@ -112,12 +117,14 @@ module Opal
112
117
 
113
118
  path = path_reader.expand(filename).to_s unless stub?(filename)
114
119
  asset = processor_for(source, filename, path, options.merge(requirable: true))
115
- process_requires(asset.requires+tree_requires(asset, path), options)
120
+ process_requires(filename, asset.requires+tree_requires(asset, path), options)
116
121
  processed << asset
117
122
  end
118
123
 
119
- def process_requires(requires, options)
124
+ def process_requires(source_filename, requires, options)
120
125
  requires.map { |r| process_require(r, options) }
126
+ rescue MissingRequire => error
127
+ raise error, "A file required by #{filename.inspect} wasn't found.\n#{error.message}"
121
128
  end
122
129
 
123
130
  def already_processed
data/lib/opal/compiler.rb CHANGED
@@ -4,10 +4,42 @@ require 'opal/fragment'
4
4
  require 'opal/nodes'
5
5
 
6
6
  module Opal
7
+ # Compile a string of ruby code into javascript.
8
+ #
9
+ # @example
10
+ #
11
+ # Opal.compile "ruby_code"
12
+ # # => "string of javascript code"
13
+ #
14
+ # @see Opal::Compiler.new for compiler options
15
+ #
16
+ # @param source [String] ruby source
17
+ # @param options [Hash] compiler options
18
+ # @return [String] javascript code
19
+ #
7
20
  def self.compile(source, options = {})
8
21
  Compiler.new(source, options).compile
9
22
  end
10
23
 
24
+ # {Opal::Compiler} is the main class used to compile ruby to javascript code.
25
+ # This class uses {Opal::Parser} to gather the sexp syntax tree for the ruby
26
+ # code, and then uses {Opal::Node} to step through the sexp to generate valid
27
+ # javascript.
28
+ #
29
+ # @example
30
+ # Opal::Compiler.new("ruby code").compile
31
+ # # => "javascript code"
32
+ #
33
+ # @example Accessing result
34
+ # compiler = Opal::Compiler.new("ruby_code")
35
+ # compiler.compile
36
+ # compiler.result # => "javascript code"
37
+ #
38
+ # @example Source Maps
39
+ # compiler = Opal::Compiler.new("")
40
+ # compiler.compile
41
+ # compiler.source_map # => #<SourceMap:>
42
+ #
11
43
  class Compiler
12
44
  # Generated code gets indented with two spaces on each scope
13
45
  INDENT = ' '
@@ -27,28 +59,53 @@ module Opal
27
59
  end
28
60
  end
29
61
 
30
- # used for __FILE__ directives as well as finding relative require()
62
+ # @!method file
63
+ #
64
+ # The filename to use for compiling this code. Used for __FILE__ directives
65
+ # as well as finding relative require()
66
+ #
67
+ # @return [String]
31
68
  compiler_option :file, '(file)'
32
69
 
70
+ # @!method method_missing?
71
+ #
33
72
  # adds method stubs for all used methods in file
73
+ #
74
+ # @return [Boolean]
34
75
  compiler_option :method_missing, true, :as => :method_missing?
35
76
 
77
+ # @!method arity_check?
78
+ #
36
79
  # adds an arity check to every method definition
80
+ #
81
+ # @return [Boolean]
37
82
  compiler_option :arity_check, false, :as => :arity_check?
38
83
 
84
+ # @!method irb?
85
+ #
39
86
  # compile top level local vars with support for irb style vars
40
87
  compiler_option :irb, false, :as => :irb?
41
88
 
89
+ # @!method dynamic_require_severity
90
+ #
42
91
  # how to handle dynamic requires (:error, :warning, :ignore)
43
92
  compiler_option :dynamic_require_severity, :error, :valid_values => [:error, :warning, :ignore]
44
93
 
94
+ # @!method requirable?
95
+ #
45
96
  # Prepare the code for future requires
46
97
  compiler_option :requirable, false, :as => :requirable?
47
98
 
99
+ # @!method inline_operators?
100
+ #
48
101
  # are operators compiled inline
49
102
  compiler_option :inline_operators, false, :as => :inline_operators?
50
103
 
51
- attr_reader :result, :fragments
104
+ # @return [String] The compiled ruby code
105
+ attr_reader :result
106
+
107
+ # @return [Array] all [Opal::Fragment] used to produce result
108
+ attr_reader :fragments
52
109
 
53
110
  # Current scope
54
111
  attr_accessor :scope
@@ -67,6 +124,8 @@ module Opal
67
124
  end
68
125
 
69
126
  # Compile some ruby code to a string.
127
+ #
128
+ # @return [String] javascript code
70
129
  def compile
71
130
  @parser = Parser.new
72
131
 
@@ -76,13 +135,25 @@ module Opal
76
135
  @fragments = process(@sexp).flatten
77
136
 
78
137
  @result = @fragments.map(&:code).join('')
138
+ rescue => error
139
+ message = "An error occurred while compiling: #{self.file}\n#{error.message}"
140
+ raise error.class, message
79
141
  end
80
142
 
143
+ # Returns a source map that can be used in the browser to map back to
144
+ # original ruby code.
145
+ #
146
+ # @param source_file [String] optional source_file to reference ruby source
147
+ # @return [Opal::SourceMap]
81
148
  def source_map(source_file = nil)
82
149
  Opal::SourceMap.new(@fragments, source_file || self.file)
83
150
  end
84
151
 
85
- # Any helpers required by this file
152
+ # Any helpers required by this file. Used by {Opal::Nodes::Top} to reference
153
+ # runtime helpers that are needed. These are used to minify resulting
154
+ # javascript by keeping a reference to helpers used.
155
+ #
156
+ # @return [Set<Symbol>]
86
157
  def helpers
87
158
  @helpers ||= Set.new([:breaker, :slice])
88
159
  end
@@ -226,7 +297,7 @@ module Opal
226
297
  #
227
298
  # Sexps that need to be returned are passed to this method, and the
228
299
  # alterned/new sexps are returned and should be used instead. Most
229
- # sexps can just be added into a s(:return) sexp, so that is the
300
+ # sexps can just be added into a `s(:return) sexp`, so that is the
230
301
  # default action if no special case is required.
231
302
  def returns(sexp)
232
303
  return returns s(:nil) unless sexp
data/lib/opal/erb.rb CHANGED
@@ -2,11 +2,33 @@ require 'opal/compiler'
2
2
 
3
3
  module Opal
4
4
  module ERB
5
+ # Compile ERB code into javascript.
6
+ #
7
+ # [Opal::ERB] can be used to compile [ERB] templates into javascript code.
8
+ # This module uses the [Opal::Compiler] internally.
9
+ #
10
+ # Compiled templates, when run in a javascript environment, will appear
11
+ # under the `Template` namespace, and can be accessed as:
12
+ #
13
+ # Template['template_name'] # => template instance
14
+ #
15
+ # @example
16
+ #
17
+ # source = "<div><%= @content %></div>"
18
+ #
19
+ # Opal::ERB.compile source, "my_template.erb"
20
+ #
21
+ # @param source [String] erb content
22
+ # @param file_name [String] filename for reference in template
23
+ # @return [String] javascript code
24
+ #
5
25
  def self.compile(source, file_name = '(erb)')
6
26
  Compiler.new(source, file_name).compile
7
27
  end
8
28
 
9
29
  class Compiler
30
+ BLOCK_EXPR = /\s+(do|\{)(\s*\|[^|]*\|)?\s*\Z/
31
+
10
32
  def initialize(source, file_name = '(erb)')
11
33
  @source, @file_name, @result = source, file_name, source
12
34
  end
@@ -31,8 +53,6 @@ module Opal
31
53
  result.gsub '"', '\\"'
32
54
  end
33
55
 
34
- BLOCK_EXPR = /\s+(do|\{)(\s*\|[^|]*\|)?\s*\Z/
35
-
36
56
  def require_erb(result)
37
57
  'require "erb";'+result
38
58
  end
data/lib/opal/fragment.rb CHANGED
@@ -4,16 +4,27 @@ module Opal
4
4
  # it was generated. Using this sexp, when writing fragments in order, a
5
5
  # mapping can be created of the original location => target location,
6
6
  # aka, source-maps!
7
+ #
8
+ # These are generated by nodes, so will not have to create directly.
7
9
  class Fragment
8
10
  # String of javascript this fragment holds
11
+ # @return [String]
9
12
  attr_reader :code
10
13
 
14
+ # Create fragment with javascript code and optional original [Opal::Sexp].
15
+ #
16
+ # @param code [String] javascript code
17
+ # @param sexp [Opal::Sexp] sexp used for creating fragment
11
18
  def initialize(code, sexp = nil)
12
19
  @code = code.to_s
13
20
  @sexp = sexp
14
21
  end
15
22
 
16
- # In debug mode we may wish to include the original line as a comment
23
+ # In debug mode we may wish to include the original line and comment in
24
+ # a javascript comment.
25
+ #
26
+ # @deprecated
27
+ #
17
28
  def to_code
18
29
  if @sexp
19
30
  "/*:#{@sexp.line}:#{@sexp.column}*/#{@code}"
@@ -22,18 +33,19 @@ module Opal
22
33
  end
23
34
  end
24
35
 
25
- # debug:
26
- # alias code to_code
27
-
28
- # inspect the contents of this fragment, f("fooo")
36
+ # Inspect the contents of this fragment, f("fooo")
29
37
  def inspect
30
38
  "f(#{@code.inspect})"
31
39
  end
32
40
 
41
+ # Original line this fragment was created from
42
+ # @return [Integer, nil]
33
43
  def line
34
44
  @sexp.line if @sexp
35
45
  end
36
46
 
47
+ # Original column this fragment was created from
48
+ # @return [Integer, nil]
37
49
  def column
38
50
  @sexp.column if @sexp
39
51
  end
@@ -8,36 +8,47 @@ module Opal
8
8
 
9
9
  children :recvr, :mid, :args, :stmts
10
10
 
11
+ def opt_args
12
+ @opt_args ||= args[1..-1].select { |arg| arg.first == :optarg }
13
+ end
14
+
15
+ def rest_arg
16
+ @rest_arg ||= args[1..-1].find { |arg| arg.first == :restarg }
17
+ end
18
+
19
+ def keyword_args
20
+ @keyword_args ||= args[1..-1].select do |arg|
21
+ [:kwarg, :kwoptarg, :kwrestarg].include? arg.first
22
+ end
23
+ end
24
+
25
+ def block_arg
26
+ @block_arg ||= args[1..-1].find { |arg| arg.first == :blockarg }
27
+ end
28
+
29
+ def argc
30
+ return @argc if @argc
31
+
32
+ @argc = args.length - 1
33
+ @argc -= 1 if block_arg
34
+ @argc -= 1 if rest_arg
35
+ @argc -= keyword_args.size
36
+
37
+ @argc
38
+ end
39
+
11
40
  def compile
12
41
  jsid = mid_to_jsid mid.to_s
13
42
  params = nil
14
43
  scope_name = nil
15
44
 
16
- # opt args if last arg is sexp
17
- opt = args.pop if Sexp === args.last
18
-
19
- argc = args.length - 1
20
-
21
45
  # block name (&block)
22
- if args.last.to_s.start_with? '&'
23
- block_name = variable(args.pop.to_s[1..-1]).to_sym
24
- argc -= 1
25
- end
26
-
27
- # splat args *splat
28
- if args.last.to_s.start_with? '*'
29
- uses_splat = true
30
- if args.last == :*
31
- argc -= 1
32
- else
33
- splat = args[-1].to_s[1..-1].to_sym
34
- args[-1] = splat
35
- argc -= 1
36
- end
46
+ if block_arg
47
+ block_name = variable(block_arg[1]).to_sym
37
48
  end
38
49
 
39
50
  if compiler.arity_check?
40
- arity_code = arity_check(args, opt, uses_splat, block_name, mid)
51
+ arity_code = arity_check(args, opt_args, rest_arg, keyword_args, block_name, mid)
41
52
  end
42
53
 
43
54
  in_scope do
@@ -49,32 +60,21 @@ module Opal
49
60
  scope.add_arg block_name
50
61
  end
51
62
 
52
- yielder = block_name || '$yield'
53
- scope.block_name = yielder
63
+ scope.block_name = block_name || '$yield'
54
64
 
55
65
  params = process(args)
56
66
  stmt_code = stmt(compiler.returns(stmts))
57
67
 
58
68
  add_temp 'self = this'
59
69
 
60
- line "#{variable(splat)} = $slice.call(arguments, #{argc});" if splat
61
-
62
- opt[1..-1].each do |o|
63
- next if o[2][2] == :undefined
64
- line "if (#{variable(o[1])} == null) {"
65
- line ' ', expr(o)
66
- line "}"
67
- end if opt
70
+ compile_rest_arg
71
+ compile_opt_args
72
+ compile_keyword_args
68
73
 
69
74
  # must do this after opt args incase opt arg uses yield
70
75
  scope_name = scope.identity
71
76
 
72
- if scope.uses_block?
73
- add_temp "$iter = #{scope_name}.$$p"
74
- add_temp "#{yielder} = $iter || nil"
75
-
76
- line "#{scope_name}.$$p = null;"
77
- end
77
+ compile_block_arg
78
78
 
79
79
  unshift "\n#{current_indent}", scope.to_vars
80
80
  line stmt_code
@@ -99,14 +99,11 @@ module Opal
99
99
  if recvr
100
100
  unshift 'Opal.defs(', recv(recvr), ", '$#{mid}', "
101
101
  push ')'
102
- elsif scope.class? and %w(Object BasicObject).include?(scope.name)
102
+ elsif uses_defn?(scope)
103
103
  wrap "Opal.defn(self, '$#{mid}', ", ')'
104
- elsif scope.class_scope?
105
- scope.methods << "$#{mid}"
104
+ elsif scope.class?
106
105
  unshift "#{scope.proto}#{jsid} = "
107
- elsif scope.iter?
108
- wrap "Opal.defn(self, '$#{mid}', ", ')'
109
- elsif scope.type == :sclass
106
+ elsif scope.sclass?
110
107
  unshift "self.$$proto#{jsid} = "
111
108
  elsif scope.top?
112
109
  unshift "Opal.Object.$$proto#{jsid} = "
@@ -117,14 +114,126 @@ module Opal
117
114
  wrap '(', ", nil) && '#{mid}'" if expr?
118
115
  end
119
116
 
117
+ def compile_block_arg
118
+ if scope.uses_block?
119
+ scope_name = scope.identity
120
+ yielder = scope.block_name
121
+
122
+ add_temp "$iter = #{scope_name}.$$p"
123
+ add_temp "#{yielder} = $iter || nil"
124
+
125
+ line "#{scope_name}.$$p = null;"
126
+ end
127
+ end
128
+
129
+ def compile_rest_arg
130
+ if rest_arg and rest_arg[1]
131
+ splat = variable(rest_arg[1].to_sym)
132
+ line "#{splat} = $slice.call(arguments, #{argc});"
133
+ end
134
+ end
135
+
136
+ def compile_opt_args
137
+ opt_args.each do |arg|
138
+ next if arg[2][2] == :undefined
139
+ line "if (#{variable(arg[1])} == null) {"
140
+ line " #{variable(arg[1])} = ", expr(arg[2])
141
+ line "}"
142
+ end
143
+ end
144
+
145
+ def compile_keyword_args
146
+ return if keyword_args.empty?
147
+ helper :hash2
148
+
149
+ if rest_arg
150
+ with_temp do |tmp|
151
+ rest_arg_name = variable(rest_arg[1].to_sym)
152
+ line "#{tmp} = #{rest_arg_name}[#{rest_arg_name}.length - 1];"
153
+ line "if (#{tmp} == null || !#{tmp}.$$is_hash) {"
154
+ line " $kwargs = $hash2([], {});"
155
+ line "} else {"
156
+ line " $kwargs = #{rest_arg_name}.pop();"
157
+ line "}"
158
+ end
159
+ elsif last_opt_arg = opt_args.last
160
+ opt_arg_name = variable(last_opt_arg[1])
161
+ line "if (#{opt_arg_name} == null) {"
162
+ line " $kwargs = $hash2([], {});"
163
+ line "}"
164
+ line "else if (#{opt_arg_name}.$$is_hash) {"
165
+ line " $kwargs = #{opt_arg_name};"
166
+ line " #{opt_arg_name} = ", expr(last_opt_arg[2]), ";"
167
+ line "}"
168
+ else
169
+ line "if ($kwargs == null) {"
170
+ line " $kwargs = $hash2([], {});"
171
+ line "}"
172
+ end
173
+
174
+ line "if (!$kwargs.$$is_hash) {"
175
+ line " throw Opal.ArgumentError.$new('expecting keyword args');"
176
+ line "}"
177
+
178
+ keyword_args.each do |kwarg|
179
+ case kwarg.first
180
+ when :kwoptarg
181
+ arg_name = kwarg[1]
182
+ var_name = variable(arg_name.to_s)
183
+ line "if ((#{var_name} = $kwargs.smap['#{arg_name}']) == null) {"
184
+ line " #{var_name} = ", expr(kwarg[2])
185
+ line "}"
186
+ when :kwarg
187
+ arg_name = kwarg[1]
188
+ var_name = variable(arg_name.to_s)
189
+ line "if ((#{var_name} = $kwargs.smap['#{arg_name}']) == null) {"
190
+ line " throw new Error('expecting keyword arg: #{arg_name}')"
191
+ line "}"
192
+ when :kwrestarg
193
+ arg_name = kwarg[1]
194
+ var_name = variable(arg_name.to_s)
195
+
196
+ kwarg_names = keyword_args.select do |kw|
197
+ [:kwoptarg, :kwarg].include? kw.first
198
+ end.map { |kw| "#{kw[1].to_s.inspect}: true" }
199
+
200
+ used_args = "{#{kwarg_names.join ','}}"
201
+ line "#{var_name} = Opal.kwrestargs($kwargs, #{used_args});"
202
+ else
203
+ raise "unknown kwarg type #{kwarg.first}"
204
+ end
205
+ end
206
+ end
207
+
208
+ # Simple helper to check whether this method should be defined through
209
+ # `Opal.defn()` runtime helper.
210
+ #
211
+ # @param [Opal::Scope] scope
212
+ # @returns [Boolean]
213
+ #
214
+ def uses_defn?(scope)
215
+ if scope.iter? or scope.module?
216
+ true
217
+ elsif scope.class? and %w(Object BasicObject).include?(scope.name)
218
+ true
219
+ else
220
+ false
221
+ end
222
+ end
223
+
120
224
  # Returns code used in debug mode to check arity of method call
121
- def arity_check(args, opt, splat, block_name, mid)
225
+ def arity_check(args, opt, splat, kwargs, block_name, mid)
122
226
  meth = mid.to_s.inspect
123
227
 
124
228
  arity = args.size - 1
125
- arity -= (opt.size - 1) if opt
229
+ arity -= (opt.size)
230
+
126
231
  arity -= 1 if splat
127
- arity = -arity - 1 if opt or splat
232
+
233
+ arity -= (kwargs.size)
234
+
235
+ arity -= 1 if block_name
236
+ arity = -arity - 1 if !opt.empty? or !kwargs.empty? or splat
128
237
 
129
238
  # $arity will point to our received arguments count
130
239
  aritycode = "var $arity = arguments.length;"
@@ -142,14 +251,26 @@ module Opal
142
251
  handle :args
143
252
 
144
253
  def compile
254
+ done_kwargs = false
145
255
  children.each_with_index do |child, idx|
146
- next if child.to_s == '*'
147
-
148
- child = child.to_sym
149
- push ', ' unless idx == 0
150
- child = variable(child)
151
- scope.add_arg child.to_sym
152
- push child.to_s
256
+ next if :blockarg == child.first
257
+ next if :restarg == child.first and child[1].nil?
258
+
259
+ case child.first
260
+ when :kwarg, :kwoptarg, :kwrestarg
261
+ unless done_kwargs
262
+ done_kwargs = true
263
+ push ', ' unless idx == 0
264
+ scope.add_arg '$kwargs'
265
+ push '$kwargs'
266
+ end
267
+ else
268
+ child = child[1].to_sym
269
+ push ', ' unless idx == 0
270
+ child = variable(child)
271
+ scope.add_arg child.to_sym
272
+ push child.to_s
273
+ end
153
274
  end
154
275
  end
155
276
  end