Page MenuHomePhorge

rules.py
No OneTemporary

Size
4 KB
Referenced Files
None
Subscribers
None

rules.py

import re
# XXX: Since we are stuck with Python 3.9, we can't disable backtrack yet.
# https://docs.python.org/3/library/re.html
patterns = {
'OP': re.compile(r'\s*(==|\|\||&&|=~|\!=|\!~|\!)'),
'PAREN_LEFT': re.compile(r'\s*\('),
'PAREN_RIGHT': re.compile(r'\s*\)'),
'STR_DOUBLE': 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_]*)'),
'NULL': re.compile(r'\s*null'),
'END': re.compile(r'\s*$'),
}
ops = {
'==': (2, lambda a, b: a == b),
'!=': (2, lambda a, b: a != b),
'=~': (2, lambda a, b: not not re.search(b, a)),
'!~': (2, lambda a, b: not re.search(b, a)),
'!': (1, lambda a: not a),
'&&': (2, lambda a, b: a and b),
'||': (2, lambda a, b: a or b),
}
TERM_PRECEDENCE = 9
PAREN_PRECEDENCE = 8
def is_term(token):
return token[0] in ['STR_DOUBLE', 'STR_SINGLE', 'REGEX', 'VAR', 'NULL']
def get_precedence(token):
if is_term(token):
return TERM_PRECEDENCE
elif token[0] in ['PAREN_LEFT', 'PAREN_RIGHT']:
return PAREN_PRECEDENCE
elif token[0] == 'OP' and (token[1][0] in ['==', '!=', '=~', '!~']):
return 6
elif token[0] == 'OP' and (token[1][0] in ['!']):
return 5
elif token[0] == 'OP' and (token[1][0] in ['&&']):
return 4
elif token[0] == 'OP' and (token[1][0] in ['||']):
return 3
raise SyntaxError('Unknown token')
def tokenize_rule(rule_str):
pos = 0
tokenized = []
while not (len(tokenized) and tokenized[-1][0] == 'END'):
match = None
for t in patterns:
regex = patterns[t]
match = regex.match(rule_str, pos)
if match:
tokenized.append((t, match.groups()))
pos = match.end()
break
if not match:
raise SyntaxError(f'Bad rule "{rule_str}", at pos {pos}')
return tokenized[:-1]
def make_tree(tokenized):
# https://en.wikipedia.org/wiki/Shunting_yard_algorithm
stack = []
res = []
for t in tokenized:
if is_term(t):
res.append(t)
elif t[0] == 'OP':
if len(stack) and stack[-1][0] == 'OP' and get_precedence(stack[-1]) >= get_precedence(t):
res.append(stack.pop())
stack.append(t)
elif t[0] == 'PAREN_LEFT':
stack.append(t)
elif t[0] == 'PAREN_RIGHT':
while len(stack) and stack[-1][0] != 'PAREN_LEFT':
res.append(stack.pop())
if not len(stack):
raise SyntaxError('Mismatched parentheses')
stack.pop()
while len(stack):
t = stack.pop()
if t[0] == 'PAREN_LEFT':
raise SyntaxError('Mismatched parentheses')
res.append(t)
stack = []
for t in res:
if t[0] == 'OP':
opname = t[1][0]
arity = ops[opname][0]
if len(stack) < arity:
raise SyntaxError('Missing operands')
operands = tuple(reversed([stack.pop() for i in range(arity)]))
stack.append((t[0], (opname,) + operands))
else:
stack.append(t)
if len(stack) != 1:
raise SyntaxError('Too many operands')
return stack[0]
def parse_rule(rule_str):
return make_tree(tokenize_rule(rule_str))
backslash_re = re.compile(r'\\(.)')
def replace_backslash(match):
c = match.groups()[0]
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):
if expr[0] == 'VAR':
return variables.get(expr[1][0])
elif expr[0] == 'STR_DOUBLE' or expr[0] == 'STR_SINGLE':
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':
return None
elif expr[0] == 'OP':
opname = expr[1][0]
operands = [evaluate_rule(o, variables) for o in expr[1][1:]]
return ops[opname][1](*operands)
raise SyntaxError(f'Cannot evaluate expression {expr}')

File Metadata

Mime Type
text/x-python
Expires
Tue, Jan 20, 12:08 PM (1 h, 7 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
971961
Default Alt Text
rules.py (4 KB)

Event Timeline