Skip to content

Commit

Permalink
Add YieldNode compiler nodes
Browse files Browse the repository at this point in the history
  • Loading branch information
adambeynon committed Oct 22, 2013
1 parent 7f8d2e2 commit 1727d0c
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 115 deletions.
1 change: 1 addition & 0 deletions lib/opal/nodes.rb
Expand Up @@ -4,3 +4,4 @@
require 'opal/nodes/constants'
require 'opal/nodes/logic'
require 'opal/nodes/definitions'
require 'opal/nodes/yield'
15 changes: 3 additions & 12 deletions lib/opal/nodes/base.rb
@@ -1,6 +1,9 @@
require 'opal/nodes/helpers'

module Opal
class Parser
class Node
include NodeHelpers

def self.children(*names)
names.each_with_index do |name, idx|
Expand Down Expand Up @@ -97,18 +100,6 @@ def expr_or_nil(sexp)
sexp ? expr(sexp) : "nil"
end

def reserved?(name)
Opal::Parser::RESERVED.include? name
end

def property(name)
reserved?(name) ? "['#{name}']" : ".#{name}"
end

def variable(name)
reserved?(name) ? "#{name}$" : name
end

def add_local(name)
scope.add_local name.to_sym
end
Expand Down
49 changes: 49 additions & 0 deletions lib/opal/nodes/definitions.rb
Expand Up @@ -56,5 +56,54 @@ def compile
end
end
end

class BeginNode < Node
children :body

def compile
if !stmt? and body.type == :block
push stmt(@parser.returns(body))
wrap '(function() {', '})()'
else
push @parser.process(body, @level)
end
end
end

class ParenNode < Node
children :body

def compile
if body.type == :block
body.children.each_with_index do |child, idx|
push ', ' unless idx == 0
push expr(child)
end

wrap '(', ')'
else
push @parser.process(body, @level)
wrap '(', ')' unless stmt?
end
end
end

class RescueModNode < Node
children :lhs, :rhs

def body
stmt? ? lhs : @parser.returns(lhs)
end

def rescue_val
stmt? ? rhs : @parser.returns(rhs)
end

def compile
push "try {", expr(body), " } catch ($err) { ", expr(rescue_val), " }"

wrap '(function() {', '})()' unless stmt?
end
end
end
end
18 changes: 18 additions & 0 deletions lib/opal/nodes/helpers.rb
@@ -0,0 +1,18 @@
module Opal
class Parser
module NodeHelpers

def property(name)
reserved?(name) ? "['#{name}']" : ".#{name}"
end

def reserved?(name)
Opal::Parser::RESERVED.include? name
end

def variable(name)
reserved?(name) ? "#{name}$" : name
end
end
end
end
78 changes: 78 additions & 0 deletions lib/opal/nodes/yield.rb
@@ -0,0 +1,78 @@
require 'opal/nodes/base'

module Opal
class Parser
class BaseYieldNode < Node
def compile_call(children, level)
scope.uses_block!

if yields_single_arg?(children)
push expr(children.first)
wrap "$opal.$yield1(#{block_name}, ", ')'
else
push @parser.process_arglist(children, level)

if uses_splat?(children)
wrap "$opal.$yieldX(#{block_name}, ", ')'
else
wrap "$opal.$yieldX(#{block_name}, [", '])'
end
end
end

def block_name
scope.block_name || '$yield'
end

def yields_single_arg?(children)
!uses_splat?(children) and children.size == 1
end

def uses_splat?(children)
children.any? { |child| child.type == :splat }
end
end

class YieldNode < BaseYieldNode
def compile
compile_call(children, @level)

if stmt?
wrap 'if (', ' === $breaker) return $breaker.$v'
else
with_temp do |tmp|
wrap "(((#{tmp} = ", ") === $breaker) ? $breaker.$v : #{tmp})"
end
end
end

end

# special opal yield assign, for `a = yield(arg1, arg2)` to assign
# to a temp value to make yield expr into stmt.
#
# level will always be stmt as its the reason for this to exist
#
# s(:yasgn, :a, s(:yield, arg1, arg2))
class YasgnNode < BaseYieldNode
children :var_name, :yield_args

