diff graal/com.oracle.truffle.ruby.parser/src/com/oracle/truffle/ruby/parser/Translator.java @ 13514:0fbee3eb71f0

Ruby: import project.
author Chris Seaton <chris.seaton@oracle.com>
date Mon, 06 Jan 2014 17:12:09 +0000
parents
children 44288fe54352 1894412de0ed
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.ruby.parser/src/com/oracle/truffle/ruby/parser/Translator.java	Mon Jan 06 17:12:09 2014 +0000
@@ -0,0 +1,2093 @@
+/*
+ * Copyright (c) 2013 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.debug.*;
+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.debug.*;
+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;
+
+    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;
+    }
+
+    @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);
+
+        return new CallNode(context, sourceSection, node.getName(), receiverTranslated, argumentsAndBlock.getBlock(), argumentsAndBlock.isSplatted(), argumentsAndBlock.getArguments());
+    }
+
+    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);
+
+        if (context.getConfiguration().getDebug()) {
+            final UniqueMethodIdentifier methodIdentifier = environment.findMethodForLocalVar(node.getName());
+
+            RubyProxyNode proxy;
+            if (translated instanceof RubyProxyNode) {
+                proxy = (RubyProxyNode) translated;
+            } else {
+                proxy = new RubyProxyNode(context, translated);
+            }
+            context.getDebugManager().registerLocalDebugProxy(methodIdentifier, node.getName(), proxy.getProbeChain());
+
+            translated = proxy;
+        }
+
+        return translated;
+    }
+
+    @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);
+
+        if (context.getConfiguration().getDebug()) {
+
+            RubyProxyNode proxy;
+            SourceSection sourceSection;
+            if (translated instanceof RubyProxyNode) {
+                proxy = (RubyProxyNode) translated;
+                sourceSection = proxy.getChild().getSourceSection();
+            } else {
+                proxy = new RubyProxyNode(context, translated);
+                sourceSection = translated.getSourceSection();
+            }
+            context.getDebugManager().registerProbeChain(sourceSection, proxy.getProbeChain());
+            translated = proxy;
+        }
+
+        if (context.getConfiguration().getTrace()) {
+            RubyProxyNode proxy;
+            if (translated instanceof RubyProxyNode) {
+                proxy = (RubyProxyNode) translated;
+            } else {
+                proxy = new RubyProxyNode(context, translated);
+            }
+            proxy.getProbeChain().appendProbe(new RubyTraceProbe(context));
+
+            translated = proxy;
+        }
+
+        return translated;
+    }
+
+    @Override
+    public Object visitNextNode(org.jrubyparser.ast.NextNode node) {
+        return new NextNode(context, translate(node.getPosition()));
+    }
+
+    @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);
+    }
+
+}