akbasic's src/ now calls libakstdlib 313 times and raw libc 7 -- 2.2% bypassed, against 86.4% on the same tree before this. The submodule bump 669b2b3 -> 2b79aca needed no source change of its own: the release is drop-in for what akbasic already used. Seven of the eight sites the earlier port left on raw libc change their own signature rather than swallowing an error, per andrew's ruling on libakstdlib#38. word_is, the is_waiting_for pair, the scanner's is_at_end, peek, peek_next and match_next_char, format.c's overflow, and sink_akgl's scroll/newline/putchar_at/echo_line/edit_key chain all return an akerr_ErrorContext * and hand the answer back through an out parameter. is_waiting_for and is_waiting_for_any are a public header change; every call site that used one as a term in a condition hoists it into a statement first. verb_compare is the eighth and stays on strcmp. bsearch(3) fixes the comparator's signature, so there is no out parameter to report through -- which is what libakstdlib#38 concluded. It carries a comment saying so and saying why the bypass is safe there. Six snprintf sites stay raw because they want truncation as an answer rather than an error, and aksl_snprintf cannot express that until libakstdlib#34 hands the required length back. Each of the six says so at the site. Two of them, in host.c, are a latent defect rather than a decision: a host type name over 31 characters truncates silently and two sharing a prefix then collide, where structtype.c refuses the same case. DLOAD leaked a file descriptor. Its read loop sat inside an ATTEMPT and the PASS in it returned past CLEANUP, so a scan error left the file open. Hoisting the loop into its own helper to convert fgets fixes it. Refs libakstdlib#26, libakstdlib#38 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
744 lines
26 KiB
C
744 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 <akerror.h>
|
|
#include <akstdlib.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;
|
|
PASS(errctx, aksl_strcpy(opname, sizeof(opname), operator_->lexeme));
|
|
|
|
/* 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_));
|
|
PASS(errctx, aksl_strcpy(fname, sizeof(fname), operator_->lexeme));
|
|
|
|
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);
|
|
}
|