Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Cleanup node hierarchy
Browse files Browse the repository at this point in the history
adambeynon committed Oct 23, 2013
1 parent 9e04360 commit 05ed472
Showing 20 changed files with 535 additions and 488 deletions.
7 changes: 7 additions & 0 deletions lib/opal/nodes.rb
Original file line number Diff line number Diff line change
@@ -4,7 +4,9 @@
require 'opal/nodes/constants'
require 'opal/nodes/call'
require 'opal/nodes/call_special'
require 'opal/nodes/module'
require 'opal/nodes/class'
require 'opal/nodes/singleton_class'
require 'opal/nodes/iter'
require 'opal/nodes/def'
require 'opal/nodes/if'
@@ -15,3 +17,8 @@
require 'opal/nodes/case'
require 'opal/nodes/super'
require 'opal/nodes/top'
require 'opal/nodes/while'
require 'opal/nodes/hash'
require 'opal/nodes/array'
require 'opal/nodes/defined'
require 'opal/nodes/masgn'
54 changes: 54 additions & 0 deletions lib/opal/nodes/array.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
require 'opal/nodes/array'

module Opal
module Nodes
class ArrayNode < Base
handle :array

def compile
return push('[]') if children.empty?

code, work = [], []

children.each do |child|
splat = child.type == :splat
part = expr(child)

if splat
if work.empty?
if code.empty?
code << fragment("[].concat(") << part << fragment(")")
else
code << fragment(".concat(") << part << fragment(")")
end
else
if code.empty?
code << fragment("[") << work << fragment("]")
else
code << fragment(".concat([") << work << fragment("])")
end

code << fragment(".concat(") << part << fragment(")")
end
work = []
else
work << fragment(", ") unless work.empty?
work << part
end
end

unless work.empty?
join = [fragment("["), work, fragment("]")]

if code.empty?
code = join
else
code.push([fragment(".concat("), join, fragment(")")])
end
end

push code
end
end
end
end
11 changes: 11 additions & 0 deletions lib/opal/nodes/base_scope.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
require 'opal/nodes/base'

module Opal
module Nodes
class BaseScopeNode < Base
def in_scope(type, &block)
indent { compiler.in_scope(type, &block) }
end
end
end
end
51 changes: 51 additions & 0 deletions lib/opal/nodes/call.rb
Original file line number Diff line number Diff line change
@@ -91,5 +91,56 @@ def using_irb?
@compiler.irb_vars? and scope.top? and arglist == s(:arglist) and recvr.nil? and iter.nil?
end
end

# FIXME: needs rewrite
class ArglistNode < Base
handle :arglist

def compile
code, work = [], []

children.each do |current|
splat = current.first == :splat
arg = expr(current)

if splat
if work.empty?
if code.empty?
code << fragment("[].concat(")
code << arg
code << fragment(")")
else
code += ".concat(#{arg})"
end
else
if code.empty?
code << [fragment("["), work, fragment("]")]
else
code << [fragment(".concat(["), work, fragment("])")]
end

code << [fragment(".concat("), arg, fragment(")")]
end

work = []
else
work << fragment(", ") unless work.empty?
work << arg
end
end

unless work.empty?
join = work

if code.empty?
code = join
else
code << fragment(".concat(") << join << fragment(")")
end
end

push(*code)
end
end
end
end
69 changes: 1 addition & 68 deletions lib/opal/nodes/class.rb
Original file line number Diff line number Diff line change
@@ -1,74 +1,7 @@
require 'opal/nodes/base'
require 'opal/nodes/module'

module Opal
module Nodes
class BaseScopeNode < Base
def in_scope(type, &block)
indent { compiler.in_scope(type, &block) }
end
end

class SingletonClassNode < BaseScopeNode
handle :sclass

children :object, :body

def compile
push "(function(self) {"

in_scope(:sclass) do
add_temp '$scope = self._scope'
add_temp 'def = self._proto'

line scope.to_vars
line stmt(body)
end

line "})(", recv(object), ".$singleton_class())"
end
end

class ModuleNode < BaseScopeNode
handle :module

children :cid, :body

def compile
name, base = name_and_base
helper :module

push "(function($base) {"
line " var self = $module($base, '#{name}');"

in_scope(:module) do
scope.name = name
add_temp "#{scope.proto} = self._proto"
add_temp '$scope = self._scope'

body_code = stmt(body)
empty_line

line scope.to_vars
line body_code
line scope.to_donate_methods
end

line "})(", base, ")"
end

def name_and_base
if Symbol === cid or String === cid
[cid.to_s, 'self']
elsif cid.type == :colon2
[cid[2].to_s, expr(cid[1])]
elsif cid.type == :colon3
[cid[1].to_s, '$opal.Object']
else
raise "Bad receiver in module"
end
end
end

