Commit 1c248f8e authored by Milian Wolff's avatar Milian Wolff
Browse files

Parse qmake `else {}` statements

Add a basic test to verify that this succeeds now.

This is the first step in unbreaking the test_qmakeproject now that
e.g. gcc-base.conf uses this syntax. We did not support it at all
previously, leading to bogus parse errors.

No semantic analysis is added here, so the existing project parse
test still fails.
parent 7b9e044b
......@@ -62,17 +62,17 @@ class KDEVQMAKEPARSER_EXPORT ScopeBodyAST: public AST
public:
explicit ScopeBodyAST( AST* parent, AST::Type type = AST::ScopeBody );
~ScopeBodyAST() override;
QList<StatementAST*> statements;
QList<StatementAST*> ifStatements;
QList<StatementAST*> elseStatements;
};
class KDEVQMAKEPARSER_EXPORT ProjectAST : public ScopeBodyAST
class KDEVQMAKEPARSER_EXPORT ProjectAST : public AST
{
public:
explicit ProjectAST();
~ProjectAST() override;
QString filename;
QList<StatementAST*> statements;
};
class KDEVQMAKEPARSER_EXPORT AssignmentAST : public StatementAST
......
......@@ -10,6 +10,8 @@
#include "qmakeparser.h"
#include "ast.h"
#include <QScopedValueRollback>
#include <debug.h>
namespace QMake {
......@@ -112,16 +114,29 @@ void BuildASTVisitor::visitScope(ScopeAst* node)
aststack.push(simple);
}
}
if (node->scopeBody) {
if (node->ifElse) {
auto* scopebody = createAst<ScopeBodyAST>(node, aststack.top());
auto* scope = stackTop<ScopeAST>();
scope->body = scopebody;
aststack.push(scopebody);
visitNode(node->scopeBody);
visitNode(node->ifElse);
aststack.pop();
}
}
void BuildASTVisitor::visitIfElse(IfElseAst* node)
{
auto* scopeBody = stackTop<ScopeBodyAST>();
{
auto guard = QScopedValueRollback(m_currentStatements, &scopeBody->ifStatements);
visitNode(node->ifBody);
}
{
auto guard = QScopedValueRollback(m_currentStatements, &scopeBody->elseStatements);
visitNode(node->elseBody);
}
}
void BuildASTVisitor::visitOp(OpAst* node)
{
auto* assign = stackTop<AssignmentAST>();
......@@ -134,6 +149,8 @@ void BuildASTVisitor::visitOp(OpAst* node)
void BuildASTVisitor::visitProject(ProjectAst* node)
{
auto* project = stackTop<ProjectAST>();
auto guard = QScopedValueRollback(m_currentStatements, &project->statements);
DefaultVisitor::visitProject(node);
}
......@@ -159,9 +176,7 @@ void BuildASTVisitor::visitStatement(StatementAst* node)
}
setIdentifierForStatement(stmt, val);
auto* scope = stackTop<ScopeBodyAST>();
// qCDebug(KDEV_QMAKE) << "scope:" << scope;
scope->statements.append(stmt);
m_currentStatements->append(stmt);
}
}
......
......@@ -19,6 +19,7 @@ class Parser;
class ProjectAST;
class AST;
class ValueAST;
class StatementAST;
class BuildASTVisitor : public DefaultVisitor
{
......@@ -30,6 +31,7 @@ public:
void visitOrOperator( OrOperatorAst *node ) override;
void visitItem( ItemAst *node ) override;
void visitScope( ScopeAst *node ) override;
void visitIfElse(IfElseAst* node) override;
void visitOp( OpAst *node ) override;
void visitProject( ProjectAst *node ) override;
void visitScopeBody( ScopeBodyAst *node ) override;
......@@ -49,6 +51,7 @@ private:
KDevelop::Stack<AST*> aststack;
Parser* m_parser;
QList<StatementAST*>* m_currentStatements = nullptr;
};
}
......
......@@ -65,7 +65,7 @@ namespace QMake
TILDEEQ("tildeeq") ;;
%token COLON("colon"), COMMA("comma"), CONT("cont"), EXCLAM("exclam"),
NEWLINE("newline"), OR("or") ;;
NEWLINE("newline"), OR("or"), ELSE("else") ;;
%token IDENTIFIER("identifier"), VALUE("value") ;;
......@@ -95,8 +95,8 @@ namespace QMake
-> statement [ member variable isNewline: bool;
member variable isExclam: bool; ] ;;
functionArguments=functionArguments ( scopeBody=scopeBody | orOperator=orOperator scopeBody=scopeBody | 0 )
| ( orOperator=orOperator | 0 ) scopeBody=scopeBody
functionArguments=functionArguments ( ifElse=ifElse | orOperator=orOperator ifElse=ifElse | 0 )
| ( orOperator=orOperator | 0 ) ifElse=ifElse
-> scope ;;
( OR #item=item )+
......@@ -126,6 +126,12 @@ namespace QMake
LBRACE ( NEWLINE | 0 ) ( #statements=statement )* RBRACE | COLON #statements=statement
-> scopeBody ;;
ELSE body=scopeBody
-> elseBody ;;
ifBody=scopeBody ( elseBody=elseBody | 0 )
-> ifElse ;;
-----------------------------------------------------------------
-- Code segments copied to the implementation (.cpp) file.
-- If existent, kdevelop-pg's current syntax requires this block
......
......@@ -61,8 +61,10 @@ ScopeBodyAST::ScopeBodyAST(AST* parent, AST::Type type)
ScopeBodyAST::~ScopeBodyAST()
{
qDeleteAll(statements);
statements.clear();
qDeleteAll(ifStatements);
ifStatements.clear();
qDeleteAll(elseStatements);
elseStatements.clear();
}
FunctionCallAST::FunctionCallAST(AST* parent)
......@@ -91,12 +93,14 @@ OrAST::~OrAST()
}
ProjectAST::ProjectAST()
: ScopeBodyAST(nullptr, AST::Project)
: AST(nullptr, AST::Project)
{
}
ProjectAST::~ProjectAST()
{
qDeleteAll(statements);
statements.clear();
}
ScopeAST::ScopeAST(AST* parent, AST::Type type)
......
......@@ -44,7 +44,10 @@ void ASTDefaultVisitor::visitFunctionCall(FunctionCallAST* node)
void ASTDefaultVisitor::visitScopeBody(ScopeBodyAST* node)
{
foreach (StatementAST* stmt, node->statements) {
foreach (StatementAST* stmt, node->ifStatements) {
visitNode(stmt);
}
foreach (StatementAST* stmt, node->elseStatements) {
visitNode(stmt);
}
}
......
......@@ -57,7 +57,20 @@ void DebugVisitor::visitScope(ScopeAst* node)
indent++;
visitNode(node->functionArguments);
visitNode(node->orOperator);
visitNode(node->scopeBody);
visitNode(node->ifElse);
indent--;
m_out << getIndent() << "END(scope)(" << getTokenInfo(node->endToken) << ")\n";
}
void DebugVisitor::visitIfElse(IfElseAst* node)
{
m_out << getIndent() << "BEGIN(scope)(" << getTokenInfo(node->startToken) << ")\n";
indent++;
visitNode(node->ifBody);
if (node->elseBody) {
m_out << getIndent() << "ELSE:\n";
visitNode(node->elseBody);
}
indent--;
m_out << getIndent() << "END(scope)(" << getTokenInfo(node->endToken) << ")\n";
}
......
......@@ -27,6 +27,7 @@ public:
void visitOrOperator( OrOperatorAst *node ) override;
void visitItem( ItemAst *node ) override;
void visitScope( ScopeAst *node ) override;
void visitIfElse(IfElseAst* node) override;
void visitOp( OpAst *node ) override;
void visitProject( ProjectAst *node ) override;
void visitScopeBody( ScopeBodyAst *node ) override;
......
......@@ -85,13 +85,14 @@ int Lexer::nextTokenKind()
it = ignoreWhitespaceAndComment(it);
m_tokenBegin = m_curpos;
if (m_curpos < m_contentSize) {
if (it->unicode() == '\n') {
if (it->unicode() == '}') {
popState();
token = Parser::Token_RBRACE;
} else if (it->unicode() == '\n') {
popState();
createNewline(m_curpos);
token = Parser::Token_NEWLINE;
} else if (it->unicode() == '\\' && isCont(it))
{
} else if (it->unicode() == '\\' && isCont(it)) {
pushState(ContState);
token = Parser::Token_CONT;
} else if (it->unicode() == '"') {
......@@ -216,6 +217,8 @@ int Lexer::nextTokenKind()
}
if (!isEndIdentifierCharacter((it - 1))) {
token = Parser::Token_INVALID;
} else if (m_content.midRef(m_tokenBegin, m_curpos - m_tokenBegin) == QLatin1String("else")) {
token = Parser::Token_ELSE;
}
m_curpos--;
} else {
......
......@@ -155,7 +155,7 @@ BEGINTESTFUNCIMPL(FunctionScopeTest, oneStatementSubScope, 1)
val->value = QStringLiteral("val2");
tst->values.append(val);
body->statements.insert(0, tst);
body->ifStatements.insert(0, tst);
call->body = body;
teststmts.append(call);
TESTSCOPEBODY(scope, teststmts, 1)
......@@ -236,7 +236,7 @@ BEGINTESTFUNCIMPL(FunctionScopeTest, multiLineScopeFuncCall, 1)
val = new QMake::ValueAST(tst);
val->value = QStringLiteral("val2");
tst->values.append(val);
body->statements.insert(0, tst);
body->ifStatements.insert(0, tst);
call->body = body;
teststmts.append(call);
TESTSCOPEBODY(scope, teststmts, 2)
......
......@@ -92,7 +92,7 @@ BEGINTESTFUNCIMPL(ScopeTest, nestedScope, 1)
val = new QMake::ValueAST(tst);
val->value = QStringLiteral("FOO");
tst->values.append(val);
body->statements.append(tst);
body->ifStatements.append(tst);
simple->body = body;
testlist.append(simple);
......@@ -120,3 +120,11 @@ void ScopeTest::strangeScopeNames()
bool ret = d.parse(&ast);
QVERIFY(ret);
}
void ScopeTest::testIfElse()
{
QMake::Driver d;
d.setContent(QStringLiteral("isEmpty(foo) { A = 1 } else { B = 2 }\n"));
bool ret = d.parse(&ast);
QVERIFY(ret);
}
......@@ -38,6 +38,8 @@ class ScopeTest : public QObject
void missingColon();
void missingColon_data();
void strangeScopeNames();
void testIfElse();
private:
QMake::ProjectAST* ast = nullptr;
......
......@@ -12,7 +12,7 @@
void matchScopeBodies(const QList<QMake::StatementAST*>& realbody, const QList<QMake::StatementAST*>& testbody)
{
QVERIFY(realbody.count() == testbody.count());
QCOMPARE(realbody.count(), testbody.count());
int i = 0;
QMake::AssignmentAST* assign;
QMake::ScopeAST* scope;
......@@ -38,13 +38,10 @@ void matchScopeBodies(const QList<QMake::StatementAST*>& realbody, const QList<Q
} else if (orop && testorop) {
TESTOROPAST(orop, testorop)
}
QVERIFY((scope->body && testscope->body) || (!scope->body && !testscope->body));
QCOMPARE(static_cast<bool>(scope->body), static_cast<bool>(testscope->body));
if (scope->body && testscope->body) {
QList<QMake::StatementAST*> bodylist;
QList<QMake::StatementAST*> testbodylist;
bodylist = scope->body->statements;
testbodylist = testscope->body->statements;
matchScopeBodies(bodylist, testbodylist);
matchScopeBodies(scope->body->ifStatements, testscope->body->ifStatements);
matchScopeBodies(scope->body->elseStatements, testscope->body->elseStatements);
}
}
assign = dynamic_cast<QMake::AssignmentAST*>(ast);
......
......@@ -85,8 +85,8 @@ void classname::funcname()\
#define TESTSCOPEBODY(scope, teststmts, stmtcount) \
QVERIFY(scope->body != nullptr); \
QVERIFY(scope->body->statements.count() == stmtcount); \
matchScopeBodies(scope->body->statements, teststmts); \
QVERIFY(scope->body->ifStatements.count() == stmtcount); \
matchScopeBodies(scope->body->ifStatements, teststmts); \
qDeleteAll(teststmts);
#define TESTSCOPEAST( scope, testscope ) \
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment