// The MIT License (MIT) // // Copyright (C) 2026 Scott Duensing // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // parseStmt.c -- DVX BASIC parser: statements // // basParseStatement reads one statement (a block statement with its // body). Statements that start with a name (assignments, calls, member // statements) are read as a postfix chain plus either "= value" or the // call's arguments; what the chain names is decided by the resolver. #include "front.h" #include #include // Words the parser reads by their text (identifiers, not reserved). #define BAS_KW_ATTRIBUTE "ATTRIBUTE" #define BAS_KW_EACH "EACH" #define BAS_KW_IN "IN" #define BAS_KW_RAISEEVENT "RAISEEVENT" #define BAS_KW_RESET "RESET" #define BAS_KW_USING "USING" #define BAS_KW_SPC "SPC" #define BAS_KW_TAB "TAB" #define BAS_KW_LEN "LEN" #define BAS_KW_OFF "OFF" #define BAS_KW_STOP "STOP" #define BAS_KW_B "B" #define BAS_KW_BF "BF" #define BAS_KW_MID "MID$" #define BAS_KW_LSET "LSET" #define BAS_KW_RSET "RSET" // SLEEP with no argument waits this many seconds. #define BAS_SLEEP_DEFAULT_SECS 1 // Arguments of BEEP, SOUND hz, ticks, PLAY macro$ and FRAMEWAIT hz. #define BAS_BEEP_ARGS 0 #define BAS_SOUND_ARGS 2 #define BAS_PLAY_ARGS 1 #define BAS_FRAMEWAIT_ARGS 1 // CLS with no argument, and FULLSCREEN with none / ON / OFF. #define BAS_CLS_OMITTED (-1) #define BAS_FULLSCREEN_ON 1 #define BAS_FULLSCREEN_OFF 0 // Arguments LOCATE, COLOR, WIDTH and SCREEN take before their given mask // (SCREEN: after the mode), and the extra ones QBasic accepts that DVX // ignores (LOCATE's cursor start and stop lines, COLOR's border). #define BAS_LOCATE_VALUES 3 #define BAS_PAIR_VALUES 2 #define BAS_SCREEN_VALUES 3 #define BAS_LOCATE_IGNORED 2 #define BAS_COLOR_IGNORED 1 // Arguments SETEVENT and INIWRITE take (REMOVECONTROL, FILECOPY, NAME, // SETATTR and SEEK take BAS_PAIR_ARGS). #define BAS_SETEVENT_ARGS 3 #define BAS_INIWRITE_ARGS 4 // Console and graphics statements: identifiers, not reserved words; a // procedure of the same name, or the name followed by '=', '.' or // "(...) =", is the program's own. typedef struct { const char *name; uint8_t func; // BasBuiltinFnE int32_t flags; // extra BAS_GFX_* flags (PRESET) } ScreenStmtT; static const ScreenStmtT sScreenStmts[] = { { "CIRCLE", BAS_FN_CIRCLE, 0 }, { "CLS", BAS_FN_CLS, 0 }, { "COLOR", BAS_FN_COLOR, 0 }, { "DRAW", BAS_FN_DRAW, 0 }, { "FULLSCREEN", BAS_FN_FULLSCREEN, 0 }, { "LOCATE", BAS_FN_LOCATE, 0 }, { "PAINT", BAS_FN_PAINT, 0 }, { "PALETTE", BAS_FN_PALETTE, 0 }, { "PCOPY", BAS_FN_PCOPY, 0 }, { "PEN", BAS_FN_PEN_SET, 0 }, { "PRESET", BAS_FN_PSET, BAS_GFX_PRESET }, { "PSET", BAS_FN_PSET, 0 }, { "SCREEN", BAS_FN_SCREEN, 0 }, { "VIEW", BAS_FN_VIEW, 0 }, { "WIDTH", BAS_FN_WIDTH, 0 }, { "WINDOW", BAS_FN_WINDOW, 0 } }; // Argument list being built (head / tail / count). typedef struct { BasNodeT *head; BasNodeT *tail; int32_t count; } ArgListT; // Function prototypes (alphabetical) static void argAdd(ArgListT *l, BasNodeT *n); static void argZero(BasCompilerT *c, ArgListT *l); BasNodeT *basParseDeferredArgs(BasCompilerT *c, BasStmtT *call, BasDeferModeE mode, int32_t *outArgc); BasNodeT *basParseLValue(BasCompilerT *c, bool allowFields); BasNodeT *basParseStatement(BasCompilerT *c); static bool classLevelStatement(BasCompilerT *c); static bool drawingMethod(const char *name); static const ScreenStmtT *findScreenStmt(BasCompilerT *c); static char *labelRef(BasCompilerT *c); static char *lineLabel(BasCompilerT *c, int32_t *outNumber); static BasStmtT *newStmt(BasCompilerT *c, uint8_t kind); static bool ownResultKeywordAhead(BasCompilerT *c); static BasNodeT *parseBlockBody(BasCompilerT *c, BasTokenTypeE endKw, BasTokenTypeE stopTok, bool *outEnded); static BasNodeT *parseBranch(BasCompilerT *c); static BasNodeT *parseCallKw(BasCompilerT *c); static BasNodeT *parseClose(BasCompilerT *c); static BasNodeT *parseData(BasCompilerT *c); static BasNodeT *parseDataItem(BasCompilerT *c, bool *outOk); static BasNodeT *parseDo(BasCompilerT *c); static BasNodeT *parseDrawingArgs(BasCompilerT *c, const char *method, int32_t *outArgc); static BasNodeT *parseErrStatement(BasCompilerT *c); static BasNodeT *parseExit(BasCompilerT *c); static void parseFlaggedArgs(BasCompilerT *c, ArgListT *l, const int32_t *slots, int32_t count, int32_t *flags); static BasNodeT *parseFor(BasCompilerT *c); static BasNodeT *parseForEach(BasCompilerT *c, int32_t line, int32_t col); static BasNodeT *parseGet(BasCompilerT *c, uint8_t kind); static void parseGfxCircleArgs(BasCompilerT *c, ArgListT *l); static void parseGfxLineArgs(BasCompilerT *c, ArgListT *l); static void parseGfxPoint(BasCompilerT *c, ArgListT *l, int32_t *flags, int32_t stepFlag); static void parseGfxPsetArgs(BasCompilerT *c, ArgListT *l, int32_t flags); static int32_t parseGfxRect(BasCompilerT *c, ArgListT *l); static BasNodeT *parseIdentStatement(BasCompilerT *c, bool isSet); static BasNodeT *parseIf(BasCompilerT *c); static void parseIgnoredArgs(BasCompilerT *c, ArgListT *l, int32_t max); static BasNodeT *parseInput(BasCompilerT *c); static BasNodeT *parseLabelName(BasCompilerT *c, uint8_t kind); static BasNodeT *parseLine(BasCompilerT *c); static BasNodeT *parseLineInput(BasCompilerT *c); static BasNodeT *parseLset(BasCompilerT *c); static BasNodeT *parseMemberCall(BasCompilerT *c, BasMemberT *m, bool isSet); static BasNodeT *parseMeStatement(BasCompilerT *c, bool isSet); static BasNodeT *parseMidAssign(BasCompilerT *c); static BasNodeT *parseNumLit(BasCompilerT *c, bool isNeg); static BasNodeT *parseObjectPrintItems(BasCompilerT *c, int32_t *outCount); static BasNodeT *parseOn(BasCompilerT *c); static BasNodeT *parseOpen(BasCompilerT *c); static int32_t parseOptionalArgs(BasCompilerT *c, ArgListT *l, int32_t count); static BasNodeT *parsePrint(BasCompilerT *c, bool isLprint); static void parsePrintItems(BasCompilerT *c, BasPrintT *pr); static BasNodeT *parseRaiseEvent(BasCompilerT *c); static BasNodeT *parseRead(BasCompilerT *c); static BasNodeT *parseScreenStatement(BasCompilerT *c, const ScreenStmtT *stmt); static BasNodeT *parseSelectCase(BasCompilerT *c); static BasNodeT *parseSimple(BasCompilerT *c, uint8_t opcode, int32_t argc, bool hashFirst); static BasNodeT *parseSwap(BasCompilerT *c); static BasNodeT *parseWhile(BasCompilerT *c); static BasNodeT *parseWith(BasCompilerT *c); static BasNodeT *parseWrite(BasCompilerT *c); static void printItem(BasCompilerT *c, BasNodeT **head, BasNodeT **tail, uint8_t kind, BasNodeT *expr); static bool procNamed(BasCompilerT *c, const char *name); static bool resetAhead(BasCompilerT *c); static BasNodeT *stmtExpr(BasCompilerT *c, BasNodeT *expr); static void argAdd(ArgListT *l, BasNodeT *n) { if (n == NULL) { return; } if (l->tail != NULL) { l->tail->next = n; } else { l->head = n; } l->tail = n; l->count++; } // An argument the source left out that the statement passes as 0. static void argZero(BasCompilerT *c, ArgListT *l) { argAdd(l, basParseIntLit(c, 0)); } // Reads the arguments of the drawing method statement call (obj.Line ... // whose object is a plain name) now that the resolver knows how: as a // class method's comma-separated arguments (BAS_DEFER_ARGS) or in the // method's QBasic syntax. The source is lexed again from where the // arguments start; the lexer is put back afterwards. BasNodeT *basParseDeferredArgs(BasCompilerT *c, BasStmtT *call, BasDeferModeE mode, int32_t *outArgc) { const BasLitT *at = (const BasLitT *)call->c; BasLexerT saved = c->lex; int32_t prevLine = c->prevLine; BasNodeT *args; c->prevLine = at->h.line; basLexerSeek(&c->lex, at->ival, at->h.line, at->h.col); if (mode == BAS_DEFER_ARGS) { args = basParseBareArgs(c, outArgc); } else { args = parseDrawingArgs(c, ((BasMemberT *)call->a)->name, outArgc); } if (!c->hasError && !basParseAtEnd(c)) { basParseErrorExpected(c, "end of statement"); } if (!c->hasError) { c->lex = saved; c->prevLine = prevLine; } return args; } // A store target: name, optional subscripts and (allowFields) a .field // chain. The resolver checks what it names. BasNodeT *basParseLValue(BasCompilerT *c, bool allowFields) { if (!basParseCheck(c, TOK_IDENT)) { basParseErrorExpected(c, "variable name"); return NULL; } BasNameT *n = (BasNameT *)basParseNode(c, (int32_t)sizeof(BasNameT), NK_NAME); if (n == NULL) { return NULL; } n->name = basParseTokenName(c); basParseAdvance(c); BasNodeT *node = &n->h; if (basParseCheck(c, TOK_LPAREN)) { BasCallT *call = (BasCallT *)basParseNode(c, (int32_t)sizeof(BasCallT), NK_CALL); if (call == NULL) { return NULL; } int32_t argc = 0; call->callee = node; call->h.line = node->line; call->h.col = node->col; call->args = basParseArgList(c, &argc); call->argc = (int16_t)argc; node = &call->h; } int32_t depth = 0; while (allowFields && !c->hasError && basParseCheck(c, TOK_DOT)) { if (++depth > BAS_MAX_FIELD_CHAIN) { basParseError(c, "TYPE field chain too deep"); return NULL; } basParseAdvance(c); if (!basParseCheck(c, TOK_IDENT)) { basParseErrorExpected(c, "field name"); return NULL; } BasMemberT *m = (BasMemberT *)basParseNode(c, (int32_t)sizeof(BasMemberT), NK_MEMBER); if (m == NULL) { return NULL; } node->flags |= NF_MEMBERBASE; m->base = node; m->name = basParseTokenName(c); basParseAdvance(c); node = &m->h; } return node; } BasNodeT *basParseStatement(BasCompilerT *c) { if (c->hasError) { return NULL; } basParseSkipNewlines(c); if (basParseCheck(c, TOK_EOF)) { return NULL; } // Every nested block recurses through here; bound the depth so a // pathological input is an error, not a stack overflow. if (c->blockDepth >= BAS_MAX_BLOCK_DEPTH) { basParseError(c, "Statements nested too deeply"); return NULL; } // A class module holds declarations and procedures only. if (c->parseInClass && !c->parseInProc && !classLevelStatement(c)) { basParseError(c, "Only declarations are allowed outside procedures in a class module"); return NULL; } c->blockDepth++; int32_t line = c->lex.token.line; int32_t col = c->lex.token.col; bool endOfStatement = true; BasNodeT *n = NULL; BasTokenTypeE tt = ownResultKeywordAhead(c) ? TOK_IDENT : c->lex.token.type; switch (tt) { case TOK_PRINT: n = parsePrint(c, false); break; case TOK_LPRINT: n = parsePrint(c, true); break; case TOK_DATA: n = parseData(c); break; case TOK_READ: n = parseRead(c); break; case TOK_RESTORE: { // RESTORE [label]: the DATA from that label on basParseAdvance(c); BasStmtT *s = newStmt(c, ST_RESTORE); if (s != NULL && !basParseAtEnd(c)) { s->name = labelRef(c); } n = &s->h; break; } case TOK_DIM: case TOK_STATIC: case TOK_SHARED: case TOK_DEF: case TOK_DEFINT: case TOK_DEFLNG: case TOK_DEFSNG: case TOK_DEFDBL: case TOK_DEFSTR: case TOK_DEFBYTE: case TOK_DEFCUR: case TOK_DECLARE: case TOK_SUB: case TOK_FUNCTION: case TOK_PROPERTY: case TOK_PUBLIC: case TOK_PRIVATE: case TOK_CONST: case TOK_TYPE: case TOK_ENUM: case TOK_REDIM: case TOK_ERASE: case TOK_OPTION: n = basParseDeclStatement(c); break; case TOK_IF: n = parseIf(c); break; case TOK_FOR: n = parseFor(c); break; case TOK_DO: n = parseDo(c); break; case TOK_WHILE: n = parseWhile(c); break; case TOK_SELECT: n = parseSelectCase(c); break; case TOK_EXIT: n = parseExit(c); break; case TOK_END: basParseAdvance(c); n = (BasNodeT *)newStmt(c, ST_END); break; case TOK_ERR: n = parseErrStatement(c); break; case TOK_ERROR_KW: // ERROR n: raise a runtime error n = parseSimple(c, OP_RAISE_ERR, 1, false); break; case TOK_FILECOPY: n = parseSimple(c, OP_FS_FILECOPY, BAS_PAIR_ARGS, false); break; case TOK_INPUT: n = parseInput(c); break; case TOK_KILL: n = parseSimple(c, OP_FS_KILL, 1, false); break; case TOK_MKDIR: n = parseSimple(c, OP_FS_MKDIR, 1, false); break; case TOK_NAME: { // NAME old AS new ArgListT args; memset(&args, 0, sizeof(args)); basParseAdvance(c); argAdd(&args, basParseExpr(c)); basParseExpect(c, TOK_AS); argAdd(&args, basParseExpr(c)); BasStmtT *s = newStmt(c, ST_SIMPLE); if (s != NULL) { s->ival = OP_FS_NAME; s->b = args.head; s->ival2 = args.count; } n = &s->h; break; } case TOK_OPEN: n = parseOpen(c); break; case TOK_CHDIR: n = parseSimple(c, OP_FS_CHDIR, 1, false); break; case TOK_CHDRIVE: n = parseSimple(c, OP_FS_CHDRIVE, 1, false); break; case TOK_CLOSE: n = parseClose(c); break; case TOK_GET: n = parseGet(c, ST_GET); break; case TOK_PUT: n = parseGet(c, ST_PUT); break; case TOK_SEEK: n = parseSimple(c, OP_FILE_SEEK, BAS_PAIR_ARGS, true); break; case TOK_WRITE: n = parseWrite(c); break; case TOK_LINE: n = parseLine(c); break; case TOK_GOTO: n = parseLabelName(c, ST_GOTO); break; case TOK_GOSUB: n = parseLabelName(c, ST_GOSUB); break; case TOK_ON: n = parseOn(c); break; case TOK_SHELL: { // SHELL [command]: the result is dropped ArgListT args; memset(&args, 0, sizeof(args)); basParseAdvance(c); argAdd(&args, basParseAtEnd(c) ? basParseStrLit(c, "", 0) : basParseExpr(c)); n = stmtExpr(c, basParseIntrinsic(c, OP_SHELL, args.head, args.count, BAS_NO_OPERAND)); break; } case TOK_RESUME: { basParseAdvance(c); BasStmtT *s = newStmt(c, ST_RESUME); // RESUME, RESUME 0 (QBasic: the same), RESUME NEXT, RESUME label if (s != NULL) { s->ival = basParseMatch(c, TOK_NEXT) ? 1 : 0; if (s->ival == 0 && basParseCheck(c, TOK_INT_LIT) && c->lex.token.intVal == 0) { basParseAdvance(c); } else if (s->ival == 0 && !basParseAtEnd(c)) { s->name = labelRef(c); } } n = &s->h; break; } case TOK_RMDIR: n = parseSimple(c, OP_FS_RMDIR, 1, false); break; case TOK_SETATTR: n = parseSimple(c, OP_FS_SETATTR, BAS_PAIR_ARGS, false); break; case TOK_RETURN: basParseAdvance(c); n = (BasNodeT *)newStmt(c, ST_RETURN); break; case TOK_SLEEP: { // SLEEP [seconds] ArgListT args; memset(&args, 0, sizeof(args)); basParseAdvance(c); argAdd(&args, basParseAtEnd(c) ? basParseIntLit(c, BAS_SLEEP_DEFAULT_SECS) : basParseExpr(c)); BasStmtT *s = newStmt(c, ST_SIMPLE); if (s != NULL) { s->ival = OP_SLEEP; s->b = args.head; s->ival2 = args.count; } n = &s->h; break; } case TOK_BEEP: n = parseSimple(c, OP_BEEP, BAS_BEEP_ARGS, false); break; case TOK_SOUND: n = parseSimple(c, OP_SOUND, BAS_SOUND_ARGS, false); break; case TOK_PLAY: n = parseSimple(c, OP_PLAY, BAS_PLAY_ARGS, false); break; case TOK_FRAMEWAIT: n = parseSimple(c, OP_FRAME_WAIT, BAS_FRAMEWAIT_ARGS, false); break; case TOK_SWAP: n = parseSwap(c); break; case TOK_CALL: n = parseCallKw(c); break; case TOK_RANDOMIZE: { // RANDOMIZE TIMER | seed ArgListT args; memset(&args, 0, sizeof(args)); basParseAdvance(c); if (basParseMatch(c, TOK_TIMER)) { argAdd(&args, basParseIntLit(c, BAS_RANDOMIZE_TIMER_SEED)); } else { argAdd(&args, basParseExpr(c)); } BasStmtT *s = newStmt(c, ST_SIMPLE); if (s != NULL) { s->ival = OP_MATH_RANDOMIZE; s->b = args.head; s->ival2 = args.count; } n = &s->h; break; } case TOK_DOEVENTS: n = parseSimple(c, OP_DO_EVENTS, 0, false); break; case TOK_CREATEFORM: case TOK_CREATECONTROL: // used as a statement: the new object is dropped n = stmtExpr(c, basParseExpr(c)); break; case TOK_SETEVENT: n = parseSimple(c, OP_SET_EVENT, BAS_SETEVENT_ARGS, false); break; case TOK_REMOVECONTROL: n = parseSimple(c, OP_REMOVE_CTRL, BAS_PAIR_ARGS, false); break; case TOK_SET: // SET target = object basParseAdvance(c); if (basParseCheck(c, TOK_ME) || (basParseCheck(c, TOK_DOT) && c->parseWith != NULL)) { n = parseMeStatement(c, true); } else if (basParseCheck(c, TOK_IDENT) || ownResultKeywordAhead(c)) { n = parseIdentStatement(c, true); } else { basParseErrorExpected(c, "variable name"); } break; case TOK_LOAD: { // Load FormName (an identifier, not a string) basParseAdvance(c); if (!basParseCheck(c, TOK_IDENT)) { basParseErrorExpected(c, "form name"); break; } BasStmtT *s = newStmt(c, ST_LOAD); if (s != NULL) { s->name = basParseTokenName(c); } basParseAdvance(c); n = &s->h; break; } case TOK_UNLOAD: { // Unload Me / Unload FormName basParseAdvance(c); BasStmtT *s = newStmt(c, ST_UNLOAD); if (s == NULL) { break; } if (!basParseMatch(c, TOK_ME)) { if (!basParseCheck(c, TOK_IDENT)) { basParseErrorExpected(c, "form name"); break; } s->name = basParseTokenName(c); basParseAdvance(c); } n = &s->h; break; } case TOK_INPUTBOX: { // InputBox$ prompt [, title [, default]] as a statement int32_t argc = 0; basParseAdvance(c); BasNodeT *args = basParseInputBoxArgs(c, &argc); n = stmtExpr(c, basParseIntrinsic(c, OP_INPUTBOX, args, argc, BAS_NO_OPERAND)); break; } case TOK_MSGBOX: { // MsgBox message [, flags [, title]] as a statement int32_t argc = 0; basParseAdvance(c); BasNodeT *args = basParseMsgBoxArgs(c, &argc); n = stmtExpr(c, basParseIntrinsic(c, OP_MSGBOX, args, argc, BAS_NO_OPERAND)); break; } case TOK_INIWRITE: n = parseSimple(c, OP_INI_WRITE, BAS_INIWRITE_ARGS, false); break; case TOK_ME: n = parseMeStatement(c, false); break; case TOK_WITH: n = parseWith(c); break; case TOK_DOT: // .member ... inside WITH if (c->parseWith == NULL) { basParseError(c, "Invalid or unqualified reference: '.' outside WITH"); break; } n = parseMeStatement(c, false); break; case TOK_LET: basParseAdvance(c); if (!basParseCheck(c, TOK_IDENT)) { basParseErrorExpected(c, "variable name after LET"); break; } n = parseIdentStatement(c, false); break; case TOK_INT_LIT: case TOK_LONG_LIT: { // A QBasic line number: a label named by its number, which ERL // reports; the rest of the line is its statement BasStmtT *s = newStmt(c, ST_LABEL); if (s != NULL) { s->name = lineLabel(c, &s->ival2); s->ival = BAS_LABEL_LINE_NUMBER; } endOfStatement = false; n = &s->h; break; } case TOK_IDENT: { // The project builder's form and class markers, a class // module's Attribute / Event / Implements lines, RaiseEvent if (basParseCheckWord(c, BAS_WRAP_BEGINFORM) || basParseCheckWord(c, BAS_WRAP_BEGINCLASS) || basParseCheckWord(c, BAS_WRAP_BEGINMODULE)) { bool isClass = basParseCheckWord(c, BAS_WRAP_BEGINCLASS); bool isModule = basParseCheckWord(c, BAS_WRAP_BEGINMODULE); basParseAdvance(c); if (!basParseCheck(c, TOK_STRING_LIT)) { basParseErrorExpected(c, isClass ? "class name string" : (isModule ? "module name string" : "form name string")); break; } BasStmtT *s = newStmt(c, isClass ? ST_BEGINCLASS : (isModule ? ST_BEGINMODULE : ST_BEGINFORM)); if (s != NULL) { s->name = basParseTokenName(c); } basParseAdvance(c); // A form's controls follow its name BasNodeT *tail = NULL; while (s != NULL && !isClass && !isModule && basParseCheck(c, TOK_STRING_LIT)) { BasNodeT *ctrl = basParseStrLit(c, c->lex.token.text, c->lex.token.textLen); if (tail != NULL) { tail->next = ctrl; } else { s->a = ctrl; } tail = ctrl; basParseAdvance(c); } if (isClass) { c->parseInClass = true; c->parseClassName = (s != NULL) ? basArenaStrDup(&c->modArena, s->name, (int32_t)strlen(s->name)) : NULL; } n = &s->h; break; } if (basParseCheckWord(c, BAS_WRAP_ENDFORM) || basParseCheckWord(c, BAS_WRAP_ENDCLASS) || basParseCheckWord(c, BAS_WRAP_ENDMODULE)) { bool isClass = basParseCheckWord(c, BAS_WRAP_ENDCLASS); bool isModule = basParseCheckWord(c, BAS_WRAP_ENDMODULE); basParseAdvance(c); n = (BasNodeT *)newStmt(c, isClass ? ST_ENDCLASS : (isModule ? ST_ENDMODULE : ST_ENDFORM)); if (isClass && !c->parseInProc) { c->parseInClass = false; } break; } if (c->parseInClass && basParseCheckWord(c, BAS_KW_ATTRIBUTE)) { n = basParseAttribute(c); break; } if (basParseFriendAhead(c)) { n = basParseFriendProc(c); break; } if (c->parseInClass && !c->parseInProc && basParseCheckWord(c, BAS_KW_EVENT)) { n = basParseEvent(c); break; } if (c->parseInClass && !c->parseInProc && basParseCheckWord(c, BAS_KW_IMPLEMENTS)) { basParseAdvance(c); if (!basParseCheck(c, TOK_IDENT)) { basParseErrorExpected(c, "the name of a class module after Implements"); break; } BasStmtT *s = newStmt(c, ST_IMPLEMENTS); if (s != NULL) { s->name = basParseTokenName(c); } basParseAdvance(c); n = &s->h; break; } if (basParseCheckWord(c, BAS_KW_RAISEEVENT) && basParseNameFollows(c, true)) { n = parseRaiseEvent(c); break; } // RESET: CLOSE every channel (QBasic), unless the program has a // procedure of that name if (basParseCheckWord(c, BAS_KW_RESET) && !procNamed(c, BAS_KW_RESET) && resetAhead(c)) { basParseAdvance(c); BasStmtT *s = newStmt(c, ST_SCREEN); if (s != NULL) { s->ival = BAS_FN_CLOSE; } n = &s->h; break; } // CLS, LOCATE, PSET ... (before the label test: CLS: PRINT) const ScreenStmtT *screenStmt = findScreenStmt(c); if (screenStmt != NULL) { n = parseScreenStatement(c, screenStmt); break; } // A label: an identifier followed by a colon BasLexerT peek = c->lex; basLexerNext(&peek); if (peek.token.type == TOK_COLON) { BasStmtT *s = newStmt(c, ST_LABEL); if (s != NULL) { s->name = basParseTokenName(c); } basParseAdvance(c); basParseAdvance(c); endOfStatement = false; n = &s->h; break; } n = parseIdentStatement(c, false); break; } default: basParseError(c, "Unexpected token: %s", basTokenName(tt)); break; } c->blockDepth--; if (n != NULL) { n->line = line; n->col = (uint16_t)col; } if (endOfStatement && !c->hasError) { basParseExpectEnd(c); } return n; } // True for a statement a class module may hold outside its procedures: // declarations, procedures (Friend ones too), OPTION, DEFxxx, ENDCLASS and // Attribute, Event and Implements lines. static bool classLevelStatement(BasCompilerT *c) { switch (c->lex.token.type) { case TOK_DIM: case TOK_PUBLIC: case TOK_PRIVATE: case TOK_CONST: case TOK_SUB: case TOK_FUNCTION: case TOK_PROPERTY: case TOK_TYPE: case TOK_ENUM: case TOK_DECLARE: case TOK_OPTION: case TOK_DEFINT: case TOK_DEFLNG: case TOK_DEFSNG: case TOK_DEFDBL: case TOK_DEFSTR: case TOK_DEFBYTE: case TOK_DEFCUR: case TOK_REM: return true; case TOK_IDENT: return basParseCheckWord(c, BAS_WRAP_ENDCLASS) || basParseCheckWord(c, BAS_KW_ATTRIBUTE) || basParseCheckWord(c, BAS_KW_EVENT) || basParseCheckWord(c, BAS_KW_IMPLEMENTS) || basParseFriendAhead(c); default: return false; } } // True for a Form / PictureBox method read with QBasic statement syntax. static bool drawingMethod(const char *name) { return strcasecmp(name, BAS_METHOD_PRINT) == 0 || strcasecmp(name, BAS_METHOD_LINE) == 0 || strcasecmp(name, BAS_METHOD_CIRCLE) == 0 || strcasecmp(name, BAS_METHOD_PSET) == 0 || strcasecmp(name, BAS_METHOD_SCALE) == 0; } // The console / graphics statement at the current identifier, or NULL // when it is none or the program gives the name its own meaning: a SUB / // FUNCTION / DECLARE of that name, or the name followed by '=', '.' or // "(...) =". static const ScreenStmtT *findScreenStmt(BasCompilerT *c) { const char *name = c->lex.token.text; for (int32_t i = 0; i < DVX_ARRAY_LEN(sScreenStmts); i++) { if (strcasecmp(name, sScreenStmts[i].name) != 0) { continue; } BasLexerT peek = c->lex; basLexerNext(&peek); if (peek.token.type == TOK_EQ || peek.token.type == TOK_DOT || procNamed(c, name)) { return NULL; } if (peek.token.type == TOK_LPAREN && basParseAfterParens(&peek) == TOK_EQ) { return NULL; } return &sScreenStmts[i]; } return NULL; } // A label a statement jumps to (GOTO, GOSUB, RESTORE, RESUME, ON ... GOTO): // a name, or a QBasic line number (named by its number). NULL after the // error. static char *labelRef(BasCompilerT *c) { int32_t number = 0; if (basParseCheck(c, TOK_INT_LIT) || basParseCheck(c, TOK_LONG_LIT)) { return lineLabel(c, &number); } if (!basParseCheck(c, TOK_IDENT)) { basParseErrorExpected(c, "label name or line number"); return NULL; } char *name = basParseTokenName(c); basParseAdvance(c); return name; } // The line number at the current (integer literal) token: its label name // (the number in decimal, so 010 and 10 are one line), *outNumber its // value; the token is consumed. NULL after the error (QBasic's line // numbers are 0 .. 65529). static char *lineLabel(BasCompilerT *c, int32_t *outNumber) { int64_t value = basParseCheck(c, TOK_INT_LIT) ? c->lex.token.intVal : c->lex.token.longVal; char text[BAS_MAX_IDENT]; if (value < 0 || value > BAS_LINE_NUMBER_MAX) { basParseError(c, "Line number out of range (0 to %d)", (int)BAS_LINE_NUMBER_MAX); return NULL; } snprintf(text, sizeof(text), "%d", (int)value); *outNumber = (int32_t)value; basParseAdvance(c); char *name = basArenaStrDup(&c->unitArena, text, (int32_t)strlen(text)); if (name == NULL) { basParseError(c, "Out of memory"); } return name; } // A statement node at the current token (the dispatcher sets its // position to the statement's start). static BasStmtT *newStmt(BasCompilerT *c, uint8_t kind) { return (BasStmtT *)basParseNode(c, (int32_t)sizeof(BasStmtT), kind); } // True at "Name =" where Name is a keyword naming the FUNCTION / Property // Get being parsed (a class member may be called Name, Open ...): its // result is set by its name. static bool ownResultKeywordAhead(BasCompilerT *c) { if (!c->parseInProc || !c->parseProcIsFunction || basParseCheck(c, TOK_IDENT) || !basTokenIsWord(&c->lex.token) || c->parseProcName == NULL || strcasecmp(c->lex.token.text, c->parseProcName) != 0) { return false; } BasLexerT peek = c->lex; basLexerNext(&peek); return peek.token.type == TOK_EQ; } // Statements up to "END endKw" (consumed, *outEnded set) or a stopTok // token (left; TOK_EOF when none; TOK_ELSEIF stops at ELSE too) or the // end of the source. static BasNodeT *parseBlockBody(BasCompilerT *c, BasTokenTypeE endKw, BasTokenTypeE stopTok, bool *outEnded) { BasNodeT *head = NULL; BasNodeT *tail = NULL; *outEnded = false; basParseSkipNewlines(c); while (!c->hasError && !basParseCheck(c, TOK_EOF) && !basParseCheck(c, stopTok) && !(stopTok == TOK_ELSEIF && basParseCheck(c, TOK_ELSE))) { if (endKw != TOK_EOF && basParseMatchEnd(c, endKw)) { *outEnded = true; break; } BasNodeT *s = basParseStatement(c); if (s != NULL) { if (tail != NULL) { tail->next = s; } else { head = s; } tail = s; } basParseSkipNewlines(c); } return head; } // A single-line IF's branch: the colon-separated statements up to ELSE // or the end of the line, or a line number (THEN 100 / ELSE 200: a GOTO // to it, QBasic). static BasNodeT *parseBranch(BasCompilerT *c) { if (basParseCheck(c, TOK_INT_LIT) || basParseCheck(c, TOK_LONG_LIT)) { BasStmtT *s = newStmt(c, ST_GOTO); if (s != NULL) { s->name = labelRef(c); } return (BasNodeT *)s; } BasNodeT *head = NULL; BasNodeT *tail = NULL; while (!c->hasError && !basParseCheck(c, TOK_ELSE) && !basParseCheck(c, TOK_NEWLINE) && !basParseCheck(c, TOK_EOF)) { BasNodeT *s = basParseStatement(c); if (s != NULL) { if (tail != NULL) { tail->next = s; } else { head = s; } tail = s; } } return head; } // CALL name [(args)] static BasNodeT *parseCallKw(BasCompilerT *c) { basParseAdvance(c); if (!basParseCheck(c, TOK_IDENT)) { basParseErrorExpected(c, "subroutine name"); return NULL; } BasNameT *name = (BasNameT *)basParseNode(c, (int32_t)sizeof(BasNameT), NK_NAME); BasStmtT *s = newStmt(c, ST_CALL); if (name == NULL || s == NULL) { return NULL; } name->name = basParseTokenName(c); basParseAdvance(c); s->a = &name->h; s->ival = BAS_ARGS_CALLKW; s->name = basTokenName(c->lex.token.type); if (basParseCheck(c, TOK_LPAREN)) { int32_t argc = 0; s->b = basParseArgList(c, &argc); s->ival2 = argc; } return (BasNodeT *)s; } // CLOSE [[#]n [, [#]n ...]]: one channel is OP_FILE_CLOSE; none (every // channel) or several go to BAS_FN_CLOSE. static BasNodeT *parseClose(BasCompilerT *c) { ArgListT args; memset(&args, 0, sizeof(args)); basParseAdvance(c); bool more = !basParseAtEnd(c); while (more && !c->hasError) { basParseMatch(c, TOK_HASH); argAdd(&args, basParseExpr(c)); more = basParseMatch(c, TOK_COMMA); } BasStmtT *s = newStmt(c, (args.count == 1) ? ST_SIMPLE : ST_SCREEN); if (s != NULL) { s->ival = (args.count == 1) ? OP_FILE_CLOSE : BAS_FN_CLOSE; s->b = args.head; s->ival2 = args.count; } return (BasNodeT *)s; } // DATA item, item ...: the values go to the data pool (no code). static BasNodeT *parseData(BasCompilerT *c) { BasStmtT *s = newStmt(c, ST_DATA); BasNodeT *tail = NULL; bool ok = true; basParseAdvance(c); if (s == NULL) { return NULL; } do { BasNodeT *item = parseDataItem(c, &ok); if (!ok) { break; } if (tail != NULL) { tail->next = item; } else { s->a = item; } tail = item; } while (basParseMatch(c, TOK_COMMA)); return (BasNodeT *)s; } // One DATA item: a quoted string, or a (signed) numeric literal standing // alone, is stored typed; anything else is the raw text up to the next // comma, colon, comment or end of line (QB): keywords, several words and // mixed text all read back as strings. static BasNodeT *parseDataItem(BasCompilerT *c, bool *outOk) { BasTokenT itemTok = c->lex.token; *outOk = true; if (basParseCheck(c, TOK_STRING_LIT)) { BasNodeT *n = basParseStrLit(c, c->lex.token.text, c->lex.token.textLen); basParseAdvance(c); return n; } // A lone numeric literal: the token after it ends the item bool isNeg = basParseCheck(c, TOK_MINUS); if (isNeg) { c->lex.allowNegMagnitude = true; basParseAdvance(c); c->lex.allowNegMagnitude = false; } if (!c->hasError && (basParseCheck(c, TOK_INT_LIT) || basParseCheck(c, TOK_LONG_LIT) || basParseCheck(c, TOK_FLOAT_LIT) || basParseCheck(c, TOK_CUR_LIT))) { BasLexerT after = c->lex; basLexerNext(&after); BasTokenTypeE next = after.token.type; if (next == TOK_COMMA || next == TOK_NEWLINE || next == TOK_COLON || next == TOK_EOF) { BasNodeT *n = parseNumLit(c, isNeg); basParseAdvance(c); return n; } } if (c->hasError) { *outOk = false; return NULL; } // Raw text from the start of the item const char *src = c->source; int32_t start = itemTok.pos; int32_t end = start; while (end < c->sourceLen && src[end] != ',' && src[end] != ':' && src[end] != '\'' && src[end] != '\n' && src[end] != '\r') { end++; } int32_t textEnd = end; while (textEnd > start && (src[textEnd - 1] == ' ' || src[textEnd - 1] == '\t')) { textEnd--; } if (textEnd == start || itemTok.type == TOK_NEWLINE || itemTok.type == TOK_EOF) { basParseError(c, "Expected DATA value"); *outOk = false; return NULL; } BasNodeT *n = basParseStrLit(c, src + start, textEnd - start); // Lexing resumes at the delimiter c->prevLine = itemTok.line; basLexerSeek(&c->lex, end, itemTok.line, itemTok.col + (end - start)); if (c->lex.token.type == TOK_ERROR) { basErrorAt(c, c->lex.errLine, c->lex.errCol, "%s", c->lex.error); } return n; } // DO [WHILE | UNTIL cond] ... LOOP [WHILE | UNTIL cond] static BasNodeT *parseDo(BasCompilerT *c) { BasLoopT *loop = (BasLoopT *)basParseNode(c, (int32_t)sizeof(BasLoopT), ST_DO); basParseAdvance(c); if (loop == NULL) { return NULL; } if (basParseCheck(c, TOK_WHILE) || basParseCheck(c, TOK_UNTIL)) { loop->preUntil = basParseCheck(c, TOK_UNTIL); basParseAdvance(c); loop->preCond = basParseExpr(c); } basParseExpectEnd(c); bool ended = false; loop->body = parseBlockBody(c, TOK_EOF, TOK_LOOP, &ended); if (c->hasError) { return (BasNodeT *)loop; } basParseExpect(c, TOK_LOOP); if (basParseCheck(c, TOK_WHILE) || basParseCheck(c, TOK_UNTIL)) { loop->postUntil = basParseCheck(c, TOK_UNTIL); basParseAdvance(c); loop->postCond = basParseExpr(c); } return (BasNodeT *)loop; } // A drawing method's arguments in its QBasic statement syntax: Print's // items (a BasPrintItemT list), the Line / Circle / PSet blocks, Scale's // optional (x1, y1)-(x2, y2). static BasNodeT *parseDrawingArgs(BasCompilerT *c, const char *method, int32_t *outArgc) { ArgListT args; memset(&args, 0, sizeof(args)); if (strcasecmp(method, BAS_METHOD_PRINT) == 0) { return parseObjectPrintItems(c, outArgc); } if (strcasecmp(method, BAS_METHOD_LINE) == 0) { parseGfxLineArgs(c, &args); } else if (strcasecmp(method, BAS_METHOD_CIRCLE) == 0) { parseGfxCircleArgs(c, &args); } else if (strcasecmp(method, BAS_METHOD_PSET) == 0) { parseGfxPsetArgs(c, &args, 0); } else { int32_t flags = parseGfxRect(c, &args); argAdd(&args, basParseIntLit(c, flags)); } *outArgc = args.count; return args.head; } // Err.Raise number [, source [, description]], Err.Clear and // Err.Number = n: the Err object's statements. static BasNodeT *parseErrStatement(BasCompilerT *c) { ArgListT args; memset(&args, 0, sizeof(args)); basParseAdvance(c); basParseExpect(c, TOK_DOT); const char *member = basParseTokenName(c); uint8_t fn = BAS_FN_ERR_SET; basParseAdvance(c); if (member != NULL && strcasecmp(member, BAS_ERR_MEMBER_RAISE) == 0) { fn = BAS_FN_ERR_RAISE; do { argAdd(&args, basParseExpr(c)); } while (!c->hasError && basParseMatch(c, TOK_COMMA)); } else if (member != NULL && strcasecmp(member, BAS_ERR_MEMBER_NUMBER) == 0) { basParseExpect(c, TOK_EQ); argAdd(&args, basParseExpr(c)); } else if (member == NULL || strcasecmp(member, BAS_ERR_MEMBER_CLEAR) != 0) { basParseError(c, "Err has no statement '%s' (Raise, Clear, or Number = value)", member != NULL ? member : ""); return NULL; } BasStmtT *s = newStmt(c, ST_SCREEN); if (s != NULL) { s->ival = fn; s->b = args.head; s->ival2 = args.count; } return (BasNodeT *)s; } // EXIT FOR / DO / SUB / FUNCTION / PROPERTY (the resolver checks it is // inside one). static BasNodeT *parseExit(BasCompilerT *c) { basParseAdvance(c); BasTokenTypeE what = c->lex.token.type; if (what != TOK_FOR && what != TOK_DO && what != TOK_PROPERTY && what != TOK_SUB && what != TOK_FUNCTION) { basParseError(c, "Expected FOR, DO, SUB, FUNCTION or PROPERTY after EXIT"); return NULL; } BasStmtT *s = newStmt(c, ST_EXIT); if (s != NULL) { s->ival = what; } basParseAdvance(c); return (BasNodeT *)s; } // Optional arguments, each after a comma and each allowed to be empty: a // written one adds its slots[i] flag, a missing one is 0. static void parseFlaggedArgs(BasCompilerT *c, ArgListT *l, const int32_t *slots, int32_t count, int32_t *flags) { bool more = true; for (int32_t i = 0; i < count; i++) { more = more && basParseMatch(c, TOK_COMMA); if (more && !basParseCheck(c, TOK_COMMA) && !basParseAtEnd(c)) { argAdd(l, basParseExpr(c)); *flags |= slots[i]; } else { argZero(c, l); } } } // FOR var = start TO limit [STEP step] ... NEXT [var], or FOR EACH. static BasNodeT *parseFor(BasCompilerT *c) { int32_t line = c->lex.token.line; int32_t col = c->lex.token.col; basParseAdvance(c); // FOR EACH item IN group (a variable named Each is still a counter) if (basParseCheckWord(c, BAS_KW_EACH)) { BasLexerT peek = c->lex; basLexerNext(&peek); if (peek.token.type == TOK_IDENT) { return parseForEach(c, line, col); } } if (!basParseCheck(c, TOK_IDENT)) { basParseErrorExpected(c, "loop variable"); return NULL; } BasLoopT *loop = (BasLoopT *)basParseNode(c, (int32_t)sizeof(BasLoopT), ST_FOR); BasNameT *var = (BasNameT *)basParseNode(c, (int32_t)sizeof(BasNameT), NK_NAME); if (loop == NULL || var == NULL) { return NULL; } var->name = basParseTokenName(c); loop->var = &var->h; basParseAdvance(c); basParseExpect(c, TOK_EQ); loop->from = basParseExpr(c); basParseExpect(c, TOK_TO); loop->to = basParseExpr(c); if (basParseMatch(c, TOK_STEP)) { loop->step = basParseExpr(c); } basParseExpectEnd(c); bool ended = false; loop->body = parseBlockBody(c, TOK_EOF, TOK_NEXT, &ended); if (c->hasError) { return (BasNodeT *)loop; } basParseExpect(c, TOK_NEXT); if (basParseCheck(c, TOK_IDENT)) { loop->nextName = basParseTokenName(c); loop->nextLine = c->lex.token.line; loop->nextCol = c->lex.token.col; basParseAdvance(c); } return (BasNodeT *)loop; } // FOR EACH item IN group ... NEXT [item] (FOR read, EACH current). static BasNodeT *parseForEach(BasCompilerT *c, int32_t line, int32_t col) { basParseAdvance(c); BasLoopT *loop = (BasLoopT *)basParseNode(c, (int32_t)sizeof(BasLoopT), ST_FOREACH); BasNameT *var = (BasNameT *)basParseNode(c, (int32_t)sizeof(BasNameT), NK_NAME); if (loop == NULL || var == NULL) { return NULL; } loop->h.line = line; loop->h.col = (uint16_t)col; var->name = basParseTokenName(c); loop->var = &var->h; basParseAdvance(c); if (!basParseCheckWord(c, BAS_KW_IN)) { basParseErrorExpected(c, "IN"); return NULL; } basParseAdvance(c); loop->from = basParseExpr(c); basParseExpectEnd(c); bool ended = false; loop->body = parseBlockBody(c, TOK_EOF, TOK_NEXT, &ended); if (c->hasError) { return (BasNodeT *)loop; } basParseExpect(c, TOK_NEXT); if (basParseCheck(c, TOK_IDENT)) { loop->nextName = basParseTokenName(c); loop->nextLine = c->lex.token.line; loop->nextCol = c->lex.token.col; basParseAdvance(c); } return (BasNodeT *)loop; } // GET [#]channel, [recno], target / PUT [#]channel, [recno], value static BasNodeT *parseGet(BasCompilerT *c, uint8_t kind) { BasStmtT *s = newStmt(c, kind); basParseAdvance(c); if (s == NULL) { return NULL; } basParseMatch(c, TOK_HASH); s->a = basParseExpr(c); basParseExpect(c, TOK_COMMA); if (!basParseCheck(c, TOK_COMMA)) { s->b = basParseExpr(c); } basParseExpect(c, TOK_COMMA); s->c = (kind == ST_GET) ? basParseLValue(c, false) : basParseExpr(c); return (BasNodeT *)s; } // CIRCLE [STEP](x, y), r [, color [, start [, end [, aspect]]]] -> // (x, y, r, color, start, end, aspect, flags). static void parseGfxCircleArgs(BasCompilerT *c, ArgListT *l) { static const int32_t slots[] = { BAS_GFX_COLOR, BAS_GFX_ARC_START, BAS_GFX_ARC_END, BAS_GFX_ASPECT }; int32_t flags = 0; parseGfxPoint(c, l, &flags, BAS_GFX_STEP); basParseExpect(c, TOK_COMMA); argAdd(l, basParseExpr(c)); parseFlaggedArgs(c, l, slots, DVX_ARRAY_LEN(slots), &flags); argAdd(l, basParseIntLit(c, flags)); } // LINE [[STEP](x1, y1)]-[STEP](x2, y2) [, [color] [, [B | BF] [, style]]] // -> (x1, y1, x2, y2, color, style, flags). static void parseGfxLineArgs(BasCompilerT *c, ArgListT *l) { int32_t flags = 0; if (basParseCheck(c, TOK_LPAREN) || basParseCheck(c, TOK_STEP)) { parseGfxPoint(c, l, &flags, BAS_GFX_STEP); flags |= BAS_GFX_START; } else { argZero(c, l); argZero(c, l); } basParseExpect(c, TOK_MINUS); parseGfxPoint(c, l, &flags, BAS_GFX_STEP2); bool colorComma = basParseMatch(c, TOK_COMMA); if (colorComma && !basParseCheck(c, TOK_COMMA) && !basParseAtEnd(c)) { argAdd(l, basParseExpr(c)); flags |= BAS_GFX_COLOR; } else { argZero(c, l); } if (colorComma && basParseMatch(c, TOK_COMMA)) { if (basParseCheckWord(c, BAS_KW_BF)) { flags |= BAS_GFX_BOX_FILL; basParseAdvance(c); } else if (basParseCheckWord(c, BAS_KW_B)) { flags |= BAS_GFX_BOX; basParseAdvance(c); } else if (!basParseCheck(c, TOK_COMMA) && !basParseAtEnd(c)) { basParseErrorExpected(c, "B or BF"); return; } if (basParseMatch(c, TOK_COMMA)) { argAdd(l, basParseExpr(c)); flags |= BAS_GFX_STYLE; } else { argZero(c, l); } } else { argZero(c, l); } argAdd(l, basParseIntLit(c, flags)); } // [STEP](x, y): adds x and y; STEP adds stepFlag. static void parseGfxPoint(BasCompilerT *c, ArgListT *l, int32_t *flags, int32_t stepFlag) { if (basParseMatch(c, TOK_STEP)) { *flags |= stepFlag; } basParseExpect(c, TOK_LPAREN); argAdd(l, basParseExpr(c)); basParseExpect(c, TOK_COMMA); argAdd(l, basParseExpr(c)); basParseExpect(c, TOK_RPAREN); } // PSET / PRESET [STEP](x, y) [, color] -> (x, y, color, flags). static void parseGfxPsetArgs(BasCompilerT *c, ArgListT *l, int32_t flags) { static const int32_t slots[] = { BAS_GFX_COLOR }; parseGfxPoint(c, l, &flags, BAS_GFX_STEP); parseFlaggedArgs(c, l, slots, DVX_ARRAY_LEN(slots), &flags); argAdd(l, basParseIntLit(c, flags)); } // The optional (x1, y1)-(x2, y2) of VIEW, WINDOW and the Scale method: // the four values (zeros without a rectangle). Returns BAS_VIEW_COORDS // when the rectangle was written. static int32_t parseGfxRect(BasCompilerT *c, ArgListT *l) { int32_t noStep = 0; if (!basParseCheck(c, TOK_LPAREN)) { for (int32_t i = 0; i < BAS_PAIR_VALUES * BAS_PAIR_VALUES; i++) { argZero(c, l); } return 0; } parseGfxPoint(c, l, &noStep, 0); basParseExpect(c, TOK_MINUS); parseGfxPoint(c, l, &noStep, 0); return BAS_VIEW_COORDS; } // A statement that starts with a name: MID$ / LSET / RSET statements, // "chain = value" (isSet: SET), or a call: name args, name(args), // obj.Method args, the drawing methods' QBasic syntax. static BasNodeT *parseIdentStatement(BasCompilerT *c, bool isSet) { BasLexerT peek = c->lex; basLexerNext(&peek); if (basParseCheckWord(c, BAS_KW_MID) && peek.token.type == TOK_LPAREN) { return parseMidAssign(c); } if ((basParseCheckWord(c, BAS_KW_LSET) || basParseCheckWord(c, BAS_KW_RSET)) && peek.token.type == TOK_IDENT && !procNamed(c, c->lex.token.text)) { return parseLset(c); } BasNameT *name = (BasNameT *)basParseNode(c, (int32_t)sizeof(BasNameT), NK_NAME); if (name == NULL) { return NULL; } name->name = basParseTokenName(c); basParseAdvance(c); BasNodeT *chain = basParseChain(c, &name->h, true); if (chain == NULL) { return NULL; } if (basParseMatch(c, TOK_EQ)) { BasStmtT *s = newStmt(c, ST_ASSIGN); if (s == NULL) { return NULL; } s->a = chain; s->b = basParseExpr(c); if (isSet) { s->h.flags |= NF_SET; basParseObjectOperand(c, s->b); } return (BasNodeT *)s; } if (chain->kind == NK_MEMBER) { return parseMemberCall(c, (BasMemberT *)chain, isSet); } BasStmtT *s = newStmt(c, ST_CALL); if (s == NULL) { return NULL; } s->a = chain; s->name = basTokenName(c->lex.token.type); if (chain->kind == NK_CALL) { BasCallT *call = (BasCallT *)chain; // name (x) without CALL: the parentheses are the argument's own, // so it is passed as a value (VB, QBasic) if (call->argc == 1 && call->args != NULL) { call->args->flags |= NF_PAREN; } s->ival = BAS_ARGS_PARENS; s->ival2 = call->argc; } else if (!basParseAtEnd(c)) { s->ival = BAS_ARGS_BARE; s->b = basParseBareArgs(c, &s->ival2); } else { s->ival = BAS_ARGS_NONE; } return (BasNodeT *)s; } // IF cond THEN stmt [: stmt] [ELSE stmt ...] on one line, or the block // form with ELSEIF / ELSE / END IF. static BasNodeT *parseIf(BasCompilerT *c) { BasIfT *n = (BasIfT *)basParseNode(c, (int32_t)sizeof(BasIfT), ST_IF); basParseAdvance(c); if (n == NULL) { return NULL; } n->cond = basParseExpr(c); // IF cond GOTO label (QBasic): a single-line IF whose THEN is the GOTO bool viaGoto = basParseCheck(c, TOK_GOTO); if (!viaGoto) { basParseExpect(c, TOK_THEN); } if (c->hasError) { return (BasNodeT *)n; } // Single-line IF: every colon-separated statement after THEN (and // after ELSE) belongs to that branch (QuickBASIC); a line number // there is a GOTO to it. if (viaGoto || (!basParseCheck(c, TOK_NEWLINE) && !basParseCheck(c, TOK_EOF))) { n->singleLine = true; n->thenBody = viaGoto ? parseLabelName(c, ST_GOTO) : parseBranch(c); if (basParseMatch(c, TOK_ELSE)) { n->hasElse = true; n->elseBody = parseBranch(c); } return (BasNodeT *)n; } basParseExpectEnd(c); bool ended = false; BasNodeT *eiTail = NULL; n->thenBody = parseBlockBody(c, TOK_IF, TOK_ELSEIF, &ended); while (!ended && !c->hasError && basParseCheck(c, TOK_ELSEIF)) { BasIfT *ei = (BasIfT *)basParseNode(c, (int32_t)sizeof(BasIfT), ST_ELSEIF); basParseAdvance(c); if (ei == NULL) { return (BasNodeT *)n; } ei->cond = basParseExpr(c); basParseExpect(c, TOK_THEN); basParseExpectEnd(c); ei->thenBody = parseBlockBody(c, TOK_IF, TOK_ELSEIF, &ended); if (eiTail != NULL) { eiTail->next = &ei->h; } else { n->elseIfs = &ei->h; } eiTail = &ei->h; } if (!ended && !c->hasError && basParseMatch(c, TOK_ELSE)) { n->hasElse = true; basParseExpectEnd(c); n->elseBody = parseBlockBody(c, TOK_IF, TOK_EOF, &ended); } if (!ended && !c->hasError) { basParseError(c, "Expected END IF"); } return (BasNodeT *)n; } // Up to max further arguments QBasic accepts and DVX ignores: evaluated // for their side effects and dropped (an NK_CONV with OP_POP marks them). static void parseIgnoredArgs(BasCompilerT *c, ArgListT *l, int32_t max) { for (int32_t i = 0; i < max && basParseMatch(c, TOK_COMMA); i++) { if (!basParseCheck(c, TOK_COMMA) && !basParseAtEnd(c)) { BasOpT *drop = (BasOpT *)basParseNode(c, (int32_t)sizeof(BasOpT), NK_CONV); if (drop != NULL) { drop->op = OP_POP; drop->a = basParseExpr(c); } argAdd(l, &drop->h); l->count--; } } } // INPUT #channel, target [, target ...] / INPUT ["prompt" {; | ,}] target static BasNodeT *parseInput(BasCompilerT *c) { BasStmtT *s = newStmt(c, ST_INPUT); basParseAdvance(c); if (s == NULL) { return NULL; } if (basParseMatch(c, TOK_HASH)) { BasNodeT *tail = NULL; s->a = basParseExpr(c); basParseExpect(c, TOK_COMMA); do { BasNodeT *t = basParseLValue(c, true); if (t == NULL) { break; } if (tail != NULL) { tail->next = t; } else { s->c = t; } tail = t; } while (basParseMatch(c, TOK_COMMA)); return (BasNodeT *)s; } if (basParseCheck(c, TOK_STRING_LIT)) { s->b = basParseStrLit(c, c->lex.token.text, c->lex.token.textLen); basParseAdvance(c); if (!basParseMatch(c, TOK_SEMICOLON)) { basParseMatch(c, TOK_COMMA); } } s->c = basParseLValue(c, true); return (BasNodeT *)s; } // GOTO / GOSUB label (a name or a line number) static BasNodeT *parseLabelName(BasCompilerT *c, uint8_t kind) { basParseAdvance(c); BasStmtT *s = newStmt(c, kind); if (s != NULL) { s->name = labelRef(c); } return (s != NULL && s->name != NULL) ? (BasNodeT *)s : NULL; } // LINE: LINE INPUT, or the graphics LINE statement. static BasNodeT *parseLine(BasCompilerT *c) { basParseAdvance(c); if (basParseMatch(c, TOK_INPUT)) { return parseLineInput(c); } ArgListT args; BasStmtT *s = newStmt(c, ST_SCREEN); memset(&args, 0, sizeof(args)); parseGfxLineArgs(c, &args); if (s != NULL) { s->ival = BAS_FN_LINE; s->b = args.head; s->ival2 = args.count; } return (BasNodeT *)s; } // LINE INPUT #channel, target / LINE INPUT [;] ["prompt" {; | ,}] target static BasNodeT *parseLineInput(BasCompilerT *c) { BasStmtT *s = newStmt(c, ST_LINEINPUT); if (s == NULL) { return NULL; } if (basParseMatch(c, TOK_HASH)) { s->a = basParseExpr(c); basParseExpect(c, TOK_COMMA); s->c = basParseLValue(c, true); return (BasNodeT *)s; } basParseMatch(c, TOK_SEMICOLON); if (basParseCheck(c, TOK_STRING_LIT)) { s->b = basParseStrLit(c, c->lex.token.text, c->lex.token.textLen); basParseAdvance(c); if (!basParseMatch(c, TOK_SEMICOLON)) { basParseExpect(c, TOK_COMMA); } } s->c = basParseLValue(c, true); return (BasNodeT *)s; } // LSET / RSET target = value (the word is current). static BasNodeT *parseLset(BasCompilerT *c) { BasStmtT *s = newStmt(c, ST_LSET); if (s == NULL) { return NULL; } s->ival = basParseCheckWord(c, BAS_KW_RSET) ? 1 : 0; basParseAdvance(c); s->a = basParseLValue(c, true); basParseExpect(c, TOK_EQ); s->c = basParseExpr(c); return (BasNodeT *)s; } // A statement on obj.member not followed by '=': Show / Hide of a form // (name.Show [modal]), a drawing method (Print, Line, Circle, PSet, // Scale) in its QBasic syntax, or a method call with comma-separated // arguments. A drawing method of a plain name may be a class's own // method instead: its arguments are read once the resolver knows. static BasNodeT *parseMemberCall(BasCompilerT *c, BasMemberT *m, bool isSet) { BasStmtT *s = newStmt(c, ST_CALL); (void)isSet; if (s == NULL) { return NULL; } s->name = basTokenName(c->lex.token.type); bool twoPart = m->base->kind == NK_NAME || (m->base->kind == NK_ME && !c->parseInClass); s->a = &m->h; s->ival = BAS_ARGS_BARE; if (twoPart && (strcasecmp(m->name, BAS_METHOD_SHOW) == 0 || strcasecmp(m->name, BAS_METHOD_HIDE) == 0)) { if (strcasecmp(m->name, BAS_METHOD_SHOW) == 0 && !basParseAtEnd(c)) { s->b = basParseExpr(c); s->ival2 = 1; } return (BasNodeT *)s; } if (!drawingMethod(m->name)) { s->b = basParseBareArgs(c, &s->ival2); return (BasNodeT *)s; } if (m->base->kind == NK_NAME) { BasLitT *at = (BasLitT *)basParseNode(c, (int32_t)sizeof(BasLitT), NK_INT); if (at == NULL) { return NULL; } at->ival = c->lex.token.pos; s->c = &at->h; s->h.flags |= NF_DEFERRED; while (!c->hasError && !basParseAtEnd(c)) { basParseAdvance(c); } return (BasNodeT *)s; } s->ival = BAS_ARGS_DRAWING; s->b = parseDrawingArgs(c, m->name, &s->ival2); return (BasNodeT *)s; } // Me.member ... or (inside WITH) .member ... as a statement (isSet: SET // Me.member = object). static BasNodeT *parseMeStatement(BasCompilerT *c, bool isSet) { BasNodeT *me; if (basParseCheck(c, TOK_DOT)) { // .member inside WITH: the WITH's object in place of Me me = basParseWithRef(c); } else { me = basParseNode(c, (int32_t)sizeof(BasNameT), NK_ME); basParseAdvance(c); if (!basParseCheck(c, TOK_DOT)) { basParseErrorExpected(c, "'.' after Me"); return NULL; } } if (me == NULL) { return NULL; } BasNodeT *chain = basParseChain(c, me, true); if (chain == NULL) { return NULL; } if (basParseMatch(c, TOK_EQ)) { BasStmtT *s = newStmt(c, ST_ASSIGN); if (s == NULL) { return NULL; } s->a = chain; s->b = basParseExpr(c); if (isSet) { s->h.flags |= NF_SET; basParseObjectOperand(c, s->b); } return (BasNodeT *)s; } if (chain->kind != NK_MEMBER) { basParseErrorExpected(c, "'=' or method arguments"); return NULL; } return parseMemberCall(c, (BasMemberT *)chain, isSet); } // MID$(var$, start [, len]) = replacement$ static BasNodeT *parseMidAssign(BasCompilerT *c) { BasStmtT *s = newStmt(c, ST_MIDASSIGN); basParseAdvance(c); basParseExpect(c, TOK_LPAREN); if (s == NULL || c->hasError) { return NULL; } if (!basParseCheck(c, TOK_IDENT)) { basParseErrorExpected(c, "string variable name"); return NULL; } s->name = basParseTokenName(c); basParseAdvance(c); basParseExpect(c, TOK_COMMA); s->a = basParseExpr(c); if (basParseMatch(c, TOK_COMMA)) { s->b = basParseExpr(c); } basParseExpect(c, TOK_RPAREN); basParseExpect(c, TOK_EQ); s->c = basParseExpr(c); return (BasNodeT *)s; } // A numeric literal (the current token), negated when isNeg (the minus // was consumed with allowNegMagnitude, so -32768% and -2147483648 arrive // as magnitudes). Integers negate with 32-bit wrap-around. static BasNodeT *parseNumLit(BasCompilerT *c, bool isNeg) { if (basParseCheck(c, TOK_FLOAT_LIT)) { BasLitT *lit = (BasLitT *)basParseNode(c, (int32_t)sizeof(BasLitT), NK_DBL); if (lit != NULL) { lit->dval = isNeg ? -c->lex.token.dblVal : c->lex.token.dblVal; lit->h.type = BAS_TYPE_DOUBLE; } return (BasNodeT *)lit; } if (basParseCheck(c, TOK_CUR_LIT)) { return basParseCurLit(c, isNeg ? -c->lex.token.longVal : c->lex.token.longVal); } bool isIntLit = basParseCheck(c, TOK_INT_LIT); int64_t mag = isIntLit ? c->lex.token.intVal : c->lex.token.longVal; uint32_t bits = (uint32_t)mag; if (isNeg) { bits = 0u - bits; } BasNodeT *n = basParseIntLit(c, (int32_t)bits); if (n != NULL && !isIntLit) { n->type = BAS_TYPE_LONG; } return n; } // Object Print items (Form / PictureBox Print): ';', ',', SPC(n), TAB(n) // and values up to the end of the statement. static BasNodeT *parseObjectPrintItems(BasCompilerT *c, int32_t *outCount) { BasNodeT *head = NULL; BasNodeT *tail = NULL; while (!c->hasError && !basParseAtEnd(c)) { if (basParseMatch(c, TOK_SEMICOLON)) { printItem(c, &head, &tail, BAS_ITEM_SEMI, NULL); continue; } if (basParseMatch(c, TOK_COMMA)) { printItem(c, &head, &tail, BAS_ITEM_COMMA, NULL); continue; } if (basParseCheckWord(c, BAS_KW_SPC) || basParseCheckWord(c, BAS_KW_TAB)) { uint8_t kind = basParseCheckWord(c, BAS_KW_SPC) ? BAS_ITEM_SPC : BAS_ITEM_TAB; basParseAdvance(c); basParseExpect(c, TOK_LPAREN); BasNodeT *e = basParseExpr(c); basParseExpect(c, TOK_RPAREN); printItem(c, &head, &tail, kind, e); continue; } printItem(c, &head, &tail, BAS_ITEM_EXPR, basParseExpr(c)); } *outCount = basNodeCount(head); return head; } // ON ERROR GOTO label | 0, ON ERROR RESUME NEXT, ON expr GOTO / GOSUB // label, label ... static BasNodeT *parseOn(BasCompilerT *c) { basParseAdvance(c); if (basParseCheck(c, TOK_ERROR_KW)) { BasStmtT *s = newStmt(c, ST_ONERROR); basParseAdvance(c); // ON ERROR RESUME NEXT (VB) if (basParseMatch(c, TOK_RESUME)) { basParseExpect(c, TOK_NEXT); if (s != NULL) { s->ival = BAS_ON_ERROR_RESUME_NEXT; } return (BasNodeT *)s; } if (!basParseCheck(c, TOK_GOTO)) { basParseError(c, "Expected GOTO or RESUME NEXT after ON ERROR"); return NULL; } basParseAdvance(c); if (s == NULL) { return NULL; } if (basParseCheck(c, TOK_INT_LIT) && c->lex.token.intVal == 0) { basParseAdvance(c); return (BasNodeT *)s; } s->name = labelRef(c); return (s->name != NULL) ? (BasNodeT *)s : NULL; } BasStmtT *s = newStmt(c, ST_ONGOTO); if (s == NULL) { return NULL; } s->a = basParseExpr(c); if (basParseMatch(c, TOK_GOSUB)) { s->ival = 1; } else if (!basParseMatch(c, TOK_GOTO)) { basParseError(c, "Expected GOTO or GOSUB after ON expression"); return NULL; } BasNodeT *tail = NULL; do { BasNameT *label = (BasNameT *)basParseNode(c, (int32_t)sizeof(BasNameT), NK_NAME); if (label == NULL) { return NULL; } label->name = labelRef(c); if (label->name == NULL) { return NULL; } if (tail != NULL) { tail->next = &label->h; } else { s->b = &label->h; } tail = &label->h; s->ival2++; } while (basParseMatch(c, TOK_COMMA)); return (BasNodeT *)s; } // OPEN file FOR mode AS [#]channel [LEN = recordLength] static BasNodeT *parseOpen(BasCompilerT *c) { BasStmtT *s = newStmt(c, ST_OPEN); basParseAdvance(c); if (s == NULL) { return NULL; } s->a = basParseExpr(c); basParseExpect(c, TOK_FOR); switch (c->lex.token.type) { case TOK_INPUT: s->ival = BAS_FILE_MODE_INPUT; break; case TOK_OUTPUT: s->ival = BAS_FILE_MODE_OUTPUT; break; case TOK_APPEND: s->ival = BAS_FILE_MODE_APPEND; break; case TOK_RANDOM: s->ival = BAS_FILE_MODE_RANDOM; break; case TOK_BINARY: s->ival = BAS_FILE_MODE_BINARY; break; default: if (!c->hasError) { basParseError(c, "Expected INPUT, OUTPUT, APPEND, RANDOM, or BINARY after FOR"); } return NULL; } basParseAdvance(c); basParseExpect(c, TOK_AS); basParseMatch(c, TOK_HASH); s->b = basParseExpr(c); if (basParseCheckWord(c, BAS_KW_LEN)) { basParseAdvance(c); basParseExpect(c, TOK_EQ); s->c = basParseExpr(c); } return (BasNodeT *)s; } // count optional positional arguments (LOCATE row, col, cursor ...): each // may be left empty, a missing one is 0. Returns the BAS_GIVEN_* mask of // those written. static int32_t parseOptionalArgs(BasCompilerT *c, ArgListT *l, int32_t count) { int32_t given = 0; for (int32_t i = 0; i < count; i++) { if (i > 0 && !basParseMatch(c, TOK_COMMA)) { for (int32_t j = i; j < count; j++) { argZero(c, l); } return given; } if (basParseCheck(c, TOK_COMMA) || basParseAtEnd(c)) { argZero(c, l); continue; } argAdd(l, basParseExpr(c)); given |= 1 << i; } return given; } // PRINT [#channel, items] / PRINT items / PRINT USING (LPRINT: no #). static BasNodeT *parsePrint(BasCompilerT *c, bool isLprint) { BasPrintT *pr = (BasPrintT *)basParseNode(c, (int32_t)sizeof(BasPrintT), ST_PRINT); basParseAdvance(c); if (pr == NULL) { return NULL; } pr->isLprint = isLprint; // PRINT #channel, then what PRINT takes (USING, zones, SPC, TAB) if (!isLprint && basParseMatch(c, TOK_HASH)) { pr->channel = basParseExpr(c); basParseExpect(c, TOK_COMMA); } parsePrintItems(c, pr); return (BasNodeT *)pr; } // What follows PRINT (or LPRINT) when it is not PRINT #: items with ';', // ',', SPC(n) and TAB(n), or USING "fmt"; values. static void parsePrintItems(BasCompilerT *c, BasPrintT *pr) { BasNodeT *tail = NULL; if (basParseCheckWord(c, BAS_KW_USING)) { basParseAdvance(c); pr->mode = BAS_PRINT_USING; pr->format = basParseExpr(c); basParseExpect(c, TOK_SEMICOLON); while (!c->hasError) { printItem(c, &pr->items, &tail, BAS_ITEM_EXPR, basParseExpr(c)); if (!basParseCheck(c, TOK_SEMICOLON) && !basParseCheck(c, TOK_COMMA)) { break; } printItem(c, &pr->items, &tail, BAS_ITEM_SEMI, NULL); basParseAdvance(c); if (basParseAtEnd(c)) { break; } } return; } pr->mode = BAS_PRINT_CONSOLE; while (!c->hasError && !basParseAtEnd(c)) { if (basParseMatch(c, TOK_SEMICOLON)) { printItem(c, &pr->items, &tail, BAS_ITEM_SEMI, NULL); continue; } if (basParseMatch(c, TOK_COMMA)) { printItem(c, &pr->items, &tail, BAS_ITEM_COMMA, NULL); continue; } if (basParseCheckWord(c, BAS_KW_SPC) || basParseCheckWord(c, BAS_KW_TAB)) { uint8_t kind = basParseCheckWord(c, BAS_KW_SPC) ? BAS_ITEM_SPC : BAS_ITEM_TAB; basParseAdvance(c); basParseExpect(c, TOK_LPAREN); BasNodeT *e = basParseExpr(c); basParseExpect(c, TOK_RPAREN); printItem(c, &pr->items, &tail, kind, e); continue; } printItem(c, &pr->items, &tail, BAS_ITEM_EXPR, basParseExpr(c)); } } // RaiseEvent Name[(args)] static BasNodeT *parseRaiseEvent(BasCompilerT *c) { basParseAdvance(c); BasStmtT *s = newStmt(c, ST_RAISEEVENT); if (s == NULL) { return NULL; } s->name = basParseTokenName(c); basParseAdvance(c); if (basParseCheck(c, TOK_LPAREN)) { s->b = basParseArgList(c, &s->ival2); } return (BasNodeT *)s; } // READ target, target, ... static BasNodeT *parseRead(BasCompilerT *c) { BasStmtT *s = newStmt(c, ST_READ); BasNodeT *tail = NULL; basParseAdvance(c); if (s == NULL) { return NULL; } do { BasNodeT *t = basParseLValue(c, true); if (t == NULL) { break; } if (tail != NULL) { tail->next = t; } else { s->a = t; } tail = t; } while (basParseMatch(c, TOK_COMMA)); return (BasNodeT *)s; } // The console and graphics statements (the word is current): their // arguments in the layout opcodes.h gives each built-in. static BasNodeT *parseScreenStatement(BasCompilerT *c, const ScreenStmtT *stmt) { ArgListT args; BasStmtT *s = newStmt(c, ST_SCREEN); int32_t flags = stmt->flags; int32_t given = 0; uint8_t func = stmt->func; memset(&args, 0, sizeof(args)); basParseAdvance(c); if (s == NULL) { return NULL; } switch (stmt->func) { case BAS_FN_CLS: argAdd(&args, basParseAtEnd(c) ? basParseIntLit(c, BAS_CLS_OMITTED) : basParseExpr(c)); break; case BAS_FN_LOCATE: given = parseOptionalArgs(c, &args, BAS_LOCATE_VALUES); parseIgnoredArgs(c, &args, BAS_LOCATE_IGNORED); argAdd(&args, basParseIntLit(c, given)); break; case BAS_FN_COLOR: given = parseOptionalArgs(c, &args, BAS_PAIR_VALUES); parseIgnoredArgs(c, &args, BAS_COLOR_IGNORED); argAdd(&args, basParseIntLit(c, given)); break; case BAS_FN_WIDTH: // WIDTH #file, width / WIDTH "device", width / WIDTH LPRINT width if (basParseCheck(c, TOK_HASH) || basParseCheck(c, TOK_STRING_LIT) || basParseCheck(c, TOK_LPRINT)) { func = BAS_FN_FILE_WIDTH; if (basParseMatch(c, TOK_LPRINT)) { argAdd(&args, basParseIntLit(c, BAS_WIDTH_LPRINT)); } else { basParseMatch(c, TOK_HASH); argAdd(&args, basParseExpr(c)); basParseExpect(c, TOK_COMMA); } argAdd(&args, basParseExpr(c)); break; } given = parseOptionalArgs(c, &args, BAS_PAIR_VALUES); argAdd(&args, basParseIntLit(c, given)); break; case BAS_FN_SCREEN: // SCREEN mode [, [colorswitch] [, [apage] [, vpage]]] argAdd(&args, basParseExpr(c)); if (basParseMatch(c, TOK_COMMA)) { given = parseOptionalArgs(c, &args, BAS_SCREEN_VALUES); } else { for (int32_t i = 0; i < BAS_SCREEN_VALUES; i++) { argZero(c, &args); } } argAdd(&args, basParseIntLit(c, given)); break; case BAS_FN_PCOPY: argAdd(&args, basParseExpr(c)); basParseExpect(c, TOK_COMMA); argAdd(&args, basParseExpr(c)); break; case BAS_FN_PSET: parseGfxPsetArgs(c, &args, flags); break; case BAS_FN_CIRCLE: parseGfxCircleArgs(c, &args); break; case BAS_FN_PAINT: { static const int32_t slots[] = { BAS_GFX_COLOR, BAS_GFX_BORDER, BAS_GFX_BACKGROUND }; parseGfxPoint(c, &args, &flags, BAS_GFX_STEP); parseFlaggedArgs(c, &args, slots, DVX_ARRAY_LEN(slots), &flags); argAdd(&args, basParseIntLit(c, flags)); break; } case BAS_FN_DRAW: argAdd(&args, basParseExpr(c)); break; case BAS_FN_PALETTE: if (basParseCheckWord(c, BAS_KW_USING)) { // PALETTE USING array[(first)] basParseAdvance(c); if (!basParseCheck(c, TOK_IDENT)) { basParseErrorExpected(c, "array name"); return NULL; } BasNameT *arr = (BasNameT *)basParseNode(c, (int32_t)sizeof(BasNameT), NK_NAME); if (arr == NULL) { return NULL; } arr->name = basParseTokenName(c); arr->h.flags = NF_OBJECTARG; basParseAdvance(c); argAdd(&args, &arr->h); if (basParseMatch(c, TOK_LPAREN)) { argAdd(&args, basParseExpr(c)); basParseExpect(c, TOK_RPAREN); given = BAS_GIVEN_2; } else { argZero(c, &args); } argAdd(&args, basParseIntLit(c, given)); func = BAS_FN_PALETTE_USING; break; } if (basParseAtEnd(c)) { argZero(c, &args); argZero(c, &args); } else { argAdd(&args, basParseExpr(c)); basParseExpect(c, TOK_COMMA); argAdd(&args, basParseExpr(c)); given = BAS_GIVEN_1; } argAdd(&args, basParseIntLit(c, given)); break; case BAS_FN_VIEW: case BAS_FN_WINDOW: { static const int32_t slots[] = { BAS_VIEW_FILL, BAS_VIEW_BORDER }; bool isView = stmt->func == BAS_FN_VIEW; // VIEW PRINT [top TO bottom]: the text viewport if (isView && basParseMatch(c, TOK_PRINT)) { if (basParseAtEnd(c)) { argZero(c, &args); argZero(c, &args); } else { argAdd(&args, basParseExpr(c)); basParseExpect(c, TOK_TO); argAdd(&args, basParseExpr(c)); given = BAS_GIVEN_1; } argAdd(&args, basParseIntLit(c, given)); func = BAS_FN_VIEW_PRINT; break; } if (basParseCheckWord(c, "SCREEN")) { basParseAdvance(c); flags |= BAS_VIEW_SCREEN; } flags |= parseGfxRect(c, &args); if (isView) { parseFlaggedArgs(c, &args, slots, DVX_ARRAY_LEN(slots), &flags); } argAdd(&args, basParseIntLit(c, flags)); break; } case BAS_FN_PEN_SET: // PEN ON | OFF | STOP if (basParseMatch(c, TOK_ON)) { argAdd(&args, basParseIntLit(c, BAS_PEN_ON)); } else if (basParseCheckWord(c, BAS_KW_OFF) || basParseCheckWord(c, BAS_KW_STOP)) { argAdd(&args, basParseIntLit(c, basParseCheckWord(c, BAS_KW_STOP) ? BAS_PEN_STOP : BAS_PEN_OFF)); basParseAdvance(c); } else { basParseErrorExpected(c, "ON, OFF or STOP after PEN"); return NULL; } break; case BAS_FN_FULLSCREEN: if (basParseAtEnd(c) || basParseMatch(c, TOK_ON)) { argAdd(&args, basParseIntLit(c, BAS_FULLSCREEN_ON)); } else if (basParseCheckWord(c, BAS_KW_OFF)) { basParseAdvance(c); argAdd(&args, basParseIntLit(c, BAS_FULLSCREEN_OFF)); } else { argAdd(&args, basParseExpr(c)); } break; default: basParseError(c, "Unknown screen statement"); return NULL; } s->ival = func; s->b = args.head; s->ival2 = args.count; return (BasNodeT *)s; } // SELECT CASE expr / CASE items / CASE ELSE / END SELECT static BasNodeT *parseSelectCase(BasCompilerT *c) { BasSelectT *sel = (BasSelectT *)basParseNode(c, (int32_t)sizeof(BasSelectT), ST_SELECT); basParseAdvance(c); basParseExpect(c, TOK_CASE); if (sel == NULL) { return NULL; } sel->expr = basParseExpr(c); basParseExpectEnd(c); basParseSkipNewlines(c); BasNodeT *caseTail = NULL; while (!c->hasError && !basParseCheck(c, TOK_EOF)) { if (basParseMatchEnd(c, TOK_SELECT)) { return (BasNodeT *)sel; } if (!basParseCheck(c, TOK_CASE)) { basParseError(c, "Expected CASE or END SELECT"); return NULL; } BasCaseT *cs = (BasCaseT *)basParseNode(c, (int32_t)sizeof(BasCaseT), ST_CASE); basParseAdvance(c); if (cs == NULL) { return NULL; } if (caseTail != NULL) { caseTail->next = &cs->h; } else { sel->cases = &cs->h; } caseTail = &cs->h; bool ended = false; // CASE ELSE: its body runs to END SELECT if (basParseMatch(c, TOK_ELSE)) { cs->isElse = true; basParseExpectEnd(c); cs->body = parseBlockBody(c, TOK_SELECT, TOK_EOF, &ended); if (ended) { return (BasNodeT *)sel; } continue; } // CASE val [, val | val TO val | IS op val] ... BasNodeT *itemTail = NULL; do { BasCaseItemT *item = (BasCaseItemT *)basParseNode(c, (int32_t)sizeof(BasCaseItemT), ST_CASEITEM); if (item == NULL) { return NULL; } if (basParseMatch(c, TOK_IS)) { static const BasTokenTypeE ops[] = { TOK_LT, TOK_GT, TOK_LE, TOK_GE, TOK_EQ, TOK_NE }; static const uint8_t opcodes[] = { OP_CMP_LT, OP_CMP_GT, OP_CMP_LE, OP_CMP_GE, OP_CMP_EQ, OP_CMP_NE }; int32_t k = 0; while (k < DVX_ARRAY_LEN(ops) && !basParseCheck(c, ops[k])) { k++; } if (k == DVX_ARRAY_LEN(ops)) { basParseError(c, "Expected comparison operator after IS"); return NULL; } basParseAdvance(c); item->kind = BAS_CASE_IS; item->cmpOp = opcodes[k]; item->a = basParseExpr(c); } else { item->a = basParseExpr(c); if (basParseMatch(c, TOK_TO)) { item->kind = BAS_CASE_RANGE; item->b = basParseExpr(c); } else { item->kind = BAS_CASE_VALUE; } } if (itemTail != NULL) { itemTail->next = &item->h; } else { cs->items = &item->h; } itemTail = &item->h; } while (!c->hasError && basParseMatch(c, TOK_COMMA)); basParseExpectEnd(c); cs->body = parseBlockBody(c, TOK_SELECT, TOK_CASE, &ended); if (ended) { return (BasNodeT *)sel; } } if (!c->hasError) { basParseError(c, "Expected END SELECT"); } return (BasNodeT *)sel; } // KEYWORD arg, arg ... with exactly argc comma-separated arguments pushed // before opcode (hashFirst: an optional '#' before the first). static BasNodeT *parseSimple(BasCompilerT *c, uint8_t opcode, int32_t argc, bool hashFirst) { ArgListT args; BasStmtT *s = newStmt(c, ST_SIMPLE); memset(&args, 0, sizeof(args)); basParseAdvance(c); if (hashFirst) { basParseMatch(c, TOK_HASH); } for (int32_t i = 0; i < argc && !c->hasError; i++) { if (i > 0) { basParseExpect(c, TOK_COMMA); } argAdd(&args, basParseExpr(c)); } if (s != NULL) { s->ival = opcode; s->b = args.head; s->ival2 = args.count; } return (BasNodeT *)s; } // SWAP a, b static BasNodeT *parseSwap(BasCompilerT *c) { BasStmtT *s = newStmt(c, ST_SWAP); basParseAdvance(c); if (s == NULL) { return NULL; } s->a = basParseLValue(c, false); basParseExpect(c, TOK_COMMA); s->b = basParseLValue(c, false); return (BasNodeT *)s; } // WHILE cond ... WEND static BasNodeT *parseWhile(BasCompilerT *c) { BasLoopT *loop = (BasLoopT *)basParseNode(c, (int32_t)sizeof(BasLoopT), ST_WHILE); basParseAdvance(c); if (loop == NULL) { return NULL; } loop->preCond = basParseExpr(c); basParseExpectEnd(c); bool ended = false; loop->body = parseBlockBody(c, TOK_EOF, TOK_WEND, &ended); if (!c->hasError) { basParseExpect(c, TOK_WEND); } return (BasNodeT *)loop; } // WITH object ... END WITH: inside, ".name" is a member (or field) of // object. static BasNodeT *parseWith(BasCompilerT *c) { BasWithT *w = (BasWithT *)basParseNode(c, (int32_t)sizeof(BasWithT), ST_WITH); basParseAdvance(c); if (w == NULL) { return NULL; } w->expr = basParseExpr(c); basParseExpectEnd(c); bool ended = false; w->outer = c->parseWith; c->parseWith = w; w->body = parseBlockBody(c, TOK_WITH, TOK_EOF, &ended); c->parseWith = w->outer; if (!c->hasError && !ended) { basParseError(c, "WITH without END WITH"); } return (BasNodeT *)w; } // WRITE #channel, expr [, expr ...] static BasNodeT *parseWrite(BasCompilerT *c) { BasStmtT *s = newStmt(c, ST_WRITE); basParseAdvance(c); if (s == NULL) { return NULL; } if (!basParseMatch(c, TOK_HASH)) { basParseError(c, "Expected # after WRITE"); return NULL; } s->a = basParseExpr(c); basParseExpect(c, TOK_COMMA); BasNodeT *tail = NULL; do { BasNodeT *e = basParseExpr(c); if (e == NULL) { break; } if (tail != NULL) { tail->next = e; } else { s->b = e; } tail = e; s->ival2++; } while (basParseMatch(c, TOK_COMMA)); return (BasNodeT *)s; } static void printItem(BasCompilerT *c, BasNodeT **head, BasNodeT **tail, uint8_t kind, BasNodeT *expr) { BasPrintItemT *item = (BasPrintItemT *)basParseNode(c, (int32_t)sizeof(BasPrintItemT), NK_PRINTITEM); if (item == NULL) { return; } item->kind = kind; item->expr = expr; if (*tail != NULL) { (*tail)->next = &item->h; } else { *head = &item->h; } *tail = &item->h; } // True when the program declares a SUB / FUNCTION (or DECLARE) named name // (the declaration pass registered them all): then a console statement // word or LSET / RSET is a call of it. In a class module the class's own // procedures count too. static bool procNamed(BasCompilerT *c, const char *name) { const BasSymT *sym = basLookupProc(c, name); return sym != NULL; } // True at RESET when the statement ends after it (RESET = x, RESET.x and // RESET(i) are the program's own names). static bool resetAhead(BasCompilerT *c) { BasLexerT peek = c->lex; basLexerNext(&peek); return peek.token.type == TOK_NEWLINE || peek.token.type == TOK_EOF || peek.token.type == TOK_COLON || peek.token.type == TOK_REM; } // A statement that evaluates expr and drops its value. static BasNodeT *stmtExpr(BasCompilerT *c, BasNodeT *expr) { BasStmtT *s = newStmt(c, ST_EXPR); if (s != NULL) { s->a = expr; } return (BasNodeT *)s; }