Changeset 3b81644b in mainline


Ignore:
Timestamp:
2019-04-06T11:46:43Z (5 years ago)
Author:
Petr Pavlu <setup@…>
Children:
4630eea
Parents:
3daba42e
git-author:
Petr Pavlu <setup@…> (2019-03-31 08:24:23)
git-committer:
Petr Pavlu <setup@…> (2019-04-06 11:46:43)
Message:

Add support for compound conditions in HelenOS.config

Allow use of compound expressions in HelenOS.config instead of limiting
the expressions to only CNF and DNF.

Example use:
% Output device class
@ "generic" Monitor or serial line
! [(PLATFORM=arm32&(MACHINE=gta02|MACHINE=integratorcp|MACHINE=beagleboardxm|MACHINE=beaglebone|MACHINE=raspberrypi))|PLATFORM=arm64] CONFIG_HID_OUT (choice)

File:
1 edited

Legend:

Unmodified
Added
Removed
  • tools/config.py

    r3daba42e r3b81644b  
    4747PRESETS_DIR = 'defaults'
    4848
     49class BinaryOp:
     50        def __init__(self, operator, left, right):
     51                assert operator in ('&', '|', '=', '!=')
     52
     53                self._operator = operator
     54                self._left = left
     55                self._right = right
     56
     57        def evaluate(self, config):
     58                if self._operator == '&':
     59                        return self._left.evaluate(config) and \
     60                            self._right.evaluate(config)
     61                if self._operator == '|':
     62                        return self._left.evaluate(config) or \
     63                            self._right.evaluate(config)
     64
     65                # '=' or '!='
     66                if not self._left in config:
     67                        config_val = ''
     68                else:
     69                        config_val = config[self._left]
     70                        if config_val == '*':
     71                                config_val = 'y'
     72
     73                if self._operator == '=':
     74                        return self._right == config_val
     75                return self._right != config_val
     76
     77# Expression parser
     78class CondParser:
     79        TOKEN_EOE = 0
     80        TOKEN_SPECIAL = 1
     81        TOKEN_STRING = 2
     82
     83        def __init__(self, text):
     84                self._text = text
     85
     86        def parse(self):
     87                self._position = -1
     88                self._next_char()
     89                self._next_token()
     90
     91                res = self._parse_expr()
     92                if self._token_type != self.TOKEN_EOE:
     93                        self._error("Expected end of expression")
     94                return res
     95
     96        def _next_char(self):
     97                self._position += 1
     98                if self._position >= len(self._text):
     99                        self._char = None
     100                else:
     101                        self._char = self._text[self._position]
     102                self._is_special_char = self._char in \
     103                    ('&', '|', '=', '!', '(', ')')
     104
     105        def _error(self, msg):
     106                raise RuntimeError("Error parsing expression: %s:\n%s\n%s^" %
     107                    (msg, self._text, " " * self._token_position))
     108
     109        def _next_token(self):
     110                self._token_position = self._position
     111
     112                # End of expression
     113                if self._char == None:
     114                        self._token = None
     115                        self._token_type = self.TOKEN_EOE
     116                        return
     117
     118                # '&', '|', '=', '!=', '(', ')'
     119                if self._is_special_char:
     120                        self._token = self._char
     121                        self._next_char()
     122                        if self._token == '!':
     123                                if self._char != '=':
     124                                        self._error("Expected '='")
     125                                self._token += self._char
     126                                self._next_char()
     127                        self._token_type = self.TOKEN_SPECIAL
     128                        return
     129
     130                # <var> or <val>
     131                self._token = ''
     132                self._token_type = self.TOKEN_STRING
     133                while True:
     134                        self._token += self._char
     135                        self._next_char()
     136                        if self._is_special_char or self._char == None:
     137                                break
     138
     139        def _parse_expr(self):
     140                """ <expr> ::= <or_expr> ('&' <or_expr>)* """
     141
     142                left = self._parse_or_expr()
     143                while self._token == '&':
     144                        self._next_token()
     145                        left = BinaryOp('&', left, self._parse_or_expr())
     146                return left
     147
     148        def _parse_or_expr(self):
     149                """ <or_expr> ::= <factor> ('|' <factor>)* """
     150
     151                left = self._parse_factor()
     152                while self._token == '|':
     153                        self._next_token()
     154                        left = BinaryOp('|', left, self._parse_factor())
     155                return left
     156
     157        def _parse_factor(self):
     158                """ <factor> ::= <var> <cond> | '(' <expr> ')' """
     159
     160                if self._token == '(':
     161                        self._next_token()
     162                        res = self._parse_expr()
     163                        if self._token != ')':
     164                                self._error("Expected ')'")
     165                        self._next_token()
     166                        return res
     167
     168                if self._token_type == self.TOKEN_STRING:
     169                        var = self._token
     170                        self._next_token()
     171                        return self._parse_cond(var)
     172
     173                self._error("Expected '(' or <var>")
     174
     175        def _parse_cond(self, var):
     176                """ <cond> ::= '=' <val> | '!=' <val> """
     177
     178                if self._token not in ('=', '!='):
     179                        self._error("Expected '=' or '!='")
     180
     181                oper = self._token
     182                self._next_token()
     183
     184                if self._token_type != self.TOKEN_STRING:
     185                        self._error("Expected <val>")
     186
     187                val = self._token
     188                self._next_token()
     189
     190                return BinaryOp(oper, var, val)
     191
    49192def read_config(fname, config):
    50193        "Read saved values from last configuration run or a preset file"
     
    59202        inf.close()
    60203
    61 def check_condition(text, config, rules):
    62         "Check that the condition specified on input line is True (only CNF and DNF is supported)"
    63 
    64         ctype = 'cnf'
    65 
    66         if (')|' in text) or ('|(' in text):
    67                 ctype = 'dnf'
    68 
    69         if ctype == 'cnf':
    70                 conds = text.split('&')
    71         else:
    72                 conds = text.split('|')
    73 
    74         for cond in conds:
    75                 if cond.startswith('(') and cond.endswith(')'):
    76                         cond = cond[1:-1]
    77 
    78                 inside = check_inside(cond, config, ctype)
    79 
    80                 if (ctype == 'cnf') and (not inside):
    81                         return False
    82 
    83                 if (ctype == 'dnf') and inside:
    84                         return True
    85 
    86         if ctype == 'cnf':
    87                 return True
    88 
    89         return False
    90 
    91 def check_inside(text, config, ctype):
    92         "Check for condition"
    93 
    94         if ctype == 'cnf':
    95                 conds = text.split('|')
    96         else:
    97                 conds = text.split('&')
    98 
    99         for cond in conds:
    100                 res = re.match(r'^(.*?)(!?=)(.*)$', cond)
    101                 if not res:
    102                         raise RuntimeError("Invalid condition: %s" % cond)
    103 
    104                 condname = res.group(1)
    105                 oper = res.group(2)
    106                 condval = res.group(3)
    107 
    108                 if not condname in config:
    109                         varval = ''
    110                 else:
    111                         varval = config[condname]
    112                         if (varval == '*'):
    113                                 varval = 'y'
    114 
    115                 if ctype == 'cnf':
    116                         if (oper == '=') and (condval == varval):
    117                                 return True
    118 
    119                         if (oper == '!=') and (condval != varval):
    120                                 return True
    121                 else:
    122                         if (oper == '=') and (condval != varval):
    123                                 return False
    124 
    125                         if (oper == '!=') and (condval == varval):
    126                                 return False
    127 
    128         if ctype == 'cnf':
    129                 return False
    130 
    131         return True
    132 
    133204def parse_rules(fname, rules):
    134205        "Parse rules file"
     
    149220
    150221                        cond = res.group(1)
     222                        if cond:
     223                                cond = CondParser(cond).parse()
    151224                        varname = res.group(2)
    152225                        vartype = res.group(3)
     
    232305                varname, vartype, name, choices, cond = rule
    233306
    234                 if cond and (not check_condition(cond, config, rules)):
     307                if cond and not cond.evaluate(config):
    235308                        continue
    236309
     
    279352
    280353        # First check that this rule would make sense
    281         if cond:
    282                 if not check_condition(cond, config, rules):
    283                         return random_choices(config, rules, start_index + 1)
     354        if cond and not cond.evaluate(config):
     355                return random_choices(config, rules, start_index + 1)
    284356
    285357        # Remember previous choices for backtracking
     
    487559
    488560        for varname, vartype, name, choices, cond in rules:
    489                 if cond and (not check_condition(cond, config, rules)):
     561                if cond and not cond.evaluate(config):
    490562                        continue
    491563
     
    676748                                varname, vartype, name, choices, cond = rule
    677749
    678                                 if cond and (not check_condition(cond, config, rules)):
     750                                if cond and not cond.evaluate(config):
    679751                                        continue
    680752
Note: See TracChangeset for help on using the changeset viewer.