Interpreter: Add "in" and "not in" operators

Closes: #3600
This commit is contained in:
Xavier Claessens 2018-07-17 13:54:56 -04:00
parent 2ff69b20df
commit fa2e096aa0
4 changed files with 43 additions and 11 deletions

View File

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

View File

@ -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.'):
@ -487,6 +495,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 +509,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

View File

@ -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'}
self.future_keywords = {'continue', 'break', 'return'}
self.token_specification = [
# Need to be sorted longest to shortest.
('ignore', re.compile(r'[ \t]')),
@ -436,7 +437,9 @@ comparison_map = {'equal': '==',
'lt': '<',
'le': '<=',
'gt': '>',
'ge': '>='
'ge': '>=',
'in': 'in',
'notin': 'not in',
}
# Recursive descent parser for Meson's definition language.
@ -543,6 +546,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):

View File

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