diff options
Diffstat (limited to 'mesonbuild/interpreter/primitives')
-rw-r--r-- | mesonbuild/interpreter/primitives/__init__.py | 29 | ||||
-rw-r--r-- | mesonbuild/interpreter/primitives/array.py | 108 | ||||
-rw-r--r-- | mesonbuild/interpreter/primitives/boolean.py | 52 | ||||
-rw-r--r-- | mesonbuild/interpreter/primitives/dict.py | 88 | ||||
-rw-r--r-- | mesonbuild/interpreter/primitives/integer.py | 81 | ||||
-rw-r--r-- | mesonbuild/interpreter/primitives/range.py | 38 | ||||
-rw-r--r-- | mesonbuild/interpreter/primitives/string.py | 233 |
7 files changed, 629 insertions, 0 deletions
diff --git a/mesonbuild/interpreter/primitives/__init__.py b/mesonbuild/interpreter/primitives/__init__.py new file mode 100644 index 0000000..aebef41 --- /dev/null +++ b/mesonbuild/interpreter/primitives/__init__.py @@ -0,0 +1,29 @@ +# Copyright 2021 The Meson development team +# SPDX-license-identifier: Apache-2.0 + +__all__ = [ + 'ArrayHolder', + 'BooleanHolder', + 'DictHolder', + 'IntegerHolder', + 'RangeHolder', + 'StringHolder', + 'MesonVersionString', + 'MesonVersionStringHolder', + 'DependencyVariableString', + 'DependencyVariableStringHolder', + 'OptionString', + 'OptionStringHolder', +] + +from .array import ArrayHolder +from .boolean import BooleanHolder +from .dict import DictHolder +from .integer import IntegerHolder +from .range import RangeHolder +from .string import ( + StringHolder, + MesonVersionString, MesonVersionStringHolder, + DependencyVariableString, DependencyVariableStringHolder, + OptionString, OptionStringHolder, +) diff --git a/mesonbuild/interpreter/primitives/array.py b/mesonbuild/interpreter/primitives/array.py new file mode 100644 index 0000000..eeea112 --- /dev/null +++ b/mesonbuild/interpreter/primitives/array.py @@ -0,0 +1,108 @@ +# Copyright 2021 The Meson development team +# SPDX-license-identifier: Apache-2.0 +from __future__ import annotations + +import typing as T + +from ...interpreterbase import ( + ObjectHolder, + IterableObject, + MesonOperator, + typed_operator, + noKwargs, + noPosargs, + noArgsFlattening, + typed_pos_args, + FeatureNew, + + TYPE_var, + + InvalidArguments, +) +from ...mparser import PlusAssignmentNode + +if T.TYPE_CHECKING: + # Object holders need the actual interpreter + from ...interpreter import Interpreter + from ...interpreterbase import TYPE_kwargs + +class ArrayHolder(ObjectHolder[T.List[TYPE_var]], IterableObject): + def __init__(self, obj: T.List[TYPE_var], interpreter: 'Interpreter') -> None: + super().__init__(obj, interpreter) + self.methods.update({ + 'contains': self.contains_method, + 'length': self.length_method, + 'get': self.get_method, + }) + + self.trivial_operators.update({ + MesonOperator.EQUALS: (list, lambda x: self.held_object == x), + MesonOperator.NOT_EQUALS: (list, lambda x: self.held_object != x), + MesonOperator.IN: (object, lambda x: x in self.held_object), + MesonOperator.NOT_IN: (object, lambda x: x not in self.held_object), + }) + + # Use actual methods for functions that require additional checks + self.operators.update({ + MesonOperator.PLUS: self.op_plus, + MesonOperator.INDEX: self.op_index, + }) + + def display_name(self) -> str: + return 'array' + + def iter_tuple_size(self) -> None: + return None + + def iter_self(self) -> T.Iterator[TYPE_var]: + return iter(self.held_object) + + def size(self) -> int: + return len(self.held_object) + + @noArgsFlattening + @noKwargs + @typed_pos_args('array.contains', object) + def contains_method(self, args: T.Tuple[object], kwargs: TYPE_kwargs) -> bool: + def check_contains(el: T.List[TYPE_var]) -> bool: + for element in el: + if isinstance(element, list): + found = check_contains(element) + if found: + return True + if element == args[0]: + return True + return False + return check_contains(self.held_object) + + @noKwargs + @noPosargs + def length_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> int: + return len(self.held_object) + + @noArgsFlattening + @noKwargs + @typed_pos_args('array.get', int, optargs=[object]) + def get_method(self, args: T.Tuple[int, T.Optional[TYPE_var]], kwargs: TYPE_kwargs) -> TYPE_var: + index = args[0] + if index < -len(self.held_object) or index >= len(self.held_object): + if args[1] is None: + raise InvalidArguments(f'Array index {index} is out of bounds for array of size {len(self.held_object)}.') + return args[1] + return self.held_object[index] + + @typed_operator(MesonOperator.PLUS, object) + def op_plus(self, other: TYPE_var) -> T.List[TYPE_var]: + if not isinstance(other, list): + if not isinstance(self.current_node, PlusAssignmentNode): + FeatureNew.single_use('list.<plus>', '0.60.0', self.subproject, 'The right hand operand was not a list.', + location=self.current_node) + other = [other] + return self.held_object + other + + @typed_operator(MesonOperator.INDEX, int) + def op_index(self, other: int) -> TYPE_var: + try: + return self.held_object[other] + except IndexError: + raise InvalidArguments(f'Index {other} out of bounds of array of size {len(self.held_object)}.') diff --git a/mesonbuild/interpreter/primitives/boolean.py b/mesonbuild/interpreter/primitives/boolean.py new file mode 100644 index 0000000..4b49caf --- /dev/null +++ b/mesonbuild/interpreter/primitives/boolean.py @@ -0,0 +1,52 @@ +# Copyright 2021 The Meson development team +# SPDX-license-identifier: Apache-2.0 +from __future__ import annotations + +from ...interpreterbase import ( + ObjectHolder, + MesonOperator, + typed_pos_args, + noKwargs, + noPosargs, + + InvalidArguments +) + +import typing as T + +if T.TYPE_CHECKING: + # Object holders need the actual interpreter + from ...interpreter import Interpreter + from ...interpreterbase import TYPE_var, TYPE_kwargs + +class BooleanHolder(ObjectHolder[bool]): + def __init__(self, obj: bool, interpreter: 'Interpreter') -> None: + super().__init__(obj, interpreter) + self.methods.update({ + 'to_int': self.to_int_method, + 'to_string': self.to_string_method, + }) + + self.trivial_operators.update({ + MesonOperator.BOOL: (None, lambda x: self.held_object), + MesonOperator.NOT: (None, lambda x: not self.held_object), + MesonOperator.EQUALS: (bool, lambda x: self.held_object == x), + MesonOperator.NOT_EQUALS: (bool, lambda x: self.held_object != x), + }) + + def display_name(self) -> str: + return 'bool' + + @noKwargs + @noPosargs + def to_int_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> int: + return 1 if self.held_object else 0 + + @noKwargs + @typed_pos_args('bool.to_string', optargs=[str, str]) + def to_string_method(self, args: T.Tuple[T.Optional[str], T.Optional[str]], kwargs: TYPE_kwargs) -> str: + true_str = args[0] or 'true' + false_str = args[1] or 'false' + if any(x is not None for x in args) and not all(x is not None for x in args): + raise InvalidArguments('bool.to_string() must have either no arguments or exactly two string arguments that signify what values to return for true and false.') + return true_str if self.held_object else false_str diff --git a/mesonbuild/interpreter/primitives/dict.py b/mesonbuild/interpreter/primitives/dict.py new file mode 100644 index 0000000..ac7c99b --- /dev/null +++ b/mesonbuild/interpreter/primitives/dict.py @@ -0,0 +1,88 @@ +# Copyright 2021 The Meson development team +# SPDX-license-identifier: Apache-2.0 +from __future__ import annotations + +import typing as T + +from ...interpreterbase import ( + ObjectHolder, + IterableObject, + MesonOperator, + typed_operator, + noKwargs, + noPosargs, + noArgsFlattening, + typed_pos_args, + + TYPE_var, + + InvalidArguments, +) + +if T.TYPE_CHECKING: + # Object holders need the actual interpreter + from ...interpreter import Interpreter + from ...interpreterbase import TYPE_kwargs + +class DictHolder(ObjectHolder[T.Dict[str, TYPE_var]], IterableObject): + def __init__(self, obj: T.Dict[str, TYPE_var], interpreter: 'Interpreter') -> None: + super().__init__(obj, interpreter) + self.methods.update({ + 'has_key': self.has_key_method, + 'keys': self.keys_method, + 'get': self.get_method, + }) + + self.trivial_operators.update({ + # Arithmetic + MesonOperator.PLUS: (dict, lambda x: {**self.held_object, **x}), + + # Comparison + MesonOperator.EQUALS: (dict, lambda x: self.held_object == x), + MesonOperator.NOT_EQUALS: (dict, lambda x: self.held_object != x), + MesonOperator.IN: (str, lambda x: x in self.held_object), + MesonOperator.NOT_IN: (str, lambda x: x not in self.held_object), + }) + + # Use actual methods for functions that require additional checks + self.operators.update({ + MesonOperator.INDEX: self.op_index, + }) + + def display_name(self) -> str: + return 'dict' + + def iter_tuple_size(self) -> int: + return 2 + + def iter_self(self) -> T.Iterator[T.Tuple[str, TYPE_var]]: + return iter(self.held_object.items()) + + def size(self) -> int: + return len(self.held_object) + + @noKwargs + @typed_pos_args('dict.has_key', str) + def has_key_method(self, args: T.Tuple[str], kwargs: TYPE_kwargs) -> bool: + return args[0] in self.held_object + + @noKwargs + @noPosargs + def keys_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.List[str]: + return sorted(self.held_object) + + @noArgsFlattening + @noKwargs + @typed_pos_args('dict.get', str, optargs=[object]) + def get_method(self, args: T.Tuple[str, T.Optional[TYPE_var]], kwargs: TYPE_kwargs) -> TYPE_var: + if args[0] in self.held_object: + return self.held_object[args[0]] + if args[1] is not None: + return args[1] + raise InvalidArguments(f'Key {args[0]!r} is not in the dictionary.') + + @typed_operator(MesonOperator.INDEX, str) + def op_index(self, other: str) -> TYPE_var: + if other not in self.held_object: + raise InvalidArguments(f'Key {other} is not in the dictionary.') + return self.held_object[other] diff --git a/mesonbuild/interpreter/primitives/integer.py b/mesonbuild/interpreter/primitives/integer.py new file mode 100644 index 0000000..f433f57 --- /dev/null +++ b/mesonbuild/interpreter/primitives/integer.py @@ -0,0 +1,81 @@ +# Copyright 2021 The Meson development team +# SPDX-license-identifier: Apache-2.0 +from __future__ import annotations + +from ...interpreterbase import ( + ObjectHolder, + MesonOperator, + typed_operator, + noKwargs, + noPosargs, + + InvalidArguments +) + +import typing as T + +if T.TYPE_CHECKING: + # Object holders need the actual interpreter + from ...interpreter import Interpreter + from ...interpreterbase import TYPE_var, TYPE_kwargs + +class IntegerHolder(ObjectHolder[int]): + def __init__(self, obj: int, interpreter: 'Interpreter') -> None: + super().__init__(obj, interpreter) + self.methods.update({ + 'is_even': self.is_even_method, + 'is_odd': self.is_odd_method, + 'to_string': self.to_string_method, + }) + + self.trivial_operators.update({ + # Arithmetic + MesonOperator.UMINUS: (None, lambda x: -self.held_object), + MesonOperator.PLUS: (int, lambda x: self.held_object + x), + MesonOperator.MINUS: (int, lambda x: self.held_object - x), + MesonOperator.TIMES: (int, lambda x: self.held_object * x), + + # Comparison + MesonOperator.EQUALS: (int, lambda x: self.held_object == x), + MesonOperator.NOT_EQUALS: (int, lambda x: self.held_object != x), + MesonOperator.GREATER: (int, lambda x: self.held_object > x), + MesonOperator.LESS: (int, lambda x: self.held_object < x), + MesonOperator.GREATER_EQUALS: (int, lambda x: self.held_object >= x), + MesonOperator.LESS_EQUALS: (int, lambda x: self.held_object <= x), + }) + + # Use actual methods for functions that require additional checks + self.operators.update({ + MesonOperator.DIV: self.op_div, + MesonOperator.MOD: self.op_mod, + }) + + def display_name(self) -> str: + return 'int' + + @noKwargs + @noPosargs + def is_even_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: + return self.held_object % 2 == 0 + + @noKwargs + @noPosargs + def is_odd_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: + return self.held_object % 2 != 0 + + @noKwargs + @noPosargs + def to_string_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: + return str(self.held_object) + + @typed_operator(MesonOperator.DIV, int) + def op_div(self, other: int) -> int: + if other == 0: + raise InvalidArguments('Tried to divide by 0') + return self.held_object // other + + @typed_operator(MesonOperator.MOD, int) + def op_mod(self, other: int) -> int: + if other == 0: + raise InvalidArguments('Tried to divide by 0') + return self.held_object % other diff --git a/mesonbuild/interpreter/primitives/range.py b/mesonbuild/interpreter/primitives/range.py new file mode 100644 index 0000000..5eb5e03 --- /dev/null +++ b/mesonbuild/interpreter/primitives/range.py @@ -0,0 +1,38 @@ +# Copyright 2021 The Meson development team +# SPDX-license-identifier: Apache-2.0 +from __future__ import annotations + +import typing as T + +from ...interpreterbase import ( + MesonInterpreterObject, + IterableObject, + MesonOperator, + InvalidArguments, +) + +if T.TYPE_CHECKING: + from ...interpreterbase import SubProject + +class RangeHolder(MesonInterpreterObject, IterableObject): + def __init__(self, start: int, stop: int, step: int, *, subproject: 'SubProject') -> None: + super().__init__(subproject=subproject) + self.range = range(start, stop, step) + self.operators.update({ + MesonOperator.INDEX: self.op_index, + }) + + def op_index(self, other: int) -> int: + try: + return self.range[other] + except IndexError: + raise InvalidArguments(f'Index {other} out of bounds of range.') + + def iter_tuple_size(self) -> None: + return None + + def iter_self(self) -> T.Iterator[int]: + return iter(self.range) + + def size(self) -> int: + return len(self.range) diff --git a/mesonbuild/interpreter/primitives/string.py b/mesonbuild/interpreter/primitives/string.py new file mode 100644 index 0000000..d9f6a06 --- /dev/null +++ b/mesonbuild/interpreter/primitives/string.py @@ -0,0 +1,233 @@ +# Copyright 2021 The Meson development team +# SPDX-license-identifier: Apache-2.0 +from __future__ import annotations + +import re +import os + +import typing as T + +from ...mesonlib import version_compare +from ...interpreterbase import ( + ObjectHolder, + MesonOperator, + FeatureNew, + typed_operator, + noArgsFlattening, + noKwargs, + noPosargs, + typed_pos_args, + + InvalidArguments, +) + + +if T.TYPE_CHECKING: + # Object holders need the actual interpreter + from ...interpreter import Interpreter + from ...interpreterbase import TYPE_var, TYPE_kwargs + +class StringHolder(ObjectHolder[str]): + def __init__(self, obj: str, interpreter: 'Interpreter') -> None: + super().__init__(obj, interpreter) + self.methods.update({ + 'contains': self.contains_method, + 'startswith': self.startswith_method, + 'endswith': self.endswith_method, + 'format': self.format_method, + 'join': self.join_method, + 'replace': self.replace_method, + 'split': self.split_method, + 'strip': self.strip_method, + 'substring': self.substring_method, + 'to_int': self.to_int_method, + 'to_lower': self.to_lower_method, + 'to_upper': self.to_upper_method, + 'underscorify': self.underscorify_method, + 'version_compare': self.version_compare_method, + }) + + self.trivial_operators.update({ + # Arithmetic + MesonOperator.PLUS: (str, lambda x: self.held_object + x), + + # Comparison + MesonOperator.EQUALS: (str, lambda x: self.held_object == x), + MesonOperator.NOT_EQUALS: (str, lambda x: self.held_object != x), + MesonOperator.GREATER: (str, lambda x: self.held_object > x), + MesonOperator.LESS: (str, lambda x: self.held_object < x), + MesonOperator.GREATER_EQUALS: (str, lambda x: self.held_object >= x), + MesonOperator.LESS_EQUALS: (str, lambda x: self.held_object <= x), + }) + + # Use actual methods for functions that require additional checks + self.operators.update({ + MesonOperator.DIV: self.op_div, + MesonOperator.INDEX: self.op_index, + MesonOperator.IN: self.op_in, + MesonOperator.NOT_IN: self.op_notin, + }) + + def display_name(self) -> str: + return 'str' + + @noKwargs + @typed_pos_args('str.contains', str) + def contains_method(self, args: T.Tuple[str], kwargs: TYPE_kwargs) -> bool: + return self.held_object.find(args[0]) >= 0 + + @noKwargs + @typed_pos_args('str.startswith', str) + def startswith_method(self, args: T.Tuple[str], kwargs: TYPE_kwargs) -> bool: + return self.held_object.startswith(args[0]) + + @noKwargs + @typed_pos_args('str.endswith', str) + def endswith_method(self, args: T.Tuple[str], kwargs: TYPE_kwargs) -> bool: + return self.held_object.endswith(args[0]) + + @noArgsFlattening + @noKwargs + @typed_pos_args('str.format', varargs=object) + def format_method(self, args: T.Tuple[T.List[object]], kwargs: TYPE_kwargs) -> str: + arg_strings: T.List[str] = [] + for arg in args[0]: + if isinstance(arg, bool): # Python boolean is upper case. + arg = str(arg).lower() + arg_strings.append(str(arg)) + + def arg_replace(match: T.Match[str]) -> str: + idx = int(match.group(1)) + if idx >= len(arg_strings): + raise InvalidArguments(f'Format placeholder @{idx}@ out of range.') + return arg_strings[idx] + + return re.sub(r'@(\d+)@', arg_replace, self.held_object) + + @noKwargs + @typed_pos_args('str.join', varargs=str) + def join_method(self, args: T.Tuple[T.List[str]], kwargs: TYPE_kwargs) -> str: + return self.held_object.join(args[0]) + + @noKwargs + @typed_pos_args('str.replace', str, str) + def replace_method(self, args: T.Tuple[str, str], kwargs: TYPE_kwargs) -> str: + return self.held_object.replace(args[0], args[1]) + + @noKwargs + @typed_pos_args('str.split', optargs=[str]) + def split_method(self, args: T.Tuple[T.Optional[str]], kwargs: TYPE_kwargs) -> T.List[str]: + return self.held_object.split(args[0]) + + @noKwargs + @typed_pos_args('str.strip', optargs=[str]) + def strip_method(self, args: T.Tuple[T.Optional[str]], kwargs: TYPE_kwargs) -> str: + return self.held_object.strip(args[0]) + + @noKwargs + @typed_pos_args('str.substring', optargs=[int, int]) + def substring_method(self, args: T.Tuple[T.Optional[int], T.Optional[int]], kwargs: TYPE_kwargs) -> str: + start = args[0] if args[0] is not None else 0 + end = args[1] if args[1] is not None else len(self.held_object) + return self.held_object[start:end] + + @noKwargs + @noPosargs + def to_int_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> int: + try: + return int(self.held_object) + except ValueError: + raise InvalidArguments(f'String {self.held_object!r} cannot be converted to int') + + @noKwargs + @noPosargs + def to_lower_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: + return self.held_object.lower() + + @noKwargs + @noPosargs + def to_upper_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: + return self.held_object.upper() + + @noKwargs + @noPosargs + def underscorify_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: + return re.sub(r'[^a-zA-Z0-9]', '_', self.held_object) + + @noKwargs + @typed_pos_args('str.version_compare', str) + def version_compare_method(self, args: T.Tuple[str], kwargs: TYPE_kwargs) -> bool: + return version_compare(self.held_object, args[0]) + + @staticmethod + def _op_div(this: str, other: str) -> str: + return os.path.join(this, other).replace('\\', '/') + + @FeatureNew('/ with string arguments', '0.49.0') + @typed_operator(MesonOperator.DIV, str) + def op_div(self, other: str) -> str: + return self._op_div(self.held_object, other) + + @typed_operator(MesonOperator.INDEX, int) + def op_index(self, other: int) -> str: + try: + return self.held_object[other] + except IndexError: + raise InvalidArguments(f'Index {other} out of bounds of string of size {len(self.held_object)}.') + + @FeatureNew('"in" string operator', '1.0.0') + @typed_operator(MesonOperator.IN, str) + def op_in(self, other: str) -> bool: + return other in self.held_object + + @FeatureNew('"not in" string operator', '1.0.0') + @typed_operator(MesonOperator.NOT_IN, str) + def op_notin(self, other: str) -> bool: + return other not in self.held_object + + +class MesonVersionString(str): + pass + +class MesonVersionStringHolder(StringHolder): + @noKwargs + @typed_pos_args('str.version_compare', str) + def version_compare_method(self, args: T.Tuple[str], kwargs: TYPE_kwargs) -> bool: + self.interpreter.tmp_meson_version = args[0] + return version_compare(self.held_object, args[0]) + +# These special subclasses of string exist to cover the case where a dependency +# exports a string variable interchangeable with a system dependency. This +# matters because a dependency can only have string-type get_variable() return +# values. If at any time dependencies start supporting additional variable +# types, this class could be deprecated. +class DependencyVariableString(str): + pass + +class DependencyVariableStringHolder(StringHolder): + def op_div(self, other: str) -> T.Union[str, DependencyVariableString]: + ret = super().op_div(other) + if '..' in other: + return ret + return DependencyVariableString(ret) + + +class OptionString(str): + optname: str + + def __new__(cls, value: str, name: str) -> 'OptionString': + obj = str.__new__(cls, value) + obj.optname = name + return obj + + def __getnewargs__(self) -> T.Tuple[str, str]: # type: ignore # because the entire point of this is to diverge + return (str(self), self.optname) + + +class OptionStringHolder(StringHolder): + held_object: OptionString + + def op_div(self, other: str) -> T.Union[str, OptionString]: + ret = super().op_div(other) + name = self._op_div(self.held_object.optname, other) + return OptionString(ret, name) |