394 lines
14 KiB
C
394 lines
14 KiB
C
|
|
/**
|
||
|
|
* @file grammar.c
|
||
|
|
* @brief Implements token and AST leaf construction.
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include <inttypes.h>
|
||
|
|
#include <stdio.h>
|
||
|
|
#include <string.h>
|
||
|
|
|
||
|
|
#include <akerror.h>
|
||
|
|
|
||
|
|
#include <akbasic/convert.h>
|
||
|
|
#include <akbasic/error.h>
|
||
|
|
#include <akbasic/grammar.h>
|
||
|
|
|
||
|
|
/* 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);
|
||
|
|
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, akbasic_str_to_int64(lexeme, base, &obj->literal_int));
|
||
|
|
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, akbasic_str_to_double(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);
|
||
|
|
}
|