view graal/com.oracle.truffle.ruby.parser/src/com/oracle/truffle/ruby/parser/Translator.java @ 13918:22bf5a8ba9eb

Ruby: restore prototype debugger.
author Chris Seaton <chris.seaton@oracle.com>
date Mon, 10 Feb 2014 03:39:21 +0000
parents 2c1c805153e6
children
line wrap: on
line source

/*
 * Copyright (c) 2013, 2014 Oracle and/or its affiliates. All rights reserved. This
 * code is released under a tri EPL/GPL/LGPL license. You can use it,
 * redistribute it and/or modify it under the terms of the:
 *
 * Eclipse Public License version 1.0
 * GNU General Public License version 2
 * GNU Lesser General Public License version 2.1
 */
package com.oracle.truffle.ruby.parser;

import java.math.*;
import java.util.*;
import java.util.regex.*;

import com.oracle.truffle.api.*;
import com.oracle.truffle.api.frame.*;
import com.oracle.truffle.api.impl.*;
import com.oracle.truffle.ruby.nodes.*;
import com.oracle.truffle.ruby.nodes.call.*;
import com.oracle.truffle.ruby.nodes.cast.*;
import com.oracle.truffle.ruby.nodes.constants.*;
import com.oracle.truffle.ruby.nodes.control.*;
import com.oracle.truffle.ruby.nodes.core.*;
import com.oracle.truffle.ruby.nodes.literal.*;
import com.oracle.truffle.ruby.nodes.literal.array.*;
import com.oracle.truffle.ruby.nodes.methods.*;
import com.oracle.truffle.ruby.nodes.methods.locals.*;
import com.oracle.truffle.ruby.nodes.objects.*;
import com.oracle.truffle.ruby.nodes.objects.instancevariables.*;
import com.oracle.truffle.ruby.nodes.yield.*;
import com.oracle.truffle.ruby.runtime.*;
import com.oracle.truffle.ruby.runtime.core.*;
import com.oracle.truffle.ruby.runtime.core.range.*;
import com.oracle.truffle.ruby.runtime.methods.*;

/**
 * A JRuby parser node visitor which translates JRuby AST nodes into our Ruby nodes, implementing a
 * Ruby parser. Therefore there is some namespace contention here! We make all references to JRuby
 * explicit. This is the only place though - it doesn't leak out elsewhere.
 */
public class Translator implements org.jrubyparser.NodeVisitor {

    protected final Translator parent;

    protected final RubyContext context;
    protected final TranslatorEnvironment environment;
    protected final Source source;
    protected final RubyNodeInstrumenter instrumenter;

    private boolean translatingForStatement = false;

    private static final Map<Class, String> nodeDefinedNames = new HashMap<>();

    static {
        nodeDefinedNames.put(org.jrubyparser.ast.SelfNode.class, "self");
        nodeDefinedNames.put(org.jrubyparser.ast.NilNode.class, "nil");
        nodeDefinedNames.put(org.jrubyparser.ast.TrueNode.class, "true");
        nodeDefinedNames.put(org.jrubyparser.ast.FalseNode.class, "false");
        nodeDefinedNames.put(org.jrubyparser.ast.LocalAsgnNode.class, "assignment");
        nodeDefinedNames.put(org.jrubyparser.ast.DAsgnNode.class, "assignment");
        nodeDefinedNames.put(org.jrubyparser.ast.GlobalAsgnNode.class, "assignment");
        nodeDefinedNames.put(org.jrubyparser.ast.InstAsgnNode.class, "assignment");
        nodeDefinedNames.put(org.jrubyparser.ast.ClassVarAsgnNode.class, "assignment");
        nodeDefinedNames.put(org.jrubyparser.ast.OpAsgnAndNode.class, "assignment");
        nodeDefinedNames.put(org.jrubyparser.ast.OpAsgnOrNode.class, "assignment");
        nodeDefinedNames.put(org.jrubyparser.ast.OpAsgnNode.class, "assignment");
        nodeDefinedNames.put(org.jrubyparser.ast.OpElementAsgnNode.class, "assignment");
        nodeDefinedNames.put(org.jrubyparser.ast.MultipleAsgnNode.class, "assignment");
        nodeDefinedNames.put(org.jrubyparser.ast.GlobalVarNode.class, "global-variable");
        nodeDefinedNames.put(org.jrubyparser.ast.StrNode.class, "expression");
        nodeDefinedNames.put(org.jrubyparser.ast.DStrNode.class, "expression");
        nodeDefinedNames.put(org.jrubyparser.ast.FixnumNode.class, "expression");
        nodeDefinedNames.put(org.jrubyparser.ast.BignumNode.class, "expression");
        nodeDefinedNames.put(org.jrubyparser.ast.FloatNode.class, "expression");
        nodeDefinedNames.put(org.jrubyparser.ast.RegexpNode.class, "expression");
        nodeDefinedNames.put(org.jrubyparser.ast.DRegexpNode.class, "expression");
        nodeDefinedNames.put(org.jrubyparser.ast.ArrayNode.class, "expression");
        nodeDefinedNames.put(org.jrubyparser.ast.HashNode.class, "expression");
        nodeDefinedNames.put(org.jrubyparser.ast.SymbolNode.class, "expression");
        nodeDefinedNames.put(org.jrubyparser.ast.DotNode.class, "expression");
        nodeDefinedNames.put(org.jrubyparser.ast.NotNode.class, "expression");
        nodeDefinedNames.put(org.jrubyparser.ast.AndNode.class, "expression");
        nodeDefinedNames.put(org.jrubyparser.ast.OrNode.class, "expression");
        nodeDefinedNames.put(org.jrubyparser.ast.LocalVarNode.class, "local-variable");
        nodeDefinedNames.put(org.jrubyparser.ast.DVarNode.class, "local-variable");
    }

    /**
     * Global variables which in common usage have frame local semantics.
     */
    public static final Set<String> FRAME_LOCAL_GLOBAL_VARIABLES = new HashSet<>(Arrays.asList("$_"));

    public Translator(RubyContext context, Translator parent, TranslatorEnvironment environment, Source source) {
        this.context = context;
        this.parent = parent;
        this.environment = environment;
        this.source = source;
        this.instrumenter = environment.getNodeInstrumenter();
    }

    @Override
    public Object visitAliasNode(org.jrubyparser.ast.AliasNode node) {
        final SourceSection sourceSection = translate(node.getPosition());

        final org.jrubyparser.ast.LiteralNode oldName = (org.jrubyparser.ast.LiteralNode) node.getOldName();
        final org.jrubyparser.ast.LiteralNode newName = (org.jrubyparser.ast.LiteralNode) node.getNewName();

        final ClassNode classNode = new ClassNode(context, sourceSection, new SelfNode(context, sourceSection));

        return new AliasNode(context, sourceSection, classNode, newName.getName(), oldName.getName());
    }

    @Override
    public Object visitAndNode(org.jrubyparser.ast.AndNode node) {
        final SourceSection sourceSection = translate(node.getPosition());

        RubyNode x;

        if (node.getFirst() == null) {
            x = new NilNode(context, sourceSection);
        } else {
            x = (RubyNode) node.getFirst().accept(this);
        }

        RubyNode y;

        if (node.getSecond() == null) {
            y = new NilNode(context, sourceSection);
        } else {
            y = (RubyNode) node.getSecond().accept(this);
        }

        return AndNodeFactory.create(context, sourceSection, x, y);
    }

    @Override
    public Object visitArgsCatNode(org.jrubyparser.ast.ArgsCatNode node) {
        final List<org.jrubyparser.ast.Node> nodes = new ArrayList<>();
        collectArgsCatNodes(nodes, node);

        final List<RubyNode> translatedNodes = new ArrayList<>();

        for (org.jrubyparser.ast.Node catNode : nodes) {
            translatedNodes.add((RubyNode) catNode.accept(this));
        }

        return new ArrayConcatNode(context, translate(node.getPosition()), translatedNodes.toArray(new RubyNode[translatedNodes.size()]));
    }

    // ArgsCatNodes can be nested - this collects them into a flat list of children
    private void collectArgsCatNodes(List<org.jrubyparser.ast.Node> nodes, org.jrubyparser.ast.ArgsCatNode node) {
        if (node.getFirst() instanceof org.jrubyparser.ast.ArgsCatNode) {
            collectArgsCatNodes(nodes, (org.jrubyparser.ast.ArgsCatNode) node.getFirst());
        } else {
            nodes.add(node.getFirst());
        }

        if (node.getSecond() instanceof org.jrubyparser.ast.ArgsCatNode) {
            collectArgsCatNodes(nodes, (org.jrubyparser.ast.ArgsCatNode) node.getSecond());
        } else {
            nodes.add(node.getSecond());
        }
    }

    @Override
    public Object visitArgsNode(org.jrubyparser.ast.ArgsNode node) {
        return unimplemented(node);
    }

    @Override
    public Object visitArgsPushNode(org.jrubyparser.ast.ArgsPushNode node) {
        return new ArrayPushNode(context, translate(node.getPosition()), (RubyNode) node.getFirstNode().accept(this), (RubyNode) node.getSecondNode().accept(this));
    }

    @Override
    public Object visitArrayNode(org.jrubyparser.ast.ArrayNode node) {
        final List<org.jrubyparser.ast.Node> values = node.childNodes();

        final RubyNode[] translatedValues = new RubyNode[values.size()];

        for (int n = 0; n < values.size(); n++) {
            translatedValues[n] = (RubyNode) values.get(n).accept(this);
        }

        return new UninitialisedArrayLiteralNode(context, translate(node.getPosition()), translatedValues);
    }

    @Override
    public Object visitAttrAssignNode(org.jrubyparser.ast.AttrAssignNode node) {
        return visitAttrAssignNodeExtraArgument(node, null);
    }

    /**
     * See translateDummyAssignment to understand what this is for.
     */
    public RubyNode visitAttrAssignNodeExtraArgument(org.jrubyparser.ast.AttrAssignNode node, RubyNode extraArgument) {
        final org.jrubyparser.ast.CallNode callNode = new org.jrubyparser.ast.CallNode(node.getPosition(), node.getReceiver(), node.getName(), node.getArgs());
        return visitCallNodeExtraArgument(callNode, extraArgument);
    }

    @Override
    public Object visitBackRefNode(org.jrubyparser.ast.BackRefNode node) {
        return unimplemented(node);
    }

    @Override
    public Object visitBeginNode(org.jrubyparser.ast.BeginNode node) {
        return node.getBody().accept(this);
    }

