Skip to content

Commit

Permalink
Some more nodes for compiler
Browse files Browse the repository at this point in the history
  • Loading branch information
adambeynon committed Oct 22, 2013
1 parent 1727d0c commit 7a16fb2
Show file tree
Hide file tree
Showing 7 changed files with 346 additions and 288 deletions.
3 changes: 3 additions & 0 deletions lib/opal/nodes.rb
Expand Up @@ -2,6 +2,9 @@
require 'opal/nodes/literal'
require 'opal/nodes/variables'
require 'opal/nodes/constants'
require 'opal/nodes/call'
require 'opal/nodes/call_special'
require 'opal/nodes/class'
require 'opal/nodes/logic'
require 'opal/nodes/definitions'
require 'opal/nodes/yield'
Empty file added lib/opal/nodes/call.rb
Empty file.
120 changes: 120 additions & 0 deletions lib/opal/nodes/call_special.rb
@@ -0,0 +1,120 @@
require 'opal/nodes/base'

module Opal
class Parser
# recv.mid = rhs
# s(:recv, :mid=, s(:arglist, rhs))
class AttrAssignNode < Node
children :recvr, :mid, :arglist

def compile
sexp = s(:call, recvr, mid, arglist)
push @parser.process(sexp, @level)
end
end

# lhs =~ rhs
# s(:match3, lhs, rhs)
class Match3Node < Node
children :lhs, :rhs

def compile
sexp = s(:call, lhs, :=~, s(:arglist, rhs))
push @parser.process(sexp, @level)
end
end

# a ||= rhs
# s(:op_asgn_or, s(:lvar, :a), s(:lasgn, :a, rhs))
class OpAsgnOrNode < Node
children :recvr, :rhs

def compile
sexp = s(:or, recvr, rhs)
push expr(sexp)
end
end

# a &&= rhs
# s(:op_asgn_and, s(:lvar, :a), s(:lasgn, a:, rhs))
class OpAsgnAndNode < Node
children :recvr, :rhs

def compile
sexp = s(:and, recvr, rhs)
push expr(sexp)
end
end

# lhs[args] ||= rhs
# s(:op_asgn1, lhs, args, :||, rhs)
class OpAsgn1Node < Node
children :lhs, :args, :op, :rhs

def first_arg
args[1]
end

def compile
with_temp do |a| # args
with_temp do |r| # recv
aref = s(:call, s(:js_tmp, r), :[], s(:arglist, s(:js_tmp, a)))
aset = s(:call, s(:js_tmp, r), :[]=, s(:arglist, s(:js_tmp, a), rhs))
orop = s(:or, aref, aset)

push "(#{a} = ", expr(first_arg), ", #{r} = ", expr(lhs)
push ", ", expr(orop), ")"
end
end
end
end

# lhs.b += rhs
# s(:op_asgn2, lhs, :b=, :+, rhs)
class OpAsgn2Node < Node
children :lhs, :mid, :op, :rhs

def meth
mid.to_s[0..-2]
end

def compile
case op.to_s
when '||' then compile_or
when '&&' then compile_and
else compile_operator
end
end

def compile_or
with_temp do |tmp|
getr = s(:call, s(:js_tmp, tmp), meth, s(:arglist))
asgn = s(:call, s(:js_tmp, tmp), mid, s(:arglist, rhs))
orop = s(:or, getr, asgn)

push "(#{tmp} = ", expr(lhs), ", ", expr(orop), ")"
end
end

def compile_and
with_temp do |tmp|
getr = s(:call, s(:js_tmp, tmp), meth, s(:arglist))
asgn = s(:call, s(:js_tmp, tmp), mid, s(:arglist, rhs))
andop = s(:and, getr, asgn)

push "(#{tmp} = ", expr(lhs), ", ", expr(andop), ")"
end
end

def compile_operator
with_temp do |tmp|
getr = s(:call, s(:js_tmp, tmp), meth, s(:arglist))
oper = s(:call, getr, op, s(:arglist, rhs))
asgn = s(:call, s(:js_tmp, tmp), mid, s(:arglist, oper))

push "(#{tmp} = ", expr(lhs), ", ", expr(asgn), ")"
end
end
end
end
end
84 changes: 84 additions & 0 deletions lib/opal/nodes/class.rb
@@ -0,0 +1,84 @@
require 'opal/nodes/base'

