🧮 Propositional logic calculator
from src.entity.operators import *


def decode_expression(expr):
    if isinstance(expr, Operator) or isinstance(expr, Negation):
        return str(expr)
    else:
        return expr

src/data/decode.py
baskerbyte/predimate
from src.entity.operators import *


def encode_expression(expr: str) -> Base:
    """
    Recursively encodes a logical expression represented as a string into a tree of logical operations.

    Args:
        expr (str): The logical expression to be encoded.

    Returns:
        Base: The root of the encoded logical expression tree.

    Raises:
        ValueError: If there are unbalanced parentheses in the expression.
    """

    i = 0
    result = []

    while i < len(expr):
        if expr[i] == '(':
            # Ignore '('
            i += 1
            # Sum `i` to find the real position of parenthesis in current expr
            # Add 1 to the result to move to the character immediately after the closing parenthesis.
            j = find_parentheses_end(expr[i:]) + i + 1

            result.append(encode_expression(expr[i:j]))
            # Ignore everything inside the parentheses, because it will be solved in the recursion above
            i = j
        elif expr[i] == ')':
            # Ignore ')'
            i += 1
        # Append if char is a real preposition, like: P, Q, R. Ignore 'v' because it's an operator
        elif expr[i] != 'v' and expr[i].isalpha():
            result.append(expr[i].upper())
            i += 1
        else:
            # The operator with the most characters is <-> (3), so the substring
            operator = find_operator(expr[i:i + 3])
            i += len(operator)

            # The behavior of negation is different from the other operators
            # While operators have a left and right side to the expression, negation has only one parameter
            if operator != '~':
                right = encode_expression(expr[i:])
                left = result.pop()

                return operators[operator](left, right)
            else:
                # After the negation there can be a parenthesis or a literal, as in ~(P^Q) or ~P
                if expr[i] == '(':
                    i += 1
                    j = find_parentheses_end(expr[i:]) + i
                    sub_result = encode_expression(expr[i:j])
                    i = j
                else:
                    sub_result = encode_expression(expr[i])
                    i += 1

                result.append(Negation(sub_result))

    if len(result) == 1:
        return result[0]


def find_parentheses_end(expr: str) -> int:
    """
    Finds the index of the closing parenthesis that corresponds to the opening parenthesis at the start of `expr`.

    Args:
        expr (str): The string starting with an opening parenthesis.

    Returns:
        int: The index of the closing parenthesis.

    Raises:
        ValueError: If the parentheses are unbalanced.
    """
    # In the expr the first parenthesis is ignored, so we start the count with 1
    count = 1

    for i, char in enumerate(expr):
        if char == '(':
            count += 1
        elif char == ')':
            count -= 1
            if count == 0:
                return i

    raise ValueError("Parênteses desequilibrados")


def find_operator(expr: str) -> str:
    """
    Finds the logical operator at the beginning of the expression.

    Args:
        expr (str): The string to search for a logical operator.

    Returns:
        str: The found logical operator or None if none is found.
    """

    found_operator = None

    for operator in operators:
        # Use startswith to avoid false truths like in PvQ, making sure that the start is an operator
        if expr.startswith(operator):
            found_operator = operator
            break

    return found_operator

src/data/encode.py
baskerbyte/predimate
class Base:
    def __repr__(self):
        class_name = self.__class__.__name__
        attributes = ", ".join(f"{attr}={getattr(self, attr)!r}" for attr in vars(self) if not attr.startswith('_'))

        return f"{class_name}({attributes})"

    def __eq__(self, other):
        if isinstance(other, self.__class__):
            return all(getattr(self, attr) == getattr(other, attr) for attr in vars(self) if not attr.startswith('_'))

        return False

src/entity/base.py
baskerbyte/predimate
from src.entity.base import Base


class Operator(Base):
    """
    Base class for logical operators in a logical expression tree.

    Attributes:
        left: The left operand of the operator.
        right: The right operand of the operator.
    """

    def __init__(self, left, right):
        self.left = left
        self.right = right

        # String representations of left and right operands
        self._left_str = f'({self.left})' if isinstance(self.left, Operator) else str(self.left)
        self._right_str = f'({self.right})' if isinstance(self.right, Operator) else str(self.right)

    def __str__(self):
        return f'{self._left_str}{self.symbol}{self._right_str}'


