diff --git a/data/syntax-highlighting/vim/syntax/meson.vim b/data/syntax-highlighting/vim/syntax/meson.vim index 161bcf1a1..321866fdb 100644 --- a/data/syntax-highlighting/vim/syntax/meson.vim +++ b/data/syntax-highlighting/vim/syntax/meson.vim @@ -119,6 +119,7 @@ syn keyword mesonBuiltin \ test \ vcs_tag \ warning + \ range if exists("meson_space_error_highlight") " trailing whitespace diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md index 3af00c45a..a70c94ba7 100644 --- a/docs/markdown/Reference-manual.md +++ b/docs/markdown/Reference-manual.md @@ -1861,6 +1861,36 @@ which to build the targets. If you desire more specific behavior than what this command provides, you should use `custom_target`. +### range() + +``` meson + rangeobject range(stop) + rangeobject range(start, stop[, step]) +``` + +*Since 0.58.0* + +Return an opaque object that can be only be used in `foreach` statements. +- `start` must be integer greater or equal to 0. Defaults to 0. +- `stop` must be integer greater or equal to `start`. +- `step` must be integer greater or equal to 1. Defaults to 1. + +It cause the `foreach` loop to be called with the value from `start` included +to `stop` excluded with an increment of `step` after each loop. + +```meson +# Loop 15 times with i from 0 to 14 included. +foreach i : range(15) + ... +endforeach +``` + +The range object can also be assigned to a variable and indexed. +```meson +r = range(5, 10, 2) +assert(r[2] == 9) +``` + ## Built-in objects These are built-in objects that are always available. diff --git a/docs/markdown/snippets/range.md b/docs/markdown/snippets/range.md new file mode 100644 index 000000000..77635d627 --- /dev/null +++ b/docs/markdown/snippets/range.md @@ -0,0 +1,28 @@ +## New `range()` function + +``` meson + rangeobject range(stop) + rangeobject range(start, stop[, step]) +``` + +Return an opaque object that can be only be used in `foreach` statements. +- `start` must be integer greater or equal to 0. Defaults to 0. +- `stop` must be integer greater or equal to `start`. +- `step` must be integer greater or equal to 1. Defaults to 1. + +It cause the `foreach` loop to be called with the value from `start` included +to `stop` excluded with an increment of `step` after each loop. + +```meson +# Loop 15 times with i from 0 to 14 included. +foreach i : range(15) + ... +endforeach +``` + +The range object can also be assigned to a variable and indexed. +```meson +r = range(5, 10, 2) +assert(r[2] == 9) +``` + diff --git a/mesonbuild/ast/interpreter.py b/mesonbuild/ast/interpreter.py index 98d141d65..71c7f4770 100644 --- a/mesonbuild/ast/interpreter.py +++ b/mesonbuild/ast/interpreter.py @@ -128,6 +128,7 @@ class AstInterpreter(interpreterbase.InterpreterBase): 'subdir_done': self.func_do_nothing, 'alias_target': self.func_do_nothing, 'summary': self.func_do_nothing, + 'range': self.func_do_nothing, }) def func_do_nothing(self, node: BaseNode, args: T.List[TYPE_nvar], kwargs: T.Dict[str, TYPE_nvar]) -> bool: diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index d554cce4d..364cf7089 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -30,7 +30,7 @@ from .interpreterbase import check_stringlist, flatten, noPosargs, noKwargs, str from .interpreterbase import InterpreterException, InvalidArguments, InvalidCode, SubdirDoneRequest from .interpreterbase import InterpreterObject, MutableInterpreterObject, Disabler, disablerIfNotFound from .interpreterbase import FeatureNew, FeatureDeprecated, FeatureNewKwargs, FeatureDeprecatedKwargs -from .interpreterbase import ObjectHolder, MesonVersionString +from .interpreterbase import ObjectHolder, MesonVersionString, RangeHolder from .interpreterbase import TYPE_var, TYPE_nkwargs from .modules import ModuleReturnValue, ModuleObject, ModuleState from .cmake import CMakeInterpreter @@ -2509,7 +2509,8 @@ class Interpreter(InterpreterBase): 'static_library': self.func_static_lib, 'both_libraries': self.func_both_lib, 'test': self.func_test, - 'vcs_tag': self.func_vcs_tag + 'vcs_tag': self.func_vcs_tag, + 'range': self.func_range, }) if 'MESON_UNIT_TEST' in os.environ: self.funcs.update({'exception': self.func_exception}) @@ -5033,3 +5034,24 @@ This will become a hard error in the future.''', location=self.current_node) raise InvalidCode('Is_disabler takes one argument.') varname = args[0] return isinstance(varname, Disabler) + + @noKwargs + @FeatureNew('range', '0.58.0') + @typed_pos_args('range', int, optargs=[int, int]) + def func_range(self, node, args: T.Tuple[int, T.Optional[int], T.Optional[int]], kwargs: T.Dict[str, T.Any]) -> RangeHolder: + start, stop, step = args + # Just like Python's range, we allow range(stop), range(start, stop), or + # range(start, stop, step) + if stop is None: + stop = start + start = 0 + if step is None: + step = 1 + # This is more strict than Python's range() + if start < 0: + raise InterpreterException('start cannot be negative') + if stop < start: + raise InterpreterException('stop cannot be less than start') + if step < 1: + raise InterpreterException('step must be >=1') + return RangeHolder(start, stop, step) diff --git a/mesonbuild/interpreterbase.py b/mesonbuild/interpreterbase.py index 17647133a..a3645109c 100644 --- a/mesonbuild/interpreterbase.py +++ b/mesonbuild/interpreterbase.py @@ -70,6 +70,20 @@ class ObjectHolder(T.Generic[TV_InterpreterObject]): class MesonVersionString(str): pass +class RangeHolder(InterpreterObject): + def __init__(self, start: int, stop: int, step: int) -> None: + super().__init__() + self.range = range(start, stop, step) + + 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) + # Decorators for method calls. def check_stringlist(a: T.Any, msg: str = 'Arguments must be strings.') -> None: @@ -950,7 +964,7 @@ The result of this is undefined and will become a hard error in a future Meson r assert(isinstance(node, mparser.ForeachClauseNode)) items = self.evaluate_statement(node.items) - if isinstance(items, list): + if isinstance(items, (list, RangeHolder)): if len(node.varnames) != 1: raise InvalidArguments('Foreach on array does not unpack') varname = node.varnames[0] diff --git a/test cases/common/61 foreach/meson.build b/test cases/common/61 foreach/meson.build index 7084e8003..af60e0fbb 100644 --- a/test cases/common/61 foreach/meson.build +++ b/test cases/common/61 foreach/meson.build @@ -31,3 +31,23 @@ foreach i : items endforeach assert(result == ['a', 'b'], 'Continue or break in foreach failed') + +items = [] +iter = range(2) +foreach i : iter + items += i +endforeach +assert(items == [0, 1]) +assert(iter[1] == 1) + +items = [] +foreach i : range(1, 2) + items += i +endforeach +assert(items == [1]) + +items = [] +foreach i : range(1, 10, 2) + items += i +endforeach +assert(items == [1, 3, 5, 7, 9])