107 lines
3.4 KiB
C
107 lines
3.4 KiB
C
|
|
/**
|
||
|
|
* @file parser_expressions.c
|
||
|
|
* @brief Tests the expression grammar via the leaf renderer.
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include "harness.h"
|
||
|
|
|
||
|
|
/* Parse a line and assert the shape of the tree it produced. */
|
||
|
|
static void expect_tree(const char *line, const char *want)
|
||
|
|
{
|
||
|
|
akbasic_ASTLeaf *leaf = NULL;
|
||
|
|
char rendered[AKBASIC_MAX_STRING_LENGTH];
|
||
|
|
akerr_ErrorContext *e = NULL;
|
||
|
|
|
||
|
|
e = harness_parse(line, &leaf);
|
||
|
|
if ( e != NULL ) {
|
||
|
|
fprintf(stderr, "FAIL: \"%s\" did not parse: %s\n", line, e->message);
|
||
|
|
akbasic_test_failures += 1;
|
||
|
|
test_discard_error(e);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
e = akbasic_leaf_to_string(leaf, rendered, sizeof(rendered));
|
||
|
|
if ( e != NULL ) {
|
||
|
|
test_discard_error(e);
|
||
|
|
akbasic_test_failures += 1;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
TEST_REQUIRE(strcmp(rendered, want) == 0,
|
||
|
|
"\"%s\": expected %s, got %s", line, want, rendered);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void expect_parse_error(const char *line)
|
||
|
|
{
|
||
|
|
akbasic_ASTLeaf *leaf = NULL;
|
||
|
|
akerr_ErrorContext *e = NULL;
|
||
|
|
|
||
|
|
e = harness_parse(line, &leaf);
|
||
|
|
TEST_REQUIRE(e != NULL, "\"%s\" should not have parsed", line);
|
||
|
|
if ( e != NULL ) {
|
||
|
|
test_discard_error(e);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
int main(void)
|
||
|
|
{
|
||
|
|
akbasic_ASTLeaf *leaf = NULL;
|
||
|
|
|
||
|
|
TEST_REQUIRE_OK(harness_start(NULL));
|
||
|
|
|
||
|
|
/*
|
||
|
|
* An assignment renders with an empty operator: the reference's
|
||
|
|
* operatorToStr() has a case for EQUAL but none for ASSIGNMENT, so it prints
|
||
|
|
* "( A# ...)". Reproduced, so these expectations look odd on purpose.
|
||
|
|
*/
|
||
|
|
|
||
|
|
/* Multiplication binds tighter than addition. */
|
||
|
|
expect_tree("PRINT 1 + 2 * 3", "(PRINT)");
|
||
|
|
expect_tree("A# = 1 + 2 * 3", "( A# (+ 1 (* 2 3)))");
|
||
|
|
expect_tree("A# = 1 * 2 + 3", "( A# (+ (* 1 2) 3))");
|
||
|
|
expect_tree("A# = 8 / 2 / 2", "( A# (/ (/ 8 2) 2))");
|
||
|
|
|
||
|
|
/* Grouping overrides precedence. */
|
||
|
|
expect_tree("A# = (1 + 2) * 3", "( A# (* (group (+ 1 2)) 3))");
|
||
|
|
|
||
|
|
/* Unary minus and NOT. */
|
||
|
|
expect_tree("A# = -5", "( A# (- 5))");
|
||
|
|
expect_tree("A# = NOT 1", "( A# (NOT 1))");
|
||
|
|
|
||
|
|
/* Comparisons sit below the arithmetic. */
|
||
|
|
expect_tree("A# = 1 + 1 == 2", "( A# (= (+ 1 1) 2))");
|
||
|
|
expect_tree("A# = 1 <> 2", "( A# (<> 1 2))");
|
||
|
|
|
||
|
|
/* AND and OR sit below comparison. */
|
||
|
|
expect_tree("A# = 1 == 1 AND 2 == 2", "( A# (AND (= 1 1) (= 2 2)))");
|
||
|
|
|
||
|
|
/* Exponent. */
|
||
|
|
expect_tree("A# = 2 ^ 3", "( A# (^ 2 3))");
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Addition loops over its operator; subtraction returns after one. So
|
||
|
|
* `1 + 2 + 3` folds left, while `1 - 2 - 3` parses only `1 - 2` and leaves
|
||
|
|
* `- 3` in the token stream for the statement loop to pick up as a second
|
||
|
|
* statement. That silently computes the wrong answer -- TODO.md section 12
|
||
|
|
* item 12 -- and is pinned here so the fix is a deliberate change to this
|
||
|
|
* assertion and not an accident.
|
||
|
|
*/
|
||
|
|
expect_tree("A# = 1 + 2 + 3", "( A# (+ (+ 1 2) 3))");
|
||
|
|
expect_tree("A# = 1 - 2 - 3", "( A# (- 1 2))");
|
||
|
|
|
||
|
|
/* An array reference parses as an identifier carrying a subscript list. */
|
||
|
|
TEST_REQUIRE_OK(harness_parse("A#(2) = 1", &leaf));
|
||
|
|
TEST_REQUIRE(leaf != NULL && leaf->left != NULL, "assignment must have an lvalue");
|
||
|
|
if ( leaf != NULL && leaf->left != NULL ) {
|
||
|
|
TEST_REQUIRE_INT(leaf->left->leaftype, AKBASIC_LEAF_IDENTIFIER_INT);
|
||
|
|
TEST_REQUIRE(akbasic_leaf_first_subscript(leaf->left) != NULL,
|
||
|
|
"A#(2) must carry an array subscript");
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Failure paths. */
|
||
|
|
expect_parse_error("A# = ");
|
||
|
|
expect_parse_error("A# = (1 + 2");
|
||
|
|
expect_parse_error("A# = *");
|
||
|
|
|
||
|
|
harness_stop();
|
||
|
|
return akbasic_test_failures;
|
||
|
|
}
|