LoadArgumentsTranslator.java
- /*
- * Copyright (c) 2014 Oracle and/or its affiliates. All rights reserved. This
- * code is released under a tri EPL/GPL/LGPL license. You can use it,
- * redistribute it and/or modify it under the terms of the:
- *
- * Eclipse Public License version 1.0
- * GNU General Public License version 2
- * GNU Lesser General Public License version 2.1
- */
- package org.jruby.truffle.translator;
- import com.oracle.truffle.api.frame.FrameSlot;
- import com.oracle.truffle.api.source.Source;
- import com.oracle.truffle.api.source.SourceSection;
- import org.jruby.ast.RequiredKeywordArgumentValueNode;
- import org.jruby.ast.StarNode;
- import org.jruby.ast.types.INameNode;
- import org.jruby.lexer.yacc.ISourcePosition;
- import org.jruby.truffle.nodes.ReadNode;
- import org.jruby.truffle.nodes.RubyNode;
- import org.jruby.truffle.nodes.cast.ArrayCastNodeFactory;
- import org.jruby.truffle.nodes.cast.BooleanCastNodeFactory;
- import org.jruby.truffle.nodes.control.IfNode;
- import org.jruby.truffle.nodes.control.SequenceNode;
- import org.jruby.truffle.nodes.core.ArrayIndexNodeFactory;
- import org.jruby.truffle.nodes.core.ArraySliceNodeFactory;
- import org.jruby.truffle.nodes.literal.ArrayLiteralNode;
- import org.jruby.truffle.nodes.literal.NilLiteralNode;
- import org.jruby.truffle.nodes.methods.arguments.*;
- import org.jruby.truffle.nodes.methods.locals.ReadLocalVariableNodeFactory;
- import org.jruby.truffle.nodes.methods.locals.WriteLocalVariableNodeFactory;
- import org.jruby.truffle.runtime.RubyContext;
- import java.util.*;
- public class LoadArgumentsTranslator extends Translator {
- private static class ArraySlot {
- private FrameSlot arraySlot;
- private int previousIndex;
- public ArraySlot(FrameSlot arraySlot, int previousIndex) {
- this.arraySlot = arraySlot;
- this.previousIndex = previousIndex;
- }
- public FrameSlot getArraySlot() {
- return arraySlot;
- }
- public int getPreviousIndex() {
- return previousIndex;
- }
- }
- private final boolean isBlock;
- private final BodyTranslator methodBodyTranslator;
- private final Deque<ArraySlot> arraySlotStack = new ArrayDeque<>();
- private enum State {
- PRE,
- OPT,
- POST
- }
- private int required;
- private int index;
- private int indexFromEnd = 1;
- private State state;
- private boolean hasKeywordArguments;
- private List<String> excludedKeywords = new ArrayList<>();
- private org.jruby.ast.ArgsNode argsNode;
- public LoadArgumentsTranslator(RubyNode currentNode, RubyContext context, Source source, boolean isBlock, BodyTranslator methodBodyTranslator) {
- super(currentNode, context, source);
- this.isBlock = isBlock;
- this.methodBodyTranslator = methodBodyTranslator;
- }
- @Override
- public RubyNode visitArgsNode(org.jruby.ast.ArgsNode node) {
- argsNode = node;
- final SourceSection sourceSection = translate(node.getPosition());
- final List<RubyNode> sequence = new ArrayList<>();
- if (node.getPre() != null) {
- state = State.PRE;
- index = 0;
- for (org.jruby.ast.Node arg : node.getPre().childNodes()) {
- sequence.add(arg.accept(this));
- ++index;
- required++;
- }
- }
- if (node.getOptArgs() != null) {
- // (BlockNode 0, (OptArgNode:a 0, (LocalAsgnNode:a 0, (FixnumNode 0))), ...)
- state = State.OPT;
- index = argsNode.getPreCount();
- for (org.jruby.ast.Node arg : node.getOptArgs().childNodes()) {
- sequence.add(arg.accept(this));
- ++index;
- }
- }
- hasKeywordArguments = node.hasKwargs() && node.getKeywords() != null;
- if (node.getRestArgNode() != null) {
- methodBodyTranslator.getEnvironment().hasRestParameter = true;
- sequence.add(node.getRestArgNode().accept(this));
- }
- if (node.getPost() != null) {
- state = State.POST;
- index = -1;
- final List<org.jruby.ast.Node> children = new ArrayList<>(node.getPost().childNodes());
- Collections.reverse(children);
- for (org.jruby.ast.Node arg : children) {
- sequence.add(arg.accept(this));
- index--;
- required++;
- }
- }
- if (hasKeywordArguments) {
- for (org.jruby.ast.Node arg : node.getKeywords().childNodes()) {
- sequence.add(arg.accept(this));
- }
- }
- if (node.getKeyRest() != null) {
- sequence.add(node.getKeyRest().accept(this));
- }
- if (node.getBlock() != null) {
- sequence.add(node.getBlock().accept(this));
- }
- return SequenceNode.sequence(context, sourceSection, sequence);
- }
- @Override
- public RubyNode visitKeywordRestArgNode(org.jruby.ast.KeywordRestArgNode node) {
- final SourceSection sourceSection = translate(node.getPosition());
- final RubyNode readNode = new ReadKeywordRestArgumentNode(context, sourceSection, required, excludedKeywords.toArray(new String[excludedKeywords.size()]));
- final FrameSlot slot = methodBodyTranslator.getEnvironment().getFrameDescriptor().findOrAddFrameSlot(node.getName());
- return WriteLocalVariableNodeFactory.create(context, sourceSection, slot, readNode);
- }
- @Override
- public RubyNode visitKeywordArgNode(org.jruby.ast.KeywordArgNode node) {
- final SourceSection sourceSection = translate(node.getPosition());
- final String name;
- final RubyNode defaultValue;
- final org.jruby.ast.Node firstChild = node.childNodes().get(0);
- if (firstChild instanceof org.jruby.ast.LocalAsgnNode) {
- final org.jruby.ast.LocalAsgnNode localAsgnNode = (org.jruby.ast.LocalAsgnNode) firstChild;
- name = localAsgnNode.getName();
- if (localAsgnNode.getValueNode() == null) {
- defaultValue = new NilLiteralNode(context, sourceSection);
- } else if (localAsgnNode.getValueNode() instanceof RequiredKeywordArgumentValueNode) {
- /*
- * This isn't a true default value - it's a marker to say there isn't one. This actually makes sense;
- * the semantic action of executing this node is to report an error, and we do the same thing.
- */
- defaultValue = new MissingKeywordArgumentNode(context, sourceSection, name);
- } else {
- defaultValue = localAsgnNode.getValueNode().accept(this);
- }
- } else if (firstChild instanceof org.jruby.ast.DAsgnNode) {
- final org.jruby.ast.DAsgnNode dAsgnNode = (org.jruby.ast.DAsgnNode) firstChild;
- name = dAsgnNode.getName();
- if (dAsgnNode.getValueNode() == null) {
- defaultValue = new NilLiteralNode(context, sourceSection);
- } else {
- defaultValue = dAsgnNode.getValueNode().accept(this);
- }
- } else {
- throw new UnsupportedOperationException("unsupported keyword arg " + node);
- }
- excludedKeywords.add(name);
- final RubyNode readNode = new ReadKeywordArgumentNode(context, sourceSection, required, name, defaultValue);
- final FrameSlot slot = methodBodyTranslator.getEnvironment().getFrameDescriptor().findFrameSlot(name);
- return WriteLocalVariableNodeFactory.create(context, sourceSection, slot, readNode);
- }
- @Override
- public RubyNode visitArgumentNode(org.jruby.ast.ArgumentNode node) {
- final SourceSection sourceSection = translate(node.getPosition());
- final RubyNode readNode = readArgument(sourceSection);
- final FrameSlot slot = methodBodyTranslator.getEnvironment().getFrameDescriptor().findFrameSlot(node.getName());
- return WriteLocalVariableNodeFactory.create(context, sourceSection, slot, readNode);
- }
- private RubyNode readArgument(SourceSection sourceSection) {
- if (useArray()) {
- return ArrayIndexNodeFactory.create(context, sourceSection, index, loadArray(sourceSection));
- } else {
- if (state == State.PRE) {
- return new ReadPreArgumentNode(context, sourceSection, index, isBlock ? MissingArgumentBehaviour.NIL : MissingArgumentBehaviour.RUNTIME_ERROR);
- } else if (state == State.POST) {
- return new ReadPostArgumentNode(context, sourceSection, index);
- } else {
- throw new IllegalStateException();
- }
- }
- }
- @Override
- public RubyNode visitRestArgNode(org.jruby.ast.RestArgNode node) {
- final SourceSection sourceSection = translate(node.getPosition());
- final RubyNode readNode;
- int from = argsNode.getPreCount() + argsNode.getOptionalArgsCount();
- int to = -argsNode.getPostCount();
- if (useArray()) {
- readNode = ArraySliceNodeFactory.create(context, sourceSection, from, to, loadArray(sourceSection));
- } else {
- readNode = new ReadRestArgumentNode(context, sourceSection, from, to, hasKeywordArguments);
- }
- final FrameSlot slot = methodBodyTranslator.getEnvironment().getFrameDescriptor().findFrameSlot(node.getName());
- return WriteLocalVariableNodeFactory.create(context, sourceSection, slot, readNode);
- }
- @Override
- public RubyNode visitBlockArgNode(org.jruby.ast.BlockArgNode node) {
- final SourceSection sourceSection = translate(node.getPosition());
- final RubyNode readNode = new ReadBlockNode(context, sourceSection, context.getCoreLibrary().getNilObject());
- final FrameSlot slot = methodBodyTranslator.getEnvironment().getFrameDescriptor().findFrameSlot(node.getName());
- return WriteLocalVariableNodeFactory.create(context, sourceSection, slot, readNode);
- }
- @Override
- public RubyNode visitOptArgNode(org.jruby.ast.OptArgNode node) {
- // (OptArgNode:a 0, (LocalAsgnNode:a 0, (FixnumNode 0)))
- return node.getValue().accept(this);
- }
- @Override
- public RubyNode visitLocalAsgnNode(org.jruby.ast.LocalAsgnNode node) {
- return translateLocalAssignment(node.getPosition(), node.getName(), node.getValueNode());
- }
- @Override
- public RubyNode visitDAsgnNode(org.jruby.ast.DAsgnNode node) {
- return translateLocalAssignment(node.getPosition(), node.getName(), node.getValueNode());
- }
- private RubyNode translateLocalAssignment(ISourcePosition sourcePosition, String name, org.jruby.ast.Node valueNode) {
- final SourceSection sourceSection = translate(sourcePosition);
- final RubyNode readNode;
- if (indexFromEnd == 1) {
- if (valueNode instanceof org.jruby.ast.NilImplicitNode) {
- // Multiple assignment
- if (useArray()) {
- readNode = ArrayIndexNodeFactory.create(context, sourceSection, index, loadArray(sourceSection));
- } else {
- readNode = readArgument(sourceSection);
- }
- } else {
- // Optional argument
- final RubyNode defaultValue = valueNode.accept(this);
- int minimum = index + 1 + argsNode.getPostCount();
- if (argsNode.hasKwargs()) {
- minimum += 1;
- }
- readNode = new ReadOptionalArgumentNode(context, sourceSection, index, minimum, defaultValue);
- }
- } else {
- readNode = ArraySliceNodeFactory.create(context, sourceSection, index, indexFromEnd, loadArray(sourceSection));
- }
- final FrameSlot slot = methodBodyTranslator.getEnvironment().getFrameDescriptor().findOrAddFrameSlot(name);
- return WriteLocalVariableNodeFactory.create(context, sourceSection, slot, readNode);
- }
- @Override
- public RubyNode visitArrayNode(org.jruby.ast.ArrayNode node) {
- // (ArrayNode 0, (MultipleAsgn19Node 0, (ArrayNode 0, (LocalAsgnNode:a 0, ), (LocalAsgnNode:b 0, )), null, null)))
- return node.childNodes().get(0).accept(this);
- }
- @Override
- public RubyNode visitMultipleAsgnNode(org.jruby.ast.MultipleAsgn19Node node) {
- final SourceSection sourceSection = translate(node.getPosition());
- // (MultipleAsgn19Node 0, (ArrayNode 0, (LocalAsgnNode:a 0, ), (LocalAsgnNode:b 0, )), null, null))
- final int arrayIndex = index;
- final String arrayName = methodBodyTranslator.getEnvironment().allocateLocalTemp("destructure");
- final FrameSlot arraySlot = methodBodyTranslator.getEnvironment().declareVar(arrayName);
- pushArraySlot(arraySlot);
- final List<org.jruby.ast.Node> childNodes;
- if (node.childNodes() == null || node.childNodes().get(0) == null) {
- childNodes = Collections.emptyList();
- } else {
- childNodes = node.childNodes().get(0).childNodes();
- }
- final List<RubyNode> notNilSequence = new ArrayList<>();
- if (node.getPre() != null) {
- index = 0;
- for (org.jruby.ast.Node child : node.getPre().childNodes()) {
- notNilSequence.add(child.accept(this));
- index++;
- }
- }
- if (node.getRest() != null) {
- index = node.getPreCount();
- indexFromEnd = -node.getPostCount();
- notNilSequence.add(node.getRest().accept(this));
- indexFromEnd = 1;
- }
- if (node.getPost() != null) {
- index = -1;
- final List<org.jruby.ast.Node> children = new ArrayList<>(node.getPost().childNodes());
- Collections.reverse(children);
- for (org.jruby.ast.Node child : children) {
- notNilSequence.add(child.accept(this));
- index--;
- }
- }
- final RubyNode notNil = SequenceNode.sequence(context, sourceSection, notNilSequence);
- popArraySlot(arraySlot);
- final List<RubyNode> nilSequence = new ArrayList<>();
- final ParameterCollector parametersToClearCollector = new ParameterCollector();
- if (node.getPre() != null) {
- for (org.jruby.ast.Node child : node.getPre().childNodes()) {
- child.accept(parametersToClearCollector);
- }
- }
- if (node.getRest() != null) {
- if (node.getRest() instanceof INameNode) {
- final String name = ((INameNode) node.getRest()).getName();
- nilSequence.add(((ReadNode) methodBodyTranslator.getEnvironment().findOrAddLocalVarNodeDangerous(name, sourceSection)).makeWriteNode(new ArrayLiteralNode.UninitialisedArrayLiteralNode(context, sourceSection, new RubyNode[]{})));
- } else if (node.getRest() instanceof StarNode) {
- // Don't think we need to do anything
- } else {
- throw new UnsupportedOperationException("unsupported rest node " + node.getRest());
- }
- }
- if (node.getPost() != null) {
- for (org.jruby.ast.Node child : node.getPost().childNodes()) {
- child.accept(parametersToClearCollector);
- }
- }
- for (String parameterToClear : parametersToClearCollector.getParameters()) {
- nilSequence.add(((ReadNode) methodBodyTranslator.getEnvironment().findOrAddLocalVarNodeDangerous(parameterToClear, sourceSection)).makeWriteNode(new NilLiteralNode(context, sourceSection)));
- }
- if (!childNodes.isEmpty()) {
- // We haven't pushed a new array slot, so this will read the value which we couldn't convert to an array into the first destructured argument
- index = arrayIndex;
- nilSequence.add(childNodes.get(0).accept(this));
- }
- final RubyNode nil = SequenceNode.sequence(context, sourceSection, nilSequence);
- return SequenceNode.sequence(context, sourceSection,
- WriteLocalVariableNodeFactory.create(context, sourceSection, arraySlot,
- ArrayCastNodeFactory.create(context, sourceSection,
- readArgument(sourceSection))),
- new IfNode(context, sourceSection,
- BooleanCastNodeFactory.create(context, sourceSection,
- new IsNilNode(context, sourceSection, ReadLocalVariableNodeFactory.create(context, sourceSection, arraySlot))),
- nil,
- notNil == null ? new NilLiteralNode(context, sourceSection) : notNil));
- }
- @Override
- protected RubyNode defaultVisit(org.jruby.ast.Node node) {
- // For normal expressions in the default value for optional arguments, use the normal body translator
- return node.accept(methodBodyTranslator);
- }
- public void pushArraySlot(FrameSlot slot) {
- arraySlotStack.push(new ArraySlot(slot, index));
- }
- public void popArraySlot(FrameSlot slot) {
- index = arraySlotStack.pop().getPreviousIndex();
- }
- protected boolean useArray() {
- return !arraySlotStack.isEmpty();
- }
- protected RubyNode loadArray(SourceSection sourceSection) {
- return ReadLocalVariableNodeFactory.create(context, sourceSection, arraySlotStack.peek().getArraySlot());
- }
- @Override
- protected String getIdentifier() {
- return methodBodyTranslator.getIdentifier();
- }
- }