summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xutil/cmake/pro2cmake.py345
-rw-r--r--util/cmake/qmake_parser.py377
-rwxr-xr-xutil/cmake/tests/test_lc_fixup.py4
-rwxr-xr-xutil/cmake/tests/test_parsing.py2
4 files changed, 381 insertions, 347 deletions
diff --git a/util/cmake/pro2cmake.py b/util/cmake/pro2cmake.py
index 211678a8863..5b7c02a7393 100755
--- a/util/cmake/pro2cmake.py
+++ b/util/cmake/pro2cmake.py
@@ -48,7 +48,6 @@ import xml.etree.ElementTree as ET
from argparse import ArgumentParser
from textwrap import dedent
from textwrap import indent as textwrap_indent
-from itertools import chain
from functools import lru_cache
from shutil import copyfile
from typing import (
@@ -65,6 +64,8 @@ from typing import (
Match,
Type,
)
+
+from qmake_parser import parseProFile
from special_case_helper import SpecialCaseHandler
from helper import (
map_qt_library,
@@ -76,9 +77,6 @@ from helper import (
generate_find_package_info,
LibraryMapping,
)
-from helper import _set_up_py_parsing_nicer_debug_output
-
-_set_up_py_parsing_nicer_debug_output(pp)
cmake_version_string = "3.15.0"
@@ -530,37 +528,6 @@ class QmlDir:
raise RuntimeError(f"Uhandled qmldir entry {line}")
-def fixup_linecontinuation(contents: str) -> str:
- # Remove all line continuations, aka a backslash followed by
- # a newline character with an arbitrary amount of whitespace
- # between the backslash and the newline.
- # This greatly simplifies the qmake parsing grammar.
- contents = re.sub(r"([^\t ])\\[ \t]*\n", "\\1 ", contents)
- contents = re.sub(r"\\[ \t]*\n", "", contents)
- return contents
-
-
-def fixup_comments(contents: str) -> str:
- # Get rid of completely commented out lines.
- # So any line which starts with a '#' char and ends with a new line
- # will be replaced by a single new line.
- #
- # This is needed because qmake syntax is weird. In a multi line
- # assignment (separated by backslashes and newlines aka
- # # \\\n ), if any of the lines are completely commented out, in
- # principle the assignment should fail.
- #
- # It should fail because you would have a new line separating
- # the previous value from the next value, and the next value would
- # not be interpreted as a value, but as a new token / operation.
- # qmake is lenient though, and accepts that, so we need to take
- # care of it as well, as if the commented line didn't exist in the
- # first place.
-
- contents = re.sub(r"\n#[^\n]*?\n", "\n", contents, re.DOTALL)
- return contents
-
-
def spaces(indent: int) -> str:
return " " * indent
@@ -611,48 +578,6 @@ def handle_vpath(source: str, base_dir: str, vpath: List[str]) -> str:
return f"{source}-NOTFOUND"
-def flatten_list(l):
- """ Flattens an irregular nested list into a simple list."""
- for el in l:
- if isinstance(el, collections.abc.Iterable) and not isinstance(el, (str, bytes)):
- yield from flatten_list(el)
- else:
- yield el
-
-
-def handle_function_value(group: pp.ParseResults):
- function_name = group[0]
- function_args = group[1]
- if function_name == "qtLibraryTarget":
- if len(function_args) > 1:
- raise RuntimeError(
- "Don't know what to with more than one function argument "
- "for $$qtLibraryTarget()."
- )
- return str(function_args[0])
-
- if function_name == "quote":
- # Do nothing, just return a string result
- return str(group)
-
- if function_name == "files":
- return str(function_args[0])
-
- if function_name == "basename":
- if len(function_args) != 1:
- print(f"XXXX basename with more than one argument")
- if function_args[0] == '_PRO_FILE_PWD_':
- return os.path.basename(os.getcwd())
- print(f"XXXX basename with value other than _PRO_FILE_PWD_")
- return os.path.basename(str(function_args[0]))
-
- if isinstance(function_args, pp.ParseResults):
- function_args = list(flatten_list(function_args.asList()))
-
- # For other functions, return the whole expression as a string.
- return f"$${function_name}({' '.join(function_args)})"
-
-
class Operation:
def __init__(self, value: Union[List[str], str]) -> None:
if isinstance(value, list):
@@ -1223,272 +1148,6 @@ class Scope(object):
return self.get("_INCLUDED")
-class QmakeParser:
- def __init__(self, *, debug: bool = False) -> None:
- self.debug = debug
- self._Grammar = self._generate_grammar()
-
- def _generate_grammar(self):
- # Define grammar:
- pp.ParserElement.setDefaultWhitespaceChars(" \t")
-
- def add_element(name: str, value: pp.ParserElement):
- nonlocal self
- if self.debug:
- value.setName(name)
- value.setDebug()
- return value
-
- EOL = add_element("EOL", pp.Suppress(pp.LineEnd()))
- Else = add_element("Else", pp.Keyword("else"))
- Identifier = add_element(
- "Identifier", pp.Word(f"{pp.alphas}_", bodyChars=pp.alphanums + "_-./")
- )
- BracedValue = add_element(
- "BracedValue",
- pp.nestedExpr(
- ignoreExpr=pp.quotedString
- | pp.QuotedString(
- quoteChar="$(", endQuoteChar=")", escQuote="\\", unquoteResults=False
- )
- ).setParseAction(lambda s, l, t: ["(", *t[0], ")"]),
- )
-
- Substitution = add_element(
- "Substitution",
- pp.Combine(
- pp.Literal("$")
- + (
- (
- (pp.Literal("$") + Identifier + pp.Optional(pp.nestedExpr()))
- | (pp.Literal("(") + Identifier + pp.Literal(")"))
- | (pp.Literal("{") + Identifier + pp.Literal("}"))
- | (
- pp.Literal("$")
- + pp.Literal("{")
- + Identifier
- + pp.Optional(pp.nestedExpr())
- + pp.Literal("}")
- )
- | (pp.Literal("$") + pp.Literal("[") + Identifier + pp.Literal("]"))
- )
- )
- ),
- )
- LiteralValuePart = add_element(
- "LiteralValuePart", pp.Word(pp.printables, excludeChars="$#{}()")
- )
- SubstitutionValue = add_element(
- "SubstitutionValue",
- pp.Combine(pp.OneOrMore(Substitution | LiteralValuePart | pp.Literal("$"))),
- )
- FunctionValue = add_element(
- "FunctionValue",
- pp.Group(
- pp.Suppress(pp.Literal("$") + pp.Literal("$"))
- + Identifier
- + pp.nestedExpr() # .setParseAction(lambda s, l, t: ['(', *t[0], ')'])
- ).setParseAction(lambda s, l, t: handle_function_value(*t)),
- )
- Value = add_element(
- "Value",
- pp.NotAny(Else | pp.Literal("}") | EOL)
- + (
- pp.QuotedString(quoteChar='"', escChar="\\")
- | FunctionValue
- | SubstitutionValue
- | BracedValue
- ),
- )
-
- Values = add_element("Values", pp.ZeroOrMore(Value)("value"))
-
- Op = add_element(
- "OP", pp.Literal("=") | pp.Literal("-=") | pp.Literal("+=") | pp.Literal("*=") | pp.Literal("~=")
- )
-
- Key = add_element("Key", Identifier)
-
- Operation = add_element("Operation", Key("key") + Op("operation") + Values("value"))
- CallArgs = add_element("CallArgs", pp.nestedExpr())
-
- def parse_call_args(results):
- out = ""
- for item in chain(*results):
- if isinstance(item, str):
- out += item
- else:
- out += "(" + parse_call_args(item) + ")"
- return out
-
- CallArgs.setParseAction(parse_call_args)
-
- Load = add_element("Load", pp.Keyword("load") + CallArgs("loaded"))
- Include = add_element("Include", pp.Keyword("include") + CallArgs("included"))
- Option = add_element("Option", pp.Keyword("option") + CallArgs("option"))
- RequiresCondition = add_element("RequiresCondition", pp.originalTextFor(pp.nestedExpr()))
-
- def parse_requires_condition(s, l, t):
- # The following expression unwraps the condition via the additional info
- # set by originalTextFor.
- condition_without_parentheses = s[t._original_start + 1 : t._original_end - 1]
-
- # And this replaces the colons with '&&' similar how it's done for 'Condition'.
- condition_without_parentheses = (
- condition_without_parentheses.strip().replace(":", " && ").strip(" && ")
- )
- return condition_without_parentheses
-
- RequiresCondition.setParseAction(parse_requires_condition)
- Requires = add_element(
- "Requires", pp.Keyword("requires") + RequiresCondition("project_required_condition")
- )
-
- # ignore the whole thing...
- DefineTestDefinition = add_element(
- "DefineTestDefinition",
- pp.Suppress(
- pp.Keyword("defineTest")
- + CallArgs
- + pp.nestedExpr(opener="{", closer="}", ignoreExpr=pp.LineEnd())
- ),
- )
-
- # ignore the whole thing...
- ForLoop = add_element(
- "ForLoop",
- pp.Suppress(
- pp.Keyword("for")
- + CallArgs
- + pp.nestedExpr(opener="{", closer="}", ignoreExpr=pp.LineEnd())
- ),
- )
-
- # ignore the whole thing...
- ForLoopSingleLine = add_element(
- "ForLoopSingleLine",
- pp.Suppress(pp.Keyword("for") + CallArgs + pp.Literal(":") + pp.SkipTo(EOL)),
- )
-
- # ignore the whole thing...
- FunctionCall = add_element("FunctionCall", pp.Suppress(Identifier + pp.nestedExpr()))
-
- Scope = add_element("Scope", pp.Forward())
-
- Statement = add_element(
- "Statement",
- pp.Group(
- Load
- | Include
- | Option
- | Requires
- | ForLoop
- | ForLoopSingleLine
- | DefineTestDefinition
- | FunctionCall
- | Operation
- ),
- )
- StatementLine = add_element("StatementLine", Statement + (EOL | pp.FollowedBy("}")))
- StatementGroup = add_element(
- "StatementGroup", pp.ZeroOrMore(StatementLine | Scope | pp.Suppress(EOL))
- )
-
- Block = add_element(
- "Block",
- pp.Suppress("{")
- + pp.Optional(EOL)
- + StatementGroup
- + pp.Optional(EOL)
- + pp.Suppress("}")
- + pp.Optional(EOL),
- )
-
- ConditionEnd = add_element(
- "ConditionEnd",
- pp.FollowedBy(
- (pp.Optional(pp.White()) + (pp.Literal(":") | pp.Literal("{") | pp.Literal("|")))
- ),
- )
-
- ConditionPart1 = add_element(
- "ConditionPart1", (pp.Optional("!") + Identifier + pp.Optional(BracedValue))
- )
- ConditionPart2 = add_element("ConditionPart2", pp.CharsNotIn("#{}|:=\\\n"))
- ConditionPart = add_element(
- "ConditionPart", (ConditionPart1 ^ ConditionPart2) + ConditionEnd
- )
-
- ConditionOp = add_element("ConditionOp", pp.Literal("|") ^ pp.Literal(":"))
- ConditionWhiteSpace = add_element(
- "ConditionWhiteSpace", pp.Suppress(pp.Optional(pp.White(" ")))
- )
-
- ConditionRepeated = add_element(
- "ConditionRepeated", pp.ZeroOrMore(ConditionOp + ConditionWhiteSpace + ConditionPart)
- )
-
- Condition = add_element("Condition", pp.Combine(ConditionPart + ConditionRepeated))
- Condition.setParseAction(lambda x: " ".join(x).strip().replace(":", " && ").strip(" && "))
-
- # Weird thing like write_file(a)|error() where error() is the alternative condition
- # which happens to be a function call. In this case there is no scope, but our code expects
- # a scope with a list of statements, so create a fake empty statement.
- ConditionEndingInFunctionCall = add_element(
- "ConditionEndingInFunctionCall",
- pp.Suppress(ConditionOp)
- + FunctionCall
- + pp.Empty().setParseAction(lambda x: [[]]).setResultsName("statements"),
- )
-
- SingleLineScope = add_element(
- "SingleLineScope",
- pp.Suppress(pp.Literal(":")) + pp.Group(Block | (Statement + EOL))("statements"),
- )
- MultiLineScope = add_element("MultiLineScope", Block("statements"))
-
- SingleLineElse = add_element(
- "SingleLineElse",
- pp.Suppress(pp.Literal(":")) + (Scope | Block | (Statement + pp.Optional(EOL))),
- )
- MultiLineElse = add_element("MultiLineElse", Block)
- ElseBranch = add_element("ElseBranch", pp.Suppress(Else) + (SingleLineElse | MultiLineElse))
-
- # Scope is already add_element'ed in the forward declaration above.
- Scope <<= pp.Group(
- Condition("condition")
- + (SingleLineScope | MultiLineScope | ConditionEndingInFunctionCall)
- + pp.Optional(ElseBranch)("else_statements")
- )
-
- Grammar = StatementGroup("statements")
- Grammar.ignore(pp.pythonStyleComment())
-
- return Grammar
-
- def parseFile(self, file: str):
- print(f'Parsing "{file}"...')
- try:
- with open(file, "r") as file_fd:
- contents = file_fd.read()
-
- # old_contents = contents
- contents = fixup_comments(contents)
- contents = fixup_linecontinuation(contents)
- result = self._Grammar.parseString(contents, parseAll=True)
- except pp.ParseException as pe:
- print(pe.line)
- print(f"{' ' * (pe.col-1)}^")
- print(pe)
- raise pe
- return result
-
-
-def parseProFile(file: str, *, debug=False):
- parser = QmakeParser(debug=debug)
- return parser.parseFile(file)
-
-
# Given "if(a|b):c" returns "(a|b):c". Uses pyparsing to keep the parentheses
# balanced.
def unwrap_if(input_string):
diff --git a/util/cmake/qmake_parser.py b/util/cmake/qmake_parser.py
new file mode 100644
index 00000000000..7aba0784e21
--- /dev/null
+++ b/util/cmake/qmake_parser.py
@@ -0,0 +1,377 @@
+#!/usr/bin/env python3
+#############################################################################
+##
+## Copyright (C) 2018 The Qt Company Ltd.
+## Contact: https://www.qt.io/licensing/
+##
+## This file is part of the plugins of the Qt Toolkit.
+##
+## $QT_BEGIN_LICENSE:GPL-EXCEPT$
+## Commercial License Usage
+## Licensees holding valid commercial Qt licenses may use this file in
+## accordance with the commercial license agreement provided with the
+## Software or, alternatively, in accordance with the terms contained in
+## a written agreement between you and The Qt Company. For licensing terms
+## and conditions see https://www.qt.io/terms-conditions. For further
+## information use the contact form at https://www.qt.io/contact-us.
+##
+## GNU General Public License Usage
+## Alternatively, this file may be used under the terms of the GNU
+## General Public License version 3 as published by the Free Software
+## Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+## included in the packaging of this file. Please review the following
+## information to ensure the GNU General Public License requirements will
+## be met: https://www.gnu.org/licenses/gpl-3.0.html.
+##
+## $QT_END_LICENSE$
+##
+#############################################################################
+
+import collections
+import os
+import re
+from itertools import chain
+
+import pyparsing as pp # type: ignore
+
+from helper import _set_up_py_parsing_nicer_debug_output
+_set_up_py_parsing_nicer_debug_output(pp)
+
+
+def fixup_linecontinuation(contents: str) -> str:
+ # Remove all line continuations, aka a backslash followed by
+ # a newline character with an arbitrary amount of whitespace
+ # between the backslash and the newline.
+ # This greatly simplifies the qmake parsing grammar.
+ contents = re.sub(r"([^\t ])\\[ \t]*\n", "\\1 ", contents)
+ contents = re.sub(r"\\[ \t]*\n", "", contents)
+ return contents
+
+
+def fixup_comments(contents: str) -> str:
+ # Get rid of completely commented out lines.
+ # So any line which starts with a '#' char and ends with a new line
+ # will be replaced by a single new line.
+ #
+ # This is needed because qmake syntax is weird. In a multi line
+ # assignment (separated by backslashes and newlines aka
+ # # \\\n ), if any of the lines are completely commented out, in
+ # principle the assignment should fail.
+ #
+ # It should fail because you would have a new line separating
+ # the previous value from the next value, and the next value would
+ # not be interpreted as a value, but as a new token / operation.
+ # qmake is lenient though, and accepts that, so we need to take
+ # care of it as well, as if the commented line didn't exist in the
+ # first place.
+
+ contents = re.sub(r"\n#[^\n]*?\n", "\n", contents, re.DOTALL)
+ return contents
+
+
+def flatten_list(l):
+ """ Flattens an irregular nested list into a simple list."""
+ for el in l:
+ if isinstance(el, collections.abc.Iterable) and not isinstance(el, (str, bytes)):
+ yield from flatten_list(el)
+ else:
+ yield el
+
+
+def handle_function_value(group: pp.ParseResults):
+ function_name = group[0]
+ function_args = group[1]
+ if function_name == "qtLibraryTarget":
+ if len(function_args) > 1:
+ raise RuntimeError(
+ "Don't know what to with more than one function argument "
+ "for $$qtLibraryTarget()."
+ )
+ return str(function_args[0])
+
+ if function_name == "quote":
+ # Do nothing, just return a string result
+ return str(group)
+
+ if function_name == "files":
+ return str(function_args[0])
+
+ if function_name == "basename":
+ if len(function_args) != 1:
+ print(f"XXXX basename with more than one argument")
+ if function_args[0] == '_PRO_FILE_PWD_':
+ return os.path.basename(os.getcwd())
+ print(f"XXXX basename with value other than _PRO_FILE_PWD_")
+ return os.path.basename(str(function_args[0]))
+
+ if isinstance(function_args, pp.ParseResults):
+ function_args = list(flatten_list(function_args.asList()))
+
+ # For other functions, return the whole expression as a string.
+ return f"$${function_name}({' '.join(function_args)})"
+
+
+class QmakeParser:
+ def __init__(self, *, debug: bool = False) -> None:
+ self.debug = debug
+ self._Grammar = self._generate_grammar()
+
+ def _generate_grammar(self):
+ # Define grammar:
+ pp.ParserElement.setDefaultWhitespaceChars(" \t")
+
+ def add_element(name: str, value: pp.ParserElement):
+ nonlocal self
+ if self.debug:
+ value.setName(name)
+ value.setDebug()
+ return value
+
+ EOL = add_element("EOL", pp.Suppress(pp.LineEnd()))
+ Else = add_element("Else", pp.Keyword("else"))
+ Identifier = add_element(
+ "Identifier", pp.Word(f"{pp.alphas}_", bodyChars=pp.alphanums + "_-./")
+ )
+ BracedValue = add_element(
+ "BracedValue",
+ pp.nestedExpr(
+ ignoreExpr=pp.quotedString
+ | pp.QuotedString(
+ quoteChar="$(", endQuoteChar=")", escQuote="\\", unquoteResults=False
+ )
+ ).setParseAction(lambda s, l, t: ["(", *t[0], ")"]),
+ )
+
+ Substitution = add_element(
+ "Substitution",
+ pp.Combine(
+ pp.Literal("$")
+ + (
+ (
+ (pp.Literal("$") + Identifier + pp.Optional(pp.nestedExpr()))
+ | (pp.Literal("(") + Identifier + pp.Literal(")"))
+ | (pp.Literal("{") + Identifier + pp.Literal("}"))
+ | (
+ pp.Literal("$")
+ + pp.Literal("{")
+ + Identifier
+ + pp.Optional(pp.nestedExpr())
+ + pp.Literal("}")
+ )
+ | (pp.Literal("$") + pp.Literal("[") + Identifier + pp.Literal("]"))
+ )
+ )
+ ),
+ )
+ LiteralValuePart = add_element(
+ "LiteralValuePart", pp.Word(pp.printables, excludeChars="$#{}()")
+ )
+ SubstitutionValue = add_element(
+ "SubstitutionValue",
+ pp.Combine(pp.OneOrMore(Substitution | LiteralValuePart | pp.Literal("$"))),
+ )
+ FunctionValue = add_element(
+ "FunctionValue",
+ pp.Group(
+ pp.Suppress(pp.Literal("$") + pp.Literal("$"))
+ + Identifier
+ + pp.nestedExpr() # .setParseAction(lambda s, l, t: ['(', *t[0], ')'])
+ ).setParseAction(lambda s, l, t: handle_function_value(*t)),
+ )
+ Value = add_element(
+ "Value",
+ pp.NotAny(Else | pp.Literal("}") | EOL)
+ + (
+ pp.QuotedString(quoteChar='"', escChar="\\")
+ | FunctionValue
+ | SubstitutionValue
+ | BracedValue
+ ),
+ )
+
+ Values = add_element("Values", pp.ZeroOrMore(Value)("value"))
+
+ Op = add_element(
+ "OP", pp.Literal("=") | pp.Literal("-=") | pp.Literal("+=") | pp.Literal("*=") | pp.Literal("~=")
+ )
+
+ Key = add_element("Key", Identifier)
+
+ Operation = add_element("Operation", Key("key") + Op("operation") + Values("value"))
+ CallArgs = add_element("CallArgs", pp.nestedExpr())
+
+ def parse_call_args(results):
+ out = ""
+ for item in chain(*results):
+ if isinstance(item, str):
+ out += item
+ else:
+ out += "(" + parse_call_args(item) + ")"
+ return out
+
+ CallArgs.setParseAction(parse_call_args)
+
+ Load = add_element("Load", pp.Keyword("load") + CallArgs("loaded"))
+ Include = add_element("Include", pp.Keyword("include") + CallArgs("included"))
+ Option = add_element("Option", pp.Keyword("option") + CallArgs("option"))
+ RequiresCondition = add_element("RequiresCondition", pp.originalTextFor(pp.nestedExpr()))
+
+ def parse_requires_condition(s, l, t):
+ # The following expression unwraps the condition via the additional info
+ # set by originalTextFor.
+ condition_without_parentheses = s[t._original_start + 1 : t._original_end - 1]
+
+ # And this replaces the colons with '&&' similar how it's done for 'Condition'.
+ condition_without_parentheses = (
+ condition_without_parentheses.strip().replace(":", " && ").strip(" && ")
+ )
+ return condition_without_parentheses
+
+ RequiresCondition.setParseAction(parse_requires_condition)
+ Requires = add_element(
+ "Requires", pp.Keyword("requires") + RequiresCondition("project_required_condition")
+ )
+
+ # ignore the whole thing...
+ DefineTestDefinition = add_element(
+ "DefineTestDefinition",
+ pp.Suppress(
+ pp.Keyword("defineTest")
+ + CallArgs
+ + pp.nestedExpr(opener="{", closer="}", ignoreExpr=pp.LineEnd())
+ ),
+ )
+
+ # ignore the whole thing...
+ ForLoop = add_element(
+ "ForLoop",
+ pp.Suppress(
+ pp.Keyword("for")
+ + CallArgs
+ + pp.nestedExpr(opener="{", closer="}", ignoreExpr=pp.LineEnd())
+ ),
+ )
+
+ # ignore the whole thing...
+ ForLoopSingleLine = add_element(
+ "ForLoopSingleLine",
+ pp.Suppress(pp.Keyword("for") + CallArgs + pp.Literal(":") + pp.SkipTo(EOL)),
+ )
+
+ # ignore the whole thing...
+ FunctionCall = add_element("FunctionCall", pp.Suppress(Identifier + pp.nestedExpr()))
+
+ Scope = add_element("Scope", pp.Forward())
+
+ Statement = add_element(
+ "Statement",
+ pp.Group(
+ Load
+ | Include
+ | Option
+ | Requires
+ | ForLoop
+ | ForLoopSingleLine
+ | DefineTestDefinition
+ | FunctionCall
+ | Operation
+ ),
+ )
+ StatementLine = add_element("StatementLine", Statement + (EOL | pp.FollowedBy("}")))
+ StatementGroup = add_element(
+ "StatementGroup", pp.ZeroOrMore(StatementLine | Scope | pp.Suppress(EOL))
+ )
+
+ Block = add_element(
+ "Block",
+ pp.Suppress("{")
+ + pp.Optional(EOL)
+ + StatementGroup
+ + pp.Optional(EOL)
+ + pp.Suppress("}")
+ + pp.Optional(EOL),
+ )
+
+ ConditionEnd = add_element(
+ "ConditionEnd",
+ pp.FollowedBy(
+ (pp.Optional(pp.White()) + (pp.Literal(":") | pp.Literal("{") | pp.Literal("|")))
+ ),
+ )
+
+ ConditionPart1 = add_element(
+ "ConditionPart1", (pp.Optional("!") + Identifier + pp.Optional(BracedValue))
+ )
+ ConditionPart2 = add_element("ConditionPart2", pp.CharsNotIn("#{}|:=\\\n"))
+ ConditionPart = add_element(
+ "ConditionPart", (ConditionPart1 ^ ConditionPart2) + ConditionEnd
+ )
+
+ ConditionOp = add_element("ConditionOp", pp.Literal("|") ^ pp.Literal(":"))
+ ConditionWhiteSpace = add_element(
+ "ConditionWhiteSpace", pp.Suppress(pp.Optional(pp.White(" ")))
+ )
+
+ ConditionRepeated = add_element(
+ "ConditionRepeated", pp.ZeroOrMore(ConditionOp + ConditionWhiteSpace + ConditionPart)
+ )
+
+ Condition = add_element("Condition", pp.Combine(ConditionPart + ConditionRepeated))
+ Condition.setParseAction(lambda x: " ".join(x).strip().replace(":", " && ").strip(" && "))
+
+ # Weird thing like write_file(a)|error() where error() is the alternative condition
+ # which happens to be a function call. In this case there is no scope, but our code expects
+ # a scope with a list of statements, so create a fake empty statement.
+ ConditionEndingInFunctionCall = add_element(
+ "ConditionEndingInFunctionCall",
+ pp.Suppress(ConditionOp)
+ + FunctionCall
+ + pp.Empty().setParseAction(lambda x: [[]]).setResultsName("statements"),
+ )
+
+ SingleLineScope = add_element(
+ "SingleLineScope",
+ pp.Suppress(pp.Literal(":")) + pp.Group(Block | (Statement + EOL))("statements"),
+ )
+ MultiLineScope = add_element("MultiLineScope", Block("statements"))
+
+ SingleLineElse = add_element(
+ "SingleLineElse",
+ pp.Suppress(pp.Literal(":")) + (Scope | Block | (Statement + pp.Optional(EOL))),
+ )
+ MultiLineElse = add_element("MultiLineElse", Block)
+ ElseBranch = add_element("ElseBranch", pp.Suppress(Else) + (SingleLineElse | MultiLineElse))
+
+ # Scope is already add_element'ed in the forward declaration above.
+ Scope <<= pp.Group(
+ Condition("condition")
+ + (SingleLineScope | MultiLineScope | ConditionEndingInFunctionCall)
+ + pp.Optional(ElseBranch)("else_statements")
+ )
+
+ Grammar = StatementGroup("statements")
+ Grammar.ignore(pp.pythonStyleComment())
+
+ return Grammar
+
+ def parseFile(self, file: str):
+ print(f'Parsing "{file}"...')
+ try:
+ with open(file, "r") as file_fd:
+ contents = file_fd.read()
+
+ # old_contents = contents
+ contents = fixup_comments(contents)
+ contents = fixup_linecontinuation(contents)
+ result = self._Grammar.parseString(contents, parseAll=True)
+ except pp.ParseException as pe:
+ print(pe.line)
+ print(f"{' ' * (pe.col-1)}^")
+ print(pe)
+ raise pe
+ return result
+
+
+def parseProFile(file: str, *, debug=False):
+ parser = QmakeParser(debug=debug)
+ return parser.parseFile(file)
diff --git a/util/cmake/tests/test_lc_fixup.py b/util/cmake/tests/test_lc_fixup.py
index 841e11615ea..42094a5288b 100755
--- a/util/cmake/tests/test_lc_fixup.py
+++ b/util/cmake/tests/test_lc_fixup.py
@@ -27,7 +27,7 @@
##
#############################################################################
-from pro2cmake import fixup_linecontinuation
+from qmake_parser import fixup_linecontinuation
def test_no_change():
@@ -42,5 +42,3 @@ def test_fix():
output = "test line2 line3 line4 line5 \n\n"
result = fixup_linecontinuation(input)
assert output == result
-
-
diff --git a/util/cmake/tests/test_parsing.py b/util/cmake/tests/test_parsing.py
index 4019836ae18..95653dc39d8 100755
--- a/util/cmake/tests/test_parsing.py
+++ b/util/cmake/tests/test_parsing.py
@@ -28,7 +28,7 @@
#############################################################################
import os
-from pro2cmake import QmakeParser
+from qmake_parser import QmakeParser
_tests_path = os.path.dirname(os.path.abspath(__file__))