Changeset View
Changeset View
Standalone View
Standalone View
lilybuild/lilybuild/ci_syntax/rules.py
| import re | import re | ||||
| # XXX: Since we are stuck with Python 3.9, we can't disable backtrack yet. | # XXX: Since we are stuck with Python 3.9, we can't disable backtrack yet. | ||||
| # https://docs.python.org/3/library/re.html | # https://docs.python.org/3/library/re.html | ||||
| patterns = { | patterns = { | ||||
| 'OP': re.compile(r'\s*(==|\|\||&&|=~|\!=|\!~|\!)'), | 'OP': re.compile(r'\s*(==|\|\||&&|=~|\!=|\!~|\!)'), | ||||
| 'PAREN_LEFT': re.compile(r'\s*\('), | 'PAREN_LEFT': re.compile(r'\s*\('), | ||||
| 'PAREN_RIGHT': re.compile(r'\s*\)'), | 'PAREN_RIGHT': re.compile(r'\s*\)'), | ||||
| 'STR_DOUBLE': re.compile(r'''\s*"((?:[^\\"]|\\.)*)"'''), | 'STR_DOUBLE': re.compile(r'''\s*"((?:[^\\"]|\\.)*)"'''), | ||||
| 'STR_SINGLE': re.compile(r"""\s*'((?:[^\\']|\\.)*)'"""), | 'STR_SINGLE': re.compile(r"""\s*'((?:[^\\']|\\.)*)'"""), | ||||
| 'REGEX': re.compile(r'''\s*/((?:[^\\/]|\\.)*)/'''), | |||||
| 'VAR': re.compile(r'\s*\$([A-Za-z_][A-Za-z0-9_]*)'), | 'VAR': re.compile(r'\s*\$([A-Za-z_][A-Za-z0-9_]*)'), | ||||
| 'NULL': re.compile(r'\s*null'), | 'NULL': re.compile(r'\s*null'), | ||||
| 'END': re.compile(r'\s*$'), | 'END': re.compile(r'\s*$'), | ||||
| } | } | ||||
| ops = { | ops = { | ||||
| '==': (2, lambda a, b: a == b), | '==': (2, lambda a, b: a == b), | ||||
| '!=': (2, lambda a, b: a != b), | '!=': (2, lambda a, b: a != b), | ||||
| '=~': (2, lambda a, b: False), | '=~': (2, lambda a, b: not not re.search(b, a)), | ||||
| '!~': (2, lambda a, b: False), | '!~': (2, lambda a, b: not re.search(b, a)), | ||||
| '!': (1, lambda a: not a), | '!': (1, lambda a: not a), | ||||
| '&&': (2, lambda a, b: a and b), | '&&': (2, lambda a, b: a and b), | ||||
| '||': (2, lambda a, b: a or b), | '||': (2, lambda a, b: a or b), | ||||
| } | } | ||||
| TERM_PRECEDENCE = 9 | TERM_PRECEDENCE = 9 | ||||
| PAREN_PRECEDENCE = 8 | PAREN_PRECEDENCE = 8 | ||||
| def is_term(token): | def is_term(token): | ||||
| return token[0] in ['STR_DOUBLE', 'STR_SINGLE', 'VAR', 'NULL'] | return token[0] in ['STR_DOUBLE', 'STR_SINGLE', 'REGEX', 'VAR', 'NULL'] | ||||
| def get_precedence(token): | def get_precedence(token): | ||||
| if is_term(token): | if is_term(token): | ||||
| return TERM_PRECEDENCE | return TERM_PRECEDENCE | ||||
| elif token[0] in ['PAREN_LEFT', 'PAREN_RIGHT']: | elif token[0] in ['PAREN_LEFT', 'PAREN_RIGHT']: | ||||
| return PAREN_PRECEDENCE | return PAREN_PRECEDENCE | ||||
| elif token[0] == 'OP' and (token[1][0] in ['==', '!=', '=~', '!~']): | elif token[0] == 'OP' and (token[1][0] in ['==', '!=', '=~', '!~']): | ||||
| return 6 | return 6 | ||||
| ▲ Show 20 Lines • Show All 64 Lines • ▼ Show 20 Lines | |||||
| def parse_rule(rule_str): | def parse_rule(rule_str): | ||||
| return make_tree(tokenize_rule(rule_str)) | return make_tree(tokenize_rule(rule_str)) | ||||
| backslash_re = re.compile(r'\\(.)') | backslash_re = re.compile(r'\\(.)') | ||||
| def replace_backslash(match): | def replace_backslash(match): | ||||
| c = match.groups()[0] | c = match.groups()[0] | ||||
| return c | return c | ||||
| def replace_backslash_in_regex(match): | |||||
| c = match.groups()[0] | |||||
| if c == '/': | |||||
| return c | |||||
| # Treat everything except \/ as is | |||||
| return '\\' + c | |||||
| def evaluate_rule(expr, variables): | def evaluate_rule(expr, variables): | ||||
| if expr[0] == 'VAR': | if expr[0] == 'VAR': | ||||
| return variables.get(expr[1][0]) | return variables.get(expr[1][0]) | ||||
| elif expr[0] == 'STR_DOUBLE' or expr[0] == 'STR_SINGLE': | elif expr[0] == 'STR_DOUBLE' or expr[0] == 'STR_SINGLE': | ||||
| return backslash_re.sub(replace_backslash, expr[1][0]) | return backslash_re.sub(replace_backslash, expr[1][0]) | ||||
| elif expr[0] == 'REGEX': | |||||
| return backslash_re.sub(replace_backslash_in_regex, expr[1][0]) | |||||
| elif expr[0] == 'NULL': | elif expr[0] == 'NULL': | ||||
| return None | return None | ||||
| elif expr[0] == 'OP': | elif expr[0] == 'OP': | ||||
| opname = expr[1][0] | opname = expr[1][0] | ||||
| operands = [evaluate_rule(o, variables) for o in expr[1][1:]] | operands = [evaluate_rule(o, variables) for o in expr[1][1:]] | ||||
| return ops[opname][1](*operands) | return ops[opname][1](*operands) | ||||
| raise SyntaxError(f'Cannot evaluate expression {expr}') | raise SyntaxError(f'Cannot evaluate expression {expr}') | ||||