/** * @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 *right) { PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL leaf in new_unary"); FAIL_ZERO_RETURN(errctx, (right != NULL), AKERR_NULLPOINTER, "nil pointer arguments"); PASS(errctx, akbasic_leaf_init(obj, AKBASIC_LEAF_UNARY)); obj->right = right; 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"); /* * The reference selects base 8 for any lexeme starting with '0', so `010` * parses as 8 and `08` is an error. Commodore BASIC has no octal literals * and this is TODO.md section 12 item 10 -- but it is reproduced here, * because "port the behaviour first" is the rule and the fix is its own * commit with its own test. */ if ( strlen(lexeme) > 2 && strncmp(lexeme, "0x", 2) == 0 ) { base = 16; } else if ( lexeme[0] == '0' ) { base = 8; } 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->right, 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); }