class Conjunction(Operator):
    """
    Represents the logical `AND` operation in a logical expression tree.
    """

    symbol = '^'


class Disjunction(Operator):
    """
    Represents the logical `OR` operation in a logical expression tree.
    """

    symbol = 'v'


class Conditional(Operator):
    """
    Represents the logical `NOT` `OR` operation in a logical expression tree.
    """

    symbol = '->'


class BiConditional(Operator):
    """
    Represents the logical `EQ` operation in a logical expression tree.
    """

    symbol = '<->'


class Negation(Base):
    """
   Represents the logical `NOT` operation in a logical expression tree.

   Attributes:
       expr: The expression being negated.
   """

    def __init__(self, expr):
        self.expr = expr

    def __str__(self):
        expr_str = f'({self.expr})' if isinstance(self.expr, Operator) or isinstance(self.expr, Negation) \
            else str(self.expr)

        return f'~{expr_str}'


operators = {
    '^': Conjunction,
    'v': Disjunction,
    '->': Conditional,
    '<->': BiConditional,
    '~': Negation
}

src/entity/operators.py
baskerbyte/predimate
from texttable import Texttable

from data.encode import encode_expression
from truth_table.table import generate_combinations, evaluate_expressions, table_type


def print_main_header():
    header = """
      _____              _ _                 _       
     |  __ \            | (_)               | |      
     | |__) | __ ___  __| |_ _ __ ___   __ _| |_ ___ 
     |  ___/ '__/ _ \/ _` | | '_ ` _ \ / _` | __/ _ \\
     | |   | | |  __/ (_| | | | | | | | (_| | ||  __/
     |_|   |_|  \___|\__,_|_|_| |_| |_|\__,_|\__\___|
    """

    print(header)


def get_menu_choice():
    print("\nMenu Principal:")
    print("1 - Tabela Verdade")
    print("0 - Sair\n")

    return int(input("Escolha uma opção: "))


def parse_input(item, prepositions, expressions,):
    wrapped = encode_expression(item)

    if wrapped is None:
        quit(f"Argumento \"{item}\" inválido")

    if wrapped not in expressions:
        expressions.append(wrapped)

    for prep in list(filter(lambda char: char.isalpha() and char != 'v', item)):
        if prep not in prepositions:
            prepositions.append(prep.upper())


def get_input_list(prompt, prepositions, expressions, repeat=True):
    if repeat:
        while True:
            item = input(prompt)

            if item == "0":
                break

            parse_input(item, prepositions, expressions)
    else:
        item = input(prompt)

        if item == "0":
            return

        parse_input(item, prepositions, expressions)
    prepositions.sort()


def main():
    print_main_header()

    while True:
        menu = get_menu_choice()

        if menu != 0:
            prepositions = []
            expressions = []

            get_input_list("Digite uma premissa (ou 0 para pular): ", prepositions, expressions)
            get_input_list("Digite a conclusão (ou 0 para pular): ", prepositions, expressions, False)

            if menu == 1:
                combinations = generate_combinations(prepositions)
                values = evaluate_expressions(prepositions, expressions, combinations)
                expanded_expressions = [result.pop(0) for result in values]

                # Orients values in rows
                rows = list(zip(*values))
                # Adds the preposition rows to the results
                rows = [list(combinations[i]) + list(rows[i]) for i in range(len(rows))] if rows else combinations

                header = prepositions + list(map(lambda x: str(x), expanded_expressions))
                results = [['V' if value else 'F' for value in row] for row in rows]

                table = Texttable()
                table.set_cols_align("c" * len(header))
                table.set_max_width(0)
                table.add_rows([header] + results)

                print(table.draw())
                print(f'Tipo de tabela: {table_type(rows)}')
        else:
            break


if __name__ == "__main__":
    main()

src/main.py
baskerbyte/predimate
import unittest

