Mercurial > hg > graal-compiler
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); + } + +}