opal 0.7.0.beta3 → 0.7.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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