org.jruby.parser.ParserSupport Maven / Gradle / Ivy
/*
***** BEGIN LICENSE BLOCK *****
* Version: CPL 1.0/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Common Public
* License Version 1.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.eclipse.org/legal/cpl-v10.html
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* Copyright (C) 2002 Benoit Cerrina
* Copyright (C) 2002-2004 Anders Bengtsson
* Copyright (C) 2002-2004 Jan Arne Petersen
* Copyright (C) 2004 Charles O Nutter
* Copyright (C) 2004 Thomas E Enebo
* Copyright (C) 2004 Stefan Matthias Aust
* Copyright (C) 2006-2007 Mirko Stocker
* Copyright (C) 2006 Thomas Corbat
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the CPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the CPL, the GPL or the LGPL.
***** END LICENSE BLOCK *****/
package org.jruby.parser;
import java.math.BigInteger;
import org.jcodings.Encoding;
import org.jcodings.specific.EUCJPEncoding;
import org.jcodings.specific.SJISEncoding;
import org.jcodings.specific.USASCIIEncoding;
import org.jcodings.specific.UTF8Encoding;
import org.jruby.CompatVersion;
import org.jruby.RubyBignum;
import org.jruby.RubyRegexp;
import org.jruby.ast.AliasNode;
import org.jruby.ast.AndNode;
import org.jruby.ast.ArgsPreOneArgNode;
import org.jruby.ast.ArgsPreTwoArgNode;
import org.jruby.ast.ArgsCatNode;
import org.jruby.ast.ArgsNoArgNode;
import org.jruby.ast.ArgsNode;
import org.jruby.ast.ArgsPushNode;
import org.jruby.ast.ArgumentNode;
import org.jruby.ast.ArrayNode;
import org.jruby.ast.AssignableNode;
import org.jruby.ast.AttrAssignNode;
import org.jruby.ast.AttrAssignOneArgNode;
import org.jruby.ast.AttrAssignThreeArgNode;
import org.jruby.ast.AttrAssignTwoArgNode;
import org.jruby.ast.BackRefNode;
import org.jruby.ast.BeginNode;
import org.jruby.ast.BignumNode;
import org.jruby.ast.BinaryOperatorNode;
import org.jruby.ast.BlockArg18Node;
import org.jruby.ast.BlockArgNode;
import org.jruby.ast.BlockNode;
import org.jruby.ast.BlockPassNode;
import org.jruby.ast.CallManyArgsBlockNode;
import org.jruby.ast.CallManyArgsBlockPassNode;
import org.jruby.ast.CallManyArgsNode;
import org.jruby.ast.CallNoArgBlockNode;
import org.jruby.ast.CallNoArgBlockPassNode;
import org.jruby.ast.CallNoArgNode;
import org.jruby.ast.CallNode;
import org.jruby.ast.CallOneArgNode;
import org.jruby.ast.CallOneArgBlockNode;
import org.jruby.ast.CallOneArgBlockPassNode;
import org.jruby.ast.CallOneArgFixnumNode;
import org.jruby.ast.CallSpecialArgNode;
import org.jruby.ast.CallSpecialArgBlockNode;
import org.jruby.ast.CallSpecialArgBlockPassNode;
import org.jruby.ast.CallThreeArgBlockNode;
import org.jruby.ast.CallThreeArgBlockPassNode;
import org.jruby.ast.CallThreeArgNode;
import org.jruby.ast.CallTwoArgBlockNode;
import org.jruby.ast.CallTwoArgBlockPassNode;
import org.jruby.ast.CallTwoArgNode;
import org.jruby.ast.CaseNode;
import org.jruby.ast.ClassVarAsgnNode;
import org.jruby.ast.ClassVarDeclNode;
import org.jruby.ast.ClassVarNode;
import org.jruby.ast.Colon2ConstNode;
import org.jruby.ast.Colon2ImplicitNode;
import org.jruby.ast.Colon2MethodNode;
import org.jruby.ast.Colon2Node;
import org.jruby.ast.Colon3Node;
import org.jruby.ast.ConstDeclNode;
import org.jruby.ast.ConstNode;
import org.jruby.ast.DAsgnNode;
import org.jruby.ast.DRegexpNode;
import org.jruby.ast.DStrNode;
import org.jruby.ast.DotNode;
import org.jruby.ast.EncodingNode;
import org.jruby.ast.EvStrNode;
import org.jruby.ast.FCallManyArgsBlockNode;
import org.jruby.ast.FCallManyArgsBlockPassNode;
import org.jruby.ast.FCallManyArgsNode;
import org.jruby.ast.FCallNoArgBlockNode;
import org.jruby.ast.FCallNoArgBlockPassNode;
import org.jruby.ast.FCallNoArgNode;
import org.jruby.ast.FCallOneArgBlockNode;
import org.jruby.ast.FCallOneArgBlockPassNode;
import org.jruby.ast.FCallOneArgNode;
import org.jruby.ast.FCallSpecialArgBlockPassNode;
import org.jruby.ast.FCallSpecialArgNode;
import org.jruby.ast.FCallThreeArgBlockNode;
import org.jruby.ast.FCallThreeArgBlockPassNode;
import org.jruby.ast.FCallThreeArgNode;
import org.jruby.ast.FCallTwoArgBlockNode;
import org.jruby.ast.FCallTwoArgBlockPassNode;
import org.jruby.ast.FCallTwoArgNode;
import org.jruby.ast.FalseNode;
import org.jruby.ast.FileNode;
import org.jruby.ast.FixnumNode;
import org.jruby.ast.FlipNode;
import org.jruby.ast.FloatNode;
import org.jruby.ast.GlobalAsgnNode;
import org.jruby.ast.GlobalVarNode;
import org.jruby.ast.IArgumentNode;
import org.jruby.ast.IfNode;
import org.jruby.ast.InstAsgnNode;
import org.jruby.ast.InstVarNode;
import org.jruby.ast.IterNode;
import org.jruby.ast.ListNode;
import org.jruby.ast.LocalAsgnNode;
import org.jruby.ast.Match2Node;
import org.jruby.ast.Match3Node;
import org.jruby.ast.MatchNode;
import org.jruby.ast.MultipleAsgnNode;
import org.jruby.ast.NewlineNode;
import org.jruby.ast.NilImplicitNode;
import org.jruby.ast.NilNode;
import org.jruby.ast.Node;
import org.jruby.ast.NthRefNode;
import org.jruby.ast.OpElementAsgnNode;
import org.jruby.ast.OpElementOneArgAndAsgnNode;
import org.jruby.ast.OpElementOneArgAsgnNode;
import org.jruby.ast.OpElementOneArgOrAsgnNode;
import org.jruby.ast.OrNode;
import org.jruby.ast.RegexpNode;
import org.jruby.ast.RestArgNode;
import org.jruby.ast.RootNode;
import org.jruby.ast.SValueNode;
import org.jruby.ast.SelfNode;
import org.jruby.ast.SplatNode;
import org.jruby.ast.StrNode;
import org.jruby.ast.SuperNode;
import org.jruby.ast.SymbolNode;
import org.jruby.ast.TrueNode;
import org.jruby.ast.UndefNode;
import org.jruby.ast.WhenNode;
import org.jruby.ast.WhenOneArgNode;
import org.jruby.ast.YieldNode;
import org.jruby.ast.YieldOneNode;
import org.jruby.ast.YieldThreeNode;
import org.jruby.ast.YieldTwoNode;
import org.jruby.ast.ZYieldNode;
import org.jruby.ast.types.ILiteralNode;
import org.jruby.ast.types.INameNode;
import org.jruby.common.IRubyWarnings;
import org.jruby.common.IRubyWarnings.ID;
import org.jruby.lexer.yacc.ISourcePosition;
import org.jruby.lexer.yacc.ISourcePositionHolder;
import org.jruby.lexer.yacc.RubyYaccLexer;
import org.jruby.lexer.yacc.SyntaxException;
import org.jruby.lexer.yacc.Token;
import org.jruby.lexer.yacc.SyntaxException.PID;
import org.jruby.runtime.DynamicScope;
import org.jruby.util.ByteList;
import org.jruby.util.IdUtil;
import org.jruby.util.RegexpOptions;
import org.jruby.util.StringSupport;
/**
*
*/
public class ParserSupport {
// Parser states:
protected StaticScope currentScope;
protected RubyYaccLexer lexer;
// Is the parser current within a singleton (value is number of nested singletons)
private int inSingleton;
// Is the parser currently within a method definition
private boolean inDefinition;
protected IRubyWarnings warnings;
protected ParserConfiguration configuration;
private RubyParserResult result;
public void reset() {
inSingleton = 0;
inDefinition = false;
}
public void allowDubyExtension(ISourcePosition position) {
if (!configuration.isDubyExtensionsEnabled()) {
throw new SyntaxException(PID.DUBY_EXTENSIONS_OFF, position,
lexer.getCurrentLine(), "Duby extensions not configured");
}
}
public StaticScope getCurrentScope() {
return currentScope;
}
public ParserConfiguration getConfiguration() {
return configuration;
}
public void popCurrentScope() {
currentScope = currentScope.getEnclosingScope();
}
public void pushBlockScope() {
currentScope = new BlockStaticScope(currentScope);
}
public void pushLocalScope() {
currentScope = new LocalStaticScope(currentScope);
}
public Node arg_concat(ISourcePosition position, Node node1, Node node2) {
return node2 == null ? node1 : new ArgsCatNode(position, node1, node2);
}
public Node arg_blk_pass(Node firstNode, BlockPassNode secondNode) {
if (secondNode != null) {
secondNode.setArgsNode(firstNode);
return secondNode;
}
return firstNode;
}
/**
* We know for callers of this that it cannot be any of the specials checked in gettable.
*
* @param node to check its variable type
* @return an AST node representing this new variable
*/
public Node gettable2(Node node) {
switch (node.getNodeType()) {
case DASGNNODE: // LOCALVAR
case LOCALASGNNODE:
return currentScope.declare(node.getPosition(), ((INameNode) node).getName());
case CONSTDECLNODE: // CONSTANT
return new ConstNode(node.getPosition(), ((INameNode) node).getName());
case INSTASGNNODE: // INSTANCE VARIABLE
return new InstVarNode(node.getPosition(), ((INameNode) node).getName());
case CLASSVARDECLNODE:
case CLASSVARASGNNODE:
return new ClassVarNode(node.getPosition(), ((INameNode) node).getName());
case GLOBALASGNNODE:
return new GlobalVarNode(node.getPosition(), ((INameNode) node).getName());
}
getterIdentifierError(node.getPosition(), ((INameNode) node).getName());
return null;
}
/**
* Create AST node representing variable type it represents.
*
* @param token to check its variable type
* @return an AST node representing this new variable
*/
public Node gettable(Token token) {
switch (token.getType()) {
case Tokens.kSELF:
return new SelfNode(token.getPosition());
case Tokens.kNIL:
return new NilNode(token.getPosition());
case Tokens.kTRUE:
return new TrueNode(token.getPosition());
case Tokens.kFALSE:
return new FalseNode(token.getPosition());
case Tokens.k__FILE__:
return new FileNode(token.getPosition(), new ByteList(token.getPosition().getFile().getBytes(),
getConfiguration().getRuntime().getEncodingService().getLocaleEncoding()));
case Tokens.k__LINE__:
return new FixnumNode(token.getPosition(), token.getPosition().getStartLine()+1);
case Tokens.k__ENCODING__:
return new EncodingNode(token.getPosition(), lexer.getEncoding());
case Tokens.tIDENTIFIER:
return currentScope.declare(token.getPosition(), (String) token.getValue());
case Tokens.tCONSTANT:
return new ConstNode(token.getPosition(), (String) token.getValue());
case Tokens.tIVAR:
return new InstVarNode(token.getPosition(), (String) token.getValue());
case Tokens.tCVAR:
return new ClassVarNode(token.getPosition(), (String) token.getValue());
case Tokens.tGVAR:
return new GlobalVarNode(token.getPosition(), (String) token.getValue());
}
getterIdentifierError(token.getPosition(), (String) token.getValue());
return null;
}
protected void getterIdentifierError(ISourcePosition position, String identifier) {
throw new SyntaxException(PID.BAD_IDENTIFIER, position, lexer.getCurrentLine(),
"identifier " + identifier + " is not valid", identifier);
}
public AssignableNode assignable(Token lhs, Node value) {
checkExpression(value);
switch (lhs.getType()) {
case Tokens.kSELF:
throw new SyntaxException(PID.CANNOT_CHANGE_SELF, lhs.getPosition(),
lexer.getCurrentLine(), "Can't change the value of self");
case Tokens.kNIL:
throw new SyntaxException(PID.INVALID_ASSIGNMENT, lhs.getPosition(),
lexer.getCurrentLine(), "Can't assign to nil", "nil");
case Tokens.kTRUE:
throw new SyntaxException(PID.INVALID_ASSIGNMENT, lhs.getPosition(),
lexer.getCurrentLine(), "Can't assign to true", "true");
case Tokens.kFALSE:
throw new SyntaxException(PID.INVALID_ASSIGNMENT, lhs.getPosition(),
lexer.getCurrentLine(), "Can't assign to false", "false");
case Tokens.k__FILE__:
throw new SyntaxException(PID.INVALID_ASSIGNMENT, lhs.getPosition(),
lexer.getCurrentLine(), "Can't assign to __FILE__", "__FILE__");
case Tokens.k__LINE__:
throw new SyntaxException(PID.INVALID_ASSIGNMENT, lhs.getPosition(),
lexer.getCurrentLine(), "Can't assign to __LINE__", "__LINE__");
case Tokens.tIDENTIFIER:
return currentScope.assign(lhs.getPosition(), (String) lhs.getValue(), makeNullNil(value));
case Tokens.tCONSTANT:
if (isInDef() || isInSingle()) {
throw new SyntaxException(PID.DYNAMIC_CONSTANT_ASSIGNMENT, lhs.getPosition(),
lexer.getCurrentLine(), "dynamic constant assignment");
}
return new ConstDeclNode(lhs.getPosition(), (String) lhs.getValue(), null, value);
case Tokens.tIVAR:
return new InstAsgnNode(lhs.getPosition(), (String) lhs.getValue(), value);
case Tokens.tCVAR:
if (isInDef() || isInSingle()) {
return new ClassVarAsgnNode(lhs.getPosition(), (String) lhs.getValue(), value);
}
return new ClassVarDeclNode(lhs.getPosition(), (String) lhs.getValue(), value);
case Tokens.tGVAR:
return new GlobalAsgnNode(lhs.getPosition(), (String) lhs.getValue(), value);
}
throw new SyntaxException(PID.BAD_IDENTIFIER, lhs.getPosition(), lexer.getCurrentLine(),
"identifier " + (String) lhs.getValue() + " is not valid", lhs.getValue());
}
/**
* Wraps node with NEWLINE node.
*
*@param node
*@return a NewlineNode or null if node is null.
*/
public Node newline_node(Node node, ISourcePosition position) {
if (node == null) return null;
return node instanceof NewlineNode ? node : new NewlineNode(position, node);
}
public Node addRootNode(Node topOfAST, ISourcePosition position) {
position = topOfAST != null ? topOfAST.getPosition() : position;
if (result.getBeginNodes().isEmpty()) {
if (topOfAST == null) topOfAST = NilImplicitNode.NIL;
return new RootNode(position, result.getScope(), topOfAST);
}
BlockNode newTopOfAST = new BlockNode(position);
for (Node beginNode: result.getBeginNodes()) {
appendToBlock(newTopOfAST, beginNode);
}
// Add real top to new top (unless this top is empty [only begin/end nodes or truly empty])
if (topOfAST != null) newTopOfAST.add(topOfAST);
return new RootNode(position, result.getScope(), newTopOfAST);
}
/* MRI: block_append */
public Node appendToBlock(Node head, Node tail) {
if (tail == null) return head;
if (head == null) return tail;
// Reduces overhead in interp by not set position every single line we encounter.
if (!configuration.hasExtraPositionInformation()) {
head = compactNewlines(head);
}
if (!(head instanceof BlockNode)) {
head = new BlockNode(head.getPosition()).add(head);
}
if (warnings.isVerbose() && isBreakStatement(((ListNode) head).getLast())) {
warnings.warning(ID.STATEMENT_NOT_REACHED, tail.getPosition(), "Statement not reached.");
}
// Assumption: tail is never a list node
((ListNode) head).addAll(tail);
return head;
}
public Node getOperatorCallNode(Node firstNode, String operator) {
checkExpression(firstNode);
return new CallNoArgNode(firstNode.getPosition(), firstNode, operator);
}
public Node getOperatorCallNode(Node firstNode, String operator, Node secondNode) {
return getOperatorCallNode(firstNode, operator, secondNode, null);
}
public Node getOperatorCallNode(Node firstNode, String operator, Node secondNode, ISourcePosition defaultPosition) {
if (defaultPosition != null) {
firstNode = checkForNilNode(firstNode, defaultPosition);
secondNode = checkForNilNode(secondNode, defaultPosition);
}
checkExpression(firstNode);
checkExpression(secondNode);
return new_call_one_arg(firstNode.getPosition(), firstNode, operator, secondNode);
// return new CallOneArgNode(firstNode.getPosition(), firstNode, operator, new ArrayNode(secondNode.getPosition(), secondNode));
}
public Node getMatchNode(Node firstNode, Node secondNode) {
if (firstNode instanceof DRegexpNode || firstNode instanceof RegexpNode) {
return new Match2Node(firstNode.getPosition(), firstNode, secondNode);
} else if (secondNode instanceof DRegexpNode || secondNode instanceof RegexpNode) {
return new Match3Node(firstNode.getPosition(), secondNode, firstNode);
}
return getOperatorCallNode(firstNode, "=~", secondNode);
}
/**
* Define an array set condition so we can return lhs
*
* @param receiver array being set
* @param index node which should evalute to index of array set
* @return an AttrAssignNode
*/
public Node aryset(Node receiver, Node index) {
checkExpression(receiver);
return new_attrassign(receiver.getPosition(), receiver, "[]=", index);
}
/**
* Define an attribute set condition so we can return lhs
*
* @param receiver object which contains attribute
* @param name of the attribute being set
* @return an AttrAssignNode
*/
public Node attrset(Node receiver, String name) {
checkExpression(receiver);
return new_attrassign(receiver.getPosition(), receiver, name + "=", null);
}
public void backrefAssignError(Node node) {
if (node instanceof NthRefNode) {
String varName = "$" + ((NthRefNode) node).getMatchNumber();
throw new SyntaxException(PID.INVALID_ASSIGNMENT, node.getPosition(),
"Can't set variable " + varName + '.', varName);
} else if (node instanceof BackRefNode) {
String varName = "$" + ((BackRefNode) node).getType();
throw new SyntaxException(PID.INVALID_ASSIGNMENT, node.getPosition(), "Can't set variable " + varName + '.', varName);
}
}
public Node arg_add(ISourcePosition position, Node node1, Node node2) {
if (node1 == null) {
if (node2 == null) {
return new ArrayNode(position, NilImplicitNode.NIL);
} else {
return new ArrayNode(node2.getPosition(), node2);
}
}
if (node1 instanceof ArrayNode) return ((ArrayNode) node1).add(node2);
return new ArgsPushNode(position, node1, node2);
}
/**
* @fixme position
**/
public Node node_assign(Node lhs, Node rhs) {
if (lhs == null) return null;
Node newNode = lhs;
checkExpression(rhs);
if (lhs instanceof AssignableNode) {
((AssignableNode) lhs).setValueNode(rhs);
} else if (lhs instanceof IArgumentNode) {
IArgumentNode invokableNode = (IArgumentNode) lhs;
return invokableNode.setArgsNode(arg_add(lhs.getPosition(), invokableNode.getArgsNode(), rhs));
}
return newNode;
}
public Node ret_args(Node node, ISourcePosition position) {
if (node != null) {
if (node instanceof BlockPassNode) {
throw new SyntaxException(PID.DYNAMIC_CONSTANT_ASSIGNMENT, position,
lexer.getCurrentLine(), "Dynamic constant assignment.");
} else if (node instanceof ArrayNode && ((ArrayNode)node).size() == 1) {
node = ((ArrayNode)node).get(0);
} else if (node instanceof SplatNode) {
node = newSValueNode(position, node);
}
}
return node;
}
/**
* Is the supplied node a break/control statement?
*
* @param node to be checked
* @return true if a control node, false otherwise
*/
public boolean isBreakStatement(Node node) {
breakLoop: do {
if (node == null) return false;
switch (node.getNodeType()) {
case NEWLINENODE:
node = ((NewlineNode) node).getNextNode();
continue breakLoop;
case BREAKNODE: case NEXTNODE: case REDONODE:
case RETRYNODE: case RETURNNODE:
return true;
default:
return false;
}
} while (true);
}
public void warnUnlessEOption(ID id, Node node, String message) {
if (!configuration.isInlineSource()) {
warnings.warn(id, node.getPosition(), message);
}
}
public void warningUnlessEOption(ID id, Node node, String message) {
if (warnings.isVerbose() && !configuration.isInlineSource()) {
warnings.warning(id, node.getPosition(), message);
}
}
private Node compactNewlines(Node head) {
while (head instanceof NewlineNode) {
Node nextNode = ((NewlineNode) head).getNextNode();
if (!(nextNode instanceof NewlineNode)) {
break;
}
head = nextNode;
}
return head;
}
// logical equivalent to value_expr in MRI
public boolean checkExpression(Node node) {
boolean conditional = false;
while (node != null) {
switch (node.getNodeType()) {
case DEFNNODE: case DEFSNODE:
warnings.warning(ID.VOID_VALUE_EXPRESSION, node.getPosition(),
"void value expression");
return false;
case RETURNNODE: case BREAKNODE: case NEXTNODE: case REDONODE:
case RETRYNODE:
if (!conditional) {
throw new SyntaxException(PID.VOID_VALUE_EXPRESSION,
node.getPosition(), lexer.getCurrentLine(),
"void value expression");
}
return false;
case BLOCKNODE:
node = ((BlockNode) node).getLast();
break;
case BEGINNODE:
node = ((BeginNode) node).getBodyNode();
break;
case IFNODE:
if (!checkExpression(((IfNode) node).getThenBody())) return false;
node = ((IfNode) node).getElseBody();
break;
case ANDNODE: case ORNODE:
conditional = true;
node = ((BinaryOperatorNode) node).getSecondNode();
break;
case NEWLINENODE:
node = ((NewlineNode) node).getNextNode();
break;
default: // Node
return true;
}
}
return true;
}
/**
* Is this a literal in the sense that MRI has a NODE_LIT for. This is different than
* ILiteralNode. We should pick a different name since ILiteralNode is something we created
* which is similiar but used for a slightly different condition (can I do singleton things).
*
* @param node to be tested
* @return true if it is a literal
*/
public boolean isLiteral(Node node) {
return node != null && (node instanceof FixnumNode || node instanceof BignumNode ||
node instanceof FloatNode || node instanceof SymbolNode ||
(node instanceof RegexpNode && ((RegexpNode) node).getOptions().toJoniOptions() == 0));
}
private void handleUselessWarn(Node node, String useless) {
warnings.warn(ID.USELESS_EXPRESSION, node.getPosition(), "Useless use of " + useless + " in void context.");
}
/**
* Check to see if current node is an useless statement. If useless a warning if printed.
*
* @param node to be checked.
*/
public void checkUselessStatement(Node node) {
if (!warnings.isVerbose() || (!configuration.isInlineSource() && configuration.isEvalParse())) return;
uselessLoop: do {
if (node == null) return;
switch (node.getNodeType()) {
case NEWLINENODE:
node = ((NewlineNode) node).getNextNode();
continue uselessLoop;
case CALLNODE: {
String name = ((CallNode) node).getName();
if (name == "+" || name == "-" || name == "*" || name == "/" || name == "%" ||
name == "**" || name == "+@" || name == "-@" || name == "|" || name == "^" ||
name == "&" || name == "<=>" || name == ">" || name == ">=" || name == "<" ||
name == "<=" || name == "==" || name == "!=") {
handleUselessWarn(node, name);
}
return;
}
case BACKREFNODE: case DVARNODE: case GLOBALVARNODE:
case LOCALVARNODE: case NTHREFNODE: case CLASSVARNODE:
case INSTVARNODE:
handleUselessWarn(node, "a variable"); return;
// FIXME: Temporarily disabling because this fires way too much running Rails tests. JRUBY-518
/*case CONSTNODE:
handleUselessWarn(node, "a constant"); return;*/
case BIGNUMNODE: case DREGEXPNODE: case DSTRNODE: case DSYMBOLNODE:
case FIXNUMNODE: case FLOATNODE: case REGEXPNODE:
case STRNODE: case SYMBOLNODE:
handleUselessWarn(node, "a literal"); return;
// FIXME: Temporarily disabling because this fires way too much running Rails tests. JRUBY-518
/*case CLASSNODE: case COLON2NODE:
handleUselessWarn(node, "::"); return;*/
case DOTNODE:
handleUselessWarn(node, ((DotNode) node).isExclusive() ? "..." : ".."); return;
case DEFINEDNODE:
handleUselessWarn(node, "defined?"); return;
case FALSENODE:
handleUselessWarn(node, "false"); return;
case NILNODE:
handleUselessWarn(node, "nil"); return;
// FIXME: Temporarily disabling because this fires way too much running Rails tests. JRUBY-518
/*case SELFNODE:
handleUselessWarn(node, "self"); return;*/
case TRUENODE:
handleUselessWarn(node, "true"); return;
default: return;
}
} while (true);
}
/**
* Check all nodes but the last one in a BlockNode for useless (void context) statements.
*
* @param blockNode to be checked.
*/
public void checkUselessStatements(BlockNode blockNode) {
if (warnings.isVerbose()) {
Node lastNode = blockNode.getLast();
for (int i = 0; i < blockNode.size(); i++) {
Node currentNode = blockNode.get(i);
if (lastNode != currentNode ) {
checkUselessStatement(currentNode);
}
}
}
}
/**
* assign_in_cond
**/
private boolean checkAssignmentInCondition(Node node) {
if (node instanceof MultipleAsgnNode) {
throw new SyntaxException(PID.MULTIPLE_ASSIGNMENT_IN_CONDITIONAL, node.getPosition(),
lexer.getCurrentLine(), "Multiple assignment in conditional.");
} else if (node instanceof LocalAsgnNode || node instanceof DAsgnNode || node instanceof GlobalAsgnNode || node instanceof InstAsgnNode) {
Node valueNode = ((AssignableNode) node).getValueNode();
if (valueNode instanceof ILiteralNode || valueNode instanceof NilNode || valueNode instanceof TrueNode || valueNode instanceof FalseNode) {
warnings.warn(ID.ASSIGNMENT_IN_CONDITIONAL, node.getPosition(), "Found '=' in conditional, should be '=='.");
}
return true;
}
return false;
}
protected Node makeNullNil(Node node) {
return node == null ? NilImplicitNode.NIL : node;
}
private Node cond0(Node node) {
checkAssignmentInCondition(node);
Node leftNode = null;
Node rightNode = null;
// FIXME: DSTR,EVSTR,STR: warning "string literal in condition"
switch(node.getNodeType()) {
case DREGEXPNODE: {
ISourcePosition position = node.getPosition();
return new Match2Node(position, node, new GlobalVarNode(position, "$_"));
}
case ANDNODE:
leftNode = cond0(((AndNode) node).getFirstNode());
rightNode = cond0(((AndNode) node).getSecondNode());
return new AndNode(node.getPosition(), makeNullNil(leftNode), makeNullNil(rightNode));
case ORNODE:
leftNode = cond0(((OrNode) node).getFirstNode());
rightNode = cond0(((OrNode) node).getSecondNode());
return new OrNode(node.getPosition(), makeNullNil(leftNode), makeNullNil(rightNode));
case DOTNODE: {
DotNode dotNode = (DotNode) node;
if (dotNode.isLiteral()) return node;
String label = String.valueOf("FLIP" + node.hashCode());
currentScope.getLocalScope().addVariable(label);
int slot = currentScope.isDefined(label);
return new FlipNode(node.getPosition(),
getFlipConditionNode(((DotNode) node).getBeginNode()),
getFlipConditionNode(((DotNode) node).getEndNode()),
dotNode.isExclusive(), slot);
}
case REGEXPNODE:
warningUnlessEOption(ID.REGEXP_LITERAL_IN_CONDITION, node, "regex literal in condition");
return new MatchNode(node.getPosition(), node);
}
return node;
}
public Node getConditionNode(Node node) {
if (node == null) return NilImplicitNode.NIL;
if (node instanceof NewlineNode) {
return new NewlineNode(node.getPosition(), cond0(((NewlineNode) node).getNextNode()));
}
return cond0(node);
}
/* MRI: range_op */
private Node getFlipConditionNode(Node node) {
if (!configuration.isInlineSource()) return node;
node = getConditionNode(node);
if (node instanceof NewlineNode) return ((NewlineNode) node).getNextNode();
if (node instanceof FixnumNode) {
warnUnlessEOption(ID.LITERAL_IN_CONDITIONAL_RANGE, node, "integer literal in conditional range");
return getOperatorCallNode(node, "==", new GlobalVarNode(node.getPosition(), "$."));
}
return node;
}
public SValueNode newSValueNode(ISourcePosition position, Node node) {
return new SValueNode(position, node);
}
public SplatNode newSplatNode(ISourcePosition position, Node node) {
return new SplatNode(position, makeNullNil(node));
}
public ArrayNode newArrayNode(ISourcePosition position, Node firstNode) {
return new ArrayNode(position, makeNullNil(firstNode));
}
public ISourcePosition position(ISourcePositionHolder one, ISourcePositionHolder two) {
return one == null ? two.getPosition() : one.getPosition();
}
public AndNode newAndNode(ISourcePosition position, Node left, Node right) {
checkExpression(left);
if (left == null && right == null) return new AndNode(position, makeNullNil(left), makeNullNil(right));
return new AndNode(position(left, right), makeNullNil(left), makeNullNil(right));
}
public OrNode newOrNode(ISourcePosition position, Node left, Node right) {
checkExpression(left);
if (left == null && right == null) return new OrNode(position, makeNullNil(left), makeNullNil(right));
return new OrNode(position(left, right), makeNullNil(left), makeNullNil(right));
}
/**
* Ok I admit that this is somewhat ugly. We post-process a chain of when nodes and analyze
* them to re-insert them back into our new CaseNode the way we want. The grammar is being
* difficult and until I go back into the depths of that this is where things are.
*
* @param expression of the case node (e.g. case foo)
* @param firstWhenNode first when (which could also be the else)
* @return a new case node
*/
public CaseNode newCaseNode(ISourcePosition position, Node expression, Node firstWhenNode) {
ArrayNode cases = new ArrayNode(firstWhenNode != null ? firstWhenNode.getPosition() : position);
CaseNode caseNode = new CaseNode(position, expression, cases);
for (Node current = firstWhenNode; current != null; current = ((WhenNode) current).getNextCase()) {
if (current instanceof WhenOneArgNode) {
cases.add(current);
} else if (current instanceof WhenNode) {
simplifyMultipleArgumentWhenNodes((WhenNode) current, cases);
} else {
caseNode.setElseNode(current);
break;
}
}
return caseNode;
}
/*
* This method exists for us to break up multiple expression when nodes (e.g. when 1,2,3:)
* into individual whenNodes. The primary reason for this is to ensure lazy evaluation of
* the arguments (when foo,bar,gar:) to prevent side-effects. In the old code this was done
* using nested when statements, which was awful for interpreter and compilation.
*
* Notes: This has semantic equivalence but will not be lexically equivalent. Compiler
* needs to detect same bodies to simplify bytecode generated.
*/
private void simplifyMultipleArgumentWhenNodes(WhenNode sourceWhen, ArrayNode cases) {
Node expressionNodes = sourceWhen.getExpressionNodes();
if (expressionNodes instanceof SplatNode || expressionNodes instanceof ArgsCatNode) {
cases.add(sourceWhen);
return;
}
if (expressionNodes instanceof ListNode) {
ListNode list = (ListNode) expressionNodes;
ISourcePosition position = sourceWhen.getPosition();
Node bodyNode = sourceWhen.getBodyNode();
for (int i = 0; i < list.size(); i++) {
Node expression = list.get(i);
if (expression instanceof SplatNode || expression instanceof ArgsCatNode) {
cases.add(new WhenNode(position, expression, bodyNode, null));
} else {
cases.add(new WhenOneArgNode(position, expression, bodyNode, null));
}
}
} else {
cases.add(sourceWhen);
}
}
public WhenNode newWhenNode(ISourcePosition position, Node expressionNodes, Node bodyNode, Node nextCase) {
if (bodyNode == null) bodyNode = NilImplicitNode.NIL;
if (expressionNodes instanceof SplatNode || expressionNodes instanceof ArgsCatNode || expressionNodes instanceof ArgsPushNode) {
return new WhenNode(position, expressionNodes, bodyNode, nextCase);
}
ListNode list = (ListNode) expressionNodes;
if (list.size() == 1) {
Node element = list.get(0);
if (!(element instanceof SplatNode)) {
return new WhenOneArgNode(position, element, bodyNode, nextCase);
}
}
return new WhenNode(position, expressionNodes, bodyNode, nextCase);
}
public Node getReturnArgsNode(Node node) {
if (node instanceof ArrayNode && ((ArrayNode) node).size() == 1) {
return ((ListNode) node).get(0);
} else if (node instanceof BlockPassNode) {
throw new SyntaxException(PID.BLOCK_ARG_UNEXPECTED, node.getPosition(),
lexer.getCurrentLine(), "Block argument should not be given.");
}
return node;
}
public Node new_opElementAsgnNode(ISourcePosition position, Node receiverNode, String operatorName, Node argsNode, Node valueNode) {
if (argsNode instanceof ArrayNode) {
ArrayNode array = (ArrayNode) argsNode;
if (array.size() == 1) {
if (operatorName.equals("||")) {
return new OpElementOneArgOrAsgnNode(position, receiverNode, operatorName, array, valueNode);
} else if (operatorName.equals("&&")) {
return new OpElementOneArgAndAsgnNode(position, receiverNode, operatorName, array, valueNode);
} else {
return new OpElementOneArgAsgnNode(position, receiverNode, operatorName, array, valueNode);
}
}
}
return new OpElementAsgnNode(position, receiverNode, operatorName, argsNode, valueNode);
}
public Node new_attrassign(ISourcePosition position, Node receiver, String name, Node args) {
if (!(args instanceof ArrayNode)) return new AttrAssignNode(position, receiver, name, args);
ArrayNode argsNode = (ArrayNode) args;
switch (argsNode.size()) {
case 1:
return new AttrAssignOneArgNode(position, receiver, name, argsNode);
case 2:
return new AttrAssignTwoArgNode(position, receiver, name, argsNode);
case 3:
return new AttrAssignThreeArgNode(position, receiver, name, argsNode);
default:
return new AttrAssignNode(position, receiver, name, argsNode);
}
}
private Node new_call_noargs(Node receiver, Token name, IterNode iter) {
ISourcePosition position = position(receiver, name);
if (receiver == null) receiver = NilImplicitNode.NIL;
if (iter != null) return new CallNoArgBlockNode(position, receiver, (String) name.getValue(), iter);
return new CallNoArgNode(position, receiver, (String) name.getValue());
}
private Node new_call_complexargs(Node receiver, Token name, Node args, Node iter) {
if (args instanceof BlockPassNode) {
// Block and block pass passed in at same time....uh oh
if (iter != null) {
throw new SyntaxException(PID.BLOCK_ARG_AND_BLOCK_GIVEN, iter.getPosition(),
lexer.getCurrentLine(), "Both block arg and actual block given.");
}
return new_call_blockpass(receiver, name, (BlockPassNode) args);
}
if (iter != null) return new CallSpecialArgBlockNode(position(receiver, args), receiver,(String) name.getValue(), args, (IterNode) iter);
return new CallSpecialArgNode(position(receiver, args), receiver, (String) name.getValue(), args);
}
private Node new_call_blockpass(Node receiver, Token operation, BlockPassNode blockPass) {
ISourcePosition position = position(receiver, blockPass);
String name = (String) operation.getValue();
Node args = blockPass.getArgsNode();
if (args == null) return new CallNoArgBlockPassNode(position, receiver, name, args, blockPass);
if (!(args instanceof ArrayNode)) return new CallSpecialArgBlockPassNode(position, receiver, name, args, blockPass);
switch (((ArrayNode) args).size()) {
case 0: // foo()
return new CallNoArgBlockPassNode(position, receiver, name, args, blockPass);
case 1:
return new CallOneArgBlockPassNode(position, receiver, name, (ArrayNode) args, blockPass);
case 2:
return new CallTwoArgBlockPassNode(position, receiver, name, (ArrayNode) args, blockPass);
case 3:
return new CallThreeArgBlockPassNode(position, receiver, name, (ArrayNode) args, blockPass);
default:
return new CallManyArgsBlockPassNode(position, receiver, name, args, blockPass);
}
}
private boolean isNumericOperator(String name) {
if (name.length() == 1) {
switch (name.charAt(0)) {
case '+': case '-': case '*': case '/': case '<': case '>':
return true;
}
} else if (name.length() == 2) {
switch (name.charAt(0)) {
case '<': case '>': case '=':
switch (name.charAt(1)) {
case '=': case '<':
return true;
}
}
}
return false;
}
private Node new_call_one_arg(ISourcePosition position, Node receiver, String name, Node first) {
if (first instanceof FixnumNode && isNumericOperator(name)) {
return new CallOneArgFixnumNode(position, receiver, name, new ArrayNode(position, first));
}
return new CallOneArgNode(position, receiver, name, new ArrayNode(position, first));
}
public Node new_call(Node receiver, Token name, Node argsNode, Node iter) {
if (argsNode == null) return new_call_noargs(receiver, name, (IterNode) iter);
if (!(argsNode instanceof ArrayNode)) return new_call_complexargs(receiver, name, argsNode, iter);
ArrayNode args = (ArrayNode) argsNode;
switch (args.size()) {
case 0:
if (iter != null) return new CallNoArgBlockNode(position(receiver, args), receiver, (String) name.getValue(), args, (IterNode) iter);
return new CallNoArgNode(position(receiver, args), receiver, args, (String) name.getValue());
case 1:
if (iter != null) return new CallOneArgBlockNode(position(receiver, args), receiver, (String) name.getValue(), args, (IterNode) iter);
return new CallOneArgNode(position(receiver, args), receiver, (String) name.getValue(), args);
case 2:
if (iter != null) return new CallTwoArgBlockNode(position(receiver, args), receiver, (String) name.getValue(), args, (IterNode) iter);
return new CallTwoArgNode(position(receiver, args), receiver, (String) name.getValue(), args);
case 3:
if (iter != null) return new CallThreeArgBlockNode(position(receiver, args), receiver, (String) name.getValue(), args, (IterNode) iter);
return new CallThreeArgNode(position(receiver, args), receiver, (String) name.getValue(), args);
default:
if (iter != null) return new CallManyArgsBlockNode(position(receiver, args), receiver, (String) name.getValue(), args, (IterNode) iter);
return new CallManyArgsNode(position(receiver, args), receiver, (String) name.getValue(), args);
}
}
public Node new_aref(Node receiver, Token name, Node argsNode) {
if (argsNode instanceof ArrayNode) {
ArrayNode args = (ArrayNode) argsNode;
if (args.size() == 1 && args.get(0) instanceof FixnumNode) {
return new CallOneArgFixnumNode(position(receiver, args), receiver, "[]", args);
}
}
return new_call(receiver, name, argsNode, null);
}
public Colon2Node new_colon2(ISourcePosition position, Node leftNode, String name) {
if (IdUtil.isConstant(name)) {
if (leftNode == null) return new Colon2ImplicitNode(position, name);
return new Colon2ConstNode(position, leftNode, name);
}
return new Colon2MethodNode(position, leftNode, name);
}
public Colon3Node new_colon3(ISourcePosition position, String name) {
return new Colon3Node(position, name);
}
private Node new_fcall_noargs(Token operation, IterNode iter) {
if (iter != null) return new FCallNoArgBlockNode(operation.getPosition(), (String) operation.getValue(), iter);
return new FCallNoArgNode(operation.getPosition(), (String) operation.getValue());
}
private Node new_fcall_simpleargs(Token operation, ArrayNode args, Node iter) {
String name = (String) operation.getValue();
ISourcePosition position = position(operation, args);
switch (args.size()) {
case 0: // foo()
if (iter != null) return new FCallNoArgBlockNode(position, name, args, (IterNode) iter);
return new FCallNoArgNode(position, args, name);
case 1:
if (iter != null) return new FCallOneArgBlockNode(position, name, args, (IterNode) iter);
return new FCallOneArgNode(position, name, args);
case 2:
if (iter != null) return new FCallTwoArgBlockNode(position, name, args, (IterNode) iter);
return new FCallTwoArgNode(position, name, args);
case 3:
if (iter != null) return new FCallThreeArgBlockNode(position, name, args, (IterNode) iter);
return new FCallThreeArgNode(position, name, args);
default:
if (iter != null) return new FCallManyArgsBlockNode(position, name, args, (IterNode) iter);
return new FCallManyArgsNode(position, name, args);
}
}
private Node new_fcall_blockpass(Token operation, BlockPassNode blockPass) {
ISourcePosition position = position(operation, blockPass);
String name = (String) operation.getValue();
Node args = blockPass.getArgsNode();
if (args == null) return new FCallNoArgBlockPassNode(position, name, args, blockPass);
if (!(args instanceof ArrayNode)) return new FCallSpecialArgBlockPassNode(position, name, args, blockPass);
switch (((ArrayNode) args).size()) {
case 0: // foo()
return new FCallNoArgBlockPassNode(position, name, args, blockPass);
case 1:
return new FCallOneArgBlockPassNode(position, name, (ArrayNode) args, blockPass);
case 2:
return new FCallTwoArgBlockPassNode(position, name, (ArrayNode) args, blockPass);
case 3:
return new FCallThreeArgBlockPassNode(position, name, (ArrayNode) args, blockPass);
default:
return new FCallManyArgsBlockPassNode(position, name, args, blockPass);
}
}
public Node new_fcall(Token operation, Node args, Node iter) {
if (args == null) return new_fcall_noargs(operation, (IterNode) iter);
if (args instanceof ArrayNode) return new_fcall_simpleargs(operation, (ArrayNode) args, iter);
if (args instanceof BlockPassNode) {
if (iter == null) return new_fcall_blockpass(operation, (BlockPassNode) args);
throw new SyntaxException(PID.BLOCK_ARG_AND_BLOCK_GIVEN, iter.getPosition(),
lexer.getCurrentLine(), "Both block arg and actual block given.");
}
return new FCallSpecialArgNode(position(operation, args), (String) operation.getValue(), args);
}
public Node new_super(Node args, Token operation) {
if (args != null && args instanceof BlockPassNode) {
return new SuperNode(position(operation, args), ((BlockPassNode) args).getArgsNode(), args);
}
return new SuperNode(operation.getPosition(), args);
}
/**
* Description of the RubyMethod
*/
public void initTopLocalVariables() {
DynamicScope scope = configuration.getScope();
currentScope = scope.getStaticScope();
result.setScope(scope);
}
/** Getter for property inSingle.
* @return Value of property inSingle.
*/
public boolean isInSingle() {
return inSingleton != 0;
}
/** Setter for property inSingle.
* @param inSingle New value of property inSingle.
*/
public void setInSingle(int inSingle) {
this.inSingleton = inSingle;
}
public boolean isInDef() {
return inDefinition;
}
public void setInDef(boolean inDef) {
this.inDefinition = inDef;
}
/** Getter for property inSingle.
* @return Value of property inSingle.
*/
public int getInSingle() {
return inSingleton;
}
/**
* Gets the result.
* @return Returns a RubyParserResult
*/
public RubyParserResult getResult() {
return result;
}
/**
* Sets the result.
* @param result The result to set
*/
public void setResult(RubyParserResult result) {
this.result = result;
}
/**
* Sets the configuration.
* @param configuration The configuration to set
*/
public void setConfiguration(ParserConfiguration configuration) {
this.configuration = configuration;
}
public void setWarnings(IRubyWarnings warnings) {
this.warnings = warnings;
}
public void setLexer(RubyYaccLexer lexer) {
this.lexer = lexer;
}
public DStrNode createDStrNode(ISourcePosition position) {
return new DStrNode(position);
}
public Node literal_concat(ISourcePosition position, Node head, Node tail) {
if (head == null) return tail;
if (tail == null) return head;
if (head instanceof EvStrNode) {
head = createDStrNode(head.getPosition()).add(head);
}
if (tail instanceof StrNode) {
if (head instanceof StrNode) {
StrNode front = (StrNode) head;
// string_contents always makes an empty strnode...which is sometimes valid but
// never if it ever is in literal_concat.
if (front.getValue().getRealSize() > 0) {
return new StrNode(head.getPosition(), front, (StrNode) tail);
} else {
return tail;
}
}
head.setPosition(head.getPosition());
return ((ListNode) head).add(tail);
} else if (tail instanceof DStrNode) {
if (head instanceof StrNode){
((DStrNode)tail).prepend(head);
return tail;
}
return ((ListNode) head).addAll(tail);
}
// tail must be EvStrNode at this point
if (head instanceof StrNode) {
//Do not add an empty string node
if(((StrNode) head).getValue().length() == 0) {
head = createDStrNode(head.getPosition());
} else {
head = createDStrNode(head.getPosition()).add(head);
}
}
return ((DStrNode) head).add(tail);
}
public Node newEvStrNode(ISourcePosition position, Node node) {
Node head = node;
while (true) {
if (node == null) break;
if (node instanceof StrNode || node instanceof DStrNode || node instanceof EvStrNode) {
return node;
}
if (!(node instanceof NewlineNode)) break;
node = ((NewlineNode) node).getNextNode();
}
return new EvStrNode(position, head);
}
public IterNode new_iter(ISourcePosition position, Node vars,
StaticScope scope, Node body) {
if (vars != null && vars instanceof BlockPassNode) {
vars = ((BlockPassNode)vars).getArgsNode();
}
return new IterNode(position, vars, scope, body);
}
public Node new_yield(ISourcePosition position, Node node) {
boolean state = true;
if (node != null) {
if (node instanceof BlockPassNode) {
throw new SyntaxException(PID.BLOCK_ARG_UNEXPECTED, node.getPosition(),
lexer.getCurrentLine(), "Block argument should not be given.");
}
if (node instanceof ArrayNode && configuration.getVersion() == CompatVersion.RUBY1_8 &&
((ArrayNode)node).size() == 1) {
node = ((ArrayNode)node).get(0);
state = false;
}
if (node != null && node instanceof SplatNode) {
state = true;
}
} else {
return new ZYieldNode(position);
}
if (state && node instanceof ArrayNode) {
ArrayNode args = (ArrayNode) node;
switch (args.size()) {
case 0:
return new ZYieldNode(position);
case 1:
return new YieldOneNode(position, args);
case 2:
return new YieldTwoNode(position, args);
case 3:
return new YieldThreeNode(position, args);
}
}
if (node instanceof FixnumNode) {
return new YieldOneNode(position, (FixnumNode) node);
}
return new YieldNode(position, node, state);
}
public Node negateInteger(Node integerNode) {
if (integerNode instanceof FixnumNode) {
FixnumNode fixnumNode = (FixnumNode) integerNode;
fixnumNode.setValue(-fixnumNode.getValue());
return fixnumNode;
} else if (integerNode instanceof BignumNode) {
BignumNode bignumNode = (BignumNode) integerNode;
BigInteger value = bignumNode.getValue().negate();
// Negating a bignum will make the last negative value of our bignum
if (value.compareTo(RubyBignum.LONG_MIN) >= 0) {
return new FixnumNode(bignumNode.getPosition(), value.longValue());
}
bignumNode.setValue(value);
}
return integerNode;
}
public FloatNode negateFloat(FloatNode floatNode) {
floatNode.setValue(-floatNode.getValue());
return floatNode;
}
// FIXME: Remove this from grammars.
public ISourcePosition createEmptyArgsNodePosition(ISourcePosition pos) {
return pos;
}
public Node unwrapNewlineNode(Node node) {
if(node instanceof NewlineNode) {
return ((NewlineNode) node).getNextNode();
}
return node;
}
private Node checkForNilNode(Node node, ISourcePosition defaultPosition) {
return (node == null) ? new NilNode(defaultPosition) : node;
}
public Node new_args(ISourcePosition position, ListNode pre, ListNode optional, RestArgNode rest,
ListNode post, BlockArgNode block) {
// Zero-Argument declaration
if (optional == null && rest == null && post == null && block == null) {
if (pre == null || pre.size() == 0) return new ArgsNoArgNode(position);
if (pre.size() == 1 && !hasAssignableArgs(pre)) return new ArgsPreOneArgNode(position, pre);
if (pre.size() == 2 && !hasAssignableArgs(pre)) return new ArgsPreTwoArgNode(position, pre);
}
return new ArgsNode(position, pre, optional, rest, post, block);
}
private boolean hasAssignableArgs(ListNode list) {
for (Node node : list.childNodes()) {
if (node instanceof AssignableNode) return true;
}
return false;
}
public Node newAlias(ISourcePosition position, Node newNode, Node oldNode) {
return new AliasNode(position, newNode, oldNode);
}
public Node newUndef(ISourcePosition position, Node nameNode) {
return new UndefNode(position, nameNode);
}
public BlockArg18Node newBlockArg18(ISourcePosition position, Node blockValue, Node args) {
return new BlockArg18Node(position, blockValue, args);
}
public BlockArgNode newBlockArg(ISourcePosition position, Token nameToken) {
String identifier = (String) nameToken.getValue();
if (getCurrentScope().getLocalScope().isDefined(identifier) >= 0) {
throw new SyntaxException(PID.BAD_IDENTIFIER, position, lexer.getCurrentLine(),
"duplicate block argument name");
}
return new BlockArgNode(position, getCurrentScope().getLocalScope().addVariable(identifier), identifier);
}
/**
* generate parsing error
*/
public void yyerror(String message) {
throw new SyntaxException(PID.GRAMMAR_ERROR, lexer.getPosition(), lexer.getCurrentLine(), message);
}
/**
* generate parsing error
* @param message text to be displayed.
* @param expected list of acceptable tokens, if available.
*/
public void yyerror(String message, String[] expected, String found) {
String text = message + ", unexpected " + found + "\n";
throw new SyntaxException(PID.GRAMMAR_ERROR, lexer.getPosition(), lexer.getCurrentLine(), text, found);
}
public ISourcePosition getPosition(ISourcePositionHolder start) {
return start != null ? lexer.getPosition(start.getPosition()) : lexer.getPosition();
}
public void warn(ID id, ISourcePosition position, String message, Object... data) {
warnings.warn(id, position, message);
}
public void warning(ID id, ISourcePosition position, String message, Object... data) {
if (warnings.isVerbose()) {
warnings.warning(id, position, message);
}
}
// ENEBO: Totally weird naming (in MRI is not allocated and is a local var name) [1.9]
public boolean is_local_id(Token identifier) {
String name = (String) identifier.getValue();
return lexer.isIdentifierChar(name.charAt(0));
}
// 1.9
public ListNode list_append(Node list, Node item) {
if (list == null) return new ArrayNode(item.getPosition(), item);
if (!(list instanceof ListNode)) return new ArrayNode(list.getPosition(), list).add(item);
return ((ListNode) list).add(item);
}
// 1.9
public Node new_bv(Token identifier) {
if (!is_local_id(identifier)) {
getterIdentifierError(identifier.getPosition(), (String) identifier.getValue());
}
shadowing_lvar(identifier);
return arg_var(identifier);
}
// 1.9
public ArgumentNode arg_var(Token identifier) {
String name = (String) identifier.getValue();
StaticScope current = getCurrentScope();
// Multiple _ arguments are allowed. To not screw with tons of arity
// issues in our runtime we will allocate unnamed bogus vars so things
// still work. MRI does not use name as intern'd value so they don't
// have this issue.
if (name == "_") {
int count = 0;
while (current.exists(name) >= 0) {
name = "_$" + count++;
}
}
return new ArgumentNode(identifier.getPosition(), name,
getCurrentScope().addVariableThisScope(name));
}
public Token formal_argument(Token identifier) {
if (!is_local_id(identifier)) yyerror("formal argument must be local variable");
return shadowing_lvar(identifier);
}
// 1.9
public Token shadowing_lvar(Token identifier) {
String name = (String) identifier.getValue();
if (name == "_") return identifier;
StaticScope current = getCurrentScope();
if (current instanceof BlockStaticScope) {
if (current.exists(name) >= 0) yyerror("duplicated argument name");
if (warnings.isVerbose() && current.isDefined(name) >= 0) {
warnings.warning(ID.STATEMENT_NOT_REACHED, identifier.getPosition(),
"shadowing outer local variable - " + name);
}
} else if (current.exists(name) >= 0) {
yyerror("duplicated argument name");
}
return identifier;
}
// 1.9
public ListNode list_concat(Node first, Node second) {
if (first instanceof ListNode) {
if (second instanceof ListNode) {
return ((ListNode) first).addAll((ListNode) second);
} else {
return ((ListNode) first).addAll(second);
}
}
return new ArrayNode(first.getPosition(), first).add(second);
}
// 1.9
/**
* If node is a splat and it is splatting a literal array then return the literal array.
* Otherwise return null. This allows grammar to not splat into a Ruby Array if splatting
* a literal array.
*/
public Node splat_array(Node node) {
if (node instanceof SplatNode) node = ((SplatNode) node).getValue();
if (node instanceof ArrayNode) return node;
return null;
}
// 1.9
public Node arg_append(Node node1, Node node2) {
if (node1 == null) return new ArrayNode(node2.getPosition(), node2);
if (node1 instanceof ListNode) return ((ListNode) node1).add(node2);
if (node1 instanceof BlockPassNode) return arg_append(((BlockPassNode) node1).getBodyNode(), node2);
if (node1 instanceof ArgsPushNode) {
ArgsPushNode pushNode = (ArgsPushNode) node1;
Node body = pushNode.getSecondNode();
return new ArgsCatNode(pushNode.getPosition(), pushNode.getFirstNode(),
new ArrayNode(body.getPosition(), body).add(node2));
}
return new ArgsPushNode(position(node1, node2), node1, node2);
}
public void regexpFragmentCheck(RegexpNode end, ByteList value) {
// 1.9 mode overrides to do extra checking...
}
protected void checkRegexpSyntax(ByteList value, RegexpOptions options) {
RubyRegexp.newRegexp(getConfiguration().getRuntime(), value, options);
}
public Node newRegexpNode(ISourcePosition position, Node contents, RegexpNode end) {
RegexpOptions options = end.getOptions();
Encoding encoding = null;
if (!lexer.isOneEight()) {
encoding = lexer.getEncoding();
}
if (contents == null) {
ByteList newValue = ByteList.create("");
if (encoding != null) {
newValue.setEncoding(encoding);
}
regexpFragmentCheck(end, newValue);
return new RegexpNode(position, newValue, options.withoutOnce());
} else if (contents instanceof StrNode) {
ByteList meat = (ByteList) ((StrNode) contents).getValue().clone();
regexpFragmentCheck(end, meat);
checkRegexpSyntax(meat, options.withoutOnce());
return new RegexpNode(contents.getPosition(), meat, options.withoutOnce());
} else if (contents instanceof DStrNode) {
DStrNode dStrNode = (DStrNode) contents;
for (Node fragment: dStrNode.childNodes()) {
if (fragment instanceof StrNode) {
ByteList frag = ((StrNode) fragment).getValue();
regexpFragmentCheck(end, frag);
// if (!lexer.isOneEight()) encoding = frag.getEncoding();
}
}
dStrNode.prepend(new StrNode(contents.getPosition(), createMaster(options)));
return new DRegexpNode(position, options, encoding).addAll(dStrNode);
}
// EvStrNode: #{val}: no fragment check, but at least set encoding
ByteList master = createMaster(options);
regexpFragmentCheck(end, master);
if (!lexer.isOneEight()) encoding = master.getEncoding();
DRegexpNode node = new DRegexpNode(position, options, encoding);
node.add(new StrNode(contents.getPosition(), master));
node.add(contents);
return node;
}
// Create the magical empty 'master' string which will be encoded with
// regexp options encoding so dregexps can end up starting with the
// right encoding.
private ByteList createMaster(RegexpOptions options) {
if (lexer.isOneEight()) {
return ByteList.create("");
} else {
Encoding encoding = options.setup19(configuration.getRuntime());
return new ByteList(new byte[] {}, encoding);
}
}
// FIXME: This logic is used by many methods in MRI, but we are only using it in lexer
// currently. Consolidate this when we tackle a big encoding refactoring
public static int associateEncoding(ByteList buffer, Encoding newEncoding, int codeRange) {
Encoding bufferEncoding = buffer.getEncoding();
if (newEncoding == bufferEncoding) return codeRange;
// TODO: Special const error
buffer.setEncoding(newEncoding);
if (codeRange != StringSupport.CR_7BIT || !newEncoding.isAsciiCompatible()) {
return StringSupport.CR_UNKNOWN;
}
return codeRange;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy