Skip to content

Commit

Permalink
Convert some more process_x parser statements into nodes
Browse files Browse the repository at this point in the history
  • Loading branch information
adambeynon committed Oct 22, 2013
1 parent 5c6d5d4 commit 7f8d2e2
Show file tree
Hide file tree
Showing 3 changed files with 186 additions and 128 deletions.
21 changes: 21 additions & 0 deletions lib/opal/nodes/definitions.rb
Expand Up @@ -35,5 +35,26 @@ def compile
push "delete #{scope.proto}#{@parser.mid_to_jsid mid[1].to_s}"
end
end

class AliasNode < Node
children :new_name, :old_name

def new_mid
@parser.mid_to_jsid new_name[1].to_s
end

def old_mid
@parser.mid_to_jsid old_name[1].to_s
end

def compile
if scope.class? or scope.module?
scope.methods << "$#{new_name[1]}"
push "$opal.defn(self, '$#{new_name[1]}', #{scope.proto}#{old_mid})"
else
push "self._proto#{new_mid} = self._proto#{old_mid}"
end
end
end
end
end
161 changes: 161 additions & 0 deletions lib/opal/nodes/logic.rb
Expand Up @@ -104,5 +104,166 @@ def compile
end
end
end

class AndNode < Node
children :lhs, :rhs

def compile
truthy_opt = nil

with_temp do |tmp|
if truthy_opt = @parser.js_truthy_optimize(lhs)
push "((#{tmp} = ", truthy_opt
push ") ? "
push expr(rhs)
push " : #{tmp})"
else
push "(#{tmp} = "
push expr(lhs)
push ", #{tmp} !== false && #{tmp} !== nil ?"
push expr(rhs)
push " : #{tmp})"
end
end
end
end

class ReturnNode < Node
children :value

def return_val
expr_or_nil value
end

def return_in_iter?
if scope.iter? and parent_def = scope.find_parent_def
parent_def
end
end

def return_expr_in_def?
return scope if expr? and scope.def?
end

def scope_to_catch_return
return_in_iter? or return_expr_in_def?
end

def compile
if def_scope = scope_to_catch_return
def_scope.catch_return = true
push '$opal.$return(', return_val, ')'
elsif stmt?
push 'return ', return_val
else
raise SyntaxError, "void value expression: cannot return as an expression"
end
end
end

class DefinedNode < Node
children :value

def compile
type = value.type

case type
when :self, :nil, :false, :true
push type.to_s.inspect
when :lasgn, :iasgn, :gasgn, :cvdecl, :masgn, :op_asgn_or, :op_asgn_and
push "'assignment'"
when :paren, :not
push expr(s(:defined, value[1]))
when :lvar
push "'local-variable'"
else
if respond_to? "compile_#{type}"
__send__ "compile_#{type}"
else
push "'expression'"
end
end
end

def compile_call
mid = @parser.mid_to_jsid value[2].to_s
recv = value[1] ? expr(value[1]) : 'self'

push '(', recv, "#{mid} || ", recv
push "['$respond_to_missing?']('#{value[2].to_s}') ? 'method' : nil)"
end

def compile_ivar
# FIXME: this check should be positive for ivars initialized as nil too.
# Since currently all known ivars are inialized to nil in the constructor
# we can't tell if it was the user that put nil and made the ivar #defined?
# or not.
with_temp do |tmp|
name = value[1].to_s[1..-1]

push "((#{tmp} = self['#{name}'], #{tmp} != null && #{tmp} !== nil) ? "
push "'instance-variable' : nil)"
end
end

def compile_super
push @parser.process_super(value, @level, :skip_call)
wrap '((', ') != null ? "super" : nil)'
end

def compile_yield
push @parser.js_block_given(@sexp, @level)
wrap '((', ') != null ? "yield" : nil)'
end

def compile_xstr
push expr(value)
wrap '(typeof(', ') !== "undefined")'
end
alias compile_dxstr compile_xstr

