Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: opal/opal
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 978b4016ab05
Choose a base ref
...
head repository: opal/opal
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 062cdb5862e9
Choose a head ref
  • 12 commits
  • 12 files changed
  • 1 contributor

Commits on Nov 30, 2014

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    ecbdd93 View commit details
  2. 3
    Copy the full SHA
    3cb3dd5 View commit details
  3. Copy the full SHA
    8463d38 View commit details

Commits on Dec 1, 2014

  1. Run keyword arg specs

    adambeynon committed Dec 1, 2014
    Copy the full SHA
    bf67f94 View commit details
  2. Copy the full SHA
    732d323 View commit details

Commits on Dec 7, 2014

  1. Copy the full SHA
    fd20330 View commit details
  2. Copy the full SHA
    a932256 View commit details
  3. Copy the full SHA
    856dd5f View commit details
  4. Copy the full SHA
    ac06053 View commit details
  5. Add last spec to bugs

    adambeynon committed Dec 7, 2014
    Copy the full SHA
    057ba23 View commit details
  6. Add kwargs to changelog

    adambeynon committed Dec 7, 2014
    Copy the full SHA
    fb3f665 View commit details
  7. Merge branch 'kwargs'

    adambeynon committed Dec 7, 2014
    1
    Copy the full SHA
    062cdb5 View commit details
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
## edge (upcoming 0.7)

* Support keyword arguments in method calls and definitions.

* Fix `begin`/`rescue` blocks to evaluate to last expression.

* Add support for `RUBY_ENGINE/RUBY_PLATFORM != "opal"` pre-processor directives.
1 change: 1 addition & 0 deletions examples/rack/app/user.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
class User
def initialize(name)
puts "wow"
@name = name
end

202 changes: 155 additions & 47 deletions lib/opal/nodes/def.rb
Original file line number Diff line number Diff line change
@@ -8,36 +8,47 @@ class DefNode < ScopeNode

children :recvr, :mid, :args, :stmts

def opt_args
@opt_args ||= args[1..-1].select { |arg| arg.first == :optarg }
end

def rest_arg
@rest_arg ||= args[1..-1].find { |arg| arg.first == :restarg }
end

def keyword_args
@keyword_args ||= args[1..-1].select do |arg|
[:kwarg, :kwoptarg, :kwrestarg].include? arg.first
end
end

def block_arg
@block_arg ||= args[1..-1].find { |arg| arg.first == :blockarg }
end

def argc
return @argc if @argc

@argc = args.length - 1
@argc -= 1 if block_arg
@argc -= 1 if rest_arg
@argc -= keyword_args.size

@argc
end

def compile
jsid = mid_to_jsid mid.to_s
params = nil
scope_name = nil

# opt args if last arg is sexp
opt = args.pop if Sexp === args.last

argc = args.length - 1

# block name (&block)
if args.last.to_s.start_with? '&'
block_name = variable(args.pop.to_s[1..-1]).to_sym
argc -= 1
end

# splat args *splat
if args.last.to_s.start_with? '*'
uses_splat = true
if args.last == :*
argc -= 1
else
splat = args[-1].to_s[1..-1].to_sym
args[-1] = splat
argc -= 1
end
if block_arg
block_name = variable(block_arg[1]).to_sym
end

if compiler.arity_check?
arity_code = arity_check(args, opt, uses_splat, block_name, mid)
arity_code = arity_check(args, opt_args, rest_arg, keyword_args, block_name, mid)
end

in_scope do
@@ -49,32 +60,21 @@ def compile
scope.add_arg block_name
end

yielder = block_name || '$yield'
scope.block_name = yielder
scope.block_name = block_name || '$yield'

params = process(args)
stmt_code = stmt(compiler.returns(stmts))

add_temp 'self = this'

line "#{variable(splat)} = $slice.call(arguments, #{argc});" if splat

opt[1..-1].each do |o|
next if o[2][2] == :undefined
line "if (#{variable(o[1])} == null) {"
line ' ', expr(o)
line "}"
end if opt
compile_rest_arg
compile_opt_args
compile_keyword_args

# must do this after opt args incase opt arg uses yield
scope_name = scope.identity

if scope.uses_block?
add_temp "$iter = #{scope_name}.$$p"
add_temp "#{yielder} = $iter || nil"

line "#{scope_name}.$$p = null;"
end
compile_block_arg

unshift "\n#{current_indent}", scope.to_vars
line stmt_code
@@ -114,6 +114,97 @@ def compile
wrap '(', ", nil) && '#{mid}'" if expr?
end

def compile_block_arg
if scope.uses_block?
scope_name = scope.identity
yielder = scope.block_name

add_temp "$iter = #{scope_name}.$$p"
add_temp "#{yielder} = $iter || nil"

line "#{scope_name}.$$p = null;"
end
end

def compile_rest_arg
if rest_arg and rest_arg[1]
splat = variable(rest_arg[1].to_sym)
line "#{splat} = $slice.call(arguments, #{argc});"
end
end

def compile_opt_args
opt_args.each do |arg|
next if arg[2][2] == :undefined
line "if (#{variable(arg[1])} == null) {"
line " #{variable(arg[1])} = ", expr(arg[2])
line "}"
end
end

def compile_keyword_args
return if keyword_args.empty?
helper :hash2