    @Override
    public Object visitBignumNode(org.jrubyparser.ast.BignumNode node) {
        return new BignumLiteralNode(context, translate(node.getPosition()), node.getValue());
    }

    @Override
    public Object visitBlockArg18Node(org.jrubyparser.ast.BlockArg18Node node) {
        return unimplemented(node);
    }

    @Override
    public Object visitBlockArgNode(org.jrubyparser.ast.BlockArgNode node) {
        return unimplemented(node);
    }

    @Override
    public Object visitBlockNode(org.jrubyparser.ast.BlockNode node) {
        final List<org.jrubyparser.ast.Node> children = node.childNodes();

        final List<RubyNode> translatedChildren = new ArrayList<>();

        for (int n = 0; n < children.size(); n++) {
            final RubyNode translatedChild = (RubyNode) children.get(n).accept(this);

            if (!(translatedChild instanceof DeadNode)) {
                translatedChildren.add(translatedChild);
            }
        }

        if (translatedChildren.size() == 1) {
            return translatedChildren.get(0);
        } else {
            return new SequenceNode(context, translate(node.getPosition()), translatedChildren.toArray(new RubyNode[translatedChildren.size()]));
        }
    }

    @Override
    public Object visitBlockPassNode(org.jrubyparser.ast.BlockPassNode node) {
        return unimplemented(node);
    }

    @Override
    public Object visitBreakNode(org.jrubyparser.ast.BreakNode node) {
        final SourceSection sourceSection = translate(node.getPosition());

        RubyNode resultNode;

        if (node.getValueNode() == null) {
            resultNode = new NilNode(context, sourceSection);
        } else {
            resultNode = (RubyNode) node.getValueNode().accept(this);
        }

        return new BreakNode(context, sourceSection, resultNode);
    }

    @Override
    public Object visitCallNode(org.jrubyparser.ast.CallNode node) {
        return visitCallNodeExtraArgument(node, null);
    }

    /**
     * See translateDummyAssignment to understand what this is for.
     */
    public RubyNode visitCallNodeExtraArgument(org.jrubyparser.ast.CallNode node, RubyNode extraArgument) {
        final SourceSection sourceSection = translate(node.getPosition());

        final RubyNode receiverTranslated = (RubyNode) node.getReceiver().accept(this);

        org.jrubyparser.ast.Node args = node.getArgs();
        org.jrubyparser.ast.Node block = node.getIter();

        if (block == null && args instanceof org.jrubyparser.ast.IterNode) {
            final org.jrubyparser.ast.Node temp = args;
            args = block;
            block = temp;
        }

        final ArgumentsAndBlockTranslation argumentsAndBlock = translateArgumentsAndBlock(sourceSection, block, args, extraArgument);

        RubyNode translated = new CallNode(context, sourceSection, node.getName(), receiverTranslated, argumentsAndBlock.getBlock(), argumentsAndBlock.isSplatted(), argumentsAndBlock.getArguments());

        return instrumenter.instrumentAsCall(translated, node.getName());
    }

    protected class ArgumentsAndBlockTranslation {

        private final RubyNode block;
        private final RubyNode[] arguments;
        private final boolean isSplatted;

        public ArgumentsAndBlockTranslation(RubyNode block, RubyNode[] arguments, boolean isSplatted) {
            super();
            this.block = block;
            this.arguments = arguments;
            this.isSplatted = isSplatted;
        }

        public RubyNode getBlock() {
            return block;
        }

        public RubyNode[] getArguments() {
            return arguments;
        }

        public boolean isSplatted() {
            return isSplatted;
        }

    }

    protected ArgumentsAndBlockTranslation translateArgumentsAndBlock(SourceSection sourceSection, org.jrubyparser.ast.Node iterNode, org.jrubyparser.ast.Node argsNode, RubyNode extraArgument) {
        assert !(argsNode instanceof org.jrubyparser.ast.IterNode);

        final List<org.jrubyparser.ast.Node> arguments = new ArrayList<>();
        org.jrubyparser.ast.Node blockPassNode = null;

        boolean isSplatted = false;

        if (argsNode instanceof org.jrubyparser.ast.ListNode) {
            arguments.addAll(((org.jrubyparser.ast.ListNode) argsNode).childNodes());
        } else if (argsNode instanceof org.jrubyparser.ast.BlockPassNode) {
            final org.jrubyparser.ast.BlockPassNode blockPass = (org.jrubyparser.ast.BlockPassNode) argsNode;

            final org.jrubyparser.ast.Node blockPassArgs = blockPass.getArgs();

            if (blockPassArgs instanceof org.jrubyparser.ast.ListNode) {
                arguments.addAll(((org.jrubyparser.ast.ListNode) blockPassArgs).childNodes());
            } else if (blockPassArgs instanceof org.jrubyparser.ast.ArgsCatNode) {
                arguments.add(blockPassArgs);
            } else if (blockPassArgs != null) {
                throw new UnsupportedOperationException("Don't know how to block pass " + blockPassArgs);
            }

            blockPassNode = blockPass.getBody();
        } else if (argsNode instanceof org.jrubyparser.ast.SplatNode) {
            isSplatted = true;
            arguments.add(argsNode);
        } else if (argsNode instanceof org.jrubyparser.ast.ArgsCatNode) {
            isSplatted = true;
            arguments.add(argsNode);
        } else if (argsNode != null) {
            isSplatted = true;
            arguments.add(argsNode);
        }

        RubyNode blockTranslated;

        if (blockPassNode != null && iterNode != null) {
            throw new UnsupportedOperationException("Don't know how to pass both an block and a block-pass argument");
        } else if (iterNode != null) {
            blockTranslated = (BlockDefinitionNode) iterNode.accept(this);
        } else if (blockPassNode != null) {
            blockTranslated = ProcCastNodeFactory.create(context, sourceSection, (RubyNode) blockPassNode.accept(this));
        } else {
            blockTranslated = null;
        }

        final List<RubyNode> argumentsTranslated = new ArrayList<>();

        for (org.jrubyparser.ast.Node argument : arguments) {
            argumentsTranslated.add((RubyNode) argument.accept(this));
        }

        if (extraArgument != null) {
            argumentsTranslated.add(extraArgument);
        }

        final RubyNode[] argumentsTranslatedArray = argumentsTranslated.toArray(new RubyNode[argumentsTranslated.size()]);

        return new ArgumentsAndBlockTranslation(blockTranslated, argumentsTranslatedArray, isSplatted);
    }

    @Override
    public Object visitCaseNode(org.jrubyparser.ast.CaseNode node) {
        final SourceSection sourceSection = translate(node.getPosition());

        RubyNode elseNode;

        if (node.getElse() != null) {
            elseNode = (RubyNode) node.getElse().accept(this);
        } else {
            elseNode = new NilNode(context, sourceSection);
        }

        /*
         * There are two sorts of case - one compares a list of expressions against a value, the
         * other just checks a list of expressions for truth.
         */

        if (node.getCase() != null) {
            // Evaluate the case expression and store it in a local

            final String tempName = environment.allocateLocalTemp();

            final RubyNode readTemp = environment.findLocalVarNode(tempName, sourceSection);

            final RubyNode assignTemp = ((ReadNode) readTemp).makeWriteNode((RubyNode) node.getCase().accept(this));

            /*
             * Build an if expression from the whens and else. Work backwards because the first if
             * contains all the others in its else clause.
             */

            for (int n = node.getCases().size() - 1; n >= 0; n--) {
                final org.jrubyparser.ast.WhenNode when = (org.jrubyparser.ast.WhenNode) node.getCases().get(n);

                // Make a condition from the one or more expressions combined in an or expression

                final List<org.jrubyparser.ast.Node> expressions;

                if (when.getExpression() instanceof org.jrubyparser.ast.ListNode) {
                    expressions = ((org.jrubyparser.ast.ListNode) when.getExpression()).childNodes();
                } else {
                    expressions = Arrays.asList(when.getExpression());
                }

                final List<RubyNode> comparisons = new ArrayList<>();

                for (org.jrubyparser.ast.Node expressionNode : expressions) {
                    final RubyNode rubyExpression = (RubyNode) expressionNode.accept(this);

                    final CallNode comparison = new CallNode(context, sourceSection, "===", rubyExpression, null, false, new RubyNode[]{environment.findLocalVarNode(tempName, sourceSection)});

                    comparisons.add(comparison);
                }

                RubyNode conditionNode = comparisons.get(comparisons.size() - 1);

                // As with the if nodes, we work backwards to make it left associative

                for (int i = comparisons.size() - 2; i >= 0; i--) {
                    conditionNode = OrNodeFactory.create(context, sourceSection, comparisons.get(i), conditionNode);
                }

                // Create the if node

                final BooleanCastNode conditionCastNode = BooleanCastNodeFactory.create(context, sourceSection, conditionNode);

                RubyNode thenNode;

                if (when.getBody() == null) {
                    thenNode = new NilNode(context, sourceSection);
                } else {
                    thenNode = (RubyNode) when.getBody().accept(this);
                }

                final IfNode ifNode = new IfNode(context, sourceSection, conditionCastNode, thenNode, elseNode);

                // This if becomes the else for the next if

                elseNode = ifNode;
            }

            final RubyNode ifNode = elseNode;

            // A top-level block assigns the temp then runs the if

            return new SequenceNode(context, sourceSection, assignTemp, ifNode);
        } else {
            for (int n = node.getCases().size() - 1; n >= 0; n--) {
                final org.jrubyparser.ast.WhenNode when = (org.jrubyparser.ast.WhenNode) node.getCases().get(n);

                // Make a condition from the one or more expressions combined in an or expression

                final List<org.jrubyparser.ast.Node> expressions;

                if (when.getExpression() instanceof org.jrubyparser.ast.ListNode) {
                    expressions = ((org.jrubyparser.ast.ListNode) when.getExpression()).childNodes();
                } else {
                    expressions = Arrays.asList(when.getExpression());
                }

                final List<RubyNode> tests = new ArrayList<>();

                for (org.jrubyparser.ast.Node expressionNode : expressions) {
                    final RubyNode rubyExpression = (RubyNode) expressionNode.accept(this);
                    tests.add(rubyExpression);
                }

                RubyNode conditionNode = tests.get(tests.size() - 1);

                // As with the if nodes, we work backwards to make it left associative

                for (int i = tests.size() - 2; i >= 0; i--) {
                    conditionNode = OrNodeFactory.create(context, sourceSection, tests.get(i), conditionNode);
                }

                // Create the if node

                final BooleanCastNode conditionCastNode = BooleanCastNodeFactory.create(context, sourceSection, conditionNode);

                final RubyNode thenNode = (RubyNode) when.getBody().accept(this);

                final IfNode ifNode = new IfNode(context, sourceSection, conditionCastNode, thenNode, elseNode);

                // This if becomes the else for the next if

                elseNode = ifNode;
            }

            return elseNode;
        }
    }

