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.
- 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
|