def compile_const
push "($scope.#{value[1]} != null)"
end

def compile_colon2
# TODO: avoid try/catch, probably a #process_colon2 alternative that
# does not raise errors is needed
push "(function(){ try { return (("
push expr(value)
push ") != null ? 'constant' : nil); } catch (err) { if (err._klass"
push " === Opal.NameError) { return nil; } else { throw(err); }}; })()"
end

def compile_colon3
push "($opal.Object._scope.#{value[1]} == null ? nil : 'constant')"
end

def compile_cvar
push "($opal.cvars['#{value[1]}'] != null ? 'class variable' : nil)"
end

def compile_gvar
name = value[1].to_s[1..-1]

if %w[~ !].include? name
push "'global-variable'"
elsif %w[` ' + &].include? name
with_temp do |tmp|
push "((#{tmp} = $gvars['~'], #{tmp} != null && #{tmp} !== nil) ? "
push "'global-variable' : nil)"
end
else
push "($gvars[#{name.inspect}] != null ? 'global-variable' : nil)"
end
end

def compile_nth_ref
with_temp do |tmp|
push "((#{tmp} = $gvars['~'], #{tmp} != null && #{tmp} != nil) ? "
push "'global-variable' : nil)"
end
end
end
end
end
132 changes: 4 additions & 128 deletions lib/opal/parser.rb
Expand Up @@ -83,10 +83,14 @@ def self.add_handler(klass, *types)
add_handler NotNode, :not
add_handler SplatNode, :splat
add_handler OrNode, :or
add_handler AndNode, :and
add_handler ReturnNode, :return
add_handler DefinedNode, :defined

# definitions
add_handler SingletonClassNode, :sclass
add_handler UndefNode, :undef
add_handler AliasNode, :alias

# Final generated javascript for this parser
attr_reader :result
Expand Down Expand Up @@ -618,77 +622,6 @@ def handle_block_given(sexp, reverse = false)
f((reverse ? "#{ name } === nil" : "#{ name } !== nil"), sexp)
end

# defined?(x) => various
def process_defined(sexp, level)
part = sexp[0]
case part[0]
when :self
f("'self'", sexp)
when :nil
f("'nil'", sexp)
when :true
f("'true'", sexp)
when :false
f("'false'", sexp)
when :call
mid = mid_to_jsid part[2].to_s
recv = part[1] ? process(part[1]) : f('self', sexp)
[f("(", sexp), recv, f("#{mid} || ", sexp), recv, f("['$respond_to_missing?'](#{part[2].to_s.inspect}) ? 'method' : nil)", sexp)]
when :xstr, :dxstr
[f("(typeof(", sexp), process(part), f(") !== 'undefined')", sexp)]
when :const
f("($scope.#{part[1].to_s} != null)", sexp)
when :cvar
f("($opal.cvars[#{part[1].to_s.inspect}] != null ? 'class variable' : nil)", sexp)
when :colon2
# TODO: avoid try/catch, probably a #process_colon2 alternative that does not raise errors is needed
[f('(function(){try { return ((', sexp), process(part, level), f(") != null ? 'constant' : nil); } "+
"catch(err) { if(err._klass === Opal.NameError) { return nil; } else { throw(err); } }; })()" , sexp)]
when :colon3
f("($opal.Object._scope.#{sexp[0][1]} == null ? nil : 'constant')", sexp)
when :ivar
# FIXME: this check should be positive for ivars initialized as nil too.
# Since currently all known ivars are inialized to nil in the constructor
# we can't tell if it was the user that put nil and made the ivar #defined?
# or not.
ivar_name = part[1].to_s[1..-1]
with_temp do |t|
f("((#{t} = self[#{ivar_name.inspect}], #{t} != null && #{t} !== nil) ? 'instance-variable' : nil)", sexp)
end
when :lvar
f("'local-variable'", sexp)
when :gvar
gvar_name = part[1].to_s[1..-1]

if %w[~ !].include? gvar_name
f("'global-variable'", sexp)
elsif %w[` ' + &].include? gvar_name
with_temp do |t|
f("((#{t} = $gvars['~'], #{t} != null && #{t} !== nil) ? 'global-variable' : nil)", sexp)
end
else
f("($gvars[#{gvar_name.inspect}] != null ? 'global-variable' : nil)", sexp)
end
when :yield
[f('( (', sexp), js_block_given(sexp, level), f(") != null ? 'yield' : nil)", sexp)]
when :super
[f('( (', sexp), process_super(part, level, :skip_call), f(") != null ? 'super' : nil)", sexp)]
when :lasgn, :iasgn, :gasgn, :cvdecl, :masgn, :op_asgn_or, :op_asgn_and
f("'assignment'", sexp)
when :paren, :not
process_defined([part[1]], level)
when :and, :or, :str, :dstr, :dregx, :int, :float, :dot2, :regexp, :array, :hash, :sym
f("'expression'", sexp)
when :nth_ref
gvar_name = "$#{part[1].to_s[1..-1]}"
with_temp do |t|
f("((#{t} = $gvars['~'], #{t} != null && #{t} !== nil) ? 'global-variable' : nil)", sexp)
end
else
raise "bad defined? part: #{part[0]} (full sexp: #{part.inspect})"
end
end

# A block pass '&foo' syntax
# s(:block_pass, value) => value.$to_proc()
def process_block_pass(exp, level)
Expand Down Expand Up @@ -1314,21 +1247,6 @@ def process_until(exp, level)
code
end

# alias foo bar
#
# s(:alias, s(:sym, :foo), s(:sym, :bar))
def process_alias(exp, level)
new = mid_to_jsid exp[0][1].to_s
old = mid_to_jsid exp[1][1].to_s

if [:class, :module].include? @scope.type
@scope.methods << "$#{exp[0][1].to_s}"
f("$opal.defn(self, '$%s', %s%s)" % [exp[0][1], @scope.proto, old], exp)
else
f("%s._proto%s = %s._proto%s" % ['self', new, 'self', old], exp)
end
end

def process_masgn(sexp, level)
lhs, rhs = sexp
tmp = @scope.new_temp
Expand Down Expand Up @@ -1385,26 +1303,6 @@ def process_svalue(sexp, level)
process sexp[0], level
end

# s(:return [val])
def process_return(sexp, level)
val = process(sexp[0] || s(:nil))

if @scope.iter? and parent_def = @scope.find_parent_def
parent_def.catch_return = true
[f("$opal.$return(", sexp), val, f(")", sexp)]

elsif level == :expr and @scope.def?
@scope.catch_return = true
[f("$opal.$return(", sexp), val, f(")", sexp)]

elsif level == :stmt
[f("return ", sexp), val]

else
raise SyntaxError, "void value expression: cannot return as an expression"
end
end

# s(:if, test, truthy, falsy)
def process_if(sexp, level)
test, truthy, falsy = sexp
Expand Down Expand Up @@ -1493,28 +1391,6 @@ def js_falsy(sexp)
end
end

# s(:and, lhs, rhs)
def process_and(sexp, level)
lhs, rhs = sexp
t = nil
tmp = @scope.new_temp

if t = js_truthy_optimize(lhs)
result = []
result << f("((#{tmp} = ", sexp) << t
result << f(") ? ", sexp) << process(rhs)
result << f(" : #{tmp})", sexp)
@scope.queue_temp tmp

return result
end

@scope.queue_temp tmp

[f("(#{tmp} = ", sexp), process(lhs), f(", #{tmp} !== false && #{tmp} !== nil ? ", sexp), process(rhs), f(" : #{tmp})", sexp)]

end

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

0 comments on commit 7f8d2e2

Please sign in to comment.