    @Override
    public Object visitClassNode(org.jrubyparser.ast.ClassNode node) {
        final SourceSection sourceSection = translate(node.getPosition());

        final String name = node.getCPath().getName();

        final TranslatorEnvironment newEnvironment = new TranslatorEnvironment(context, environment, environment.getParser(), environment.getParser().allocateReturnID(), true, true,
                        new UniqueMethodIdentifier());
        final ModuleTranslator classTranslator = new ModuleTranslator(context, this, newEnvironment, source);

        final MethodDefinitionNode definitionMethod = classTranslator.compileClassNode(node.getPosition(), node.getCPath().getName(), node.getBody());

        /*
         * See my note in visitDefnNode about where the class gets defined - the same applies here.
         */

        RubyNode superClass;

        if (node.getSuper() != null) {
            superClass = (RubyNode) node.getSuper().accept(this);
        } else {
            superClass = new ObjectLiteralNode(context, sourceSection, context.getCoreLibrary().getObjectClass());
        }

        final DefineOrGetClassNode defineOrGetClass = new DefineOrGetClassNode(context, sourceSection, name, getModuleToDefineModulesIn(sourceSection), superClass);

        return new OpenModuleNode(context, sourceSection, defineOrGetClass, definitionMethod);
    }

    protected RubyNode getModuleToDefineModulesIn(SourceSection sourceSection) {
        return new ClassNode(context, sourceSection, new SelfNode(context, sourceSection));
    }

    @Override
    public Object visitClassVarAsgnNode(org.jrubyparser.ast.ClassVarAsgnNode node) {
        final SourceSection sourceSection = translate(node.getPosition());

        final RubyNode receiver = new ClassNode(context, sourceSection, new SelfNode(context, sourceSection));

        final RubyNode rhs = (RubyNode) node.getValue().accept(this);

        return new WriteClassVariableNode(context, sourceSection, node.getName(), receiver, rhs);
    }

    @Override
    public Object visitClassVarDeclNode(org.jrubyparser.ast.ClassVarDeclNode node) {
        return unimplemented(node);
    }

    @Override
    public Object visitClassVarNode(org.jrubyparser.ast.ClassVarNode node) {
        final SourceSection sourceSection = translate(node.getPosition());
        return new ReadClassVariableNode(context, sourceSection, node.getName(), new SelfNode(context, sourceSection));
    }

    @Override
    public Object visitColon2Node(org.jrubyparser.ast.Colon2Node node) {
        final RubyNode lhs = (RubyNode) node.getLeftNode().accept(this);

        return new UninitializedReadConstantNode(context, translate(node.getPosition()), node.getName(), lhs);
    }

    @Override
    public Object visitColon3Node(org.jrubyparser.ast.Colon3Node node) {
        // Colon3 means the root namespace, as in ::Foo

        final SourceSection sourceSection = translate(node.getPosition());

        final ObjectLiteralNode root = new ObjectLiteralNode(context, sourceSection, context.getCoreLibrary().getMainObject());

        return new UninitializedReadConstantNode(context, sourceSection, node.getName(), root);
    }

    @Override
    public Object visitConstDeclNode(org.jrubyparser.ast.ConstDeclNode node) {
        final SourceSection sourceSection = translate(node.getPosition());

        final ClassNode classNode = new ClassNode(context, sourceSection, new SelfNode(context, sourceSection));

        return new WriteConstantNode(context, sourceSection, node.getName(), classNode, (RubyNode) node.getValue().accept(this));
    }

    @Override
    public Object visitConstNode(org.jrubyparser.ast.ConstNode node) {
        final SourceSection sourceSection = translate(node.getPosition());

        return new UninitializedReadConstantNode(context, sourceSection, node.getName(), new SelfNode(context, sourceSection));
    }

    @Override
    public Object visitDAsgnNode(org.jrubyparser.ast.DAsgnNode node) {
        return new org.jrubyparser.ast.LocalAsgnNode(node.getPosition(), node.getName(), node.getDepth(), node.getValue()).accept(this);
    }

    @Override
    public Object visitDRegxNode(org.jrubyparser.ast.DRegexpNode node) {
        SourceSection sourceSection = translate(node.getPosition());

        final RubyNode stringNode = translateInterpolatedString(sourceSection, node.childNodes());

        return StringToRegexpNodeFactory.create(context, sourceSection, stringNode);
    }

    @Override
    public Object visitDStrNode(org.jrubyparser.ast.DStrNode node) {
        return translateInterpolatedString(translate(node.getPosition()), node.childNodes());
    }

    @Override
    public Object visitDSymbolNode(org.jrubyparser.ast.DSymbolNode node) {
        SourceSection sourceSection = translate(node.getPosition());

        final RubyNode stringNode = translateInterpolatedString(sourceSection, node.childNodes());

        return StringToSymbolNodeFactory.create(context, sourceSection, stringNode);
    }

    private RubyNode translateInterpolatedString(SourceSection sourceSection, List<org.jrubyparser.ast.Node> childNodes) {
        final List<RubyNode> children = new ArrayList<>();

        for (org.jrubyparser.ast.Node child : childNodes) {
            children.add((RubyNode) child.accept(this));
        }

        return new InterpolatedStringNode(context, sourceSection, children.toArray(new RubyNode[children.size()]));
    }

    @Override
    public Object visitDVarNode(org.jrubyparser.ast.DVarNode node) {
        RubyNode readNode = environment.findLocalVarNode(node.getName(), translate(node.getPosition()));

        if (readNode == null) {
            context.implementationMessage("can't find variable %s at %s, using noop", node.getName(), node.getPosition());
            readNode = new NilNode(context, translate(node.getPosition()));
        }

        return readNode;
    }

    @Override
    public Object visitDXStrNode(org.jrubyparser.ast.DXStrNode node) {
        SourceSection sourceSection = translate(node.getPosition());

        final RubyNode string = translateInterpolatedString(sourceSection, node.childNodes());

        return new SystemNode(context, sourceSection, string);
    }

    @Override
    public Object visitDefinedNode(org.jrubyparser.ast.DefinedNode node) {
        final SourceSection sourceSection = translate(node.getPosition());

        org.jrubyparser.ast.Node expressionNode = node.getExpression();

        while (expressionNode instanceof org.jrubyparser.ast.NewlineNode) {
            expressionNode = ((org.jrubyparser.ast.NewlineNode) expressionNode).getNextNode();
        }

        final String name = nodeDefinedNames.get(expressionNode.getClass());

        if (name != null) {
            final StringLiteralNode literal = new StringLiteralNode(context, sourceSection, name);
            return literal;
        }

        return new DefinedNode(context, sourceSection, (RubyNode) node.getExpression().accept(this));
    }

    @Override
    public Object visitDefnNode(org.jrubyparser.ast.DefnNode node) {
        final SourceSection sourceSection = translate(node.getPosition());
        final ClassNode classNode = new ClassNode(context, sourceSection, new SelfNode(context, sourceSection));
        return translateMethodDefinition(sourceSection, classNode, node.getName(), node.getArgs(), node.getBody());
    }

    @Override
    public Object visitDefsNode(org.jrubyparser.ast.DefsNode node) {
        final SourceSection sourceSection = translate(node.getPosition());

        final RubyNode objectNode = (RubyNode) node.getReceiver().accept(this);

        final SingletonClassNode singletonClassNode = new SingletonClassNode(context, sourceSection, objectNode);

        return translateMethodDefinition(sourceSection, singletonClassNode, node.getName(), node.getArgs(), node.getBody());
    }

    private RubyNode translateMethodDefinition(SourceSection sourceSection, RubyNode classNode, String methodName, org.jrubyparser.ast.ArgsNode argsNode, org.jrubyparser.ast.Node bodyNode) {
        final TranslatorEnvironment newEnvironment = new TranslatorEnvironment(context, environment, environment.getParser(), environment.getParser().allocateReturnID(), true, true,
                        new UniqueMethodIdentifier());

        // ownScopeForAssignments is the same for the defined method as the current one.

        final MethodTranslator methodCompiler = new MethodTranslator(context, this, newEnvironment, false, source);

        final MethodDefinitionNode functionExprNode = methodCompiler.compileFunctionNode(sourceSection, methodName, argsNode, bodyNode);

        /*
         * In the top-level, methods are defined in the class of the main object. This is
         * counter-intuitive - I would have expected them to be defined in the singleton class.
         * Apparently this is a design decision to make top-level methods sort of global.
         * 
         * http://stackoverflow.com/questions/1761148/where-are-methods-defined-at-the-ruby-top-level
         */

        return new AddMethodNode(context, sourceSection, classNode, functionExprNode);
    }

    @Override
    public Object visitDotNode(org.jrubyparser.ast.DotNode node) {
        final RubyNode begin = (RubyNode) node.getBegin().accept(this);
        final RubyNode end = (RubyNode) node.getEnd().accept(this);
        SourceSection sourceSection = translate(node.getPosition());

        if (begin instanceof FixnumLiteralNode && end instanceof FixnumLiteralNode) {
            final int beginValue = ((FixnumLiteralNode) begin).getValue();
            final int endValue = ((FixnumLiteralNode) end).getValue();

            return new ObjectLiteralNode(context, sourceSection, new FixnumRange(context.getCoreLibrary().getRangeClass(), beginValue, endValue, node.isExclusive()));
        }
        // See RangeNode for why there is a node specifically for creating this one type
        return RangeLiteralNodeFactory.create(context, sourceSection, node.isExclusive(), begin, end);
    }

    @Override
    public Object visitEncodingNode(org.jrubyparser.ast.EncodingNode node) {
        return unimplemented(node);
    }

    @Override
    public Object visitEnsureNode(org.jrubyparser.ast.EnsureNode node) {
        final RubyNode tryPart = (RubyNode) node.getBody().accept(this);
        final RubyNode ensurePart = (RubyNode) node.getEnsure().accept(this);
        return new EnsureNode(context, translate(node.getPosition()), tryPart, ensurePart);
    }

    @Override
    public Object visitEvStrNode(org.jrubyparser.ast.EvStrNode node) {
        return node.getBody().accept(this);
    }

