| /***** 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 <b.cerrina@wanadoo.fr> |
| * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se> |
| * Copyright (C) 2002-2004 Jan Arne Petersen <jpetersen@uni-bonn.de> |
| * Copyright (C) 2004 Charles O Nutter <headius@headius.com> |
| * Copyright (C) 2004 Thomas E Enebo <enebo@acm.org> |
| * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de> |
| * Copyright (C) 2006 Mirko Stocker <me@misto.ch> |
| * Copyright (C) 2006 Thomas Corbat <tcorbat@hsr.ch> |
| * |
| * 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 org.jruby.ast.AndNode; |
| import org.jruby.ast.ArgsCatNode; |
| import org.jruby.ast.ArgsPushNode; |
| import org.jruby.ast.ArrayNode; |
| import org.jruby.ast.AssignableNode; |
| import org.jruby.ast.AttrAssignNode; |
| import org.jruby.ast.BackRefNode; |
| import org.jruby.ast.BeginNode; |
| import org.jruby.ast.BignumNode; |
| import org.jruby.ast.BlockNode; |
| import org.jruby.ast.BlockPassNode; |
| import org.jruby.ast.BreakNode; |
| import org.jruby.ast.CallNode; |
| import org.jruby.ast.ClassVarAsgnNode; |
| import org.jruby.ast.ClassVarDeclNode; |
| import org.jruby.ast.ClassVarNode; |
| 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.EvStrNode; |
| import org.jruby.ast.FCallNode; |
| import org.jruby.ast.FalseNode; |
| 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.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.NilNode; |
| import org.jruby.ast.Node; |
| import org.jruby.ast.NodeTypes; |
| import org.jruby.ast.NthRefNode; |
| import org.jruby.ast.OptNNode; |
| import org.jruby.ast.OrNode; |
| import org.jruby.ast.RegexpNode; |
| 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.TrueNode; |
| import org.jruby.ast.YieldNode; |
| import org.jruby.ast.types.ILiteralNode; |
| import org.jruby.common.IRubyWarnings; |
| import org.jruby.lexer.yacc.ISourcePosition; |
| import org.jruby.lexer.yacc.ISourcePositionHolder; |
| import org.jruby.lexer.yacc.SourcePosition; |
| import org.jruby.lexer.yacc.SyntaxException; |
| import org.jruby.lexer.yacc.Token; |
| import org.jruby.runtime.DynamicScope; |
| import org.jruby.util.ByteList; |
| import org.jruby.util.IdUtil; |
| |
| /** |
| * |
| */ |
| public class ParserSupport { |
| // Parser states: |
| private StaticScope currentScope; |
| |
| // 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; |
| |
| private IRubyWarnings warnings; |
| |
| private RubyParserConfiguration configuration; |
| private RubyParserResult result; |
| |
| public void reset() { |
| inSingleton = 0; |
| inDefinition = false; |
| } |
| |
| public StaticScope getCurrentScope() { |
| return currentScope; |
| } |
| |
| 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; |
| } |
| |
| public Node appendPrintToBlock(Node block) { |
| ISourcePosition position = block.getPosition(); |
| return appendToBlock(block, new FCallNode(position, "print", |
| new ArrayNode(position).add(new GlobalVarNode(position, "$_")))); |
| } |
| |
| public Node appendWhileLoopToBlock(Node block, boolean chop, boolean split) { |
| ISourcePosition position = block.getPosition(); |
| if (split) { |
| block = appendToBlock(new GlobalAsgnNode(position, "$F", |
| new CallNode(position, new GlobalVarNode(position, "$_"), "split", null)), block); |
| } |
| if (chop) { |
| block = appendToBlock(new CallNode(position, new GlobalVarNode(position, "$_"), "chop!", null), block); |
| } |
| return new OptNNode(position, block); |
| } |
| |
| /** |
| * We know for callers of this that it cannot be any of the specials checked in gettable. |
| * |
| * @param id to check its variable type |
| * @param position location of this position |
| * @return an AST node representing this new variable |
| */ |
| public Node gettable2(String id, ISourcePosition position) { |
| switch (IdUtil.getVarType(id)) { |
| case IdUtil.LOCAL_VAR: |
| return currentScope.declare(position, id); |
| case IdUtil.CONSTANT: |
| return new ConstNode(position, id); |
| case IdUtil.INSTANCE_VAR: |
| return new InstVarNode(position, id); |
| case IdUtil.CLASS_VAR: |
| return new ClassVarNode(position, id); |
| case IdUtil.GLOBAL_VAR: |
| return new GlobalVarNode(position, id); |
| } |
| |
| throw new SyntaxException(position, "identifier " + id + " is not valid"); |
| } |
| |
| /** |
| * Create AST node representing variable type it represents. |
| * |
| * @param id to check its variable type |
| * @param position location of this position |
| * @return an AST node representing this new variable |
| */ |
| public Node gettable(String id, ISourcePosition position) { |
| if (id.equals("self")) { |
| return new SelfNode(position); |
| } else if (id.equals("nil")) { |
| return new NilNode(position); |
| } else if (id.equals("true")) { |
| return new TrueNode(position); |
| } else if (id.equals("false")) { |
| return new FalseNode(position); |
| } else if (id.equals("__FILE__")) { |
| return new StrNode(position, ByteList.create(position.getFile())); |
| } else if (id.equals("__LINE__")) { |
| return new FixnumNode(position, position.getEndLine()+1); |
| } |
| |
| return gettable2(id, position); |
| } |
| |
| public AssignableNode assignable(Token lhs, Node value) { |
| checkExpression(value); |
| |
| String id = (String) lhs.getValue(); |
| |
| if ("self".equals(id)) { |
| throw new SyntaxException(lhs.getPosition(), "Can't change the value of self"); |
| } else if ("nil".equals(id)) { |
| throw new SyntaxException(lhs.getPosition(), "Can't assign to nil"); |
| } else if ("true".equals(id)) { |
| throw new SyntaxException(lhs.getPosition(), "Can't assign to true"); |
| } else if ("false".equals(id)) { |
| throw new SyntaxException(lhs.getPosition(), "Can't assign to false"); |
| } else if ("__FILE__".equals(id)) { |
| throw new SyntaxException(lhs.getPosition(), "Can't assign to __FILE__"); |
| } else if ("__LINE__".equals(id)) { |
| throw new SyntaxException(lhs.getPosition(), "Can't assign to __LINE__"); |
| } else { |
| switch (IdUtil.getVarType(id)) { |
| case IdUtil.LOCAL_VAR: |
| return currentScope.assign(value != null ? union(lhs, value) : lhs.getPosition(), id, value); |
| case IdUtil.CONSTANT: |
| if (isInDef() || isInSingle()) { |
| throw new SyntaxException(lhs.getPosition(), "dynamic constant assignment"); |
| } |
| return new ConstDeclNode(lhs.getPosition(), id, null, value); |
| case IdUtil.INSTANCE_VAR: |
| return new InstAsgnNode(lhs.getPosition(), id, value); |
| case IdUtil.CLASS_VAR: |
| if (isInDef() || isInSingle()) { |
| return new ClassVarAsgnNode(lhs.getPosition(), id, value); |
| } |
| return new ClassVarDeclNode(lhs.getPosition(), id, value); |
| case IdUtil.GLOBAL_VAR: |
| return new GlobalAsgnNode(lhs.getPosition(), id, value); |
| } |
| } |
| |
| throw new SyntaxException(lhs.getPosition(), "identifier " + id + " is not valid"); |
| } |
| |
| /** |
| * 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 ISourcePosition union(ISourcePositionHolder first, ISourcePositionHolder second) { |
| while (first instanceof NewlineNode) { |
| first = ((NewlineNode) first).getNextNode(); |
| } |
| |
| while (second instanceof NewlineNode) { |
| second = ((NewlineNode) second).getNextNode(); |
| } |
| |
| if(second == null) { |
| return first.getPosition(); |
| } |
| |
| if(first == null){ |
| return second.getPosition(); |
| } |
| |
| return first.getPosition().union(second.getPosition()); |
| } |
| |
| public ISourcePosition union(ISourcePosition first, ISourcePosition second) { |
| // assert first.getFile().equals(second.getFile()); |
| |
| if (first.getStartOffset() < second.getStartOffset()) { |
| return new SourcePosition(first.getFile(), first.getStartLine(), |
| second.getEndLine(), first.getStartOffset(), second.getEndOffset()); |
| } else { |
| return new SourcePosition(first.getFile(), second.getStartLine(), |
| first.getEndLine(), second.getStartOffset(), first.getEndOffset()); |
| } |
| } |
| |
| public Node addRootNode(Node topOfAST, ISourcePosition position) { |
| // I am not sure we need to get AST to set AST and the appendToBlock could maybe get removed. |
| // For sure once we do two pass parsing we should since this is mostly just optimzation. |
| RootNode root = new RootNode(topOfAST != null ? topOfAST.getPosition() : position, result.getScope(), |
| appendToBlock(result.getAST(), topOfAST)); |
| |
| // FIXME: Should add begin and end nodes |
| |
| return root; |
| |
| } |
| |
| public Node appendToBlock(Node head, Node tail) { |
| if (tail == null) return head; |
| if (head == null) return tail; |
| |
| //Mirko asks: This was added, and it breaks a lof of my code, is it really needed? |
| //while (head instanceof NewlineNode) { |
| // head = ((NewlineNode) head).getNextNode(); |
| //} |
| |
| if (!(head instanceof BlockNode)) { |
| head = new BlockNode(head.getPosition()).add(head); |
| } |
| |
| if (warnings.isVerbose() && isBreakStatement(((ListNode) head).getLast())) { |
| warnings.warning(tail.getPosition(), "Statement not reached."); |
| } |
| |
| // Assumption: tail is never a list node |
| ((ListNode) head).addAll(tail); |
| head.setPosition(union(head, tail)); |
| return head; |
| } |
| |
| public Node getOperatorCallNode(Node firstNode, String operator) { |
| checkExpression(firstNode); |
| |
| return new CallNode(firstNode.getPosition(), firstNode, operator, null); |
| } |
| |
| 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 CallNode(union(firstNode.getPosition(), secondNode.getPosition()), firstNode, operator, new ArrayNode(secondNode.getPosition()).add(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 AttrAssignNode(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 AttrAssignNode(receiver.getPosition(), receiver, name + "=", null); |
| } |
| |
| public void backrefAssignError(Node node) { |
| if (node instanceof NthRefNode) { |
| throw new SyntaxException(node.getPosition(), "Can't set variable $" + ((NthRefNode) node).getMatchNumber() + '.'); |
| } else if (node instanceof BackRefNode) { |
| throw new SyntaxException(node.getPosition(), "Can't set variable $" + ((BackRefNode) node).getType() + '.'); |
| } |
| } |
| |
| public Node arg_add(ISourcePosition position, Node node1, Node node2) { |
| if (node1 == null) 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); |
| lhs.setPosition(union(lhs, rhs)); |
| } else if (lhs instanceof IArgumentNode) { |
| IArgumentNode invokableNode = (IArgumentNode) lhs; |
| |
| 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(position, "Dynamic constant assignment."); |
| } else if (node instanceof ArrayNode && ((ArrayNode)node).size() == 1) { |
| node = ((ArrayNode)node).get(0); |
| } else if (node instanceof SplatNode) { |
| node = new SValueNode(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.nodeId) { |
| case NodeTypes.NEWLINENODE: |
| node = ((NewlineNode) node).getNextNode(); |
| continue breakLoop; |
| case NodeTypes.BREAKNODE: case NodeTypes.NEXTNODE: case NodeTypes.REDONODE: |
| case NodeTypes.RETRYNODE: case NodeTypes.RETURNNODE: |
| return true; |
| default: |
| return false; |
| } |
| } while (true); |
| } |
| |
| /** |
| * Does this node represent an expression? |
| * @param node to be checked |
| * @return true if an expression, false otherwise |
| */ |
| public void checkExpression(Node node) { |
| if (!isExpression(node)) { |
| warnings.warning(node.getPosition(), "void value expression"); |
| } |
| } |
| |
| private boolean isExpression(Node node) { |
| expressionLoop: do { |
| if (node == null) return true; |
| |
| switch (node.nodeId) { |
| case NodeTypes.BEGINNODE: |
| node = ((BeginNode) node).getBodyNode(); |
| continue expressionLoop; |
| case NodeTypes.BLOCKNODE: |
| node = ((BlockNode) node).getLast(); |
| continue expressionLoop; |
| case NodeTypes.BREAKNODE: |
| node = ((BreakNode) node).getValueNode(); |
| continue expressionLoop; |
| case NodeTypes.CLASSNODE: case NodeTypes.DEFNNODE: case NodeTypes.DEFSNODE: |
| case NodeTypes.MODULENODE: case NodeTypes.NEXTNODE: case NodeTypes.REDONODE: |
| case NodeTypes.RETRYNODE: case NodeTypes.RETURNNODE: case NodeTypes.UNTILNODE: |
| case NodeTypes.WHILENODE: |
| return false; |
| case NodeTypes.IFNODE: |
| return isExpression(((IfNode) node).getThenBody()) && |
| isExpression(((IfNode) node).getElseBody()); |
| case NodeTypes.NEWLINENODE: |
| node = ((NewlineNode) node).getNextNode(); |
| continue expressionLoop; |
| default: // Node |
| return true; |
| } |
| } while (true); |
| } |
| |
| private void handleUselessWarn(Node node, String useless) { |
| warnings.warn(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()) return; |
| |
| uselessLoop: do { |
| if (node == null) return; |
| |
| switch (node.nodeId) { |
| case NodeTypes.NEWLINENODE: |
| node = ((NewlineNode) node).getNextNode(); |
| continue uselessLoop; |
| case NodeTypes.CALLNODE: { |
| String name = ((CallNode) node).getName().intern(); |
| |
| if (name == "+" || name == "-" || name == "*" || name == "/" || name == "%" || |
| name == "**" || name == "+@" || name == "-@" || name == "|" || name == "^" || |
| name == "&" || name == "<=>" || name == ">" || name == ">=" || name == "<" || |
| name == "<=" || name == "==" || name == "!=") { |
| handleUselessWarn(node, name); |
| } |
| return; |
| } |
| case NodeTypes.BACKREFNODE: case NodeTypes.DVARNODE: case NodeTypes.GLOBALVARNODE: |
| case NodeTypes.LOCALVARNODE: case NodeTypes.NTHREFNODE: case NodeTypes.CLASSVARNODE: |
| case NodeTypes.INSTVARNODE: |
| handleUselessWarn(node, "a variable"); return; |
| // FIXME: Temporarily disabling because this fires way too much running Rails tests. JRUBY-518 |
| /*case NodeTypes.CONSTNODE: |
| handleUselessWarn(node, "a constant"); return;*/ |
| case NodeTypes.BIGNUMNODE: case NodeTypes.DREGEXPNODE: case NodeTypes.DSTRNODE: |
| case NodeTypes.FIXNUMNODE: case NodeTypes.FLOATNODE: case NodeTypes.REGEXPNODE: |
| case NodeTypes.STRNODE: case NodeTypes.SYMBOLNODE: |
| handleUselessWarn(node, "a literal"); return; |
| // FIXME: Temporarily disabling because this fires way too much running Rails tests. JRUBY-518 |
| /*case NodeTypes.CLASSNODE: case NodeTypes.COLON2NODE: |
| handleUselessWarn(node, "::"); return;*/ |
| case NodeTypes.DOTNODE: |
| handleUselessWarn(node, ((DotNode) node).isExclusive() ? "..." : ".."); return; |
| case NodeTypes.DEFINEDNODE: |
| handleUselessWarn(node, "defined?"); return; |
| case NodeTypes.FALSENODE: |
| handleUselessWarn(node, "false"); return; |
| case NodeTypes.NILNODE: |
| handleUselessWarn(node, "nil"); return; |
| // FIXME: Temporarily disabling because this fires way too much running Rails tests. JRUBY-518 |
| /*case NodeTypes.SELFNODE: |
| handleUselessWarn(node, "self"); return;*/ |
| case NodeTypes.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); |
| } |
| } |
| } |
| } |
| |
| /** |
| * @fixme error handling |
| **/ |
| private boolean checkAssignmentInCondition(Node node) { |
| if (node instanceof MultipleAsgnNode) { |
| throw new SyntaxException(node.getPosition(), "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(node.getPosition(), "Found '=' in conditional, should be '=='."); |
| } |
| return true; |
| } |
| |
| return false; |
| } |
| |
| private Node cond0(Node node) { |
| checkAssignmentInCondition(node); |
| |
| if (node instanceof DRegexpNode) { |
| ISourcePosition position = node.getPosition(); |
| |
| return new Match2Node(position, node, new GlobalVarNode(position, "$_")); |
| } else if (node instanceof DotNode) { |
| int slot = currentScope.getLocalScope().addVariable(""); |
| return new FlipNode(node.getPosition(), |
| getFlipConditionNode(((DotNode) node).getBeginNode()), |
| getFlipConditionNode(((DotNode) node).getEndNode()), |
| ((DotNode) node).isExclusive(), slot); |
| } else if (node instanceof RegexpNode) { |
| return new MatchNode(node.getPosition(), node); |
| } else if (node instanceof StrNode) { |
| ISourcePosition position = node.getPosition(); |
| |
| return new MatchNode(position, new RegexpNode(position, (ByteList) ((StrNode) node).getValue().clone(), 0)); |
| } |
| |
| return node; |
| } |
| |
| public Node getConditionNode(Node node) { |
| if (node == null) return null; |
| |
| if (node instanceof NewlineNode) { |
| return new NewlineNode(node.getPosition(), cond0(((NewlineNode) node).getNextNode())); |
| } |
| |
| return cond0(node); |
| } |
| |
| private Node getFlipConditionNode(Node node) { |
| node = getConditionNode(node); |
| |
| if (node instanceof NewlineNode) return ((NewlineNode) node).getNextNode(); |
| |
| if (node instanceof FixnumNode) { |
| return getOperatorCallNode(node, "==", new GlobalVarNode(node.getPosition(), "$.")); |
| } |
| |
| return node; |
| } |
| |
| public AndNode newAndNode(Node left, Node right) { |
| checkExpression(left); |
| |
| return new AndNode(union(left, right), left, right); |
| } |
| |
| public OrNode newOrNode(Node left, Node right) { |
| checkExpression(left); |
| |
| return new OrNode(union(left, right), left, right); |
| } |
| |
| 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(node.getPosition(), "Block argument should not be given."); |
| } |
| return node; |
| } |
| |
| public Node new_call(Node receiver, Token name, Node args, Node iter) { |
| if (args == null) { |
| return new CallNode(union(receiver, name), receiver,(String) name.getValue(), null, iter); |
| } |
| |
| if (args instanceof BlockPassNode) { |
| // Block and block pass passed in at same time....uh oh |
| if (iter != null) { |
| throw new SyntaxException(iter.getPosition(), "Both block arg and actual block given."); |
| } |
| |
| return new CallNode(union(receiver, args), receiver, (String) name.getValue(), |
| ((BlockPassNode) args).getArgsNode(), args); |
| } |
| |
| return new CallNode(union(receiver, args), receiver,(String) name.getValue(), args, iter); |
| } |
| |
| public Node new_fcall(Token operation, Node args, Node iter) { |
| String name = (String) operation.getValue(); |
| |
| if (args == null) return new FCallNode(operation.getPosition(), name, args, iter); |
| |
| if (args instanceof BlockPassNode) { |
| if (iter != null) { |
| throw new SyntaxException(iter.getPosition(), "Both block arg and actual block given."); |
| } |
| return new FCallNode(union(operation, args), name, ((BlockPassNode) args).getArgsNode(), args); |
| } |
| |
| return new FCallNode(union(operation, args), name, args, iter); |
| } |
| |
| public Node new_super(Node args, Token operation) { |
| if (args != null && args instanceof BlockPassNode) { |
| return new SuperNode(union(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(RubyParserConfiguration configuration) { |
| this.configuration = configuration; |
| } |
| |
| public void setWarnings(IRubyWarnings warnings) { |
| this.warnings = warnings; |
| } |
| |
| 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 = new DStrNode(union(head.getPosition(), position)).add(head); |
| } |
| |
| if (tail instanceof StrNode) { |
| if (head instanceof StrNode) { |
| return new StrNode(union(head, tail), (StrNode) head, (StrNode) tail); |
| } |
| head.setPosition(union(head, tail)); |
| 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 = new DStrNode(head.getPosition()); |
| } else { |
| // All first element StrNode's do not include syntacical sugar. |
| head.getPosition().adjustStartOffset(-1); |
| head = new DStrNode(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 Node new_yield(ISourcePosition position, Node node) { |
| boolean state = true; |
| |
| if (node != null) { |
| if (node instanceof BlockPassNode) { |
| throw new SyntaxException(node.getPosition(), "Block argument should not be given."); |
| } |
| |
| if (node instanceof ArrayNode && ((ArrayNode)node).size() == 1) { |
| node = ((ArrayNode)node).get(0); |
| state = false; |
| } |
| |
| if (node != null && node instanceof SplatNode) { |
| state = true; |
| } |
| } else { |
| state = false; |
| } |
| |
| 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; |
| |
| bignumNode.setValue(bignumNode.getValue().negate()); |
| } |
| |
| return integerNode; |
| } |
| |
| public FloatNode negateFloat(FloatNode floatNode) { |
| floatNode.setValue(-floatNode.getValue()); |
| |
| return floatNode; |
| } |
| |
| public ISourcePosition createEmptyArgsNodePosition(ISourcePosition pos) { |
| return new SourcePosition(pos.getFile(), pos.getStartLine(), pos.getEndLine(), pos.getEndOffset() - 1, pos.getEndOffset() - 1); |
| } |
| |
| 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; |
| } |
| } |