From 3bcabf5876411b6919f0e8b80ecfb7b43240a1c4 Mon Sep 17 00:00:00 2001 From: Dmitry Cheryasov Date: Thu, 2 Apr 2009 09:51:49 +0400 Subject: [PATCH] Fixes PY-71, introduces parts in multi-part statements. Slight fixes to autoindenter. --- .../com/jetbrains/python/PyElementTypes.java | 19 ++- .../jetbrains/python/formatter/PyBlock.java | 89 +++++++++- .../python/parsing/StatementParsing.java | 39 ++++- .../psi/PyConditionalStatementPart.java | 16 ++ .../python/psi/PyElementVisitor.java | 2 +- .../com/jetbrains/python/psi/PyElsePart.java | 16 ++ .../{PyExceptBlock.java => PyExceptPart.java} | 6 +- .../jetbrains/python/psi/PyFinallyPart.java | 10 ++ .../com/jetbrains/python/psi/PyForPart.java | 23 +++ .../jetbrains/python/psi/PyForStatement.java | 13 +- .../com/jetbrains/python/psi/PyIfPart.java | 16 ++ .../jetbrains/python/psi/PyIfStatement.java | 12 +- .../jetbrains/python/psi/PyStatementPart.java | 18 ++ .../python/psi/PyTryExceptStatement.java | 14 +- .../com/jetbrains/python/psi/PyTryPart.java | 12 ++ .../com/jetbrains/python/psi/PyWhilePart.java | 10 ++ .../python/psi/PyWhileStatement.java | 10 +- .../impl/PyConditionalStatementPartImpl.java | 24 +++ .../python/psi/impl/PyElsePartImpl.java | 25 +++ ...ptBlockImpl.java => PyExceptPartImpl.java} | 4 +- .../python/psi/impl/PyFinallyPartImpl.java | 14 ++ .../python/psi/impl/PyForPartImpl.java | 67 ++++++++ .../python/psi/impl/PyForStatementImpl.java | 50 +++--- .../python/psi/impl/PyIfPartElifImpl.java | 19 +++ .../python/psi/impl/PyIfPartIfImpl.java | 21 +++ .../python/psi/impl/PyIfStatementImpl.java | 96 +++++------ .../psi/impl/PyPartitionedElementImpl.java | 39 +++++ .../python/psi/impl/PyStatementPartImpl.java | 25 +++ .../psi/impl/PyTryExceptStatementImpl.java | 75 +++------ .../python/psi/impl/PyTryPartImpl.java | 14 ++ .../python/psi/impl/PyWhilePartImpl.java | 14 ++ .../python/psi/impl/PyWhileStatementImpl.java | 61 ++++--- .../validation/AssignTargetAnnotator.java | 4 +- .../validation/BreakContinueAnnotator.java | 44 +++-- .../python/validation/TryExceptAnnotator.java | 10 +- python/testData/psi/IfStatement.py | 20 +++ python/testData/psi/IfStatement.txt | 88 ++++++++++ python/testData/psi/TryExceptFinally.txt | 32 ++-- python/testData/psi/TryFinally.txt | 30 ++-- python/testData/psi/parts/For.py | 5 + python/testData/psi/parts/If.py | 3 + python/testData/psi/parts/IfElifElse.py | 7 + python/testData/psi/parts/IfElse.py | 5 + python/testData/psi/parts/Try.py | 12 ++ python/testData/psi/parts/While.py | 5 + .../com/jetbrains/python/MarkedTestCase.java | 103 ++++++++++++ .../jetbrains/python/PythonParsingTest.java | 4 + .../jetbrains/python/StatementPartsTest.java | 159 ++++++++++++++++++ 48 files changed, 1136 insertions(+), 268 deletions(-) create mode 100644 python/src/com/jetbrains/python/psi/PyConditionalStatementPart.java create mode 100644 python/src/com/jetbrains/python/psi/PyElsePart.java rename python/src/com/jetbrains/python/psi/{PyExceptBlock.java => PyExceptPart.java} (81%) create mode 100644 python/src/com/jetbrains/python/psi/PyFinallyPart.java create mode 100644 python/src/com/jetbrains/python/psi/PyForPart.java create mode 100644 python/src/com/jetbrains/python/psi/PyIfPart.java create mode 100644 python/src/com/jetbrains/python/psi/PyStatementPart.java create mode 100644 python/src/com/jetbrains/python/psi/PyTryPart.java create mode 100644 python/src/com/jetbrains/python/psi/PyWhilePart.java create mode 100644 python/src/com/jetbrains/python/psi/impl/PyConditionalStatementPartImpl.java create mode 100644 python/src/com/jetbrains/python/psi/impl/PyElsePartImpl.java rename python/src/com/jetbrains/python/psi/impl/{PyExceptBlockImpl.java => PyExceptPartImpl.java} (93%) create mode 100644 python/src/com/jetbrains/python/psi/impl/PyFinallyPartImpl.java create mode 100644 python/src/com/jetbrains/python/psi/impl/PyForPartImpl.java create mode 100644 python/src/com/jetbrains/python/psi/impl/PyIfPartElifImpl.java create mode 100644 python/src/com/jetbrains/python/psi/impl/PyIfPartIfImpl.java create mode 100644 python/src/com/jetbrains/python/psi/impl/PyPartitionedElementImpl.java create mode 100644 python/src/com/jetbrains/python/psi/impl/PyStatementPartImpl.java create mode 100644 python/src/com/jetbrains/python/psi/impl/PyTryPartImpl.java create mode 100644 python/src/com/jetbrains/python/psi/impl/PyWhilePartImpl.java create mode 100644 python/testData/psi/IfStatement.py create mode 100644 python/testData/psi/IfStatement.txt create mode 100644 python/testData/psi/parts/For.py create mode 100644 python/testData/psi/parts/If.py create mode 100644 python/testData/psi/parts/IfElifElse.py create mode 100644 python/testData/psi/parts/IfElse.py create mode 100644 python/testData/psi/parts/Try.py create mode 100644 python/testData/psi/parts/While.py create mode 100644 python/testSrc/com/jetbrains/python/MarkedTestCase.java create mode 100644 python/testSrc/com/jetbrains/python/StatementPartsTest.java diff --git a/python/src/com/jetbrains/python/PyElementTypes.java b/python/src/com/jetbrains/python/PyElementTypes.java index 863a817c96d2..86f7d0314ef4 100644 --- a/python/src/com/jetbrains/python/PyElementTypes.java +++ b/python/src/com/jetbrains/python/PyElementTypes.java @@ -27,7 +27,7 @@ public interface PyElementTypes { PyElementType ARGUMENT_LIST = new PyElementType("ARGUMENT_LIST", PyArgumentListImpl.class); PyElementType IMPORT_ELEMENT = new PyElementType("IMPORT_ELEMENT", PyImportElementImpl.class); PyElementType STAR_IMPORT_ELEMENT = new PyElementType("STAR_IMPORT_ELEMENT", PyStarImportElementImpl.class); - PyElementType EXCEPT_BLOCK = new PyElementType("EXCEPT_BLOCK", PyExceptBlockImpl.class); + PyElementType EXCEPT_PART = new PyElementType("EXCEPT_PART", PyExceptPartImpl.class); PyElementType PRINT_TARGET = new PyElementType("PRINT_TARGET", PyPrintTargetImpl.class); PyElementType DECORATOR = new PyElementType("DECORATOR", PyDecoratorImpl.class); @@ -100,6 +100,8 @@ public interface PyElementTypes { REPR_EXPRESSION, GENERATOR_EXPRESSION, CONDITIONAL_EXPRESSION, YIELD_EXPRESSION, TARGET_EXPRESSION); + TokenSet LIST_LIKE_EXPRESSIONS = TokenSet.create(LIST_LITERAL_EXPRESSION, LIST_COMP_EXPRESSION, TUPLE_EXPRESSION); + TokenSet STATEMENT_LISTS = TokenSet.create(STATEMENT_LIST); TokenSet BINARY_OPS = TokenSet.create(PyTokenTypes.OR_KEYWORD, PyTokenTypes.AND_KEYWORD, PyTokenTypes.LT, PyTokenTypes.GT, @@ -108,4 +110,19 @@ public interface PyElementTypes { PyTokenTypes.IN_KEYWORD, PyTokenTypes.IS_KEYWORD, PyTokenTypes.NOT_KEYWORD, PyTokenTypes.PLUS, PyTokenTypes.MINUS, PyTokenTypes.MULT, PyTokenTypes.FLOORDIV, PyTokenTypes.DIV, PyTokenTypes.PERC); + // Parts + PyElementType IF_PART_IF = new PyElementType("IF_IF", PyIfPartIfImpl.class); + PyElementType IF_PART_ELIF = new PyElementType("IF_ELIF", PyIfPartElifImpl.class); + + PyElementType FOR_PART = new PyElementType("FOR_PART", PyForPartImpl.class); + PyElementType WHILE_PART = new PyElementType("WHILE_PART", PyWhilePartImpl.class); + + PyElementType TRY_PART = new PyElementType("TRY_PART", PyTryPartImpl.class); + PyElementType FINALLY_PART = new PyElementType("FINALLY_PART", PyFinallyPartImpl.class); + + PyElementType ELSE_PART = new PyElementType("ELSE_PART", PyElsePartImpl.class); + + TokenSet PARTS = TokenSet.create(IF_PART_IF, IF_PART_ELIF, FOR_PART, WHILE_PART, TRY_PART, FINALLY_PART, ELSE_PART); + TokenSet ELIFS = TokenSet.create(IF_PART_ELIF); + } diff --git a/python/src/com/jetbrains/python/formatter/PyBlock.java b/python/src/com/jetbrains/python/formatter/PyBlock.java index 6aa19a40eff5..8ebc62e72b85 100644 --- a/python/src/com/jetbrains/python/formatter/PyBlock.java +++ b/python/src/com/jetbrains/python/formatter/PyBlock.java @@ -18,15 +18,19 @@ package com.jetbrains.python.formatter; import com.intellij.formatting.*; import com.intellij.lang.ASTNode; +import com.intellij.openapi.editor.Document; import com.intellij.openapi.util.TextRange; -import com.intellij.psi.PsiErrorElement; -import com.intellij.psi.TokenType; +import com.intellij.psi.*; import com.intellij.psi.codeStyle.CodeStyleSettings; import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.TokenSet; import com.jetbrains.python.PyElementTypes; import com.jetbrains.python.PyTokenTypes; import com.jetbrains.python.PythonLanguage; +import com.jetbrains.python.psi.PyElement; +import com.jetbrains.python.psi.PyFile; +import com.jetbrains.python.psi.PyStatementList; +import com.jetbrains.python.psi.PyStatementPart; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -223,14 +227,35 @@ public class PyBlock implements Block { public ChildAttributes getChildAttributes(int newChildIndex) { int statementListsBelow = 0; if (newChildIndex > 0) { + // always pass decision to a sane block from top level from file or definition + if (_node.getPsi() instanceof PyFile || _node.getElementType() == PyTokenTypes.COLON) { + return ChildAttributes.DELEGATE_TO_PREV_CHILD; + } + PyBlock insertAfterBlock = (PyBlock)_subBlocks.get(newChildIndex - 1); + + System.out.println( // XXX debug + "getChildAttributes(" + newChildIndex + "): " + ((PyBlock)insertAfterBlock).getNode().getPsi().toString() + + " " + insertAfterBlock.getTextRange().getStartOffset() + + ":" + insertAfterBlock.getTextRange().getLength() + ); + + ASTNode prevNode = insertAfterBlock.getNode(); + PsiElement prevElt = prevNode.getPsi(); + + // stmt lists, parts and definitions should also think for themselves + if (prevElt instanceof PyStatementList || prevElt instanceof PyStatementPart) { + return ChildAttributes.DELEGATE_TO_PREV_CHILD; + } + ASTNode lastChild = insertAfterBlock.getNode(); // HACK? This code fragment is needed to make testClass2() pass, // but I don't quite understand why it is necessary and why the formatter // doesn't request childAttributes from the correct block while (lastChild != null) { - if (lastChild.getElementType() == PyElementTypes.STATEMENT_LIST && hasLineBreakBefore(lastChild)) { + IElementType last_type = lastChild.getElementType(); + if ( last_type == PyElementTypes.STATEMENT_LIST && hasLineBreakBefore(lastChild)) { statementListsBelow++; } else if (statementListsBelow > 0 && lastChild.getPsi() instanceof PsiErrorElement) { @@ -251,11 +276,19 @@ public class PyBlock implements Block { // the innermost block, so we need to resolve the situation here. Nested // delegation sometimes causes NPEs in formatter core, so we calculate the // correct indent manually. - if (statementListsBelow > 1) { + if (statementListsBelow > 0) { // was 1... strange int indent = _settings.getIndentSize(_language.getAssociatedFileType()); return new ChildAttributes(Indent.getSpaceIndent(indent * statementListsBelow), null); } + /* + // it might be something like "def foo(): # comment" or "[1, # comment"; jump up to the real thing + if (_node instanceof PsiComment || _node instanceof PsiWhiteSpace) { + get + } + */ + + return new ChildAttributes(getChildIndent(newChildIndex), getChildAlignment()); } @@ -273,6 +306,13 @@ public class PyBlock implements Block { PyBlock insertAfterBlock = (PyBlock)_subBlocks.get(newChildIndex - 1); ASTNode afterNode = insertAfterBlock.getNode(); + System.out.println( // XXX debug + "getChildIndent(" + newChildIndex + "): " + ((PyBlock)insertAfterBlock).getNode().getPsi().toString() + + " " + insertAfterBlock.getTextRange().getStartOffset() + + ":" + insertAfterBlock.getTextRange().getLength() + ); + + // handle pressing Enter after colon and before first statement in // existing statement list if (afterNode.getElementType() == PyElementTypes.STATEMENT_LIST || afterNode.getElementType() == PyTokenTypes.COLON) { @@ -286,8 +326,47 @@ public class PyBlock implements Block { return Indent.getNormalIndent(); } } + else if (lastChild != null && PyElementTypes.LIST_LIKE_EXPRESSIONS.contains(lastChild.getElementType())) { + // handle pressing enter at the end of a list literal when there's no closing paren or bracket + ASTNode lastLastChild = lastChild.getLastChildNode(); + if (lastLastChild != null && lastLastChild.getPsi() instanceof PsiErrorElement) { + // we're at a place like this: [foo, ... bar, + // we'd rather align to foo. this may be not a multiple of tabs. + PsiElement expr = lastChild.getPsi(); + PsiElement exprItem = expr.getFirstChild(); + boolean found = false; + while (exprItem != null) { // find a worthy element to align to + if (exprItem instanceof PyElement) { + found = true; // align to foo in "[foo," + break; + } + if (exprItem instanceof PsiComment) { + found = true; // align to foo in "[ # foo," + break; + } + exprItem = exprItem.getNextSibling(); + } + if (found) { + PsiDocumentManager docMgr = PsiDocumentManager.getInstance(exprItem.getProject()); + Document doc = docMgr.getDocument(exprItem.getContainingFile()); + if (doc != null) { + int line_num = doc.getLineNumber(exprItem.getTextOffset()); + int item_col = exprItem.getTextOffset() - doc.getLineStartOffset(line_num); + PsiElement here_elt = getNode().getPsi(); + line_num = doc.getLineNumber(here_elt.getTextOffset()); + int node_col = here_elt.getTextOffset() - doc.getLineStartOffset(line_num); + int padding = item_col - node_col; + if (padding > 0) { // negative is a syntax error, but possible + return Indent.getSpaceIndent(padding); + } + } + } + return Indent.getContinuationIndent(); // a fallback + } + } - if (_listElementTypes.contains(_node.getElementType())) { + // constructs that imply indent for their children + if (_listElementTypes.contains(_node.getElementType()) || _node.getPsi() instanceof PyStatementPart) { return Indent.getNormalIndent(); } diff --git a/python/src/com/jetbrains/python/parsing/StatementParsing.java b/python/src/com/jetbrains/python/parsing/StatementParsing.java index 55473e16ccfe..54dfb7fb39b7 100644 --- a/python/src/com/jetbrains/python/parsing/StatementParsing.java +++ b/python/src/com/jetbrains/python/parsing/StatementParsing.java @@ -483,62 +483,83 @@ public class StatementParsing private void parseIfStatement() { assertCurrentToken(PyTokenTypes.IF_KEYWORD); final PsiBuilder.Marker ifStatement = myBuilder.mark(); + final PsiBuilder.Marker ifPart = myBuilder.mark(); myBuilder.advanceLexer(); getExpressionParser().parseExpression(); checkMatches(PyTokenTypes.COLON, "colon expected"); parseSuite(); + ifPart.done(PyElementTypes.IF_PART_IF); + PsiBuilder.Marker elifPart = myBuilder.mark(); while (myBuilder.getTokenType() == PyTokenTypes.ELIF_KEYWORD) { myBuilder.advanceLexer(); getExpressionParser().parseExpression(); checkMatches(PyTokenTypes.COLON, "colon expected"); parseSuite(); + elifPart.done(PyElementTypes.IF_PART_ELIF); + elifPart = myBuilder.mark(); } + elifPart.drop(); // we always kept an open extra elif + final PsiBuilder.Marker elsePart = myBuilder.mark(); if (myBuilder.getTokenType() == PyTokenTypes.ELSE_KEYWORD) { myBuilder.advanceLexer(); checkMatches(PyTokenTypes.COLON, "colon expected"); parseSuite(); + elsePart.done(PyElementTypes.ELSE_PART); } + else elsePart.drop(); ifStatement.done(PyElementTypes.IF_STATEMENT); } private void parseForStatement() { assertCurrentToken(PyTokenTypes.FOR_KEYWORD); final PsiBuilder.Marker statement = myBuilder.mark(); + final PsiBuilder.Marker forPart = myBuilder.mark(); myBuilder.advanceLexer(); getExpressionParser().parseExpression(true, true); checkMatches(PyTokenTypes.IN_KEYWORD, "'in' expected"); getExpressionParser().parseExpression(); checkMatches(PyTokenTypes.COLON, "colon expected"); parseSuite(); + forPart.done(PyElementTypes.FOR_PART); + final PsiBuilder.Marker elsePart = myBuilder.mark(); if (myBuilder.getTokenType() == PyTokenTypes.ELSE_KEYWORD) { myBuilder.advanceLexer(); checkMatches(PyTokenTypes.COLON, "colon expected"); parseSuite(); + elsePart.done(PyElementTypes.ELSE_PART); } + else elsePart.drop(); statement.done(PyElementTypes.FOR_STATEMENT); } private void parseWhileStatement() { assertCurrentToken(PyTokenTypes.WHILE_KEYWORD); final PsiBuilder.Marker statement = myBuilder.mark(); + final PsiBuilder.Marker whilePart = myBuilder.mark(); myBuilder.advanceLexer(); getExpressionParser().parseExpression(); checkMatches(PyTokenTypes.COLON, "colon expected"); parseSuite(); + whilePart.done(PyElementTypes.WHILE_PART); + final PsiBuilder.Marker elsePart = myBuilder.mark(); if (myBuilder.getTokenType() == PyTokenTypes.ELSE_KEYWORD) { myBuilder.advanceLexer(); checkMatches(PyTokenTypes.COLON, "colon expected"); parseSuite(); + elsePart.done(PyElementTypes.ELSE_PART); } + else elsePart.drop(); statement.done(PyElementTypes.WHILE_STATEMENT); } private void parseTryStatement() { assertCurrentToken(PyTokenTypes.TRY_KEYWORD); final PsiBuilder.Marker statement = myBuilder.mark(); + final PsiBuilder.Marker tryPart = myBuilder.mark(); myBuilder.advanceLexer(); checkMatches(PyTokenTypes.COLON, "colon expected"); parseSuite(); + tryPart.done(PyElementTypes.TRY_PART); boolean haveExceptClause = false; if (myBuilder.getTokenType() == PyTokenTypes.EXCEPT_KEYWORD) { haveExceptClause = true; @@ -558,23 +579,31 @@ public class StatementParsing } checkMatches(PyTokenTypes.COLON, "colon expected"); parseSuite(); - exceptBlock.done(PyElementTypes.EXCEPT_BLOCK); + exceptBlock.done(PyElementTypes.EXCEPT_PART); } + final PsiBuilder.Marker elsePart = myBuilder.mark(); if (myBuilder.getTokenType() == PyTokenTypes.ELSE_KEYWORD) { myBuilder.advanceLexer(); checkMatches(PyTokenTypes.COLON, "colon expected"); parseSuite(); + elsePart.done(PyElementTypes.ELSE_PART); } + else elsePart.drop(); } + final PsiBuilder.Marker finallyPart = myBuilder.mark(); if (myBuilder.getTokenType() == PyTokenTypes.FINALLY_KEYWORD) { myBuilder.advanceLexer(); checkMatches(PyTokenTypes.COLON, "colon expected"); parseSuite(); + finallyPart.done(PyElementTypes.FINALLY_PART); } - else if (!haveExceptClause) { - myBuilder.error("'except' or 'finally' expected"); - // much better to have a statement of incorrectly determined type - // than "TRY" and "COLON" tokens attached to nothing + else { + finallyPart.drop(); + if (!haveExceptClause) { + myBuilder.error("'except' or 'finally' expected"); + // much better to have a statement of incorrectly determined type + // than "TRY" and "COLON" tokens attached to nothing + } } statement.done(PyElementTypes.TRY_EXCEPT_STATEMENT); } diff --git a/python/src/com/jetbrains/python/psi/PyConditionalStatementPart.java b/python/src/com/jetbrains/python/psi/PyConditionalStatementPart.java new file mode 100644 index 000000000000..586d4b7bce01 --- /dev/null +++ b/python/src/com/jetbrains/python/psi/PyConditionalStatementPart.java @@ -0,0 +1,16 @@ +package com.jetbrains.python.psi; + +import org.jetbrains.annotations.Nullable; + +/** + * A statement part that has a condition before it. + * User: dcheryasov + * Date: Mar 16, 2009 4:44:25 AM + */ +public interface PyConditionalStatementPart extends PyStatementPart { + /** + * @return the condition expression. + */ + @Nullable + PyExpression getCondition(); +} diff --git a/python/src/com/jetbrains/python/psi/PyElementVisitor.java b/python/src/com/jetbrains/python/psi/PyElementVisitor.java index 79f8e7b42b7c..73198050def2 100644 --- a/python/src/com/jetbrains/python/psi/PyElementVisitor.java +++ b/python/src/com/jetbrains/python/psi/PyElementVisitor.java @@ -158,7 +158,7 @@ public class PyElementVisitor extends PsiElementVisitor { visitPyElement(node); } - public void visitPyExceptBlock(final PyExceptBlock node) { + public void visitPyExceptBlock(final PyExceptPart node) { visitPyElement(node); } diff --git a/python/src/com/jetbrains/python/psi/PyElsePart.java b/python/src/com/jetbrains/python/psi/PyElsePart.java new file mode 100644 index 000000000000..6d06066cdda0 --- /dev/null +++ b/python/src/com/jetbrains/python/psi/PyElsePart.java @@ -0,0 +1,16 @@ +package com.jetbrains.python.psi; + +import org.jetbrains.annotations.Nullable; + +/** + * The 'else:' part of various compound statements. + * User: dcheryasov + * Date: Mar 15, 2009 9:34:51 PM + */ +public interface PyElsePart extends PyStatementPart { + /** + * @return the body of the 'else' part. + */ + @Nullable + PyStatementList getStatementList(); +} diff --git a/python/src/com/jetbrains/python/psi/PyExceptBlock.java b/python/src/com/jetbrains/python/psi/PyExceptPart.java similarity index 81% rename from python/src/com/jetbrains/python/psi/PyExceptBlock.java rename to python/src/com/jetbrains/python/psi/PyExceptPart.java index afb3dba950d8..7d117b0189a9 100644 --- a/python/src/com/jetbrains/python/psi/PyExceptBlock.java +++ b/python/src/com/jetbrains/python/psi/PyExceptPart.java @@ -17,7 +17,6 @@ package com.jetbrains.python.psi; import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.NotNull; /** * Created by IntelliJ IDEA. @@ -26,10 +25,9 @@ import org.jetbrains.annotations.NotNull; * Time: 23:37:32 * To change this template use File | Settings | File Templates. */ -public interface PyExceptBlock extends PyElement, NameDefiner { - PyExceptBlock[] EMPTY_ARRAY = new PyExceptBlock[0]; +public interface PyExceptPart extends PyElement, NameDefiner, PyStatementPart { + PyExceptPart[] EMPTY_ARRAY = new PyExceptPart[0]; @Nullable PyExpression getExceptClass(); @Nullable PyExpression getTarget(); - @NotNull PyStatementList getStatementList(); } diff --git a/python/src/com/jetbrains/python/psi/PyFinallyPart.java b/python/src/com/jetbrains/python/psi/PyFinallyPart.java new file mode 100644 index 000000000000..e503b53e6d65 --- /dev/null +++ b/python/src/com/jetbrains/python/psi/PyFinallyPart.java @@ -0,0 +1,10 @@ +package com.jetbrains.python.psi; + +/** + * The 'finally' part. + * @see PyTryPart + * User: dcheryasov + * Date: Mar 16, 2009 6:29:22 AM + */ +public interface PyFinallyPart extends PyStatementPart { +} diff --git a/python/src/com/jetbrains/python/psi/PyForPart.java b/python/src/com/jetbrains/python/psi/PyForPart.java new file mode 100644 index 000000000000..f6730810856e --- /dev/null +++ b/python/src/com/jetbrains/python/psi/PyForPart.java @@ -0,0 +1,23 @@ +package com.jetbrains.python.psi; + +import org.jetbrains.annotations.Nullable; + +/** + * Main part of a 'for' statement + * User: dcheryasov + * Date: Mar 15, 2009 8:55:22 PM + */ +public interface PyForPart extends PyStatementPart { + /** + * @return target: the "x" in "for x in (1, 2, 3)". + */ + @Nullable + PyExpression getTarget(); + + /** + * @return source of iteration: the "(1, 2, 3)" in "for x in (1, 2, 3)". + */ + @Nullable + PyExpression getSource(); + +} diff --git a/python/src/com/jetbrains/python/psi/PyForStatement.java b/python/src/com/jetbrains/python/psi/PyForStatement.java index 3d3922bfd3c0..613884d92c1a 100644 --- a/python/src/com/jetbrains/python/psi/PyForStatement.java +++ b/python/src/com/jetbrains/python/psi/PyForStatement.java @@ -20,15 +20,10 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** - * Created by IntelliJ IDEA. - * User: yole - * Date: 29.05.2005 - * Time: 21:20:52 - * To change this template use File | Settings | File Templates. + * The 'for/else' statement. */ public interface PyForStatement extends PyStatement, NameDefiner { - @NotNull PyStatementList getStatementList(); - @Nullable PyStatementList getElseStatementList(); - @Nullable PyExpression getTargetExpression(); - @Nullable PyExpression getLoopExpression(); + @NotNull PyForPart getForPart(); + @Nullable PyElsePart getElsePart(); + } diff --git a/python/src/com/jetbrains/python/psi/PyIfPart.java b/python/src/com/jetbrains/python/psi/PyIfPart.java new file mode 100644 index 000000000000..2a18f7317983 --- /dev/null +++ b/python/src/com/jetbrains/python/psi/PyIfPart.java @@ -0,0 +1,16 @@ +package com.jetbrains.python.psi; + +/** + * Branches of an 'if' statement. + * @see PyElsePart + * User: dcheryasov + * Date: Mar 12, 2009 2:16:00 AM + */ +public interface PyIfPart extends PyConditionalStatementPart { + PyIfPart[] EMPTY_ARRAY = new PyIfPart[0]; + /** + * @return true for a 'elif' part. + */ + boolean isElif(); + +} diff --git a/python/src/com/jetbrains/python/psi/PyIfStatement.java b/python/src/com/jetbrains/python/psi/PyIfStatement.java index 17cfcfff0505..02e554602d76 100644 --- a/python/src/com/jetbrains/python/psi/PyIfStatement.java +++ b/python/src/com/jetbrains/python/psi/PyIfStatement.java @@ -20,14 +20,10 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** - * Created by IntelliJ IDEA. - * User: yole - * Date: 29.05.2005 - * Time: 21:07:48 - * To change this template use File | Settings | File Templates. + * The 'if/elif/else' statement. */ public interface PyIfStatement extends PyStatement { - @NotNull PyExpression[] getConditions(); - @NotNull PyStatementList[] getStatementLists(); - @Nullable PyStatementList getElseStatementList(); + @NotNull PyIfPart getIfPart(); + @NotNull PyIfPart[] getElifParts(); + @Nullable PyElsePart getElsePart(); } diff --git a/python/src/com/jetbrains/python/psi/PyStatementPart.java b/python/src/com/jetbrains/python/psi/PyStatementPart.java new file mode 100644 index 000000000000..0552b1e1182f --- /dev/null +++ b/python/src/com/jetbrains/python/psi/PyStatementPart.java @@ -0,0 +1,18 @@ +package com.jetbrains.python.psi; + +import org.jetbrains.annotations.Nullable; + +/** + * Abstract part of a multipart statement. + * User: dcheryasov + * Date: Mar 16, 2009 4:34:59 AM + */ +public interface PyStatementPart extends PyElement { + PyStatementPart[] EMPTY_ARRAY = new PyStatementPart[0]; + + /** + * @return the body of the part. + */ + @Nullable + PyStatementList getStatementList(); +} diff --git a/python/src/com/jetbrains/python/psi/PyTryExceptStatement.java b/python/src/com/jetbrains/python/psi/PyTryExceptStatement.java index 9e7bfe394ae4..a54aeb2cc2a6 100644 --- a/python/src/com/jetbrains/python/psi/PyTryExceptStatement.java +++ b/python/src/com/jetbrains/python/psi/PyTryExceptStatement.java @@ -20,22 +20,20 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** - * Created by IntelliJ IDEA. - * User: yole + * The 'try/except/else/finally' statement. * Date: 02.06.2005 - * Time: 23:14:36 - * To change this template use File | Settings | File Templates. */ public interface PyTryExceptStatement extends PyStatement { @NotNull - PyStatementList getTryStatementList(); + PyTryPart getTryPart(); @NotNull - PyExceptBlock[] getExceptBlocks(); + PyExceptPart[] getExceptParts(); @Nullable - PyStatementList getElseStatementList(); + PyElsePart getElsePart(); @Nullable - PyStatementList getFinallyStatementList(); + PyFinallyPart getFinallyPart(); + } diff --git a/python/src/com/jetbrains/python/psi/PyTryPart.java b/python/src/com/jetbrains/python/psi/PyTryPart.java new file mode 100644 index 000000000000..331641bcf8df --- /dev/null +++ b/python/src/com/jetbrains/python/psi/PyTryPart.java @@ -0,0 +1,12 @@ +package com.jetbrains.python.psi; + +/** + * The 'try' part. + * @see PyExceptPart + * @see PyFinallyPart + * @see PyElsePart + * User: dcheryasov + * Date: Mar 16, 2009 6:26:40 AM + */ +public interface PyTryPart extends PyStatementPart { +} diff --git a/python/src/com/jetbrains/python/psi/PyWhilePart.java b/python/src/com/jetbrains/python/psi/PyWhilePart.java new file mode 100644 index 000000000000..a5cad325d4bb --- /dev/null +++ b/python/src/com/jetbrains/python/psi/PyWhilePart.java @@ -0,0 +1,10 @@ +package com.jetbrains.python.psi; + +/** + * The 'while' part of a cycle. + * @see PyElsePart + * User: dcheryasov + * Date: Mar 16, 2009 4:25:59 AM + */ +public interface PyWhilePart extends PyConditionalStatementPart { +} diff --git a/python/src/com/jetbrains/python/psi/PyWhileStatement.java b/python/src/com/jetbrains/python/psi/PyWhileStatement.java index a04aeb425bd8..57bf6fc56833 100644 --- a/python/src/com/jetbrains/python/psi/PyWhileStatement.java +++ b/python/src/com/jetbrains/python/psi/PyWhileStatement.java @@ -20,13 +20,9 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** - * Created by IntelliJ IDEA. - * User: yole - * Date: 29.05.2005 - * Time: 15:47:19 - * To change this template use File | Settings | File Templates. + * The 'while' statement. */ public interface PyWhileStatement extends PyLoopStatement { - @NotNull PyStatementList getStatementList(); - @Nullable PyStatementList getElseStatementList(); + @NotNull PyWhilePart getWhilePart(); + @Nullable PyElsePart getElsePart(); } diff --git a/python/src/com/jetbrains/python/psi/impl/PyConditionalStatementPartImpl.java b/python/src/com/jetbrains/python/psi/impl/PyConditionalStatementPartImpl.java new file mode 100644 index 000000000000..258d2cd7307f --- /dev/null +++ b/python/src/com/jetbrains/python/psi/impl/PyConditionalStatementPartImpl.java @@ -0,0 +1,24 @@ +package com.jetbrains.python.psi.impl; + +import com.jetbrains.python.psi.PyConditionalStatementPart; +import com.jetbrains.python.psi.PyExpression; +import com.jetbrains.python.PyElementTypes; +import com.intellij.lang.ASTNode; + +/** + * User: dcheryasov + * Date: Mar 16, 2009 4:46:26 AM + */ +public abstract class PyConditionalStatementPartImpl extends PyStatementPartImpl implements PyConditionalStatementPart { + public PyConditionalStatementPartImpl(ASTNode astNode) { + super(astNode); + } + + public PyExpression getCondition() { + ASTNode n = getNode().findChildByType(PyElementTypes.EXPRESSIONS); + if (n != null) { + return (PyExpression)n.getPsi(); + } + return null; + } +} diff --git a/python/src/com/jetbrains/python/psi/impl/PyElsePartImpl.java b/python/src/com/jetbrains/python/psi/impl/PyElsePartImpl.java new file mode 100644 index 000000000000..c3f2de59613f --- /dev/null +++ b/python/src/com/jetbrains/python/psi/impl/PyElsePartImpl.java @@ -0,0 +1,25 @@ +package com.jetbrains.python.psi.impl; + +import com.jetbrains.python.psi.PyElsePart; +import com.jetbrains.python.psi.PyStatementList; +import com.jetbrains.python.PyElementTypes; +import com.intellij.lang.ASTNode; + +/** + * User: dcheryasov + * Date: Mar 15, 2009 9:40:35 PM + */ +public class PyElsePartImpl extends PyElementImpl implements PyElsePart { + + public PyElsePartImpl(ASTNode astNode) { + super(astNode); + } + + public PyStatementList getStatementList() { + ASTNode n = getNode().findChildByType(PyElementTypes.STATEMENT_LISTS); + if (n != null) { + return (PyStatementList)n.getPsi(); + } + return null; + } +} diff --git a/python/src/com/jetbrains/python/psi/impl/PyExceptBlockImpl.java b/python/src/com/jetbrains/python/psi/impl/PyExceptPartImpl.java similarity index 93% rename from python/src/com/jetbrains/python/psi/impl/PyExceptBlockImpl.java rename to python/src/com/jetbrains/python/psi/impl/PyExceptPartImpl.java index c0c0bd9d9619..b6cf3e6e1c04 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyExceptBlockImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyExceptPartImpl.java @@ -29,8 +29,8 @@ import com.jetbrains.python.psi.*; * Time: 23:37:49 * To change this template use File | Settings | File Templates. */ -public class PyExceptBlockImpl extends PyElementImpl implements PyExceptBlock { - public PyExceptBlockImpl(ASTNode astNode) { +public class PyExceptPartImpl extends PyElementImpl implements PyExceptPart { + public PyExceptPartImpl(ASTNode astNode) { super(astNode); } diff --git a/python/src/com/jetbrains/python/psi/impl/PyFinallyPartImpl.java b/python/src/com/jetbrains/python/psi/impl/PyFinallyPartImpl.java new file mode 100644 index 000000000000..db8ad398feda --- /dev/null +++ b/python/src/com/jetbrains/python/psi/impl/PyFinallyPartImpl.java @@ -0,0 +1,14 @@ +package com.jetbrains.python.psi.impl; + +import com.intellij.lang.ASTNode; +import com.jetbrains.python.psi.PyFinallyPart; + +/** + * User: dcheryasov + * Date: Mar 16, 2009 6:58:03 AM + */ +public class PyFinallyPartImpl extends PyStatementPartImpl implements PyFinallyPart{ + public PyFinallyPartImpl(ASTNode astNode) { + super(astNode); + } +} diff --git a/python/src/com/jetbrains/python/psi/impl/PyForPartImpl.java b/python/src/com/jetbrains/python/psi/impl/PyForPartImpl.java new file mode 100644 index 000000000000..ae8da477c5bf --- /dev/null +++ b/python/src/com/jetbrains/python/psi/impl/PyForPartImpl.java @@ -0,0 +1,67 @@ +package com.jetbrains.python.psi.impl; + +import com.intellij.lang.ASTNode; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiWhiteSpace; +import com.jetbrains.python.PyElementTypes; +import com.jetbrains.python.PyTokenTypes; +import com.jetbrains.python.psi.PyElementType; +import com.jetbrains.python.psi.PyExpression; +import com.jetbrains.python.psi.PyForPart; + +import java.util.List; + +/** + * User: dcheryasov + * Date: Mar 15, 2009 9:57:48 PM + */ +public class PyForPartImpl extends PyStatementPartImpl implements PyForPart { + public PyForPartImpl(ASTNode astNode) { + super(astNode); + } + + /** + * Checks that given node actually follows a node of given type, skipping whitespace. + * @param node node to check. + * @param eltType type of a node that must precede the node we're checking. + * @return true if node is really a next sibling to a node of eltType type. + */ + protected static boolean followsNodeOfType(ASTNode node, PyElementType eltType) { + if (node != null) { + PsiElement checker = node.getPsi(); + checker = checker.getPrevSibling(); // step from the source node + while (checker != null) { + ASTNode ch_node = checker.getNode(); + if (ch_node == null) return false; + else { + if (ch_node.getElementType() == eltType) { + return true; + } + else if (!(checker instanceof PsiWhiteSpace)) { + return false; + } + } + checker = checker.getPrevSibling(); + } + } + return false; + } + + public PyExpression getTarget() { + ASTNode n = getNode().findChildByType(PyElementTypes.EXPRESSIONS); + if (followsNodeOfType(n, PyTokenTypes.FOR_KEYWORD)) { + return (PyExpression)n.getPsi(); // can't be null, 'if' would fail + } + else return null; + } + + public PyExpression getSource() { + List exprs = findChildrenByType(PyElementTypes.EXPRESSIONS); + // normally there are 2 exprs, the second is the source. + if (exprs.size() != 2) return null; // could be a parsing error + PyExpression ret = (PyExpression)exprs.get(1); + if (followsNodeOfType(ret.getNode(), PyTokenTypes.IN_KEYWORD)) return ret; + else return null; + } + +} diff --git a/python/src/com/jetbrains/python/psi/impl/PyForStatementImpl.java b/python/src/com/jetbrains/python/psi/impl/PyForStatementImpl.java index e9903e053644..989a535589e8 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyForStatementImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyForStatementImpl.java @@ -21,20 +21,15 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.ResolveState; import com.intellij.psi.scope.PsiScopeProcessor; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import com.jetbrains.python.PyElementTypes; import com.jetbrains.python.psi.PyElementVisitor; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.PyStatementList; /** - * Created by IntelliJ IDEA. - * User: yole - * Date: 29.05.2005 - * Time: 21:21:12 - * To change this template use File | Settings | File Templates. + * Date: 29.05.2005 (initial) */ -public class PyForStatementImpl extends PyElementImpl implements PyForStatement { +public class PyForStatementImpl extends PyPartitionedElementImpl implements PyForStatement { public PyForStatementImpl(ASTNode astNode) { super(astNode); } @@ -44,24 +39,13 @@ public class PyForStatementImpl extends PyElementImpl implements PyForStatement pyVisitor.visitPyForStatement(this); } + public PyElsePart getElsePart() { + return (PyElsePart)getPart(PyElementTypes.ELSE_PART); + } + @NotNull - public PyStatementList getStatementList() { - return childToPsiNotNull(PyElementTypes.STATEMENT_LISTS, 0); - } - - @Nullable - public PyStatementList getElseStatementList() { - return childToPsi(PyElementTypes.STATEMENT_LISTS, 1); - } - - @Nullable - public PyExpression getTargetExpression() { - return childToPsi(PyElementTypes.EXPRESSIONS, 0); - } - - @Nullable - public PyExpression getLoopExpression() { - return childToPsi(PyElementTypes.EXPRESSIONS, 1); + public PyForPart getForPart() { + return (PyForPart)getPartNotNull(PyElementTypes.FOR_PART); } @Override @@ -69,25 +53,29 @@ public class PyForStatementImpl extends PyElementImpl implements PyForStatement @NotNull ResolveState substitutor, PsiElement lastParent, @NotNull PsiElement place) { - final PyExpression target = getTargetExpression(); + PyForPart forPart = getForPart(); + final PyExpression target = forPart.getTarget(); if (target != null && target != lastParent && !target.processDeclarations(processor, substitutor, null, place)) { return false; } - final PyStatementList statementList = getStatementList(); - if (statementList != lastParent && !statementList.processDeclarations(processor, substitutor, null, place)) { + final PyStatementList statementList = forPart.getStatementList(); + if (statementList != null && statementList != lastParent && !statementList.processDeclarations(processor, substitutor, null, place)) { return false; } - PyStatementList elseList = getElseStatementList(); - if (elseList != null && elseList != lastParent) { - return elseList.processDeclarations(processor, substitutor, null, place); + PyElsePart elsePart = getElsePart(); + if (elsePart != null) { + PyStatementList elseList = elsePart.getStatementList(); + if (elseList != null && elseList != lastParent) { + return elseList.processDeclarations(processor, substitutor, null, place); + } } return true; } @NotNull public Iterable iterateNames() { - PyExpression tgt = getTargetExpression(); + PyExpression tgt = getForPart().getTarget(); if (tgt instanceof PyReferenceExpression) return new SingleIterable(tgt); else { return PyUtil.flattenedParens(new PyElement[]{tgt}); diff --git a/python/src/com/jetbrains/python/psi/impl/PyIfPartElifImpl.java b/python/src/com/jetbrains/python/psi/impl/PyIfPartElifImpl.java new file mode 100644 index 000000000000..952eb4d5eaea --- /dev/null +++ b/python/src/com/jetbrains/python/psi/impl/PyIfPartElifImpl.java @@ -0,0 +1,19 @@ +package com.jetbrains.python.psi.impl; + +import com.intellij.lang.ASTNode; +import com.jetbrains.python.psi.PyIfPart; + +/** + * PyIfPart that represents an 'elif' part. + * User: dcheryasov + * Date: Mar 12, 2009 5:21:11 PM + */ +public class PyIfPartElifImpl extends PyConditionalStatementPartImpl implements PyIfPart { + public PyIfPartElifImpl(ASTNode astNode) { + super(astNode); + } + + public boolean isElif() { + return true; + } +} diff --git a/python/src/com/jetbrains/python/psi/impl/PyIfPartIfImpl.java b/python/src/com/jetbrains/python/psi/impl/PyIfPartIfImpl.java new file mode 100644 index 000000000000..afd0ea296be2 --- /dev/null +++ b/python/src/com/jetbrains/python/psi/impl/PyIfPartIfImpl.java @@ -0,0 +1,21 @@ +package com.jetbrains.python.psi.impl; + +import com.intellij.lang.ASTNode; +import com.jetbrains.python.psi.PyIfPart; + +/** + * PyIfPart that represents an 'if' part. + * User: dcheryasov + * Date: Mar 12, 2009 2:33:39 AM + */ +public class PyIfPartIfImpl extends PyConditionalStatementPartImpl implements PyIfPart { + + public PyIfPartIfImpl(ASTNode astNode) { + super(astNode); + } + + public boolean isElif() { + return false; + } + +} diff --git a/python/src/com/jetbrains/python/psi/impl/PyIfStatementImpl.java b/python/src/com/jetbrains/python/psi/impl/PyIfStatementImpl.java index 9f80de37b909..5ff1b906c096 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyIfStatementImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyIfStatementImpl.java @@ -22,12 +22,8 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.ResolveState; import com.intellij.psi.scope.PsiScopeProcessor; import com.jetbrains.python.PyElementTypes; -import com.jetbrains.python.psi.PyElementVisitor; -import com.jetbrains.python.psi.PyExpression; -import com.jetbrains.python.psi.PyIfStatement; -import com.jetbrains.python.psi.PyStatementList; +import com.jetbrains.python.psi.*; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; /** * Created by IntelliJ IDEA. @@ -36,65 +32,61 @@ import org.jetbrains.annotations.Nullable; * Time: 21:08:06 * To change this template use File | Settings | File Templates. */ -public class PyIfStatementImpl extends PyElementImpl implements PyIfStatement { - private static final Logger LOG = Logger.getInstance("#com.jetbrains.python.psi.impl.PyIfStatementImpl"); +public class PyIfStatementImpl extends PyPartitionedElementImpl implements PyIfStatement { + private static final Logger LOG = Logger.getInstance("#com.jetbrains.python.psi.impl.PyIfStatementImpl"); - public PyIfStatementImpl(ASTNode astNode) { - super(astNode); - } + public PyIfStatementImpl(ASTNode astNode) { + super(astNode); + } - @Override protected void acceptPyVisitor(PyElementVisitor pyVisitor) { - pyVisitor.visitPyIfStatement(this); - } + @Override protected void acceptPyVisitor(PyElementVisitor pyVisitor) { + pyVisitor.visitPyIfStatement(this); + } + @NotNull + public PyIfPart getIfPart() { + return (PyIfPart)getPartNotNull(PyElementTypes.IF_PART_IF); + } - @NotNull public PyExpression[] getConditions() { - return childrenToPsi(PyElementTypes.EXPRESSIONS, PyExpression.EMPTY_ARRAY); - } + @NotNull + public PyIfPart[] getElifParts() { + return childrenToPsi(PyElementTypes.ELIFS, PyIfPart.EMPTY_ARRAY); + } - @NotNull public PyStatementList[] getStatementLists() { - final ASTNode[] conditions = getNode().getChildren(PyElementTypes.EXPRESSIONS); - final PyStatementList[] statementLists = childrenToPsi(PyElementTypes.STATEMENT_LISTS, PyStatementList.EMPTY_ARRAY); - LOG.assertTrue(statementLists.length == conditions.length || statementLists.length == conditions.length+1); - if (statementLists.length > conditions.length) { - final PyStatementList[] result = new PyStatementList[conditions.length]; - System.arraycopy(statementLists, 0, result, 0, conditions.length); - return result; - } - return statementLists; - } + public PyElsePart getElsePart() { + return (PyElsePart)getPart(PyElementTypes.ELSE_PART); + } - public @Nullable PyStatementList getElseStatementList() { - final ASTNode[] conditions = getNode().getChildren(PyElementTypes.EXPRESSIONS); - final PyStatementList[] statementLists = childrenToPsi(PyElementTypes.STATEMENT_LISTS, PyStatementList.EMPTY_ARRAY); - LOG.assertTrue(statementLists.length == conditions.length || statementLists.length == conditions.length+1); - if (statementLists.length > conditions.length) { - return statementLists [statementLists.length-1]; - } - return null; - } - - @Override public boolean processDeclarations(@NotNull PsiScopeProcessor processor, + @Override public boolean processDeclarations(@NotNull PsiScopeProcessor processor, @NotNull ResolveState substitutor, PsiElement lastParent, @NotNull PsiElement place) { - if (lastParent != null) { - return true; - } - - PyStatementList[] statementLists = getStatementLists(); - for (PyStatementList statementList: statementLists) { - if (!statementList.processDeclarations(processor, substitutor, lastParent, place)) { - return false; - } - } - PyStatementList elseList = getElseStatementList(); - //noinspection RedundantIfStatement - if (elseList != null && !elseList.processDeclarations(processor, substitutor, lastParent, place)) { + if (lastParent != null) { + return true; + } + for (PyStatementPart part : getParts()) { + PyStatementList stmtList = part.getStatementList(); + if (stmtList != null && !stmtList.processDeclarations(processor, substitutor, lastParent, place)) { return false; } - return true; + } + + /* + + PyStatementList[] statementLists = getStatementLists(); + for (PyStatementList statementList: statementLists) { + if (!statementList.processDeclarations(processor, substitutor, lastParent, place)) { + return false; + } + } + PyStatementList elseList = getElseStatementList(); + //noinspection RedundantIfStatement + if (elseList != null && !elseList.processDeclarations(processor, substitutor, lastParent, place)) { + return false; + } + */ + return true; } } diff --git a/python/src/com/jetbrains/python/psi/impl/PyPartitionedElementImpl.java b/python/src/com/jetbrains/python/psi/impl/PyPartitionedElementImpl.java new file mode 100644 index 000000000000..67416055ed4d --- /dev/null +++ b/python/src/com/jetbrains/python/psi/impl/PyPartitionedElementImpl.java @@ -0,0 +1,39 @@ +package com.jetbrains.python.psi.impl; + +import com.intellij.lang.ASTNode; +import com.jetbrains.python.PyElementTypes; +import com.jetbrains.python.psi.PyElementType; +import com.jetbrains.python.psi.PyStatementPart; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Common parts functionality. + * User: dcheryasov + * Date: Mar 19, 2009 2:51:15 AM + */ +public class PyPartitionedElementImpl extends PyElementImpl { + public PyPartitionedElementImpl(ASTNode astNode) { + super(astNode); + } + + @NotNull + PyStatementPart[] getParts() { + return childrenToPsi(PyElementTypes.PARTS, PyStatementPart.EMPTY_ARRAY); + } + + @Nullable + protected PyStatementPart getPart(PyElementType which) { + ASTNode n = getNode().findChildByType(which); + if (n == null) return null; + return (PyStatementPart)n.getPsi(); + } + + @NotNull + protected PyStatementPart getPartNotNull(PyElementType which) { + ASTNode n = getNode().findChildByType(which); + assert n != null; + return (PyStatementPart)n.getPsi(); + } + +} diff --git a/python/src/com/jetbrains/python/psi/impl/PyStatementPartImpl.java b/python/src/com/jetbrains/python/psi/impl/PyStatementPartImpl.java new file mode 100644 index 000000000000..9cc4630ec41e --- /dev/null +++ b/python/src/com/jetbrains/python/psi/impl/PyStatementPartImpl.java @@ -0,0 +1,25 @@ +package com.jetbrains.python.psi.impl; + +import com.jetbrains.python.psi.PyStatementPart; +import com.jetbrains.python.psi.PyStatementList; +import com.jetbrains.python.PyElementTypes; +import com.intellij.lang.ASTNode; + +/** + * Abstract statement part implementation; extracts the statements list. + * User: dcheryasov + * Date: Mar 16, 2009 4:36:50 AM + */ +public abstract class PyStatementPartImpl extends PyElementImpl implements PyStatementPart { + protected PyStatementPartImpl(ASTNode astNode) { + super(astNode); + } + + public PyStatementList getStatementList() { + ASTNode n = getNode().findChildByType(PyElementTypes.STATEMENT_LISTS); + if (n != null) { + return (PyStatementList)n.getPsi(); + } + return null; + } +} diff --git a/python/src/com/jetbrains/python/psi/impl/PyTryExceptStatementImpl.java b/python/src/com/jetbrains/python/psi/impl/PyTryExceptStatementImpl.java index d5907c8eba6a..6f87156b764d 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyTryExceptStatementImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyTryExceptStatementImpl.java @@ -22,13 +22,8 @@ import com.intellij.psi.ResolveState; import com.intellij.psi.scope.PsiScopeProcessor; import com.intellij.psi.tree.TokenSet; import com.jetbrains.python.PyElementTypes; -import com.jetbrains.python.PyTokenTypes; -import com.jetbrains.python.psi.PyElementVisitor; -import com.jetbrains.python.psi.PyExceptBlock; -import com.jetbrains.python.psi.PyStatementList; -import com.jetbrains.python.psi.PyTryExceptStatement; +import com.jetbrains.python.psi.*; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; /** * Created by IntelliJ IDEA. @@ -37,8 +32,8 @@ import org.jetbrains.annotations.Nullable; * Time: 23:14:57 * To change this template use File | Settings | File Templates. */ -public class PyTryExceptStatementImpl extends PyElementImpl implements PyTryExceptStatement { - private static final TokenSet EXCEPT_BLOCKS = TokenSet.create(PyElementTypes.EXCEPT_BLOCK); +public class PyTryExceptStatementImpl extends PyPartitionedElementImpl implements PyTryExceptStatement { + private static final TokenSet EXCEPT_BLOCKS = TokenSet.create(PyElementTypes.EXCEPT_PART); public PyTryExceptStatementImpl(ASTNode astNode) { super(astNode); @@ -50,45 +45,22 @@ public class PyTryExceptStatementImpl extends PyElementImpl implements PyTryExce } @NotNull - public PyStatementList getTryStatementList() { - return childToPsiNotNull(PyElementTypes.STATEMENT_LISTS, 0); + public PyExceptPart[] getExceptParts() { + return childrenToPsi(EXCEPT_BLOCKS, PyExceptPart.EMPTY_ARRAY); + } + + public PyElsePart getElsePart() { + return (PyElsePart)getPart(PyElementTypes.ELSE_PART); } @NotNull - public PyExceptBlock[] getExceptBlocks() { - return childrenToPsi(EXCEPT_BLOCKS, PyExceptBlock.EMPTY_ARRAY); + public PyTryPart getTryPart() { + return (PyTryPart)getPartNotNull(PyElementTypes.TRY_PART); } - @Nullable - public PyStatementList getElseStatementList() { - final ASTNode node = getNode().findChildByType(PyTokenTypes.ELSE_KEYWORD); - if (node != null) { - return (PyStatementList)findNextChildOfType(node, PyElementTypes.STATEMENT_LISTS); - } - return null; - } - - @Nullable - public PyStatementList getFinallyStatementList() { - final ASTNode node = getNode().findChildByType(PyTokenTypes.FINALLY_KEYWORD); - if (node != null) { - return (PyStatementList)findNextChildOfType(node, PyElementTypes.STATEMENT_LISTS); - } - - return null; - } - - @Nullable - private static PsiElement findNextChildOfType(ASTNode node, final TokenSet matchTokens) { - ASTNode sibling = node.getTreeNext(); - while (sibling != null) { - if (matchTokens.contains(sibling.getElementType())) { - return sibling.getPsi(); - } - sibling = sibling.getTreeNext(); - } - return null; + public PyFinallyPart getFinallyPart() { + return (PyFinallyPart)getPart(PyElementTypes.FINALLY_PART); } @Override @@ -96,21 +68,28 @@ public class PyTryExceptStatementImpl extends PyElementImpl implements PyTryExce @NotNull ResolveState substitutor, PsiElement lastParent, @NotNull PsiElement place) { - final PyStatementList tryStatementList = getTryStatementList(); - if (tryStatementList != lastParent && !tryStatementList.processDeclarations(processor, substitutor, null, place)) { + /* + final PyStatementList tryStatementList = getTryPart().getStatementList(); + if (tryStatementList != null && tryStatementList != lastParent && !tryStatementList.processDeclarations(processor, substitutor, null, place)) { return false; } + */ - for (PyExceptBlock block : getExceptBlocks()) { - if (block != lastParent && !block.processDeclarations(processor, substitutor, null, place)) { + for (PyStatementPart part : /*getExceptParts()*/ getParts()) { + if (part != lastParent && !part.processDeclarations(processor, substitutor, null, place)) { return false; } } - PyStatementList elseStatementList = getElseStatementList(); - if (elseStatementList != null && elseStatementList != lastParent) { - return elseStatementList.processDeclarations(processor, substitutor, null, place); + /* + final PyElsePart elsePart = getElsePart(); + if (elsePart != null) { + PyStatementList elseStatementList = elsePart.getStatementList(); + if (elseStatementList != null && elseStatementList != lastParent) { + return elseStatementList.processDeclarations(processor, substitutor, null, place); + } } + */ return true; } } diff --git a/python/src/com/jetbrains/python/psi/impl/PyTryPartImpl.java b/python/src/com/jetbrains/python/psi/impl/PyTryPartImpl.java new file mode 100644 index 000000000000..e9179f5a09fb --- /dev/null +++ b/python/src/com/jetbrains/python/psi/impl/PyTryPartImpl.java @@ -0,0 +1,14 @@ +package com.jetbrains.python.psi.impl; + +import com.intellij.lang.ASTNode; +import com.jetbrains.python.psi.PyTryPart; + +/** + * User: dcheryasov + * Date: Mar 16, 2009 6:56:55 AM + */ +public class PyTryPartImpl extends PyStatementPartImpl implements PyTryPart { + public PyTryPartImpl(ASTNode astNode) { + super(astNode); + } +} diff --git a/python/src/com/jetbrains/python/psi/impl/PyWhilePartImpl.java b/python/src/com/jetbrains/python/psi/impl/PyWhilePartImpl.java new file mode 100644 index 000000000000..30db44890107 --- /dev/null +++ b/python/src/com/jetbrains/python/psi/impl/PyWhilePartImpl.java @@ -0,0 +1,14 @@ +package com.jetbrains.python.psi.impl; + +import com.intellij.lang.ASTNode; +import com.jetbrains.python.psi.PyWhilePart; + +/** + * User: dcheryasov + * Date: Mar 16, 2009 4:33:32 AM + */ +public class PyWhilePartImpl extends PyConditionalStatementPartImpl implements PyWhilePart { + public PyWhilePartImpl(ASTNode astNode) { + super(astNode); + } +} diff --git a/python/src/com/jetbrains/python/psi/impl/PyWhileStatementImpl.java b/python/src/com/jetbrains/python/psi/impl/PyWhileStatementImpl.java index 6fb660fbd513..95b537ee95cd 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyWhileStatementImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyWhileStatementImpl.java @@ -20,12 +20,9 @@ import com.intellij.lang.ASTNode; import com.intellij.psi.PsiElement; import com.intellij.psi.ResolveState; import com.intellij.psi.scope.PsiScopeProcessor; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import com.jetbrains.python.PyElementTypes; -import com.jetbrains.python.psi.PyElementVisitor; -import com.jetbrains.python.psi.PyStatementList; -import com.jetbrains.python.psi.PyWhileStatement; +import com.jetbrains.python.psi.*; +import org.jetbrains.annotations.NotNull; /** * Created by IntelliJ IDEA. @@ -34,39 +31,51 @@ import com.jetbrains.python.psi.PyWhileStatement; * Time: 15:47:44 * To change this template use File | Settings | File Templates. */ -public class PyWhileStatementImpl extends PyElementImpl implements PyWhileStatement { - public PyWhileStatementImpl(ASTNode astNode) { - super(astNode); - } +public class PyWhileStatementImpl extends PyPartitionedElementImpl implements PyWhileStatement { + public PyWhileStatementImpl(ASTNode astNode) { + super(astNode); + } - @Override protected void acceptPyVisitor(PyElementVisitor pyVisitor) { - pyVisitor.visitPyWhileStatement(this); - } + @Override protected void acceptPyVisitor(PyElementVisitor pyVisitor) { + pyVisitor.visitPyWhileStatement(this); + } - public @NotNull PyStatementList getStatementList() { - return childToPsiNotNull(PyElementTypes.STATEMENT_LISTS, 0); - } + @NotNull + public PyWhilePart getWhilePart() { + return (PyWhilePart)getPartNotNull(PyElementTypes.WHILE_PART); + } - public @Nullable PyStatementList getElseStatementList() { - return childToPsi(PyElementTypes.STATEMENT_LISTS, 1); - } + public PyElsePart getElsePart() { + return (PyElsePart)getPart(PyElementTypes.ELSE_PART); + } - @Override public boolean processDeclarations(@NotNull PsiScopeProcessor processor, + @Override public boolean processDeclarations(@NotNull PsiScopeProcessor processor, @NotNull ResolveState substitutor, PsiElement lastParent, @NotNull PsiElement place) { - if (lastParent != null) { - return true; - } + if (lastParent != null) return true; - if (!getStatementList().processDeclarations(processor, substitutor, null, place)) { - return false; + for (PyStatementPart part : getParts()) { + PyStatementList stmtList = part.getStatementList(); + if (stmtList != null) { + return stmtList.processDeclarations(processor, substitutor, null, place); } - PyStatementList elseList = getElseStatementList(); + } + + /* + final PyStatementList whileStmts = getWhilePart().getStatementList(); + if (whileStmts != null && !whileStmts.processDeclarations(processor, substitutor, null, place)) { + return false; + } + final PyElsePart elsePart = getElsePart(); + if (elsePart != null) { + PyStatementList elseList = elsePart.getStatementList(); if (elseList != null) { return elseList.processDeclarations(processor, substitutor, null, place); } - return true; + } + */ + return true; } } diff --git a/python/src/com/jetbrains/python/validation/AssignTargetAnnotator.java b/python/src/com/jetbrains/python/validation/AssignTargetAnnotator.java index d5d9a28ab453..d3b5381f9079 100644 --- a/python/src/com/jetbrains/python/validation/AssignTargetAnnotator.java +++ b/python/src/com/jetbrains/python/validation/AssignTargetAnnotator.java @@ -48,7 +48,7 @@ public class AssignTargetAnnotator extends PyAnnotator { } } - @Override public void visitPyExceptBlock(final PyExceptBlock node) { + @Override public void visitPyExceptBlock(final PyExceptPart node) { PyExpression target = node.getTarget(); if (target != null) { target.accept(new ExprVisitor(Operation.Except)); @@ -56,7 +56,7 @@ public class AssignTargetAnnotator extends PyAnnotator { } @Override public void visitPyForStatement(final PyForStatement node) { - PyExpression target = node.getTargetExpression(); + PyExpression target = node.getForPart().getTarget(); if (target != null) { target.accept(new ExprVisitor(Operation.For)); } diff --git a/python/src/com/jetbrains/python/validation/BreakContinueAnnotator.java b/python/src/com/jetbrains/python/validation/BreakContinueAnnotator.java index 222188ce7da7..09cf23690cd6 100644 --- a/python/src/com/jetbrains/python/validation/BreakContinueAnnotator.java +++ b/python/src/com/jetbrains/python/validation/BreakContinueAnnotator.java @@ -16,42 +16,36 @@ package com.jetbrains.python.validation; -import com.intellij.psi.PsiElement; +import com.intellij.psi.util.PsiTreeUtil; import static com.jetbrains.python.PyBundle.message; import com.jetbrains.python.PyElementTypes; -import com.jetbrains.python.psi.PyBreakStatement; -import com.jetbrains.python.psi.PyContinueStatement; -import com.jetbrains.python.psi.PyTryExceptStatement; +import com.jetbrains.python.psi.*; /** * Created by IntelliJ IDEA. * User: yole * Date: 13.06.2005 * Time: 15:01:05 - * To change this template use File | Settings | File Templates. */ public class BreakContinueAnnotator extends PyAnnotator { - @Override public void visitPyBreakStatement(final PyBreakStatement node) { - if (node.getContainingElement(PyElementTypes.LOOPS) == null) { - getHolder().createErrorAnnotation(node, message("ANN.break.outside.loop")); - } + @Override public void visitPyBreakStatement(final PyBreakStatement node) { + if (node.getContainingElement(PyElementTypes.LOOPS) == null) { + getHolder().createErrorAnnotation(node, message("ANN.break.outside.loop")); } + } - @Override public void visitPyContinueStatement(final PyContinueStatement node) { - if (node.getContainingElement(PyElementTypes.LOOPS) == null) { - getHolder().createErrorAnnotation(node, message("ANN.continue.outside.loop")); - return; - } - PyTryExceptStatement tryStatement = node.getContainingElement(PyTryExceptStatement.class); - if (tryStatement != null) { - PsiElement parent = node.getParent(); - while (parent != null) { - if (parent == tryStatement.getFinallyStatementList()) { - getHolder().createErrorAnnotation(node, message("ANN.cant.continue.in.finally")); - break; - } - parent = parent.getParent(); - } - } + @Override public void visitPyContinueStatement(final PyContinueStatement node) { + final PyElement loopStmt = node.getContainingElement(PyElementTypes.LOOPS); // closest loop to contain the 'continue' + if (loopStmt == null) { + getHolder().createErrorAnnotation(node, message("ANN.continue.outside.loop")); + return; } + PyTryExceptStatement tryStmt = node.getContainingElement(PyTryExceptStatement.class); + if (tryStmt != null) { + final PyFinallyPart finallyPart = tryStmt.getFinallyPart(); + if (finallyPart != null && PsiTreeUtil.isAncestor(loopStmt, finallyPart, true)) { + getHolder().createErrorAnnotation(node, message("ANN.cant.continue.in.finally")); + } + } + } } \ No newline at end of file diff --git a/python/src/com/jetbrains/python/validation/TryExceptAnnotator.java b/python/src/com/jetbrains/python/validation/TryExceptAnnotator.java index 271b4f67ddc1..ccb510197742 100644 --- a/python/src/com/jetbrains/python/validation/TryExceptAnnotator.java +++ b/python/src/com/jetbrains/python/validation/TryExceptAnnotator.java @@ -17,7 +17,7 @@ package com.jetbrains.python.validation; import com.jetbrains.python.PyBundle; -import com.jetbrains.python.psi.PyExceptBlock; +import com.jetbrains.python.psi.PyExceptPart; import com.jetbrains.python.psi.PyTryExceptStatement; /** @@ -27,13 +27,13 @@ import com.jetbrains.python.psi.PyTryExceptStatement; */ public class TryExceptAnnotator extends PyAnnotator { @Override public void visitPyTryExceptStatement(final PyTryExceptStatement node) { - PyExceptBlock[] exceptBlocks = node.getExceptBlocks(); + PyExceptPart[] exceptParts = node.getExceptParts(); boolean haveDefaultExcept = false; - for(PyExceptBlock block: exceptBlocks) { + for(PyExceptPart part : exceptParts) { if (haveDefaultExcept) { - getHolder().createErrorAnnotation(block, PyBundle.message("ANN.default.except.must.be.last")); + getHolder().createErrorAnnotation(part, PyBundle.message("ANN.default.except.must.be.last")); } - if (block.getExceptClass() == null) { + if (part.getExceptClass() == null) { haveDefaultExcept = true; } } diff --git a/python/testData/psi/IfStatement.py b/python/testData/psi/IfStatement.py new file mode 100644 index 000000000000..b8e874401328 --- /dev/null +++ b/python/testData/psi/IfStatement.py @@ -0,0 +1,20 @@ +if 0: + pass + +if 1: + pass +else: + pass + +if 2: + pass +elif 3: + pass + +if 4: + pass +elif 5: + pass +else: + 1 + diff --git a/python/testData/psi/IfStatement.txt b/python/testData/psi/IfStatement.txt new file mode 100644 index 000000000000..b098d2cf646c --- /dev/null +++ b/python/testData/psi/IfStatement.txt @@ -0,0 +1,88 @@ +PyFile:IfStatement.py + PyIfStatement + PyIfPartIf + PsiElement(Py:IF_KEYWORD)('if') + PsiWhiteSpace(' ') + PyNumericLiteralExpression + PsiElement(Py:INTEGER_LITERAL)('0') + PsiElement(Py:COLON)(':') + PsiWhiteSpace('\n ') + PyStatementList + PyPassStatement + PsiElement(Py:PASS_KEYWORD)('pass') + PsiWhiteSpace('\n\n') + PyIfStatement + PyIfPartIf + PsiElement(Py:IF_KEYWORD)('if') + PsiWhiteSpace(' ') + PyNumericLiteralExpression + PsiElement(Py:INTEGER_LITERAL)('1') + PsiElement(Py:COLON)(':') + PsiWhiteSpace('\n ') + PyStatementList + PyPassStatement + PsiElement(Py:PASS_KEYWORD)('pass') + PsiWhiteSpace('\n') + PyElsePart + PsiElement(Py:ELSE_KEYWORD)('else') + PsiElement(Py:COLON)(':') + PsiWhiteSpace('\n ') + PyStatementList + PyPassStatement + PsiElement(Py:PASS_KEYWORD)('pass') + PsiWhiteSpace('\n\n') + PyIfStatement + PyIfPartIf + PsiElement(Py:IF_KEYWORD)('if') + PsiWhiteSpace(' ') + PyNumericLiteralExpression + PsiElement(Py:INTEGER_LITERAL)('2') + PsiElement(Py:COLON)(':') + PsiWhiteSpace('\n ') + PyStatementList + PyPassStatement + PsiElement(Py:PASS_KEYWORD)('pass') + PsiWhiteSpace('\n') + PyIfPartElif + PsiElement(Py:ELIF_KEYWORD)('elif') + PsiWhiteSpace(' ') + PyNumericLiteralExpression + PsiElement(Py:INTEGER_LITERAL)('3') + PsiElement(Py:COLON)(':') + PsiWhiteSpace('\n ') + PyStatementList + PyPassStatement + PsiElement(Py:PASS_KEYWORD)('pass') + PsiWhiteSpace('\n\n') + PyIfStatement + PyIfPartIf + PsiElement(Py:IF_KEYWORD)('if') + PsiWhiteSpace(' ') + PyNumericLiteralExpression + PsiElement(Py:INTEGER_LITERAL)('4') + PsiElement(Py:COLON)(':') + PsiWhiteSpace('\n ') + PyStatementList + PyPassStatement + PsiElement(Py:PASS_KEYWORD)('pass') + PsiWhiteSpace(' \n') + PyIfPartElif + PsiElement(Py:ELIF_KEYWORD)('elif') + PsiWhiteSpace(' ') + PyNumericLiteralExpression + PsiElement(Py:INTEGER_LITERAL)('5') + PsiElement(Py:COLON)(':') + PsiWhiteSpace('\n ') + PyStatementList + PyPassStatement + PsiElement(Py:PASS_KEYWORD)('pass') + PsiWhiteSpace('\n') + PyElsePart + PsiElement(Py:ELSE_KEYWORD)('else') + PsiElement(Py:COLON)(':') + PsiWhiteSpace('\n ') + PyStatementList + PyExpressionStatement + PyNumericLiteralExpression + PsiElement(Py:INTEGER_LITERAL)('1') + diff --git a/python/testData/psi/TryExceptFinally.txt b/python/testData/psi/TryExceptFinally.txt index dfbe843fa1e6..5f92b8008333 100644 --- a/python/testData/psi/TryExceptFinally.txt +++ b/python/testData/psi/TryExceptFinally.txt @@ -1,14 +1,15 @@ PyFile:TryExceptFinally.py PyTryExceptStatement - PsiElement(Py:TRY_KEYWORD)('try') - PsiElement(Py:COLON)(':') - PsiWhiteSpace('\n ') - PyStatementList - PyExpressionStatement - PyReferenceExpression: x - PsiElement(Py:IDENTIFIER)('x') + PyTryPart + PsiElement(Py:TRY_KEYWORD)('try') + PsiElement(Py:COLON)(':') + PsiWhiteSpace('\n ') + PyStatementList + PyExpressionStatement + PyReferenceExpression: x + PsiElement(Py:IDENTIFIER)('x') PsiWhiteSpace('\n') - PyExceptBlock + PyExceptPart PsiElement(Py:EXCEPT_KEYWORD)('except') PsiElement(Py:COLON)(':') PsiWhiteSpace('\n ') @@ -17,10 +18,11 @@ PyFile:TryExceptFinally.py PyReferenceExpression: y PsiElement(Py:IDENTIFIER)('y') PsiWhiteSpace('\n') - PsiElement(Py:FINALLY_KEYWORD)('finally') - PsiElement(Py:COLON)(':') - PsiWhiteSpace('\n ') - PyStatementList - PyExpressionStatement - PyReferenceExpression: z - PsiElement(Py:IDENTIFIER)('z') \ No newline at end of file + PyFinallyPart + PsiElement(Py:FINALLY_KEYWORD)('finally') + PsiElement(Py:COLON)(':') + PsiWhiteSpace('\n ') + PyStatementList + PyExpressionStatement + PyReferenceExpression: z + PsiElement(Py:IDENTIFIER)('z') diff --git a/python/testData/psi/TryFinally.txt b/python/testData/psi/TryFinally.txt index 10f8a2d61aee..970c9619699a 100644 --- a/python/testData/psi/TryFinally.txt +++ b/python/testData/psi/TryFinally.txt @@ -1,17 +1,19 @@ PyFile:TryFinally.py PyTryExceptStatement - PsiElement(Py:TRY_KEYWORD)('try') - PsiElement(Py:COLON)(':') - PsiWhiteSpace('\n ') - PyStatementList - PyExpressionStatement - PyReferenceExpression: x - PsiElement(Py:IDENTIFIER)('x') + PyTryPart + PsiElement(Py:TRY_KEYWORD)('try') + PsiElement(Py:COLON)(':') + PsiWhiteSpace('\n ') + PyStatementList + PyExpressionStatement + PyReferenceExpression: x + PsiElement(Py:IDENTIFIER)('x') PsiWhiteSpace('\n') - PsiElement(Py:FINALLY_KEYWORD)('finally') - PsiElement(Py:COLON)(':') - PsiWhiteSpace('\n ') - PyStatementList - PyExpressionStatement - PyReferenceExpression: z - PsiElement(Py:IDENTIFIER)('z') \ No newline at end of file + PyFinallyPart + PsiElement(Py:FINALLY_KEYWORD)('finally') + PsiElement(Py:COLON)(':') + PsiWhiteSpace('\n ') + PyStatementList + PyExpressionStatement + PyReferenceExpression: z + PsiElement(Py:IDENTIFIER)('z') diff --git a/python/testData/psi/parts/For.py b/python/testData/psi/parts/For.py new file mode 100644 index 000000000000..2b321d78fdce --- /dev/null +++ b/python/testData/psi/parts/For.py @@ -0,0 +1,5 @@ +# for stmt +for x in list: + pass +else: + pass diff --git a/python/testData/psi/parts/If.py b/python/testData/psi/parts/If.py new file mode 100644 index 000000000000..adace71c1c6d --- /dev/null +++ b/python/testData/psi/parts/If.py @@ -0,0 +1,3 @@ +# only if +if True: + pass diff --git a/python/testData/psi/parts/IfElifElse.py b/python/testData/psi/parts/IfElifElse.py new file mode 100644 index 000000000000..5b09d94e8bfe --- /dev/null +++ b/python/testData/psi/parts/IfElifElse.py @@ -0,0 +1,7 @@ +# if, elif, and else +if True: + pass +elif 0: + pass +else: + pass diff --git a/python/testData/psi/parts/IfElse.py b/python/testData/psi/parts/IfElse.py new file mode 100644 index 000000000000..36b3abe059a0 --- /dev/null +++ b/python/testData/psi/parts/IfElse.py @@ -0,0 +1,5 @@ +# if and else +if True: + pass +else: + pass diff --git a/python/testData/psi/parts/Try.py b/python/testData/psi/parts/Try.py new file mode 100644 index 000000000000..2831fcf4c3e7 --- /dev/null +++ b/python/testData/psi/parts/Try.py @@ -0,0 +1,12 @@ +# try and friends +try: + pass +except ArithmeticError, e: + pass +except: + pass +else: + pass +finally: + pass + diff --git a/python/testData/psi/parts/While.py b/python/testData/psi/parts/While.py new file mode 100644 index 000000000000..6e51effa0e26 --- /dev/null +++ b/python/testData/psi/parts/While.py @@ -0,0 +1,5 @@ +# while stmt +while True: + pass +else: + pass diff --git a/python/testSrc/com/jetbrains/python/MarkedTestCase.java b/python/testSrc/com/jetbrains/python/MarkedTestCase.java new file mode 100644 index 000000000000..65ef04e16820 --- /dev/null +++ b/python/testSrc/com/jetbrains/python/MarkedTestCase.java @@ -0,0 +1,103 @@ +package com.jetbrains.python; + +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiElement; +import com.intellij.testFramework.PsiTestCase; +import com.intellij.util.containers.HashMap; +import org.jetbrains.annotations.NonNls; + +import java.io.File; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Base for cases that need marked PSI elements. + * User: dcheryasov + * Date: Mar 14, 2009 11:57:52 PM + */ +public abstract class MarkedTestCase extends PsiTestCase { + + /** + * Marker "as expected", any alphanumeric sting in angle brackets. + */ + public final @NonNls String MARKER = "<[a-zA-Z0-9_]+>"; + + /** + * Uses MARKER as regexp. + * @see #configureByFileText(String, String, String) + * @param filePath file to load and parse + * @return a mapping of markers to PSI elements + * @throws Exception + */ + protected Map configureByFile(@NonNls String filePath) throws Exception { + return configureByFile(filePath, MARKER); + } + + /** + * Like configureByFileText, but with a file to be read. + * @param filePath file to read and parse + * @param markerRegexp regexp for markers + * @return a mapping of markers to PSI elements + * @throws Exception + */ + protected Map configureByFile(@NonNls String filePath, @NonNls String markerRegexp) + throws Exception + { + final String fullPath = getTestDataPath() + filePath; + final VirtualFile vFile = LocalFileSystem.getInstance().findFileByPath(fullPath.replace(File.separatorChar, '/')); + assertNotNull("file " + filePath + " not found", vFile); + + String fileText = StringUtil.convertLineSeparators(VfsUtil.loadText(vFile), "\n"); + + final String fileName = vFile.getName(); + + return configureByFileText(fileText, fileName, markerRegexp); + } + + /** + * Typically a text is marked with patterns: "foo <ref1>bar() + <ref2>baz", etc, and the result is + * a map where strings "<ref1>" and "<ref2>" are mapped to PSI elements for "bar" and "baz". + * @param fileText text to parse + * @param fileName name to give to the PSI file + * @param markerRegexp regexp to detect markers in the text + * @return mapping of markers to the PSI elements + * @throws Exception + */ + protected Map configureByFileText(String fileText, String fileName, @NonNls String markerRegexp) + throws Exception + { + // build a map of marks to positions, and the text with marks stripped + Pattern pat = Pattern.compile(markerRegexp); + Matcher mat = pat.matcher(fileText); + int rest_index = 0; // from here on fileText is not yet looked at + Map offsets = new HashMap(); + StringBuffer text = new StringBuffer(); + while (mat.find(rest_index)) { + String mark = mat.group(); + CharSequence prev_part = fileText.subSequence(rest_index, mat.start()); + text.append(prev_part); + offsets.put(mark, text.length()); + rest_index = mat.end(); + } + if (rest_index < fileText.length()) text.append(fileText.substring(rest_index)); + + // create a file and map marks to PSI elements + Map result = new HashMap(); + myFile = createFile(myModule, fileName, text.toString()); + for (Map.Entry entry : offsets.entrySet()) { + result.put(entry.getKey(), myFile.findElementAt(entry.getValue())); + } + return result; + } + + protected Map loadTest() throws Exception { + String fname = getTestName(false) + ".py"; + return configureByFile(fname); + } + + protected abstract String getTestDataPath(); +} diff --git a/python/testSrc/com/jetbrains/python/PythonParsingTest.java b/python/testSrc/com/jetbrains/python/PythonParsingTest.java index 332d7efa575f..f416dfc8c99b 100644 --- a/python/testSrc/com/jetbrains/python/PythonParsingTest.java +++ b/python/testSrc/com/jetbrains/python/PythonParsingTest.java @@ -19,6 +19,10 @@ public class PythonParsingTest extends ParsingTestCase { doTest(true); } + public void testIfStatement() throws Exception { + doTest(true); + } + public void testConditionalExpression() throws Exception { doTest(true); } diff --git a/python/testSrc/com/jetbrains/python/StatementPartsTest.java b/python/testSrc/com/jetbrains/python/StatementPartsTest.java new file mode 100644 index 000000000000..5c708f488a40 --- /dev/null +++ b/python/testSrc/com/jetbrains/python/StatementPartsTest.java @@ -0,0 +1,159 @@ +package com.jetbrains.python; + +import com.intellij.openapi.application.PathManager; +import com.intellij.psi.PsiElement; +import com.jetbrains.python.psi.*; + +import java.util.Map; + +/** + * Tests statement parts. + * User: dcheryasov + * Date: Mar 15, 2009 3:11:01 AM + */ +public class StatementPartsTest extends MarkedTestCase { + + protected String getTestDataPath() { + return PathManager.getHomePath() + "/plugins/python/testData/psi/parts/"; + } + + public void testIf() throws Exception { + Map marks = loadTest(); + assertEquals(2, marks.size()); + PsiElement elt = marks.get("").getParent().getParent(); // if_keyword -> if_part -> if_stmt + assertTrue(elt instanceof PyIfStatement); + PyIfStatement if_stmt = (PyIfStatement)elt; + PyIfPart if_part = if_stmt.getIfPart(); + assertFalse(if_part.isElif()); + PyExpression if_cond = if_part.getCondition(); + assertEquals(marks.get("").getParent(), if_cond); + } + + public void testIfElse() throws Exception { + Map marks = loadTest(); + assertEquals(4, marks.size()); + PsiElement elt = marks.get("").getParent().getParent(); // if_keyword -> if_part -> if_stmt + assertTrue(elt instanceof PyIfStatement); + PyIfStatement if_stmt = (PyIfStatement)elt; + + PyIfPart if_part = if_stmt.getIfPart(); + assertFalse(if_part.isElif()); + PyExpression if_cond = if_part.getCondition(); + assertEquals(marks.get("").getParent(), if_cond); + + PyStatementList stmt_list = if_part.getStatementList(); + assertNotNull(stmt_list); + assertEquals(marks.get("").getParent().getParent(), stmt_list); // keyword -> stmt -> stmt_list + + PyElsePart else_part = if_stmt.getElsePart(); + assertNotNull(else_part); + + stmt_list = else_part.getStatementList(); + assertNotNull(stmt_list); + assertEquals(marks.get("").getParent().getParent(), stmt_list); // keyword -> stmt -> stmt_list + + } + + public void testIfElifElse() throws Exception { + Map marks = loadTest(); + assertEquals(6, marks.size()); + PsiElement elt = marks.get("").getParent().getParent(); // if_keyword -> if_part -> if_stmt + assertTrue(elt instanceof PyIfStatement); + PyIfStatement if_stmt = (PyIfStatement)elt; + + PyIfPart if_part = if_stmt.getIfPart(); + assertFalse(if_part.isElif()); + PyExpression if_cond = if_part.getCondition(); + assertEquals(marks.get("").getParent(), if_cond); + + PyStatementList stmt_list = if_part.getStatementList(); + assertNotNull(stmt_list); + assertEquals(marks.get("").getParent().getParent(), stmt_list); // keyword -> stmt -> stmt_list + + PyElsePart else_part = if_stmt.getElsePart(); + assertNotNull(else_part); + + stmt_list = else_part.getStatementList(); + assertNotNull(stmt_list); + assertEquals(marks.get("").getParent().getParent(), stmt_list); // keyword -> stmt -> stmt_list + + PyIfPart elif_part = if_stmt.getElifParts()[0]; + assertTrue(elif_part.isElif()); + if_cond = elif_part.getCondition(); + assertEquals(marks.get("").getParent(), if_cond); + + stmt_list = elif_part.getStatementList(); + assertNotNull(stmt_list); + assertEquals(marks.get("").getParent().getParent(), stmt_list); // keyword -> stmt -> stmt_list + + } + + public void testWhile() throws Exception { + Map marks = loadTest(); + assertEquals(3, marks.size()); + + PsiElement elt = marks.get("").getParent().getParent(); // keyword -> part -> stmt + assertTrue(elt instanceof PyWhileStatement); + PyWhileStatement while_stmt = (PyWhileStatement)elt; + + PyWhilePart while_part = while_stmt.getWhilePart(); + + PyExpression cond = while_part.getCondition(); + assertEquals(marks.get("").getParent(), cond); + + elt = marks.get("").getParent(); // keyword -> part + assertTrue(elt instanceof PyElsePart); + assertEquals(while_stmt.getElsePart(), elt); + } + + public void testFor() throws Exception { + Map marks = loadTest(); + assertEquals(4, marks.size()); + + PsiElement elt = marks.get("").getParent().getParent(); // keyword -> part -> stmt + assertTrue(elt instanceof PyForStatement); + PyForStatement stmt = (PyForStatement)elt; + + PyForPart for_part = stmt.getForPart(); + + PyExpression target = for_part.getTarget(); + assertEquals(marks.get("").getParent(), target); // ident -> expr + + PyExpression source = for_part.getSource(); + assertEquals(marks.get("").getParent(), source); // ident -> expr + + elt = marks.get("").getParent(); // keyword -> part + assertTrue(elt instanceof PyElsePart); + assertEquals(stmt.getElsePart(), elt); + } + + public void testTry() throws Exception { + Map marks = loadTest(); + assertEquals(6, marks.size()); + + PsiElement elt = marks.get("").getParent().getParent(); // keyword -> part -> stmt + assertTrue(elt instanceof PyTryExceptStatement); + PyTryExceptStatement stmt = (PyTryExceptStatement)elt; + + PyTryPart try_part = stmt.getTryPart(); + PyStatementList stmt_list = try_part.getStatementList(); + assertNotNull(stmt_list); + assertEquals(marks.get("").getParent().getParent(), stmt_list); // keyword -> stmt -> stmt_list + + PyExceptPart exc_part = stmt.getExceptParts()[0]; + assertEquals("ArithmeticError", exc_part.getExceptClass().getText()); + assertEquals(marks.get("").getParent(), exc_part); + + exc_part = (PyExceptPart)marks.get("").getParent(); // keyword -> part + assertEquals(stmt.getExceptParts()[1], exc_part); + assertNull(exc_part.getExceptClass()); + + elt = marks.get("").getParent(); // keyword -> part + assertTrue(elt instanceof PyElsePart); + assertEquals(stmt.getElsePart(), elt); + + elt = marks.get("").getParent(); // keyword -> part + assertTrue(elt instanceof PyFinallyPart); + assertEquals(stmt.getFinallyPart(), elt); + } +}