    @Override
    public Object visitFCallNode(org.jrubyparser.ast.FCallNode node) {
        final org.jrubyparser.ast.Node receiver = new org.jrubyparser.ast.SelfNode(node.getPosition());
        final org.jrubyparser.ast.Node callNode = new org.jrubyparser.ast.CallNode(node.getPosition(), receiver, node.getName(), node.getArgs(), node.getIter());

        return callNode.accept(this);
    }

    @Override
    public Object visitFalseNode(org.jrubyparser.ast.FalseNode node) {
        return new BooleanLiteralNode(context, translate(node.getPosition()), false);
    }

    @Override
    public Object visitFixnumNode(org.jrubyparser.ast.FixnumNode node) {
        final long value = node.getValue();

        if (value >= RubyFixnum.MIN_VALUE && value <= RubyFixnum.MAX_VALUE) {
            return new FixnumLiteralNode(context, translate(node.getPosition()), (int) value);
        }
        return new BignumLiteralNode(context, translate(node.getPosition()), BigInteger.valueOf(value));
    }

    @Override
    public Object visitFlipNode(org.jrubyparser.ast.FlipNode node) {
        final SourceSection sourceSection = translate(node.getPosition());

        final RubyNode begin = (RubyNode) node.getBegin().accept(this);
        final RubyNode end = (RubyNode) node.getEnd().accept(this);

        final BooleanCastNode beginCast = BooleanCastNodeFactory.create(context, sourceSection, begin);
        final BooleanCastNode endCast = BooleanCastNodeFactory.create(context, sourceSection, end);
        final FlipFlopStateNode stateNode = createFlipFlopState(sourceSection, 0);

        return new FlipFlopNode(context, sourceSection, beginCast, endCast, stateNode, node.isExclusive());
    }

    protected FlipFlopStateNode createFlipFlopState(SourceSection sourceSection, int depth) {
        final FrameSlot frameSlot = environment.declareVar(environment.allocateLocalTemp());
        environment.getFlipFlopStates().add(frameSlot);

        if (depth == 0) {
            return new LocalFlipFlopStateNode(sourceSection, frameSlot);
        } else {
            return new LevelFlipFlopStateNode(sourceSection, depth, frameSlot);
        }
    }

    @Override
    public Object visitFloatNode(org.jrubyparser.ast.FloatNode node) {
        return new FloatLiteralNode(context, translate(node.getPosition()), node.getValue());
    }

    @Override
    public Object visitForNode(org.jrubyparser.ast.ForNode node) {
        /**
         * A Ruby for-loop, such as:
         * 
         * <pre>
         * for x in y
         *     z = x
         *     puts z
         * end
         * </pre>
         * 
         * naively desugars to:
         * 
         * <pre>
         * y.each do |x|
         *     z = x
         *     puts z
         * end
         * </pre>
         * 
         * The main difference is that z is always going to be local to the scope outside the block,
         * so it's a bit more like:
         * 
         * <pre>
         * z = nil unless z is already defined
         * y.each do |x|
         *    z = x
         *    puts x
         * end
         * </pre>
         * 
         * Which forces z to be defined in the correct scope. The parser already correctly calls z a
         * local, but then that causes us a problem as if we're going to translate to a block we
         * need a formal parameter - not a local variable. My solution to this is to add a
         * temporary:
         * 
         * <pre>
         * z = nil unless z is already defined
         * y.each do |temp|
         *    x = temp
         *    z = x
         *    puts x
         * end
         * </pre>
         * 
         * We also need that temp because the expression assigned in the for could be index
         * assignment, multiple assignment, or whatever:
         * 
         * <pre>
         * for x[0] in y
         *     z = x[0]
         *     puts z
         * end
         * </pre>
         * 
         * http://blog.grayproductions.net/articles/the_evils_of_the_for_loop
         * http://stackoverflow.com/questions/3294509/for-vs-each-in-ruby
         * 
         * The other complication is that normal locals should be defined in the enclosing scope,
         * unlike a normal block. We do that by setting a flag on this translator object when we
         * visit the new iter, translatingForStatement, which we recognise when visiting an iter
         * node.
         * 
         * Finally, note that JRuby's terminology is strange here. Normally 'iter' is a different
         * term for a block. Here, JRuby calls the object being iterated over the 'iter'.
         */

        final String temp = environment.allocateLocalTemp();

        final org.jrubyparser.ast.Node receiver = node.getIter();

        /*
         * The x in for x in ... is like the nodes in multiple assignment - it has a dummy RHS which
         * we need to replace with our temp. Just like in multiple assignment this is really awkward
         * with the JRuby AST.
         */

        final org.jrubyparser.ast.LocalVarNode readTemp = new org.jrubyparser.ast.LocalVarNode(node.getPosition(), 0, temp);
        final org.jrubyparser.ast.Node forVar = node.getVar();
        final org.jrubyparser.ast.Node assignTemp = setRHS(forVar, readTemp);

        final org.jrubyparser.ast.BlockNode bodyWithTempAssign = new org.jrubyparser.ast.BlockNode(node.getPosition());
        bodyWithTempAssign.add(assignTemp);
        bodyWithTempAssign.add(node.getBody());

        final org.jrubyparser.ast.ArgumentNode blockVar = new org.jrubyparser.ast.ArgumentNode(node.getPosition(), temp);
        final org.jrubyparser.ast.ListNode blockArgsPre = new org.jrubyparser.ast.ListNode(node.getPosition(), blockVar);
        final org.jrubyparser.ast.ArgsNode blockArgs = new org.jrubyparser.ast.ArgsNode(node.getPosition(), blockArgsPre, null, null, null, null, null, null);
        final org.jrubyparser.ast.IterNode block = new org.jrubyparser.ast.IterNode(node.getPosition(), blockArgs, node.getScope(), bodyWithTempAssign);

        final org.jrubyparser.ast.CallNode callNode = new org.jrubyparser.ast.CallNode(node.getPosition(), receiver, "each", null, block);

        translatingForStatement = true;
        final RubyNode translated = (RubyNode) callNode.accept(this);
        translatingForStatement = false;

        return translated;
    }

    private static org.jrubyparser.ast.Node setRHS(org.jrubyparser.ast.Node node, org.jrubyparser.ast.Node rhs) {
        if (node instanceof org.jrubyparser.ast.LocalAsgnNode) {
            final org.jrubyparser.ast.LocalAsgnNode localAsgnNode = (org.jrubyparser.ast.LocalAsgnNode) node;
            return new org.jrubyparser.ast.LocalAsgnNode(node.getPosition(), localAsgnNode.getName(), 0, rhs);
        } else if (node instanceof org.jrubyparser.ast.DAsgnNode) {
            final org.jrubyparser.ast.DAsgnNode dAsgnNode = (org.jrubyparser.ast.DAsgnNode) node;
            return new org.jrubyparser.ast.DAsgnNode(node.getPosition(), dAsgnNode.getName(), 0, rhs);
        } else if (node instanceof org.jrubyparser.ast.MultipleAsgnNode) {
            final org.jrubyparser.ast.MultipleAsgnNode multAsgnNode = (org.jrubyparser.ast.MultipleAsgnNode) node;
            return new org.jrubyparser.ast.MultipleAsgnNode(node.getPosition(), multAsgnNode.getPre(), multAsgnNode.getRest(), multAsgnNode.getPost());
        } else if (node instanceof org.jrubyparser.ast.InstAsgnNode) {
            final org.jrubyparser.ast.InstAsgnNode instAsgnNode = (org.jrubyparser.ast.InstAsgnNode) node;
            return new org.jrubyparser.ast.InstAsgnNode(node.getPosition(), instAsgnNode.getName(), rhs);
        } else if (node instanceof org.jrubyparser.ast.ClassVarAsgnNode) {
            final org.jrubyparser.ast.ClassVarAsgnNode instAsgnNode = (org.jrubyparser.ast.ClassVarAsgnNode) node;
            return new org.jrubyparser.ast.ClassVarAsgnNode(node.getPosition(), instAsgnNode.getName(), rhs);
        } else if (node instanceof org.jrubyparser.ast.ConstDeclNode) {
            final org.jrubyparser.ast.ConstDeclNode constDeclNode = (org.jrubyparser.ast.ConstDeclNode) node;
            return new org.jrubyparser.ast.ConstDeclNode(node.getPosition(), constDeclNode.getName(), (org.jrubyparser.ast.INameNode) constDeclNode.getConstNode(), rhs);
        } else {
            throw new UnsupportedOperationException("Don't know how to set the RHS of a " + node.getClass().getName());
        }
    }

    @Override
    public Object visitGlobalAsgnNode(org.jrubyparser.ast.GlobalAsgnNode node) {
        final SourceSection sourceSection = translate(node.getPosition());

        final String name = "$" + node.getName();
        final RubyNode rhs = (RubyNode) node.getValue().accept(this);

        if (FRAME_LOCAL_GLOBAL_VARIABLES.contains(name)) {
            context.implementationMessage("Assigning to frame local global variables not implemented at %s", node.getPosition());

            return rhs;
        } else {
            final ObjectLiteralNode globalVariablesObjectNode = new ObjectLiteralNode(context, sourceSection, context.getCoreLibrary().getGlobalVariablesObject());

            return new UninitializedWriteInstanceVariableNode(context, sourceSection, name, globalVariablesObjectNode, rhs);
        }
    }

    @Override
    public Object visitGlobalVarNode(org.jrubyparser.ast.GlobalVarNode node) {
        final String name = "$" + node.getName();
        final SourceSection sourceSection = translate(node.getPosition());

        if (FRAME_LOCAL_GLOBAL_VARIABLES.contains(name)) {
            // Assignment is implicit for many of these, so we need to declare when we use

            environment.declareVar(name);

            final RubyNode readNode = environment.findLocalVarNode(name, sourceSection);

            return readNode;
        } else {
            final ObjectLiteralNode globalVariablesObjectNode = new ObjectLiteralNode(context, sourceSection, context.getCoreLibrary().getGlobalVariablesObject());

            return new UninitializedReadInstanceVariableNode(context, sourceSection, name, globalVariablesObjectNode);
        }
    }

