import pytest from control_backend.agents.bdi.agentspeak_ast import ( AstAtom, AstBinaryOp, AstLiteral, AstLogicalExpression, AstNumber, AstPlan, AstProgram, AstRule, AstStatement, AstString, AstVar, BinaryOperatorType, StatementType, TriggerType, _coalesce_expr, ) def test_ast_atom(): atom = AstAtom("test") assert str(atom) == "test" assert atom._to_agentspeak() == "test" def test_ast_var(): var = AstVar("Variable") assert str(var) == "Variable" assert var._to_agentspeak() == "Variable" def test_ast_number(): num = AstNumber(42) assert str(num) == "42" num_float = AstNumber(3.14) assert str(num_float) == "3.14" def test_ast_string(): s = AstString("hello") assert str(s) == '"hello"' def test_ast_literal(): lit = AstLiteral("functor", [AstAtom("atom"), AstNumber(1)]) assert str(lit) == "functor(atom, 1)" lit_empty = AstLiteral("functor") assert str(lit_empty) == "functor" def test_ast_binary_op(): left = AstNumber(1) right = AstNumber(2) op = AstBinaryOp(left, BinaryOperatorType.GREATER_THAN, right) assert str(op) == "1 > 2" # Test logical wrapper assert isinstance(op.left, AstLogicalExpression) assert isinstance(op.right, AstLogicalExpression) def test_ast_binary_op_parens(): # 1 > 2 inner = AstBinaryOp(AstNumber(1), BinaryOperatorType.GREATER_THAN, AstNumber(2)) # (1 > 2) & 3 outer = AstBinaryOp(inner, BinaryOperatorType.AND, AstNumber(3)) assert str(outer) == "(1 > 2) & 3" # 3 & (1 > 2) outer_right = AstBinaryOp(AstNumber(3), BinaryOperatorType.AND, inner) assert str(outer_right) == "3 & (1 > 2)" def test_ast_binary_op_parens_negated(): inner = AstLogicalExpression(AstAtom("foo"), negated=True) outer = AstBinaryOp(inner, BinaryOperatorType.AND, AstAtom("bar")) # The current implementation checks `if self.left.negated: l_str = f"({l_str})"` # str(inner) is "not foo" # so we expect "(not foo) & bar" assert str(outer) == "(not foo) & bar" outer_right = AstBinaryOp(AstAtom("bar"), BinaryOperatorType.AND, inner) assert str(outer_right) == "bar & (not foo)" def test_ast_logical_expression_negation(): expr = AstLogicalExpression(AstAtom("true"), negated=True) assert str(expr) == "not true" expr_neg_neg = ~expr assert str(expr_neg_neg) == "true" assert not expr_neg_neg.negated # Invert a non-logical expression (wraps it) term = AstAtom("true") inverted = ~term assert isinstance(inverted, AstLogicalExpression) assert inverted.negated assert str(inverted) == "not true" def test_ast_logical_expression_no_negation(): # _as_logical on already logical expression expr = AstLogicalExpression(AstAtom("x")) # Doing binary op will call _as_logical op = AstBinaryOp(expr, BinaryOperatorType.AND, AstAtom("y")) assert isinstance(op.left, AstLogicalExpression) assert op.left is expr # Should reuse instance def test_ast_operators(): t1 = AstAtom("a") t2 = AstAtom("b") assert str(t1 & t2) == "a & b" assert str(t1 | t2) == "a | b" assert str(t1 >= t2) == "a >= b" assert str(t1 > t2) == "a > b" assert str(t1 <= t2) == "a <= b" assert str(t1 < t2) == "a < b" assert str(t1 == t2) == "a == b" assert str(t1 != t2) == r"a \== b" def test_coalesce_expr(): t = AstAtom("a") assert str(t & "b") == 'a & "b"' assert str(t & 1) == "a & 1" assert str(t & 1.5) == "a & 1.5" with pytest.raises(TypeError): _coalesce_expr(None) def test_ast_statement(): stmt = AstStatement(StatementType.DO_ACTION, AstLiteral("action")) assert str(stmt) == ".action" def test_ast_rule(): # Rule with condition rule = AstRule(AstLiteral("head"), AstLiteral("body")) assert str(rule) == "head :- body." # Rule without condition rule_simple = AstRule(AstLiteral("fact")) assert str(rule_simple) == "fact." def test_ast_plan(): plan = AstPlan( TriggerType.ADDED_GOAL, AstLiteral("goal"), [AstLiteral("context")], [AstStatement(StatementType.DO_ACTION, AstLiteral("action"))], ) output = str(plan) # verify parts exist assert "+!goal" in output assert ": context" in output assert "<- .action." in output def test_ast_plan_no_context(): plan = AstPlan( TriggerType.ADDED_GOAL, AstLiteral("goal"), [], [AstStatement(StatementType.DO_ACTION, AstLiteral("action"))], ) output = str(plan) assert "+!goal" in output assert ": " not in output assert "<- .action." in output def test_ast_program(): prog = AstProgram( rules=[AstRule(AstLiteral("fact"))], plans=[AstPlan(TriggerType.ADDED_BELIEF, AstLiteral("b"), [], [])], ) output = str(prog) assert "fact." in output assert "+b" in output