Files
akbasic/src/parser.c
Andrew Kesterson 6a7c8cd920 Add records: TYPE, DIM ... AS, field access, copy on assign
BASIC 7.0 has no records at all, so none of this is a port. The `@` suffix was
not invented either: the Go reference reserved IDENTIFIER_STRUCT and never used
it, and src/grammar.c rendered such a leaf as "NOT IMPLEMENTED" until now.

Declaring the type is what buys the storage model. A TYPE states its fields, so
an instance has a known slot count and is laid out exactly as an array is -- one
contiguous run from the same value pool DIM already draws from, with field access
as offset arithmetic. No new pool holds data; the only new table holds
descriptors. A nested value flattens into its container's run, which is why
LINE with two POINTs and a string is five slots rather than three.

Each field takes its type from its own suffix, the same rule every other name
here follows, so a field list needs no type column. An `@` field is the
exception and has to name its type, because three primitive types fit in three
suffix characters and N declared types do not fit in one.

The declaration is prescanned before the program runs, like labels and DATA and
for the same reason: it has to be in effect wherever control goes. Three passes,
each for a case the one before cannot do -- names first so a field can refer to
a type declared later, then field lists, then sizes by repeated resolution. What
never resolves is a cycle of by-value containment, so "a TYPE cannot contain
itself by value" is a diagnosis rather than an assumption, and the message says
to use PTR TO instead.

Assignment copies. That interception is the whole feature and it cannot live in
akbasic_value_clone(), which copies one slot -- and one slot holds a *reference*
to an instance rather than the instance, so going through it would alias. A
structure is intercepted before that path and its slots are copied one at a
time, walking the descriptor rather than memcpy-ing the run, because a pointer
field must copy its reference where a value field must copy its slots.

Two smaller things the work required. All three prescans now sit inside one
ATTEMPT: a malformed declaration is the program's mistake, and it was printing a
stack trace and taking the driver with it, which is the boundary goal 3 exists
to draw. And a fresh variable's structtype is -1 rather than the 0 a memset
leaves, because 0 is a valid type index and every new variable was claiming to
be the first type declared.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 11:39:47 -04:00

746 lines
26 KiB
C