    @Override
    public Object visitHashNode(org.jrubyparser.ast.HashNode node) {
        final SourceSection sourceSection = translate(node.getPosition());

        final List<RubyNode> keys = new ArrayList<>();
        final List<RubyNode> values = new ArrayList<>();

        final org.jrubyparser.ast.ListNode entries = node.getListNode();

        assert entries.size() % 2 == 0;

        for (int n = 0; n < entries.size(); n += 2) {
            if (entries.get(n) == null) {
                final NilNode nilNode = new NilNode(context, sourceSection);
                keys.add(nilNode);
            } else {
                keys.add((RubyNode) entries.get(n).accept(this));
            }

            if (entries.get(n + 1) == null) {
                final NilNode nilNode = new NilNode(context, sourceSection);
                values.add(nilNode);
            } else {
                values.add((RubyNode) entries.get(n + 1).accept(this));
            }
        }

        return new HashLiteralNode(translate(node.getPosition()), keys.toArray(new RubyNode[keys.size()]), values.toArray(new RubyNode[values.size()]), context);
    }

    @Override
    public Object visitIfNode(org.jrubyparser.ast.IfNode node) {
        final SourceSection sourceSection = translate(node.getPosition());

        org.jrubyparser.ast.Node thenBody = node.getThenBody();

        if (thenBody == null) {
            thenBody = new org.jrubyparser.ast.NilNode(node.getPosition());
        }

        org.jrubyparser.ast.Node elseBody = node.getElseBody();

        if (elseBody == null) {
            elseBody = new org.jrubyparser.ast.NilNode(node.getPosition());
        }

        RubyNode condition;

        if (node.getCondition() == null) {
            condition = new NilNode(context, sourceSection);
        } else {
            condition = (RubyNode) node.getCondition().accept(this);
        }

        final BooleanCastNode conditionCast = BooleanCastNodeFactory.create(context, sourceSection, condition);

        final RubyNode thenBodyTranslated = (RubyNode) thenBody.accept(this);
        final RubyNode elseBodyTranslated = (RubyNode) elseBody.accept(this);

        return new IfNode(context, sourceSection, conditionCast, thenBodyTranslated, elseBodyTranslated);
    }

    @Override
    public Object visitInstAsgnNode(org.jrubyparser.ast.InstAsgnNode node) {
        final SourceSection sourceSection = translate(node.getPosition());
        final String nameWithoutSigil = node.getName();

        final RubyNode receiver = new SelfNode(context, sourceSection);

        RubyNode rhs;

        if (node.getValue() == null) {
            rhs = new DeadNode(context, sourceSection);
        } else {
            rhs = (RubyNode) node.getValue().accept(this);
        }

        return new UninitializedWriteInstanceVariableNode(context, sourceSection, nameWithoutSigil, receiver, rhs);
    }

    @Override
    public Object visitInstVarNode(org.jrubyparser.ast.InstVarNode node) {
        final SourceSection sourceSection = translate(node.getPosition());
        final String nameWithoutSigil = node.getName();

        final RubyNode receiver = new SelfNode(context, sourceSection);

        return new UninitializedReadInstanceVariableNode(context, sourceSection, nameWithoutSigil, receiver);
    }

    @Override
    public Object visitIterNode(org.jrubyparser.ast.IterNode node) {
        /*
         * In a block we do NOT allocate a new return ID - returns will return from the method, not
         * the block (in the general case, see Proc and the difference between Proc and Lambda for
         * specifics).
         */

        final boolean hasOwnScope = !translatingForStatement;

        // Unset this flag for any for any blocks within the for statement's body

        translatingForStatement = false;

        final TranslatorEnvironment newEnvironment = new TranslatorEnvironment(context, environment, environment.getParser(), environment.getReturnID(), hasOwnScope, false,
                        new UniqueMethodIdentifier());
        final MethodTranslator methodCompiler = new MethodTranslator(context, this, newEnvironment, true, source);

        org.jrubyparser.ast.ArgsNode argsNode;

        if (node.getVar() instanceof org.jrubyparser.ast.ArgsNode) {
            argsNode = (org.jrubyparser.ast.ArgsNode) node.getVar();
        } else if (node.getVar() instanceof org.jrubyparser.ast.DAsgnNode) {
            final org.jrubyparser.ast.ArgumentNode arg = new org.jrubyparser.ast.ArgumentNode(node.getPosition(), ((org.jrubyparser.ast.DAsgnNode) node.getVar()).getName());
            final org.jrubyparser.ast.ListNode preArgs = new org.jrubyparser.ast.ArrayNode(node.getPosition(), arg);
            argsNode = new org.jrubyparser.ast.ArgsNode(node.getPosition(), preArgs, null, null, null, null, null, null);
        } else if (node.getVar() == null) {
            argsNode = null;
        } else {
            throw new UnsupportedOperationException();
        }

        return methodCompiler.compileFunctionNode(translate(node.getPosition()), "(block)", argsNode, node.getBody());
    }

    @Override
    public Object visitLiteralNode(org.jrubyparser.ast.LiteralNode node) {
        return unimplemented(node);
    }

    @Override
    public Object visitLocalAsgnNode(org.jrubyparser.ast.LocalAsgnNode node) {
        final SourceSection sourceSection = translate(node.getPosition());

        if (environment.getNeverAssignInParentScope()) {
            environment.declareVar(node.getName());
        }

        RubyNode lhs = environment.findLocalVarNode(node.getName(), sourceSection);

        if (lhs == null) {
            if (environment.hasOwnScopeForAssignments()) {
                environment.declareVar(node.getName());
            }

            TranslatorEnvironment environmentToDeclareIn = environment;

            while (!environmentToDeclareIn.hasOwnScopeForAssignments()) {
                environmentToDeclareIn = environmentToDeclareIn.getParent();
            }

            environmentToDeclareIn.declareVar(node.getName());
            lhs = environment.findLocalVarNode(node.getName(), sourceSection);

            if (lhs == null) {
                throw new RuntimeException("shoudln't be here");
            }
        }

        RubyNode rhs;

        if (node.getValue() == null) {
            rhs = new DeadNode(context, sourceSection);
        } else {
            rhs = (RubyNode) node.getValue().accept(this);
        }

        RubyNode translated = ((ReadNode) lhs).makeWriteNode(rhs);

        final UniqueMethodIdentifier methodIdentifier = environment.findMethodForLocalVar(node.getName());

        return instrumenter.instrumentAsLocalAssignment(translated, methodIdentifier, node.getName());
    }

    @Override
    public Object visitLocalVarNode(org.jrubyparser.ast.LocalVarNode node) {
        final SourceSection sourceSection = translate(node.getPosition());

        final String name = node.getName();

        RubyNode readNode = environment.findLocalVarNode(name, sourceSection);

        if (readNode == null) {
            context.implementationMessage("Local variable found by parser but not by translator - " + name + " at " + node.getPosition());
            readNode = environment.findLocalVarNode(environment.allocateLocalTemp(), sourceSection);
        }

        return readNode;
    }

    @Override
    public Object visitMatch2Node(org.jrubyparser.ast.Match2Node node) {
        final org.jrubyparser.ast.Node argsNode = buildArrayNode(node.getPosition(), node.getValue());
        final org.jrubyparser.ast.Node callNode = new org.jrubyparser.ast.CallNode(node.getPosition(), node.getReceiver(), "=~", argsNode, null);
        return callNode.accept(this);
    }

    @Override
    public Object visitMatch3Node(org.jrubyparser.ast.Match3Node node) {
        final org.jrubyparser.ast.Node argsNode = buildArrayNode(node.getPosition(), node.getValue());
        final org.jrubyparser.ast.Node callNode = new org.jrubyparser.ast.CallNode(node.getPosition(), node.getReceiver(), "=~", argsNode, null);
        return callNode.accept(this);
    }

    @Override
    public Object visitMatchNode(org.jrubyparser.ast.MatchNode node) {
        return unimplemented(node);
    }

    @Override
    public Object visitModuleNode(org.jrubyparser.ast.ModuleNode node) {
        // See visitClassNode

        final SourceSection sourceSection = translate(node.getPosition());

        final String name = node.getCPath().getName();

        final TranslatorEnvironment newEnvironment = new TranslatorEnvironment(context, environment, environment.getParser(), environment.getParser().allocateReturnID(), true, true,
                        new UniqueMethodIdentifier());
        final ModuleTranslator classTranslator = new ModuleTranslator(context, this, newEnvironment, source);

        final MethodDefinitionNode definitionMethod = classTranslator.compileClassNode(node.getPosition(), node.getCPath().getName(), node.getBody());

        final DefineOrGetModuleNode defineModuleNode = new DefineOrGetModuleNode(context, sourceSection, name, getModuleToDefineModulesIn(sourceSection));

        return new OpenModuleNode(context, sourceSection, defineModuleNode, definitionMethod);
    }

