Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.jruby.truffle.parser.BodyTranslator Maven / Gradle / Ivy
/*
* Copyright (c) 2013, 2016 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 org.jruby.truffle.parser;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.frame.FrameSlot;
import com.oracle.truffle.api.nodes.NodeUtil;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.source.Source;
import com.oracle.truffle.api.source.SourceSection;
import org.jcodings.specific.UTF8Encoding;
import org.joni.NameEntry;
import org.joni.Regex;
import org.joni.Syntax;
import org.jruby.truffle.Log;
import org.jruby.truffle.RubyContext;
import org.jruby.truffle.RubyLanguage;
import org.jruby.truffle.builtins.PrimitiveNodeConstructor;
import org.jruby.truffle.collections.Tuple;
import org.jruby.truffle.core.CoreLibrary;
import org.jruby.truffle.core.IsNilNode;
import org.jruby.truffle.core.IsRubiniusUndefinedNode;
import org.jruby.truffle.core.RaiseIfFrozenNode;
import org.jruby.truffle.core.array.ArrayAppendOneNodeGen;
import org.jruby.truffle.core.array.ArrayConcatNode;
import org.jruby.truffle.core.array.ArrayDropTailNode;
import org.jruby.truffle.core.array.ArrayDropTailNodeGen;
import org.jruby.truffle.core.array.ArrayGetTailNodeGen;
import org.jruby.truffle.core.array.ArrayLiteralNode;
import org.jruby.truffle.core.array.PrimitiveArrayNodeFactory;
import org.jruby.truffle.core.cast.HashCastNodeGen;
import org.jruby.truffle.core.cast.IntegerCastNodeGen;
import org.jruby.truffle.core.cast.SplatCastNode;
import org.jruby.truffle.core.cast.SplatCastNodeGen;
import org.jruby.truffle.core.cast.StringToSymbolNodeGen;
import org.jruby.truffle.core.cast.ToProcNodeGen;
import org.jruby.truffle.core.cast.ToSNode;
import org.jruby.truffle.core.cast.ToSNodeGen;
import org.jruby.truffle.core.hash.ConcatHashLiteralNode;
import org.jruby.truffle.core.hash.EnsureSymbolKeysNode;
import org.jruby.truffle.core.hash.HashLiteralNode;
import org.jruby.truffle.core.hash.HashNodesFactory;
import org.jruby.truffle.core.kernel.KernelNodesFactory;
import org.jruby.truffle.core.module.ModuleNodesFactory;
import org.jruby.truffle.core.numeric.BignumOperations;
import org.jruby.truffle.core.proc.ProcType;
import org.jruby.truffle.core.range.RangeNodesFactory;
import org.jruby.truffle.core.regexp.InterpolatedRegexpNode;
import org.jruby.truffle.core.regexp.MatchDataNodesFactory;
import org.jruby.truffle.core.regexp.RegexpNodes;
import org.jruby.truffle.core.regexp.RegexpOptions;
import org.jruby.truffle.core.rope.CodeRange;
import org.jruby.truffle.core.rope.Rope;
import org.jruby.truffle.core.rope.RopeConstants;
import org.jruby.truffle.core.rope.RopeOperations;
import org.jruby.truffle.core.rubinius.RubiniusLastStringReadNode;
import org.jruby.truffle.core.rubinius.RubiniusLastStringWriteNodeGen;
import org.jruby.truffle.core.string.InterpolatedStringNode;
import org.jruby.truffle.core.string.StringOperations;
import org.jruby.truffle.core.string.StringUtils;
import org.jruby.truffle.language.LexicalScope;
import org.jruby.truffle.language.NotProvided;
import org.jruby.truffle.language.RubyNode;
import org.jruby.truffle.language.RubyRootNode;
import org.jruby.truffle.language.SourceIndexLength;
import org.jruby.truffle.language.Visibility;
import org.jruby.truffle.language.arguments.ArrayIsAtLeastAsLargeAsNode;
import org.jruby.truffle.language.arguments.SingleBlockArgNode;
import org.jruby.truffle.language.constants.ReadConstantNode;
import org.jruby.truffle.language.constants.ReadConstantWithDynamicScopeNode;
import org.jruby.truffle.language.constants.ReadConstantWithLexicalScopeNode;
import org.jruby.truffle.language.constants.WriteConstantNode;
import org.jruby.truffle.language.control.AndNode;
import org.jruby.truffle.language.control.BreakID;
import org.jruby.truffle.language.control.BreakNode;
import org.jruby.truffle.language.control.ElidableResultNode;
import org.jruby.truffle.language.control.FrameOnStackNode;
import org.jruby.truffle.language.control.IfElseNode;
import org.jruby.truffle.language.control.IfNode;
import org.jruby.truffle.language.control.NextNode;
import org.jruby.truffle.language.control.NotNode;
import org.jruby.truffle.language.control.OnceNode;
import org.jruby.truffle.language.control.OrNode;
import org.jruby.truffle.language.control.RaiseException;
import org.jruby.truffle.language.control.RedoNode;
import org.jruby.truffle.language.control.RetryNode;
import org.jruby.truffle.language.control.ReturnID;
import org.jruby.truffle.language.control.ReturnNode;
import org.jruby.truffle.language.control.UnlessNode;
import org.jruby.truffle.language.control.WhileNode;
import org.jruby.truffle.language.defined.DefinedNode;
import org.jruby.truffle.language.defined.DefinedWrapperNode;
import org.jruby.truffle.language.dispatch.RubyCallNode;
import org.jruby.truffle.language.dispatch.RubyCallNodeParameters;
import org.jruby.truffle.language.exceptions.DisablingBacktracesNode;
import org.jruby.truffle.language.exceptions.EnsureNode;
import org.jruby.truffle.language.exceptions.RescueAnyNode;
import org.jruby.truffle.language.exceptions.RescueClassesNode;
import org.jruby.truffle.language.exceptions.RescueNode;
import org.jruby.truffle.language.exceptions.RescueSplatNode;
import org.jruby.truffle.language.exceptions.TryNode;
import org.jruby.truffle.language.globals.AliasGlobalVarNode;
import org.jruby.truffle.language.globals.CheckMatchVariableTypeNode;
import org.jruby.truffle.language.globals.CheckOutputSeparatorVariableTypeNode;
import org.jruby.truffle.language.globals.CheckProgramNameVariableTypeNode;
import org.jruby.truffle.language.globals.CheckRecordSeparatorVariableTypeNode;
import org.jruby.truffle.language.globals.CheckStdoutVariableTypeNode;
import org.jruby.truffle.language.globals.ReadGlobalVariableNodeGen;
import org.jruby.truffle.language.globals.ReadLastBacktraceNode;
import org.jruby.truffle.language.globals.ReadMatchReferenceNode;
import org.jruby.truffle.language.globals.ReadThreadLocalGlobalVariableNode;
import org.jruby.truffle.language.globals.UpdateLastBacktraceNode;
import org.jruby.truffle.language.globals.UpdateVerbosityNode;
import org.jruby.truffle.language.globals.WriteGlobalVariableNodeGen;
import org.jruby.truffle.language.globals.WriteReadOnlyGlobalNode;
import org.jruby.truffle.language.literal.BooleanLiteralNode;
import org.jruby.truffle.language.literal.FloatLiteralNode;
import org.jruby.truffle.language.literal.IntegerFixnumLiteralNode;
import org.jruby.truffle.language.literal.LongFixnumLiteralNode;
import org.jruby.truffle.language.literal.NilLiteralNode;
import org.jruby.truffle.language.literal.ObjectLiteralNode;
import org.jruby.truffle.language.literal.StringLiteralNode;
import org.jruby.truffle.language.locals.DeclarationFlipFlopStateNode;
import org.jruby.truffle.language.locals.FlipFlopNode;
import org.jruby.truffle.language.locals.FlipFlopStateNode;
import org.jruby.truffle.language.locals.InitFlipFlopSlotNode;
import org.jruby.truffle.language.locals.LocalFlipFlopStateNode;
import org.jruby.truffle.language.locals.LocalVariableType;
import org.jruby.truffle.language.locals.ReadLocalVariableNode;
import org.jruby.truffle.language.methods.AddMethodNodeGen;
import org.jruby.truffle.language.methods.Arity;
import org.jruby.truffle.language.methods.BlockDefinitionNode;
import org.jruby.truffle.language.methods.CatchBreakNode;
import org.jruby.truffle.language.methods.ExceptionTranslatingNode;
import org.jruby.truffle.language.methods.GetCurrentVisibilityNode;
import org.jruby.truffle.language.methods.GetDefaultDefineeNode;
import org.jruby.truffle.language.methods.MethodDefinitionNode;
import org.jruby.truffle.language.methods.ModuleBodyDefinitionNode;
import org.jruby.truffle.language.methods.SharedMethodInfo;
import org.jruby.truffle.language.methods.UnsupportedOperationBehavior;
import org.jruby.truffle.language.objects.DefineClassNode;
import org.jruby.truffle.language.objects.DefineModuleNode;
import org.jruby.truffle.language.objects.DefineModuleNodeGen;
import org.jruby.truffle.language.objects.DynamicLexicalScopeNode;
import org.jruby.truffle.language.objects.LexicalScopeNode;
import org.jruby.truffle.language.objects.ReadClassVariableNode;
import org.jruby.truffle.language.objects.ReadInstanceVariableNode;
import org.jruby.truffle.language.objects.RunModuleDefinitionNode;
import org.jruby.truffle.language.objects.SelfNode;
import org.jruby.truffle.language.objects.SingletonClassNode;
import org.jruby.truffle.language.objects.SingletonClassNodeGen;
import org.jruby.truffle.language.objects.WriteClassVariableNode;
import org.jruby.truffle.language.objects.WriteInstanceVariableNode;
import org.jruby.truffle.language.threadlocal.GetFromThreadLocalNode;
import org.jruby.truffle.language.threadlocal.ThreadLocalObjectNode;
import org.jruby.truffle.language.threadlocal.ThreadLocalObjectNodeGen;
import org.jruby.truffle.language.threadlocal.WrapInThreadLocalNodeGen;
import org.jruby.truffle.language.yield.YieldExpressionNode;
import org.jruby.truffle.parser.ast.AliasParseNode;
import org.jruby.truffle.parser.ast.AndParseNode;
import org.jruby.truffle.parser.ast.ArgsCatParseNode;
import org.jruby.truffle.parser.ast.ArgsParseNode;
import org.jruby.truffle.parser.ast.ArgsPushParseNode;
import org.jruby.truffle.parser.ast.ArgumentParseNode;
import org.jruby.truffle.parser.ast.ArrayParseNode;
import org.jruby.truffle.parser.ast.AssignableParseNode;
import org.jruby.truffle.parser.ast.AttrAssignParseNode;
import org.jruby.truffle.parser.ast.BackRefParseNode;
import org.jruby.truffle.parser.ast.BeginParseNode;
import org.jruby.truffle.parser.ast.BignumParseNode;
import org.jruby.truffle.parser.ast.BlockParseNode;
import org.jruby.truffle.parser.ast.BlockPassParseNode;
import org.jruby.truffle.parser.ast.BreakParseNode;
import org.jruby.truffle.parser.ast.CallParseNode;
import org.jruby.truffle.parser.ast.CaseParseNode;
import org.jruby.truffle.parser.ast.ClassParseNode;
import org.jruby.truffle.parser.ast.ClassVarAsgnParseNode;
import org.jruby.truffle.parser.ast.ClassVarParseNode;
import org.jruby.truffle.parser.ast.Colon2ConstParseNode;
import org.jruby.truffle.parser.ast.Colon2ImplicitParseNode;
import org.jruby.truffle.parser.ast.Colon2ParseNode;
import org.jruby.truffle.parser.ast.Colon3ParseNode;
import org.jruby.truffle.parser.ast.ComplexParseNode;
import org.jruby.truffle.parser.ast.ConstDeclParseNode;
import org.jruby.truffle.parser.ast.ConstParseNode;
import org.jruby.truffle.parser.ast.DAsgnParseNode;
import org.jruby.truffle.parser.ast.DRegexpParseNode;
import org.jruby.truffle.parser.ast.DStrParseNode;
import org.jruby.truffle.parser.ast.DSymbolParseNode;
import org.jruby.truffle.parser.ast.DVarParseNode;
import org.jruby.truffle.parser.ast.DXStrParseNode;
import org.jruby.truffle.parser.ast.DefinedParseNode;
import org.jruby.truffle.parser.ast.DefnParseNode;
import org.jruby.truffle.parser.ast.DefsParseNode;
import org.jruby.truffle.parser.ast.DotParseNode;
import org.jruby.truffle.parser.ast.EncodingParseNode;
import org.jruby.truffle.parser.ast.EnsureParseNode;
import org.jruby.truffle.parser.ast.EvStrParseNode;
import org.jruby.truffle.parser.ast.FCallParseNode;
import org.jruby.truffle.parser.ast.FalseParseNode;
import org.jruby.truffle.parser.ast.FixnumParseNode;
import org.jruby.truffle.parser.ast.FlipParseNode;
import org.jruby.truffle.parser.ast.FloatParseNode;
import org.jruby.truffle.parser.ast.ForParseNode;
import org.jruby.truffle.parser.ast.GlobalAsgnParseNode;
import org.jruby.truffle.parser.ast.GlobalVarParseNode;
import org.jruby.truffle.parser.ast.HashParseNode;
import org.jruby.truffle.parser.ast.IfParseNode;
import org.jruby.truffle.parser.ast.InstAsgnParseNode;
import org.jruby.truffle.parser.ast.InstVarParseNode;
import org.jruby.truffle.parser.ast.IterParseNode;
import org.jruby.truffle.parser.ast.LambdaParseNode;
import org.jruby.truffle.parser.ast.ListParseNode;
import org.jruby.truffle.parser.ast.LiteralParseNode;
import org.jruby.truffle.parser.ast.LocalAsgnParseNode;
import org.jruby.truffle.parser.ast.LocalVarParseNode;
import org.jruby.truffle.parser.ast.Match2ParseNode;
import org.jruby.truffle.parser.ast.Match3ParseNode;
import org.jruby.truffle.parser.ast.MatchParseNode;
import org.jruby.truffle.parser.ast.MethodDefParseNode;
import org.jruby.truffle.parser.ast.ModuleParseNode;
import org.jruby.truffle.parser.ast.MultipleAsgnParseNode;
import org.jruby.truffle.parser.ast.NextParseNode;
import org.jruby.truffle.parser.ast.NilImplicitParseNode;
import org.jruby.truffle.parser.ast.NilParseNode;
import org.jruby.truffle.parser.ast.NthRefParseNode;
import org.jruby.truffle.parser.ast.OpAsgnAndParseNode;
import org.jruby.truffle.parser.ast.OpAsgnConstDeclParseNode;
import org.jruby.truffle.parser.ast.OpAsgnOrParseNode;
import org.jruby.truffle.parser.ast.OpAsgnParseNode;
import org.jruby.truffle.parser.ast.OpElementAsgnParseNode;
import org.jruby.truffle.parser.ast.OrParseNode;
import org.jruby.truffle.parser.ast.ParseNode;
import org.jruby.truffle.parser.ast.PostExeParseNode;
import org.jruby.truffle.parser.ast.PreExeParseNode;
import org.jruby.truffle.parser.ast.RationalParseNode;
import org.jruby.truffle.parser.ast.RedoParseNode;
import org.jruby.truffle.parser.ast.RegexpParseNode;
import org.jruby.truffle.parser.ast.RescueBodyParseNode;
import org.jruby.truffle.parser.ast.RescueParseNode;
import org.jruby.truffle.parser.ast.RetryParseNode;
import org.jruby.truffle.parser.ast.ReturnParseNode;
import org.jruby.truffle.parser.ast.SClassParseNode;
import org.jruby.truffle.parser.ast.SValueParseNode;
import org.jruby.truffle.parser.ast.SelfParseNode;
import org.jruby.truffle.parser.ast.SideEffectFree;
import org.jruby.truffle.parser.ast.SplatParseNode;
import org.jruby.truffle.parser.ast.StarParseNode;
import org.jruby.truffle.parser.ast.StrParseNode;
import org.jruby.truffle.parser.ast.SymbolParseNode;
import org.jruby.truffle.parser.ast.TrueParseNode;
import org.jruby.truffle.parser.ast.TruffleFragmentParseNode;
import org.jruby.truffle.parser.ast.UndefParseNode;
import org.jruby.truffle.parser.ast.UntilParseNode;
import org.jruby.truffle.parser.ast.VAliasParseNode;
import org.jruby.truffle.parser.ast.VCallParseNode;
import org.jruby.truffle.parser.ast.WhenParseNode;
import org.jruby.truffle.parser.ast.WhileParseNode;
import org.jruby.truffle.parser.ast.XStrParseNode;
import org.jruby.truffle.parser.ast.YieldParseNode;
import org.jruby.truffle.parser.ast.ZArrayParseNode;
import org.jruby.truffle.parser.ast.visitor.NodeVisitor;
import org.jruby.truffle.parser.parser.ParserSupport;
import org.jruby.truffle.parser.scope.StaticScope;
import org.jruby.truffle.platform.graal.AssertConstantNodeGen;
import org.jruby.truffle.platform.graal.AssertNotCompiledNodeGen;
import org.jruby.truffle.tools.ChaosNodeGen;
import java.io.File;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
/**
* A JRuby parser node visitor which translates JRuby AST nodes into truffle Nodes. Therefore there is some namespace
* contention here! We make all references to JRuby explicit.
*/
public class BodyTranslator extends Translator {
protected final BodyTranslator parent;
protected final TranslatorEnvironment environment;
public boolean translatingForStatement = false;
private boolean translatingNextExpression = false;
private boolean translatingWhile = false;
protected String currentCallMethodName = null;
private boolean privately = false;
public BodyTranslator(com.oracle.truffle.api.nodes.Node currentNode, RubyContext context, BodyTranslator parent, TranslatorEnvironment environment, Source source, boolean topLevel) {
super(currentNode, context, source);
parserSupport = new ParserSupport(context, source.getName());
this.parent = parent;
this.environment = environment;
}
private DynamicObject translateNameNodeToSymbol(ParseNode node) {
return context.getSymbolTable().getSymbol(((LiteralParseNode) node).getName());
}
@Override
public RubyNode visitAliasNode(AliasParseNode node) {
final SourceIndexLength sourceSection = node.getPosition();
final DynamicObject oldName = translateNameNodeToSymbol(node.getOldName());
final DynamicObject newName = translateNameNodeToSymbol(node.getNewName());
/*
* ostruct in the stdlib defines an #allocate method to be the same as #new, but our #new calls
* #allocate with full lookup, and so this forms an infinite loop. MRI doesn't do full lookup
* for #allocate so doesn't have the same problem. MRI bug #11884 about this is to workaround
* a problem Pysch has. We'll fix that when we see it for real. For now this is the easier fix.
*/
if (newName.toString().equals("allocate") && source.getName().endsWith("/ostruct.rb")) {
return nilNode(source, sourceSection);
}
final RubyNode newNameNode = new ObjectLiteralNode(newName);
newNameNode.unsafeSetSourceSection(sourceSection);
final RubyNode oldNameNode = new ObjectLiteralNode(oldName);
oldNameNode.unsafeSetSourceSection(sourceSection);
final RubyNode ret = ModuleNodesFactory.AliasMethodNodeFactory.create(
new RaiseIfFrozenNode(new GetDefaultDefineeNode()),
newNameNode,
oldNameNode);
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitVAliasNode(VAliasParseNode node) {
final SourceIndexLength sourceSection = node.getPosition();
final RubyNode ret = new AliasGlobalVarNode(node.getOldName(), node.getNewName());
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitAndNode(AndParseNode node) {
final SourceIndexLength sourceSection = node.getPosition();
final RubyNode x = translateNodeOrNil(sourceSection, node.getFirstNode());
final RubyNode y = translateNodeOrNil(sourceSection, node.getSecondNode());
final RubyNode ret = new AndNode(x, y);
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitArgsCatNode(ArgsCatParseNode node) {
final List nodes = new ArrayList<>();
collectArgsCatNodes(nodes, node);
final List translatedNodes = new ArrayList<>();
for (ParseNode catNode : nodes) {
translatedNodes.add(catNode.accept(this));
}
final RubyNode ret = new ArrayConcatNode(translatedNodes.toArray(new RubyNode[translatedNodes.size()]));
ret.unsafeSetSourceSection(node.getPosition());
return addNewlineIfNeeded(node, ret);
}
// ArgsCatNodes can be nested - this collects them into a flat list of children
private void collectArgsCatNodes(List nodes, ArgsCatParseNode node) {
if (node.getFirstNode() instanceof ArgsCatParseNode) {
collectArgsCatNodes(nodes, (ArgsCatParseNode) node.getFirstNode());
} else {
nodes.add(node.getFirstNode());
}
if (node.getSecondNode() instanceof ArgsCatParseNode) {
collectArgsCatNodes(nodes, (ArgsCatParseNode) node.getSecondNode());
} else {
// ArgsCatParseNode implicitly splat its second argument. See Helpers.argsCat.
ParseNode secondNode = new SplatParseNode(node.getSecondNode().getPosition(), node.getSecondNode());
nodes.add(secondNode);
}
}
@Override
public RubyNode visitArgsPushNode(ArgsPushParseNode node) {
final RubyNode args = node.getFirstNode().accept(this);
final RubyNode value = node.getSecondNode().accept(this);
final RubyNode ret = ArrayAppendOneNodeGen.create(
KernelNodesFactory.DupNodeFactory.create(new RubyNode[] { args }),
value);
ret.unsafeSetSourceSection(node.getPosition());
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitArrayNode(ArrayParseNode node) {
final ParseNode[] values = node.children();
final RubyNode[] translatedValues = new RubyNode[values.length];
for (int n = 0; n < values.length; n++) {
translatedValues[n] = values[n].accept(this);
}
final RubyNode ret = ArrayLiteralNode.create(translatedValues);
ret.unsafeSetSourceSection(node.getPosition());
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitAttrAssignNode(AttrAssignParseNode node) {
final CallParseNode callNode = new CallParseNode(
node.getPosition(), node.getReceiverNode(), node.getName(), node.getArgsNode(), null, node.isLazy());
copyNewline(node, callNode);
boolean isAccessorOnSelf = (node.getReceiverNode() instanceof SelfParseNode);
final RubyNode actualCall = translateCallNode(callNode, isAccessorOnSelf, false, true);
return addNewlineIfNeeded(node, actualCall);
}
@Override
public RubyNode visitBeginNode(BeginParseNode node) {
final RubyNode ret = node.getBodyNode().accept(this);
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitBignumNode(BignumParseNode node) {
final SourceIndexLength sourceSection = node.getPosition();
final SourceSection fullSourceSection = sourceSection.toSourceSection(source);
// These aren't always Bignums!
final BigInteger value = node.getValue();
final RubyNode ret;
if (value.bitLength() >= 64) {
ret = new ObjectLiteralNode(BignumOperations.createBignum(context, node.getValue()));
} else {
ret = new LongFixnumLiteralNode(value.longValue());
}
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitBlockNode(BlockParseNode node) {
final SourceIndexLength sourceSection = node.getPosition();
final List translatedChildren = new ArrayList<>();
final int start = node.getPosition().getCharIndex();
int end = node.getPosition().getCharEnd();
for (ParseNode child : node.children()) {
if (child.getPosition() != null) {
end = Math.max(end, child.getPosition().getCharEnd());
}
final RubyNode translatedChild = translateNodeOrNil(sourceSection, child);
if (!(translatedChild instanceof DeadNode)) {
translatedChildren.add(translatedChild);
}
}
final RubyNode ret;
if (translatedChildren.size() == 1) {
ret = translatedChildren.get(0);
} else {
ret = sequence(new SourceIndexLength(start, end - start), translatedChildren);
}
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitBreakNode(BreakParseNode node) {
assert environment.isBlock() || translatingWhile : "The parser did not see an invalid break";
final SourceIndexLength sourceSection = node.getPosition();
final RubyNode resultNode = translateNodeOrNil(sourceSection, node.getValueNode());
final RubyNode ret = new BreakNode(environment.getBreakID(), translatingWhile, resultNode);
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitCallNode(CallParseNode node) {
final SourceIndexLength sourceSection = node.getPosition();
final SourceSection fullSourceSection = sourceSection.toSourceSection(source);
final ParseNode receiver = node.getReceiverNode();
final String methodName = node.getName();
if (receiver instanceof StrParseNode && methodName.equals("freeze")) {
final StrParseNode strNode = (StrParseNode) receiver;
final ParserByteList byteList = strNode.getValue();
final CodeRange codeRange = strNode.getCodeRange();
final Rope rope = context.getRopeTable().getRope(byteList, codeRange);
final DynamicObject frozenString = context.getFrozenStrings().getFrozenString(rope);
return addNewlineIfNeeded(node, Translator.withSourceSection(sourceSection, new DefinedWrapperNode(context.getCoreStrings().METHOD, new ObjectLiteralNode(frozenString))));
}
if (receiver instanceof ConstParseNode
&& ((ConstParseNode) receiver).getName().equals("Truffle")) {
// Truffle.
if (methodName.equals("primitive")) {
throw new AssertionError("Invalid usage of Truffle.primitive at " + RubyLanguage.fileLine(fullSourceSection));
} else if (methodName.equals("invoke_primitive")) {
final RubyNode ret = translateRubiniusInvokePrimitive(sourceSection, node);
return addNewlineIfNeeded(node, ret);
} else if (methodName.equals("privately")) {
final RubyNode ret = translateRubiniusPrivately(fullSourceSection, node);
return addNewlineIfNeeded(node, ret);
} else if (methodName.equals("single_block_arg")) {
final RubyNode ret = translateSingleBlockArg(sourceSection, node);
return addNewlineIfNeeded(node, ret);
} else if (methodName.equals("check_frozen")) {
final RubyNode ret = translateCheckFrozen(sourceSection);
return addNewlineIfNeeded(node, ret);
}
} else if (receiver instanceof Colon2ConstParseNode // Truffle::Graal.
&& ((Colon2ConstParseNode) receiver).getLeftNode() instanceof ConstParseNode
&& ((ConstParseNode) ((Colon2ConstParseNode) receiver).getLeftNode()).getName().equals("Truffle")
&& ((Colon2ConstParseNode) receiver).getName().equals("Graal")) {
if (methodName.equals("assert_constant")) {
final RubyNode ret = AssertConstantNodeGen.create(((ArrayParseNode) node.getArgsNode()).get(0).accept(this));
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
} else if (methodName.equals("assert_not_compiled")) {
final RubyNode ret = AssertNotCompiledNodeGen.create();
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
}
} else if (receiver instanceof VCallParseNode // undefined.equal?(obj)
&& ((VCallParseNode) receiver).getName().equals("undefined")
&& getSourcePath(sourceSection).startsWith(corePath())
&& methodName.equals("equal?")) {
RubyNode argument = translateArgumentsAndBlock(sourceSection, null, node.getArgsNode(), methodName).getArguments()[0];
final RubyNode ret = new IsRubiniusUndefinedNode(argument);
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
}
return translateCallNode(node, false, false, false);
}
protected RubyNode translateRubiniusPrimitive(SourceIndexLength sourceSection, BlockParseNode body, RubyNode loadArguments) {
/*
* Translates something that looks like
*
* def foo
* Truffle.primitive :foo
* fallback code
* end
*
* into
*
* if value = CallPrimitiveNode(FooNode(arg1, arg2, ..., argN))
* return value
* else
* fallback code
* end
*
* Where the arguments are the same arguments as the method. It looks like this is only exercised with simple
* arguments so we're not worrying too much about what happens when they're more complicated (rest,
* keywords etc).
*/
final CallParseNode node = (CallParseNode) body.get(0);
final ArrayParseNode argsNode = (ArrayParseNode) node.getArgsNode();
if (argsNode.size() != 1 || !(argsNode.get(0) instanceof SymbolParseNode)) {
throw new UnsupportedOperationException("Truffle.primitive must have a single literal symbol argument");
}
final String primitiveName = ((SymbolParseNode) argsNode.get(0)).getName();
BlockParseNode fallback = new BlockParseNode(body.getPosition());
for (int i = 1; i < body.size(); i++) {
fallback.add(body.get(i));
}
RubyNode fallbackNode = fallback.accept(this);
fallbackNode = sequence(sourceSection, Arrays.asList(loadArguments, fallbackNode));
final PrimitiveNodeConstructor primitive = context.getPrimitiveManager().getPrimitive(primitiveName);
return primitive.createCallPrimitiveNode(context, source, sourceSection, fallbackNode);
}
private RubyNode translateRubiniusInvokePrimitive(SourceIndexLength sourceSection, CallParseNode node) {
/*
* Translates something that looks like
*
* Truffle.invoke_primitive :foo, arg1, arg2, argN
*
* into
*
* InvokePrimitiveNode(FooNode(arg1, arg2, ..., argN))
*/
final ArrayParseNode args = (ArrayParseNode) node.getArgsNode();
if (args.size() < 1 || !(args.get(0) instanceof SymbolParseNode)) {
throw new UnsupportedOperationException("Truffle.invoke_primitive must have at least an initial literal symbol argument");
}
final String primitiveName = ((SymbolParseNode) args.get(0)).getName();
final PrimitiveNodeConstructor primitive = context.getPrimitiveManager().getPrimitive(primitiveName);
final List arguments = new ArrayList<>();
// The first argument was the symbol so we ignore it
for (int n = 1; n < args.size(); n++) {
RubyNode readArgumentNode = args.get(n).accept(this);
arguments.add(readArgumentNode);
}
return primitive.createInvokePrimitiveNode(context, source, sourceSection, arguments.toArray(new RubyNode[arguments.size()]));
}
private RubyNode translateRubiniusPrivately(SourceSection sourceSection, CallParseNode node) {
/*
* Translates something that looks like
*
* Truffle.privately { foo }
*
* into just
*
* foo
*
* While we translate foo we'll mark all call sites as ignoring visbility.
*/
if (!(node.getIterNode() instanceof IterParseNode)) {
throw new UnsupportedOperationException("Truffle.privately needs a literal block");
}
final ArrayParseNode argsNode = (ArrayParseNode) node.getArgsNode();
if (argsNode != null && argsNode.size() > 0) {
throw new UnsupportedOperationException("Truffle.privately should not have any arguments");
}
/*
* Normally when you visit an 'iter' (block) node it will set the method name for you, so that we can name the
* block something like 'times-block'. Here we bypass the iter node and translate its child. So we set the
* name here.
*/
currentCallMethodName = "privately";
/*
* While we translate the body of the iter we want to create all call nodes with the ignore-visbility flag.
* This flag is checked in visitCallNode.
*/
final boolean previousPrivately = privately;
privately = true;
try {
return (((IterParseNode) node.getIterNode()).getBodyNode()).accept(this);
} finally {
// Restore the previous value of the privately flag - allowing for nesting
privately = previousPrivately;
}
}
public RubyNode translateSingleBlockArg(SourceIndexLength sourceSection, CallParseNode node) {
final RubyNode ret = new SingleBlockArgNode();
ret.unsafeSetSourceSection(sourceSection);
return ret;
}
private RubyNode translateCheckFrozen(SourceIndexLength sourceSection) {
return Translator.withSourceSection(sourceSection, new RaiseIfFrozenNode(new SelfNode(environment.getFrameDescriptor())));
}
private RubyNode translateCallNode(CallParseNode node, boolean ignoreVisibility, boolean isVCall, boolean isAttrAssign) {
final SourceIndexLength sourceSection = node.getPosition();
final RubyNode receiver = node.getReceiverNode().accept(this);
ParseNode args = node.getArgsNode();
ParseNode block = node.getIterNode();
if (block == null && args instanceof IterParseNode) {
block = args;
args = null;
}
final String methodName = node.getName();
final ArgumentsAndBlockTranslation argumentsAndBlock = translateArgumentsAndBlock(sourceSection, block, args, methodName);
final List children = new ArrayList<>();
if (argumentsAndBlock.getBlock() != null) {
children.add(argumentsAndBlock.getBlock());
}
children.addAll(Arrays.asList(argumentsAndBlock.getArguments()));
final SourceIndexLength enclosingSourceSection = enclosing(sourceSection, children.toArray(new RubyNode[children.size()]));
RubyCallNodeParameters callParameters = new RubyCallNodeParameters(receiver, methodName, argumentsAndBlock.getBlock(), argumentsAndBlock.getArguments(), argumentsAndBlock.isSplatted(), privately || ignoreVisibility, isVCall, node.isLazy(), isAttrAssign);
RubyNode translated = Translator.withSourceSection(enclosingSourceSection, context.getCoreMethods().createCallNode(source, callParameters));
if (argumentsAndBlock.getBlock() instanceof BlockDefinitionNode) { // if we have a literal block, break breaks out of this call site
BlockDefinitionNode blockDef = (BlockDefinitionNode) argumentsAndBlock.getBlock();
translated = new FrameOnStackNode(translated, argumentsAndBlock.getFrameOnStackMarkerSlot());
translated = new CatchBreakNode(blockDef.getBreakID(), translated);
}
return addNewlineIfNeeded(node, translated);
}
protected static class ArgumentsAndBlockTranslation {
private final RubyNode block;
private final RubyNode[] arguments;
private final boolean isSplatted;
private final FrameSlot frameOnStackMarkerSlot;
public ArgumentsAndBlockTranslation(RubyNode block, RubyNode[] arguments, boolean isSplatted, FrameSlot frameOnStackMarkerSlot) {
super();
this.block = block;
this.arguments = arguments;
this.isSplatted = isSplatted;
this.frameOnStackMarkerSlot = frameOnStackMarkerSlot;
}
public RubyNode getBlock() {
return block;
}
public RubyNode[] getArguments() {
return arguments;
}
public boolean isSplatted() {
return isSplatted;
}
public FrameSlot getFrameOnStackMarkerSlot() {
return frameOnStackMarkerSlot;
}
}
public static final Object BAD_FRAME_SLOT = new Object();
public Deque frameOnStackMarkerSlotStack = new ArrayDeque<>();
protected ArgumentsAndBlockTranslation translateArgumentsAndBlock(SourceIndexLength sourceSection, ParseNode iterNode, ParseNode argsNode, String nameToSetWhenTranslatingBlock) {
assert !(argsNode instanceof IterParseNode);
final List arguments = new ArrayList<>();
ParseNode blockPassNode = null;
boolean isSplatted = false;
if (argsNode instanceof ListParseNode) {
arguments.addAll(argsNode.childNodes());
} else if (argsNode instanceof BlockPassParseNode) {
final BlockPassParseNode blockPass = (BlockPassParseNode) argsNode;
final ParseNode blockPassArgs = blockPass.getArgsNode();
if (blockPassArgs instanceof ListParseNode) {
arguments.addAll(blockPassArgs.childNodes());
} else if (blockPassArgs instanceof ArgsCatParseNode) {
arguments.add(blockPassArgs);
} else if (blockPassArgs != null) {
throw new UnsupportedOperationException("Don't know how to block pass " + blockPassArgs);
}
blockPassNode = blockPass.getBodyNode();
} else if (argsNode instanceof SplatParseNode) {
isSplatted = true;
arguments.add(argsNode);
} else if (argsNode instanceof ArgsCatParseNode) {
isSplatted = true;
arguments.add(argsNode);
} else if (argsNode != null) {
isSplatted = true;
arguments.add(argsNode);
}
final RubyNode[] argumentsTranslated = new RubyNode[arguments.size()];
for (int i = 0; i < arguments.size(); i++) {
argumentsTranslated[i] = arguments.get(i).accept(this);
}
if (iterNode instanceof BlockPassParseNode) {
blockPassNode = ((BlockPassParseNode) iterNode).getBodyNode();
}
currentCallMethodName = nameToSetWhenTranslatingBlock;
final FrameSlot frameOnStackMarkerSlot;
RubyNode blockTranslated;
if (blockPassNode != null) {
blockTranslated = ToProcNodeGen.create(blockPassNode.accept(this));
blockTranslated.unsafeSetSourceSection(sourceSection);
frameOnStackMarkerSlot = null;
} else if (iterNode != null) {
frameOnStackMarkerSlot = environment.declareVar(environment.allocateLocalTemp("frame_on_stack_marker"));
frameOnStackMarkerSlotStack.push(frameOnStackMarkerSlot);
try {
blockTranslated = iterNode.accept(this);
} finally {
frameOnStackMarkerSlotStack.pop();
}
if (blockTranslated instanceof ObjectLiteralNode && ((ObjectLiteralNode) blockTranslated).getObject() == context.getCoreLibrary().getNilObject()) {
blockTranslated = null;
}
} else {
blockTranslated = null;
frameOnStackMarkerSlot = null;
}
return new ArgumentsAndBlockTranslation(blockTranslated, argumentsTranslated, isSplatted, frameOnStackMarkerSlot);
}
@Override
public RubyNode visitCaseNode(CaseParseNode node) {
final SourceIndexLength sourceSection = node.getPosition();
final SourceSection fullSourceSection = sourceSection.toSourceSection(source);
RubyNode elseNode = translateNodeOrNil(sourceSection, node.getElseNode());
/*
* 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.
*/
final RubyNode ret;
if (node.getCaseNode() != null) {
// Evaluate the case expression and store it in a local
final String tempName = environment.allocateLocalTemp("case");
final ReadLocalNode readTemp = environment.findLocalVarNode(tempName, source, sourceSection);
final RubyNode assignTemp = readTemp.makeWriteNode(node.getCaseNode().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 WhenParseNode when = (WhenParseNode) node.getCases().get(n);
// Make a condition from the one or more expressions combined in an or expression
final List expressions;
if (when.getExpressionNodes() instanceof ListParseNode && !(when.getExpressionNodes() instanceof ArrayParseNode)) {
expressions = when.getExpressionNodes().childNodes();
} else {
expressions = Collections.singletonList(when.getExpressionNodes());
}
final List comparisons = new ArrayList<>();
for (ParseNode expressionNode : expressions) {
final RubyNode rubyExpression = expressionNode.accept(this);
final RubyNode receiver;
final RubyNode[] arguments;
final String method;
if (expressionNode instanceof SplatParseNode
|| expressionNode instanceof ArgsCatParseNode
|| expressionNode instanceof ArgsPushParseNode) {
receiver = new ObjectLiteralNode(context.getCoreLibrary().getTruffleModule());
receiver.unsafeSetSourceSection(sourceSection);
method = "when_splat";
arguments = new RubyNode[] { rubyExpression, NodeUtil.cloneNode(readTemp) };
} else {
receiver = rubyExpression;
method = "===";
arguments = new RubyNode[] { NodeUtil.cloneNode(readTemp) };
}
RubyCallNodeParameters callParameters = new RubyCallNodeParameters(receiver, method, null, arguments, false, true);
comparisons.add(Translator.withSourceSection(sourceSection, new RubyCallNode(callParameters)));
}
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 = new OrNode(comparisons.get(i), conditionNode);
}
// Create the if node
final RubyNode thenNode = translateNodeOrNil(sourceSection, when.getBodyNode());
final IfElseNode ifNode = new IfElseNode(conditionNode, 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
ret = sequence(sourceSection, Arrays.asList(assignTemp, ifNode));
} else {
for (int n = node.getCases().size() - 1; n >= 0; n--) {
final WhenParseNode when = (WhenParseNode) node.getCases().get(n);
// Make a condition from the one or more expressions combined in an or expression
final List expressions;
if (when.getExpressionNodes() instanceof ListParseNode) {
expressions = when.getExpressionNodes().childNodes();
} else {
expressions = Collections.singletonList(when.getExpressionNodes());
}
final List tests = new ArrayList<>();
for (ParseNode expressionNode : expressions) {
final RubyNode rubyExpression = 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 = new OrNode(tests.get(i), conditionNode);
}
// Create the if node
final RubyNode thenNode = when.getBodyNode().accept(this);
final IfElseNode ifNode = new IfElseNode(conditionNode, thenNode, elseNode);
// This if becomes the else for the next if
elseNode = ifNode;
}
ret = elseNode;
}
return addNewlineIfNeeded(node, ret);
}
private RubyNode openModule(SourceIndexLength sourceSection, RubyNode defineOrGetNode, String name, ParseNode bodyNode, boolean sclass) {
final SourceSection fullSourceSection = sourceSection.toSourceSection(source);
LexicalScope newLexicalScope = environment.pushLexicalScope();
try {
final SharedMethodInfo sharedMethodInfo = new SharedMethodInfo(
fullSourceSection,
newLexicalScope,
Arity.NO_ARGUMENTS,
null,
name,
sclass ? "class body" : "module body",
null,
false,
false,
false);
final ReturnID returnId;
if (sclass) {
returnId = environment.getReturnID();
} else {
returnId = environment.getParseEnvironment().allocateReturnID();
}
final TranslatorEnvironment newEnvironment = new TranslatorEnvironment(context, environment, environment.getParseEnvironment(),
returnId, true, true, true, sharedMethodInfo, name, 0, null);
final BodyTranslator moduleTranslator = new BodyTranslator(currentNode, context, this, newEnvironment, source, false);
final ModuleBodyDefinitionNode definition = moduleTranslator.compileClassNode(sourceSection, name, bodyNode, sclass);
return Translator.withSourceSection(sourceSection, new RunModuleDefinitionNode(newLexicalScope, definition, defineOrGetNode));
} finally {
environment.popLexicalScope();
}
}
/**
* Translates module and class nodes.
*
* In Ruby, a module or class definition is somewhat like a method. It has a local scope and a value
* for self, which is the module or class object that is being defined. Therefore for a module or
* class definition we translate into a special method. We run that method with self set to be the
* newly allocated module or class.
*
*/
private ModuleBodyDefinitionNode compileClassNode(SourceIndexLength sourceSection, String name, ParseNode bodyNode, boolean sclass) {
RubyNode body = translateNodeOrNil(sourceSection, bodyNode);
if (environment.getFlipFlopStates().size() > 0) {
body = sequence(sourceSection, Arrays.asList(initFlipFlopStates(sourceSection), body));
}
final RubyNode writeSelfNode = loadSelf(context, environment);
body = sequence(sourceSection, Arrays.asList(writeSelfNode, body));
if (context.getOptions().CHAOS) {
body = ChaosNodeGen.create(body);
}
final SourceSection fullSourceSection = sourceSection.toSourceSection(source);
final RubyRootNode rootNode = new RubyRootNode(context, fullSourceSection, environment.getFrameDescriptor(), environment.getSharedMethodInfo(), body, environment.needsDeclarationFrame());
final ModuleBodyDefinitionNode definitionNode = new ModuleBodyDefinitionNode(
environment.getSharedMethodInfo().getName(),
environment.getSharedMethodInfo(),
Truffle.getRuntime().createCallTarget(rootNode),
sclass,
environment.isDynamicConstantLookup());
definitionNode.unsafeSetSourceSection(sourceSection);
return definitionNode;
}
@Override
public RubyNode visitClassNode(ClassParseNode node) {
final SourceIndexLength sourceSection = node.getPosition();
final SourceSection fullSourceSection = sourceSection.toSourceSection(source);
final String name = node.getCPath().getName();
RubyNode lexicalParent = translateCPath(sourceSection, node.getCPath());
final RubyNode superClass = node.getSuperNode() != null ? node.getSuperNode().accept(this) : null;
final DefineClassNode defineOrGetClass = new DefineClassNode(name, lexicalParent, superClass);
final RubyNode ret = openModule(sourceSection, defineOrGetClass, name, node.getBodyNode(), false);
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitClassVarAsgnNode(ClassVarAsgnParseNode node) {
final SourceIndexLength sourceSection = node.getPosition();
final RubyNode rhs = node.getValueNode().accept(this);
final RubyNode ret = new WriteClassVariableNode(environment.getLexicalScope(), node.getName(), rhs);
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitClassVarNode(ClassVarParseNode node) {
final SourceIndexLength sourceSection = node.getPosition();
final RubyNode ret = new ReadClassVariableNode(environment.getLexicalScope(), node.getName());
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitColon2Node(Colon2ParseNode node) {
// Qualified constant access, as in Mod::CONST
if (!(node instanceof Colon2ConstParseNode)) {
throw new UnsupportedOperationException(node.toString());
}
final SourceIndexLength sourceSection = node.getPosition();
final SourceSection fullSourceSection = sourceSection.toSourceSection(source);
final String name = ConstantReplacer.replacementName(fullSourceSection, node.getName());
final RubyNode lhs = node.getLeftNode().accept(this);
final RubyNode ret = new ReadConstantNode(lhs, name);
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitColon3Node(Colon3ParseNode node) {
// Root namespace constant access, as in ::Foo
final SourceIndexLength sourceSection = node.getPosition();
final SourceSection fullSourceSection = sourceSection.toSourceSection(source);
final String name = ConstantReplacer.replacementName(fullSourceSection, node.getName());
final ObjectLiteralNode root = new ObjectLiteralNode(context.getCoreLibrary().getObjectClass());
root.unsafeSetSourceSection(sourceSection);
final RubyNode ret = new ReadConstantNode(root, name);
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
}
private RubyNode translateCPath(SourceIndexLength sourceSection, Colon3ParseNode node) {
final SourceSection fullSourceSection = sourceSection.toSourceSection(source);
final RubyNode ret;
if (node instanceof Colon2ImplicitParseNode) { // use current lexical scope
if (environment.getParseEnvironment().isDynamicConstantLookup()) {
if (context.getOptions().LOG_DYNAMIC_CONSTANT_LOOKUP) {
Log.LOGGER.info(() -> "dynamic constant lookup at " + RubyLanguage.fileLine(fullSourceSection));
}
ret = new DynamicLexicalScopeNode();
} else {
ret = new LexicalScopeNode(environment.getLexicalScope());
}
ret.unsafeSetSourceSection(sourceSection);
} else if (node instanceof Colon2ConstParseNode) { // A::B
ret = node.childNodes().get(0).accept(this);
} else { // Colon3ParseNode: on top-level (Object)
ret = new ObjectLiteralNode(context.getCoreLibrary().getObjectClass());
ret.unsafeSetSourceSection(sourceSection);
}
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitComplexNode(ComplexParseNode node) {
final SourceIndexLength sourceSection = node.getPosition();
final RubyNode ret = translateRationalComplex(sourceSection, "Complex",
new IntegerFixnumLiteralNode(0),
node.getNumber().accept(this));
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitConstDeclNode(ConstDeclParseNode node) {
final SourceIndexLength sourceSection = node.getPosition();
RubyNode rhs = node.getValueNode().accept(this);
final RubyNode moduleNode;
ParseNode constNode = node.getConstNode();
if (constNode == null || constNode instanceof Colon2ImplicitParseNode) {
if (environment.getParseEnvironment().isDynamicConstantLookup()) {
if (context.getOptions().LOG_DYNAMIC_CONSTANT_LOOKUP) {
Log.LOGGER.info(() -> "set dynamic constant at " + RubyLanguage.fileLine(sourceSection.toSourceSection(source)));
}
moduleNode = new DynamicLexicalScopeNode();
} else {
moduleNode = new LexicalScopeNode(environment.getLexicalScope());
}
moduleNode.unsafeSetSourceSection(sourceSection);
} else if (constNode instanceof Colon2ConstParseNode) {
constNode = ((Colon2ParseNode) constNode).getLeftNode(); // Misleading doc, we only want the defined part.
moduleNode = constNode.accept(this);
} else if (constNode instanceof Colon3ParseNode) {
moduleNode = new ObjectLiteralNode(context.getCoreLibrary().getObjectClass());
} else {
throw new UnsupportedOperationException();
}
final RubyNode ret = new WriteConstantNode(node.getName(), moduleNode, rhs);
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
}
private String getSourcePath(SourceIndexLength sourceSection) {
if (sourceSection == null) {
return "(unknown)";
}
if (source == null) {
return "(unknown)";
}
final String path = source.getName();
if (path == null) {
return source.getName();
}
return path;
}
private String corePath() {
return environment.getParseEnvironment().getCorePath();
}
private String buildPartialPath(String... components) {
final StringBuilder ret = new StringBuilder();
for (final String component : components) {
ret.append(File.separatorChar);
ret.append(component);
}
return ret.toString();
}
@Override
public RubyNode visitConstNode(ConstParseNode node) {
// Unqualified constant access, as in CONST
final SourceIndexLength sourceSection = node.getPosition();
final SourceSection fullSourceSection = sourceSection.toSourceSection(source);
/*
* Constants of the form Rubinius::Foo in the Rubinius kernel code always seem to get resolved, even if
* Rubinius is not defined, such as in BasicObject. We get around this by translating Rubinius to be
* ::Rubinius. Note that this isn't quite what Rubinius does, as they say that Rubinius isn't defined, but
* we will because we'll translate that to ::Rubinius. But it is a simpler translation.
*/
final String name = ConstantReplacer.replacementName(fullSourceSection, node.getName());
if (name.equals("Rubinius") && getSourcePath(sourceSection).startsWith(corePath())) {
final RubyNode ret = new Colon3ParseNode(node.getPosition(), name).accept(this);
return addNewlineIfNeeded(node, ret);
}
// TODO (pitr 01-Dec-2015): remove when RUBY_PLATFORM is set to "truffle"
if (name.equals("RUBY_PLATFORM") && getSourcePath(sourceSection).contains(buildPartialPath("test", "xml_mini", "jdom_engine_test.rb"))) {
final ObjectLiteralNode ret = new ObjectLiteralNode(StringOperations.createString(context, StringOperations.encodeRope("truffle", UTF8Encoding.INSTANCE, CodeRange.CR_7BIT)));
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
}
final RubyNode ret;
if (environment.getParseEnvironment().isDynamicConstantLookup()) {
if (context.getOptions().LOG_DYNAMIC_CONSTANT_LOOKUP) {
Log.LOGGER.info(() -> "dynamic constant lookup at " + RubyLanguage.fileLine(fullSourceSection));
}
ret = new ReadConstantWithDynamicScopeNode(name);
} else {
final LexicalScope lexicalScope = environment.getLexicalScope();
ret = new ReadConstantWithLexicalScopeNode(lexicalScope, name);
}
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitDAsgnNode(DAsgnParseNode node) {
final RubyNode ret = new LocalAsgnParseNode(node.getPosition(), node.getName(), node.getDepth(), node.getValueNode()).accept(this);
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitDRegxNode(DRegexpParseNode node) {
SourceIndexLength sourceSection = node.getPosition();
final List children = new ArrayList<>();
for (ParseNode child : node.children()) {
children.add(child.accept(this));
}
final InterpolatedRegexpNode i = new InterpolatedRegexpNode(children.toArray(new RubyNode[children.size()]), node.getOptions());
i.unsafeSetSourceSection(sourceSection);
if (node.getOptions().isOnce()) {
final RubyNode ret = new OnceNode(i);
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
}
return addNewlineIfNeeded(node, i);
}
@Override
public RubyNode visitDStrNode(DStrParseNode node) {
final RubyNode ret = translateInterpolatedString(node.getPosition(), node.children());
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitDSymbolNode(DSymbolParseNode node) {
SourceIndexLength sourceSection = node.getPosition();
final RubyNode stringNode = translateInterpolatedString(sourceSection, node.children());
final RubyNode ret = StringToSymbolNodeGen.create(stringNode);
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
}
private RubyNode translateInterpolatedString(SourceIndexLength sourceSection, ParseNode[] childNodes) {
final ToSNode[] children = new ToSNode[childNodes.length];
for (int i = 0; i < childNodes.length; i++) {
children[i] = ToSNodeGen.create(childNodes[i].accept(this));
}
final RubyNode ret = new InterpolatedStringNode(children);
ret.unsafeSetSourceSection(sourceSection);
return ret;
}
@Override
public RubyNode visitDVarNode(DVarParseNode node) {
RubyNode readNode = environment.findLocalVarNode(node.getName(), source, node.getPosition());
if (readNode == null) {
// If we haven't seen this dvar before it's possible that it's a block local variable
final int depth = node.getDepth();
TranslatorEnvironment e = environment;
for (int n = 0; n < depth; n++) {
e = e.getParent();
}
e.declareVar(node.getName());
// Searching for a local variable must start at the base environment, even though we may have determined
// the variable should be declared in a parent frame descriptor. This is so the search can determine
// whether to return a ReadLocalVariableNode or a ReadDeclarationVariableNode and potentially record the
// fact that a declaration frame is needed.
readNode = environment.findLocalVarNode(node.getName(), source, node.getPosition());
}
return addNewlineIfNeeded(node, readNode);
}
@Override
public RubyNode visitDXStrNode(DXStrParseNode node) {
final DStrParseNode string = new DStrParseNode(node.getPosition(), node.getEncoding());
string.addAll(node);
final ParseNode argsNode = buildArrayNode(node.getPosition(), string);
final ParseNode callNode = new FCallParseNode(node.getPosition(), "`", argsNode, null);
copyNewline(node, callNode);
final RubyNode ret = callNode.accept(this);
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitDefinedNode(DefinedParseNode node) {
final SourceIndexLength sourceSection = node.getPosition();
final RubyNode ret = new DefinedNode(node.getExpressionNode().accept(this));
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitDefnNode(DefnParseNode node) {
final SourceIndexLength sourceSection = node.getPosition();
final RubyNode classNode = new RaiseIfFrozenNode(new GetDefaultDefineeNode());
String methodName = node.getName();
final RubyNode ret = translateMethodDefinition(sourceSection, classNode, methodName, node.getArgsNode(), node, node.getBodyNode(), false);
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitDefsNode(DefsParseNode node) {
final SourceIndexLength sourceSection = node.getPosition();
final RubyNode objectNode = node.getReceiverNode().accept(this);
final SingletonClassNode singletonClassNode = SingletonClassNodeGen.create(objectNode);
singletonClassNode.unsafeSetSourceSection(sourceSection);
final RubyNode ret = translateMethodDefinition(sourceSection, singletonClassNode, node.getName(), node.getArgsNode(), node, node.getBodyNode(), true);
return addNewlineIfNeeded(node, ret);
}
protected RubyNode translateMethodDefinition(SourceIndexLength sourceSection, RubyNode classNode, String methodName, ArgsParseNode argsNode, MethodDefParseNode defNode, ParseNode bodyNode,
boolean isDefs) {
final SourceSection fullSourceSection = sourceSection.toSourceSection(source);
final Arity arity = MethodTranslator.getArity(argsNode);
final ArgumentDescriptor[] argumentDescriptors = Helpers.argsNodeToArgumentDescriptors(argsNode);
final SharedMethodInfo sharedMethodInfo = new SharedMethodInfo(
sourceSection.toSourceSection(source),
environment.getLexicalScopeOrNull(),
arity,
null,
methodName,
null,
argumentDescriptors,
false,
false,
false);
final TranslatorEnvironment newEnvironment = new TranslatorEnvironment(
context, environment, environment.getParseEnvironment(), environment.getParseEnvironment().allocateReturnID(), true, true, false, sharedMethodInfo, methodName, 0, null);
// ownScopeForAssignments is the same for the defined method as the current one.
final MethodTranslator methodCompiler = new MethodTranslator(currentNode, context, this, newEnvironment, false, source, argsNode);
final MethodDefinitionNode methodDefinitionNode = methodCompiler.compileMethodNode(sourceSection, methodName, defNode, bodyNode, sharedMethodInfo);
final RubyNode visibilityNode;
if (isDefs) {
visibilityNode = new ObjectLiteralNode(Visibility.PUBLIC);
} else {
visibilityNode = new GetCurrentVisibilityNode();
}
return withSourceSection(sourceSection, AddMethodNodeGen.create(isDefs, true, classNode, methodDefinitionNode, visibilityNode));
}
@Override
public RubyNode visitDotNode(DotParseNode node) {
final SourceIndexLength sourceSection = node.getPosition();
final RubyNode begin = node.getBeginNode().accept(this);
final RubyNode end = node.getEndNode().accept(this);
final RubyNode rangeClass = new ObjectLiteralNode(context.getCoreLibrary().getRangeClass());
final RubyNode isExclusive = new ObjectLiteralNode(node.isExclusive());
final RubyNode ret = RangeNodesFactory.NewNodeFactory.create(rangeClass, begin, end, isExclusive);
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitEncodingNode(EncodingParseNode node) {
SourceIndexLength sourceSection = node.getPosition();
final RubyNode ret = new ObjectLiteralNode(context.getEncodingManager().getRubyEncoding(node.getEncoding()));
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitEnsureNode(EnsureParseNode node) {
final RubyNode tryPart = node.getBodyNode().accept(this);
final RubyNode ensurePart = node.getEnsureNode().accept(this);
final RubyNode ret = new EnsureNode(tryPart, ensurePart);
ret.unsafeSetSourceSection(node.getPosition());
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitEvStrNode(EvStrParseNode node) {
final RubyNode ret;
if (node.getBody() == null) {
final SourceIndexLength sourceSection = node.getPosition();
ret = new ObjectLiteralNode(StringOperations.createString(context, RopeConstants.EMPTY_ASCII_8BIT_ROPE));
ret.unsafeSetSourceSection(sourceSection);
} else {
ret = node.getBody().accept(this);
}
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitFCallNode(FCallParseNode node) {
final ParseNode receiver = new SelfParseNode(node.getPosition());
final CallParseNode callNode = new CallParseNode(node.getPosition(), receiver, node.getName(), node.getArgsNode(), node.getIterNode());
copyNewline(node, callNode);
return translateCallNode(callNode, true, false, false);
}
@Override
public RubyNode visitFalseNode(FalseParseNode node) {
final SourceIndexLength sourceSection = node.getPosition();
final RubyNode ret = new BooleanLiteralNode(false);
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitFixnumNode(FixnumParseNode node) {
final SourceIndexLength sourceSection = node.getPosition();
final long value = node.getValue();
final RubyNode ret;
if (CoreLibrary.fitsIntoInteger(value)) {
ret = new IntegerFixnumLiteralNode((int) value);
} else {
ret = new LongFixnumLiteralNode(value);
}
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitFlipNode(FlipParseNode node) {
final SourceIndexLength sourceSection = node.getPosition();
final RubyNode begin = node.getBeginNode().accept(this);
final RubyNode end = node.getEndNode().accept(this);
final FlipFlopStateNode stateNode = createFlipFlopState(sourceSection, 0);
final RubyNode ret = new FlipFlopNode(begin, end, stateNode, node.isExclusive());
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
}
protected FlipFlopStateNode createFlipFlopState(SourceIndexLength sourceSection, int depth) {
final FrameSlot frameSlot = environment.declareVar(environment.allocateLocalTemp("flipflop"));
environment.getFlipFlopStates().add(frameSlot);
if (depth == 0) {
return new LocalFlipFlopStateNode(frameSlot);
} else {
return new DeclarationFlipFlopStateNode(depth, frameSlot);
}
}
@Override
public RubyNode visitFloatNode(FloatParseNode node) {
final RubyNode ret = new FloatLiteralNode(node.getValue());
ret.unsafeSetSourceSection(node.getPosition());
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitForNode(ForParseNode node) {
/**
* A Ruby for-loop, such as:
*
*
* for x in y
* z = x
* puts z
* end
*
*
* naively desugars to:
*
*
* y.each do |x|
* z = x
* puts z
* end
*
*
* 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:
*
*
* z = nil unless z is already defined
* y.each do |x|
* z = x
* puts x
* end
*
*
* 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:
*
*
* z = nil unless z is already defined
* y.each do |temp|
* x = temp
* z = x
* puts x
* end
*
*
* We also need that temp because the expression assigned in the for could be index
* assignment, multiple assignment, or whatever:
*
*
* for x[0] in y
* z = x[0]
* puts z
* end
*
*
* 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("for");
final ParseNode receiver = node.getIterNode();
/*
* 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 LocalVarParseNode readTemp = new LocalVarParseNode(node.getPosition(), 0, temp);
final ParseNode forVar = node.getVarNode();
final ParseNode assignTemp = setRHS(forVar, readTemp);
final BlockParseNode bodyWithTempAssign = new BlockParseNode(node.getPosition());
bodyWithTempAssign.add(assignTemp);
bodyWithTempAssign.add(node.getBodyNode());
final ArgumentParseNode blockVar = new ArgumentParseNode(node.getPosition(), temp);
final ListParseNode blockArgsPre = new ListParseNode(node.getPosition(), blockVar);
final ArgsParseNode blockArgs = new ArgsParseNode(node.getPosition(), blockArgsPre, null, null, null, null, null, null);
final IterParseNode block = new IterParseNode(node.getPosition(), blockArgs, node.getScope(), bodyWithTempAssign);
final CallParseNode callNode = new CallParseNode(node.getPosition(), receiver, "each", null, block);
copyNewline(node, callNode);
translatingForStatement = true;
final RubyNode translated = callNode.accept(this);
translatingForStatement = false;
return addNewlineIfNeeded(node, translated);
}
private final ParserSupport parserSupport;
private ParseNode setRHS(ParseNode node, ParseNode rhs) {
if (node instanceof AssignableParseNode || node instanceof org.jruby.truffle.parser.ast.IArgumentNode) {
return parserSupport.node_assign(node, rhs);
} else {
throw new UnsupportedOperationException("Don't know how to set the RHS of a " + node.getClass().getName());
}
}
private RubyNode translateDummyAssignment(ParseNode dummyAssignment, final RubyNode rhs) {
// The JRuby AST includes assignment nodes without a proper value,
// so we need to patch them to include the proper rhs value to translate them correctly.
if (dummyAssignment instanceof StarParseNode) {
// Nothing to assign to, just execute the RHS
return rhs;
} else if (dummyAssignment instanceof AssignableParseNode || dummyAssignment instanceof org.jruby.truffle.parser.ast.IArgumentNode) {
final ParseNode wrappedRHS = new ParseNode(dummyAssignment.getPosition(), false) {
@SuppressWarnings("unchecked")
@Override
public T accept(NodeVisitor visitor) {
return (T) rhs;
}
@Override
public List childNodes() {
return Collections.emptyList();
}
@Override
public org.jruby.truffle.parser.ast.NodeType getNodeType() {
return org.jruby.truffle.parser.ast.NodeType.FIXNUMNODE; // since we behave like a value
}
};
return setRHS(dummyAssignment, wrappedRHS).accept(this);
} else {
throw new UnsupportedOperationException("Don't know how to translate the dummy asgn " + dummyAssignment.getClass().getName());
}
}
@Override
public RubyNode visitGlobalAsgnNode(GlobalAsgnParseNode node) {
final SourceIndexLength sourceSection = node.getPosition();
RubyNode rhs = node.getValueNode().accept(this);
String name = node.getName();
if (GLOBAL_VARIABLE_ALIASES.containsKey(name)) {
name = GLOBAL_VARIABLE_ALIASES.get(name);
}
if (name.equals("$~")) {
rhs = new CheckMatchVariableTypeNode(rhs);
rhs = WrapInThreadLocalNodeGen.create(rhs);
rhs.unsafeSetSourceSection(sourceSection);
environment.declareVarInMethodScope("$~");
} else if (name.equals("$0")) {
rhs = new CheckProgramNameVariableTypeNode(rhs);
rhs.unsafeSetSourceSection(sourceSection);
} else if (name.equals("$/")) {
rhs = new CheckRecordSeparatorVariableTypeNode(rhs);
rhs.unsafeSetSourceSection(sourceSection);
} else if (name.equals("$,")) {
rhs = new CheckOutputSeparatorVariableTypeNode(rhs);
rhs.unsafeSetSourceSection(sourceSection);
} else if (name.equals("$_")) {
if (getSourcePath(sourceSection).endsWith(buildPartialPath("truffle", "rubysl", "rubysl-stringio", "lib", "rubysl", "stringio", "stringio.rb"))) {
rhs = RubiniusLastStringWriteNodeGen.create(rhs);
} else {
rhs = WrapInThreadLocalNodeGen.create(rhs);
}
rhs.unsafeSetSourceSection(sourceSection);
environment.declareVar("$_");
} else if (name.equals("$stdout")) {
rhs = new CheckStdoutVariableTypeNode(rhs);
rhs.unsafeSetSourceSection(sourceSection);
} else if (name.equals("$VERBOSE")) {
rhs = new UpdateVerbosityNode(rhs);
rhs.unsafeSetSourceSection(sourceSection);
} else if (name.equals("$@")) {
// $@ is a special-case and doesn't write directly to an ivar field in the globals object.
// Instead, it writes to the backtrace field of the thread-local $! value.
return withSourceSection(sourceSection, new UpdateLastBacktraceNode(rhs));
}
final boolean inCore = getSourcePath(node.getValueNode().getPosition()).startsWith(corePath());
if (!inCore && READ_ONLY_GLOBAL_VARIABLES.contains(name)) {
return addNewlineIfNeeded(node, withSourceSection(sourceSection, new WriteReadOnlyGlobalNode(name, rhs)));
}
if (THREAD_LOCAL_GLOBAL_VARIABLES.contains(name)) {
final ThreadLocalObjectNode threadLocalVariablesObjectNode = ThreadLocalObjectNodeGen.create();
threadLocalVariablesObjectNode.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, withSourceSection(sourceSection, new WriteInstanceVariableNode(name, threadLocalVariablesObjectNode, rhs)));
} else if (FRAME_LOCAL_GLOBAL_VARIABLES.contains(name)) {
if (environment.getNeverAssignInParentScope()) {
environment.declareVar(name);
}
ReadLocalNode localVarNode = environment.findLocalVarNode(node.getName(), source, sourceSection);
if (localVarNode == null) {
if (environment.hasOwnScopeForAssignments()) {
environment.declareVar(node.getName());
}
TranslatorEnvironment environmentToDeclareIn = environment;
while (!environmentToDeclareIn.hasOwnScopeForAssignments()) {
environmentToDeclareIn = environmentToDeclareIn.getParent();
}
environmentToDeclareIn.declareVar(node.getName());
localVarNode = environment.findLocalVarNode(node.getName(), source, sourceSection);
if (localVarNode == null) {
throw new RuntimeException("shouldn't be here");
}
}
RubyNode assignment = localVarNode.makeWriteNode(rhs);
if (name.equals("$_") || name.equals("$~")) {
// TODO CS 4-Jan-16 I can't work out why this is a *get* node
assignment = withSourceSection(sourceSection, new GetFromThreadLocalNode(assignment));
}
return addNewlineIfNeeded(node, assignment);
} else {
final RubyNode writeGlobalVariableNode = withSourceSection(sourceSection, WriteGlobalVariableNodeGen.create(name, rhs));
final RubyNode translated;
if (name.equals("$0")) {
// Call Process.setproctitle
RubyNode processClass = new ObjectLiteralNode(context.getCoreLibrary().getProcessModule());
translated = new RubyCallNode(new RubyCallNodeParameters(processClass, "setproctitle", null,
new RubyNode[]{writeGlobalVariableNode}, false, false));
translated.unsafeSetSourceSection(sourceSection);
} else {
translated = writeGlobalVariableNode;
}
return addNewlineIfNeeded(node, translated);
}
}
@Override
public RubyNode visitGlobalVarNode(GlobalVarParseNode node) {
String name = node.getName();
if (GLOBAL_VARIABLE_ALIASES.containsKey(name)) {
name = GLOBAL_VARIABLE_ALIASES.get(name);
}
final SourceIndexLength sourceSection = node.getPosition();
final SourceSection fullSourceSection = sourceSection.toSourceSection(source);
final RubyNode ret;
if (FRAME_LOCAL_GLOBAL_VARIABLES.contains(name)) {
// Assignment is implicit for many of these, so we need to declare when we use
RubyNode readNode = environment.findLocalVarNode(name, source, sourceSection);
if (readNode == null) {
environment.declareVarInMethodScope(name);
readNode = environment.findLocalVarNode(name, source, sourceSection);
}
if (name.equals("$_")) {
if (getSourcePath(sourceSection).equals(corePath() + "regexp.rb")) {
readNode = new RubiniusLastStringReadNode();
readNode.unsafeSetSourceSection(sourceSection);
} else {
readNode = new GetFromThreadLocalNode(readNode);
readNode.unsafeSetSourceSection(sourceSection);
}
} else if (name.equals("$~")) {
readNode = new GetFromThreadLocalNode(readNode);
readNode.unsafeSetSourceSection(sourceSection);
}
ret = readNode;
} else if (THREAD_LOCAL_GLOBAL_VARIABLES.contains(name)) {
ret = new ReadThreadLocalGlobalVariableNode(name, ALWAYS_DEFINED_GLOBALS.contains(name));
ret.unsafeSetSourceSection(sourceSection);
} else if (name.equals("$@")) {
// $@ is a special-case and doesn't read directly from an ivar field in the globals object.
// Instead, it reads the backtrace field of the thread-local $! value.
ret = new ReadLastBacktraceNode();
ret.unsafeSetSourceSection(sourceSection);
} else {
ret = ReadGlobalVariableNodeGen.create(name);
ret.unsafeSetSourceSection(sourceSection);
}
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitHashNode(HashParseNode node) {
final SourceIndexLength sourceSection = node.getPosition();
final List hashConcats = new ArrayList<>();
final List keyValues = new ArrayList<>();
for (Tuple pair: node.getPairs()) {
if (pair.getKey() == null) {
// This null case is for splats {a: 1, **{b: 2}, c: 3}
final RubyNode hashLiteralSoFar = HashLiteralNode.create(context, keyValues.toArray(new RubyNode[keyValues.size()]));
hashConcats.add(hashLiteralSoFar);
hashConcats.add(new EnsureSymbolKeysNode(
HashCastNodeGen.create(pair.getValue().accept(this))));
keyValues.clear();
} else {
keyValues.add(pair.getKey().accept(this));
if (pair.getValue() == null) {
keyValues.add(nilNode(source, sourceSection));
} else {
keyValues.add(pair.getValue().accept(this));
}
}
}
final RubyNode hashLiteralSoFar = HashLiteralNode.create(context, keyValues.toArray(new RubyNode[keyValues.size()]));
hashConcats.add(hashLiteralSoFar);
if (hashConcats.size() == 1) {
final RubyNode ret = hashConcats.get(0);
return addNewlineIfNeeded(node, ret);
}
final RubyNode ret = new ConcatHashLiteralNode(hashConcats.toArray(new RubyNode[hashConcats.size()]));
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitIfNode(IfParseNode node) {
final SourceIndexLength sourceSection = node.getPosition();
final RubyNode condition = translateNodeOrNil(sourceSection, node.getCondition());
ParseNode thenBody = node.getThenBody();
ParseNode elseBody = node.getElseBody();
final RubyNode ret;
if (thenBody != null && elseBody != null) {
final RubyNode thenBodyTranslated = thenBody.accept(this);
final RubyNode elseBodyTranslated = elseBody.accept(this);
ret = new IfElseNode(condition, thenBodyTranslated, elseBodyTranslated);
ret.unsafeSetSourceSection(sourceSection);
} else if (thenBody != null) {
final RubyNode thenBodyTranslated = thenBody.accept(this);
ret = new IfNode(condition, thenBodyTranslated);
ret.unsafeSetSourceSection(sourceSection);
} else if (elseBody != null) {
final RubyNode elseBodyTranslated = elseBody.accept(this);
ret = new UnlessNode(condition, elseBodyTranslated);
ret.unsafeSetSourceSection(sourceSection);
} else {
ret = sequence(sourceSection, Arrays.asList(condition, new NilLiteralNode(true)));
}
return ret; // no addNewlineIfNeeded(node, ret) as the condition will already have a newline
}
@Override
public RubyNode visitInstAsgnNode(InstAsgnParseNode node) {
final SourceIndexLength sourceSection = node.getPosition();
final String name = node.getName();
final RubyNode rhs;
if (node.getValueNode() == null) {
rhs = new DeadNode(new Exception("null RHS of instance variable assignment"));
rhs.unsafeSetSourceSection(sourceSection);
} else {
rhs = node.getValueNode().accept(this);
}
// Every case will use a SelfParseNode, just don't it use more than once.
// Also note the check for frozen.
final RubyNode self = new RaiseIfFrozenNode(new SelfNode(environment.getFrameDescriptor()));
final String path = getSourcePath(sourceSection);
final String corePath = corePath();
final RubyNode ret;
if (path.equals(corePath + "hash.rb")) {
if (name.equals("@default")) {
ret = HashNodesFactory.SetDefaultValueNodeFactory.create(self, rhs);
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
} else if (name.equals("@default_proc")) {
ret = HashNodesFactory.SetDefaultProcNodeFactory.create(self, rhs);
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
}
} else if (path.equals(corePath + "range.rb")) {
if (name.equals("@begin")) {
ret = RangeNodesFactory.InternalSetBeginNodeGen.create(self, rhs);
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
} else if (name.equals("@end")) {
ret = RangeNodesFactory.InternalSetEndNodeGen.create(self, rhs);
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
} else if (name.equals("@excl")) {
ret = RangeNodesFactory.InternalSetExcludeEndNodeGen.create(self, rhs);
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
}
} else if (path.equals(corePath + "io.rb")) {
// TODO (pitr 08-Aug-2015): values of predefined OM properties should be casted to defined types automatically
if (name.equals("@used") || name.equals("@total") || name.equals("@lineno")) {
// Cast int-fitting longs back to int
ret = new WriteInstanceVariableNode(name, self, IntegerCastNodeGen.create(rhs));
return addNewlineIfNeeded(node, ret);
}
}
ret = new WriteInstanceVariableNode(name, self, rhs);
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitInstVarNode(InstVarParseNode node) {
final SourceIndexLength sourceSection = node.getPosition();
final String name = node.getName();
// About every case will use a SelfParseNode, just don't it use more than once.
final SelfNode self = new SelfNode(environment.getFrameDescriptor());
final String path = getSourcePath(sourceSection);
final String corePath = corePath();
final RubyNode ret;
if (path.equals(corePath + "regexp.rb")) {
if (name.equals("@source")) {
ret = MatchDataNodesFactory.RubiniusSourceNodeGen.create(self);
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
} else if (name.equals("@regexp")) {
ret = MatchDataNodesFactory.RegexpNodeFactory.create(new RubyNode[]{ self });
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
}
}
ret = new ReadInstanceVariableNode(name, self);
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitIterNode(IterParseNode node) {
return translateBlockLikeNode(node, false);
}
@Override
public RubyNode visitLambdaNode(LambdaParseNode node) {
return translateBlockLikeNode(node, true);
}
private RubyNode translateBlockLikeNode(IterParseNode node, boolean isLambda) {
final SourceIndexLength sourceSection = node.getPosition();
final ArgsParseNode argsNode = node.getArgsNode();
// Unset this flag for any for any blocks within the for statement's body
final boolean hasOwnScope = isLambda || !translatingForStatement;
final boolean isProc = !isLambda;
final SharedMethodInfo sharedMethodInfo = new SharedMethodInfo(
sourceSection.toSourceSection(source),
environment.getLexicalScopeOrNull(),
MethodTranslator.getArity(argsNode),
null,
null,
isLambda ? "lambda" : getIdentifierInNewEnvironment(true, currentCallMethodName),
Helpers.argsNodeToArgumentDescriptors(argsNode),
false,
false,
false);
final String namedMethodName = isLambda ? sharedMethodInfo.getName(): environment.getNamedMethodName();
final ParseEnvironment parseEnvironment = environment.getParseEnvironment();
final ReturnID returnID = isLambda ? parseEnvironment.allocateReturnID() : environment.getReturnID();
final TranslatorEnvironment newEnvironment = new TranslatorEnvironment(
context, environment, parseEnvironment, returnID, hasOwnScope, false,
false, sharedMethodInfo, namedMethodName, environment.getBlockDepth() + 1, parseEnvironment.allocateBreakID());
final MethodTranslator methodCompiler = new MethodTranslator(currentNode, context, this, newEnvironment, true, source, argsNode);
if (isProc) {
methodCompiler.translatingForStatement = translatingForStatement;
}
methodCompiler.frameOnStackMarkerSlotStack = frameOnStackMarkerSlotStack;
final ProcType type = isLambda ? ProcType.LAMBDA : ProcType.PROC;
if (isLambda) {
frameOnStackMarkerSlotStack.push(BAD_FRAME_SLOT);
}
final RubyNode definitionNode;
try {
definitionNode = methodCompiler.compileBlockNode(sourceSection, sharedMethodInfo.getName(), node.getBodyNode(), sharedMethodInfo, type, node.getScope().getVariables());
} finally {
if (isLambda) {
frameOnStackMarkerSlotStack.pop();
}
}
return addNewlineIfNeeded(node, definitionNode);
}
@Override
public RubyNode visitLocalAsgnNode(LocalAsgnParseNode node) {
final SourceIndexLength sourceSection = node.getPosition();
if (environment.getNeverAssignInParentScope()) {
environment.declareVar(node.getName());
}
ReadLocalNode lhs = environment.findLocalVarNode(node.getName(), source, sourceSection);
if (lhs == null) {
if (environment.hasOwnScopeForAssignments()) {
environment.declareVar(node.getName());
} else {
TranslatorEnvironment environmentToDeclareIn = environment;
while (!environmentToDeclareIn.hasOwnScopeForAssignments()) {
environmentToDeclareIn = environmentToDeclareIn.getParent();
}
environmentToDeclareIn.declareVar(node.getName());
}
lhs = environment.findLocalVarNode(node.getName(), source, sourceSection);
if (lhs == null) {
throw new RuntimeException("shouldn't be here");
}
}
RubyNode rhs;
if (node.getValueNode() == null) {
rhs = new DeadNode(new Exception());
rhs.unsafeSetSourceSection(sourceSection);
} else {
rhs = node.getValueNode().accept(this);
}
final RubyNode ret = lhs.makeWriteNode(rhs);
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitLocalVarNode(LocalVarParseNode node) {
final SourceIndexLength sourceSection = node.getPosition();
final String name = node.getName();
RubyNode readNode = environment.findLocalVarNode(name, source, sourceSection);
if (readNode == null) {
/*
This happens for code such as:
def destructure4r((*c,d))
[c,d]
end
We're going to just assume that it should be there and add it...
*/
environment.declareVar(node.getName());
readNode = environment.findLocalVarNode(name, source, sourceSection);
}
return addNewlineIfNeeded(node, readNode);
}
@Override
public RubyNode visitMatchNode(MatchParseNode node) {
// Triggered when a Regexp literal is used as a conditional's value.
final ParseNode argsNode = buildArrayNode(node.getPosition(), new GlobalVarParseNode(node.getPosition(), "$_"));
final ParseNode callNode = new CallParseNode(node.getPosition(), node.getRegexpNode(), "=~", argsNode, null);
copyNewline(node, callNode);
final RubyNode ret = callNode.accept(this);
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitMatch2Node(Match2ParseNode node) {
// Triggered when a Regexp literal is the LHS of an expression.
if (node.getReceiverNode() instanceof RegexpParseNode) {
final RegexpParseNode regexpNode = (RegexpParseNode) node.getReceiverNode();
final byte[] bytes = regexpNode.getValue().getBytes();
final Regex regex = new Regex(bytes, 0, bytes.length, regexpNode.getOptions().toOptions(), regexpNode.getEncoding(), Syntax.RUBY);
if (regex.numberOfNames() > 0) {
for (Iterator i = regex.namedBackrefIterator(); i.hasNext(); ) {
final NameEntry e = i.next();
final String name = new String(e.name, e.nameP, e.nameEnd - e.nameP, StandardCharsets.UTF_8).intern();
TranslatorEnvironment environmentToDeclareIn = environment;
while (!environmentToDeclareIn.hasOwnScopeForAssignments()) {
environmentToDeclareIn = environmentToDeclareIn.getParent();
}
environmentToDeclareIn.declareVar(name);
}
}
}
final ParseNode argsNode = buildArrayNode(node.getPosition(), node.getValueNode());
final ParseNode callNode = new CallParseNode(node.getPosition(), node.getReceiverNode(), "=~", argsNode, null);
copyNewline(node, callNode);
final RubyNode ret = callNode.accept(this);
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitMatch3Node(Match3ParseNode node) {
// Triggered when a Regexp literal is the RHS of an expression.
final ParseNode argsNode = buildArrayNode(node.getPosition(), node.getValueNode());
final ParseNode callNode = new CallParseNode(node.getPosition(), node.getReceiverNode(), "=~", argsNode, null);
copyNewline(node, callNode);
final RubyNode ret = callNode.accept(this);
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitModuleNode(ModuleParseNode node) {
final SourceIndexLength sourceSection = node.getPosition();
final String name = node.getCPath().getName();
RubyNode lexicalParent = translateCPath(sourceSection, node.getCPath());
final DefineModuleNode defineModuleNode = DefineModuleNodeGen.create(name, lexicalParent);
final RubyNode ret = openModule(sourceSection, defineModuleNode, name, node.getBodyNode(), false);
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitMultipleAsgnNode(MultipleAsgnParseNode node) {
final SourceIndexLength sourceSection = node.getPosition();
final ListParseNode preArray = node.getPre();
final ListParseNode postArray = node.getPost();
final ParseNode rhs = node.getValueNode();
RubyNode rhsTranslated;
if (rhs == null) {
throw new UnsupportedOperationException("null rhs");
} else {
rhsTranslated = rhs.accept(this);
}
final RubyNode result;
// TODO CS 5-Jan-15 we shouldn't be doing this kind of low level optimisation or pattern matching - EA should do it for us
if (preArray != null
&& node.getPost() == null
&& node.getRest() == null
&& rhsTranslated instanceof ArrayLiteralNode
&& ((ArrayLiteralNode) rhsTranslated).getSize() == preArray.size()) {
/*
* We can deal with this common case be rewriting
*
* a, b = c, d
*
* 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 ArrayLiteralNode rhsArrayLiteral = (ArrayLiteralNode) rhsTranslated;
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("multi");
final ReadLocalNode readTemp = environment.findLocalVarNode(tempName, source, sourceSection);
final RubyNode assignTemp = readTemp.makeWriteNode(rhsArrayLiteral.stealNode(n));
final RubyNode assignFinalValue = translateDummyAssignment(preArray.get(n), NodeUtil.cloneNode(readTemp));
sequence[n] = assignTemp;
sequence[assignedValuesCount + n] = assignFinalValue;
tempValues[n] = NodeUtil.cloneNode(readTemp);
}
final RubyNode blockNode = sequence(sourceSection, Arrays.asList(sequence));
final ArrayLiteralNode arrayNode = ArrayLiteralNode.create(tempValues);
arrayNode.unsafeSetSourceSection(sourceSection);
result = new ElidableResultNode(blockNode, arrayNode);
} else if (preArray != null) {
/*
* The other simple case is
*
* a, b, c = x
*
* If x is an array, then it's
*
* a = 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.
*
* In either case, we return the RHS
*/
final List sequence = new ArrayList<>();
/*
* Store the RHS in a temp.
*/
final String tempRHSName = environment.allocateLocalTemp("rhs");
final RubyNode writeTempRHS = environment.findLocalVarNode(tempRHSName, source, sourceSection).makeWriteNode(rhsTranslated);
sequence.add(writeTempRHS);
/*
* Create a temp for the array.
*/
final String tempName = environment.allocateLocalTemp("array");
/*
* Create a sequence of instructions, with the first being the literal array assigned to
* the temp.
*/
final RubyNode splatCastNode = SplatCastNodeGen.create(translatingNextExpression ? SplatCastNode.NilBehavior.EMPTY_ARRAY : SplatCastNode.NilBehavior.ARRAY_WITH_NIL, true, environment.findLocalVarNode(tempRHSName, source, sourceSection));
splatCastNode.unsafeSetSourceSection(sourceSection);
final RubyNode writeTemp = environment.findLocalVarNode(tempName, source, 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 RubyNode assignedValue = PrimitiveArrayNodeFactory.read(environment.findLocalVarNode(tempName, source, sourceSection), n);
sequence.add(translateDummyAssignment(preArray.get(n), assignedValue));
}
if (node.getRest() != null) {
RubyNode assignedValue = ArrayGetTailNodeGen.create(preArray.size(), environment.findLocalVarNode(tempName, source, sourceSection));
if (postArray != null) {
assignedValue = ArrayDropTailNodeGen.create(postArray.size(), assignedValue);
}
sequence.add(translateDummyAssignment(node.getRest(), assignedValue));
}
if (postArray != null) {
final List smallerSequence = new ArrayList<>();
for (int n = 0; n < postArray.size(); n++) {
final RubyNode assignedValue = PrimitiveArrayNodeFactory.read(environment.findLocalVarNode(tempName, source, sourceSection), node.getPreCount() + n);
smallerSequence.add(translateDummyAssignment(postArray.get(n), assignedValue));
}
final RubyNode smaller = sequence(sourceSection, smallerSequence);
final List atLeastAsLargeSequence = new ArrayList<>();
for (int n = 0; n < postArray.size(); n++) {
final RubyNode assignedValue = PrimitiveArrayNodeFactory.read(environment.findLocalVarNode(tempName, source, sourceSection), -(postArray.size() - n));
atLeastAsLargeSequence.add(translateDummyAssignment(postArray.get(n), assignedValue));
}
final RubyNode atLeastAsLarge = sequence(sourceSection, atLeastAsLargeSequence);
final RubyNode assignPost =
new IfElseNode(
new ArrayIsAtLeastAsLargeAsNode(node.getPreCount() + node.getPostCount(), environment.findLocalVarNode(tempName, source, sourceSection)),
atLeastAsLarge,
smaller);
sequence.add(assignPost);
}
result = new ElidableResultNode(sequence(sourceSection, sequence), environment.findLocalVarNode(tempRHSName, source, sourceSection));
} else if (node.getPre() == null
&& node.getPost() == null
&& node.getRest() instanceof StarParseNode) {
result = rhsTranslated;
} else if (node.getPre() == null
&& node.getPost() == null
&& node.getRest() != null
&& rhs != null
&& !(rhs instanceof ArrayParseNode)) {
/*
* *a = b
*
* >= 1.8, this seems to be the same as:
*
* a = *b
*/
final List sequence = new ArrayList<>();
SplatCastNode.NilBehavior nilBehavior;
if (translatingNextExpression) {
nilBehavior = SplatCastNode.NilBehavior.EMPTY_ARRAY;
} else {
if (rhsTranslated instanceof SplatCastNode && ((SplatCastNodeGen) rhsTranslated).getChild() instanceof NilLiteralNode) {
rhsTranslated = ((SplatCastNodeGen) rhsTranslated).getChild();
nilBehavior = SplatCastNode.NilBehavior.CONVERT;
} else {
nilBehavior = SplatCastNode.NilBehavior.ARRAY_WITH_NIL;
}
}
final String tempRHSName = environment.allocateLocalTemp("rhs");
final RubyNode writeTempRHS = environment.findLocalVarNode(tempRHSName, source, sourceSection).makeWriteNode(rhsTranslated);
sequence.add(writeTempRHS);
final SplatCastNode rhsSplatCast = SplatCastNodeGen.create(
nilBehavior,
true, environment.findLocalVarNode(tempRHSName, source, sourceSection));
rhsSplatCast.unsafeSetSourceSection(sourceSection);
final String tempRHSSplattedName = environment.allocateLocalTemp("rhs");
final RubyNode writeTempSplattedRHS = environment.findLocalVarNode(tempRHSSplattedName, source, sourceSection).makeWriteNode(rhsSplatCast);
sequence.add(writeTempSplattedRHS);
sequence.add(translateDummyAssignment(node.getRest(), environment.findLocalVarNode(tempRHSSplattedName, source, sourceSection)));
final RubyNode assignmentResult;
if (nilBehavior == SplatCastNode.NilBehavior.CONVERT) {
assignmentResult = environment.findLocalVarNode(tempRHSSplattedName, source, sourceSection);
} else {
assignmentResult = environment.findLocalVarNode(tempRHSName, source, sourceSection);
}
result = new ElidableResultNode(sequence(sourceSection, sequence), assignmentResult);
} else if (node.getPre() == null
&& node.getPost() == null
&& node.getRest() != null
&& rhs != null
&& rhs instanceof ArrayParseNode) {
/*
* *a = [b, c]
*
* This seems to be the same as:
*
* a = [b, c]
*/
result = translateDummyAssignment(node.getRest(), rhsTranslated);
} else if (node.getPre() == null && node.getRest() != null && node.getPost() != null) {
/*
* Something like
*
* *a,b = [1, 2, 3, 4]
*/
// This is very similar to the case with pre and rest, so unify with that
final List sequence = new ArrayList<>();
final String tempRHSName = environment.allocateLocalTemp("rhs");
final RubyNode writeTempRHS = environment.findLocalVarNode(tempRHSName, source, sourceSection).makeWriteNode(rhsTranslated);
sequence.add(writeTempRHS);
/*
* Create a temp for the array.
*/
final String tempName = environment.allocateLocalTemp("array");
/*
* Create a sequence of instructions, with the first being the literal array assigned to
* the temp.
*/
final RubyNode splatCastNode = SplatCastNodeGen.create(translatingNextExpression ? SplatCastNode.NilBehavior.EMPTY_ARRAY : SplatCastNode.NilBehavior.ARRAY_WITH_NIL, false, environment.findLocalVarNode(tempRHSName, source, sourceSection));
splatCastNode.unsafeSetSourceSection(sourceSection);
final RubyNode writeTemp = environment.findLocalVarNode(tempName, source, sourceSection).makeWriteNode(splatCastNode);
sequence.add(writeTemp);
/*
* Then index the temp array for each assignment on the LHS.
*/
if (node.getRest() != null) {
final ArrayDropTailNode assignedValue = ArrayDropTailNodeGen.create(postArray.size(), environment.findLocalVarNode(tempName, source, sourceSection));
sequence.add(translateDummyAssignment(node.getRest(), assignedValue));
}
final List smallerSequence = new ArrayList<>();
for (int n = 0; n < postArray.size(); n++) {
final RubyNode assignedValue = PrimitiveArrayNodeFactory.read(environment.findLocalVarNode(tempName, source, sourceSection), node.getPreCount() + n);
smallerSequence.add(translateDummyAssignment(postArray.get(n), assignedValue));
}
final RubyNode smaller = sequence(sourceSection, smallerSequence);
final List atLeastAsLargeSequence = new ArrayList<>();
for (int n = 0; n < postArray.size(); n++) {
final RubyNode assignedValue = PrimitiveArrayNodeFactory.read(environment.findLocalVarNode(tempName, source, sourceSection), -(postArray.size() - n));
atLeastAsLargeSequence.add(translateDummyAssignment(postArray.get(n), assignedValue));
}
final RubyNode atLeastAsLarge = sequence(sourceSection, atLeastAsLargeSequence);
final RubyNode assignPost =
new IfElseNode(
new ArrayIsAtLeastAsLargeAsNode(node.getPreCount() + node.getPostCount(), environment.findLocalVarNode(tempName, source, sourceSection)),
atLeastAsLarge,
smaller);
sequence.add(assignPost);
result = new ElidableResultNode(sequence(sourceSection, sequence), environment.findLocalVarNode(tempRHSName, source, sourceSection));
} else {
throw new UnsupportedOperationException();
}
final RubyNode ret = new DefinedWrapperNode(context.getCoreStrings().ASSIGNMENT, result);
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitNextNode(NextParseNode node) {
final SourceIndexLength sourceSection = node.getPosition();
if (!environment.isBlock() && !translatingWhile) {
throw new RaiseException(context.getCoreExceptions().syntaxError("Invalid next", currentNode));
}
final RubyNode resultNode;
final boolean t = translatingNextExpression;
translatingNextExpression = true;
try {
resultNode = translateNodeOrNil(sourceSection, node.getValueNode());
} finally {
translatingNextExpression = t;
}
final RubyNode ret = new NextNode(resultNode);
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitNilNode(NilParseNode node) {
if (node instanceof NilImplicitParseNode) {
final RubyNode ret = new NilLiteralNode(true);
ret.unsafeSetSourceSection(node.getPosition());
return addNewlineIfNeeded(node, ret);
}
if (node.getPosition() == null) {
final RubyNode ret = new DeadNode(new Exception());
return addNewlineIfNeeded(node, ret);
}
SourceIndexLength sourceSection = node.getPosition();
final RubyNode ret = nilNode(source, sourceSection);
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitNthRefNode(NthRefParseNode node) {
final SourceIndexLength sourceSection = node.getPosition();
environment.declareVarInMethodScope("$~");
final GetFromThreadLocalNode readMatchNode = new GetFromThreadLocalNode(environment.findLocalVarNode("$~", source, sourceSection));
final RubyNode ret = new ReadMatchReferenceNode(readMatchNode, node.getMatchNumber());
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitOpAsgnAndNode(OpAsgnAndParseNode node) {
return translateOpAsgnAndNode(node, node.getFirstNode().accept(this), node.getSecondNode().accept(this));
}
private RubyNode translateOpAsgnAndNode(ParseNode node, RubyNode lhs, RubyNode rhs) {
/*
* This doesn't translate as you might expect!
*
* http://www.rubyinside.com/what-rubys-double-pipe-or-equals-really-does-5488.html
*/
final SourceIndexLength sourceSection = node.getPosition();
final SourceSection fullSourceSection = sourceSection.toSourceSection(source);
final RubyNode andNode = new AndNode(lhs, rhs);
andNode.unsafeSetSourceSection(sourceSection);
final RubyNode ret = new DefinedWrapperNode(context.getCoreStrings().ASSIGNMENT, andNode);
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitOpAsgnConstDeclNode(OpAsgnConstDeclParseNode node) {
RubyNode lhs = node.getFirstNode().accept(this);
RubyNode rhs = node.getSecondNode().accept(this);
if (!(rhs instanceof WriteConstantNode)) {
rhs = ((ReadConstantNode) lhs).makeWriteNode(rhs);
}
switch (node.getOperator()) {
case "&&": {
return translateOpAsgnAndNode(node, lhs, rhs);
}
case "||": {
final RubyNode defined = new DefinedNode(lhs);
lhs = new AndNode(defined, lhs);
return translateOpAsgOrNode(node, lhs, rhs);
}
default: {
final SourceIndexLength sourceSection = node.getPosition();
final RubyCallNodeParameters callParameters = new RubyCallNodeParameters(lhs, node.getOperator(), null, new RubyNode[] { rhs }, false, true);
final RubyNode opNode = context.getCoreMethods().createCallNode(source, callParameters);
final RubyNode ret = ((ReadConstantNode) lhs).makeWriteNode(opNode);
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
}
}
}
@Override
public RubyNode visitOpAsgnNode(OpAsgnParseNode node) {
final SourceIndexLength pos = node.getPosition();
final boolean isOrOperator = node.getOperatorName().equals("||");
if (isOrOperator || node.getOperatorName().equals("&&")) {
// Why does this ||= or &&= come through as a visitOpAsgnNode and not a visitOpAsgnOrNode?
final String temp = environment.allocateLocalTemp("opassign");
final ParseNode writeReceiverToTemp = new LocalAsgnParseNode(pos, temp, 0, node.getReceiverNode());
final ParseNode readReceiverFromTemp = new LocalVarParseNode(pos, 0, temp);
final ParseNode readMethod = new CallParseNode(pos, readReceiverFromTemp, node.getVariableName(), null, null);
final ParseNode writeMethod = new CallParseNode(pos, readReceiverFromTemp, node.getVariableName() + "=", buildArrayNode(pos,
node.getValueNode()), null);
final SourceIndexLength sourceSection = pos;
final SourceSection fullSourceSection = sourceSection.toSourceSection(source);
RubyNode lhs = readMethod.accept(this);
RubyNode rhs = writeMethod.accept(this);
final RubyNode controlNode = isOrOperator ? new OrNode(lhs, rhs) : new AndNode(lhs, rhs);
final RubyNode ret = new DefinedWrapperNode(
context.getCoreStrings().ASSIGNMENT,
sequence(
sourceSection,
Arrays.asList(writeReceiverToTemp.accept(this), controlNode)));
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
}
/*
* 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("opassign");
final ParseNode writeReceiverToTemp = new LocalAsgnParseNode(pos, temp, 0, node.getReceiverNode());
final ParseNode readReceiverFromTemp = new LocalVarParseNode(pos, 0, temp);
final ParseNode readMethod = new CallParseNode(pos, readReceiverFromTemp, node.getVariableName(), null, null);
final ParseNode operation = new CallParseNode(pos, readMethod, node.getOperatorName(),
buildArrayNode(pos, node.getValueNode()), null);
final ParseNode writeMethod = new CallParseNode(pos, readReceiverFromTemp, node.getVariableName() + "=",
buildArrayNode(pos, operation), null);
final BlockParseNode block = new BlockParseNode(pos);
block.add(writeReceiverToTemp);
final RubyNode writeTemp = writeReceiverToTemp.accept(this);
RubyNode body = writeMethod.accept(this);
final SourceIndexLength sourceSection = pos;
final SourceSection fullSourceSection = sourceSection.toSourceSection(source);
if (node.isLazy()) {
ReadLocalNode readLocal = environment.findLocalVarNode(temp, source, sourceSection);
body = new IfNode(
new NotNode(new IsNilNode(readLocal)),
body);
body.unsafeSetSourceSection(sourceSection);
}
final RubyNode ret = sequence(sourceSection, Arrays.asList(writeTemp, body));
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitOpAsgnOrNode(OpAsgnOrParseNode node) {
RubyNode lhs = node.getFirstNode().accept(this);
RubyNode rhs = node.getSecondNode().accept(this);
// This is needed for class variables. Constants are handled separately in visitOpAsgnConstDeclNode.
if (node.getFirstNode().needsDefinitionCheck() && !(node.getFirstNode() instanceof InstVarParseNode)) {
RubyNode defined = new DefinedNode(lhs);
lhs = new AndNode(defined, lhs);
}
return translateOpAsgOrNode(node, lhs, rhs);
}
private RubyNode translateOpAsgOrNode(ParseNode node, RubyNode lhs, RubyNode rhs) {
/*
* This doesn't translate as you might expect!
*
* http://www.rubyinside.com/what-rubys-double-pipe-or-equals-really-does-5488.html
*/
final SourceIndexLength sourceSection = node.getPosition();
final RubyNode ret = new DefinedWrapperNode(context.getCoreStrings().ASSIGNMENT,
new OrNode(lhs, rhs));
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitOpElementAsgnNode(OpElementAsgnParseNode node) {
/*
* We're going to de-sugar a[b] += c into a[b] = a[b] + c. See discussion in
* visitOpAsgnNode.
*/
ParseNode index;
if (node.getArgsNode() == null) {
index = null;
} else {
index = node.getArgsNode().childNodes().get(0);
}
final ParseNode operand = node.getValueNode();
final String temp = environment.allocateLocalTemp("opelementassign");
final ParseNode writeArrayToTemp = new LocalAsgnParseNode(node.getPosition(), temp, 0, node.getReceiverNode());
final ParseNode readArrayFromTemp = new LocalVarParseNode(node.getPosition(), 0, temp);
final ParseNode arrayRead = new CallParseNode(node.getPosition(), readArrayFromTemp, "[]", buildArrayNode(node.getPosition(), index), null);
final String op = node.getOperatorName();
ParseNode operation = null;
if (op.equals("||")) {
operation = new OrParseNode(node.getPosition(), arrayRead, operand);
} else if (op.equals("&&")) {
operation = new AndParseNode(node.getPosition(), arrayRead, operand);
} else {
operation = new CallParseNode(node.getPosition(), arrayRead, node.getOperatorName(), buildArrayNode(node.getPosition(), operand), null);
}
copyNewline(node, operation);
final ParseNode arrayWrite = new CallParseNode(node.getPosition(), readArrayFromTemp, "[]=", buildArrayNode(node.getPosition(), index, operation), null);
final BlockParseNode block = new BlockParseNode(node.getPosition());
block.add(writeArrayToTemp);
block.add(arrayWrite);
final RubyNode ret = block.accept(this);
return addNewlineIfNeeded(node, ret);
}
private static ArrayParseNode buildArrayNode(SourceIndexLength sourcePosition, ParseNode first, ParseNode... rest) {
if (first == null) {
return new ArrayParseNode(sourcePosition);
}
final ArrayParseNode array = new ArrayParseNode(sourcePosition, first);
for (ParseNode node : rest) {
array.add(node);
}
return array;
}
@Override
public RubyNode visitOrNode(OrParseNode node) {
final SourceIndexLength sourceSection = node.getPosition();
final RubyNode x = translateNodeOrNil(sourceSection, node.getFirstNode());
final RubyNode y = translateNodeOrNil(sourceSection, node.getSecondNode());
final RubyNode ret = new OrNode(x, y);
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitPreExeNode(PreExeParseNode node) {
// The parser seems to visit BEGIN blocks for us first, so we just need to translate them in place
final RubyNode ret = node.getBodyNode().accept(this);
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitPostExeNode(PostExeParseNode node) {
// END blocks run after any other code - not just code in the same file
// Turn into a call to Truffle::Kernel.at_exit
// The scope is empty - we won't be able to access local variables
// TODO fix this
// https://github.com/jruby/jruby/issues/4257
final StaticScope scope = new StaticScope(StaticScope.Type.BLOCK, null);
return translateCallNode(
new CallParseNode(node.getPosition(),
new TruffleFragmentParseNode(node.getPosition(), false, new ObjectLiteralNode(context.getCoreLibrary().getTruffleKernelModule())),
"at_exit",
new ListParseNode(node.getPosition(), new TrueParseNode(node.getPosition())),
new IterParseNode(node.getPosition(), node.getArgsNode(), scope, node.getBodyNode())),
false, false, false);
}
@Override
public RubyNode visitRationalNode(RationalParseNode node) {
final SourceIndexLength sourceSection = node.getPosition();
// TODO(CS): use IntFixnumLiteralNode where possible
final RubyNode ret = translateRationalComplex(sourceSection, "Rational",
new LongFixnumLiteralNode(node.getNumerator()),
new LongFixnumLiteralNode(node.getDenominator()));
return addNewlineIfNeeded(node, ret);
}
private RubyNode translateRationalComplex(SourceIndexLength sourceSection, String name, RubyNode a, RubyNode b) {
// Translate as Truffle.privately { Rational.convert(a, b) }
final RubyNode moduleNode = new ObjectLiteralNode(context.getCoreLibrary().getObjectClass());
ReadConstantNode receiver = new ReadConstantNode(moduleNode, name);
RubyNode[] arguments = new RubyNode[] { a, b };
RubyCallNodeParameters parameters = new RubyCallNodeParameters(receiver, "convert", null, arguments, false, true);
return withSourceSection(sourceSection, new RubyCallNode(parameters));
}
@Override
public RubyNode visitRedoNode(RedoParseNode node) {
if (!environment.isBlock() && !translatingWhile) {
throw new RaiseException(context.getCoreExceptions().syntaxError("Invalid redo", currentNode));
}
final RubyNode ret = new RedoNode();
ret.unsafeSetSourceSection(node.getPosition());
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitRegexpNode(RegexpParseNode node) {
final Rope rope = node.getValue().toRope();
final RegexpOptions options = node.getOptions();
options.setLiteral(true);
Regex regex = RegexpNodes.compile(currentNode, context, rope, options);
// The RegexpNodes.compile operation may modify the encoding of the source rope. This modified copy is stored
// in the Regex object as the "user object". Since ropes are immutable, we need to take this updated copy when
// constructing the final regexp.
final Rope updatedRope = (Rope) regex.getUserObject();
final DynamicObject regexp = RegexpNodes.createRubyRegexp(context.getCoreLibrary().getRegexpFactory(), regex, updatedRope, options);
final ObjectLiteralNode literalNode = new ObjectLiteralNode(regexp);
literalNode.unsafeSetSourceSection(node.getPosition());
return addNewlineIfNeeded(node, literalNode);
}
public static boolean all7Bit(byte[] bytes) {
for (int n = 0; n < bytes.length; n++) {
if (bytes[n] < 0) {
return false;
}
if (bytes[n] == '\\' && n + 1 < bytes.length && bytes[n + 1] == 'x') {
final String num;
final boolean isSecondHex = n + 3 < bytes.length && Character.digit(bytes[n + 3], 16) != -1;
if (isSecondHex) {
num = new String(Arrays.copyOfRange(bytes, n + 2, n + 4), StandardCharsets.UTF_8);
} else {
num = new String(Arrays.copyOfRange(bytes, n + 2, n + 3), StandardCharsets.UTF_8);
}
int b = Integer.parseInt(num, 16);
if (b > 0x7F) {
return false;
}
if (isSecondHex) {
n += 3;
} else {
n += 2;
}
}
}
return true;
}
@Override
public RubyNode visitRescueNode(RescueParseNode node) {
final SourceIndexLength sourceSection = node.getPosition();
final SourceSection fullSourceSection = sourceSection.toSourceSection(source);
RubyNode tryPart;
if (node.getBodyNode() == null || node.getBodyNode().getPosition() == null) {
tryPart = nilNode(source, sourceSection);
} else {
tryPart = node.getBodyNode().accept(this);
}
final List rescueNodes = new ArrayList<>();
RescueBodyParseNode rescueBody = node.getRescueNode();
if (context.getOptions().BACKTRACES_OMIT_UNUSED
&& rescueBody != null
&& rescueBody.getExceptionNodes() == null
&& rescueBody.getBodyNode() instanceof SideEffectFree
// allow `expression rescue $!` pattern
&& (!(rescueBody.getBodyNode() instanceof GlobalVarParseNode) || !((GlobalVarParseNode) rescueBody.getBodyNode()).getName().equals("$!"))
&& rescueBody.getOptRescueNode() == null) {
tryPart = new DisablingBacktracesNode(tryPart);
RubyNode bodyNode;
if (rescueBody.getBodyNode() == null || rescueBody.getBodyNode().getPosition() == null) {
bodyNode = nilNode(source, sourceSection);
} else {
bodyNode = rescueBody.getBodyNode().accept(this);
}
final RescueAnyNode rescueNode = new RescueAnyNode(bodyNode);
rescueNodes.add(rescueNode);
} else {
while (rescueBody != null) {
if (rescueBody.getExceptionNodes() != null) {
final Deque exceptionNodes = new ArrayDeque<>();
exceptionNodes.push(rescueBody.getExceptionNodes());
while (! exceptionNodes.isEmpty()) {
final ParseNode exceptionNode = exceptionNodes.pop();
if (exceptionNode instanceof ArrayParseNode) {
final RescueNode rescueNode = translateRescueArrayParseNode((ArrayParseNode) exceptionNode, rescueBody, sourceSection, fullSourceSection);
rescueNodes.add(rescueNode);
} else if (exceptionNode instanceof SplatParseNode) {
final RescueNode rescueNode = translateRescueSplatParseNode((SplatParseNode) exceptionNode, rescueBody, sourceSection, fullSourceSection);
rescueNodes.add(rescueNode);
} else if (exceptionNode instanceof ArgsCatParseNode) {
final ArgsCatParseNode argsCat = (ArgsCatParseNode) exceptionNode;
exceptionNodes.push(new SplatParseNode(argsCat.getSecondNode().getPosition(), argsCat.getSecondNode()));
exceptionNodes.push(argsCat.getFirstNode());
} else if (exceptionNode instanceof ArgsPushParseNode) {
final ArgsPushParseNode argsPush = (ArgsPushParseNode) exceptionNode;
exceptionNodes.push(new ArrayParseNode(argsPush.getSecondNode().getPosition(), argsPush.getSecondNode()));
exceptionNodes.push(argsPush.getFirstNode());
} else {
throw new UnsupportedOperationException();
}
}
} else {
RubyNode bodyNode;
if (rescueBody.getBodyNode() == null || rescueBody.getBodyNode().getPosition() == null) {
bodyNode = nilNode(source, sourceSection);
} else {
bodyNode = rescueBody.getBodyNode().accept(this);
}
final RescueAnyNode rescueNode = new RescueAnyNode(bodyNode);
rescueNodes.add(rescueNode);
}
rescueBody = rescueBody.getOptRescueNode();
}
}
RubyNode elsePart;
if (node.getElseNode() == null || node.getElseNode().getPosition() == null) {
elsePart = null; //nilNode(sourceSection);
} else {
elsePart = node.getElseNode().accept(this);
}
final RubyNode ret = new TryNode(
new ExceptionTranslatingNode(tryPart, UnsupportedOperationBehavior.TYPE_ERROR),
rescueNodes.toArray(new RescueNode[rescueNodes.size()]), elsePart);
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
}
private RescueNode translateRescueArrayParseNode(ArrayParseNode arrayParse, RescueBodyParseNode rescueBody, SourceIndexLength sourceSection, SourceSection fullSourceSection) {
final ParseNode[] exceptionNodes = arrayParse.children();
final RubyNode[] handlingClasses = new RubyNode[exceptionNodes.length];
for (int n = 0; n < handlingClasses.length; n++) {
handlingClasses[n] = exceptionNodes[n].accept(this);
}
RubyNode translatedBody;
if (rescueBody.getBodyNode() == null || rescueBody.getBodyNode().getPosition() == null) {
translatedBody = nilNode(source, sourceSection);
} else {
translatedBody = rescueBody.getBodyNode().accept(this);
}
return withSourceSection(sourceSection, new RescueClassesNode(handlingClasses, translatedBody));
}
private RescueNode translateRescueSplatParseNode(SplatParseNode splat, RescueBodyParseNode rescueBody, SourceIndexLength sourceSection, SourceSection fullSourceSection) {
final RubyNode splatTranslated = translateNodeOrNil(sourceSection, splat.getValue());
RubyNode rescueBodyTranslated;
if (rescueBody.getBodyNode() == null || rescueBody.getBodyNode().getPosition() == null) {
rescueBodyTranslated = nilNode(source, sourceSection);
} else {
rescueBodyTranslated = rescueBody.getBodyNode().accept(this);
}
return withSourceSection(sourceSection, new RescueSplatNode(splatTranslated, rescueBodyTranslated));
}
@Override
public RubyNode visitRetryNode(RetryParseNode node) {
final RubyNode ret = new RetryNode();
ret.unsafeSetSourceSection(node.getPosition());
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitReturnNode(ReturnParseNode node) {
final SourceIndexLength sourceSection = node.getPosition();
RubyNode translatedChild = translateNodeOrNil(sourceSection, node.getValueNode());
final RubyNode ret = new ReturnNode(environment.getReturnID(), translatedChild);
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitSClassNode(SClassParseNode node) {
final SourceIndexLength sourceSection = node.getPosition();
final SourceSection fullSourceSection = sourceSection.toSourceSection(source);
final RubyNode receiverNode = node.getReceiverNode().accept(this);
final SingletonClassNode singletonClassNode = SingletonClassNodeGen.create(receiverNode);
final boolean dynamicConstantLookup = environment.getParseEnvironment().isDynamicConstantLookup();
if (!dynamicConstantLookup) {
if (environment.isModuleBody() && node.getReceiverNode() instanceof SelfParseNode) {
// Common case of class << self in a module body, the constant lookup scope is still static
} else if (environment.parent == null && environment.isModuleBody()) {
// At the top-level of a file, opening the singleton class of a single expression
} else {
// Switch to dynamic constant lookup
environment.getParseEnvironment().setDynamicConstantLookup(true);
if (context.getOptions().LOG_DYNAMIC_CONSTANT_LOOKUP) {
Log.LOGGER.info(() -> "start dynamic constant lookup at " + RubyLanguage.fileLine(fullSourceSection));
}
}
}
final RubyNode ret;
try {
ret = openModule(sourceSection, singletonClassNode, "(singleton-def)", node.getBodyNode(), true);
} finally {
environment.getParseEnvironment().setDynamicConstantLookup(dynamicConstantLookup);
}
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitSValueNode(SValueParseNode node) {
final RubyNode ret = node.getValue().accept(this);
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitSelfNode(SelfParseNode node) {
final RubyNode ret = new SelfNode(environment.getFrameDescriptor());
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitSplatNode(SplatParseNode node) {
final SourceIndexLength sourceSection = node.getPosition();
final RubyNode value = translateNodeOrNil(sourceSection, node.getValue());
final RubyNode ret = SplatCastNodeGen.create(SplatCastNode.NilBehavior.CONVERT, false, value);
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitStrNode(StrParseNode node) {
final SourceIndexLength sourceSection = node.getPosition();
final ParserByteList byteList = node.getValue();
final CodeRange codeRange = node.getCodeRange();
final Rope rope = context.getRopeTable().getRope(byteList, codeRange);
final RubyNode ret;
if (node.isFrozen() && !getSourcePath(sourceSection).startsWith(context.getCoreLibrary().getCoreLoadPath() + "/core/")) {
final DynamicObject frozenString = context.getFrozenStrings().getFrozenString(rope);
ret = new DefinedWrapperNode(context.getCoreStrings().METHOD,
new ObjectLiteralNode(frozenString));
} else {
ret = new StringLiteralNode(rope);
}
ret.unsafeSetSourceSection(node.getPosition());
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitSymbolNode(SymbolParseNode node) {
String name = node.getName();
// The symbol is passed as a String but it's really
// "interpret the char[] as a byte[] with the given encoding".
byte[] bytes = new byte[name.length()];
for (int i = 0; i < name.length(); i++) {
char val = name.charAt(i);
assert val >= 0 && val < 256;
bytes[i] = (byte) (val & 0xFF);
}
final Rope rope = RopeOperations.create(bytes, node.getEncoding(), CodeRange.CR_UNKNOWN);
final RubyNode ret = new ObjectLiteralNode(context.getSymbolTable().getSymbol(rope));
ret.unsafeSetSourceSection(node.getPosition());
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitTrueNode(TrueParseNode node) {
final SourceIndexLength sourceSection = node.getPosition();
final RubyNode ret = new BooleanLiteralNode(true);
ret.unsafeSetSourceSection(node.getPosition());
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitUndefNode(UndefParseNode node) {
final SourceIndexLength sourceSection = node.getPosition();
final DynamicObject nameSymbol = translateNameNodeToSymbol(node.getName());
final RubyNode ret = ModuleNodesFactory.UndefMethodNodeFactory.create(new RubyNode[]{
new RaiseIfFrozenNode(new GetDefaultDefineeNode()),
new ObjectLiteralNode(new Object[]{ nameSymbol })
});
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitUntilNode(UntilParseNode node) {
WhileParseNode whileNode = new WhileParseNode(node.getPosition(), node.getConditionNode(), node.getBodyNode(), node.evaluateAtStart());
copyNewline(node, whileNode);
final RubyNode ret = translateWhileNode(whileNode, true);
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitVCallNode(VCallParseNode node) {
final SourceIndexLength sourceSection = node.getPosition();
if (node.getName().equals("undefined") && getSourcePath(sourceSection).startsWith(corePath())) { // translate undefined
final RubyNode ret = new ObjectLiteralNode(NotProvided.INSTANCE);
ret.unsafeSetSourceSection(node.getPosition());
return addNewlineIfNeeded(node, ret);
}
final ParseNode receiver = new SelfParseNode(node.getPosition());
final CallParseNode callNode = new CallParseNode(node.getPosition(), receiver, node.getName(), null, null);
copyNewline(node, callNode);
final RubyNode ret = translateCallNode(callNode, true, true, false);
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitWhileNode(WhileParseNode node) {
final RubyNode ret = translateWhileNode(node, false);
return addNewlineIfNeeded(node, ret);
}
private RubyNode translateWhileNode(WhileParseNode node, boolean conditionInversed) {
final SourceIndexLength sourceSection = node.getPosition();
RubyNode condition = node.getConditionNode().accept(this);
if (conditionInversed) {
condition = new NotNode(condition);
}
RubyNode body;
final BreakID whileBreakID = environment.getParseEnvironment().allocateBreakID();
final boolean oldTranslatingWhile = translatingWhile;
translatingWhile = true;
BreakID oldBreakID = environment.getBreakID();
environment.setBreakIDForWhile(whileBreakID);
frameOnStackMarkerSlotStack.push(BAD_FRAME_SLOT);
try {
body = translateNodeOrNil(sourceSection, node.getBodyNode());
} finally {
frameOnStackMarkerSlotStack.pop();
environment.setBreakIDForWhile(oldBreakID);
translatingWhile = oldTranslatingWhile;
}
final RubyNode loop;
if (node.evaluateAtStart()) {
loop = new WhileNode(new WhileNode.WhileRepeatingNode(context, condition, body));
} else {
loop = new WhileNode(new WhileNode.DoWhileRepeatingNode(context, condition, body));
}
final RubyNode ret = new CatchBreakNode(whileBreakID, loop);
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitXStrNode(XStrParseNode node) {
final ParseNode argsNode = buildArrayNode(node.getPosition(), new StrParseNode(node.getPosition(), node.getValue()));
final ParseNode callNode = new FCallParseNode(node.getPosition(), "`", argsNode, null);
final RubyNode ret = callNode.accept(this);
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitYieldNode(YieldParseNode node) {
final List arguments = new ArrayList<>();
ParseNode argsNode = node.getArgsNode();
final boolean unsplat = argsNode instanceof SplatParseNode || argsNode instanceof ArgsCatParseNode;
if (argsNode instanceof SplatParseNode) {
argsNode = ((SplatParseNode) argsNode).getValue();
}
if (argsNode != null) {
if (argsNode instanceof ListParseNode) {
arguments.addAll((node.getArgsNode()).childNodes());
} else {
arguments.add(node.getArgsNode());
}
}
final List argumentsTranslated = new ArrayList<>();
for (ParseNode argument : arguments) {
argumentsTranslated.add(argument.accept(this));
}
final RubyNode[] argumentsTranslatedArray = argumentsTranslated.toArray(new RubyNode[argumentsTranslated.size()]);
final RubyNode ret = new YieldExpressionNode(unsplat, argumentsTranslatedArray);
ret.unsafeSetSourceSection(node.getPosition());
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitZArrayNode(ZArrayParseNode node) {
final RubyNode[] values = new RubyNode[0];
final RubyNode ret = ArrayLiteralNode.create(values);
ret.unsafeSetSourceSection(node.getPosition());
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitBackRefNode(BackRefParseNode node) {
int index = 0;
switch (node.getType()) {
case '`':
index = ReadMatchReferenceNode.PRE;
break;
case '\'':
index = ReadMatchReferenceNode.POST;
break;
case '&':
index = ReadMatchReferenceNode.GLOBAL;
break;
case '+':
index = ReadMatchReferenceNode.HIGHEST;
break;
default:
throw new UnsupportedOperationException(Character.toString(node.getType()));
}
final SourceIndexLength sourceSection = node.getPosition();
environment.declareVarInMethodScope("$~");
final GetFromThreadLocalNode readMatchNode = new GetFromThreadLocalNode(environment.findLocalVarNode("$~", source, sourceSection));
final RubyNode ret = new ReadMatchReferenceNode(readMatchNode, index);
ret.unsafeSetSourceSection(sourceSection);
return addNewlineIfNeeded(node, ret);
}
@Override
public RubyNode visitStarNode(StarParseNode star) {
return nilNode(source, star.getPosition());
}
protected RubyNode initFlipFlopStates(SourceIndexLength sourceSection) {
final RubyNode[] initNodes = new RubyNode[environment.getFlipFlopStates().size()];
for (int n = 0; n < initNodes.length; n++) {
initNodes[n] = new InitFlipFlopSlotNode(environment.getFlipFlopStates().get(n));
}
return sequence(sourceSection, Arrays.asList(initNodes));
}
@Override
protected RubyNode defaultVisit(ParseNode node) {
throw new UnsupportedOperationException(node.toString() + " " + node.getPosition());
}
public TranslatorEnvironment getEnvironment() {
return environment;
}
protected String getIdentifierInNewEnvironment(boolean isBlock, String namedMethodName) {
if (isBlock) {
TranslatorEnvironment methodParent = environment;
while (methodParent.isBlock()) {
methodParent = methodParent.getParent();
}
if (environment.getBlockDepth() + 1 > 1) {
return StringUtils.format("block (%d levels) in %s", environment.getBlockDepth() + 1, methodParent.getNamedMethodName());
} else {
return StringUtils.format("block in %s", methodParent.getNamedMethodName());
}
} else {
return namedMethodName;
}
}
@Override
public RubyNode visitTruffleFragmentNode(TruffleFragmentParseNode node) {
return addNewlineIfNeeded(node, node.getFragment());
}
@Override
public RubyNode visitOther(ParseNode node) {
if (node instanceof ReadLocalDummyParseNode) {
final ReadLocalDummyParseNode readLocal = (ReadLocalDummyParseNode) node;
final RubyNode ret = new ReadLocalVariableNode(LocalVariableType.FRAME_LOCAL, readLocal.getFrameSlot());
ret.unsafeSetSourceSection(readLocal.getPosition());
return addNewlineIfNeeded(node, ret);
} else {
throw new UnsupportedOperationException();
}
}
private void copyNewline(ParseNode from, ParseNode to) {
if (from.isNewline()) {
to.setNewline();
}
}
private RubyNode addNewlineIfNeeded(ParseNode jrubyNode, RubyNode node) {
if (jrubyNode.isNewline()) {
final SourceIndexLength current = node.getEncapsulatingSourceIndexLength();
if (current == null) {
return node;
}
if (context.getCoverageManager().isEnabled()) {
node.unsafeSetIsCoverageLine();
context.getCoverageManager().setLineHasCode(source, current.toSourceSection(source).getStartLine());
}
node.unsafeSetIsNewLine();
}
return node;
}
}