/**
* @file parser.c
* @brief Implements the recursive-descent parser.
*
* A faithful port of basicparser.go, with two changes. The reflection lookup for
* a verb's special parse path becomes a table lookup, and the debug.PrintStack()
* the reference calls on a parse failure is gone: an interpreter library does not
* dump the host's stack to stderr, and the akerr stack trace already carries what
* that call was for.
*/
#include <inttypes.h>
#include <string.h>
#include <akerror.h>
#include <akbasic/error.h>
#include <akbasic/parser.h>
#include <akbasic/verbs.h>
akerr_ErrorContext *akbasic_parser_init(akbasic_Parser *obj, akbasic_Runtime *runtime)
{
PREPARE_ERROR(errctx);
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL parser in init");
FAIL_ZERO_RETURN(errctx, (runtime != NULL), AKERR_NULLPOINTER, "nil runtime argument");
obj->runtime = runtime;
obj->comparing = false;
SUCCEED_RETURN(errctx);
}
akerr_ErrorContext *akbasic_parser_zero(akbasic_Parser *obj)
{
PREPARE_ERROR(errctx);
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "nil self reference!");
PASS(errctx, akbasic_environment_zero_parser(obj->runtime->environment));
SUCCEED_RETURN(errctx);
}
akerr_ErrorContext *akbasic_parser_new_leaf(akbasic_Parser *obj, akbasic_ASTLeaf **dest)
{
PREPARE_ERROR(errctx);
PASS(errctx, akbasic_environment_new_leaf(obj->runtime->environment, dest));
SUCCEED_RETURN(errctx);
}
bool akbasic_parser_is_at_end(akbasic_Parser *obj)
{
akbasic_Environment *env = NULL;
if ( obj == NULL || obj->runtime == NULL || obj->runtime->environment == NULL ) {
return true;
}
env = obj->runtime->environment;
return (env->curtoken >= (AKBASIC_MAX_TOKENS - 1) || env->curtoken >= env->nexttoken);
}
akbasic_Token *akbasic_parser_peek(akbasic_Parser *obj)
{
if ( akbasic_parser_is_at_end(obj) ) {
return NULL;
}
return &obj->runtime->environment->tokens[obj->runtime->environment->curtoken];
}
akerr_ErrorContext *akbasic_parser_previous(akbasic_Parser *obj, akbasic_Token **dest)
{
PREPARE_ERROR(errctx);
FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER,
"NULL argument in previous");
FAIL_ZERO_RETURN(errctx, (obj->runtime->environment->curtoken != 0), AKBASIC_ERR_SYNTAX,
"Current token is index 0, no previous token");
*dest = &obj->runtime->environment->tokens[obj->runtime->environment->curtoken - 1];
SUCCEED_RETURN(errctx);
}
static bool check(akbasic_Parser *obj, akbasic_TokenType tokentype)
{
akbasic_Token *next = NULL;
if ( akbasic_parser_is_at_end(obj) ) {
return false;
}
next = akbasic_parser_peek(obj);
return (next != NULL && next->tokentype == tokentype);
}
static void advance(akbasic_Parser *obj)
{
if ( !akbasic_parser_is_at_end(obj) ) {
obj->runtime->environment->curtoken += 1;
}
}
bool akbasic_parser_match(akbasic_Parser *obj, const akbasic_TokenType *types, int count)
{
int i = 0;
for ( i = 0; i < count; i++ ) {
if ( check(obj, types[i]) ) {
advance(obj);
return true;
}
}
return false;
}
bool akbasic_parser_match1(akbasic_Parser *obj, akbasic_TokenType type)
{
return akbasic_parser_match(obj, &type, 1);
}
akerr_ErrorContext *akbasic_parser_error(akbasic_Parser *obj, const char *message)
{
PREPARE_ERROR(errctx);
akbasic_Token *token = NULL;
token = akbasic_parser_peek(obj);
obj->runtime->environment->errorToken = token;
FAIL_ZERO_RETURN(errctx, (token != NULL), AKBASIC_ERR_SYNTAX, "peek() returned nil token!");
if ( token->tokentype == AKBASIC_TOK_EOF ) {
FAIL_RETURN(errctx, AKBASIC_ERR_SYNTAX, "%" PRId64 " at end %s", token->lineno, message);
}
FAIL_RETURN(errctx, AKBASIC_ERR_SYNTAX, "%" PRId64 " at '%s', %s",
token->lineno, token->lexeme, message);
}
static akerr_ErrorContext *consume(akbasic_Parser *obj, akbasic_TokenType tokentype, const char *message)
{
PREPARE_ERROR(errctx);
if ( check(obj, tokentype) ) {
advance(obj);
SUCCEED_RETURN(errctx);
}
PASS(errctx, akbasic_parser_error(obj, message));
SUCCEED_RETURN(errctx);
}
/* Forward declarations for the grammar chain. */
static akerr_ErrorContext *logicalandor(akbasic_Parser *obj, akbasic_ASTLeaf **dest);
static akerr_ErrorContext *logicalnot(akbasic_Parser *obj, akbasic_ASTLeaf **dest);
static akerr_ErrorContext *subtraction(akbasic_Parser *obj, akbasic_ASTLeaf **dest);
static akerr_ErrorContext *addition(akbasic_Parser *obj, akbasic_ASTLeaf **dest);
static akerr_ErrorContext *multiplication(akbasic_Parser *obj, akbasic_ASTLeaf **dest);
static akerr_ErrorContext *division(akbasic_Parser *obj, akbasic_ASTLeaf **dest);
static akerr_ErrorContext *unary(akbasic_Parser *obj, akbasic_ASTLeaf **dest);
static akerr_ErrorContext *exponent(akbasic_Parser *obj, akbasic_ASTLeaf **dest);
static akerr_ErrorContext *function_call(akbasic_Parser *obj, akbasic_ASTLeaf **dest);
bool akbasic_parser_skip_separators(akbasic_Parser *obj)
{
while ( akbasic_parser_match1(obj, AKBASIC_TOK_COLON) ) {
/*
* A run of them, so `10 PRINT 1 :: PRINT 2` and a trailing colon both
* work. An empty statement is not an error on a C128 and is not one
* here.
*/
}
return !akbasic_parser_is_at_end(obj);
}
akerr_ErrorContext *akbasic_parser_parse(akbasic_Parser *obj, akbasic_ASTLeaf **dest)
{
PREPARE_ERROR(errctx);
FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER,
"NULL argument in parse");
*dest = NULL;
/*
* Statements are separated by colons. The scanner has emitted the token
* since the port began and nothing consumed it, so a line with two
* statements on it was a parse error -- TODO.md section 4.
*
* Nothing after the separators is a line that ended in one. That is not an
* error, so the caller gets a NULL leaf and skips it.
*/
if ( !akbasic_parser_skip_separators(obj) ) {
SUCCEED_RETURN(errctx);
}
PASS(errctx, akbasic_parser_command(obj, dest));
SUCCEED_RETURN(errctx);
}
akerr_ErrorContext *akbasic_parser_command(akbasic_Parser *obj, akbasic_ASTLeaf **dest)
{
PREPARE_ERROR(errctx);
static const akbasic_TokenType COMMANDS[] = {
AKBASIC_TOK_COMMAND, AKBASIC_TOK_COMMAND_IMMEDIATE
};
akbasic_ASTLeaf *expr = NULL;
akbasic_ASTLeaf *right = NULL;
akbasic_Token *operator_ = NULL;
akbasic_Token *righttoken = NULL;
const akbasic_Verb *verb = NULL;
akbasic_TokenType optype = AKBASIC_TOK_UNDEFINED;
char opname[AKBASIC_MAX_LINE_LENGTH];
if ( !akbasic_parser_match(obj, COMMANDS, 2) ) {
PASS(errctx, akbasic_parser_assignment(obj, dest));
SUCCEED_RETURN(errctx);
}
PASS(errctx, akbasic_parser_previous(obj, &operator_));
optype = operator_->tokentype;
strncpy(opname, operator_->lexeme, sizeof(opname) - 1);
opname[sizeof(opname) - 1] = '\0';
/* Does this verb need its own parse path? */
PASS(errctx, akbasic_verb_lookup(opname, &verb));
if ( verb != NULL && verb->parse != NULL ) {
PASS(errctx, verb->parse(obj, dest));
SUCCEED_RETURN(errctx);
}
/*
* Some verbs take no rval. Do not fail when there is not one -- but do fail
* when there is one and it will not parse.
*/
righttoken = akbasic_parser_peek(obj);
if ( righttoken != NULL && righttoken->tokentype != AKBASIC_TOK_UNDEFINED &&
righttoken->tokentype != AKBASIC_TOK_COLON ) {
/*
* A colon ends the statement, so `PRINT : PRINT "X"` is a bare PRINT
* followed by another statement rather than a PRINT of whatever a colon
* evaluates to.
*/
PASS(errctx, akbasic_parser_expression(obj, &right));
}
PASS(errctx, akbasic_parser_new_leaf(obj, &expr));
if ( optype == AKBASIC_TOK_COMMAND_IMMEDIATE ) {
PASS(errctx, akbasic_leaf_new_immediate_command(expr, opname, right));
} else {
PASS(errctx, akbasic_leaf_new_command(expr, opname, right));
}
*dest = expr;
SUCCEED_RETURN(errctx);
}
akerr_ErrorContext *akbasic_parser_assignment(akbasic_Parser *obj, akbasic_ASTLeaf **dest)
{
PREPARE_ERROR(errctx);
akbasic_ASTLeaf *identifier = NULL;
akbasic_ASTLeaf *expr = NULL;
akbasic_ASTLeaf *right = NULL;
PASS(errctx, akbasic_parser_expression(obj, &identifier));
/*
* A field access is assignable too, and it is not an identifier: the
* name on the left of the = is reached by walking a chain rather than by
* looking one name up. Both spellings continue into the = below.
*/
if ( identifier == NULL ||
(identifier->leaftype != AKBASIC_LEAF_IDENTIFIER_INT &&
identifier->leaftype != AKBASIC_LEAF_IDENTIFIER_FLOAT &&
identifier->leaftype != AKBASIC_LEAF_IDENTIFIER_STRING &&
identifier->leaftype != AKBASIC_LEAF_IDENTIFIER_STRUCT &&
identifier->leaftype != AKBASIC_LEAF_FIELD) ) {
*dest = identifier;
SUCCEED_RETURN(errctx);
}
if ( akbasic_parser_match1(obj, AKBASIC_TOK_ASSIGNMENT) ) {
PASS(errctx, akbasic_parser_expression(obj, &right));
PASS(errctx, akbasic_parser_new_leaf(obj, &expr));
PASS(errctx, akbasic_leaf_new_binary(expr, identifier, AKBASIC_TOK_ASSIGNMENT, right));
*dest = expr;
SUCCEED_RETURN(errctx);
}
*dest = identifier;
SUCCEED_RETURN(errctx);
}
/*
* An argument list is just .right-joined expressions continuing ad infinitum.
* When requireparens is false and there is no opening paren, this still builds a
* list -- that is how DATA and READ take a bare comma-separated series.
*/
akerr_ErrorContext *akbasic_parser_argument_list(akbasic_Parser *obj, akbasic_TokenType arglisttype, bool requireparens, akbasic_ASTLeaf **dest)
{
PREPARE_ERROR(errctx);
akbasic_ASTLeaf *arglist = NULL;
akbasic_ASTLeaf *expr = NULL;
*dest = NULL;
if ( !akbasic_parser_match1(obj, AKBASIC_TOK_LEFT_PAREN) && requireparens ) {
SUCCEED_RETURN(errctx);
}
FAIL_ZERO_RETURN(errctx,
(arglisttype == AKBASIC_TOK_ARRAY_SUBSCRIPT ||
arglisttype == AKBASIC_TOK_FUNCTION_ARGUMENT),
AKBASIC_ERR_SYNTAX,
"argumentList expects argListType [ARRAY_SUBSCRIPT || FUNCTION_ARGUMENT]");
PASS(errctx, akbasic_parser_new_leaf(obj, &arglist));
arglist->leaftype = AKBASIC_LEAF_ARGUMENTLIST;
arglist->operator_ = arglisttype;
PASS(errctx, akbasic_parser_expression(obj, &arglist->right));
/*
* Siblings chain through .next, never through .right. Every leaf type that
* can be an argument already uses .right for something of its own -- a
* unary's operand, a binary's right-hand side -- and reusing it here is what
* made ABS(-9) count as two arguments. See akbasic_ASTLeaf.next.
*/
expr = arglist->right;
while ( expr != NULL && akbasic_parser_match1(obj, AKBASIC_TOK_COMMA) ) {
PASS(errctx, akbasic_parser_expression(obj, &expr->next));
expr = expr->next;
}
if ( !akbasic_parser_match1(obj, AKBASIC_TOK_RIGHT_PAREN) && requireparens ) {
FAIL_RETURN(errctx, AKBASIC_ERR_SYNTAX, "Unbalanced parenthesis");
}
*dest = arglist;
SUCCEED_RETURN(errctx);
}
akerr_ErrorContext *akbasic_parser_expression(akbasic_Parser *obj, akbasic_ASTLeaf **dest)
{
PREPARE_ERROR(errctx);
PASS(errctx, logicalandor(obj, dest));
SUCCEED_RETURN(errctx);
}
static akerr_ErrorContext *logicalandor(akbasic_Parser *obj, akbasic_ASTLeaf **dest)
{
PREPARE_ERROR(errctx);
static const akbasic_TokenType OPS[] = { AKBASIC_TOK_AND, AKBASIC_TOK_OR };
akbasic_ASTLeaf *left = NULL;
akbasic_ASTLeaf *right = NULL;
akbasic_ASTLeaf *expr = NULL;
akbasic_Token *operator_ = NULL;
PASS(errctx, logicalnot(obj, &left));
if ( akbasic_parser_match(obj, OPS, 2) ) {
PASS(errctx, akbasic_parser_previous(obj, &operator_));
PASS(errctx, logicalnot(obj, &right));
PASS(errctx, akbasic_parser_new_leaf(obj, &expr));
PASS(errctx, akbasic_leaf_new_binary(expr, left, operator_->tokentype, right));
*dest = expr;
SUCCEED_RETURN(errctx);
}
*dest = left;
SUCCEED_RETURN(errctx);
}
static akerr_ErrorContext *logicalnot(akbasic_Parser *obj, akbasic_ASTLeaf **dest)
{
PREPARE_ERROR(errctx);
akbasic_ASTLeaf *right = NULL;
akbasic_ASTLeaf *expr = NULL;
akbasic_Token *operator_ = NULL;
if ( akbasic_parser_match1(obj, AKBASIC_TOK_NOT) ) {
PASS(errctx, akbasic_parser_previous(obj, &operator_));
PASS(errctx, akbasic_parser_relation(obj, &right));
PASS(errctx, akbasic_parser_new_leaf(obj, &expr));
PASS(errctx, akbasic_leaf_new_unary(expr, operator_->tokentype, right));
*dest = expr;
SUCCEED_RETURN(errctx);
}
PASS(errctx, akbasic_parser_relation(obj, dest));
SUCCEED_RETURN(errctx);
}
akerr_ErrorContext *akbasic_parser_relation(akbasic_Parser *obj, akbasic_ASTLeaf **dest)
{
PREPARE_ERROR(errctx);
/*
* A lone `=` is an equality test here when the caller has said a condition
* is what it is parsing. `==` keeps working either way, which is what the Go
* reference used and what the whole checked-in corpus is written in. See
* akbasic_Parser::comparing and TODO.md section 5.
*/
static const akbasic_TokenType OPS[] = {
AKBASIC_TOK_LESS_THAN, AKBASIC_TOK_LESS_THAN_EQUAL, AKBASIC_TOK_EQUAL,
AKBASIC_TOK_NOT_EQUAL, AKBASIC_TOK_GREATER_THAN, AKBASIC_TOK_GREATER_THAN_EQUAL,
AKBASIC_TOK_ASSIGNMENT
};
akbasic_ASTLeaf *left = NULL;
akbasic_ASTLeaf *right = NULL;
akbasic_ASTLeaf *expr = NULL;
akbasic_Token *operator_ = NULL;
akbasic_TokenType op = AKBASIC_TOK_UNDEFINED;
/*
* ASSIGNMENT is the last entry, and it is only offered while `comparing` --
* see akbasic_Parser. Outside a condition `=` has to stay an assignment, or
* `A# = 2` stops storing anything and `FOR I# = 1 TO 5` never initializes
* its counter.
*/
int opcount = (obj->comparing ? 7 : 6);
PASS(errctx, subtraction(obj, &left));
if ( akbasic_parser_match(obj, OPS, opcount) ) {
PASS(errctx, akbasic_parser_previous(obj, &operator_));
op = operator_->tokentype;
if ( op == AKBASIC_TOK_ASSIGNMENT ) {
op = AKBASIC_TOK_EQUAL;
}
PASS(errctx, subtraction(obj, &right));
PASS(errctx, akbasic_parser_new_leaf(obj, &expr));
PASS(errctx, akbasic_leaf_new_binary(expr, left, op, right));
*dest = expr;
SUCCEED_RETURN(errctx);
}
*dest = left;
SUCCEED_RETURN(errctx);
}
/*
* Loops, where the reference returns after one operator (basicparser.go:329).
* That asymmetry was reproduced faithfully at first and is TODO.md section 6
* item 12: `1 - 2 - 3` computed `1 - 2` and abandoned the rest of the line,
* which the statement loop then picked up as a second statement and discarded.
* A wrong answer, silently -- the highest-impact item on that list.
*
* Left-associative, matching addition, multiplication and division here and
* matching a C128, which evaluates a run of same-precedence operators left to
* right.
*/
static akerr_ErrorContext *subtraction(akbasic_Parser *obj, akbasic_ASTLeaf **dest)
{
PREPARE_ERROR(errctx);
akbasic_ASTLeaf *left = NULL;
akbasic_ASTLeaf *right = NULL;
akbasic_ASTLeaf *expr = NULL;
akbasic_Token *operator_ = NULL;
PASS(errctx, addition(obj, &left));
while ( akbasic_parser_match1(obj, AKBASIC_TOK_MINUS) ) {
PASS(errctx, akbasic_parser_previous(obj, &operator_));
PASS(errctx, addition(obj, &right));
if ( expr != NULL ) {
left = expr;
}
PASS(errctx, akbasic_parser_new_leaf(obj, &expr));
PASS(errctx, akbasic_leaf_new_binary(expr, left, operator_->tokentype, right));
}
*dest = (expr != NULL ? expr : left);
SUCCEED_RETURN(errctx);
}
static akerr_ErrorContext *addition(akbasic_Parser *obj, akbasic_ASTLeaf **dest)
{
PREPARE_ERROR(errctx);
akbasic_ASTLeaf *left = NULL;
akbasic_ASTLeaf *right = NULL;
akbasic_ASTLeaf *expr = NULL;
akbasic_Token *operator_ = NULL;
PASS(errctx, multiplication(obj, &left));
while ( akbasic_parser_match1(obj, AKBASIC_TOK_PLUS) ) {
PASS(errctx, akbasic_parser_previous(obj, &operator_));
PASS(errctx, multiplication(obj, &right));
if ( expr != NULL ) {
left = expr;
}
PASS(errctx, akbasic_parser_new_leaf(obj, &expr));
PASS(errctx, akbasic_leaf_new_binary(expr, left, operator_->tokentype, right));
}
*dest = (expr != NULL ? expr : left);
SUCCEED_RETURN(errctx);
}
static akerr_ErrorContext *multiplication(akbasic_Parser *obj, akbasic_ASTLeaf **dest)
{
PREPARE_ERROR(errctx);
akbasic_ASTLeaf *left = NULL;
akbasic_ASTLeaf *right = NULL;
akbasic_ASTLeaf *expr = NULL;
akbasic_Token *operator_ = NULL;
PASS(errctx, division(obj, &left));
while ( akbasic_parser_match1(obj, AKBASIC_TOK_STAR) ) {
PASS(errctx, akbasic_parser_previous(obj, &operator_));
PASS(errctx, division(obj, &right));
if ( expr != NULL ) {
left = expr;
}
PASS(errctx, akbasic_parser_new_leaf(obj, &expr));
PASS(errctx, akbasic_leaf_new_binary(expr, left, operator_->tokentype, right));
}
*dest = (expr != NULL ? expr : left);
SUCCEED_RETURN(errctx);
}
static akerr_ErrorContext *division(akbasic_Parser *obj, akbasic_ASTLeaf **dest)
{
PREPARE_ERROR(errctx);
akbasic_ASTLeaf *left = NULL;
akbasic_ASTLeaf *right = NULL;
akbasic_ASTLeaf *expr = NULL;
akbasic_Token *operator_ = NULL;
PASS(errctx, unary(obj, &left));
while ( akbasic_parser_match1(obj, AKBASIC_TOK_LEFT_SLASH) ) {
PASS(errctx, akbasic_parser_previous(obj, &operator_));
PASS(errctx, unary(obj, &right));
if ( expr != NULL ) {
left = expr;
}
PASS(errctx, akbasic_parser_new_leaf(obj, &expr));
PASS(errctx, akbasic_leaf_new_binary(expr, left, operator_->tokentype, right));
}
*dest = (expr != NULL ? expr : left);
SUCCEED_RETURN(errctx);
}
static akerr_ErrorContext *unary(akbasic_Parser *obj, akbasic_ASTLeaf **dest)
{
PREPARE_ERROR(errctx);
akbasic_ASTLeaf *right = NULL;
akbasic_ASTLeaf *expr = NULL;
akbasic_Token *operator_ = NULL;
if ( akbasic_parser_match1(obj, AKBASIC_TOK_MINUS) ) {
PASS(errctx, akbasic_parser_previous(obj, &operator_));
PASS(errctx, akbasic_parser_primary(obj, &right));
PASS(errctx, akbasic_parser_new_leaf(obj, &expr));
PASS(errctx, akbasic_leaf_new_unary(expr, operator_->tokentype, right));
*dest = expr;
SUCCEED_RETURN(errctx);
}
PASS(errctx, exponent(obj, dest));
SUCCEED_RETURN(errctx);
}
static akerr_ErrorContext *exponent(akbasic_Parser *obj, akbasic_ASTLeaf **dest)
{
PREPARE_ERROR(errctx);
akbasic_ASTLeaf *left = NULL;
akbasic_ASTLeaf *right = NULL;
akbasic_ASTLeaf *expr = NULL;
akbasic_Token *operator_ = NULL;
PASS(errctx, function_call(obj, &left));
/*
* Loops for the same reason subtraction does -- `2 ^ 3 ^ 2` abandoned the
* second operator before this. Left-associative, which is a C128: 2^3^2 is
* 64 there, not the 512 a right-associative reading gives.
*/
while ( akbasic_parser_match1(obj, AKBASIC_TOK_CARAT) ) {
PASS(errctx, akbasic_parser_previous(obj, &operator_));
PASS(errctx, function_call(obj, &right));
if ( expr != NULL ) {
left = expr;
}
PASS(errctx, akbasic_parser_new_leaf(obj, &expr));
PASS(errctx, akbasic_leaf_new_binary(expr, left, operator_->tokentype, right));
}
*dest = (expr != NULL ? expr : left);
SUCCEED_RETURN(errctx);
}
/* Count the arguments hanging off an argument list. */
static int arglist_length(akbasic_ASTLeaf *arglist)
{
akbasic_ASTLeaf *leaf = NULL;
int count = 0;
if ( arglist == NULL ) {
return 0;
}
for ( leaf = arglist->right; leaf != NULL; leaf = leaf->next ) {
count += 1;
}
return count;
}
/*
* Called for function *calls* only, never for a DEF. A FUNCTION token is either
* a table builtin or a user DEF; both check their argument count here, so a
* wrong-arity call is a parse error rather than a runtime surprise.
*/
static akerr_ErrorContext *function_call(akbasic_Parser *obj, akbasic_ASTLeaf **dest)
{
PREPARE_ERROR(errctx);
akbasic_ASTLeaf *arglist = NULL;
akbasic_ASTLeaf *leaf = NULL;
akbasic_Token *operator_ = NULL;
const akbasic_Verb *verb = NULL;
akbasic_FunctionDef *fndef = NULL;
void *fnptr = NULL;
char fname[AKBASIC_MAX_LINE_LENGTH];
int wanted = 0;
int given = 0;
bool found = false;
if ( !akbasic_parser_match1(obj, AKBASIC_TOK_FUNCTION) ) {
PASS(errctx, akbasic_parser_primary(obj, dest));
SUCCEED_RETURN(errctx);
}
PASS(errctx, akbasic_parser_previous(obj, &operator_));
strncpy(fname, operator_->lexeme, sizeof(fname) - 1);
fname[sizeof(fname) - 1] = '\0';
PASS(errctx, akbasic_verb_lookup(fname, &verb));
if ( verb != NULL && verb->tokentype == AKBASIC_TOK_FUNCTION ) {
wanted = verb->arity;
found = true;
} else {
ATTEMPT {
CATCH(errctx, akbasic_environment_get_function(obj->runtime->environment, fname, &fnptr));
found = true;
} CLEANUP {
} PROCESS(errctx) {
} HANDLE(errctx, AKERR_KEY) {
found = false;
} FINISH(errctx, true);
if ( found ) {
fndef = (akbasic_FunctionDef *)fnptr;
wanted = arglist_length(fndef->arglist);
}
}
FAIL_ZERO_RETURN(errctx, found, AKBASIC_ERR_UNDEFINED, "No such function %s", fname);
PASS(errctx, akbasic_parser_argument_list(obj, AKBASIC_TOK_FUNCTION_ARGUMENT, true, &arglist));
given = arglist_length(arglist);
FAIL_ZERO_RETURN(errctx, (given == wanted), AKBASIC_ERR_SYNTAX,
"function %s takes %d arguments, received %d", fname, wanted, given);
PASS(errctx, akbasic_parser_new_leaf(obj, &leaf));
PASS(errctx, akbasic_leaf_new_function(leaf, fname, arglist));
*dest = leaf;
SUCCEED_RETURN(errctx);
}
/**
* @brief Consume a run of `.field` and `->field` after a primary.
*
* Left-associative, so `A@.B@.C#` reads as `(A@.B@).C#` and each step's base is
* the leaf before it. The two operators are kept apart all the way to the
* runtime rather than folded into one "field access", because which one was
* written is the whole of the strict-pointer rule: `.` requires a structure and
* `->` requires a pointer to one, and getting it wrong is an error rather than a
* convenience the interpreter silently absorbs.
*/
static akerr_ErrorContext *parse_field_chain(akbasic_Parser *obj, akbasic_ASTLeaf **expr)
{
PREPARE_ERROR(errctx);
static const akbasic_TokenType ACCESSORS[] = { AKBASIC_TOK_DOT, AKBASIC_TOK_ARROW };
/*
* A field name carries its own type suffix, exactly as a variable does, so
* the four suffixed identifier tokens are the whole set. A bare identifier
* is not among them: that spelling is a label everywhere else and a field
* with no suffix would have no type.
*/
static const akbasic_TokenType FIELDNAMES[] = {
AKBASIC_TOK_IDENTIFIER_INT, AKBASIC_TOK_IDENTIFIER_FLOAT,
AKBASIC_TOK_IDENTIFIER_STRING, AKBASIC_TOK_IDENTIFIER_STRUCT
};
akbasic_ASTLeaf *field = NULL;
akbasic_Token *accessor = NULL;
akbasic_Token *name = NULL;
akbasic_TokenType op = AKBASIC_TOK_DOT;
while ( akbasic_parser_match(obj, ACCESSORS, 2) ) {
PASS(errctx, akbasic_parser_previous(obj, &accessor));
op = accessor->tokentype;
FAIL_ZERO_RETURN(errctx, akbasic_parser_match(obj, FIELDNAMES, 4), AKBASIC_ERR_SYNTAX,
"Expected a field name after %s", (op == AKBASIC_TOK_ARROW ? "->" : "."));
PASS(errctx, akbasic_parser_previous(obj, &name));
PASS(errctx, akbasic_parser_new_leaf(obj, &field));
PASS(errctx, akbasic_leaf_new_field(field, *expr, name->lexeme, op));
/*
* A field may itself be an array element -- `E@.DROPS#(2)` -- and the
* subscript list belongs to the field, not to the base, so it is
* collected here and onto the field leaf's own `.expr`.
*/
PASS(errctx, akbasic_parser_argument_list(obj, AKBASIC_TOK_ARRAY_SUBSCRIPT, true, &field->expr));
*expr = field;
}
SUCCEED_RETURN(errctx);
}
akerr_ErrorContext *akbasic_parser_primary(akbasic_Parser *obj, akbasic_ASTLeaf **dest)
{
PREPARE_ERROR(errctx);
static const akbasic_TokenType PRIMARIES[] = {
AKBASIC_TOK_LITERAL_INT, AKBASIC_TOK_LITERAL_FLOAT, AKBASIC_TOK_LITERAL_STRING,
AKBASIC_TOK_IDENTIFIER, AKBASIC_TOK_IDENTIFIER_STRING, AKBASIC_TOK_IDENTIFIER_FLOAT,
AKBASIC_TOK_IDENTIFIER_INT, AKBASIC_TOK_FUNCTION, AKBASIC_TOK_IDENTIFIER_STRUCT
};
akbasic_ASTLeaf *expr = NULL;
akbasic_ASTLeaf *groupexpr = NULL;
akbasic_Token *previous = NULL;
if ( akbasic_parser_match(obj, PRIMARIES, 9) ) {
PASS(errctx, akbasic_parser_previous(obj, &previous));
PASS(errctx, akbasic_parser_new_leaf(obj, &expr));
switch ( previous->tokentype ) {
case AKBASIC_TOK_LITERAL_INT:
PASS(errctx, akbasic_leaf_new_literal_int(expr, previous->lexeme));
break;
case AKBASIC_TOK_LITERAL_FLOAT:
PASS(errctx, akbasic_leaf_new_literal_float(expr, previous->lexeme));
break;
case AKBASIC_TOK_LITERAL_STRING:
PASS(errctx, akbasic_leaf_new_literal_string(expr, previous->lexeme));
break;
case AKBASIC_TOK_IDENTIFIER_INT:
PASS(errctx, akbasic_leaf_new_identifier(expr, AKBASIC_LEAF_IDENTIFIER_INT, previous->lexeme));
PASS(errctx, akbasic_parser_argument_list(obj, AKBASIC_TOK_ARRAY_SUBSCRIPT, true, &expr->expr));
break;
case AKBASIC_TOK_IDENTIFIER_FLOAT:
PASS(errctx, akbasic_leaf_new_identifier(expr, AKBASIC_LEAF_IDENTIFIER_FLOAT, previous->lexeme));
PASS(errctx, akbasic_parser_argument_list(obj, AKBASIC_TOK_ARRAY_SUBSCRIPT, true, &expr->expr));
break;
case AKBASIC_TOK_IDENTIFIER_STRING:
PASS(errctx, akbasic_leaf_new_identifier(expr, AKBASIC_LEAF_IDENTIFIER_STRING, previous->lexeme));
PASS(errctx, akbasic_parser_argument_list(obj, AKBASIC_TOK_ARRAY_SUBSCRIPT, true, &expr->expr));
break;
case AKBASIC_TOK_IDENTIFIER_STRUCT:
PASS(errctx, akbasic_leaf_new_identifier(expr, AKBASIC_LEAF_IDENTIFIER_STRUCT, previous->lexeme));
PASS(errctx, akbasic_parser_argument_list(obj, AKBASIC_TOK_ARRAY_SUBSCRIPT, true, &expr->expr));
break;
case AKBASIC_TOK_FUNCTION:
case AKBASIC_TOK_IDENTIFIER:
PASS(errctx, akbasic_leaf_new_identifier(expr, AKBASIC_LEAF_IDENTIFIER, previous->lexeme));
break;
default:
FAIL_RETURN(errctx, AKBASIC_ERR_SYNTAX, "Invalid literal type, command or function name");
}
PASS(errctx, parse_field_chain(obj, &expr));
*dest = expr;
SUCCEED_RETURN(errctx);
}
if ( akbasic_parser_match1(obj, AKBASIC_TOK_LEFT_PAREN) ) {
PASS(errctx, akbasic_parser_expression(obj, &groupexpr));
PASS(errctx, consume(obj, AKBASIC_TOK_RIGHT_PAREN, "Missing ) after expression"));
PASS(errctx, akbasic_parser_new_leaf(obj, &expr));
PASS(errctx, akbasic_leaf_new_grouping(expr, groupexpr));
*dest = expr;
SUCCEED_RETURN(errctx);
}
PASS(errctx, akbasic_parser_error(obj, "Expected expression or literal"));
SUCCEED_RETURN(errctx);
}