Merge pull request #3900 from xclaesse/in-operator

Interpreter: Add "in", "not in", "break", and "continue" operators
pull/4576/head
Jussi Pakkanen 6 years ago committed by GitHub
commit 85efd363cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 43
      docs/markdown/Syntax.md
  2. 42
      docs/markdown/snippets/new_syntax.md
  3. 9
      mesonbuild/interpreter.py
  4. 39
      mesonbuild/interpreterbase.py
  5. 21
      mesonbuild/mparser.py
  6. 15
      test cases/common/17 comparison/meson.build
  7. 13
      test cases/common/64 foreach/meson.build

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

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

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

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

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

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

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

Loading…
Cancel
Save