1# Copyright 2019 The Meson development team
2
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6
7#     http://www.apache.org/licenses/LICENSE-2.0
8
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15from .. import mesonlib
16import typing as T
17
18def parse_generator_expressions(raw: str) -> str:
19    '''Parse CMake generator expressions
20
21    Most generator expressions are simply ignored for
22    simplicety, however some are required for some common
23    use cases.
24    '''
25
26    # Early abort if no generator expression present
27    if '$<' not in raw:
28        return raw
29
30    out = ''  # type: str
31    i = 0     # type: int
32
33    def equal(arg: str) -> str:
34        col_pos = arg.find(',')
35        if col_pos < 0:
36            return '0'
37        else:
38            return '1' if arg[:col_pos] == arg[col_pos + 1:] else '0'
39
40    def vers_comp(op: str, arg: str) -> str:
41        col_pos = arg.find(',')
42        if col_pos < 0:
43            return '0'
44        else:
45            return '1' if mesonlib.version_compare(arg[:col_pos], '{}{}'.format(op, arg[col_pos + 1:])) else '0'
46
47    supported = {
48        # Boolean functions
49        'BOOL': lambda x: '0' if x.upper() in ['0', 'FALSE', 'OFF', 'N', 'NO', 'IGNORE', 'NOTFOUND'] or x.endswith('-NOTFOUND') else '1',
50        'AND': lambda x: '1' if all([y == '1' for y in x.split(',')]) else '0',
51        'OR': lambda x: '1' if any([y == '1' for y in x.split(',')]) else '0',
52        'NOT': lambda x: '0' if x == '1' else '1',
53
54        '0': lambda x: '',
55        '1': lambda x: x,
56
57        # String operations
58        'STREQUAL': equal,
59        'EQUAL': equal,
60        'VERSION_LESS': lambda x: vers_comp('<', x),
61        'VERSION_GREATER': lambda x: vers_comp('>', x),
62        'VERSION_EQUAL': lambda x: vers_comp('=', x),
63        'VERSION_LESS_EQUAL': lambda x: vers_comp('<=', x),
64        'VERSION_GREATER_EQUAL': lambda x: vers_comp('>=', x),
65
66        # String modification
67        'LOWER_CASE': lambda x: x.lower(),
68        'UPPER_CASE': lambda x: x.upper(),
69
70        # Always assume the BUILD_INTERFACE is valid.
71        # INSTALL_INTERFACE is always invalid for subprojects and
72        # it should also never appear in CMake config files, used
73        # for dependencies
74        'INSTALL_INTERFACE': lambda x: '',
75        'BUILD_INTERFACE': lambda x: x,
76
77        # Constants
78        'ANGLE-R': lambda x: '>',
79        'COMMA': lambda x: ',',
80        'SEMICOLON': lambda x: ';',
81    }  # type: T.Dict[str, T.Callable[[str], str]]
82
83    # Recursively evaluate generator expressions
84    def eval_generator_expressions() -> str:
85        nonlocal i
86        i += 2
87
88        func = ''  # type: str
89        args = ''  # type: str
90        res = ''   # type: str
91        exp = ''   # type: str
92
93        # Determine the body of the expression
94        while i < len(raw):
95            if raw[i] == '>':
96                # End of the generator expression
97                break
98            elif i < len(raw) - 1 and raw[i] == '$' and raw[i + 1] == '<':
99                # Nested generator expression
100                exp += eval_generator_expressions()
101            else:
102                # Generator expression body
103                exp += raw[i]
104
105            i += 1
106
107        # Split the expression into a function and arguments part
108        col_pos = exp.find(':')
109        if col_pos < 0:
110            func = exp
111        else:
112            func = exp[:col_pos]
113            args = exp[col_pos + 1:]
114
115        func = func.strip()
116        args = args.strip()
117
118        # Evaluate the function
119        if func in supported:
120            res = supported[func](args)
121
122        return res
123
124    while i < len(raw):
125        if i < len(raw) - 1 and raw[i] == '$' and raw[i + 1] == '<':
126            # Generator expression detected --> try resolving it
127            out += eval_generator_expressions()
128        else:
129            # Normal string, leave unchanged
130            out += raw[i]
131
132        i += 1
133
134    return out
135