#!python # vim: ts=8 sts=4 et si sw=4 ft=python """ evaluate range expressions for integer values and create a Python expression and filter function """ VERSION = '.'.join(map(str, (0, 1, # initial version )) ) __author__ = 'Tobias Herp' _comparator = { '>': '>', '<': '<', '==': '==', '>=': '>=', '<=': '<=', '<>': '<>', # C etc.: '!=': '<>', # Pascal, Rexx: '=': '==', '\xbf=': '<>', r'\=': '<>', '\xbf>': '<=', r'\>': '<=', '\xbf<': '>=', r'\<': '>=', } _logical = { 'and': 'and', 'or': 'or', '&&': 'and', '||': 'or', '&': 'and', '|': 'or', } _range = { ',': '..', '-': '..', '..': '..', '...': '..', ';': '..', } _ch_parentheses = tuple('()[]') _ch_comparators = tuple('<>=!') _ch_logical = tuple('&|') _ch_range = tuple('.-,;') WHITESPACE, COMPARE, LOGICAL, PARENTH, NUMBER, NAME, RANGE, \ COMPOUND = range(8) normal = ( lambda s: ' ', # WHITESPACE lambda s: _comparator[s], # COMPARE lambda s: _logical[s.lower()], # LOGICAL lambda s: s, # PARENTH lambda s: str(int(s)), # NUMBER lambda s: s, # NAME lambda s: _range[s], # RANGE ) def init_chartable(): tmp = {} from string import whitespace, digits, letters, printable for ch in printable: if ch in whitespace: tmp[ch] = WHITESPACE elif ch in digits: tmp[ch] = NUMBER elif ch in letters: tmp[ch] = NAME elif ch in _ch_parentheses: tmp[ch] = PARENTH elif ch in _ch_comparators: tmp[ch] = COMPARE elif ch in _ch_logical: tmp[ch] = LOGICAL elif ch in _ch_range: tmp[ch] = RANGE return tmp chartype = init_chartable() def parse(s): """ parse an filtering expression for integer values and yield (type, token) tuples """ try: buf = [] prevmode = None for ch in s+' ': newmode = chartype[ch] if newmode == prevmode: buf.append(ch) elif newmode == NUMBER and prevmode == NAME: buf.append(ch) else: if prevmode is not None: tok = ''.join(buf) if prevmode is NAME: try: yield LOGICAL, _logical[tok] except: yield NAME, tok else: yield prevmode, normal[prevmode](''.join(buf)) del buf[:] buf.append(ch) prevmode = newmode except KeyError, k: raise ExpressionError(k) class ExpressionError(ValueError): """ Error in integer range expression """ def tupel_info(ty, to): typename = ['WHITESPACE', 'COMPARE', 'LOGICAL', 'PARENTH', 'NUMBER', 'NAME', 'RANGE', 'COMPOUND'] print '*** %s:\t%s' % (typename[ty], to) def completed(s): """ parses the given string (using the 'parse' generator function) and adds implicit variable (and needed whitespace) tokens """ prevtype = None varname = None for newtype, token in parse(s): # tupel_info(newtype, token) if newtype == NAME: if varname is None: varname = token elif varname != token: raise ExpressionError('Only one varname allowed (%s, %s)' % (varname, token)) if newtype != prevtype: pass elif newtype == PARENTH: pass else: raise ExpressionError('2nd token of same type (%s, %s)' % (token, newtype)) if (prevtype in (NAME, NUMBER, LOGICAL) and newtype in (NAME, NUMBER, LOGICAL)): yield WHITESPACE, ' ' if (newtype is COMPARE and prevtype in (None, PARENTH, LOGICAL)): if varname is None: varname = 'x' if prevtype in (LOGICAL,): yield WHITESPACE, ' ' yield NAME, varname if newtype: yield newtype, token prevtype = newtype def formatted(s): res = [] prevtype = None for newtype, token in completed(s): if (newtype in (PARENTH, RANGE) or prevtype in (None, WHITESPACE, PARENTH, RANGE) ) and not ( prevtype in (NAME, NUMBER, LOGICAL) and newtype in (NAME, NUMBER, LOGICAL) ): pass else: res.append(' ') res.append(str(token)) if newtype: prevtype = newtype return ''.join(res) def expressify(s): """ create a boolean expression """ res = [] prevtype = None varname = None has_comparison = 0 has_value = 0 for newtype, token in completed(s): if newtype == NAME: if varname is None: varname = token elif newtype == NUMBER: has_value = 1 elif newtype == COMPARE: has_comparison = 1 if (newtype in (PARENTH, RANGE) or prevtype in (None, PARENTH, RANGE) ): pass else: res.append(WHITESPACE, ' ') res.append((newtype, token)) if len(res) >= 5: if (res[-1][0] is PARENTH and res[-2][0] is NUMBER and res[-3][0] is RANGE and res[-4][0] is NUMBER and res[-5][0] is PARENTH ): tmpl = ['('] if res[-5][1] in '(]': tmpl.extend((res[-4][1], ' ', '<')) else: tmpl.extend((res[-4][1], ' ', '<=')) if varname is None: varname = 'x' tmpl.extend((' ', varname, ' ')) if res[-1][1] in ')[': tmpl.extend(('<', ' ', res[-2][1])) else: tmpl.extend(('<=', ' ', res[-2][1])) tmpl.append(')') del res[-5:] has_value, has_comparison = 1, 1 res.append((COMPOUND, ''.join(tmpl))) if not (has_value and has_comparison): raise ExpressionError('at least a comparison value and ' 'operator required') return ''.join([tup[1] for tup in res]), varname def makefilter(s): if not s.strip(): return lambda x: 1 try: expr, varname = expressify(s) return eval('lambda %s: %s' % (varname, expr)) except SyntaxError, e: raise ExpressionError(e) if __name__ == '__main__': try: from errors import err, check_errors except ImportError: print 'Sorry, errors module not present' def _(s): return s # Dummy for 18n try: from enhopa import OptionParser, OptionGroup except: from optparse import OptionParser, OptionGroup parser = OptionParser(usage="%prog [options] {filter expression} | --help", version=VERSION) parser.set_description('%prog parses an expression, e.g. ' '">5" or "[2,5]", and applies it to the given integer range. ' 'Supports interval specs in ISO or set builder syntax.') parser.add_option('--verbose', '-v', action='count', help='be verbose (-vv: even more verbose)') group_ = OptionGroup(parser, 'Specific options') group_.add_option('--min', default=1, type='int', action='store', metavar='1', help='minimal value of filtered integer pool' ' (default: %default)') group_.add_option('--max', default=10, type='int', action='store', metavar='10', help='maximal value of filtered integer pool' ' (default: %default)') parser.add_option_group(group_) try: parser.set_collecting_group() except AttributeError: pass (option, args) = parser.parse_args() if not args: err(_('nothing to do')) check_errors() pool = range(option.min, option.max+1) print 'pool: [%d..%d]' % (option.min, option.max) for a in args: print print 'specified: %s' % a if option.verbose > 1: print 'completed:', [tup[1] for tup in completed(a)] filt = makefilter(a) if option.verbose: print 'filter expression: %s' % (expressify(a)[0]) print 'filtered pool:' print ' ', [n for n in pool if filt(n)]