from src.data.decode import decode_expression
from src.entity.operators import *


class TestDecode(unittest.TestCase):
    def test_decode_conjunction(self):
        self.assertEqual(
            "P^Q",
            decode_expression(Conjunction(left='P', right='Q'))
        )

        self.assertEqual(
            "(A^B)^C",
            decode_expression(Conjunction(left=Conjunction(left='A', right='B'), right='C'))
        )

        self.assertEqual(
            "((D^E)^F)^G",
            decode_expression(Conjunction(left=Conjunction(left=Conjunction(left='D', right='E'), right='F'), right='G'))
        )

        self.assertEqual(
            "(((H^I)^J)^K)^L",
            decode_expression(Conjunction(left=Conjunction(left=Conjunction(left=Conjunction(left='H', right='I'), right='J'), right='K'), right='L'))
        )

        self.assertEqual(
            "((((M^N)^O)^P)^Q)^R",
            decode_expression(Conjunction(left=Conjunction(left=Conjunction(left=Conjunction(left=Conjunction(left='M', right='N'), right='O'), right='P'), right='Q'), right='R'))
        )

    def test_decode_disjunction(self):
        self.assertEqual(
            "PvQ",
            decode_expression(Disjunction(left='P', right='Q'))
        )

        self.assertEqual(
            "(AvB)vC",
            decode_expression(Disjunction(left=Disjunction(left='A', right='B'), right='C'))
        )

        self.assertEqual(
            "Dv(EvF)",
            decode_expression(Disjunction(left='D', right=Disjunction(left='E', right='F')))
        )

        self.assertEqual(
            "(((PvQ)v(Rv(SvT)))v(Uv(Vv(Wv(XvY)))))vZ",
            decode_expression(Disjunction(left=Disjunction(left=Disjunction(left=Disjunction(left='P', right='Q'), right=Disjunction(left='R', right=Disjunction(left='S', right='T'))), right=Disjunction(left='U', right=Disjunction(left='V', right=Disjunction(left='W', right=Disjunction(left='X', right='Y'))))), right='Z'))
        )

        self.assertEqual(
            "((JvK)vL)vM",
            decode_expression(Disjunction(left=Disjunction(left=Disjunction(left='J', right='K'), right='L'), right='M'))
        )

    def test_decode_conditional(self):
        self.assertEqual(
            "P->Q",
            decode_expression(Conditional(left='P', right='Q'))
        )

        self.assertEqual(
            "((P->(R->(T->V)))->(R->G))->R",
            decode_expression(Conditional(left=Conditional(left=Conditional(left='P', right=Conditional(left='R', right=Conditional(left='T', right='V'))), right=Conditional(left='R', right='G')), right='R'))
        )

        self.assertEqual(
            "((A->(B->C))->(D->(E->F)))->G",
            decode_expression(Conditional(left=Conditional(left=Conditional(left='A', right=Conditional(left='B', right='C')), right=Conditional(left='D', right=Conditional(left='E', right='F'))), right='G'))
        )

        self.assertEqual(
            "(((P->Q)->(R->S))->((T->U)->(V->W)))->X",
            decode_expression(Conditional(left=Conditional(left=Conditional(left=Conditional(left='P', right='Q'), right=Conditional(left='R', right='S')), right=Conditional(left=Conditional(left='T', right='U'), right=Conditional(left='V', right='W'))), right='X'))
        )

        self.assertEqual(
            "((P->Q)->(R->(S->(T->(U->(V->(W->(X->(Y->Z)))))))))->(((A->B)->(C->(D->(E->(F->(G->(H->I)))))))->J)",
            decode_expression(Conditional(left=Conditional(left=Conditional(left='P', right='Q'), right=Conditional(left='R', right=Conditional(left='S', right=Conditional(left='T', right=Conditional(left='U', right=Conditional(left='V', right=Conditional(left='W', right=Conditional(left='X', right=Conditional(left='Y', right='Z'))))))))), right=Conditional(left=Conditional(left=Conditional(left='A', right='B'), right=Conditional(left='C', right=Conditional(left='D', right=Conditional(left='E', right=Conditional(left='F', right=Conditional(left='G', right=Conditional(left='H', right='I'))))))), right='J')))
        )

    def test_decode_biconditional(self):
        self.assertEqual(
            "P<->Q",
            decode_expression(BiConditional(left='P', right='Q'))
        )

        self.assertEqual(
            "(P<->Q)<->(R<->(S<->(T<->(U<->(V<->(W<->(X<->(Y<->Z))))))))",
            decode_expression(BiConditional(left=BiConditional(left='P', right='Q'), right=BiConditional(left='R', right=BiConditional(left='S', right=BiConditional(left='T', right=BiConditional(left='U', right=BiConditional(left='V', right=BiConditional(left='W', right=BiConditional(left='X', right=BiConditional(left='Y', right='Z'))))))))))
        )

        self.assertEqual(
            "(P<->(Q<->(R<->(S<->(T<->U)))))<->V",
            decode_expression(BiConditional(left=BiConditional(left='P', right=BiConditional(left='Q', right=BiConditional(left='R', right=BiConditional(left='S', right=BiConditional(left='T', right='U'))))), right='V'))
        )

        self.assertEqual(
            "(((((A<->B)<->C)<->D)<->E)<->F)<->G",
            decode_expression(BiConditional(left=BiConditional(left=BiConditional(left=BiConditional(left=BiConditional(left=BiConditional(left='A', right='B'), right='C'), right='D'), right='E'), right='F'), right='G'))
        )

        self.assertEqual(
            "((((((((P<->Q)<->R)<->S)<->T)<->U)<->V)<->W)<->X)<->Y",
            decode_expression(BiConditional(left=BiConditional(left=BiConditional(left=BiConditional(left=BiConditional(left=BiConditional(left=BiConditional(left=BiConditional(left=BiConditional(left='P', right='Q'), right='R'), right='S'), right='T'), right='U'), right='V'), right='W'), right='X'), right='Y'))
        )

    def test_decode_negation(self):
        self.assertEqual(
            "~P",
            decode_expression(Negation(expr='P'))
        )

    def test_decode(self):
        self.assertEqual(
            "~((PvQ)v(P->(Q^R)))^((P<->R)v(T->U))",
            decode_expression(Conjunction(left=Negation(expr=Disjunction(left=Disjunction(left='P', right='Q'), right=Conditional(left='P', right=Conjunction(left='Q', right='R')))), right=Disjunction(left=BiConditional(left='P', right='R'), right=Conditional(left='T', right='U'))))
        )

        self.assertEqual(
            "(PvQ)^~P",
            decode_expression(Conjunction(left=Disjunction(left='P', right='Q'), right=Negation('P')))
        )

        self.assertEqual(
            "P->Q",
            decode_expression(Conditional(left='P', right='Q'))
        )

        self.assertEqual(
            "~(P^Q)v((RvS)->((TvU)<->~W))",
            decode_expression(Disjunction(left=Negation(expr=Conjunction(left='P', right='Q')), right=Conditional(left=Disjunction(left='R', right='S'), right=BiConditional(left=Disjunction(left='T', right='U'), right=Negation(expr='W')))))
        )

        self.assertEqual(
            "(A^(BvC))^((Dv(EvF))->((G<->(H<->I))^(J^(KvL))))",
            decode_expression(Conjunction(left=Conjunction(left='A', right=Disjunction(left='B', right='C')), right=Conditional(left=Disjunction(left='D', right=Disjunction(left='E', right='F')), right=Conjunction(left=BiConditional(left='G', right=BiConditional(left='H', right='I')), right=Conjunction(left='J', right=Disjunction(left='K', right='L'))))))
        )

        self.assertEqual(
            "~(~(~(PvQ))v(~(RvS)->~((TvU)<->W)))",
            decode_expression(Negation(expr=Disjunction(left=Negation(expr=Negation(expr=Disjunction(left='P', right='Q'))), right=Conditional(left=Negation(expr=Disjunction(left='R', right='S')), right=Negation(expr=BiConditional(left=Disjunction(left='T', right='U'), right='W'))))))
        )