module Opal
class Parser
class BaseScopeNode < Node
def in_scope(type, &block)
@parser.in_scope(type, &block)
end
end

class SingletonClassNode < BaseScopeNode
children :object, :body

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

push scope.to_vars
push stmt(body)
end

push "})("
push recv(object)
wrap "(function(self) {", ".$singleton_class())"
end
end

class ModuleNode < BaseScopeNode
children :cid, :body

def compile
helper :module

push pre_code

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

body_code = stmt(body)

push "#{@indent}", scope.to_vars, "\n\n#{@indent}"
push body_code
push "\n#{@indent}", scope.to_donate_methods
end
end

push "\n#{@indent}})(", module_base, ")"
end

def pre_code
name = module_name
"(function($base) {\n#{@indent} var self = $module($base, '#{name}');\n"
end

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

def module_name
if Symbol === cid or String === cid
cid.to_s
elsif cid.type == :colon2
cid[2].to_s
elsif cid.type == :colon3
cid[1].to_s
else
raise "Bad receiver in module"
end
end
end
end
end
108 changes: 89 additions & 19 deletions lib/opal/nodes/definitions.rb
Expand Up @@ -3,27 +3,15 @@
module Opal
class Parser

class BaseScopeNode < Node
def in_scope(type, &block)
@parser.in_scope(type, &block)
end
end

class SingletonClassNode < BaseScopeNode
children :object, :body
# :scope nodes are actually inside scopes (e.g. :module, :class).
# These are not actually the scopes themselves.
class ScopeNode < Node
children :body

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

push scope.to_vars
push stmt(body)
end

push "})("
push recv(object)
wrap "(function(self) {", ".$singleton_class())"
body = self.body || s(:nil)
body = @parser.returns(body) unless scope.class_scope?
push stmt(body)
end
end

Expand Down Expand Up @@ -105,5 +93,87 @@ def compile
wrap '(function() {', '})()' unless stmt?
end
end

class BlockNode < Node
def compile
return push "nil" if children.empty?

children.each_with_index do |child, idx|
push stmt_join unless idx == 0

if yasgn = find_inline_yield(child)
push @parser.process(yasgn, @level)
push ";"
end

push @parser.process(child, @level)
push ";" if child_is_expr?(child)
end
end

def stmt_join
indent = @parser.instance_variable_get(:@indent)
scope.class_scope? ? "\n\n#{indent}" : "\n#{indent}"
end

def child_is_expr?(child)
raw_expression?(child) and [:stmt, :stmt_closure].include?(@level)
end

def raw_expression?(child)
![:xstr, :dxstr].include?(child.type)
end

# When a block sexp gets generated, any inline yields (i.e. yield
# statements that are not direct members of the block) need to be
# generated as a top level member. This is because if a yield
# is returned by a break statement, then the method must return.
#
# As inline expressions in javascript cannot return, the block
# must be rewritten.
#
# For example, a yield inside an array:
#
# [1, 2, 3, yield(4)]
#
# Must be rewitten into:
#
# tmp = yield 4
# [1, 2, 3, tmp]
#
# This rewriting happens on sexps directly.
#
# @param [Sexp] stmt sexps to (maybe) rewrite
# @return [Sexp]
def find_inline_yield(stmt)
found = nil
case stmt.first
when :js_return
if found = find_inline_yield(stmt[1])
found = found[2]
end
when :array
stmt[1..-1].each_with_index do |el, idx|
if el.first == :yield
found = el
stmt[idx+1] = s(:js_tmp, '$yielded')
end
end
when :call
arglist = stmt[3]
arglist[1..-1].each_with_index do |el, idx|
if el.first == :yield
found = el
arglist[idx+1] = s(:js_tmp, '$yielded')
end
end
end

if found
scope.add_temp '$yielded' unless scope.has_temp? '$yielded'
s(:yasgn, '$yielded', found)
end
end
end
end
end
25 changes: 25 additions & 0 deletions lib/opal/nodes/logic.rb
Expand Up @@ -161,6 +161,31 @@ def compile
end
end

class JSReturnNode < Node
children :value

def compile
push "return "
push expr(value)
end
end

class JSTempNode < Node
children :value

def compile
push value.to_s
end
end

class BlockPassNode < Node
children :value

def compile
push expr(s(:call, value, :to_proc, s(:arglist)))
end
end

class DefinedNode < Node
children :value

Expand Down

0 comments on commit 7a16fb2

Please sign in to comment.