~trufas/ledgeroni

ref: 7a9e4ad4a64b45840f93b4ab44b6c2e463e16b13 ledgeroni/ledgeroni/expression.py -rw-r--r-- 2.8 KiB
7a9e4ad4 — Rafael Castillo Clean up stdout 1 year, 10 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import re
from collections import deque

from ledgeroni.query import RegexQuery, Or, Not, And, PayeeQuery

TOKEN_REGEX = re.compile(r'(?P<token>\(|\)|@|[^\s\(\)\@]+)')

def tokenize_expression(expr_str):
    expr_str = expr_str.lstrip()
    match = TOKEN_REGEX.match(expr_str)
    while match is not None:
        token = match.group('token')
        yield token
        expr_str = expr_str[len(token):].lstrip()
        match = TOKEN_REGEX.match(expr_str)


PRECEDENCE = {'and': 1, 'or': 1, 'not': 2, '@': 2, 'payee': 3, '(': 0}

def build_postfix_expression(expr_str):
    operator_stack = []
    output = deque()
    last_was_expr = False
    for token in tokenize_expression(expr_str):
        if token == '(':
            if last_was_expr:
                flush_op_stack('or', operator_stack, output)
                operator_stack.append('or')
            operator_stack.append(token)
        elif token == ')':
            while operator_stack:
                op = operator_stack.pop()
                if op == '(':
                    break
                output.append(op)
            if op != '(':
                raise ValueError
        elif token in ('and', 'or', 'not', '@', 'payee'):
            flush_op_stack(token, operator_stack, output)
            operator_stack.append(token)
        else:
            if last_was_expr:
                flush_op_stack('or', operator_stack, output)
                operator_stack.append('or')
            output.append(token)

        last_was_expr = token not in PRECEDENCE.keys()

    output += list(operator_stack[::-1])

    return output


def flush_op_stack(operator, operator_stack, output):
    while (operator_stack 
           and PRECEDENCE[operator_stack[-1]] >= PRECEDENCE[operator]):
        output.append(operator_stack.pop())


def build_expression(expr_str):
    postfix_expr = build_postfix_expression(expr_str)
    expr = build_expr_from_postfix(postfix_expr)

    return expr


def build_expr_from_postfix(postfix_expr):
    operand_stack = deque()
    for token in postfix_expr:
        if token == 'and':
            a, b = operand_stack.pop(), operand_stack.pop()
            operand_stack.append(And((a, b)))
        elif token == 'or':
            a, b = operand_stack.pop(), operand_stack.pop()
            operand_stack.append(Or((a, b)))
        elif token == 'not':
            a = operand_stack.pop()
            operand_stack.append(Not(a))
        elif token in ('payee', '@'):
            a = operand_stack.pop()
            if not isinstance(a, RegexQuery):
                raise ValueError
            operand_stack.append(PayeeQuery(a))
        else:
            operand = RegexQuery(re.compile(token))
            operand_stack.append(operand)

    if len(operand_stack) != 1:
        raise ValueError

    return operand_stack[0]