if __name__ == '__main__':
    unittest.main()

src/tests/data/test_decode.py
baskerbyte/predimate
import unittest

from src.data.encode import encode_expression
from src.entity.operators import *


class TestEncode(unittest.TestCase):
    def test_encode_conjunction(self):
        self.assertEqual(
            Conjunction(left='P', right='Q'),
            encode_expression("(P^Q)")
        )

        self.assertEqual(
            Conjunction(left=Conjunction(left='A', right='B'), right='C'),
            encode_expression("((A^B)^C)")
        )

        self.assertEqual(
            Conjunction(left=Conjunction(left=Conjunction(left='D', right='E'), right='F'), right='G'),
            encode_expression("(((D^E)^F)^G)")
        )

        self.assertEqual(
            Conjunction(left=Conjunction(left=Conjunction(left=Conjunction(left='H', right='I'), right='J'), right='K'), right='L'),
            encode_expression("((((H^I)^J)^K)^L)")
        )

        self.assertEqual(
            Conjunction(left=Conjunction(left=Conjunction(left=Conjunction(left=Conjunction(left='M', right='N'), right='O'), right='P'), right='Q'), right='R'),
            encode_expression("(((((M^N)^O)^P)^Q)^R)")
        )

    def test_encode_disjunction(self):
        self.assertEqual(
            Disjunction(left='P', right='Q'),
            encode_expression("PvQ")
        )

        self.assertEqual(
            Disjunction(left=Disjunction(left='A', right='B'), right='C'),
            encode_expression("((AvB)vC)")
        )

        self.assertEqual(
            Disjunction(left='D', right=Disjunction(left='E', right='F')),
            encode_expression("Dv(EvF)")
        )

        self.assertEqual(
            Disjunction(left=Disjunction(left=Disjunction(left=Disjunction(left='P', right='Q'), right=Disjunction(left='R', right=Disjunction(left='S', right='T'))), right=Disjunction(left='U', right=Disjunction(left='V', right=Disjunction(left='W', right=Disjunction(left='X', right='Y'))))), right='Z'),
            encode_expression("(((PvQ)v(Rv(SvT)))v(Uv(Vv(Wv(XvY)))))vZ")
        )

        self.assertEqual(
            Disjunction(left=Disjunction(left=Disjunction(left='J', right='K'), right='L'), right='M'),
            encode_expression("((JvK)vL)vM")
        )

    def test_encode_conditional(self):
        self.assertEqual(
            Conditional(left='P', right='Q'),
            encode_expression("P->Q")
        )

        self.assertEqual(
            Conditional(left=Conditional(left=Conditional(left='P', right=Conditional(left='R', right=Conditional(left='T', right='V'))), right=Conditional(left='R', right='G')), right='R'),
            encode_expression("((P->(R->(T->V)))->(R->G))->R")
        )

        self.assertEqual(
            Conditional(left=Conditional(left=Conditional(left='A', right=Conditional(left='B', right='C')), right=Conditional(left='D', right=Conditional(left='E', right='F'))), right='G'),
            encode_expression("((A->(B->C))->(D->(E->F)))->G")
        )

        self.assertEqual(
            Conditional(left=Conditional(left=Conditional(left=Conditional(left='P', right='Q'), right=Conditional(left='R', right='S')), right=Conditional(left=Conditional(left='T', right='U'), right=Conditional(left='V', right='W'))), right='X'),
            encode_expression("(((P->Q)->(R->S))->((T->U)->(V->W)))->X")
        )

        self.assertEqual(
            Conditional(left=Conditional(left=Conditional(left='P', right='Q'), right=Conditional(left='R', right=Conditional(left='S', right=Conditional(left='T', right=Conditional(left='U', right=Conditional(left='V', right=Conditional(left='W', right=Conditional(left='X', right=Conditional(left='Y', right='Z'))))))))), right=Conditional(left=Conditional(left=Conditional(left='A', right='B'), right=Conditional(left='C', right=Conditional(left='D', right=Conditional(left='E', right=Conditional(left='F', right=Conditional(left='G', right=Conditional(left='H', right='I'))))))), right='J')),
            encode_expression("((P->Q)->(R->(S->(T->(U->(V->(W->(X->(Y->Z)))))))))->(((A->B)->(C->(D->(E->(F->(G->(H->I)))))))->J)")
        )

    def test_encode_biconditional(self):
        self.assertEqual(
            BiConditional(left='P', right='Q'),
            encode_expression("P<->Q")
        )

        self.assertEqual(
            BiConditional(left=BiConditional(left='P', right='Q'), right=BiConditional(left='R', right=BiConditional(left='S', right=BiConditional(left='T', right=BiConditional(left='U', right=BiConditional(left='V', right=BiConditional(left='W', right=BiConditional(left='X', right=BiConditional(left='Y', right='Z'))))))))),
            encode_expression("(P<->Q)<->(R<->(S<->(T<->(U<->(V<->(W<->(X<->(Y<->Z))))))))"),
        )

        self.assertEqual(
            BiConditional(left=BiConditional(left='P', right=BiConditional(left='Q', right=BiConditional(left='R', right=BiConditional(left='S', right=BiConditional(left='T', right='U'))))), right='V'),
            encode_expression("(P<->(Q<->(R<->(S<->(T<->U)))))<->V"),
        )

        self.assertEqual(
            BiConditional(left=BiConditional(left=BiConditional(left=BiConditional(left=BiConditional(left=BiConditional(left='A', right='B'), right='C'), right='D'), right='E'), right='F'), right='G'),
            encode_expression("(((((A<->B)<->C)<->D)<->E)<->F)<->G"),
        )

        self.assertEqual(
            BiConditional(left=BiConditional(left=BiConditional(left=BiConditional(left=BiConditional(left=BiConditional(left=BiConditional(left=BiConditional(left=BiConditional(left='P', right='Q'), right='R'), right='S'), right='T'), right='U'), right='V'), right='W'), right='X'), right='Y'),
            encode_expression("((((((((P<->Q)<->R)<->S)<->T)<->U)<->V)<->W)<->X)<->Y")
        )

    def test_encode_negation(self):
        self.assertEqual(
            Negation(expr='P'),
            encode_expression("~P")
        )

    def test_encode(self):
        self.assertEqual(
            Conjunction(left=Negation(expr=Disjunction(left=Disjunction(left='P', right='Q'), right=Conditional(left='P', right=Conjunction(left='Q', right='R')))), right=Disjunction(left=BiConditional(left='P', right='R'), right=Conditional(left='T', right='U'))),
            encode_expression("~((PvQ)v(P->(Q^R)))^((P<->R)v(T->U))")
        )

        self.assertEqual(
            Conjunction(left=Disjunction(left='P', right='Q'), right=Negation(expr='P')),
            encode_expression("(PvQ)^~P"),
        )

        self.assertEqual(
            Disjunction(left=Negation(expr=Conjunction(left='P', right='Q')), right=Conditional(left=Disjunction(left='R', right='S'), right=BiConditional(left=Disjunction(left='T', right='U'), right=Negation(expr='W')))),
            encode_expression("~(P^Q)v((RvS)->((TvU)<->~W))")
        )

        self.assertEqual(
            Conjunction(left=Conjunction(left='A', right=Disjunction(left='B', right='C')), right=Conditional(left=Disjunction(left='D', right=Disjunction(left='E', right='F')), right=Conjunction(left=BiConditional(left='G', right=BiConditional(left='H', right='I')), right=Conjunction(left='J', right=Disjunction(left='K', right='L'))))),
            encode_expression("(A^(BvC))^((Dv(EvF))->((G<->(H<->I))^(J^(KvL))))")
        )

        self.assertEqual(
            Negation(expr=Disjunction(left=Negation(expr=Negation(expr=Disjunction(left='P', right='Q'))), right=Conditional(left=Negation(expr=Disjunction(left='R', right='S')), right=Negation(expr=BiConditional(left=Disjunction(left='T', right='U'), right='W'))))),
            encode_expression("~(~(~(PvQ))v(~(RvS)->~((TvU)<->W)))")
        )


