GRAPHIC, COLOR, DRAW, BOX, CIRCLE, PAINT, SCALE, SSHAPE, GSHAPE and LOCATE, all against the akbasic_GraphicsBackend record rather than akgl_draw_* directly, so src/runtime_graphics.c includes no SDL and the whole group is testable in a build with no SDL on the machine. The reference lists every one of these as unimplemented, so the semantics come from Commodore BASIC 7.0 rather than from a port, and four places where a modern renderer cannot do what a C128 did are recorded in TODO.md section 5 rather than silently substituted: - CIRCLE is drawn as a polygon of inc-degree segments and akgl_draw_circle is deliberately unused. 7.0's CIRCLE takes two radii, an arc range and a rotation, so the primitive could serve only the fully-defaulted call, and a shape that changed character depending on whether the radii happened to be equal would be worse than one uniformly a polygon. - SSHAPE puts a SHAPE:<n> handle in the string variable rather than the pixels, because a value's string is a fixed 256 bytes and a region is a device surface. GSHAPE refuses a string without that prefix instead of parsing whatever digits it finds and pasting an unrelated slot. - BOX fills on a negative angle; 7.0 puts the fill flag after the rotation, which would make a filled box a seventh argument. - GRAPHIC stores its mode and honours only the one consequence that means anything here -- mode 0 is text -- while still refusing an out-of-range mode, since that is a typo worth catching. PAINT surfaces the flood fill's AKERR_OUTOFBOUNDS as an error rather than success. The device gives up when its span stack runs out having filled *part* of the region, and a program that cannot tell that happened cannot recover from it. Note the shape of that handler: HANDLE sets handled = true on the context, so a FAIL_RETURN from inside the HANDLE block hands the caller something already marked handled, whose FINISH_LOGIC then declines to pass it up and releases it -- the error disappears and PAINT reports success. Flag inside the block, raise after FINISH. COLOR, LOCATE and SCALE need no device on purpose, so a program can set itself up before a host has lent it a renderer. Adds a second golden corpus under tests/language/. The corpus in deps/basicinterpret is a submodule and nothing here may add files to it, but goal 2's new verbs still need the .bas/.txt half of their coverage. Registered under local_ so a failure names which corpus it came from. What it can cover is limited -- these verbs draw rather than print -- so the behaviour that reaches a device is asserted against tests/mockdevice.h instead. 65/65 ctest, clean under -Wall -Wextra, doxygen clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
437 lines
16 KiB
C
437 lines
16 KiB
C
/**
|
|
* @file parser_commands.c
|
|
* @brief Verbs that need their own parse path rather than a plain expression.
|
|
*
|
|
* Ported from basicparser_commands.go. Two of these mutate runtime state from
|
|
* inside the parser and that is not an accident: DEF installs the function so a
|
|
* later line can call it, and FOR builds and installs the loop's environment
|
|
* before the body is ever scanned. The waitingForCommand scheme depends on the
|
|
* latter.
|
|
*/
|
|
|
|
#include <inttypes.h>
|
|
#include <string.h>
|
|
|
|
#include <akerror.h>
|
|
|
|
#include <akbasic/error.h>
|
|
#include <akbasic/parser.h>
|
|
#include <akbasic/runtime.h>
|
|
|
|
#include "verbs.h"
|
|
|
|
akerr_ErrorContext *akbasic_parse_arglist(akbasic_Parser *parser, akbasic_ASTLeaf **dest)
|
|
{
|
|
PREPARE_ERROR(errctx);
|
|
akbasic_ASTLeaf *arglist = NULL;
|
|
akbasic_ASTLeaf *expr = NULL;
|
|
akbasic_Token *operator_ = NULL;
|
|
|
|
/*
|
|
* The generic "this verb takes a comma-separated list" path, shared by the
|
|
* graphics and sound verbs. The default command path parses a *single*
|
|
* expression, which quietly turns COLOR 0, 1 into COLOR 0 with a stray 1 --
|
|
* so every verb taking more than one argument needs this rather than NULL.
|
|
*
|
|
* The verb name comes back out of the token stream rather than being passed
|
|
* in, because a parse handler is dispatched by name and the table has no
|
|
* column to carry one.
|
|
*/
|
|
PASS(errctx, akbasic_parser_previous(parser, &operator_));
|
|
PASS(errctx, akbasic_parser_argument_list(parser, AKBASIC_TOK_FUNCTION_ARGUMENT, false, &arglist));
|
|
FAIL_ZERO_RETURN(errctx, (arglist != NULL), AKBASIC_ERR_SYNTAX,
|
|
"%s expected at least one argument", operator_->lexeme);
|
|
PASS(errctx, akbasic_parser_new_leaf(parser, &expr));
|
|
PASS(errctx, akbasic_leaf_new_command(expr, operator_->lexeme, arglist));
|
|
*dest = expr;
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
|
|
akerr_ErrorContext *akbasic_parse_draw(akbasic_Parser *parser, akbasic_ASTLeaf **dest)
|
|
{
|
|
PREPARE_ERROR(errctx);
|
|
akbasic_ASTLeaf *arglist = NULL;
|
|
akbasic_ASTLeaf *expr = NULL;
|
|
akbasic_ASTLeaf *tail = NULL;
|
|
akbasic_Token *peeked = NULL;
|
|
akbasic_Token *operator_ = NULL;
|
|
|
|
/*
|
|
* DRAW source, x1,y1 TO x2,y2 TO x3,y3 -- a polyline, with TO between pairs
|
|
* instead of a comma. TO is a reserved word the argument list will not cross,
|
|
* so the pairs after the first are appended by hand and the whole thing
|
|
* flattens to one argument chain: source, x1, y1, x2, y2, ... The exec
|
|
* handler walks it in pairs and never has to know a TO was involved.
|
|
*/
|
|
PASS(errctx, akbasic_parser_previous(parser, &operator_));
|
|
PASS(errctx, akbasic_parser_argument_list(parser, AKBASIC_TOK_FUNCTION_ARGUMENT, false, &arglist));
|
|
FAIL_ZERO_RETURN(errctx, (arglist != NULL && arglist->right != NULL), AKBASIC_ERR_SYNTAX,
|
|
"DRAW expected a color source and a coordinate");
|
|
|
|
tail = arglist->right;
|
|
while ( tail->right != NULL ) {
|
|
tail = tail->right;
|
|
}
|
|
|
|
for ( ;; ) {
|
|
peeked = akbasic_parser_peek(parser);
|
|
if ( peeked == NULL ||
|
|
peeked->tokentype != AKBASIC_TOK_COMMAND ||
|
|
strcmp(peeked->lexeme, "TO") != 0 ) {
|
|
break;
|
|
}
|
|
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND),
|
|
AKBASIC_ERR_SYNTAX, "DRAW expected TO");
|
|
PASS(errctx, akbasic_parser_expression(parser, &tail->right));
|
|
FAIL_ZERO_RETURN(errctx, (tail->right != NULL), AKBASIC_ERR_SYNTAX,
|
|
"DRAW expected X after TO");
|
|
tail = tail->right;
|
|
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_COMMA),
|
|
AKBASIC_ERR_SYNTAX, "DRAW expected TO X,Y");
|
|
PASS(errctx, akbasic_parser_expression(parser, &tail->right));
|
|
FAIL_ZERO_RETURN(errctx, (tail->right != NULL), AKBASIC_ERR_SYNTAX,
|
|
"DRAW expected Y after TO X,");
|
|
tail = tail->right;
|
|
}
|
|
|
|
PASS(errctx, akbasic_parser_new_leaf(parser, &expr));
|
|
PASS(errctx, akbasic_leaf_new_command(expr, operator_->lexeme, arglist));
|
|
*dest = expr;
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
|
|
akerr_ErrorContext *akbasic_parse_let(akbasic_Parser *parser, akbasic_ASTLeaf **dest)
|
|
{
|
|
PREPARE_ERROR(errctx);
|
|
|
|
/*
|
|
* LET is optional in this dialect and in Commodore BASIC 7.0. Assignment is
|
|
* handled by expression evaluation, so LET parses as a bare assignment and
|
|
* its exec handler does nothing.
|
|
*/
|
|
PASS(errctx, akbasic_parser_assignment(parser, dest));
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
|
|
/* LABEL and DIM share a shape: the verb, then one identifier. */
|
|
static akerr_ErrorContext *parse_verb_with_identifier(akbasic_Parser *parser, const char *verbname, akbasic_ASTLeaf **dest)
|
|
{
|
|
PREPARE_ERROR(errctx);
|
|
akbasic_ASTLeaf *identifier = NULL;
|
|
akbasic_ASTLeaf *command = NULL;
|
|
|
|
PASS(errctx, akbasic_parser_primary(parser, &identifier));
|
|
FAIL_ZERO_RETURN(errctx, akbasic_leaf_is_identifier(identifier), AKBASIC_ERR_SYNTAX,
|
|
"Expected identifier");
|
|
PASS(errctx, akbasic_parser_new_leaf(parser, &command));
|
|
PASS(errctx, akbasic_leaf_new_command(command, verbname, identifier));
|
|
*dest = command;
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
|
|
akerr_ErrorContext *akbasic_parse_label(akbasic_Parser *parser, akbasic_ASTLeaf **dest)
|
|
{
|
|
PREPARE_ERROR(errctx);
|
|
|
|
PASS(errctx, parse_verb_with_identifier(parser, "LABEL", dest));
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
|
|
akerr_ErrorContext *akbasic_parse_dim(akbasic_Parser *parser, akbasic_ASTLeaf **dest)
|
|
{
|
|
PREPARE_ERROR(errctx);
|
|
|
|
PASS(errctx, parse_verb_with_identifier(parser, "DIM", dest));
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
|
|
/*
|
|
* DEF NAME (A, ...) [= expression]
|
|
* COMMAND IDENTIFIER ARGUMENTLIST [ASSIGNMENT EXPRESSION]
|
|
*
|
|
* With an `=` the function is a single expression. Without one it is a
|
|
* multi-line subroutine whose body starts on the next line and ends at RETURN,
|
|
* so the environment is told to skip forward to that RETURN rather than execute
|
|
* the body during the definition.
|
|
*/
|
|
akerr_ErrorContext *akbasic_parse_def(akbasic_Parser *parser, akbasic_ASTLeaf **dest)
|
|
{
|
|
PREPARE_ERROR(errctx);
|
|
akbasic_Runtime *runtime = parser->runtime;
|
|
akbasic_ASTLeaf *identifier = NULL;
|
|
akbasic_ASTLeaf *arglist = NULL;
|
|
akbasic_ASTLeaf *expression = NULL;
|
|
akbasic_ASTLeaf *walk = NULL;
|
|
akbasic_ASTLeaf *command = NULL;
|
|
akbasic_FunctionDef *fndef = NULL;
|
|
size_t i = 0;
|
|
|
|
PASS(errctx, akbasic_parser_primary(parser, &identifier));
|
|
FAIL_ZERO_RETURN(errctx, (identifier->leaftype == AKBASIC_LEAF_IDENTIFIER),
|
|
AKBASIC_ERR_SYNTAX, "Expected identifier");
|
|
|
|
PASS(errctx, akbasic_parser_argument_list(parser, AKBASIC_TOK_FUNCTION_ARGUMENT, true, &arglist));
|
|
FAIL_ZERO_RETURN(errctx, (arglist != NULL), AKBASIC_ERR_SYNTAX,
|
|
"Expected argument list (identifier names)");
|
|
|
|
for ( walk = arglist->right; walk != NULL; walk = walk->right ) {
|
|
FAIL_ZERO_RETURN(errctx,
|
|
(walk->leaftype == AKBASIC_LEAF_IDENTIFIER_STRING ||
|
|
walk->leaftype == AKBASIC_LEAF_IDENTIFIER_INT ||
|
|
walk->leaftype == AKBASIC_LEAF_IDENTIFIER_FLOAT),
|
|
AKBASIC_ERR_SYNTAX,
|
|
"Only variable identifiers are valid arguments for DEF");
|
|
}
|
|
|
|
PASS(errctx, akbasic_runtime_new_function(runtime, &fndef));
|
|
|
|
/* Uppercase the name: verbs and function names are case-insensitive. */
|
|
FAIL_ZERO_RETURN(errctx, (strlen(identifier->identifier) < sizeof(fndef->name)),
|
|
AKBASIC_ERR_BOUNDS, "Function name '%s' is too long", identifier->identifier);
|
|
for ( i = 0; i < strlen(identifier->identifier); i++ ) {
|
|
char c = identifier->identifier[i];
|
|
fndef->name[i] = (char)((c >= 'a' && c <= 'z') ? (c - 'a' + 'A') : c);
|
|
}
|
|
fndef->name[strlen(identifier->identifier)] = '\0';
|
|
|
|
if ( akbasic_parser_match1(parser, AKBASIC_TOK_ASSIGNMENT) ) {
|
|
PASS(errctx, akbasic_parser_expression(parser, &expression));
|
|
PASS(errctx, akbasic_leaf_clone(expression, &fndef->leafpool, &fndef->expression));
|
|
} else {
|
|
/*
|
|
* No expression: the body is the lines that follow. Record where it
|
|
* starts and skip to RETURN so the definition itself does not execute
|
|
* the body.
|
|
*/
|
|
fndef->expression = NULL;
|
|
PASS(errctx, akbasic_environment_wait_for_command(runtime->environment, "RETURN"));
|
|
}
|
|
PASS(errctx, akbasic_leaf_clone(arglist, &fndef->leafpool, &fndef->arglist));
|
|
fndef->lineno = runtime->environment->lineno + 1;
|
|
|
|
PASS(errctx, akbasic_symtab_set(&runtime->environment->functions, fndef->name, fndef, 0));
|
|
|
|
PASS(errctx, akbasic_parser_new_leaf(parser, &command));
|
|
PASS(errctx, akbasic_leaf_new_command(command, "DEF", NULL));
|
|
*dest = command;
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
|
|
/*
|
|
* FOR ... TO .... [STEP ...]
|
|
* COMMAND ASSIGNMENT EXPRESSION [COMMAND EXPRESSION]
|
|
*
|
|
* Sets up the loop's environment with the TO and STEP expressions and the first
|
|
* body line, then makes it the active environment. The FOR leaf itself carries
|
|
* the assignment.
|
|
*/
|
|
akerr_ErrorContext *akbasic_parse_for(akbasic_Parser *parser, akbasic_ASTLeaf **dest)
|
|
{
|
|
PREPARE_ERROR(errctx);
|
|
akbasic_Runtime *runtime = parser->runtime;
|
|
akbasic_ASTLeaf *assignment = NULL;
|
|
akbasic_ASTLeaf *expr = NULL;
|
|
akbasic_Token *operator_ = NULL;
|
|
akbasic_Environment *parent = runtime->environment;
|
|
akbasic_Environment *newenv = NULL;
|
|
int64_t firstline = 0;
|
|
|
|
PASS(errctx, akbasic_parser_assignment(parser, &assignment));
|
|
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND),
|
|
AKBASIC_ERR_SYNTAX,
|
|
"Expected FOR (assignment) TO (expression) [STEP (expression)]");
|
|
PASS(errctx, akbasic_parser_previous(parser, &operator_));
|
|
FAIL_NONZERO_RETURN(errctx, strcmp(operator_->lexeme, "TO"), AKBASIC_ERR_SYNTAX,
|
|
"Expected FOR (assignment) TO (expression) [STEP (expression)]");
|
|
FAIL_ZERO_RETURN(errctx,
|
|
(assignment != NULL && akbasic_leaf_is_identifier(assignment->left)),
|
|
AKBASIC_ERR_SYNTAX,
|
|
"Expected FOR (assignment) TO (expression) [STEP (expression)]");
|
|
|
|
firstline = parent->lineno + 1;
|
|
|
|
/*
|
|
* The loop body is scanned against the *parent* environment's token stream,
|
|
* so the new environment cannot become active until parsing is done. Parse
|
|
* TO and STEP first, then switch.
|
|
*/
|
|
PASS(errctx, akbasic_runtime_new_environment(runtime));
|
|
newenv = runtime->environment;
|
|
runtime->environment = parent;
|
|
|
|
PASS(errctx, akbasic_parser_expression(parser, &newenv->forToLeaf));
|
|
|
|
if ( akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND) ) {
|
|
PASS(errctx, akbasic_parser_previous(parser, &operator_));
|
|
FAIL_NONZERO_RETURN(errctx, strcmp(operator_->lexeme, "STEP"), AKBASIC_ERR_SYNTAX,
|
|
"Expected FOR (assignment) TO (expression) [STEP (expression)]");
|
|
PASS(errctx, akbasic_parser_expression(parser, &newenv->forStepLeaf));
|
|
} else {
|
|
/*
|
|
* Dartmouth BASIC says not to infer a negative step: it is either given
|
|
* explicitly or assumed to be +1.
|
|
*/
|
|
PASS(errctx, akbasic_parser_new_leaf(parser, &newenv->forStepLeaf));
|
|
PASS(errctx, akbasic_leaf_new_literal_int(newenv->forStepLeaf, "1"));
|
|
}
|
|
|
|
/* A NEXT already being awaited means this is an inner loop over the same variable. */
|
|
if ( strcmp(parent->waitingForCommand, "NEXT") == 0 ) {
|
|
newenv->forNextVariable = parent->forNextVariable;
|
|
}
|
|
newenv->loopFirstLine = firstline;
|
|
|
|
PASS(errctx, akbasic_parser_new_leaf(parser, &expr));
|
|
PASS(errctx, akbasic_leaf_new_command(expr, "FOR", assignment));
|
|
|
|
runtime->environment = newenv;
|
|
*dest = expr;
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
|
|
/*
|
|
* READ VARNAME [, ...]
|
|
* COMMAND ARGUMENTLIST
|
|
*
|
|
* The identifier leaves are deep-copied into the environment, because the DATA
|
|
* line that fills them is parsed later and will have overwritten the per-line
|
|
* leaf pool by then.
|
|
*/
|
|
akerr_ErrorContext *akbasic_parse_read(akbasic_Parser *parser, akbasic_ASTLeaf **dest)
|
|
{
|
|
PREPARE_ERROR(errctx);
|
|
akbasic_Environment *env = parser->runtime->environment;
|
|
akbasic_ASTLeaf *arglist = NULL;
|
|
akbasic_ASTLeaf *expr = NULL;
|
|
akbasic_ASTLeaf *command = NULL;
|
|
int i = 0;
|
|
|
|
PASS(errctx, akbasic_parser_argument_list(parser, AKBASIC_TOK_FUNCTION_ARGUMENT, false, &arglist));
|
|
FAIL_ZERO_RETURN(errctx, (arglist != NULL && arglist->right != NULL), AKBASIC_ERR_SYNTAX,
|
|
"Expected identifier");
|
|
|
|
env->readLeafPool.next = 0;
|
|
expr = arglist->right;
|
|
for ( i = 0; i < AKBASIC_MAX_LEAVES; i++ ) {
|
|
if ( expr == NULL ) {
|
|
env->readIdentifierLeaves[i] = NULL;
|
|
continue;
|
|
}
|
|
FAIL_ZERO_RETURN(errctx, akbasic_leaf_is_identifier(expr), AKBASIC_ERR_SYNTAX,
|
|
"Expected identifier");
|
|
PASS(errctx, akbasic_leaf_clone(expr, &env->readLeafPool, &env->readIdentifierLeaves[i]));
|
|
/*
|
|
* A cloned identifier keeps its .right chain, which for READ is the
|
|
* *next* identifier, not a subscript. Sever it so evaluating this leaf
|
|
* cannot walk into its sibling.
|
|
*/
|
|
env->readIdentifierLeaves[i]->right = NULL;
|
|
expr = expr->right;
|
|
}
|
|
env->readReturnLine = env->lineno + 1;
|
|
|
|
PASS(errctx, akbasic_parser_new_leaf(parser, &command));
|
|
PASS(errctx, akbasic_leaf_new_command(command, "READ", arglist));
|
|
*dest = command;
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
|
|
/*
|
|
* DATA LITERAL [, ...]
|
|
* COMMAND ARGUMENTLIST
|
|
*/
|
|
akerr_ErrorContext *akbasic_parse_data(akbasic_Parser *parser, akbasic_ASTLeaf **dest)
|
|
{
|
|
PREPARE_ERROR(errctx);
|
|
akbasic_ASTLeaf *arglist = NULL;
|
|
akbasic_ASTLeaf *expr = NULL;
|
|
akbasic_ASTLeaf *command = NULL;
|
|
|
|
PASS(errctx, akbasic_parser_argument_list(parser, AKBASIC_TOK_FUNCTION_ARGUMENT, false, &arglist));
|
|
FAIL_ZERO_RETURN(errctx, (arglist != NULL && arglist->right != NULL), AKBASIC_ERR_SYNTAX,
|
|
"Expected literal");
|
|
for ( expr = arglist->right; expr != NULL; expr = expr->right ) {
|
|
FAIL_ZERO_RETURN(errctx, akbasic_leaf_is_literal(expr), AKBASIC_ERR_SYNTAX,
|
|
"Expected literal");
|
|
}
|
|
PASS(errctx, akbasic_parser_new_leaf(parser, &command));
|
|
PASS(errctx, akbasic_leaf_new_command(command, "DATA", arglist));
|
|
*dest = command;
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
|
|
akerr_ErrorContext *akbasic_parse_poke(akbasic_Parser *parser, akbasic_ASTLeaf **dest)
|
|
{
|
|
PREPARE_ERROR(errctx);
|
|
akbasic_ASTLeaf *arglist = NULL;
|
|
akbasic_ASTLeaf *expr = NULL;
|
|
|
|
PASS(errctx, akbasic_parser_argument_list(parser, AKBASIC_TOK_FUNCTION_ARGUMENT, false, &arglist));
|
|
FAIL_ZERO_RETURN(errctx, (arglist != NULL), AKBASIC_ERR_SYNTAX,
|
|
"POKE expected INTEGER, INTEGER");
|
|
PASS(errctx, akbasic_parser_new_leaf(parser, &expr));
|
|
PASS(errctx, akbasic_leaf_new_command(expr, "POKE", arglist));
|
|
*dest = expr;
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
|
|
/*
|
|
* IF relation THEN command [ELSE command]
|
|
* becomes BRANCH(expr=relation, left=then_command, right=else_command).
|
|
*/
|
|
akerr_ErrorContext *akbasic_parse_if(akbasic_Parser *parser, akbasic_ASTLeaf **dest)
|
|
{
|
|
PREPARE_ERROR(errctx);
|
|
akbasic_ASTLeaf *relation = NULL;
|
|
akbasic_ASTLeaf *then_command = NULL;
|
|
akbasic_ASTLeaf *else_command = NULL;
|
|
akbasic_ASTLeaf *branch = NULL;
|
|
akbasic_Token *operator_ = NULL;
|
|
|
|
PASS(errctx, akbasic_parser_relation(parser, &relation));
|
|
FAIL_ZERO_RETURN(errctx, akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND),
|
|
AKBASIC_ERR_SYNTAX, "Incomplete IF statement");
|
|
PASS(errctx, akbasic_parser_previous(parser, &operator_));
|
|
FAIL_NONZERO_RETURN(errctx, strcmp(operator_->lexeme, "THEN"), AKBASIC_ERR_SYNTAX,
|
|
"Expected IF ... THEN");
|
|
|
|
PASS(errctx, akbasic_parser_command(parser, &then_command));
|
|
|
|
if ( akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND) ) {
|
|
PASS(errctx, akbasic_parser_previous(parser, &operator_));
|
|
FAIL_NONZERO_RETURN(errctx, strcmp(operator_->lexeme, "ELSE"), AKBASIC_ERR_SYNTAX,
|
|
"Expected IF ... THEN ... ELSE ...");
|
|
PASS(errctx, akbasic_parser_command(parser, &else_command));
|
|
}
|
|
|
|
PASS(errctx, akbasic_parser_new_leaf(parser, &branch));
|
|
PASS(errctx, akbasic_leaf_new_branch(branch, relation, then_command, else_command));
|
|
*dest = branch;
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
|
|
/*
|
|
* INPUT "PROMPT" VARIABLE
|
|
* COMMAND EXPRESSION IDENTIFIER
|
|
*
|
|
* The prompt is hung off the identifier's .left, which is where the exec handler
|
|
* looks for it.
|
|
*/
|
|
akerr_ErrorContext *akbasic_parse_input(akbasic_Parser *parser, akbasic_ASTLeaf **dest)
|
|
{
|
|
PREPARE_ERROR(errctx);
|
|
akbasic_ASTLeaf *promptexpr = NULL;
|
|
akbasic_ASTLeaf *identifier = NULL;
|
|
akbasic_ASTLeaf *command = NULL;
|
|
|
|
PASS(errctx, akbasic_parser_expression(parser, &promptexpr));
|
|
PASS(errctx, akbasic_parser_primary(parser, &identifier));
|
|
FAIL_ZERO_RETURN(errctx, akbasic_leaf_is_identifier(identifier), AKBASIC_ERR_SYNTAX,
|
|
"Expected identifier");
|
|
PASS(errctx, akbasic_parser_new_leaf(parser, &command));
|
|
PASS(errctx, akbasic_leaf_new_command(command, "INPUT", identifier));
|
|
identifier->left = promptexpr;
|
|
*dest = command;
|
|
SUCCEED_RETURN(errctx);
|
|
}
|