Add range() function

Fixes: #5026.
pull/8503/head
Xavier Claessens 4 years ago committed by Xavier Claessens
parent 6415453f17
commit a45f939092
  1. 1
      data/syntax-highlighting/vim/syntax/meson.vim
  2. 30
      docs/markdown/Reference-manual.md
  3. 28
      docs/markdown/snippets/range.md
  4. 1
      mesonbuild/ast/interpreter.py
  5. 26
      mesonbuild/interpreter.py
  6. 16
      mesonbuild/interpreterbase.py
  7. 20
      test cases/common/61 foreach/meson.build

@ -119,6 +119,7 @@ syn keyword mesonBuiltin
\ test
\ vcs_tag
\ warning
\ range
if exists("meson_space_error_highlight")
" trailing whitespace

@ -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.

@ -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)
```

@ -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:

@ -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)

@ -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]

@ -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])

Loading…
Cancel
Save