class ClassNode < ModuleNode
handle :class

58 changes: 12 additions & 46 deletions lib/opal/nodes/def.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
require 'opal/nodes/class'
require 'opal/nodes/base_scope'

module Opal
module Nodes
@@ -138,54 +138,20 @@ def arity_check(args, opt, splat, block_name, mid)
end
end

# FIXME: needs rewrite
class ArglistNode < Base
handle :arglist
# def args list
class ArgsNode < Base
handle :args

def compile
code, work = [], []

children.each do |current|
splat = current.first == :splat
arg = expr(current)

if splat
if work.empty?
if code.empty?
code << fragment("[].concat(")
code << arg
code << fragment(")")
else
code += ".concat(#{arg})"
end
else
if code.empty?
code << [fragment("["), work, fragment("]")]
else
code << [fragment(".concat(["), work, fragment("])")]
end

code << [fragment(".concat("), arg, fragment(")")]
end

work = []
else
work << fragment(", ") unless work.empty?
work << arg
end
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
end

unless work.empty?
join = work

if code.empty?
code = join
else
code << fragment(".concat(") << join << fragment(")")
end
end

push(*code)
end
end
end
111 changes: 111 additions & 0 deletions lib/opal/nodes/defined.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
require 'opal/nodes/base'

module Opal
module Nodes
class DefinedNode < Base
handle :defined

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 = 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 expr(s(:defined_super, value))
end

def compile_yield
push compiler.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
80 changes: 0 additions & 80 deletions lib/opal/nodes/definitions.rb
Original file line number Diff line number Diff line change
@@ -96,26 +96,6 @@ def compile
end
end

class RescueModNode < Base
handle :rescue_mod

children :lhs, :rhs

def body
stmt? ? lhs : compiler.returns(lhs)
end

def rescue_val
stmt? ? rhs : compiler.returns(rhs)
end

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

wrap '(function() {', '})()' unless stmt?
end
end

class BlockNode < Base
handle :block

@@ -198,65 +178,5 @@ def find_inline_yield(stmt)
end
end
end

class WhileNode < Base
handle :while

children :test, :body

def compile
with_temp do |redo_var|
test_code = js_truthy(test)

compiler.in_while do
while_loop[:closure] = true if wrap_in_closure?
while_loop[:redo_var] = redo_var

body_code = stmt(body)

if uses_redo?
push "#{redo_var} = false; #{while_open}#{redo_var} || "
push test_code
push while_close
else
push while_open, test_code, while_close
end

push "#{redo_var} = false;" if uses_redo?
line body_code, "}"
end
end

wrap '(function() {', '; return nil; })()' if wrap_in_closure?
end

def while_open
"while ("
end

def while_close
") {"
end

def uses_redo?
while_loop[:use_redo]
end

def wrap_in_closure?
expr? or recv?
end
end

class UntilNode < WhileNode
handle :until

def while_open
"while (!("
end

def while_close
")) {"
end
end
end
end
67 changes: 67 additions & 0 deletions lib/opal/nodes/hash.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
require 'opal/nodes/base'

module Opal
module Nodes
class HashNode < Base
handle :hash

def keys_and_values
keys, values = [], []

children.each_with_index do |obj, idx|
if idx.even?
keys << obj
else
values << obj
end
end

[keys, values]
end

def simple_keys?(keys)
keys.all? { |key| [:sym, :str].include? key.type }
end

def compile
keys, values = keys_and_values

if simple_keys? keys
compile_hash2 keys, values
else
compile_hash
end
end

def compile_hash
helper :hash

children.each_with_index do |child, idx|
push ', ' unless idx == 0
push expr(child)
end

wrap '$hash(', ')'
end

def compile_hash2(keys, values)
hash_obj, hash_keys = {}, []
helper :hash2

keys.size.times do |idx|
key = keys[idx][1].to_s.inspect
hash_keys << key unless hash_obj.include? key
hash_obj[key] = expr(values[idx])
end

hash_keys.each_with_index do |key, idx|
push ', ' unless idx == 0
push "#{key}: "
push hash_obj[key]
end

wrap "$hash2([#{hash_keys.join ', '}], {", "})"
end
end
end
end
2 changes: 1 addition & 1 deletion lib/opal/nodes/iter.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
require 'opal/nodes/class'
require 'opal/nodes/base_scope'

module Opal
module Nodes
128 changes: 0 additions & 128 deletions lib/opal/nodes/literal.rb
Original file line number Diff line number Diff line change
@@ -189,133 +189,5 @@ def compile
push ", true)"
end
end

