def logop(type, left, right)
left = value_expr left
if left and left[0] == type and not left.paren then
node, second = left, nil
while (second = node[2]) && second[0] == type and not second.paren do
node = second
end
node[2] = s(type, second, right)
return left
end
return s(type, left, right)
end
def new_aref val
val[2] ||= s(:arglist)
val[2][0] = :arglist if val[2][0] == :array
if val[0].node_type == :self then
result = new_call nil, "[]""[]", val[2]
else
result = new_call val[0], "[]""[]", val[2]
end
result
end
def new_body val
result = val[0]
if val[1] then
result = s(:rescue)
result << val[0] if val[0]
resbody = val[1]
while resbody do
result << resbody
resbody = resbody.resbody(true)
end
result << val[2] if val[2]
result.line = (val[0] || val[1]).line
elsif not val[2].nil? then
warning("else without rescue is useless")
result = block_append(result, val[2])
end
result = s(:ensure, result, val[3]).compact if val[3]
return result
end
def new_call recv, meth, args = nil
result = s(:call, recv, meth)
result.line = recv.line if recv
args ||= s(:arglist)
args[0] = :arglist if args.first == :array
args = s(:arglist, args) unless args.first == :arglist
result << args
result
end
def new_case expr, body
result = s(:case, expr)
line = (expr || body).line
while body and body.node_type == :when
result << body
body = body.delete_at 3
end
body = nil if body == s(:block)
result << body
result.line = line
result
end
def new_class val
line, path, superclass, body = val[1], val[2], val[3], val[5]
scope = s(:scope, body).compact
result = s(:class, path, superclass, scope)
result.line = line
result.comments = self.comments.pop
result
end
def new_compstmt val
result = void_stmts(val[0])
result = remove_begin(result) if result
result
end
def new_defn val
(line, bol), name, args, body = val[2], val[1], val[3], val[4]
body ||= s(:nil)
body ||= s(:block)
body = s(:block, body) unless body.first == :block
result = s(:defn, name.to_sym, args, s(:scope, body))
result.line = line
result.line -= 1 if bol
result.comments = self.comments.pop
result
end
def new_defs val
recv, name, args, body = val[1], val[4], val[6], val[7]
body ||= s(:block)
body = s(:block, body) unless body.first == :block
result = s(:defs, recv, name.to_sym, args, s(:scope, body))
result.line = recv.line
result.comments = self.comments.pop
result
end
def new_for expr, var, body
result = s(:for, expr, var).line(var.line)
result << body if body
result
end
def new_if c, t, f
l = [c.line, t && t.line, f && f.line].compact.min
c = cond c
c, t, f = c.last, f, t if c[0] == :not
s(:if, c, t, f).line(l)
end
def new_iter call, args, body
result = s(:iter)
result << call if call
result << args
result << body if body
result
end
def new_masgn lhs, rhs, wrap = false
rhs = value_expr(rhs)
rhs = lhs[1] ? s(:to_ary, rhs) : s(:array, rhs) if wrap
lhs.delete_at 1 if lhs[1].nil?
lhs << rhs
lhs
end
def new_module val
line, path, body = val[1], val[2], val[4]
body = s(:scope, body).compact
result = s(:module, path, body)
result.line = line
result.comments = self.comments.pop
result
end
def new_op_asgn val
lhs, asgn_op, arg = val[0], val[1].to_sym, val[2]
name = lhs.value
arg = remove_begin(arg)
result = case asgn_op
when "||""||" then
lhs << arg
s(:op_asgn_or, self.gettable(name), lhs)
when "&&""&&" then
lhs << arg
s(:op_asgn_and, self.gettable(name), lhs)
else
lhs[2] = new_call(self.gettable(name), asgn_op,
s(:arglist, arg))
lhs
end
result.line = lhs.line
result
end
def new_regexp val
node = val[1] || s(:str, '')
options = val[2]
o, k = 0, nil
options.split(//).uniq.each do |c|
v = {
'x' => Regexp::EXTENDED,
'i' => Regexp::IGNORECASE,
'm' => Regexp::MULTILINE,
'o' => Regexp::ONCE,
'n' => Regexp::ENC_NONE,
'e' => Regexp::ENC_EUC,
's' => Regexp::ENC_SJIS,
'u' => Regexp::ENC_UTF8,
}[c]
raise "unknown regexp option: #{c}" unless v
o += v
k = c if c =~ /[esu]/
end
case node[0]
when :str then
node[0] = :lit
node[1] = if k then
Regexp.new(node[1], o, k)
else
Regexp.new(node[1], o)
end
when :dstr then
if options =~ /o/ then
node[0] = :dregx_once
else
node[0] = :dregx
end
node << o if o and o != 0
else
node = s(:dregx, '', node);
node[0] = :dregx_once if options =~ /o/
node << o if o and o != 0
end
node
end
def new_sclass val
recv, in_def, in_single, body = val[3], val[4], val[6], val[7]
scope = s(:scope, body).compact
result = s(:sclass, recv, scope)
result.line = val[2]
self.in_def = in_def
self.in_single = in_single
result
end
def new_super args
if args && args.node_type == :block_pass then
s(:super, args)
else
args ||= s(:arglist)
s(:super, *args[1..-1])
end
end
def new_undef n, m = nil
if m then
block_append(n, s(:undef, m))
else
s(:undef, n)
end
end
def new_until block, expr, pre
expr = (expr.first == :not ? expr.last : s(:not, expr)).line(expr.line)
new_while block, expr, pre
end
def new_while block, expr, pre
line = [block && block.line, expr.line].compact.min
block, pre = block.last, false if block && block[0] == :begin
expr = cond expr
result = if expr.first == :not then
s(:until, expr.last, block, pre)
else
s(:while, expr, block, pre)
end
result.line = line
result
end
def new_xstring str
if str then
case str[0]
when :str
str[0] = :xstr
when :dstr
str[0] = :dxstr
else
str = s(:dxstr, '', str)
end
str
else
s(:xstr, '')
end
end
def new_yield args = nil
raise SyntaxError, "Block argument should not be given." if
args && args.node_type == :block_pass
args ||= s(:arglist)
args[0] = :arglist if args.first == :array
args = s(:arglist, args) unless args.first == :arglist
return s(:yield, *args[1..-1])
end
def next_token
if self.lexer.advance then
return self.lexer.token, self.lexer.yacc_value
else
return [false, '$end']
end
end
def node_assign(lhs, rhs)
return nil unless lhs
rhs = value_expr rhs
case lhs[0]
when :gasgn, :iasgn, :lasgn, :dasgn, :dasgn_curr,
:masgn, :cdecl, :cvdecl, :cvasgn then
lhs << rhs
when :attrasgn, :call then
args = lhs.pop unless Symbol === lhs.last
lhs << arg_add(args, rhs)
when :const then
lhs[0] = :cdecl
lhs << rhs
else
raise "unknown lhs #{lhs.inspect}"
end
lhs
end
def process(str, file = "(string)")
raise "bad val: #{str.inspect}" unless String === str
self.file = file
self.lexer.src = str
@yydebug = ENV.has_key? 'DEBUG'
do_parse
end
alias :parse :process
def remove_begin node
oldnode = node
if node and :begin == node[0] and node.size == 2 then
node = node[-1]
node.line = oldnode.line
end
node
end
def reset
lexer.reset
self.in_def = false
self.in_single = 0
self.env.reset
self.comments.clear
end
def ret_args node
if node then
raise SyntaxError, "block argument should not be given" if
node[0] == :block_pass
node = node.last if node[0] == :array && node.size == 2
node = s(:svalue, node) if node[0] == :splat and not node.paren
node[0] = :svalue if node[0] == :arglist && node[1][0] == :splat
end
node
end
def s(*args)
result = Sexp.new(*args)
result.line ||= lexer.lineno if lexer.src
result.file = self.file
result
end
def value_expr oldnode
node = remove_begin oldnode
node.line = oldnode.line if oldnode
node[2] = value_expr(node[2]) if node and node[0] == :if
node
end
def void_stmts node
return nil unless node
return node unless node[0] == :block
node[1..-1] = node[1..-1].map { |n| remove_begin(n) }
node
end
def warning s
end
alias :old_yyerror :yyerror
def yyerror msg
old_yyerror
end
end