618 lines
21 KiB
C
618 lines
21 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;
|
||
|
|
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);
|
||
|
|
|
||
|
|
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");
|
||
|
|
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 ) {
|
||
|
|
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));
|
||
|
|
if ( identifier == NULL ||
|
||
|
|
(identifier->leaftype != AKBASIC_LEAF_IDENTIFIER_INT &&
|
||
|
|
identifier->leaftype != AKBASIC_LEAF_IDENTIFIER_FLOAT &&
|
||
|
|
identifier->leaftype != AKBASIC_LEAF_IDENTIFIER_STRING) ) {
|
||
|
|
*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));
|
||
|
|
|
||
|
|
expr = arglist->right;
|
||
|
|
while ( expr != NULL && akbasic_parser_match1(obj, AKBASIC_TOK_COMMA) ) {
|
||
|
|
PASS(errctx, akbasic_parser_expression(obj, &expr->right));
|
||
|
|
expr = expr->right;
|
||
|
|
}
|
||
|
|
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);
|
||
|
|
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_ASTLeaf *left = NULL;
|
||
|
|
akbasic_ASTLeaf *right = NULL;
|
||
|
|
akbasic_ASTLeaf *expr = NULL;
|
||
|
|
akbasic_Token *operator_ = NULL;
|
||
|
|
|
||
|
|
PASS(errctx, subtraction(obj, &left));
|
||
|
|
if ( akbasic_parser_match(obj, OPS, 6) ) {
|
||
|
|
PASS(errctx, akbasic_parser_previous(obj, &operator_));
|
||
|
|
PASS(errctx, subtraction(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);
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
* subtraction and exponent return after one operator where addition,
|
||
|
|
* multiplication and division loop. That asymmetry is the reference's, not a
|
||
|
|
* transcription slip: `1 - 2 - 3` parses as `1 - (2 - 3)` there. Reproduced,
|
||
|
|
* because the golden corpus encodes the observed associativity.
|
||
|
|
*/
|
||
|
|
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));
|
||
|
|
if ( akbasic_parser_match1(obj, AKBASIC_TOK_MINUS) ) {
|
||
|
|
PASS(errctx, akbasic_parser_previous(obj, &operator_));
|
||
|
|
PASS(errctx, addition(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 *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));
|
||
|
|
if ( akbasic_parser_match1(obj, AKBASIC_TOK_CARAT) ) {
|
||
|
|
PASS(errctx, akbasic_parser_previous(obj, &operator_));
|
||
|
|
PASS(errctx, function_call(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);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Count the .right-joined chain 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->right ) {
|
||
|
|
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);
|
||
|
|
}
|
||
|
|
|
||
|
|
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_ASTLeaf *expr = NULL;
|
||
|
|
akbasic_ASTLeaf *groupexpr = NULL;
|
||
|
|
akbasic_Token *previous = NULL;
|
||
|
|
|
||
|
|
if ( akbasic_parser_match(obj, PRIMARIES, 8) ) {
|
||
|
|
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->right));
|
||
|
|
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->right));
|
||
|
|
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->right));
|
||
|
|
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");
|
||
|
|
}
|
||
|
|
*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);
|
||
|
|
}
|