193 lines
5.1 KiB
Python
193 lines
5.1 KiB
Python
"""
|
|
This program has been developed by students from the bachelor Computer Science at Utrecht
|
|
University within the Software Project course.
|
|
© Copyright Utrecht University (Department of Information and Computing Sciences)
|
|
"""
|
|
|
|
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
|