Files
akbasic/tests/grammar_leaves.c
Andrew Kesterson 1eb8f9ffaa Fix five defects the port uncovered in the reference parser and scanner
Chained subtraction and exponentiation fold left instead of abandoning the
rest of the line -- `1 - 2 - 3` gave -1 and dropped the `- 3`. A unary leaf's
operand moves to .left, so a negative literal is one argument again and a
second argument no longer overwrites it. An operator in a line's final column
survives. Hex literals reach the parser whole. A leading zero is padding, not
a radix.

Item 16 stays pinned: the fix works and costs an upstream golden case, which
is a decision rather than a defect.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 11:35:14 -04:00

112 lines
4.9 KiB
C

/**
* @file grammar_leaves.c
* @brief Tests AST leaf construction, rendering and deep cloning.
*/
#include <string.h>
#include <akbasic/error.h>
#include <akbasic/grammar.h>
#include "testutil.h"
static akbasic_ASTLeaf LEAVES[8];
static akbasic_ASTLeaf STORAGE[AKBASIC_MAX_LEAVES];
static akbasic_LeafPool POOL;
int main(void)
{
akbasic_ASTLeaf *copy = NULL;
char rendered[AKBASIC_MAX_STRING_LENGTH];
TEST_REQUIRE_OK(akbasic_error_register());
POOL.next = 0;
POOL.capacity = AKBASIC_MAX_LEAVES;
POOL.leaves = STORAGE;
/* Literals render the way PRINT renders them. */
TEST_REQUIRE_OK(akbasic_leaf_new_literal_int(&LEAVES[0], "42"));
TEST_REQUIRE_INT(LEAVES[0].literal_int, 42);
TEST_REQUIRE_OK(akbasic_leaf_to_string(&LEAVES[0], rendered, sizeof(rendered)));
TEST_REQUIRE_STR(rendered, "42");
/* 0x is hex. */
TEST_REQUIRE_OK(akbasic_leaf_new_literal_int(&LEAVES[1], "0xff"));
TEST_REQUIRE_INT(LEAVES[1].literal_int, 255);
/*
* A leading zero is padding, not a radix -- TODO.md section 6 item 10's
* regression test. The reference selects base 8 for any lexeme starting with
* '0' (basicgrammar.go:224), so 010 was 8 and 08 would not parse at all.
* Commodore BASIC has no octal literals.
*/
TEST_REQUIRE_OK(akbasic_leaf_new_literal_int(&LEAVES[1], "010"));
TEST_REQUIRE_INT(LEAVES[1].literal_int, 10);
TEST_REQUIRE_OK(akbasic_leaf_new_literal_int(&LEAVES[1], "08"));
TEST_REQUIRE_INT(LEAVES[1].literal_int, 8);
TEST_REQUIRE_OK(akbasic_leaf_new_literal_int(&LEAVES[1], "0"));
TEST_REQUIRE_INT(LEAVES[1].literal_int, 0);
/* 0x still selects base 16, which is the one prefix that means anything. */
TEST_REQUIRE_OK(akbasic_leaf_new_literal_int(&LEAVES[1], "0x10"));
TEST_REQUIRE_INT(LEAVES[1].literal_int, 16);
TEST_REQUIRE_OK(akbasic_leaf_new_literal_float(&LEAVES[2], "2.5"));
TEST_REQUIRE_FEQ(LEAVES[2].literal_float, 2.5);
TEST_REQUIRE_OK(akbasic_leaf_to_string(&LEAVES[2], rendered, sizeof(rendered)));
TEST_REQUIRE_STR(rendered, "2.500000");
TEST_REQUIRE_OK(akbasic_leaf_new_literal_string(&LEAVES[3], "HELLO"));
TEST_REQUIRE_STR(LEAVES[3].literal_string, "HELLO");
/* Identifier predicates. */
TEST_REQUIRE_OK(akbasic_leaf_new_identifier(&LEAVES[4], AKBASIC_LEAF_IDENTIFIER_INT, "A#"));
TEST_REQUIRE(akbasic_leaf_is_identifier(&LEAVES[4]), "A# should be an identifier");
TEST_REQUIRE(!akbasic_leaf_is_literal(&LEAVES[4]), "A# is not a literal");
TEST_REQUIRE(akbasic_leaf_is_literal(&LEAVES[0]), "42 should be a literal");
TEST_REQUIRE(!akbasic_leaf_is_identifier(NULL), "NULL is not an identifier");
/* Binary and unary render in prefix form. */
TEST_REQUIRE_OK(akbasic_leaf_new_binary(&LEAVES[5], &LEAVES[4], AKBASIC_TOK_PLUS, &LEAVES[0]));
TEST_REQUIRE_OK(akbasic_leaf_to_string(&LEAVES[5], rendered, sizeof(rendered)));
TEST_REQUIRE_STR(rendered, "(+ A# 42)");
TEST_REQUIRE_OK(akbasic_leaf_new_unary(&LEAVES[6], AKBASIC_TOK_MINUS, &LEAVES[0]));
TEST_REQUIRE_OK(akbasic_leaf_to_string(&LEAVES[6], rendered, sizeof(rendered)));
TEST_REQUIRE_STR(rendered, "(- 42)");
TEST_REQUIRE_OK(akbasic_leaf_new_grouping(&LEAVES[7], &LEAVES[5]));
TEST_REQUIRE_OK(akbasic_leaf_to_string(&LEAVES[7], rendered, sizeof(rendered)));
TEST_REQUIRE_STR(rendered, "(group (+ A# 42))");
/* A deep clone copies the whole tree into pool storage. */
TEST_REQUIRE_OK(akbasic_leaf_clone(&LEAVES[7], &POOL, &copy));
TEST_REQUIRE(copy != NULL && copy != &LEAVES[7], "clone must be a distinct leaf");
TEST_REQUIRE(copy->expr != NULL && copy->expr != &LEAVES[5], "clone must be deep");
TEST_REQUIRE(copy->expr->left != NULL && copy->expr->left != &LEAVES[4],
"clone must recurse into .left");
TEST_REQUIRE_OK(akbasic_leaf_to_string(copy, rendered, sizeof(rendered)));
TEST_REQUIRE_STR(rendered, "(group (+ A# 42))");
/* Cloning NULL yields NULL, not an error: the reference does the same. */
TEST_REQUIRE_OK(akbasic_leaf_clone(NULL, &POOL, &copy));
TEST_REQUIRE(copy == NULL, "cloning NULL must yield NULL");
/* Pool exhaustion is diagnosed, not a buffer overrun. */
POOL.next = POOL.capacity;
TEST_REQUIRE_STATUS(akbasic_leaf_clone(&LEAVES[7], &POOL, &copy), AKBASIC_ERR_BOUNDS);
/* Constructors reject the nil arguments the reference rejects. */
TEST_REQUIRE_STATUS(akbasic_leaf_new_binary(&LEAVES[0], NULL, AKBASIC_TOK_PLUS, &LEAVES[1]),
AKERR_NULLPOINTER);
TEST_REQUIRE_STATUS(akbasic_leaf_new_unary(&LEAVES[0], AKBASIC_TOK_MINUS, NULL),
AKERR_NULLPOINTER);
TEST_REQUIRE_STATUS(akbasic_leaf_new_grouping(&LEAVES[0], NULL), AKERR_NULLPOINTER);
TEST_REQUIRE_STATUS(akbasic_leaf_new_branch(&LEAVES[0], NULL, NULL, NULL), AKERR_NULLPOINTER);
TEST_REQUIRE_STATUS(akbasic_leaf_new_comparison(&LEAVES[0], &LEAVES[1], AKBASIC_TOK_PLUS,
&LEAVES[2]),
AKBASIC_ERR_SYNTAX);
return akbasic_test_failures;
}