if __name__ == '__main__':
    unittest.main()

src/tests/data/test_encode.py
baskerbyte/predimate
import itertools

from src.entity.operators import *


def generate_combinations(prepositions):
    """
    Generate all possible combinations of True and False for a list of prepositions.

    Args:
        prepositions (list of str): A list of prepositions for which combinations are generated.

    Returns:
        list of tuples: A list of tuples representing all possible combinations of True and False for prepositions.
   """
    return list(itertools.product([True, False], repeat=len(prepositions)))


def evaluate_expression(prepositions, expr, combination, result):
    """
    Recursively evaluate a logical expression.

    Args:
        prepositions (list of str): A list of prepositions in the expression.
        expr (Base): The logical expression to be evaluated.
        combination (tuple): A list of tuples representing combinations of True and False for prepositions.
        result (list of list): A list of evaluation results for each expression.

    Returns:
        bool: The result of the evaluation.

    This function recursively evaluates logical expressions, either prepositions or compound expressions, and stores
    the results in the 'result' list.

    When the expression is a preposition (a string), it retrieves the corresponding value from the combinations.

    When the expression is a Negation or an Operator (e.g., Conjunction, Disjunction), it evaluates the subexpressions
    and calculates the result, which is then stored in the 'result' list.

    The function returns the final result of the evaluation.
    """

    if isinstance(expr, Operator):
        # Starts recursively until it reaches a str, to obtain its boolean value
        left_value = evaluate_expression(prepositions, expr.left, combination, result)
        right_value = evaluate_expression(prepositions, expr.right, combination, result)
        result_value = None

        if isinstance(expr, Conjunction):
            result_value = left_value and right_value
        elif isinstance(expr, Disjunction):
            result_value = left_value or right_value
        elif isinstance(expr, Conditional):
            result_value = (not left_value) or right_value
        elif isinstance(expr, BiConditional):
            result_value = left_value == right_value

        # For each horizontal combination, add the vertical value
        idx = find_or_add_element(result, expr)
        result[idx].append(result_value)

        return result_value
    elif isinstance(expr, Negation):
        value = not evaluate_expression(prepositions, expr.expr, combination, result)

        idx = find_or_add_element(result, expr)
        result[idx].append(value)

        return value
    elif isinstance(expr, str):
        # The prepositions are organized in alphabetical order, so you can deduce the position of the tuple
        return combination[prepositions.index(expr)]


