/** * @file grammar.c * @brief Implements token and AST leaf construction. */ #include #include #include #include #include #include #include /* Copy into a leaf's inline identifier/literal buffer, refusing truncation. */ static akerr_ErrorContext *copy_bounded(char *dest, const char *src, const char *what) { PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, (src != NULL), AKERR_NULLPOINTER, "NULL %s", what); FAIL_ZERO_RETURN(errctx, (strlen(src) < AKBASIC_MAX_STRING_LENGTH), AKBASIC_ERR_VALUE, "%s of %zu characters exceeds the %d character limit", what, strlen(src), AKBASIC_MAX_STRING_LENGTH - 1); strncpy(dest, src, AKBASIC_MAX_STRING_LENGTH - 1); dest[AKBASIC_MAX_STRING_LENGTH - 1] = '\0'; SUCCEED_RETURN(errctx); } akerr_ErrorContext *akbasic_token_init(akbasic_Token *obj) { PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL token in init"); obj->tokentype = AKBASIC_TOK_UNDEFINED; obj->lineno = 0; obj->lexeme[0] = '\0'; SUCCEED_RETURN(errctx); } akerr_ErrorContext *akbasic_leaf_init(akbasic_ASTLeaf *obj, akbasic_LeafType leaftype) { PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL leaf in init"); obj->leaftype = leaftype; obj->parent = NULL; obj->left = NULL; obj->right = NULL; obj->expr = NULL; obj->identifier[0] = '\0'; obj->literal_int = 0; obj->literal_float = 0.0; obj->literal_string[0] = '\0'; obj->operator_ = AKBASIC_TOK_UNDEFINED; SUCCEED_RETURN(errctx); } /* * Take one leaf from the pool. Recursion in clone_into() is bounded by the pool * capacity, so a cyclic tree exhausts the pool and errors rather than running * off the stack. */ static akerr_ErrorContext *pool_take(akbasic_LeafPool *pool, akbasic_ASTLeaf **dest) { PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, (pool != NULL), AKERR_NULLPOINTER, "NULL leaf pool"); FAIL_ZERO_RETURN(errctx, (pool->leaves != NULL), AKERR_NULLPOINTER, "Leaf pool has no storage"); FAIL_ZERO_RETURN(errctx, (pool->next < pool->capacity), AKBASIC_ERR_BOUNDS, "No more leaves available"); *dest = &pool->leaves[pool->next]; pool->next += 1; SUCCEED_RETURN(errctx); } static akerr_ErrorContext *clone_into(akbasic_ASTLeaf *self, akbasic_LeafPool *pool, akbasic_ASTLeaf **dest) { PREPARE_ERROR(errctx); akbasic_ASTLeaf *copy = NULL; if ( self == NULL ) { *dest = NULL; SUCCEED_RETURN(errctx); } PASS(errctx, pool_take(pool, ©)); PASS(errctx, akbasic_leaf_init(copy, self->leaftype)); copy->parent = self->parent; copy->literal_int = self->literal_int; copy->literal_float = self->literal_float; memcpy(copy->literal_string, self->literal_string, sizeof(copy->literal_string)); memcpy(copy->identifier, self->identifier, sizeof(copy->identifier)); copy->operator_ = self->operator_; PASS(errctx, clone_into(self->left, pool, ©->left)); PASS(errctx, clone_into(self->right, pool, ©->right)); PASS(errctx, clone_into(self->expr, pool, ©->expr)); *dest = copy; SUCCEED_RETURN(errctx); } akerr_ErrorContext *akbasic_leaf_clone(akbasic_ASTLeaf *self, akbasic_LeafPool *pool, akbasic_ASTLeaf **dest) { PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, (dest != NULL), AKERR_NULLPOINTER, "NULL destination in clone"); PASS(errctx, clone_into(self, pool, dest)); SUCCEED_RETURN(errctx); } akbasic_ASTLeaf *akbasic_leaf_first_argument(akbasic_ASTLeaf *self) { if ( self == NULL || self->right == NULL || self->right->leaftype != AKBASIC_LEAF_ARGUMENTLIST || self->right->operator_ != AKBASIC_TOK_FUNCTION_ARGUMENT ) { return NULL; } return self->right->right; } akbasic_ASTLeaf *akbasic_leaf_first_subscript(akbasic_ASTLeaf *self) { if ( self == NULL || self->right == NULL || self->right->leaftype != AKBASIC_LEAF_ARGUMENTLIST || self->right->operator_ != AKBASIC_TOK_ARRAY_SUBSCRIPT ) { return NULL; } return self->right->right; } bool akbasic_leaf_is_identifier(akbasic_ASTLeaf *self) { return (self != NULL && (self->leaftype == AKBASIC_LEAF_IDENTIFIER || self->leaftype == AKBASIC_LEAF_IDENTIFIER_INT || self->leaftype == AKBASIC_LEAF_IDENTIFIER_FLOAT || self->leaftype == AKBASIC_LEAF_IDENTIFIER_STRING)); } bool akbasic_leaf_is_literal(akbasic_ASTLeaf *self) { return (self != NULL && (self->leaftype == AKBASIC_LEAF_LITERAL_INT || self->leaftype == AKBASIC_LEAF_LITERAL_FLOAT || self->leaftype == AKBASIC_LEAF_LITERAL_STRING)); } akerr_ErrorContext *akbasic_leaf_new_comparison(akbasic_ASTLeaf *obj, akbasic_ASTLeaf *left, akbasic_TokenType op, akbasic_ASTLeaf *right) { PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL leaf in new_comparison"); FAIL_ZERO_RETURN(errctx, (left != NULL && right != NULL), AKERR_NULLPOINTER, "nil pointer arguments"); PASS(errctx, akbasic_leaf_init(obj, AKBASIC_LEAF_COMPARISON)); obj->left = left; obj->right = right; switch ( op ) { case AKBASIC_TOK_LESS_THAN: case AKBASIC_TOK_LESS_THAN_EQUAL: case AKBASIC_TOK_NOT_EQUAL: case AKBASIC_TOK_GREATER_THAN: case AKBASIC_TOK_GREATER_THAN_EQUAL: SUCCEED_RETURN(errctx); default: FAIL_RETURN(errctx, AKBASIC_ERR_SYNTAX, "Invalid operator %d for comparison", (int)op); } } akerr_ErrorContext *akbasic_leaf_new_binary(akbasic_ASTLeaf *obj, akbasic_ASTLeaf *left, akbasic_TokenType op, akbasic_ASTLeaf *right) { PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL leaf in new_binary"); FAIL_ZERO_RETURN(errctx, (left != NULL && right != NULL), AKERR_NULLPOINTER, "nil pointer arguments"); PASS(errctx, akbasic_leaf_init(obj, AKBASIC_LEAF_BINARY)); obj->left = left; obj->right = right; obj->operator_ = op; SUCCEED_RETURN(errctx); } akerr_ErrorContext *akbasic_leaf_new_unary(akbasic_ASTLeaf *obj, akbasic_TokenType op, akbasic_ASTLeaf *operand) { PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL leaf in new_unary"); FAIL_ZERO_RETURN(errctx, (operand != NULL), AKERR_NULLPOINTER, "nil pointer arguments"); PASS(errctx, akbasic_leaf_init(obj, AKBASIC_LEAF_UNARY)); /* * .left, not .right. An argument list chains its arguments through .right, * so an operand there is indistinguishable from a second argument: ABS(-9) * counted as two and was refused. See the note on the declaration. */ obj->left = operand; obj->operator_ = op; SUCCEED_RETURN(errctx); } akerr_ErrorContext *akbasic_leaf_new_function(akbasic_ASTLeaf *obj, const char *fname, akbasic_ASTLeaf *right) { PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL leaf in new_function"); PASS(errctx, akbasic_leaf_init(obj, AKBASIC_LEAF_FUNCTION)); obj->right = right; obj->operator_ = AKBASIC_TOK_COMMAND; PASS(errctx, copy_bounded(obj->identifier, fname, "function name")); SUCCEED_RETURN(errctx); } akerr_ErrorContext *akbasic_leaf_new_command(akbasic_ASTLeaf *obj, const char *cmdname, akbasic_ASTLeaf *right) { PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL leaf in new_command"); PASS(errctx, akbasic_leaf_init(obj, AKBASIC_LEAF_COMMAND)); obj->right = right; obj->operator_ = AKBASIC_TOK_COMMAND; PASS(errctx, copy_bounded(obj->identifier, cmdname, "command name")); SUCCEED_RETURN(errctx); } akerr_ErrorContext *akbasic_leaf_new_immediate_command(akbasic_ASTLeaf *obj, const char *cmdname, akbasic_ASTLeaf *right) { PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL leaf in new_immediate_command"); PASS(errctx, akbasic_leaf_init(obj, AKBASIC_LEAF_COMMAND_IMMEDIATE)); obj->right = right; obj->operator_ = AKBASIC_TOK_COMMAND_IMMEDIATE; PASS(errctx, copy_bounded(obj->identifier, cmdname, "command name")); SUCCEED_RETURN(errctx); } akerr_ErrorContext *akbasic_leaf_new_branch(akbasic_ASTLeaf *obj, akbasic_ASTLeaf *expr, akbasic_ASTLeaf *trueleaf, akbasic_ASTLeaf *falseleaf) { PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL leaf in new_branch"); FAIL_ZERO_RETURN(errctx, (expr != NULL), AKERR_NULLPOINTER, "nil pointer arguments"); PASS(errctx, akbasic_leaf_init(obj, AKBASIC_LEAF_BRANCH)); obj->expr = expr; obj->left = trueleaf; obj->right = falseleaf; SUCCEED_RETURN(errctx); } akerr_ErrorContext *akbasic_leaf_new_grouping(akbasic_ASTLeaf *obj, akbasic_ASTLeaf *expr) { PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL leaf in new_grouping"); FAIL_ZERO_RETURN(errctx, (expr != NULL), AKERR_NULLPOINTER, "nil pointer arguments"); PASS(errctx, akbasic_leaf_init(obj, AKBASIC_LEAF_GROUPING)); obj->expr = expr; SUCCEED_RETURN(errctx); } akerr_ErrorContext *akbasic_leaf_new_literal_int(akbasic_ASTLeaf *obj, const char *lexeme) { PREPARE_ERROR(errctx); long long value = 0; int base = 10; FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL leaf in new_literal_int"); FAIL_ZERO_RETURN(errctx, (lexeme != NULL), AKERR_NULLPOINTER, "NULL lexeme in new_literal_int"); FAIL_ZERO_RETURN(errctx, (lexeme[0] != '\0'), AKBASIC_ERR_VALUE, "Empty integer literal"); /* * Base 10 unless the lexeme is prefixed `0x`. The reference selects base 8 * for *any* lexeme starting with '0' (basicgrammar.go:224), so `PRINT 010` * printed 8 and `PRINT 08` was a parse error -- TODO.md section 6 item 10. * Commodore BASIC has no octal literals, and a leading zero in a listing is * padding, not a radix. */ if ( strlen(lexeme) > 2 && strncmp(lexeme, "0x", 2) == 0 ) { base = 16; } PASS(errctx, akbasic_leaf_init(obj, AKBASIC_LEAF_LITERAL_INT)); PASS(errctx, aksl_strtoll(lexeme, NULL, base, &value)); obj->literal_int = (int64_t)value; SUCCEED_RETURN(errctx); } akerr_ErrorContext *akbasic_leaf_new_literal_float(akbasic_ASTLeaf *obj, const char *lexeme) { PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL leaf in new_literal_float"); PASS(errctx, akbasic_leaf_init(obj, AKBASIC_LEAF_LITERAL_FLOAT)); PASS(errctx, aksl_atof(lexeme, &obj->literal_float)); SUCCEED_RETURN(errctx); } akerr_ErrorContext *akbasic_leaf_new_literal_string(akbasic_ASTLeaf *obj, const char *lexeme) { PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL leaf in new_literal_string"); PASS(errctx, akbasic_leaf_init(obj, AKBASIC_LEAF_LITERAL_STRING)); PASS(errctx, copy_bounded(obj->literal_string, lexeme, "string literal")); SUCCEED_RETURN(errctx); } akerr_ErrorContext *akbasic_leaf_new_identifier(akbasic_ASTLeaf *obj, akbasic_LeafType leaftype, const char *lexeme) { PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL leaf in new_identifier"); PASS(errctx, akbasic_leaf_init(obj, leaftype)); PASS(errctx, copy_bounded(obj->identifier, lexeme, "identifier")); SUCCEED_RETURN(errctx); } static const char *operator_to_str(akbasic_TokenType op) { switch ( op ) { case AKBASIC_TOK_EQUAL: return "="; case AKBASIC_TOK_LESS_THAN: return "<"; case AKBASIC_TOK_GREATER_THAN: return ">"; case AKBASIC_TOK_LESS_THAN_EQUAL: return "<="; case AKBASIC_TOK_GREATER_THAN_EQUAL: return ">="; case AKBASIC_TOK_NOT_EQUAL: return "<>"; case AKBASIC_TOK_PLUS: return "+"; case AKBASIC_TOK_MINUS: return "-"; case AKBASIC_TOK_STAR: return "*"; case AKBASIC_TOK_LEFT_SLASH: return "/"; case AKBASIC_TOK_CARAT: return "^"; case AKBASIC_TOK_NOT: return "NOT"; case AKBASIC_TOK_AND: return "AND"; case AKBASIC_TOK_OR: return "OR"; default: return ""; } } akerr_ErrorContext *akbasic_leaf_to_string(akbasic_ASTLeaf *self, char *dest, size_t len) { PREPARE_ERROR(errctx); char sub1[AKBASIC_MAX_STRING_LENGTH]; char sub2[AKBASIC_MAX_STRING_LENGTH]; FAIL_ZERO_RETURN(errctx, (self != NULL), AKERR_NULLPOINTER, "NULL leaf in to_string"); FAIL_ZERO_RETURN(errctx, (dest != NULL), AKERR_NULLPOINTER, "NULL destination in to_string"); FAIL_ZERO_RETURN(errctx, (len > 0), AKBASIC_ERR_BOUNDS, "Zero-length destination in to_string"); switch ( self->leaftype ) { case AKBASIC_LEAF_LITERAL_INT: snprintf(dest, len, "%" PRId64, self->literal_int); break; case AKBASIC_LEAF_LITERAL_FLOAT: snprintf(dest, len, "%f", self->literal_float); break; case AKBASIC_LEAF_LITERAL_STRING: snprintf(dest, len, "%s", self->literal_string); break; case AKBASIC_LEAF_IDENTIFIER_INT: case AKBASIC_LEAF_IDENTIFIER_FLOAT: case AKBASIC_LEAF_IDENTIFIER_STRING: case AKBASIC_LEAF_IDENTIFIER: snprintf(dest, len, "%s", self->identifier); break; case AKBASIC_LEAF_IDENTIFIER_STRUCT: snprintf(dest, len, "NOT IMPLEMENTED"); break; case AKBASIC_LEAF_UNARY: PASS(errctx, akbasic_leaf_to_string(self->left, sub1, sizeof(sub1))); snprintf(dest, len, "(%s %s)", operator_to_str(self->operator_), sub1); break; case AKBASIC_LEAF_BINARY: PASS(errctx, akbasic_leaf_to_string(self->left, sub1, sizeof(sub1))); PASS(errctx, akbasic_leaf_to_string(self->right, sub2, sizeof(sub2))); snprintf(dest, len, "(%s %s %s)", operator_to_str(self->operator_), sub1, sub2); break; case AKBASIC_LEAF_GROUPING: PASS(errctx, akbasic_leaf_to_string(self->expr, sub1, sizeof(sub1))); snprintf(dest, len, "(group %s)", sub1); break; case AKBASIC_LEAF_COMMAND: case AKBASIC_LEAF_COMMAND_IMMEDIATE: case AKBASIC_LEAF_FUNCTION: /* * The reference falls through to Go's %+v struct dump here, which has no * useful C equivalent. Print something a test can assert on instead. */ snprintf(dest, len, "(%s)", self->identifier); break; default: snprintf(dest, len, "(leaf %d)", (int)self->leaftype); break; } SUCCEED_RETURN(errctx); }