class HashNode < Base
handle :hash

def keys_and_values
keys, values = [], []

children.each_with_index do |obj, idx|
if idx.even?
keys << obj
else
values << obj
end
end

[keys, values]
end

def simple_keys?(keys)
keys.all? { |key| [:sym, :str].include? key.type }
end

def compile
keys, values = keys_and_values

if simple_keys? keys
compile_hash2 keys, values
else
compile_hash
end
end

def compile_hash
helper :hash

children.each_with_index do |child, idx|
push ', ' unless idx == 0
push expr(child)
end

wrap '$hash(', ')'
end

def compile_hash2(keys, values)
hash_obj, hash_keys = {}, []
helper :hash2

keys.size.times do |idx|
key = keys[idx][1].to_s.inspect
hash_keys << key unless hash_obj.include? key
hash_obj[key] = expr(values[idx])
end

hash_keys.each_with_index do |key, idx|
push ', ' unless idx == 0
push "#{key}: "
push hash_obj[key]
end

wrap "$hash2([#{hash_keys.join ', '}], {", "})"
end
end

class ArrayNode < Base
handle :array

def compile
return push('[]') if children.empty?

code, work = [], []

children.each do |child|
splat = child.type == :splat
part = expr(child)

if splat
if work.empty?
if code.empty?
code << fragment("[].concat(") << part << fragment(")")
else
code << fragment(".concat(") << part << fragment(")")
end
else
if code.empty?
code << fragment("[") << work << fragment("]")
else
code << fragment(".concat([") << work << fragment("])")
end

code << fragment(".concat(") << part << fragment(")")
end
work = []
else
work << fragment(", ") unless work.empty?
work << part
end
end

unless work.empty?
join = [fragment("["), work, fragment("]")]

if code.empty?
code = join
else
code.push([fragment(".concat("), join, fragment(")")])
end
end

push code
end
end

# def args list
class ArgsNode < Base
handle :args

def compile
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
end
end
end
end
end
106 changes: 0 additions & 106 deletions lib/opal/nodes/logic.rb
Original file line number Diff line number Diff line change
@@ -207,111 +207,5 @@ def compile
push expr(s(:call, value, :to_proc, s(:arglist)))
end
end

class DefinedNode < Base
handle :defined

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 = 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 expr(s(:defined_super, value))
end

def compile_yield
push compiler.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
62 changes: 62 additions & 0 deletions lib/opal/nodes/masgn.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
require 'opal/nodes/base'

module Opal
module Nodes
class MassAssignNode < Base
handle :masgn

children :lhs, :rhs

def compile
tmp = scope.new_temp
len = 0 # how many rhs items are we sure we have

if rhs.type == :array
len = rhs.size - 1
push "#{tmp} = ", expr(rhs)
elsif rhs.type == :to_ary
push "#{tmp} = $opal.to_ary(", expr(rhs[1]), ")"
elsif rhs.type == :splat
push "(#{tmp} = ", expr(rhs[1]), ")['$to_a'] ? (#{tmp} = #{tmp}['$to_a']())"
push " : (#{tmp})._isArray ? #{tmp} : (#{tmp} = [#{tmp}])"
else
raise "unsupported mlhs type"
end

lhs.children.each_with_index do |child, idx|
push ', '

if child.type == :splat
if part = child[1]
part = part.dup
part << s(:js_tmp, "$slice.call(#{tmp}, #{idx})")
push expr(part)
end
else
if idx >= len
assign = s(:js_tmp, "(#{tmp}[#{idx}] == null ? nil : #{tmp}[#{idx}])")
else
assign = s(:js_tmp, "#{tmp}[#{idx}]")
end

part = child.dup
if child.type == :lasgn or child.type == :iasgn or child.type == :lvar
part << assign
elsif child.type == :call
part[2] = "#{part[2]}=".to_sym
part.last << assign
elsif child.type == :attrasgn
part.last << assign
else
raise "Bad lhs for masgn"
end

push expr(part)
end
end

scope.queue_temp tmp
end
end
end
end
46 changes: 46 additions & 0 deletions lib/opal/nodes/module.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
require 'opal/nodes/base_scope'

module Opal
module Nodes
class ModuleNode < BaseScopeNode
handle :module

children :cid, :body

def compile
name, base = name_and_base
helper :module

push "(function($base) {"
line " var self = $module($base, '#{name}');"

in_scope(:module) do
scope.name = name
add_temp "#{scope.proto} = self._proto"
add_temp '$scope = self._scope'

body_code = stmt(body)
empty_line

line scope.to_vars
line body_code
line scope.to_donate_methods
end