def find_or_add_element(lst, element):
    for i, item in enumerate(lst):
        if item[0] == element:
            return i

    lst.append([element])
    return len(lst) - 1


def evaluate_expressions(prepositions, expressions, combinations):
    """
    Evaluate a list of logical expressions for all possible combinations of prepositions.

    Args:
        prepositions (list of str): A list of prepositions used in the expressions.
        expressions (list of Base): A list of logical expressions to evaluate.
        combinations (list of tuples): A list of tuples representing combinations of True and False for prepositions.

    Returns:
        list of lists: A list of evaluation results for each expression and each combination.

    This function evaluates a list of logical expressions (expressions) for all possible combinations of True and False
    for prepositions. It returns a list of evaluation results for each expression and each combination
    **in column orientation**.
    """

    result = []

    for combination in combinations:
        for expr in expressions:
            evaluate_expression(prepositions, expr, combination, result)

    return result


def table_type(results):
    """
    Determine the type of a logical table based on its final evaluation results.

    Args:
        results (list of lists): A list of evaluation results for logical expressions and combinations.

    Returns:
        str: The type of the logical table, which can be "Tautologia," "Contradição," or "Contingência."

    This function analyzes the evaluation results of logical expressions and returns the type of the logical table based on
    those results. If all results indicate True, the table is a "Tautologia." If all results indicate False, it is a
    "Contradição." Otherwise, it is a "Contingência."
   """

    if all(result[-1] for result in results):
        return "Tautologia"
    elif all(not result[-1] for result in results):
        return "Contradição"
    else:
        return "Contingência"

src/truth_table/table.py
baskerbyte/predimate