Skip to content

Commit cb90c85

Browse files
facontidavideclaude
andcommitted
Add unit tests for tokenizer and parser edge cases
New test cases: - TokenizerEdgeCases: unterminated strings, hex errors (0x, 0xG), exponent errors (3e, 3e+), DotDot with numbers, empty/whitespace scripts - ChainedComparisons: 1<2<3, 3>2>1, mixed chains, chained equality - OperatorPrecedence: bitwise vs logical, parenthesized subexpressions - UnaryOperators: !, !!, ~, unary minus in expressions - TernaryExpressions: basic, with subexpressions, comparison conditions - MultipleStatements: extra semicolons, return value of last expression Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 40b7a96 commit cb90c85

1 file changed

Lines changed: 129 additions & 0 deletions

File tree

tests/script_parser_test.cpp

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,3 +522,132 @@ TEST(ParserTest, NewLine)
522522
ASSERT_EQ(tree.rootBlackboard()->get<int>("A"), 5);
523523
ASSERT_EQ(tree.rootBlackboard()->get<int>("B"), 6);
524524
}
525+
526+
TEST(ParserTest, TokenizerEdgeCases)
527+
{
528+
// Unterminated string
529+
EXPECT_FALSE(BT::ValidateScript("'hello"));
530+
531+
// Hex edge cases
532+
EXPECT_FALSE(BT::ValidateScript("0x"));
533+
EXPECT_FALSE(BT::ValidateScript("0xG"));
534+
535+
// Exponent without digits
536+
EXPECT_FALSE(BT::ValidateScript("3e"));
537+
EXPECT_FALSE(BT::ValidateScript("3e+"));
538+
539+
// DotDot adjacent to integer: "65..66" should parse as 65 .. 66
540+
BT::Ast::Environment env = { BT::Blackboard::create(), {} };
541+
auto result = BT::ParseScriptAndExecute(env, "A:='65'; B:='66'; A..B");
542+
EXPECT_TRUE(result.has_value());
543+
EXPECT_EQ(result.value().cast<std::string>(), "6566");
544+
545+
// Empty and whitespace-only scripts
546+
EXPECT_FALSE(BT::ValidateScript(""));
547+
EXPECT_FALSE(BT::ValidateScript(" "));
548+
EXPECT_FALSE(BT::ValidateScript("\t\n\r"));
549+
}
550+
551+
TEST(ParserTest, ChainedComparisons)
552+
{
553+
BT::Ast::Environment env = { BT::Blackboard::create(), {} };
554+
auto Parse = [&env](const char* str) { return BT::ParseScriptAndExecute(env, str); };
555+
556+
// 1 < 2 < 3 should be true (chained: 1<2 AND 2<3)
557+
EXPECT_EQ(Parse("1 < 2 < 3").value().cast<int>(), 1);
558+
559+
// 3 > 2 > 1 should be true
560+
EXPECT_EQ(Parse("3 > 2 > 1").value().cast<int>(), 1);
561+
562+
// 1 < 2 > 3 should be false (1<2 is true, but 2>3 is false)
563+
EXPECT_EQ(Parse("1 < 2 > 3").value().cast<int>(), 0);
564+
565+
// Chained equality
566+
EXPECT_EQ(Parse("5 == 5 == 5").value().cast<int>(), 1);
567+
EXPECT_EQ(Parse("5 == 5 != 3").value().cast<int>(), 1);
568+
569+
// 1 <= 2 <= 3
570+
EXPECT_EQ(Parse("1 <= 2 <= 3").value().cast<int>(), 1);
571+
572+
// 3 >= 2 >= 1
573+
EXPECT_EQ(Parse("3 >= 2 >= 1").value().cast<int>(), 1);
574+
}
575+
576+
TEST(ParserTest, OperatorPrecedence)
577+
{
578+
BT::Ast::Environment env = { BT::Blackboard::create(), {} };
579+
auto Parse = [&env](const char* str) { return BT::ParseScriptAndExecute(env, str); };
580+
581+
// Bitwise AND binds tighter than bitwise OR
582+
// 6 | 3 & 5 should be 6 | (3 & 5) = 6 | 1 = 7
583+
EXPECT_EQ(Parse("6 | 3 & 5").value().cast<int>(), 7);
584+
585+
// Bitwise OR binds tighter than logical AND
586+
// true && (6 | 0) should be true
587+
EXPECT_EQ(Parse("true && (6 | 0)").value().cast<int>(), 1);
588+
589+
// Logical AND binds tighter than logical OR
590+
// false || true && true should be false || (true && true) = true
591+
EXPECT_EQ(Parse("false || true && true").value().cast<int>(), 1);
592+
593+
// false && true || true should be (false && true) || true = true
594+
EXPECT_EQ(Parse("false && true || true").value().cast<int>(), 1);
595+
596+
// Parentheses override precedence
597+
EXPECT_EQ(Parse("(2 + 3) * 4").value().cast<double>(), 20.0);
598+
EXPECT_EQ(Parse("2 * (3 + 4)").value().cast<double>(), 14.0);
599+
}
600+
601+
TEST(ParserTest, UnaryOperators)
602+
{
603+
BT::Ast::Environment env = { BT::Blackboard::create(), {} };
604+
auto Parse = [&env](const char* str) { return BT::ParseScriptAndExecute(env, str); };
605+
606+
// Logical NOT
607+
EXPECT_EQ(Parse("!true").value().cast<int>(), 0);
608+
EXPECT_EQ(Parse("!false").value().cast<int>(), 1);
609+
EXPECT_EQ(Parse("!!true").value().cast<int>(), 1);
610+
611+
// Bitwise complement
612+
auto result = Parse("~0");
613+
EXPECT_TRUE(result.has_value());
614+
EXPECT_EQ(result.value().cast<int64_t>(), ~int64_t(0));
615+
616+
// Unary minus
617+
EXPECT_EQ(Parse("-(3 + 2)").value().cast<double>(), -5.0);
618+
619+
// Unary minus in expressions
620+
EXPECT_EQ(Parse("10 + -3").value().cast<double>(), 7.0);
621+
}
622+
623+
TEST(ParserTest, TernaryExpressions)
624+
{
625+
BT::Ast::Environment env = { BT::Blackboard::create(), {} };
626+
auto Parse = [&env](const char* str) { return BT::ParseScriptAndExecute(env, str); };
627+
628+
EXPECT_EQ(Parse("true ? 1 : 2").value().cast<int>(), 1);
629+
EXPECT_EQ(Parse("false ? 1 : 2").value().cast<int>(), 2);
630+
631+
// Ternary with expressions in branches
632+
EXPECT_EQ(Parse("true ? 2 + 3 : 10").value().cast<double>(), 5.0);
633+
EXPECT_EQ(Parse("false ? 10 : 2 + 3").value().cast<double>(), 5.0);
634+
635+
// Ternary with comparison as condition
636+
EXPECT_EQ(Parse("3 > 2 ? 'yes' : 'no'").value().cast<std::string>(), "yes");
637+
EXPECT_EQ(Parse("3 < 2 ? 'yes' : 'no'").value().cast<std::string>(), "no");
638+
}
639+
640+
TEST(ParserTest, MultipleStatements)
641+
{
642+
BT::Ast::Environment env = { BT::Blackboard::create(), {} };
643+
auto Parse = [&env](const char* str) { return BT::ParseScriptAndExecute(env, str); };
644+
645+
// Multiple semicolons
646+
Parse("a:=1;;; b:=2;;");
647+
EXPECT_EQ(env.vars->get<double>("a"), 1.0);
648+
EXPECT_EQ(env.vars->get<double>("b"), 2.0);
649+
650+
// Last expression is the return value
651+
auto result = Parse("a:=10; b:=20; a+b");
652+
EXPECT_EQ(result.value().cast<double>(), 30.0);
653+
}

0 commit comments

Comments
 (0)