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.
- checksums.yaml +4 -4
- data/.gitmodules +4 -0
- data/.travis.yml +7 -3
- data/.yardopts +6 -0
- data/CHANGELOG.md +28 -0
- data/Gemfile +1 -1
- data/README.md +3 -12
- data/Rakefile +4 -150
- data/bin/opal-mspec +1 -1
- data/docs/compiler_directives.md +127 -0
- data/examples/rack/.gitignore +1 -0
- data/examples/rack/app/user.rb +1 -0
- data/lib/mspec/opal/special_calls.rb +15 -2
- data/lib/opal/builder.rb +15 -8
- data/lib/opal/compiler.rb +75 -4
- data/lib/opal/erb.rb +22 -2
- data/lib/opal/fragment.rb +17 -5
- data/lib/opal/nodes/def.rb +174 -53
- data/lib/opal/nodes/if.rb +14 -0
- data/lib/opal/nodes/module.rb +0 -1
- data/lib/opal/nodes/rescue.rb +10 -2
- data/lib/opal/nodes/scope.rb +0 -17
- data/lib/opal/parser.rb +83 -19
- data/lib/opal/parser/grammar.rb +2511 -2414
- data/lib/opal/parser/grammar.y +71 -9
- data/lib/opal/parser/lexer.rb +44 -12
- data/lib/opal/parser/parser_scope.rb +3 -0
- data/lib/opal/parser/sexp.rb +7 -1
- data/lib/opal/paths.rb +5 -5
- data/lib/opal/sprockets/environment.rb +2 -10
- data/lib/opal/sprockets/path_reader.rb +1 -1
- data/lib/opal/sprockets/processor.rb +1 -0
- data/lib/opal/sprockets/server.rb +2 -0
- data/lib/opal/util.rb +7 -2
- data/lib/opal/version.rb +1 -1
- data/opal.gemspec +1 -0
- data/opal/README.md +1 -1
- data/opal/corelib/dir.rb +1 -1
- data/opal/corelib/enumerable.rb +3 -1
- data/opal/corelib/error.rb +3 -0
- data/opal/corelib/file.rb +2 -0
- data/opal/corelib/hash.rb +3 -0
- data/opal/corelib/io.rb +15 -1
- data/opal/corelib/kernel.rb +8 -0
- data/opal/corelib/module.rb +42 -17
- data/opal/corelib/runtime.js +223 -49
- data/opal/corelib/string.rb +1 -1
- data/opal/corelib/struct.rb +1 -7
- data/spec/README.md +8 -0
- data/spec/filters/bugs/language.rb +1 -0
- data/spec/filters/bugs/module.rb +4 -0
- data/spec/filters/unsupported/frozen.rb +2 -0
- data/spec/lib/compiler/pre_processed_conditionals_spec.rb +87 -0
- data/spec/lib/compiler_spec.rb +1 -67
- data/spec/lib/fixtures/file_with_directives.js +2 -0
- data/spec/lib/fixtures/required_file.js +1 -0
- data/spec/lib/parser/def_spec.rb +29 -16
- data/spec/lib/parser/variables_spec.rb +5 -5
- data/spec/lib/sprockets/path_reader_spec.rb +24 -8
- data/spec/lib/sprockets/server_spec.rb +10 -3
- data/spec/opal/core/date_spec.rb +14 -0
- data/spec/opal/core/language/versions/def_2_0_spec.rb +62 -0
- data/spec/opal/core/language_spec.rb +23 -0
- data/spec/opal/core/runtime/donate_spec.rb +53 -0
- data/spec/opal/stdlib/native/native_alias_spec.rb +19 -0
- data/spec/opal/stdlib/native/native_class_spec.rb +18 -0
- data/spec/opal/stdlib/native/native_module_spec.rb +13 -0
- data/spec/rubyspecs +2 -0
- data/stdlib/buffer.rb +1 -0
- data/stdlib/date.rb +18 -0
- data/stdlib/encoding.rb +3 -2
- data/stdlib/minitest.rb +780 -0
- data/stdlib/minitest/assertions.rb +662 -0
- data/stdlib/minitest/autorun.rb +12 -0
- data/stdlib/minitest/benchmark.rb +426 -0
- data/stdlib/minitest/expectations.rb +281 -0
- data/stdlib/minitest/hell.rb +11 -0
- data/stdlib/minitest/mock.rb +220 -0
- data/stdlib/minitest/parallel.rb +65 -0
- data/stdlib/minitest/pride.rb +4 -0
- data/stdlib/minitest/pride_plugin.rb +142 -0
- data/stdlib/minitest/spec.rb +310 -0
- data/stdlib/minitest/test.rb +293 -0
- data/stdlib/minitest/unit.rb +45 -0
- data/stdlib/native.rb +12 -3
- data/stdlib/nodejs/process.rb +16 -2
- data/stdlib/promise.rb +99 -0
- data/stdlib/test/unit.rb +10 -0
- data/stdlib/thread.rb +4 -0
- data/tasks/building.rake +58 -0
- data/tasks/documentation.rake +38 -0
- data/tasks/documenting.rake +37 -0
- data/tasks/testing.rake +102 -0
- 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
|
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
|
-
#
|
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
|
-
|
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
|
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
|
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
|
-
#
|
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
|
data/lib/opal/nodes/def.rb
CHANGED
@@ -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
|
23
|
-
block_name = variable(
|
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,
|
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
|
-
|
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
|
-
|
61
|
-
|
62
|
-
|
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
|
-
|
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
|
102
|
+
elsif uses_defn?(scope)
|
103
103
|
wrap "Opal.defn(self, '$#{mid}', ", ')'
|
104
|
-
elsif scope.
|
105
|
-
scope.methods << "$#{mid}"
|
104
|
+
elsif scope.class?
|
106
105
|
unshift "#{scope.proto}#{jsid} = "
|
107
|
-
elsif scope.
|
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
|
229
|
+
arity -= (opt.size)
|
230
|
+
|
126
231
|
arity -= 1 if splat
|
127
|
-
|
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
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
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
|