| # SPDX-License-Identifier: Apache-2.0 | |
| # Copyright © 2022-2023 Intel Corporation | |
| """Rust CFG parser. | |
| Rust uses its `cfg()` format in cargo. | |
| https://doc.rust-lang.org/reference/conditional-compilation.html | |
| This may have the following functions: | |
| - all() | |
| - any() | |
| - not() | |
| And additionally is made up of `identifier [ = str]`. Where the str is optional, | |
| so you could have examples like: | |
| ``` | |
| [target.`cfg(unix)`.dependencies] | |
| [target.'cfg(target_arch = "x86_64")'.dependencies] | |
| [target.'cfg(all(target_arch = "x86_64", target_arch = "x86"))'.dependencies] | |
| ``` | |
| """ | |
| from __future__ import annotations | |
| import dataclasses | |
| import enum | |
| import typing as T | |
| from ..mesonlib import MesonBugException, lookahead | |
| if T.TYPE_CHECKING: | |
| _T = T.TypeVar('_T') | |
| _LEX_TOKEN = T.Tuple['TokenType', T.Optional[str]] | |
| _LEX_STREAM = T.Iterator[_LEX_TOKEN] | |
| _LEX_STREAM_AH = T.Iterator[T.Tuple[_LEX_TOKEN, T.Optional[_LEX_TOKEN]]] | |
| class TokenType(enum.Enum): | |
| LPAREN = enum.auto() | |
| RPAREN = enum.auto() | |
| STRING = enum.auto() | |
| IDENTIFIER = enum.auto() | |
| ALL = enum.auto() | |
| ANY = enum.auto() | |
| NOT = enum.auto() | |
| COMMA = enum.auto() | |
| EQUAL = enum.auto() | |
| CFG = enum.auto() | |
| def lexer(raw: str) -> _LEX_STREAM: | |
| """Lex a cfg() expression. | |
| :param raw: The raw cfg() expression | |
| :return: An iterable of tokens | |
| """ | |
| start: int = 0 | |
| is_string: bool = False | |
| for i, s in enumerate(raw): | |
| if s.isspace() or s in {')', '(', ',', '=', '"'}: | |
| val = raw[start:i] | |
| start = i + 1 | |
| if s == '"' and is_string: | |
| yield (TokenType.STRING, val) | |
| is_string = False | |
| continue | |
| elif val == 'any': | |
| yield (TokenType.ANY, None) | |
| elif val == 'all': | |
| yield (TokenType.ALL, None) | |
| elif val == 'not': | |
| yield (TokenType.NOT, None) | |
| elif val == 'cfg': | |
| yield (TokenType.CFG, None) | |
| elif val: | |
| yield (TokenType.IDENTIFIER, val) | |
| if s == '(': | |
| yield (TokenType.LPAREN, None) | |
| elif s == ')': | |
| yield (TokenType.RPAREN, None) | |
| elif s == ',': | |
| yield (TokenType.COMMA, None) | |
| elif s == '=': | |
| yield (TokenType.EQUAL, None) | |
| elif s == '"': | |
| is_string = True | |
| val = raw[start:] | |
| if val: | |
| # This should always be an identifier | |
| yield (TokenType.IDENTIFIER, val) | |
| @dataclasses.dataclass | |
| class IR: | |
| """Base IR node for Cargo CFG.""" | |
| @dataclasses.dataclass | |
| class String(IR): | |
| value: str | |
| @dataclasses.dataclass | |
| class Identifier(IR): | |
| value: str | |
| @dataclasses.dataclass | |
| class Equal(IR): | |
| lhs: Identifier | |
| rhs: String | |
| @dataclasses.dataclass | |
| class Any(IR): | |
| args: T.List[IR] | |
| @dataclasses.dataclass | |
| class All(IR): | |
| args: T.List[IR] | |
| @dataclasses.dataclass | |
| class Not(IR): | |
| value: IR | |
| def _parse(ast: _LEX_STREAM_AH) -> IR: | |
| (token, value), n_stream = next(ast) | |
| if n_stream is not None: | |
| ntoken, _ = n_stream | |
| else: | |
| ntoken, _ = (None, None) | |
| if token is TokenType.IDENTIFIER: | |
| assert value | |
| id_ = Identifier(value) | |
| if ntoken is TokenType.EQUAL: | |
| next(ast) | |
| (token, value), _ = next(ast) | |
| assert token is TokenType.STRING | |
| assert value is not None | |
| return Equal(id_, String(value)) | |
| return id_ | |
| elif token in {TokenType.ANY, TokenType.ALL}: | |
| type_ = All if token is TokenType.ALL else Any | |
| args: T.List[IR] = [] | |
| (token, value), n_stream = next(ast) | |
| assert token is TokenType.LPAREN | |
| if n_stream and n_stream[0] == TokenType.RPAREN: | |
| return type_(args) | |
| while True: | |
| args.append(_parse(ast)) | |
| (token, value), _ = next(ast) | |
| if token is TokenType.RPAREN: | |
| break | |
| assert token is TokenType.COMMA | |
| return type_(args) | |
| elif token in {TokenType.NOT, TokenType.CFG}: | |
| is_not = token is TokenType.NOT | |
| (token, value), _ = next(ast) | |
| assert token is TokenType.LPAREN | |
| arg = _parse(ast) | |
| (token, value), _ = next(ast) | |
| assert token is TokenType.RPAREN | |
| return Not(arg) if is_not else arg | |
| else: | |
| raise MesonBugException(f'Unhandled Cargo token:{token} {value}') | |
| def parse(ast: _LEX_STREAM) -> IR: | |
| """Parse the tokenized list into Meson AST. | |
| :param ast: An iterable of Tokens | |
| :return: An mparser Node to be used as a conditional | |
| """ | |
| ast_i: _LEX_STREAM_AH = lookahead(ast) | |
| return _parse(ast_i) | |
| def _eval_cfg(ir: IR, cfgs: T.Dict[str, str]) -> bool: | |
| if isinstance(ir, Identifier): | |
| return ir.value in cfgs | |
| elif isinstance(ir, Equal): | |
| return cfgs.get(ir.lhs.value) == ir.rhs.value | |
| elif isinstance(ir, Not): | |
| return not _eval_cfg(ir.value, cfgs) | |
| elif isinstance(ir, Any): | |
| return any(_eval_cfg(i, cfgs) for i in ir.args) | |
| elif isinstance(ir, All): | |
| return all(_eval_cfg(i, cfgs) for i in ir.args) | |
| else: | |
| raise MesonBugException(f'Unhandled Cargo cfg IR: {ir}') | |
| def eval_cfg(raw: str, cfgs: T.Dict[str, str]) -> bool: | |
| return _eval_cfg(parse(lexer(raw)), cfgs) |