if rest_arg
with_temp do |tmp|
rest_arg_name = variable(rest_arg[1].to_sym)
line "#{tmp} = #{rest_arg_name}[#{rest_arg_name}.length - 1];"
line "if (#{tmp} == null || !#{tmp}.$$is_hash) {"
line " $kwargs = $hash2([], {});"
line "} else {"
line " $kwargs = #{rest_arg_name}.pop();"
line "}"
end
elsif last_opt_arg = opt_args.last
opt_arg_name = variable(last_opt_arg[1])
line "if (#{opt_arg_name} == null) {"
line " $kwargs = $hash2([], {});"
line "}"
line "else if (#{opt_arg_name}.$$is_hash) {"
line " $kwargs = #{opt_arg_name};"
line " #{opt_arg_name} = ", expr(last_opt_arg[2]), ";"
line "}"
else
line "if ($kwargs == null) {"
line " $kwargs = $hash2([], {});"
line "}"
end

line "if (!$kwargs.$$is_hash) {"
line " throw Opal.ArgumentError.$new('expecting keyword args');"
line "}"

keyword_args.each do |kwarg|
case kwarg.first
when :kwoptarg
arg_name = kwarg[1]
var_name = variable(arg_name.to_s)
line "if ((#{var_name} = $kwargs.smap['#{arg_name}']) == null) {"
line " #{var_name} = ", expr(kwarg[2])
line "}"
when :kwarg
arg_name = kwarg[1]
var_name = variable(arg_name.to_s)
line "if ((#{var_name} = $kwargs.smap['#{arg_name}']) == null) {"
line " throw new Error('expecting keyword arg: #{arg_name}')"
line "}"
when :kwrestarg
arg_name = kwarg[1]
var_name = variable(arg_name.to_s)

kwarg_names = keyword_args.select do |kw|
[:kwoptarg, :kwarg].include? kw.first
end.map { |kw| "#{kw[1].to_s.inspect}: true" }

used_args = "{#{kwarg_names.join ','}}"
line "#{var_name} = Opal.kwrestargs($kwargs, #{used_args});"
else
raise "unknown kwarg type #{kwarg.first}"
end
end
end

# Simple helper to check whether this method should be defined through
# `Opal.defn()` runtime helper.
#
@@ -131,13 +222,18 @@ def uses_defn?(scope)
end

# Returns code used in debug mode to check arity of method call
def arity_check(args, opt, splat, block_name, mid)
def arity_check(args, opt, splat, kwargs, block_name, mid)
meth = mid.to_s.inspect

arity = args.size - 1
arity -= (opt.size - 1) if opt
arity -= (opt.size)

arity -= 1 if splat
arity = -arity - 1 if opt or splat

arity -= (kwargs.size)

arity -= 1 if block_name
arity = -arity - 1 if !opt.empty? or !kwargs.empty? or splat

# $arity will point to our received arguments count
aritycode = "var $arity = arguments.length;"
@@ -155,14 +251,26 @@ class ArgsNode < Base
handle :args

def compile
done_kwargs = false
children.each_with_index do |child, idx|
next if child.to_s == '*'

child = child.to_sym
push ', ' unless idx == 0
child = variable(child)
scope.add_arg child.to_sym
push child.to_s
next if :blockarg == child.first
next if :restarg == child.first and child[1].nil?

case child.first
when :kwarg, :kwoptarg, :kwrestarg
unless done_kwargs
done_kwargs = true
push ', ' unless idx == 0
scope.add_arg '$kwargs'
push '$kwargs'
end
else
child = child[1].to_sym
push ', ' unless idx == 0
child = variable(child)
scope.add_arg child.to_sym
push child.to_s
end
end
end
end
62 changes: 52 additions & 10 deletions lib/opal/parser.rb
Original file line number Diff line number Diff line change
@@ -327,38 +327,80 @@ def new_paren(open, expr, close)
end
end

def new_args(norm, opt, rest, block)
def new_args_tail(kwarg, kwrest, block)
[kwarg, kwrest, block]
end

def new_args(norm, opt, rest, tail)
res = s(:args)

if norm
norm.each do |arg|
scope.add_local arg
res << arg
res << s(:arg, arg)
end
end

if opt
opt[1..-1].each do |_opt|
res << _opt[1]
res << s(:optarg, _opt[1], _opt[2])
end
end

if rest
res << rest
rest_str = rest.to_s[1..-1]
scope.add_local rest_str.to_sym unless rest_str.empty?
restname = rest.to_s[1..-1]

if restname.empty?
res << s(:restarg)
else
res << s(:restarg, restname.to_sym)
scope.add_local restname.to_sym
end
end

if block
res << block
scope.add_local block.to_s[1..-1].to_sym
# kwarg
if tail and tail[0]
tail[0].each do |kwarg|
res << kwarg
end
end

res << opt if opt
# kwrestarg
if tail and tail[1]
res << tail[1]
end

# block
if tail and tail[2]
blockname = tail[2].to_s[1..-1].to_sym
scope.add_local blockname
res << s(:blockarg, blockname)
end

res
end

def new_kwarg(name)
scope.add_local name[1]
s(:kwarg, name[1])
end

def new_kwoptarg(name, val)
scope.add_local name[1]
s(:kwoptarg, name[1], val)
end

def new_kwrestarg(name = nil)
result = s(:kwrestarg)

if name
scope.add_local name[0].to_sym
result << name[0].to_sym
end

result
end

def new_block_args(norm, opt, rest, block)
res = s(:array)

Loading