aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/markdown/Syntax.md43
-rw-r--r--docs/markdown/snippets/new_syntax.md42
-rw-r--r--mesonbuild/interpreter.py9
-rw-r--r--mesonbuild/interpreterbase.py43
-rw-r--r--mesonbuild/mparser.py21
-rw-r--r--test cases/common/17 comparison/meson.build15
-rw-r--r--test cases/common/64 foreach/meson.build13
7 files changed, 173 insertions, 13 deletions
diff --git a/docs/markdown/Syntax.md b/docs/markdown/Syntax.md
index ff4c142..22b8be3 100644
--- a/docs/markdown/Syntax.md
+++ b/docs/markdown/Syntax.md
@@ -283,6 +283,17 @@ Note appending to an array will always create a new array object and
assign it to `my_array` instead of modifying the original since all
objects in Meson are immutable.
+Since 0.49.0, you can check if an array contains an element like this:
+```meson
+my_array = [1, 2]
+if 1 in my_array
+# This condition is true
+endif
+if 1 not in my_array
+# This condition is false
+endif
+```
+
#### Array methods
The following methods are defined for all arrays:
@@ -316,6 +327,20 @@ Dictionaries are available since 0.47.0.
Visit the [Reference Manual](Reference-manual.md#dictionary-object) to read
about the methods exposed by dictionaries.
+Since 0.49.0, you can check if a dictionary contains a key like this:
+```meson
+my_dict = {'foo': 42, 'foo': 43}
+if 'foo' in my_dict
+# This condition is true
+endif
+if 42 in my_dict
+# This condition is false
+endif
+if 'foo' not in my_dict
+# This condition is false
+endif
+```
+
Function calls
--
@@ -432,6 +457,24 @@ foreach name, sources : components
endforeach
```
+### Foreach `break` and `continue`
+
+Since 0.49.0 `break` and `continue` keywords can be used inside foreach loops.
+
+```meson
+items = ['a', 'continue', 'b', 'break', 'c']
+result = []
+foreach i : items
+ if i == 'continue'
+ continue
+ elif i == 'break'
+ break
+ endif
+ result += i
+endforeach
+# result is ['a', 'b']
+```
+
Comments
--
diff --git a/docs/markdown/snippets/new_syntax.md b/docs/markdown/snippets/new_syntax.md
new file mode 100644
index 0000000..98eccd0
--- /dev/null
+++ b/docs/markdown/snippets/new_syntax.md
@@ -0,0 +1,42 @@
+## Foreach `break` and `continue`
+
+`break` and `continue` keywords can be used inside foreach loops.
+
+```meson
+items = ['a', 'continue', 'b', 'break', 'c']
+result = []
+foreach i : items
+ if i == 'continue'
+ continue
+ elif i == 'break'
+ break
+ endif
+ result += i
+endforeach
+# result is ['a', 'b']
+```
+
+You can check if an array contains an element like this:
+```meson
+my_array = [1, 2]
+if 1 in my_array
+# This condition is true
+endif
+if 1 not in my_array
+# This condition is false
+endif
+```
+
+You can check if a dictionary contains a key like this:
+```meson
+my_dict = {'foo': 42, 'foo': 43}
+if 'foo' in my_dict
+# This condition is true
+endif
+if 42 in my_dict
+# This condition is false
+endif
+if 'foo' not in my_dict
+# This condition is false
+endif
+```
diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py
index e97ac8e..f2b8f34 100644
--- a/mesonbuild/interpreter.py
+++ b/mesonbuild/interpreter.py
@@ -29,6 +29,7 @@ from .interpreterbase import check_stringlist, flatten, noPosargs, noKwargs, str
from .interpreterbase import InterpreterException, InvalidArguments, InvalidCode, SubdirDoneRequest
from .interpreterbase import InterpreterObject, MutableInterpreterObject, Disabler
from .interpreterbase import FeatureNew, FeatureDeprecated, FeatureNewKwargs
+from .interpreterbase import ObjectHolder
from .modules import ModuleReturnValue
import os, shutil, uuid
@@ -57,14 +58,6 @@ def stringifyUserArguments(args):
raise InvalidArguments('Function accepts only strings, integers, lists and lists thereof.')
-class ObjectHolder:
- def __init__(self, obj, subproject=None):
- self.held_object = obj
- self.subproject = subproject
-
- def __repr__(self):
- return '<Holder: {!r}>'.format(self.held_object)
-
class FeatureOptionHolder(InterpreterObject, ObjectHolder):
def __init__(self, env, option):
InterpreterObject.__init__(self)
diff --git a/mesonbuild/interpreterbase.py b/mesonbuild/interpreterbase.py
index 1c74eeb..c0064ab 100644
--- a/mesonbuild/interpreterbase.py
+++ b/mesonbuild/interpreterbase.py
@@ -21,6 +21,14 @@ from . import environment, dependencies
import os, copy, re, types
from functools import wraps
+class ObjectHolder:
+ def __init__(self, obj, subproject=None):
+ self.held_object = obj
+ self.subproject = subproject
+
+ def __repr__(self):
+ return '<Holder: {!r}>'.format(self.held_object)
+
# Decorators for method calls.
def check_stringlist(a, msg='Arguments must be strings.'):
@@ -292,6 +300,12 @@ class InvalidArguments(InterpreterException):
class SubdirDoneRequest(BaseException):
pass
+class ContinueRequest(BaseException):
+ pass
+
+class BreakRequest(BaseException):
+ pass
+
class InterpreterObject:
def __init__(self):
self.methods = {}
@@ -445,6 +459,10 @@ class InterpreterBase:
return self.evaluate_indexing(cur)
elif isinstance(cur, mparser.TernaryNode):
return self.evaluate_ternary(cur)
+ elif isinstance(cur, mparser.ContinueNode):
+ raise ContinueRequest()
+ elif isinstance(cur, mparser.BreakNode):
+ raise BreakRequest()
elif self.is_elementary_type(cur):
return cur
else:
@@ -487,6 +505,13 @@ class InterpreterBase:
return False
return True
+ def evaluate_in(self, val1, val2):
+ if not isinstance(val1, (str, int, float, ObjectHolder)):
+ raise InvalidArguments('lvalue of "in" operator must be a string, integer, float, or object')
+ if not isinstance(val2, (list, dict)):
+ raise InvalidArguments('rvalue of "in" operator must be an array or a dict')
+ return val1 in val2
+
def evaluate_comparison(self, node):
val1 = self.evaluate_statement(node.left)
if is_disabler(val1):
@@ -494,6 +519,10 @@ class InterpreterBase:
val2 = self.evaluate_statement(node.right)
if is_disabler(val2):
return val2
+ if node.ctype == 'in':
+ return self.evaluate_in(val1, val2)
+ elif node.ctype == 'notin':
+ return not self.evaluate_in(val1, val2)
valid = self.validate_comparison_types(val1, val2)
# Ordering comparisons of different types isn't allowed since PR #1810
# (0.41.0). Since PR #2884 we also warn about equality comparisons of
@@ -622,7 +651,12 @@ The result of this is undefined and will become a hard error in a future Meson r
return items
for item in items:
self.set_variable(varname, item)
- self.evaluate_codeblock(node.block)
+ try:
+ self.evaluate_codeblock(node.block)
+ except ContinueRequest:
+ continue
+ except BreakRequest:
+ break
elif isinstance(items, dict):
if len(node.varnames) != 2:
raise InvalidArguments('Foreach on dict unpacks key and value')
@@ -631,7 +665,12 @@ The result of this is undefined and will become a hard error in a future Meson r
for key, value in items.items():
self.set_variable(node.varnames[0].value, key)
self.set_variable(node.varnames[1].value, value)
- self.evaluate_codeblock(node.block)
+ try:
+ self.evaluate_codeblock(node.block)
+ except ContinueRequest:
+ continue
+ except BreakRequest:
+ break
else:
raise InvalidArguments('Items of foreach loop must be an array or a dict')
diff --git a/mesonbuild/mparser.py b/mesonbuild/mparser.py
index 9af6dac..be5c807 100644
--- a/mesonbuild/mparser.py
+++ b/mesonbuild/mparser.py
@@ -90,8 +90,9 @@ class Lexer:
def __init__(self, code):
self.code = code
self.keywords = {'true', 'false', 'if', 'else', 'elif',
- 'endif', 'and', 'or', 'not', 'foreach', 'endforeach'}
- self.future_keywords = {'continue', 'break', 'in', 'return'}
+ 'endif', 'and', 'or', 'not', 'foreach', 'endforeach',
+ 'in', 'continue', 'break'}
+ self.future_keywords = {'return'}
self.token_specification = [
# Need to be sorted longest to shortest.
('ignore', re.compile(r'[ \t]')),
@@ -242,6 +243,12 @@ class StringNode(ElementaryNode):
def __str__(self):
return "String node: '%s' (%d, %d)." % (self.value, self.lineno, self.colno)
+class ContinueNode(ElementaryNode):
+ pass
+
+class BreakNode(ElementaryNode):
+ pass
+
class ArrayNode:
def __init__(self, args):
self.subdir = args.subdir
@@ -436,7 +443,9 @@ comparison_map = {'equal': '==',
'lt': '<',
'le': '<=',
'gt': '>',
- 'ge': '>='
+ 'ge': '>=',
+ 'in': 'in',
+ 'notin': 'not in',
}
# Recursive descent parser for Meson's definition language.
@@ -543,6 +552,8 @@ class Parser:
for nodename, operator_type in comparison_map.items():
if self.accept(nodename):
return ComparisonNode(operator_type, left, self.e5())
+ if self.accept('not') and self.accept('in'):
+ return ComparisonNode('notin', left, self.e5())
return left
def e5(self):
@@ -754,6 +765,10 @@ class Parser:
block = self.foreachblock()
self.block_expect('endforeach', block_start)
return block
+ if self.accept('continue'):
+ return ContinueNode(self.current)
+ if self.accept('break'):
+ return BreakNode(self.current)
return self.statement()
def codeblock(self):
diff --git a/test cases/common/17 comparison/meson.build b/test cases/common/17 comparison/meson.build
index fb641ed..bba0168 100644
--- a/test cases/common/17 comparison/meson.build
+++ b/test cases/common/17 comparison/meson.build
@@ -137,3 +137,18 @@ assert(2 != 'st', 'not equal')
assert(not ([] == 'st'), 'not equal')
assert(not ([] == 1), 'not equal')
assert(not (2 == 'st'), 'not equal')
+
+# "in" and "not in" operators
+
+assert(1 in [1, 2], '''1 should be in [1, 2]''')
+assert(3 not in [1, 2], '''3 shouldn't be in [1, 2]''')
+assert(not (3 in [1, 2]), '''3 shouldn't be in [1, 2]''')
+
+assert('b' in ['a', 'b'], ''''b' should be in ['a', 'b']''')
+assert('c' not in ['a', 'b'], ''''c' shouldn't be in ['a', 'b']''')
+
+assert(exe1 in [exe1, exe2], ''''exe1 should be in [exe1, exe2]''')
+assert(exe3 not in [exe1, exe2], ''''exe3 shouldn't be in [exe1, exe2]''')
+
+assert('a' in {'a': 'b'}, '''1 should be in {'a': 'b'}''')
+assert('b' not in {'a': 'b'}, '''1 should be in {'a': 'b'}''')
diff --git a/test cases/common/64 foreach/meson.build b/test cases/common/64 foreach/meson.build
index e633de8..7084e80 100644
--- a/test cases/common/64 foreach/meson.build
+++ b/test cases/common/64 foreach/meson.build
@@ -18,3 +18,16 @@ foreach i : tests
# we definitely don't want that.
tests = ['test4', 'prog4', 'prog4.c']
endforeach
+
+items = ['a', 'continue', 'b', 'break', 'c']
+result = []
+foreach i : items
+ if i == 'continue'
+ continue
+ elif i == 'break'
+ break
+ endif
+ result += i
+endforeach
+
+assert(result == ['a', 'b'], 'Continue or break in foreach failed')