|
| 1 | +# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD |
| 2 | +# SPDX-License-Identifier: Apache-2.0 |
| 3 | +import operator |
| 4 | +import os |
| 5 | +from ast import ( |
| 6 | + literal_eval, |
| 7 | +) |
| 8 | +from typing import ( |
| 9 | + Any, |
| 10 | +) |
| 11 | + |
| 12 | +from packaging.version import ( |
| 13 | + Version, |
| 14 | +) |
| 15 | +from pyparsing import ( |
| 16 | + Keyword, |
| 17 | + Literal, |
| 18 | + MatchFirst, |
| 19 | + ParseResults, |
| 20 | + QuotedString, |
| 21 | + Suppress, |
| 22 | + Word, |
| 23 | + alphas, |
| 24 | + delimitedList, |
| 25 | + hexnums, |
| 26 | + infixNotation, |
| 27 | + nums, |
| 28 | + opAssoc, |
| 29 | +) |
| 30 | + |
| 31 | +from .constants import ( |
| 32 | + IDF_VERSION, |
| 33 | + IDF_VERSION_MAJOR, |
| 34 | + IDF_VERSION_MINOR, |
| 35 | + IDF_VERSION_PATCH, |
| 36 | +) |
| 37 | +from utils import ( |
| 38 | + InvalidIfClause, |
| 39 | + InvalidInput, |
| 40 | + to_version, |
| 41 | +) |
| 42 | +from soc_header import ( |
| 43 | + SOC_HEADERS, |
| 44 | +) |
| 45 | + |
| 46 | + |
| 47 | +class Stmt: |
| 48 | + """ |
| 49 | + Statement |
| 50 | + """ |
| 51 | + |
| 52 | + def get_value(self, target: str, config_name: str) -> Any: |
| 53 | + """ |
| 54 | + Lazy calculated. All subclasses of `Stmt` should implement this function. |
| 55 | +
|
| 56 | + :param target: ESP-IDF target |
| 57 | + :type target: str |
| 58 | + :param config_name: config name |
| 59 | + :type target: str |
| 60 | + :return: the value of the statement |
| 61 | + """ |
| 62 | + raise NotImplementedError('Please implement this function in sub classes') |
| 63 | + |
| 64 | + |
| 65 | +class ChipAttr(Stmt): |
| 66 | + """ |
| 67 | + Attributes defined in SOC Header Files and other keywords as followed: |
| 68 | +
|
| 69 | + - IDF_TARGET: target |
| 70 | + - INCLUDE_DEFAULT: take the default build targets into account or not |
| 71 | + - IDF_VERSION_MAJOR: major version of ESP-IDF |
| 72 | + - IDF_VERSION_MINOR: minor version of ESP-IDF |
| 73 | + - IDF_VERSION_PATCH: patch version of ESP-IDF |
| 74 | + - CONFIG_NAME: config name defined in the config rules |
| 75 | + """ |
| 76 | + |
| 77 | + def __init__(self, t: ParseResults): |
| 78 | + self.attr: str = t[0] |
| 79 | + |
| 80 | + def get_value(self, target: str, config_name: str) -> Any: |
| 81 | + from .manifest import FolderRule # lazy-load |
| 82 | + |
| 83 | + if self.attr == 'IDF_TARGET': |
| 84 | + return target |
| 85 | + |
| 86 | + if self.attr == 'INCLUDE_DEFAULT': |
| 87 | + return 1 if target in FolderRule.DEFAULT_BUILD_TARGETS else 0 |
| 88 | + |
| 89 | + if self.attr == 'IDF_VERSION': |
| 90 | + return IDF_VERSION |
| 91 | + |
| 92 | + if self.attr == 'IDF_VERSION_MAJOR': |
| 93 | + return IDF_VERSION_MAJOR |
| 94 | + |
| 95 | + if self.attr == 'IDF_VERSION_MINOR': |
| 96 | + return IDF_VERSION_MINOR |
| 97 | + |
| 98 | + if self.attr == 'IDF_VERSION_PATCH': |
| 99 | + return IDF_VERSION_PATCH |
| 100 | + |
| 101 | + if self.attr == 'CONFIG_NAME': |
| 102 | + return config_name |
| 103 | + |
| 104 | + if self.attr in SOC_HEADERS[target]: |
| 105 | + return SOC_HEADERS[target][self.attr] |
| 106 | + |
| 107 | + # for non-keyword cap words, check if it is defined in the environment variables |
| 108 | + if self.attr in os.environ: |
| 109 | + return os.environ[self.attr] |
| 110 | + |
| 111 | + return 0 # default return 0 as false |
| 112 | + |
| 113 | + |
| 114 | +class Integer(Stmt): |
| 115 | + def __init__(self, t: ParseResults): |
| 116 | + self.expr: str = t[0] |
| 117 | + |
| 118 | + def get_value(self, target: str, config_name: str) -> Any: # noqa: ARG002 |
| 119 | + return literal_eval(self.expr) |
| 120 | + |
| 121 | + |
| 122 | +class String(Stmt): |
| 123 | + def __init__(self, t: ParseResults): |
| 124 | + self.expr: str = t[0] |
| 125 | + |
| 126 | + def get_value(self, target: str, config_name: str) -> Any: # noqa: ARG002 |
| 127 | + return literal_eval(f'"{self.expr}"') # double quotes is swallowed by QuotedString |
| 128 | + |
| 129 | + |
| 130 | +class List_(Stmt): |
| 131 | + def __init__(self, t: ParseResults): |
| 132 | + self.expr = t |
| 133 | + |
| 134 | + def get_value(self, target: str, config_name: str) -> Any: |
| 135 | + return [item.get_value(target, config_name) for item in self.expr] |
| 136 | + |
| 137 | + |
| 138 | +class BoolStmt(Stmt): |
| 139 | + _OP_DICT = { |
| 140 | + '==': operator.eq, |
| 141 | + '!=': operator.ne, |
| 142 | + '>': operator.gt, |
| 143 | + '>=': operator.ge, |
| 144 | + '<': operator.lt, |
| 145 | + '<=': operator.le, |
| 146 | + 'not in': lambda x, y: x not in y, |
| 147 | + 'in': lambda x, y: x in y, |
| 148 | + } |
| 149 | + |
| 150 | + def __init__(self, t: ParseResults): |
| 151 | + self.left: Stmt = t[0] |
| 152 | + self.comparison: str = t[1] |
| 153 | + self.right: Stmt = t[2] |
| 154 | + |
| 155 | + def get_value(self, target: str, config_name: str) -> Any: |
| 156 | + _l = self.left.get_value(target, config_name) |
| 157 | + _r = self.right.get_value(target, config_name) |
| 158 | + |
| 159 | + if self.comparison not in ['in', 'not in']: |
| 160 | + # will use version comparison if any of the operands is a Version |
| 161 | + if any(isinstance(x, Version) for x in [_l, _r]): |
| 162 | + _l = to_version(_l) |
| 163 | + _r = to_version(_r) |
| 164 | + else: |
| 165 | + # use str for "in" and "not in" operator |
| 166 | + if isinstance(_l, Version): |
| 167 | + _l = str(_l) |
| 168 | + if isinstance(_r, Version): |
| 169 | + _r = str(_r) |
| 170 | + |
| 171 | + if self.comparison in self._OP_DICT: |
| 172 | + return self._OP_DICT[self.comparison](_l, _r) |
| 173 | + |
| 174 | + raise InvalidInput(f'Unsupported comparison operator: "{self.comparison}"') |
| 175 | + |
| 176 | + |
| 177 | +class BoolExpr(Stmt): |
| 178 | + pass |
| 179 | + |
| 180 | + |
| 181 | +def _and(_l, _r): |
| 182 | + return _l and _r |
| 183 | + |
| 184 | + |
| 185 | +def _or(_l, _r): |
| 186 | + return _l or _r |
| 187 | + |
| 188 | + |
| 189 | +class BoolOrAnd(BoolExpr): |
| 190 | + def __init__(self, t: ParseResults): |
| 191 | + if len(t[0]) > 3: |
| 192 | + raise InvalidIfClause( |
| 193 | + 'Chaining "and"/"or" is not allowed. Please use paratheses instead. ' |
| 194 | + 'For example: "a and b and c" should be "(a and b) and c".' |
| 195 | + ) |
| 196 | + self.left: BoolStmt = t[0][0] |
| 197 | + self.right: BoolStmt = t[0][2] |
| 198 | + |
| 199 | + if t[0][1] == 'and': |
| 200 | + self.operation = _and |
| 201 | + if t[0][1] == 'or': |
| 202 | + self.operation = _or |
| 203 | + |
| 204 | + def get_value(self, target: str, config_name: str) -> Any: |
| 205 | + return self.operation(self.left.get_value(target, config_name), self.right.get_value(target, config_name)) |
| 206 | + |
| 207 | + |
| 208 | +CAP_WORD = Word(alphas.upper(), nums + alphas.upper() + '_').setParseAction(ChipAttr) |
| 209 | + |
| 210 | +DECIMAL_NUMBER = Word(nums) |
| 211 | +HEX_NUMBER = Literal('0x') + Word(hexnums) |
| 212 | +INTEGER = (HEX_NUMBER | DECIMAL_NUMBER).setParseAction(Integer) |
| 213 | + |
| 214 | +STRING = QuotedString('"').setParseAction(String) |
| 215 | + |
| 216 | +LIST = Suppress('[') + delimitedList(INTEGER | STRING).setParseAction(List_) + Suppress(']') |
| 217 | + |
| 218 | +BOOL_OPERAND = CAP_WORD | INTEGER | STRING | LIST |
| 219 | + |
| 220 | +EQ = Keyword('==').setParseAction(lambda t: t[0]) |
| 221 | +NE = Keyword('!=').setParseAction(lambda t: t[0]) |
| 222 | +LE = Keyword('<=').setParseAction(lambda t: t[0]) |
| 223 | +LT = Keyword('<').setParseAction(lambda t: t[0]) |
| 224 | +GE = Keyword('>=').setParseAction(lambda t: t[0]) |
| 225 | +GT = Keyword('>').setParseAction(lambda t: t[0]) |
| 226 | +NOT_IN = Keyword('not in').setParseAction(lambda t: t[0]) |
| 227 | +IN = Keyword('in').setParseAction(lambda t: t[0]) |
| 228 | + |
| 229 | +BOOL_STMT = BOOL_OPERAND + (EQ | NE | LE | LT | GE | GT | NOT_IN | IN) + BOOL_OPERAND |
| 230 | +BOOL_STMT.setParseAction(BoolStmt) |
| 231 | + |
| 232 | +AND = Keyword('and') |
| 233 | +OR = Keyword('or') |
| 234 | + |
| 235 | +BOOL_EXPR = infixNotation( |
| 236 | + BOOL_STMT, |
| 237 | + [ |
| 238 | + (MatchFirst((AND, OR)), 2, opAssoc.LEFT, BoolOrAnd), |
| 239 | + ], |
| 240 | +) |
0 commit comments