aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/markdown/Syntax.md12
-rw-r--r--mesonbuild/build.py3
-rw-r--r--mesonbuild/interpreter/interpreterobjects.py23
-rw-r--r--mesonbuild/interpreter/primitives/string.py8
-rw-r--r--mesonbuild/interpreterbase/__init__.py3
-rw-r--r--mesonbuild/interpreterbase/baseobjects.py12
-rw-r--r--mesonbuild/interpreterbase/decorators.py9
-rw-r--r--mesonbuild/interpreterbase/helpers.py12
-rw-r--r--mesonbuild/interpreterbase/interpreterbase.py10
-rw-r--r--test cases/common/35 string operations/meson.build3
10 files changed, 63 insertions, 32 deletions
diff --git a/docs/markdown/Syntax.md b/docs/markdown/Syntax.md
index e3a70c7..33b06cb 100644
--- a/docs/markdown/Syntax.md
+++ b/docs/markdown/Syntax.md
@@ -178,6 +178,18 @@ These are raw strings that do not support the escape sequences listed
above. These strings can also be combined with the string formatting
functionality described below.
+### String index
+
+Stings support the indexing (`[<num>]`) operator. This operator allows (read
+only) acessing a specific character. The returned value is guaranteed to be
+a string of length 1.
+
+```meson
+foo = 'abcd'
+message(foo[1]) # Will print 'b'
+foo[2] = 'C' # ERROR: Meson objects are immutable!
+```
+
### String formatting
#### .format()
diff --git a/mesonbuild/build.py b/mesonbuild/build.py
index 98385df..24eff8c 100644
--- a/mesonbuild/build.py
+++ b/mesonbuild/build.py
@@ -2524,6 +2524,9 @@ class CustomTarget(Target, CommandBase):
for i in self.outputs:
yield CustomTargetIndex(self, i)
+ def __len__(self) -> int:
+ return len(self.outputs)
+
class RunTarget(Target, CommandBase):
def __init__(self, name: str, command, dependencies,
subdir: str, subproject: str, env: T.Optional['EnvironmentVariables'] = None):
diff --git a/mesonbuild/interpreter/interpreterobjects.py b/mesonbuild/interpreter/interpreterobjects.py
index 78c7fb9..fca371c 100644
--- a/mesonbuild/interpreter/interpreterobjects.py
+++ b/mesonbuild/interpreter/interpreterobjects.py
@@ -14,10 +14,10 @@ from .. import mlog
from ..modules import ModuleReturnValue, ModuleObject, ModuleState, ExtensionModule
from ..backend.backends import TestProtocol
from ..interpreterbase import (
- ContainerTypeInfo, KwargInfo,
+ ContainerTypeInfo, KwargInfo, MesonOperator,
InterpreterObject, MesonInterpreterObject, ObjectHolder, MutableInterpreterObject,
FeatureCheckBase, FeatureNewKwargs, FeatureNew, FeatureDeprecated,
- typed_pos_args, typed_kwargs, permittedKwargs,
+ typed_pos_args, typed_kwargs, typed_operator, permittedKwargs,
noArgsFlattening, noPosargs, noKwargs, unholder_return, TYPE_var, TYPE_kwargs, TYPE_nvar, TYPE_nkwargs,
flatten, resolve_second_level_holders, InterpreterException, InvalidArguments, InvalidCode)
from ..interpreter.type_checking import NoneType
@@ -912,6 +912,10 @@ class CustomTargetHolder(ObjectHolder[build.CustomTarget]):
'to_list': self.to_list_method,
})
+ self.operators.update({
+ MesonOperator.INDEX: self.op_index,
+ })
+
def __repr__(self) -> str:
r = '<{} {}: {}>'
h = self.held_object
@@ -931,14 +935,13 @@ class CustomTargetHolder(ObjectHolder[build.CustomTarget]):
result.append(i)
return result
- def __getitem__(self, index: int) -> build.CustomTargetIndex:
- return self.held_object[index]
-
- def __setitem__(self, index: int, value: T.Any) -> None: # lgtm[py/unexpected-raise-in-special-method]
- raise InterpreterException('Cannot set a member of a CustomTarget')
-
- def __delitem__(self, index: int) -> None: # lgtm[py/unexpected-raise-in-special-method]
- raise InterpreterException('Cannot delete a member of a CustomTarget')
+ @noKwargs
+ @typed_operator(MesonOperator.INDEX, int)
+ def op_index(self, other: int) -> build.CustomTargetIndex:
+ try:
+ return self.held_object[other]
+ except IndexError:
+ raise InvalidArguments(f'Index {other} out of bounds of custom target {self.held_object.name} output of size {len(self.held_object)}.')
class RunTargetHolder(ObjectHolder[build.RunTarget]):
pass
diff --git a/mesonbuild/interpreter/primitives/string.py b/mesonbuild/interpreter/primitives/string.py
index 6b7155a..82da6ce 100644
--- a/mesonbuild/interpreter/primitives/string.py
+++ b/mesonbuild/interpreter/primitives/string.py
@@ -64,6 +64,7 @@ class StringHolder(ObjectHolder[str]):
# Use actual methods for functions that require additional checks
self.operators.update({
MesonOperator.DIV: self.op_div,
+ MesonOperator.INDEX: self.op_index,
})
def display_name(self) -> str:
@@ -162,6 +163,13 @@ class StringHolder(ObjectHolder[str]):
def op_div(self, other: str) -> str:
return (PurePath(self.held_object) / other).as_posix()
+ @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)}.')
+
class MesonVersionString(str):
pass
diff --git a/mesonbuild/interpreterbase/__init__.py b/mesonbuild/interpreterbase/__init__.py
index d4c4a36..0375430 100644
--- a/mesonbuild/interpreterbase/__init__.py
+++ b/mesonbuild/interpreterbase/__init__.py
@@ -31,7 +31,6 @@ __all__ = [
'ContinueRequest',
'BreakRequest',
- 'check_stringlist',
'default_resolve_key',
'flatten',
'resolve_second_level_holders',
@@ -127,6 +126,6 @@ from .exceptions import (
)
from .disabler import Disabler, is_disabled
-from .helpers import check_stringlist, default_resolve_key, flatten, resolve_second_level_holders
+from .helpers import default_resolve_key, flatten, resolve_second_level_holders
from .interpreterbase import InterpreterBase
from .operator import MesonOperator
diff --git a/mesonbuild/interpreterbase/baseobjects.py b/mesonbuild/interpreterbase/baseobjects.py
index c979f7d..62a2381 100644
--- a/mesonbuild/interpreterbase/baseobjects.py
+++ b/mesonbuild/interpreterbase/baseobjects.py
@@ -168,12 +168,18 @@ class RangeHolder(MesonInterpreterObject):
def __init__(self, start: int, stop: int, step: int, *, subproject: str) -> 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:
+ raise InvalidArguments(f'Index {other} out of bounds of range.')
def __iter__(self) -> T.Iterator[int]:
return iter(self.range)
- def __getitem__(self, key: int) -> int:
- return self.range[key]
-
def __len__(self) -> int:
return len(self.range)
diff --git a/mesonbuild/interpreterbase/decorators.py b/mesonbuild/interpreterbase/decorators.py
index b9c4a1f..54f4be3 100644
--- a/mesonbuild/interpreterbase/decorators.py
+++ b/mesonbuild/interpreterbase/decorators.py
@@ -16,7 +16,6 @@ from .. import mesonlib, mlog
from .baseobjects import TV_func, TYPE_var, TYPE_kwargs
from .disabler import Disabler
from .exceptions import InterpreterException, InvalidArguments
-from .helpers import check_stringlist
from .operator import MesonOperator
from ._unholder import _unholder
@@ -64,8 +63,12 @@ def stringArgs(f: TV_func) -> TV_func:
@wraps(f)
def wrapped(*wrapped_args: T.Any, **wrapped_kwargs: T.Any) -> T.Any:
args = get_callee_args(wrapped_args)[1]
- assert isinstance(args, list)
- check_stringlist(args)
+ if not isinstance(args, list):
+ mlog.debug('Not a list:', str(args))
+ raise InvalidArguments('Argument not a list.')
+ if not all(isinstance(s, str) for s in args):
+ mlog.debug('Element not a string:', str(args))
+ raise InvalidArguments('Arguments must be strings.')
return f(*wrapped_args, **wrapped_kwargs)
return T.cast(TV_func, wrapped)
diff --git a/mesonbuild/interpreterbase/helpers.py b/mesonbuild/interpreterbase/helpers.py
index 3d45e1f..12fa813 100644
--- a/mesonbuild/interpreterbase/helpers.py
+++ b/mesonbuild/interpreterbase/helpers.py
@@ -12,8 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from .. import mesonlib, mparser, mlog
-from .exceptions import InvalidArguments, InterpreterException
+from .. import mesonlib, mparser
+from .exceptions import InterpreterException
import collections.abc
import typing as T
@@ -49,14 +49,6 @@ def resolve_second_level_holders(args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs'
return arg
return [resolver(x) for x in args], {k: resolver(v) for k, v in kwargs.items()}
-def check_stringlist(a: T.Any, msg: str = 'Arguments must be strings.') -> None:
- if not isinstance(a, list):
- mlog.debug('Not a list:', str(a))
- raise InvalidArguments('Argument not a list.')
- if not all(isinstance(s, str) for s in a):
- mlog.debug('Element not a string:', str(a))
- raise InvalidArguments(msg)
-
def default_resolve_key(key: mparser.BaseNode) -> str:
if not isinstance(key, mparser.IdNode):
raise InterpreterException('Invalid kwargs format.')
diff --git a/mesonbuild/interpreterbase/interpreterbase.py b/mesonbuild/interpreterbase/interpreterbase.py
index 9ae50c8..4b4b3c0 100644
--- a/mesonbuild/interpreterbase/interpreterbase.py
+++ b/mesonbuild/interpreterbase/interpreterbase.py
@@ -44,7 +44,7 @@ from .exceptions import (
from .decorators import FeatureNew, noKwargs
from .disabler import Disabler, is_disabled
-from .helpers import check_stringlist, default_resolve_key, flatten, resolve_second_level_holders
+from .helpers import default_resolve_key, flatten, resolve_second_level_holders
from .operator import MesonOperator
from ._unholder import _unholder
@@ -579,11 +579,13 @@ class InterpreterBase:
iobject = self.evaluate_statement(node.iobject)
if isinstance(iobject, Disabler):
return iobject
+ index = _unholder(self.evaluate_statement(node.index))
+
+ if isinstance(iobject, InterpreterObject):
+ return self._holderify(iobject.operator_call(MesonOperator.INDEX, index))
if not hasattr(iobject, '__getitem__'):
raise InterpreterException(
'Tried to index an object that doesn\'t support indexing.')
- index = _unholder(self.evaluate_statement(node.index))
-
if isinstance(iobject, dict):
if not isinstance(index, str):
raise InterpreterException('Key is not a string')
@@ -887,7 +889,7 @@ To specify a keyword argument, use : instead of =.''')
raise InvalidCode('Unknown variable "%s".' % varname)
def is_assignable(self, value: T.Any) -> bool:
- return isinstance(value, (InterpreterObject, str, int, list, dict))
+ return isinstance(value, (InterpreterObject, list, dict))
def validate_extraction(self, buildtarget: mesonlib.HoldableObject) -> None:
raise InterpreterException('validate_extraction is not implemented in this context (please file a bug)')
diff --git a/test cases/common/35 string operations/meson.build b/test cases/common/35 string operations/meson.build
index 3405e95..b86e13a 100644
--- a/test cases/common/35 string operations/meson.build
+++ b/test cases/common/35 string operations/meson.build
@@ -19,6 +19,9 @@ long = 'abcde'
prefix = 'abc'
suffix = 'cde'
+assert(long[0] == 'a')
+assert(long[2] == 'c')
+
assert(long.replace('b', 'd') == 'adcde')
assert(long.replace('z', 'x') == long)
assert(long.replace(prefix, suffix) == 'cdede')