line "})(", base, ")"
end

def name_and_base
if Symbol === cid or String === cid
[cid.to_s, 'self']
elsif cid.type == :colon2
[cid[2].to_s, expr(cid[1])]
elsif cid.type == :colon3
[cid[1].to_s, '$opal.Object']
else
raise "Bad receiver in module"
end
end
end
end
end
20 changes: 20 additions & 0 deletions lib/opal/nodes/rescue.rb
Original file line number Diff line number Diff line change
@@ -2,6 +2,26 @@

module Opal
module Nodes
class RescueModNode < Base
handle :rescue_mod

children :lhs, :rhs

def body
stmt? ? lhs : compiler.returns(lhs)
end

def rescue_val
stmt? ? rhs : compiler.returns(rhs)
end

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

wrap '(function() {', '})()' unless stmt?
end
end

class EnsureNode < Base
handle :ensure

26 changes: 26 additions & 0 deletions lib/opal/nodes/singleton_class.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
require 'opal/nodes/base_scope'

module Opal
module Nodes
class SingletonClassNode < BaseScopeNode
handle :sclass

children :object, :body

def compile
push "(function(self) {"

in_scope(:sclass) do
add_temp '$scope = self._scope'
add_temp 'def = self._proto'

line scope.to_vars
line stmt(body)
end

line "})(", recv(object), ".$singleton_class())"
end
end

end
end
1 change: 0 additions & 1 deletion lib/opal/nodes/super.rb
Original file line number Diff line number Diff line change
@@ -92,7 +92,6 @@ def compile
def has_splat?
args.children.any? { |child| child.type == :splat }
end

end
end
end
2 changes: 1 addition & 1 deletion lib/opal/nodes/top.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
require 'opal/nodes/class'
require 'opal/nodes/base_scope'

module Opal
module Nodes
57 changes: 0 additions & 57 deletions lib/opal/nodes/variables.rb
Original file line number Diff line number Diff line change
@@ -151,62 +151,5 @@ def compile
push ")"
end
end

class MassAssignNode < Base
handle :masgn

children :lhs, :rhs

def compile
tmp = scope.new_temp
len = 0 # how many rhs items are we sure we have

if rhs.type == :array
len = rhs.size - 1
push "#{tmp} = ", expr(rhs)
elsif rhs.type == :to_ary
push "#{tmp} = $opal.to_ary(", expr(rhs[1]), ")"
elsif rhs.type == :splat
push "(#{tmp} = ", expr(rhs[1]), ")['$to_a'] ? (#{tmp} = #{tmp}['$to_a']())"
push " : (#{tmp})._isArray ? #{tmp} : (#{tmp} = [#{tmp}])"
else
raise "unsupported mlhs type"
end

lhs.children.each_with_index do |child, idx|
push ', '

if child.type == :splat
if part = child[1]
part = part.dup
part << s(:js_tmp, "$slice.call(#{tmp}, #{idx})")
push expr(part)
end
else
if idx >= len
assign = s(:js_tmp, "(#{tmp}[#{idx}] == null ? nil : #{tmp}[#{idx}])")
else
assign = s(:js_tmp, "#{tmp}[#{idx}]")
end

part = child.dup
if child.type == :lasgn or child.type == :iasgn or child.type == :lvar
part << assign
elsif child.type == :call
part[2] = "#{part[2]}=".to_sym
part.last << assign
elsif child.type == :attrasgn
part.last << assign
else
raise "Bad lhs for masgn"
end

push expr(part)
end
end

scope.queue_temp tmp
end
end
end
end
65 changes: 65 additions & 0 deletions lib/opal/nodes/while.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
require 'opal/nodes/base'

module Opal
module Nodes
class WhileNode < Base
handle :while

children :test, :body

def compile
with_temp do |redo_var|
test_code = js_truthy(test)

compiler.in_while do
while_loop[:closure] = true if wrap_in_closure?
while_loop[:redo_var] = redo_var

body_code = stmt(body)

if uses_redo?
push "#{redo_var} = false; #{while_open}#{redo_var} || "
push test_code
push while_close
else
push while_open, test_code, while_close
end

push "#{redo_var} = false;" if uses_redo?
line body_code, "}"
end
end

wrap '(function() {', '; return nil; })()' if wrap_in_closure?
end

def while_open
"while ("
end

def while_close
") {"
end

def uses_redo?
while_loop[:use_redo]
end

def wrap_in_closure?
expr? or recv?
end
end

class UntilNode < WhileNode
handle :until

def while_open
"while (!("
end

def while_close
")) {"
end
end
end
end

0 comments on commit 05ed472

Please sign in to comment.