def operatorPrecedence(base, operators):
    expression = Forward()
    last = base | Suppress('(') + expression + Suppress(')')
    def parse_operator(expr, arity, association, action=None, extra=None):
        return expr, arity, association, action, extra
    for op in operators:
        expr, arity, association, action, extra = parse_operator(*op)
        if arity < 1 or arity > 2:
            raise Exception("Arity must be unary (1) or binary (2)")
        if association not in (opAssoc.LEFT, opAssoc.RIGHT):
            raise Exception("Association must be LEFT or RIGHT")
        this = Forward()
        if association is opAssoc.LEFT:
            new_last = (last | extra) if extra else last
            if arity == 1:
                operator_expression = new_last + OneOrMore(expr)
            elif arity == 2:
                operator_expression = last + OneOrMore(expr + new_last)
        elif association is opAssoc.RIGHT:
            new_this = (this | extra) if extra else this
            if arity == 1:
                operator_expression = expr + new_this
            elif arity == 2:
                operator_expression = last + OneOrMore(new_this)
        if action is not None:
            operator_expression.setParseAction(action)
        this <<= (operator_expression | last)
        last = this
    expression <<= last
    return expression