    @Override
    public Object visitMultipleAsgnNode(org.jrubyparser.ast.MultipleAsgnNode node) {
        final SourceSection sourceSection = translate(node.getPosition());

        final org.jrubyparser.ast.ArrayNode preArray = (org.jrubyparser.ast.ArrayNode) node.getPre();
        final org.jrubyparser.ast.Node rhs = node.getValue();

        RubyNode rhsTranslated;

        if (rhs == null) {
            context.implementationMessage("warning: no RHS for multiple assignment - using noop");
            rhsTranslated = new NilNode(context, sourceSection);
        } else {
            rhsTranslated = (RubyNode) rhs.accept(this);
        }

        /*
         * One very common case is to do
         * 
         * a, b = c, d
         */

        if (preArray != null && node.getPost() == null && node.getRest() == null && rhsTranslated instanceof UninitialisedArrayLiteralNode &&
                        ((UninitialisedArrayLiteralNode) rhsTranslated).getValues().length == preArray.size()) {
            /*
             * We can deal with this common case be rewriting as
             * 
             * temp1 = c; temp2 = d; a = temp1; b = temp2
             * 
             * We can't just do
             * 
             * a = c; b = d
             * 
             * As we don't know if d depends on the original value of a.
             * 
             * We also need to return an array [c, d], but we make that result elidable so it isn't
             * executed if it isn't actually demanded.
             */

            final RubyNode[] rhsValues = ((UninitialisedArrayLiteralNode) rhsTranslated).getValues();
            final int assignedValuesCount = preArray.size();

            final RubyNode[] sequence = new RubyNode[assignedValuesCount * 2];

            final RubyNode[] tempValues = new RubyNode[assignedValuesCount];

            for (int n = 0; n < assignedValuesCount; n++) {
                final String tempName = environment.allocateLocalTemp();
                final RubyNode readTemp = environment.findLocalVarNode(tempName, sourceSection);
                final RubyNode assignTemp = ((ReadNode) readTemp).makeWriteNode(rhsValues[n]);
                final RubyNode assignFinalValue = translateDummyAssignment(preArray.get(n), readTemp);

                sequence[n] = assignTemp;
                sequence[assignedValuesCount + n] = assignFinalValue;

                tempValues[n] = readTemp;
            }

            final RubyNode blockNode = new SequenceNode(context, sourceSection, sequence);

            final UninitialisedArrayLiteralNode arrayNode = new UninitialisedArrayLiteralNode(context, sourceSection, tempValues);

            final ElidableResultNode elidableResult = new ElidableResultNode(context, sourceSection, blockNode, arrayNode);

            return elidableResult;
        } else if (preArray != null) {
            /*
             * The other simple case is
             * 
             * a, b, c = x
             * 
             * If x is an array, then it's
             * 
             * a[0] = x[0] etc
             * 
             * If x isn't an array then it's
             * 
             * a, b, c = [x, nil, nil]
             * 
             * Which I believe is the same effect as
             * 
             * a, b, c, = *x
             * 
             * So we insert the splat cast node, even though it isn't there.
             */

            /*
             * Create a temp for the array.
             */

            final String tempName = environment.allocateLocalTemp();

            /*
             * Create a sequence of instructions, with the first being the literal array assigned to
             * the temp.
             */

            final List<RubyNode> sequence = new ArrayList<>();

            final RubyNode splatCastNode = SplatCastNodeFactory.create(context, sourceSection, rhsTranslated);

            final RubyNode writeTemp = ((ReadNode) environment.findLocalVarNode(tempName, sourceSection)).makeWriteNode(splatCastNode);

            sequence.add(writeTemp);

            /*
             * Then index the temp array for each assignment on the LHS.
             */

            for (int n = 0; n < preArray.size(); n++) {
                final ArrayIndexNode assignedValue = ArrayIndexNodeFactory.create(context, sourceSection, n, environment.findLocalVarNode(tempName, sourceSection));

                sequence.add(translateDummyAssignment(preArray.get(n), assignedValue));
            }

            if (node.getRest() != null) {
                final ArrayRestNode assignedValue = new ArrayRestNode(context, sourceSection, preArray.size(), environment.findLocalVarNode(tempName, sourceSection));

                sequence.add(translateDummyAssignment(node.getRest(), assignedValue));
            }

            return new SequenceNode(context, sourceSection, sequence.toArray(new RubyNode[sequence.size()]));
        } else if (node.getPre() == null && node.getPost() == null && node.getRest() instanceof org.jrubyparser.ast.StarNode) {
            return rhsTranslated;
        } else if (node.getPre() == null && node.getPost() == null && node.getRest() != null && rhs != null && !(rhs instanceof org.jrubyparser.ast.ArrayNode)) {
            /*
             * *a = b
             * 
             * >= 1.8, this seems to be the same as:
             * 
             * a = *b
             */

            final RubyNode restTranslated = ((RubyNode) node.getRest().accept(this)).getNonProxyNode();

            /*
             * Sometimes rest is a corrupt write with no RHS, like in other multiple assignments,
             * and sometimes it is already a read.
             */

            ReadNode restRead;

            if (restTranslated instanceof ReadNode) {
                restRead = (ReadNode) restTranslated;
            } else if (restTranslated instanceof WriteNode) {
                restRead = (ReadNode) ((WriteNode) restTranslated).makeReadNode();
            } else {
                throw new RuntimeException("Unknown form of multiple assignment " + node + " at " + node.getPosition());
            }

            final SplatCastNode rhsSplatCast = SplatCastNodeFactory.create(context, sourceSection, rhsTranslated);

            return restRead.makeWriteNode(rhsSplatCast);
        } else if (node.getPre() == null && node.getPost() == null && node.getRest() != null && rhs != null && rhs instanceof org.jrubyparser.ast.ArrayNode) {
            /*
             * *a = [b, c]
             * 
             * This seems to be the same as:
             * 
             * a = [b, c]
             */

            final RubyNode restTranslated = ((RubyNode) node.getRest().accept(this)).getNonProxyNode();

            /*
             * Sometimes rest is a corrupt write with no RHS, like in other multiple assignments,
             * and sometimes it is already a read.
             */

            ReadNode restRead;

            if (restTranslated instanceof ReadNode) {
                restRead = (ReadNode) restTranslated;
            } else if (restTranslated instanceof WriteNode) {
                restRead = (ReadNode) ((WriteNode) restTranslated).makeReadNode();
            } else {
                throw new RuntimeException("Unknown form of multiple assignment " + node + " at " + node.getPosition());
            }

            return restRead.makeWriteNode(rhsTranslated);
        } else {
            throw new RuntimeException("Unknown form of multiple assignment " + node + " at " + node.getPosition());
        }
    }

    private RubyNode translateDummyAssignment(org.jrubyparser.ast.Node dummyAssignment, RubyNode rhs) {
        final SourceSection sourceSection = translate(dummyAssignment.getPosition());

        /*
         * This is tricky. To represent the RHS of a multiple assignment they use corrupt assignment
         * values, in some cases with no value to be assigned, and in other cases with a dummy
         * value. We can't visit them normally, as they're corrupt. We can't just modify them to
         * have our RHS, as that's a node in our AST, not theirs. We can't use a dummy value in
         * their AST because I can't add new visitors to this interface.
         */

        RubyNode translated;

        if (dummyAssignment instanceof org.jrubyparser.ast.LocalAsgnNode) {
            /*
             * They have a dummy NilImplicitNode as the RHS. Translate, convert to read, convert to
             * write which allows us to set the RHS.
             */

            final WriteNode dummyTranslated = (WriteNode) ((RubyNode) dummyAssignment.accept(this)).getNonProxyNode();
            translated = ((ReadNode) dummyTranslated.makeReadNode()).makeWriteNode(rhs);
        } else if (dummyAssignment instanceof org.jrubyparser.ast.InstAsgnNode) {
            /*
             * Same as before, just a different type of assignment.
             */

            final WriteInstanceVariableNode dummyTranslated = (WriteInstanceVariableNode) dummyAssignment.accept(this);
            translated = dummyTranslated.makeReadNode().makeWriteNode(rhs);
        } else if (dummyAssignment instanceof org.jrubyparser.ast.AttrAssignNode) {
            /*
             * They've given us an AttrAssignNode with the final argument, the assigned value,
             * missing. If we translate that we'll get foo.[]=(index), so missing the value. To
             * solve we have a special version of the visitCallNode that allows us to pass another
             * already translated argument, visitCallNodeExtraArgument. However, we initially have
             * an AttrAssignNode, so we also need a special version of that.
             */

            final org.jrubyparser.ast.AttrAssignNode dummyAttrAssignment = (org.jrubyparser.ast.AttrAssignNode) dummyAssignment;
            translated = visitAttrAssignNodeExtraArgument(dummyAttrAssignment, rhs);
        } else if (dummyAssignment instanceof org.jrubyparser.ast.DAsgnNode) {
            final RubyNode dummyTranslated = (RubyNode) dummyAssignment.accept(this);

            if (dummyTranslated.getNonProxyNode() instanceof WriteLevelVariableNode) {
                translated = ((ReadNode) ((WriteLevelVariableNode) dummyTranslated.getNonProxyNode()).makeReadNode()).makeWriteNode(rhs);
            } else {
                translated = ((ReadNode) ((WriteLocalVariableNode) dummyTranslated.getNonProxyNode()).makeReadNode()).makeWriteNode(rhs);
            }
        } else {
            translated = ((ReadNode) environment.findLocalVarNode(environment.allocateLocalTemp(), sourceSection)).makeWriteNode(rhs);
        }

        return translated;
    }

    @Override
    public Object visitNewlineNode(org.jrubyparser.ast.NewlineNode node) {
        RubyNode translated = (RubyNode) node.getNextNode().accept(this);
        return instrumenter.instrumentAsStatement(translated);
    }

    @Override
    public Object visitNextNode(org.jrubyparser.ast.NextNode node) {
        final SourceSection sourceSection = translate(node.getPosition());

        RubyNode resultNode;

        if (node.getValueNode() == null) {
            resultNode = new NilNode(context, sourceSection);
        } else {
            resultNode = (RubyNode) node.getValueNode().accept(this);
        }

        return new NextNode(context, sourceSection, resultNode);
    }

    @Override
    public Object visitNilNode(org.jrubyparser.ast.NilNode node) {
        return new NilNode(context, translate(node.getPosition()));
    }

    @Override
    public Object visitNotNode(org.jrubyparser.ast.NotNode node) {
        final SourceSection sourceSection = translate(node.getPosition());

        final BooleanCastNode booleanCastNode = BooleanCastNodeFactory.create(context, sourceSection, (RubyNode) node.getCondition().accept(this));

        return new NotNode(context, sourceSection, booleanCastNode);
    }

    @Override
    public Object visitNthRefNode(org.jrubyparser.ast.NthRefNode node) {
        final SourceSection sourceSection = translate(node.getPosition());

        final String name = "$" + node.getMatchNumber();

        RubyNode readLocal = environment.findLocalVarNode(name, sourceSection);

        if (readLocal == null) {
            environment.declareVar(name);
            readLocal = environment.findLocalVarNode(name, sourceSection);
        }

        return readLocal;
    }

    @Override
    public Object visitOpAsgnAndNode(org.jrubyparser.ast.OpAsgnAndNode node) {
        final org.jrubyparser.ast.Node lhs = node.getFirst();
        final org.jrubyparser.ast.Node rhs = node.getSecond();

        return AndNodeFactory.create(context, translate(node.getPosition()), (RubyNode) lhs.accept(this), (RubyNode) rhs.accept(this));
    }

    @Override
    public Object visitOpAsgnNode(org.jrubyparser.ast.OpAsgnNode node) {
        /*
         * We're going to de-sugar a.foo += c into a.foo = a.foo + c. Note that we can't evaluate a
         * more than once, so we put it into a temporary, and we're doing something more like:
         * 
         * temp = a; temp.foo = temp.foo + c
         */

        final String temp = environment.allocateLocalTemp();
        final org.jrubyparser.ast.Node writeReceiverToTemp = new org.jrubyparser.ast.LocalAsgnNode(node.getPosition(), temp, 0, node.getReceiver());
        final org.jrubyparser.ast.Node readReceiverFromTemp = new org.jrubyparser.ast.LocalVarNode(node.getPosition(), 0, temp);

        final org.jrubyparser.ast.Node readMethod = new org.jrubyparser.ast.CallNode(node.getPosition(), readReceiverFromTemp, node.getVariableName(), null);
        final org.jrubyparser.ast.Node operation = new org.jrubyparser.ast.CallNode(node.getPosition(), readMethod, node.getOperatorName(), buildArrayNode(node.getPosition(), node.getValue()));
        final org.jrubyparser.ast.Node writeMethod = new org.jrubyparser.ast.CallNode(node.getPosition(), readReceiverFromTemp, node.getVariableName() + "=", buildArrayNode(node.getPosition(),
                        operation));

        final org.jrubyparser.ast.BlockNode block = new org.jrubyparser.ast.BlockNode(node.getPosition());
        block.add(writeReceiverToTemp);
        block.add(writeMethod);

        return block.accept(this);
    }

