diff options
author | Joerg Bornemann <[email protected]> | 2025-07-13 22:08:56 +0200 |
---|---|---|
committer | Joerg Bornemann <[email protected]> | 2025-07-25 14:07:49 +0200 |
commit | ecda5bb8c1b043331c3a8b569c5a9cfb91cda0aa (patch) | |
tree | 15bfd90a4f0763f02bf7a24e787275f644e05ec2 | |
parent | 6c5a692c9c5760fb3d14a2f6acf9c5ab0c34ed61 (diff) |
In QMake, literal values can contain curly braces:
A = foo{bar}
Assignments can also have multiple opening curly braces and the same
amount or less closing curly braces:
A = { { { }
The following is invalid in QMake:
A = { } }
A matching curly brace in an assignment doesn't close the current scope:
true {
A = { foo }
}
Fix the parser grammar to allow the cases above. In order to do that we
had to:
- allow curly braces in LiteralValuePart
- add an alternative to Value to allow {} being parsed as one string
- add an alternative to Value to allow values that are surrounded by curly
braces
Caveats: The scope handling is still a bit wonky. For instance, we don't
support
A = else
which certainly is an edge case.
Task-number: QTBUG-123132
Fixes: QTBUG-137322
Change-Id: I3aab8075ccbac6161dfc4431260f21392eeaf652
Reviewed-by: Alexandru Croitor <[email protected]>
-rwxr-xr-x | src/qmake2cmake/qmake_parser.py | 9 | ||||
-rw-r--r-- | tests/data/assignment_with_braces.pro | 9 | ||||
-rwxr-xr-x | tests/test_parsing.py | 18 |
3 files changed, 34 insertions, 2 deletions
diff --git a/src/qmake2cmake/qmake_parser.py b/src/qmake2cmake/qmake_parser.py index 1119576..cdbcda9 100755 --- a/src/qmake2cmake/qmake_parser.py +++ b/src/qmake2cmake/qmake_parser.py @@ -151,7 +151,7 @@ class QmakeParser: LiteralValuePart = add_element( "LiteralValuePart", - pp.Word(pp.unicode.printables, excludeChars="$#{}()").setParseAction( + pp.Word(pp.unicode.printables, excludeChars="$#()").setParseAction( process_escape_sequences ), ) @@ -167,19 +167,24 @@ class QmakeParser: + pp.nestedExpr() # .setParseAction(lambda s, l, t: ['(', *t[0], ')']) ).setParseAction(lambda s, loc, t: handle_function_value(*t)), ) + + Values = pp.Forward() + Value = add_element( "Value", pp.NotAny(Else | pp.Literal("}") | EOL) + ( pp.QuotedString(quoteChar='"', escChar="\\") | pp.QuotedString(quoteChar="'", escChar="\\") + | pp.Literal("{}") + | (pp.Literal("{") + Values + pp.Literal("}")) | FunctionValue | SubstitutionValue | BracedValue ), ) - Values = add_element("Values", pp.ZeroOrMore(Value)("value")) + Values <<= add_element("Values", pp.ZeroOrMore(Value)("value")) Op = add_element( "OP", diff --git a/tests/data/assignment_with_braces.pro b/tests/data/assignment_with_braces.pro new file mode 100644 index 0000000..c9712a8 --- /dev/null +++ b/tests/data/assignment_with_braces.pro @@ -0,0 +1,9 @@ +A = {} foo{bar} +B = { } +C = { { { } +true { + D = { foo } + E = { one { two } three } + F = zero { one { two } three } four +} +G = foo{bar} diff --git a/tests/test_parsing.py b/tests/test_parsing.py index be188a4..3668ad5 100755 --- a/tests/test_parsing.py +++ b/tests/test_parsing.py @@ -358,3 +358,21 @@ def test_condition_operator_precedence(): validate_simplify(result[0]["condition"], "a1 OR a2") validate_simplify(result[1]["condition"], "b3 AND (b1 OR b2)") validate_simplify(result[2]["condition"], "c4 OR (c1 AND c3) OR (c2 AND c3)") + + +def test_assignment_with_braces(): + result = parse_file(_tests_path + '/data/assignment_with_braces.pro') + assert len(result) == 5 + + validate_op('A', '=', ['{}', 'foo{bar}'], result[0]) + validate_op('B', '=', ['{', '}'], result[1]) + validate_op('C', '=', ['{', '{', '{', '}'], result[2]) + + assert 'condition' in result[3] + assert result[3]['condition'] == 'true' + true_statements = result[3]['statements'] + assert len(true_statements) == 3 + + validate_op('D', '=', ['{', 'foo', '}'], true_statements[0]) + validate_op('E', '=', ['{', 'one', '{', 'two', '}', 'three', '}'], true_statements[1]) + validate_op('F', '=', ['zero', '{', 'one', '{', 'two', '}', 'three', '}', 'four'], true_statements[2]) |