From 92eb60bd597c56afb95c018eafbdcc31c9bc0d60 Mon Sep 17 00:00:00 2001 From: Zbigniew Jędrzejewski-Szmek Date: Aug 13 2018 14:02:01 +0000 Subject: Add evaluation of assignments and options --- diff --git a/rustcfg/__init__.py b/rustcfg/__init__.py index 72d2440..e7e2109 100644 --- a/rustcfg/__init__.py +++ b/rustcfg/__init__.py @@ -6,21 +6,23 @@ def paren_exp(keyword, contents): return pp.Keyword(keyword)('op') + pp.Suppress('(') + contents + pp.Suppress(')') def cfg_exp(): - word = pp.Word(pp.alphanums + '_-')('word') + option = pp.Word(pp.alphanums + '_')('option') exp = pp.Forward() + assign = (option + pp.Suppress("=") + pp.QuotedString('"')('value'))('assign') + any_exp = paren_exp('any', pp.delimitedList(exp, delim=',')) all_exp = paren_exp('all', pp.delimitedList(exp, delim=',')) not_exp = paren_exp('not', exp) - exp << pp.Group(any_exp | all_exp | not_exp | word) + exp << pp.Group(any_exp | all_exp | not_exp | assign | option) return paren_exp('cfg', exp) def multiarch_tuple(): word = pp.Word(pp.alphanums + '_') opt = pp.Optional(pp.Suppress('-') + word) - tup = (word('a') + pp.Suppress('-') + word('b') + opt('c') + opt('d')) + tup = (word('a') + pp.Suppress('-') + word('b') + opt('c') + opt('d'))('archtuple') return tup @lru_cache() @@ -28,30 +30,49 @@ def cfg_grammar(): grammar = (cfg_exp() | multiarch_tuple()) + pp.stringEnd() return grammar -def eval_tree(tree): - kind = tree.getName() - assert kind - if kind == 'word': - return True - elif kind == 'op': - op = tree[0] - if op == 'cfg': - assert(len(tree) == 2) - return eval_tree(tree[1]) - if op == 'any': - assert(len(tree) >= 2) - return any(eval_tree(item) for item in tree[1:]) - if op == 'all': - assert(len(tree) >= 2) - return all(eval_tree(item) for item in tree[1:]) - if op == 'not': - assert(len(tree) == 2) - return not eval_tree(tree[1]) - assert False, f'Unknown operator {op}' - else: - assert False, f'Unknown element {kind}' - -def dump_tree(t, level=0, evalf=eval_tree): +class Evaluator: + """Evalutate cfg expressions + + From rust docs: + Configuration options are boolean (on or off) and are named + either with a single identifier (e.g. foo) or an identifier and + a string (e.g. foo = "bar"; the quotes are required and spaces + around the = are unimportant). Note that similarly-named + options, such as foo, foo="bar" and foo="baz" may each be set or + unset independently. + """ + def __init__(self, options=()): + self.options = options + + def eval_tree(self, tree): + kind = tree.getName() + assert kind + if kind == 'option': + return tree.option in self.options + elif kind == 'assign': + option = tree.option, tree.value + return option in self.options + elif kind == 'op': + op = tree[0] + if op == 'cfg': + assert(len(tree) == 2) + return self.eval_tree(tree[1]) + if op == 'any': + assert(len(tree) >= 2) + return any(self.eval_tree(item) for item in tree[1:]) + if op == 'all': + assert(len(tree) >= 2) + return all(self.eval_tree(item) for item in tree[1:]) + if op == 'not': + assert(len(tree) == 2) + return not self.eval_tree(tree[1]) + assert False, f'Unknown operator {op}' + elif kind == 'archtuple': + return 'linux' in list(tree) + else: + assert False, f'Unknown element {kind}' + +def dump_tree(t, level=0, evalf=None): print('{}structure {}{}{}{}'.format(' '*level, t.getName(), ' [' if evalf else '', evalf(t) if evalf else '', diff --git a/rustcfg/test/test_expressions.py b/rustcfg/test/test_expressions.py index 06800ed..e7d6f67 100644 --- a/rustcfg/test/test_expressions.py +++ b/rustcfg/test/test_expressions.py @@ -1,32 +1,43 @@ import pytest import rustcfg -""" -"cfg(ok)", "cfg( ok )".parse::().unwrap().to_string()); -"foo-bar-baz", "foo-bar-baz".parse::().unwrap().to_string()); -"foo-bar-baz-quz", " foo-bar-baz-quz ".parse::().unwrap().to_string()); -"cfg(foo)", &Cfg::Is("foo".to_string()).to_string()); -"cfg(not(foo))", &Cfg::Not(Box::new(Cfg::Is("foo".to_string()))).to_string()); -"cfg(not(any(foo, bar))) -""" - testdata = [ + # None means expression is unparsable ('cfg(ok)', True), + ('cfg(not_ok)', False), ('cfg( ok )', True), - ('cfg(foo-bar-bar)', True), + ('cfg(foo-bar-bar)', None), + ('cfg(foo_bar_bar)', True), (' foo-bar-bar ', False), - (' cfg(not(foo))', True), - (' cfg(not(foo foo))', False), + (' cfg(not(foo))', False), + (' cfg(not(foo foo))', None), ('cfg(any(asdf, asdf))', True), ('cfg(all(asdf, asdf))', True), - ('cfg(any())', False), - ('cfg(all())', False), - ('cfg(all(not(asdf)))', True), - ('cfg(all(not(any(all(asdf)))))', True), + ('cfg(any())', None), + ('cfg(all())', None), + ('cfg(all(not(asdf)))', False), + ('cfg(all(not(any(all(asdf)))))', False), + ('cfg(foo="bar")', True), + ('cfg(foo = "bar")', True), + ('cfg(foo = " bar ")', True), + ('cfg(foo = "not bar")', False), + ('cfg(foo=bar)', None), + ('cfg(foo foo = = " bar ")', None), + ('cfg(foo = foo = " bar ")', None), ] -@pytest.mark.parametrize("expression,ok", testdata) -def test_parsing(expression, ok): +@pytest.fixture +def asdf_evaluator(): + options = ('ok', + 'foo_bar_bar', + 'foo', + 'asdf', + ('foo', 'bar'), + ('foo', ' bar ')) + return rustcfg.Evaluator(options=options) + +@pytest.mark.parametrize("expression,result", testdata) +def test_parsing(expression, result, asdf_evaluator): g = rustcfg.cfg_grammar() try: t = g.parseString(expression) @@ -34,4 +45,8 @@ def test_parsing(expression, ok): good = False else: good = True - assert good == ok + assert good == (result is not None) + + if result is not None: + res = asdf_evaluator.eval_tree(t) + assert res == result