    @Override
    public Object visitOpAsgnOrNode(org.jrubyparser.ast.OpAsgnOrNode node) {
        /*
         * De-sugar x ||= y into x || x = y. No repeated evaluations there so it's easy. It's also
         * basically how jruby-parser represents it already. We'll do it directly, rather than via
         * another JRuby AST node.
         */

        final org.jrubyparser.ast.Node lhs = node.getFirst();
        final org.jrubyparser.ast.Node rhs = node.getSecond();

        return OrNodeFactory.create(context, translate(node.getPosition()), (RubyNode) lhs.accept(this), (RubyNode) rhs.accept(this));
    }

    @Override
    public Object visitOpElementAsgnNode(org.jrubyparser.ast.OpElementAsgnNode node) {
        /*
         * We're going to de-sugar a[b] += c into a[b] = a[b] + c. See discussion in
         * visitOpAsgnNode.
         */

        org.jrubyparser.ast.Node index;

        if (node.getArgs() == null) {
            index = null;
        } else {
            index = node.getArgs().childNodes().get(0);
        }

        final org.jrubyparser.ast.Node operand = node.getValue();

        final String temp = environment.allocateLocalTemp();
        final org.jrubyparser.ast.Node writeArrayToTemp = new org.jrubyparser.ast.LocalAsgnNode(node.getPosition(), temp, 0, node.getReceiver());
        final org.jrubyparser.ast.Node readArrayFromTemp = new org.jrubyparser.ast.LocalVarNode(node.getPosition(), 0, temp);

        final org.jrubyparser.ast.Node arrayRead = new org.jrubyparser.ast.CallNode(node.getPosition(), readArrayFromTemp, "[]", buildArrayNode(node.getPosition(), index));

        final String op = node.getOperatorName();

        org.jrubyparser.ast.Node operation = null;

        if (op.equals("||")) {
            operation = new org.jrubyparser.ast.OrNode(node.getPosition(), arrayRead, operand);
        } else if (op.equals("&&")) {
            operation = new org.jrubyparser.ast.AndNode(node.getPosition(), arrayRead, operand);
        } else {
            operation = new org.jrubyparser.ast.CallNode(node.getPosition(), arrayRead, node.getOperatorName(), buildArrayNode(node.getPosition(), operand));
        }

        final org.jrubyparser.ast.Node arrayWrite = new org.jrubyparser.ast.CallNode(node.getPosition(), readArrayFromTemp, "[]=", buildArrayNode(node.getPosition(), index, operation));

        final org.jrubyparser.ast.BlockNode block = new org.jrubyparser.ast.BlockNode(node.getPosition());
        block.add(writeArrayToTemp);
        block.add(arrayWrite);

        return block.accept(this);
    }

    private static org.jrubyparser.ast.ArrayNode buildArrayNode(org.jrubyparser.SourcePosition sourcePosition, org.jrubyparser.ast.Node first, org.jrubyparser.ast.Node... rest) {
        if (first == null) {
            return new org.jrubyparser.ast.ArrayNode(sourcePosition);
        }

        final org.jrubyparser.ast.ArrayNode array = new org.jrubyparser.ast.ArrayNode(sourcePosition, first);

        for (org.jrubyparser.ast.Node node : rest) {
            array.add(node);
        }

        return array;
    }

    @Override
    public Object visitOrNode(org.jrubyparser.ast.OrNode node) {
        final SourceSection sourceSection = translate(node.getPosition());

        RubyNode x;

        if (node.getFirst() == null) {
            x = new NilNode(context, sourceSection);
        } else {
            x = (RubyNode) node.getFirst().accept(this);
        }

        RubyNode y;

        if (node.getSecond() == null) {
            y = new NilNode(context, sourceSection);
        } else {
            y = (RubyNode) node.getSecond().accept(this);
        }

        return OrNodeFactory.create(context, sourceSection, x, y);
    }

    @Override
    public Object visitPostExeNode(org.jrubyparser.ast.PostExeNode node) {
        return unimplemented(node);
    }

    @Override
    public Object visitPreExeNode(org.jrubyparser.ast.PreExeNode node) {
        return unimplemented(node);
    }

    @Override
    public Object visitRedoNode(org.jrubyparser.ast.RedoNode node) {
        return new RedoNode(context, translate(node.getPosition()));
    }

    @Override
    public Object visitRegexpNode(org.jrubyparser.ast.RegexpNode node) {
        RubyRegexp regexp;

        try {
            final String patternText = node.getValue();

            int flags = Pattern.MULTILINE | Pattern.UNIX_LINES;

            final org.jrubyparser.RegexpOptions options = node.getOptions();

            if (options.isIgnorecase()) {
                flags |= Pattern.CASE_INSENSITIVE;
            }

            if (options.isMultiline()) {
                // TODO(cs): isn't this the default?
                flags |= Pattern.MULTILINE;
            }

            final Pattern pattern = Pattern.compile(patternText, flags);

            regexp = new RubyRegexp(context.getCoreLibrary().getRegexpClass(), pattern);
        } catch (PatternSyntaxException e) {
            context.implementationMessage("failed to parse Ruby regexp " + node.getValue() + " as Java regexp - replacing with .");
            regexp = new RubyRegexp(context.getCoreLibrary().getRegexpClass(), ".");
        }

        final ObjectLiteralNode literalNode = new ObjectLiteralNode(context, translate(node.getPosition()), regexp);
        return literalNode;
    }

    @Override
    public Object visitRescueBodyNode(org.jrubyparser.ast.RescueBodyNode node) {
        return unimplemented(node);
    }

    @Override
    public Object visitRescueNode(org.jrubyparser.ast.RescueNode node) {
        final SourceSection sourceSection = translate(node.getPosition());

        RubyNode tryPart;

        if (node.getBody() != null) {
            tryPart = (RubyNode) node.getBody().accept(this);
        } else {
            tryPart = new NilNode(context, sourceSection);
        }

        final List<RescueNode> rescueNodes = new ArrayList<>();

        org.jrubyparser.ast.RescueBodyNode rescueBody = node.getRescue();

        while (rescueBody != null) {
            if (rescueBody.getExceptions() != null) {
                if (rescueBody.getExceptions() instanceof org.jrubyparser.ast.ArrayNode) {
                    final List<org.jrubyparser.ast.Node> exceptionNodes = ((org.jrubyparser.ast.ArrayNode) rescueBody.getExceptions()).childNodes();

                    final RubyNode[] handlingClasses = new RubyNode[exceptionNodes.size()];

                    for (int n = 0; n < handlingClasses.length; n++) {
                        handlingClasses[n] = (RubyNode) exceptionNodes.get(n).accept(this);
                    }

                    RubyNode translatedBody;

                    if (rescueBody.getBody() == null) {
                        translatedBody = new NilNode(context, sourceSection);
                    } else {
                        translatedBody = (RubyNode) rescueBody.getBody().accept(this);
                    }

                    final RescueClassesNode rescueNode = new RescueClassesNode(context, sourceSection, handlingClasses, translatedBody);
                    rescueNodes.add(rescueNode);
                } else if (rescueBody.getExceptions() instanceof org.jrubyparser.ast.SplatNode) {
                    final org.jrubyparser.ast.SplatNode splat = (org.jrubyparser.ast.SplatNode) rescueBody.getExceptions();

                    RubyNode splatTranslated;

                    if (splat.getValue() == null) {
                        splatTranslated = new NilNode(context, sourceSection);
                    } else {
                        splatTranslated = (RubyNode) splat.getValue().accept(this);
                    }

                    RubyNode bodyTranslated;

                    if (rescueBody.getBody() == null) {
                        bodyTranslated = new NilNode(context, sourceSection);
                    } else {
                        bodyTranslated = (RubyNode) rescueBody.getBody().accept(this);
                    }

                    final RescueSplatNode rescueNode = new RescueSplatNode(context, sourceSection, splatTranslated, bodyTranslated);
                    rescueNodes.add(rescueNode);
                } else {
                    unimplemented(node);
                }
            } else {
                RubyNode bodyNode;

                if (rescueBody.getBody() == null) {
                    bodyNode = new NilNode(context, sourceSection);
                } else {
                    bodyNode = (RubyNode) rescueBody.getBody().accept(this);
                }

                final RescueAnyNode rescueNode = new RescueAnyNode(context, sourceSection, bodyNode);
                rescueNodes.add(rescueNode);
            }

            rescueBody = rescueBody.getOptRescue();
        }

        RubyNode elsePart;

        if (node.getElse() != null) {
            elsePart = (RubyNode) node.getElse().accept(this);
        } else {
            elsePart = new NilNode(context, sourceSection);
        }

        return new TryNode(context, sourceSection, tryPart, rescueNodes.toArray(new RescueNode[rescueNodes.size()]), elsePart);
    }

    @Override
    public Object visitRestArgNode(org.jrubyparser.ast.RestArgNode node) {
        return unimplemented(node);
    }

    @Override
    public Object visitRetryNode(org.jrubyparser.ast.RetryNode node) {
        return new RetryNode(context, translate(node.getPosition()));
    }

    @Override
    public Object visitReturnNode(org.jrubyparser.ast.ReturnNode node) {
        final SourceSection sourceSection = translate(node.getPosition());

        RubyNode translatedChild;

        if (node.getValue() == null) {
            translatedChild = new NilNode(context, sourceSection);
        } else {
            translatedChild = (RubyNode) node.getValue().accept(this);
        }

        return new ReturnNode(context, sourceSection, environment.getReturnID(), translatedChild);
    }

    @Override
    public Object visitRootNode(org.jrubyparser.ast.RootNode node) {
        return unimplemented(node);
    }