def compile
compile_call(s(*yield_args[1..-1]), :stmt)
wrap "if ((#{var_name} = ", ") === $breaker) return $breaker.$v"
end
end

# Created by `#returns()` for when a yield statement should return
# it's value (its last in a block etc).
class ReturnableYieldNode < BaseYieldNode
def compile
compile_call children, @level

with_temp do |tmp|
wrap "return #{tmp} = ", ", #{tmp} === $breaker ? #{tmp} : #{tmp}"
end
end
end
end
end
111 changes: 8 additions & 103 deletions lib/opal/parser.rb
Expand Up @@ -87,10 +87,18 @@ def self.add_handler(klass, *types)
add_handler ReturnNode, :return
add_handler DefinedNode, :defined

# yield
add_handler YieldNode, :yield
add_handler YasgnNode, :yasgn
add_handler ReturnableYieldNode, :returnable_yield

# definitions
add_handler SingletonClassNode, :sclass
add_handler UndefNode, :undef
add_handler AliasNode, :alias
add_handler BeginNode, :begin
add_handler ParenNode, :paren
add_handler RescueModNode, :rescue_mod

# Final generated javascript for this parser
attr_reader :result
Expand Down Expand Up @@ -1391,61 +1399,6 @@ def js_falsy(sexp)
end
end

# s(:yield, arg1, arg2)
def process_yield(sexp, level)
call = handle_yield_call sexp, level

if level == :stmt
[f("if (", sexp), call, f(" === $breaker) return $breaker.$v")]
else
with_temp do |tmp|
[f("(((#{tmp} = ", sexp), call, f(") === $breaker) ? $breaker.$v : #{tmp})", sexp)]
end
end
end

# special opal yield assign, for `a = yield(arg1, arg2)` to assign
# to a temp value to make yield expr into stmt.
#
# level will always be stmt as its the reason for this to exist
#
# s(:yasgn, :a, s(:yield, arg1, arg2))
def process_yasgn(sexp, level)
call = handle_yield_call s(*sexp[1][1..-1]), :stmt

[f("if ((#{sexp[0]} = ", sexp), call, f(") === $breaker) return $breaker.$v", sexp)]
end

# Created by `#returns()` for when a yield statement should return
# it's value (its last in a block etc).
def process_returnable_yield(sexp, level)
call = handle_yield_call sexp, level

with_temp do |tmp|
[f("return #{tmp} = ", sexp), call,
f(", #{tmp} === $breaker ? #{tmp} : #{tmp}")]
end
end

def handle_yield_call(sexp, level)
@scope.uses_block!

splat = sexp.any? { |s| s.first == :splat }

if !splat and sexp.size == 1
return [f("$opal.$yield1(#{@scope.block_name || '$yield'}, "), process(sexp[0]), f(")")]
end

args = process_arglist sexp, level
y = @scope.block_name || '$yield'

if splat
[f("$opal.$yieldX(#{y}, ", sexp), args, f(")")]
else
[f("$opal.$yieldX(#{y}, [", sexp), args, f("])")]
end
end

# s(:case, expr, when1, when2, ..)
def process_case(exp, level)
pre, code = [], []
Expand Down Expand Up @@ -1750,53 +1703,5 @@ def process_resbody(exp, level)

[f("if (", exp), err, f("){#@space", exp), val, body, f("}", exp)]
end

def process_rescue_mod(sexp, level)
body, resc = sexp

unless level == :stmt
body = returns body
resc = returns resc
end

result = [f("try { "), process(body), f(" } catch($err) { "), process(resc), f(" }")]

unless level == :stmt
result.unshift f("(function() {")
result.push f("})()")
end

result
end

# FIXME: Hack.. grammar should remove top level begin.
def process_begin(exp, level)
if level != :stmt and exp[0][0] == :block
[f("(function() {"), process(returns(exp[0]), :stmt), f("})()")]
else
process exp[0], level
end
end

def process_paren(sexp, level)
if sexp[0][0] == :block
result = []

sexp[0][1..-1].each do |part|
result << f(', ') unless result.empty?
result << process(part, :expr)
end

[f('('), result, f(')')]
else
result = process sexp[0], level

unless level == :stmt
result = [f('('), result, f(')')]
end

result
end
end
end
end

0 comments on commit 1727d0c

Please sign in to comment.