    @Override
    public Object visitSClassNode(org.jrubyparser.ast.SClassNode node) {
        final SourceSection sourceSection = translate(node.getPosition());

        final TranslatorEnvironment newEnvironment = new TranslatorEnvironment(context, environment, environment.getParser(), environment.getParser().allocateReturnID(), true, true,
                        new UniqueMethodIdentifier());
        final ModuleTranslator classTranslator = new ModuleTranslator(context, this, newEnvironment, source);

        final MethodDefinitionNode definitionMethod = classTranslator.compileClassNode(node.getPosition(), "singleton", node.getBody());

        final RubyNode receiverNode = (RubyNode) node.getReceiver().accept(this);

        final SingletonClassNode singletonClassNode = new SingletonClassNode(context, sourceSection, receiverNode);

        return new OpenModuleNode(context, sourceSection, singletonClassNode, definitionMethod);
    }

    @Override
    public Object visitSValueNode(org.jrubyparser.ast.SValueNode node) {
        return node.getValue().accept(this);
    }

    @Override
    public Object visitSelfNode(org.jrubyparser.ast.SelfNode node) {
        return new SelfNode(context, translate(node.getPosition()));
    }

    @Override
    public Object visitSplatNode(org.jrubyparser.ast.SplatNode node) {
        final SourceSection sourceSection = translate(node.getPosition());

        RubyNode value;

        if (node.getValue() == null) {
            value = new NilNode(context, sourceSection);
        } else {
            value = (RubyNode) node.getValue().accept(this);
        }

        return SplatCastNodeFactory.create(context, sourceSection, value);
    }

    @Override
    public Object visitStrNode(org.jrubyparser.ast.StrNode node) {
        return new StringLiteralNode(context, translate(node.getPosition()), node.getValue());
    }

    @Override
    public Object visitSuperNode(org.jrubyparser.ast.SuperNode node) {
        return unimplemented(node);
    }

    @Override
    public Object visitSymbolNode(org.jrubyparser.ast.SymbolNode node) {
        return new ObjectLiteralNode(context, translate(node.getPosition()), new RubySymbol(context.getCoreLibrary().getSymbolClass(), node.getName()));
    }

    @Override
    public Object visitToAryNode(org.jrubyparser.ast.ToAryNode node) {
        return unimplemented(node);
    }

    @Override
    public Object visitTrueNode(org.jrubyparser.ast.TrueNode node) {
        return new BooleanLiteralNode(context, translate(node.getPosition()), true);
    }

    @Override
    public Object visitUndefNode(org.jrubyparser.ast.UndefNode node) {
        return unimplemented(node);
    }

    @Override
    public Object visitUntilNode(org.jrubyparser.ast.UntilNode node) {
        final SourceSection sourceSection = translate(node.getPosition());

        RubyNode condition;

        if (node.getCondition() == null) {
            condition = new NilNode(context, sourceSection);
        } else {
            condition = (RubyNode) node.getCondition().accept(this);
        }

        final BooleanCastNode conditionCast = BooleanCastNodeFactory.create(context, sourceSection, condition);
        final NotNode conditionCastNot = new NotNode(context, sourceSection, conditionCast);
        final BooleanCastNode conditionCastNotCast = BooleanCastNodeFactory.create(context, sourceSection, conditionCastNot);

        final RubyNode body = (RubyNode) node.getBody().accept(this);

        return new WhileNode(context, sourceSection, conditionCastNotCast, body);
    }

    @Override
    public Object visitVAliasNode(org.jrubyparser.ast.VAliasNode node) {
        return unimplemented(node);
    }

    @Override
    public Object visitVCallNode(org.jrubyparser.ast.VCallNode node) {
        final org.jrubyparser.ast.Node receiver = new org.jrubyparser.ast.SelfNode(node.getPosition());
        final org.jrubyparser.ast.Node args = null;
        final org.jrubyparser.ast.Node callNode = new org.jrubyparser.ast.CallNode(node.getPosition(), receiver, node.getName(), args);

        return callNode.accept(this);
    }

    @Override
    public Object visitWhenNode(org.jrubyparser.ast.WhenNode node) {
        return unimplemented(node);
    }

    @Override
    public Object visitWhileNode(org.jrubyparser.ast.WhileNode node) {
        final SourceSection sourceSection = translate(node.getPosition());

        RubyNode condition;

        if (node.getCondition() == null) {
            condition = new NilNode(context, sourceSection);
        } else {
            condition = (RubyNode) node.getCondition().accept(this);
        }

        final BooleanCastNode conditionCast = BooleanCastNodeFactory.create(context, sourceSection, condition);

        final RubyNode body = (RubyNode) node.getBody().accept(this);

        return new WhileNode(context, sourceSection, conditionCast, body);
    }

    @Override
    public Object visitXStrNode(org.jrubyparser.ast.XStrNode node) {
        SourceSection sourceSection = translate(node.getPosition());

        final StringLiteralNode literal = new StringLiteralNode(context, sourceSection, node.getValue());

        return new SystemNode(context, sourceSection, literal);
    }

    @Override
    public Object visitYieldNode(org.jrubyparser.ast.YieldNode node) {
        final List<org.jrubyparser.ast.Node> arguments = new ArrayList<>();

        final org.jrubyparser.ast.Node argsNode = node.getArgs();

        if (argsNode != null) {
            if (argsNode instanceof org.jrubyparser.ast.ListNode) {
                arguments.addAll(((org.jrubyparser.ast.ListNode) node.getArgs()).childNodes());
            } else {
                arguments.add(node.getArgs());
            }
        }

        final List<RubyNode> argumentsTranslated = new ArrayList<>();

        for (org.jrubyparser.ast.Node argument : arguments) {
            argumentsTranslated.add((RubyNode) argument.accept(this));
        }

        final RubyNode[] argumentsTranslatedArray = argumentsTranslated.toArray(new RubyNode[argumentsTranslated.size()]);

        return new YieldNode(context, translate(node.getPosition()), argumentsTranslatedArray);
    }

    @Override
    public Object visitZArrayNode(org.jrubyparser.ast.ZArrayNode node) {
        final RubyNode[] values = new RubyNode[0];

        return new UninitialisedArrayLiteralNode(context, translate(node.getPosition()), values);
    }

    @Override
    public Object visitZSuperNode(org.jrubyparser.ast.ZSuperNode node) {
        return unimplemented(node);
    }

    public Object visitArgumentNode(org.jrubyparser.ast.ArgumentNode node) {
        return unimplemented(node);
    }

    public Object visitCommentNode(org.jrubyparser.ast.CommentNode node) {
        return unimplemented(node);
    }

    public Object visitKeywordArgNode(org.jrubyparser.ast.KeywordArgNode node) {
        return unimplemented(node);
    }

    public Object visitKeywordRestArgNode(org.jrubyparser.ast.KeywordRestArgNode node) {
        return unimplemented(node);
    }

    public Object visitListNode(org.jrubyparser.ast.ListNode node) {
        return unimplemented(node);
    }

    public Object visitMethodNameNode(org.jrubyparser.ast.MethodNameNode node) {
        return unimplemented(node);
    }

    public Object visitOptArgNode(org.jrubyparser.ast.OptArgNode node) {
        return unimplemented(node);
    }

    public Object visitSyntaxNode(org.jrubyparser.ast.SyntaxNode node) {
        return unimplemented(node);
    }

    public Object visitImplicitNilNode(org.jrubyparser.ast.ImplicitNilNode node) {
        return new NilNode(context, translate(node.getPosition()));
    }

    public Object visitLambdaNode(org.jrubyparser.ast.LambdaNode node) {
        // TODO(cs): code copied and modified from visitIterNode - extract common

        final TranslatorEnvironment newEnvironment = new TranslatorEnvironment(context, environment, environment.getParser(), environment.getReturnID(), false, false, new UniqueMethodIdentifier());
        final MethodTranslator methodCompiler = new MethodTranslator(context, this, newEnvironment, false, source);

        org.jrubyparser.ast.ArgsNode argsNode;

        if (node.getVar() instanceof org.jrubyparser.ast.ArgsNode) {
            argsNode = (org.jrubyparser.ast.ArgsNode) node.getVar();
        } else if (node.getVar() instanceof org.jrubyparser.ast.DAsgnNode) {
            final org.jrubyparser.ast.ArgumentNode arg = new org.jrubyparser.ast.ArgumentNode(node.getPosition(), ((org.jrubyparser.ast.DAsgnNode) node.getVar()).getName());
            final org.jrubyparser.ast.ListNode preArgs = new org.jrubyparser.ast.ArrayNode(node.getPosition(), arg);
            argsNode = new org.jrubyparser.ast.ArgsNode(node.getPosition(), preArgs, null, null, null, null, null, null);
        } else if (node.getVar() == null) {
            argsNode = null;
        } else {
            throw new UnsupportedOperationException();
        }

        final MethodDefinitionNode definitionNode = methodCompiler.compileFunctionNode(translate(node.getPosition()), "(lambda)", argsNode, node.getBody());

        return new LambdaNode(context, translate(node.getPosition()), definitionNode);
    }

    public Object visitUnaryCallNode(org.jrubyparser.ast.UnaryCallNode node) {
        final org.jrubyparser.ast.Node callNode = new org.jrubyparser.ast.CallNode(node.getPosition(), node.getReceiver(), node.getName(), null, null);
        return callNode.accept(this);
    }

    protected Object unimplemented(org.jrubyparser.ast.Node node) {
        context.implementationMessage("warning: %s at %s does nothing", node, node.getPosition());
        return new NilNode(context, translate(node.getPosition()));
    }

    protected SourceSection translate(final org.jrubyparser.SourcePosition sourcePosition) {
        try {
            // TODO(cs): get an identifier
            final String identifier = "(identifier)";

            // TODO(cs): work out the start column
            final int startColumn = -1;

            final int charLength = sourcePosition.getEndOffset() - sourcePosition.getStartOffset();

            return new DefaultSourceSection(source, identifier, sourcePosition.getStartLine() + 1, startColumn, sourcePosition.getStartOffset(), charLength);
        } catch (UnsupportedOperationException e) {
            // In some circumstances JRuby can't tell you what the position is
            return translate(new org.jrubyparser.SourcePosition("(unknown)", 0, 0));
        }
    }

    protected SequenceNode initFlipFlopStates(SourceSection sourceSection) {
        final RubyNode[] initNodes = new RubyNode[environment.getFlipFlopStates().size()];

        for (int n = 0; n < initNodes.length; n++) {
            initNodes[n] = new InitFlipFlopSlotNode(context, sourceSection, environment.getFlipFlopStates().get(n));
        }

        return new SequenceNode(context, sourceSection, initNodes);
    }

}