// 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. // basDevBas.c -- DVX BASIC entry points of the basdev library // // Called through DECLARE LIBRARY (src/include/basic/compiler.bas and // debug.bas), so the signatures use only BASIC-marshalable types: // strings and LONG in, LONG or STRING out, True (-1) / False (0). A // returned string stays valid until the next call that returns one. // // Source sets, compiled modules, projects and runs are handles (1, 2, // ...; 0 means none). Each belongs to the program that made it; whatever a program // leaves open is freed when it ends. // // A run is a child program inside the calling program: its own VM and // form runtime in the same process, driven by RunPoll from a Timer. // RunPoll never blocks for longer than it is told. Two things nest: // * A breakpoint or step that stops in the child's module-level code // just makes RunPoll report RUN_PAUSED. // * One that stops inside a child's event handler (or its forms' Load // code, which runs inside RunStart) pauses inside that handler: the // child keeps the DVX event pump going, so the caller's Timer and // menus still run and can step, continue or stop it; RunPoll called // from there returns at once. RunStart (Load code) does not return // until such a pause is over. // Freeing a run whose code is on the stack ends it at once; its memory // is released by the next call into this library once that code has // unwound (or when the program ends). #include "basDebug.h" #include "basHelpMap.h" #include "basProcs.h" #include "basProject.h" #include "basSource.h" #include "basValidate.h" #include "../basBuild.h" #include "../basRes.h" #include "../compiler/lexer.h" #include "../formrt/basRunHost.h" #include "../formrt/formrt.h" #include "dvxDlg.h" #include "dvxMem.h" #include "dvxPlat.h" #include "dvxTypes.h" #include "stb_ds_wrap.h" #include "dvxBas.h" #include #include #include #include #include #include DVX_ABI_STAMP(basdev); #define SHELL_CLEANUP_SYM "shellRegisterAppCleanup" #define DEV_OUTPUT_MAX 32768 // PRINT text kept for RunOutput$ (oldest lines dropped) #define DEV_MESSAGE_LEN (BAS_DBG_VALUE_LEN * 2) #define DEV_INPUT_PROMPT "Enter value:" #define DEV_KIND_SUB "Sub" #define DEV_KIND_FUNCTION "Function" #define DEV_LINE_SEPS " ,\t\r\n" // between DbgSetBreakpoints line numbers #define DEV_DECIMAL 10 #define DEV_KIND_AUTO (-1) // CompSourceAdd: the kind follows the file name #define DEV_NO_INDEX (-1) // no such keyword / file #define DEV_TOKEN_ROW_MAX (BAS_MAX_TOKEN_LEN + DEV_MESSAGE_LEN) // one CompTokens$ row #define DEV_YIELD_MS 50 // a compile or a debugger snippet lets the other tasks run this often (devYield) #define DEV_DESIGN_BUSY "The design UserControls' code is running; try again when it is done." // The states RunState reports (debug.bas RUN_*) #define RUN_READY 0 #define RUN_RUNNING 1 #define RUN_EVENTS 2 #define RUN_PAUSED 3 #define RUN_ENDED 4 #define RUN_ERROR 5 typedef enum { DevSourceE, DevModuleE, DevRunE, DevProjectE } DevKindE; // Where a run is typedef enum { PhaseReadyE, // made, not started PhaseModuleE, // module-level code PhaseEventsE, // waiting for events (a form is open) PhaseFinalE, // Class_Terminates of the module variables PhaseEndedE, PhaseErrorE } DevPhaseE; // A source set: the concatenated program and its source map typedef struct { BasProjectSourceT ps; char dbpPath[DVX_MAX_PATH]; // "" when not read from a project char projectDir[DVX_MAX_PATH]; char name[BAS_MAX_IDENT]; // [Project] Name char startupForm[BAS_MAX_IDENT]; char helpFile[DVX_MAX_PATH]; // relative to projectDir } DevSourceT; typedef struct { BasModuleT *mod; const BasFormRtT *designer; // the program whose design forms run its UserControls (CompDesignControls), NULL: none int32_t runs; // runs using it bool freeRequested; // CompModuleFree while a run or a designer used it } DevModuleT; typedef struct { BasFormRtT *rt; // NULL once the program ended (output, error stay) BasFormRtT *parentRt; // the program that made the run int32_t module; // module handle it runs DevPhaseE phase; bool paused; bool stopRequested; bool freeRequested; int32_t busy; // calls of this library into the child on the stack int32_t pausedLine; // concatenated line where paused (0: unknown) int32_t *breakpoints; // stb_ds: sorted concatenated lines BasBreakTitleT *breakTitles; // stb_ds: captions tagged while paused char *output; // DEV_OUTPUT_MAX bytes int32_t outputLen; int32_t errorLine; char error[BAS_VM_ERROR_MSG_LEN]; char title[BAS_MAX_IDENT]; char startupForm[BAS_MAX_IDENT]; } DevRunT; typedef struct { void *obj; // DevSourceT / DevModuleT / DevRunT / PrjStateT; NULL = free slot int32_t appId; // owner: freed when it ends uint8_t kind; // DevKindE } DevHandleT; // CompTokens$ in progress: the source the rows quote typedef struct { const char *src; } DevTokensT; // Text a caller's print callback gathers typedef struct { char *buf; int32_t cap; int32_t len; } DevCaptureT; // The shell's app-cleanup registry (shellApp.h), looked up at run time: // the library loads before the shell, and the host tests have none. typedef void (*DevCleanupFnT)(int32_t appId); typedef void (*DevCleanupRegFnT)(DevCleanupFnT fn); // ============================================================ // Module state // ============================================================ static DevHandleT *sHandles = NULL; // stb_ds; handle = index + 1 static bool sCleanupHooked = false; static BasTextT sText; // the last string result static char *sCompError = NULL; // stb_ds: why the last Comp* call failed static int32_t sCompErrorLine = 0; // concatenated line of the last compile error static int32_t sCompErrorCol = 0; static int32_t sCompFileLine = 0; // the same line in its file static char sCompErrorFile[DVX_MAX_PATH]; static PlatformTicksT sYieldAt = 0; // when devYield last let the other tasks run static char sDbgMessage[DEV_MESSAGE_LEN]; // the last Dbg* / Run* message // ============================================================ // Prototypes // ============================================================ static void appEnded(int32_t appId); static void cellAppend(const char *text, int32_t len); static void classEventRow(void *ctx, const char *name, const char *params); static void collectAppend(DevCaptureT *cap, const char *text, int32_t len); static void collectPrint(void *ctx, const char *text, bool newline); int32_t CompBuildApp(int32_t mdl, int32_t src, const char *outPath, int32_t release); const char *CompClassEvents(const char *classText); int32_t CompCompile(int32_t src, int32_t optionExplicit, int32_t validate); int32_t CompConcatLine(int32_t src, const char *path, int32_t fileLine); int32_t CompDesignControls(int32_t mdl, int32_t src, const char *appDir); int32_t CompDesignRelease(void); const char *CompError(void); static void compErrorAppend(const char *s); int32_t CompErrorCol(void); const char *CompErrorFile(void); int32_t CompErrorFileLine(void); int32_t CompErrorLine(void); static void compFail(const char *msg); const char *CompHelpCtrlTopic(const char *typeName); const char *CompHelpTopic(const char *word); int32_t CompInCode(const char *source, int32_t position); int32_t CompIsIdent(const char *name); const char *CompKeyword(int32_t idx); int32_t CompKeywordClass(int32_t idx); int32_t CompKeywordCount(void); int32_t CompKeywordReserved(int32_t idx); const char *CompMapFile(int32_t src, int32_t line); int32_t CompMapLine(int32_t src, int32_t line); void CompModuleFree(int32_t mdl); const char *CompProcList(const char *source); const char *CompProcs(int32_t mdl); const char *CompRenameInCode(const char *source, const char *oldName, const char *newName, int32_t codeOnly); int32_t CompSourceAdd(int32_t src, const char *path, const char *text, int32_t kind); int32_t CompSourceCreate(void); const char *CompSourceFiles(int32_t src); void CompSourceFree(int32_t src); int32_t CompSourceFromProject(int32_t src, const char *dbpPath); const char *CompSourceText(int32_t src); const char *CompTokens(const char *source); const char *CompWithEvents(const char *source); static int32_t currentAppId(void); const char *DbgCallStack(int32_t run); void DbgContinue(int32_t run); const char *DbgEval(int32_t run, const char *expr); const char *DbgExec(int32_t run, const char *line); int32_t DbgLine(int32_t run); const char *DbgLocals(int32_t run); const char *DbgMessage(void); void DbgPause(int32_t run); const char *DbgProc(int32_t run); void DbgRunToLine(int32_t run, int32_t line); void DbgSetBreakpoints(int32_t run, const char *lines); int32_t DbgSetVar(int32_t run, const char *path, const char *expr); void DbgStepInto(int32_t run); void DbgStepOut(int32_t run); void DbgStepOver(int32_t run); static bool designRelease(const BasFormRtT *owner); static const char *devAppDir(const DevSourceT *s, const char *appDir, const BasFormRtT *rt); static bool devDoEvents(void *ctx); static bool devInput(void *ctx, const char *prompt, char *buf, int32_t bufSize); static void devOnBreak(void *ctx, int32_t line); static void devPrint(void *ctx, const char *text, bool newline); static const char *devTitle(const DevSourceT *s); static void devYield(void *ctx); static int32_t handleNew(DevKindE kind, void *obj); static void *handleOf(int32_t handle, DevKindE kind); static void handleRelease(int32_t idx); static void moduleRelease(DevModuleT *m); int32_t ProjAddFile(int32_t prj, const char *fileName); int32_t ProjAddFont(int32_t prj, const char *fileName); void ProjClose(int32_t prj); const char *ProjCtlHeader(const char *text, const char *key); const char *ProjCtlKeys(void); const char *ProjCtlNew(const char *name); const char *ProjCtlSetHeader(const char *text, const char *key, const char *value); const char *ProjFile(int32_t prj, int32_t idx); int32_t ProjFileCount(int32_t prj); int32_t ProjFileFind(int32_t prj, const char *fileName); int32_t ProjFileKind(int32_t prj, int32_t idx); const char *ProjFilePath(int32_t prj, int32_t idx); const char *ProjFont(int32_t prj, int32_t idx); int32_t ProjFontCount(int32_t prj); const char *ProjGet(int32_t prj, const char *key); static int32_t projHandle(PrjStateT *p); int32_t ProjNew(const char *name, const char *folder); int32_t ProjOpen(const char *dbpPath); const char *ProjPath(int32_t prj); int32_t ProjRemoveFile(int32_t prj, int32_t idx); int32_t ProjRemoveFont(int32_t prj, int32_t idx); int32_t ProjRenameFile(int32_t prj, int32_t idx, const char *newName); int32_t ProjSave(int32_t prj); int32_t ProjSet(int32_t prj, const char *key, const char *value); static void rowAdd(void *ctx, const char *const *cells, int32_t cellCount); int32_t RunCreate(int32_t mdl, int32_t src, const char *appDir, const char *args); static void runDestroyHost(DevRunT *run); static void runEnd(DevRunT *run); const char *RunError(int32_t run); int32_t RunErrorLine(int32_t run); static void runFinal(DevRunT *run); void RunFree(int32_t run); static bool runOnStack(const DevRunT *run); const char *RunOutput(int32_t run); static DevRunT *runPaused(int32_t handle); int32_t RunPoll(int32_t run, int32_t ms); static void runResume(DevRunT *run); static void runSlices(DevRunT *run, int32_t ms); int32_t RunStart(int32_t run, const char *startupForm, int32_t breakOnStart); int32_t RunState(int32_t run); void RunStop(int32_t run); static int32_t stateOf(const DevRunT *run); static void sweepDeferred(void); static void tokenRow(void *ctx, BasSrcKindE kind, int32_t line, int32_t col, int32_t pos, int32_t len); static void withEventsRow(void *ctx, const char *name, const char *className); // The debugger's snippets let the other tasks run (devYield) static const BasDbgPumpT sDevPump = { devYield, NULL }; // Shell app-cleanup hook: free what a program that ended left open (its // code has unwound, so nothing of it is on the stack). Its runtime let // go of its design controls when it ended (basUcRuntimeEnd), so their // modules -- its own, CompDesignControls takes no other -- are free to go. static void appEnded(int32_t appId) { for (int32_t i = 0; i < (int32_t)arrlen(sHandles); i++) { if (sHandles[i].obj && sHandles[i].appId == appId) { if (sHandles[i].kind == DevModuleE) { ((DevModuleT *)sHandles[i].obj)->designer = NULL; } handleRelease(i); } } } // Append text as one cell of a row: a TAB or line break inside it // becomes a blank so the row keeps its shape. static void cellAppend(const char *text, int32_t len) { for (int32_t i = 0; i < len; i++) { char c = (text[i] == BAS_ROW_CELL_SEP || text[i] == BAS_ROW_END || text[i] == '\r') ? BAS_ROW_BLANK : text[i]; basTextAppendLen(&sText, &c, 1); } } static void classEventRow(void *ctx, const char *name, const char *params) { const char *cells[] = { name, params }; rowAdd(ctx, cells, DVX_ARRAY_LEN(cells)); } static void collectAppend(DevCaptureT *cap, const char *text, int32_t len) { cap->len = basDbgAppendDropping(cap->buf, cap->cap, cap->len, text, len); } static void collectPrint(void *ctx, const char *text, bool newline) { DevCaptureT *cap = (DevCaptureT *)ctx; if (text) { collectAppend(cap, text, (int32_t)strlen(text)); } if (newline) { collectAppend(cap, "\n", 1); } } int32_t CompBuildApp(int32_t mdl, int32_t src, const char *outPath, int32_t release) { DevModuleT *m = (DevModuleT *)handleOf(mdl, DevModuleE); DevSourceT *s = (DevSourceT *)handleOf(src, DevSourceE); PrefsHandleT *prefs = NULL; char dir[DVX_MAX_PATH]; BasBuildSpecT spec; if (!m || !s || !outPath || !outPath[0]) { compFail("No compiled program, source set, or output file."); return BAS_FALSE; } memset(&spec, 0, sizeof(spec)); // A project's metadata (name, icon, help file ...) as bascomp reads it if (s->dbpPath[0] && basBuildOpenProject(s->dbpPath, &prefs, dir, sizeof(dir))) { basBuildSpecFromProject(prefs, s->projectDir, &spec); } else { spec.projName = s->name; spec.projectDir = s->projectDir; } spec.module = m->mod; spec.frmSources = (const char *const *)s->ps.frmTexts; spec.frmCount = (int32_t)arrlen(s->ps.frmTexts); spec.ctlSources = (const char *const *)s->ps.ctlTexts; spec.ctlCount = (int32_t)arrlen(s->ps.ctlTexts); spec.release = release != 0; // A control project builds a control file (.wgt), a program an .app const char *failure = (prefs && basBuildIsControlProject(prefs)) ? basBuildControl(outPath, &spec) : basBuildApp(outPath, &spec); if (prefs) { prefsClose(prefs); } if (failure) { compFail(failure); return BAS_FALSE; } return BAS_TRUE; } // One row per Event a class module declares: name, parameter list. const char *CompClassEvents(const char *classText) { basTextStart(&sText); basSourceClassEvents(classText ? classText : "", classEventRow, NULL); return basTextEnd(&sText); } int32_t CompCompile(int32_t src, int32_t optionExplicit, int32_t validate) { DevSourceT *s = (DevSourceT *)handleOf(src, DevSourceE); compFail(""); sCompErrorLine = 0; sCompErrorCol = 0; sCompFileLine = 0; sCompErrorFile[0] = '\0'; if (!s || s->ps.sourceLen == 0) { compFail("Nothing to compile."); return BAS_NO_HANDLE; } BasValidatorT v; BasCtrlValidatorT cb; memset(&v, 0, sizeof(v)); basValidateCallbacks(&v, &cb); if (validate) { // The program's own UserControls are control types of its forms for (int32_t i = 0; i < (int32_t)arrlen(s->ps.ctlTexts); i++) { char name[BAS_MAX_IDENT]; if (basExtractCtlName(s->ps.ctlTexts[i], (int32_t)strlen(s->ps.ctlTexts[i]), name, (int32_t)sizeof(name))) { basValidateAddType(&v, name); } } for (int32_t i = 0; i < (int32_t)arrlen(s->ps.frmTexts); i++) { basValidateScanFrm(&v, s->ps.frmTexts[i], (int32_t)strlen(s->ps.frmTexts[i]), NULL, NULL); } } // A control type no loaded widget library knows fails at once if (arrlen(v.badTypes) > 0) { char line[DEV_MESSAGE_LEN]; arrsetlen(sCompError, 0); for (int32_t i = 0; i < (int32_t)arrlen(v.badTypes); i++) { const BasBadTypeT *b = &v.badTypes[i]; snprintf(line, sizeof(line), BAS_VALIDATE_BAD_TYPE_FMT "\n", b->formName, b->ctrlName, b->typeName); compErrorAppend(line); } compErrorAppend(BAS_VALIDATE_BAD_TYPE_HINT); arrput(sCompError, '\0'); basValidateFree(&v); return BAS_NO_HANDLE; } BasCompileErrorT err; BasModuleT *mod = basBuildCompile(s->ps.source, s->ps.sourceLen, optionExplicit || s->ps.optionExplicit, validate ? &cb : NULL, &v.compiler, &err, devYield, NULL); basValidateFree(&v); if (!mod) { const char *path = NULL; compFail(basBuildErrorText(err.message)); sCompErrorLine = err.line; sCompErrorCol = err.col; if (err.line > 0 && basBuildMapLine(s->ps.map, err.line, &path, &sCompFileLine)) { snprintf(sCompErrorFile, sizeof(sCompErrorFile), "%s", path); } return BAS_NO_HANDLE; } DevModuleT *m = (DevModuleT *)calloc(1, sizeof(DevModuleT)); int32_t h = m ? handleNew(DevModuleE, m) : BAS_NO_HANDLE; if (h == BAS_NO_HANDLE) { free(m); basModuleFree(mod); compFail("Out of memory."); return BAS_NO_HANDLE; } m->mod = mod; return h; } int32_t CompConcatLine(int32_t src, const char *path, int32_t fileLine) { DevSourceT *s = (DevSourceT *)handleOf(src, DevSourceE); return s ? basBuildMapFileLine(s->ps.map, path, fileLine) : 0; } // The UserControls of source set src (its .ctl texts), classes of module // mdl, registered for the calling program's design forms in place of the // ones it registered before, as the DVX BASIC IDE's designer registers a // project's (basCtlLoadDesign): their code runs without the module-level // code, App.Path appDir (else the project's folder, else the caller's). // The module stays until they go; it must be the calling program's own // (another program's would be freed when that one ends). The number // registered; CompError$ says why when not every control was. int32_t CompDesignControls(int32_t mdl, int32_t src, const char *appDir) { DevModuleT *m = (DevModuleT *)handleOf(mdl, DevModuleE); DevSourceT *s = (DevSourceT *)handleOf(src, DevSourceE); BasFormRtT *owner = basFormRtCurrent(); compFail(""); if (!m || m->freeRequested || !s || !owner || sHandles[mdl - 1].appId != currentAppId()) { compFail("No compiled program or source set."); return 0; } if (!designRelease(owner)) { compFail(DEV_DESIGN_BUSY); return 0; } char path[DVX_MAX_PATH]; int32_t count = (int32_t)arrlen(s->ps.ctlTexts); snprintf(path, sizeof(path), "%s" DVX_PATH_SEP "%s", devAppDir(s, appDir, owner), devTitle(s)); m->designer = owner; int32_t added = basCtlLoadDesign(owner, m->mod, (const char *const *)s->ps.ctlTexts, count, path); if (added < count) { compFail("A UserControl of the set is not one of the module's, or a control type of its name is installed already."); } return added; } // The calling program's design controls (CompDesignControls) let go. // False while their code runs. int32_t CompDesignRelease(void) { BasFormRtT *owner = basFormRtCurrent(); compFail(""); if (owner && !designRelease(owner)) { compFail(DEV_DESIGN_BUSY); return BAS_FALSE; } return BAS_TRUE; } const char *CompError(void) { return sCompError ? sCompError : ""; } static void compErrorAppend(const char *s) { for (const char *c = s; *c; c++) { arrput(sCompError, *c); } } int32_t CompErrorCol(void) { return sCompErrorCol; } const char *CompErrorFile(void) { return sCompErrorFile; } int32_t CompErrorFileLine(void) { return sCompFileLine; } int32_t CompErrorLine(void) { return sCompErrorLine; } // Record why a Comp* call failed. static void compFail(const char *msg) { arrsetlen(sCompError, 0); compErrorAppend(msg); arrput(sCompError, '\0'); } const char *CompHelpCtrlTopic(const char *typeName) { char topic[BAS_MAX_IDENT + sizeof(BAS_HELP_CTRL_PREFIX)]; basHelpCtrlTopic(typeName ? typeName : "", topic, (int32_t)sizeof(topic)); return basTextSet(&sText, topic); } const char *CompHelpTopic(const char *word) { const char *topic = basHelpKeywordTopic(word); return topic ? topic : ""; } // position is 1-based, as INSTR and MID$ count. int32_t CompInCode(const char *source, int32_t position) { const char *src = source ? source : ""; return basSourceInCode(src, (int32_t)strlen(src), position - 1) ? BAS_TRUE : BAS_FALSE; } int32_t CompIsIdent(const char *name) { return basIsValidIdent(name) ? BAS_TRUE : BAS_FALSE; } const char *CompKeyword(int32_t idx) { return (idx >= 0 && idx < basLexerKeywordCount()) ? basLexerKeywordAt(idx) : ""; } // The keyword's BasKeywordClassE (compiler.bas COMP_KW_*), -1 for no such // keyword. int32_t CompKeywordClass(int32_t idx) { return (idx >= 0 && idx < basLexerKeywordCount()) ? (int32_t)basLexerKeywordClass(idx) : DEV_NO_INDEX; } int32_t CompKeywordCount(void) { return basLexerKeywordCount(); } int32_t CompKeywordReserved(int32_t idx) { return (idx >= 0 && idx < basLexerKeywordCount() && basLexerKeywordReserved(idx)) ? BAS_TRUE : BAS_FALSE; } const char *CompMapFile(int32_t src, int32_t line) { DevSourceT *s = (DevSourceT *)handleOf(src, DevSourceE); const char *path = NULL; int32_t fileLine = 0; return (s && basBuildMapLine(s->ps.map, line, &path, &fileLine)) ? basTextSet(&sText, path) : ""; } int32_t CompMapLine(int32_t src, int32_t line) { DevSourceT *s = (DevSourceT *)handleOf(src, DevSourceE); const char *path = NULL; int32_t fileLine = 0; return (s && basBuildMapLine(s->ps.map, line, &path, &fileLine)) ? fileLine : 0; } void CompModuleFree(int32_t mdl) { if (handleOf(mdl, DevModuleE)) { handleRelease(mdl - 1); } } // One row per SUB / FUNCTION block of source: kind, name, first line, // last line (1-based). const char *CompProcList(const char *source) { const char *pos = source ? source : ""; int32_t line = 1; basTextStart(&sText); while (*pos) { bool isSub = false; const char *afterKw = basProcDeclAt(dvxSkipWs(pos), &isSub); const char *next = strchr(pos, '\n'); if (afterKw) { char name[BAS_MAX_IDENT]; char row[DEV_MESSAGE_LEN]; int32_t endOffset = 0; const char *end = basProcSkipToEnd(pos, isSub, &endOffset); basProcNameFromDecl(afterKw, name, sizeof(name)); int32_t n = snprintf(row, sizeof(row), "%s%c%s%c%" PRId32 "%c%" PRId32 "%c", isSub ? DEV_KIND_SUB : DEV_KIND_FUNCTION, BAS_ROW_CELL_SEP, name, BAS_ROW_CELL_SEP, line, BAS_ROW_CELL_SEP, line + endOffset, BAS_ROW_END); basTextAppendLen(&sText, row, n); line += endOffset + 1; pos = end; continue; } pos = next ? next + 1 : pos + strlen(pos); line++; } return basTextEnd(&sText); } // One row per compiled SUB / FUNCTION: name, kind, owning form. const char *CompProcs(int32_t mdl) { DevModuleT *m = (DevModuleT *)handleOf(mdl, DevModuleE); basTextStart(&sText); for (int32_t i = 0; m && i < m->mod->procCount; i++) { const BasProcEntryT *p = &m->mod->procs[i]; char row[DEV_MESSAGE_LEN]; int32_t n = snprintf(row, sizeof(row), "%s%c%s%c%s%c", p->name, BAS_ROW_CELL_SEP, p->isFunction ? DEV_KIND_FUNCTION : DEV_KIND_SUB, BAS_ROW_CELL_SEP, p->formName, BAS_ROW_END); basTextAppendLen(&sText, row, n); } return basTextEnd(&sText); } // source with the form or control oldName renamed (the text unchanged // when nothing refers to it). const char *CompRenameInCode(const char *source, const char *oldName, const char *newName, int32_t codeOnly) { const char *src = source ? source : ""; char *renamed = basSourceRename(src, oldName, newName, codeOnly != 0); if (!renamed) { return basTextSet(&sText, src); } const char *text = basTextSet(&sText, renamed); free(renamed); return text; } int32_t CompSourceAdd(int32_t src, const char *path, const char *text, int32_t kind) { DevSourceT *s = (DevSourceT *)handleOf(src, DevSourceE); if (!s || !path || !text) { compFail("No source set."); return BAS_FALSE; } BasBuildWrapE wrap = (kind == DEV_KIND_AUTO) ? basBuildWrapForPath(path) : (BasBuildWrapE)kind; if (wrap < BAS_BUILD_WRAP_NONE || wrap > BAS_BUILD_WRAP_CONTROL) { compFail("Unknown kind of source file."); return BAS_FALSE; } if (!basBuildAddSource(&s->ps, path, text, (int32_t)strlen(text), wrap)) { compFail(s->ps.error); return BAS_FALSE; } return BAS_TRUE; } int32_t CompSourceCreate(void) { DevSourceT *s = (DevSourceT *)calloc(1, sizeof(DevSourceT)); int32_t h = s ? handleNew(DevSourceE, s) : BAS_NO_HANDLE; if (h == BAS_NO_HANDLE) { free(s); return BAS_NO_HANDLE; } snprintf(s->projectDir, sizeof(s->projectDir), "."); return h; } // The files of the set in compile order, one row each: the path as the // source map names it, the first line of its code in the file, and the // line of the concatenated source that line is. const char *CompSourceFiles(int32_t src) { DevSourceT *s = (DevSourceT *)handleOf(src, DevSourceE); basTextStart(&sText); for (int32_t i = 0; s && i < (int32_t)arrlen(s->ps.map); i++) { const BasSourceMapT *m = &s->ps.map[i]; char row[DVX_MAX_PATH + DEV_MESSAGE_LEN]; int32_t n = snprintf(row, sizeof(row), "%s%c%" PRId32 "%c%" PRId32 "%c", m->path, BAS_ROW_CELL_SEP, m->firstFileLine, BAS_ROW_CELL_SEP, m->startLine, BAS_ROW_END); basTextAppendLen(&sText, row, n); } return basTextEnd(&sText); } void CompSourceFree(int32_t src) { if (handleOf(src, DevSourceE)) { handleRelease(src - 1); } } // Replaces the set with the project dbpPath names, read from disk. int32_t CompSourceFromProject(int32_t src, const char *dbpPath) { DevSourceT *s = (DevSourceT *)handleOf(src, DevSourceE); PrefsHandleT *prefs = NULL; if (!s || !dbpPath || !basBuildOpenProject(dbpPath, &prefs, s->projectDir, sizeof(s->projectDir))) { compFail("Cannot open the project file."); return BAS_FALSE; } basBuildFreeProject(&s->ps); bool ok = basBuildConcatProject(prefs, s->projectDir, &s->ps); snprintf(s->dbpPath, sizeof(s->dbpPath), "%s", dbpPath); snprintf(s->name, sizeof(s->name), "%s", prefsGetString(prefs, BAS_INI_SECTION_PROJECT, BAS_INI_KEY_NAME, "")); snprintf(s->startupForm, sizeof(s->startupForm), "%s", prefsGetString(prefs, BAS_INI_SECTION_SETTINGS, BAS_INI_KEY_STARTUPFORM, "")); snprintf(s->helpFile, sizeof(s->helpFile), "%s", prefsGetString(prefs, BAS_INI_SECTION_PROJECT, BAS_INI_KEY_HELPFILE, "")); prefsClose(prefs); if (!ok) { compFail(s->ps.error); return BAS_FALSE; } return BAS_TRUE; } const char *CompSourceText(int32_t src) { DevSourceT *s = (DevSourceT *)handleOf(src, DevSourceE); return (s && s->ps.source) ? s->ps.source : ""; } // One row per token of source: line, column, length, kind, text. const char *CompTokens(const char *source) { DevTokensT tokens; tokens.src = source ? source : ""; basTextStart(&sText); basSourceTokens(tokens.src, (int32_t)strlen(tokens.src), tokenRow, &tokens); return basTextEnd(&sText); } // One row per WithEvents variable of source: name, class. const char *CompWithEvents(const char *source) { basTextStart(&sText); basSourceWithEvents(source ? source : "", withEventsRow, NULL); return basTextEnd(&sText); } // The app the calling code belongs to (0 outside DVX's app tracking): the // owner of the handles it makes. static int32_t currentAppId(void) { return dvxMemAppIdPtr ? *dvxMemAppIdPtr : 0; } const char *DbgCallStack(int32_t run) { DevRunT *r = runPaused(run); basTextStart(&sText); if (r) { basDbgCallStack(r->rt->vm, r->pausedLine, rowAdd, NULL); } return basTextEnd(&sText); } void DbgContinue(int32_t run) { DevRunT *r = runPaused(run); if (r) { basVmClearStepState(r->rt->vm); runResume(r); } } const char *DbgEval(int32_t run, const char *expr) { DevRunT *r = runPaused(run); char out[BAS_DBG_VALUE_LEN]; if (!r || !expr) { return ""; } return basTextSet(&sText, basDbgEval(r->rt->vm, expr, out, sizeof(out), &sDevPump) ? out : BAS_DBG_EVAL_ERROR); } const char *DbgExec(int32_t run, const char *line) { DevRunT *r = runPaused(run); char out[BAS_DBG_VALUE_LEN]; DevCaptureT cap; cap.buf = out; cap.cap = sizeof(out); cap.len = 0; out[0] = '\0'; basDbgExec(r ? r->rt->vm : NULL, line ? line : "", collectPrint, &cap, &sDevPump); return basTextSet(&sText, out); } int32_t DbgLine(int32_t run) { DevRunT *r = runPaused(run); return r ? r->pausedLine : 0; } const char *DbgLocals(int32_t run) { DevRunT *r = runPaused(run); basTextStart(&sText); if (r) { basDbgLocals(r->rt->vm, rowAdd, NULL); } return basTextEnd(&sText); } const char *DbgMessage(void) { return sDbgMessage; } // Break at the next line the program runs. void DbgPause(int32_t run) { DevRunT *r = (DevRunT *)handleOf(run, DevRunE); if (r && r->rt && !r->paused) { basVmStepInto(r->rt->vm); } } const char *DbgProc(int32_t run) { DevRunT *r = runPaused(run); if (!r) { return ""; } int32_t idx = basDbgCurProcIdx(r->rt->vm); return idx >= 0 ? r->rt->vm->module->procs[idx].name : BAS_DBG_MODULE_FRAME; } void DbgRunToLine(int32_t run, int32_t line) { DevRunT *r = runPaused(run); if (r) { basVmRunToCursor(r->rt->vm, line); runResume(r); } } // The breakpoints: concatenated line numbers separated by commas, blanks // or new lines ("" removes them all). void DbgSetBreakpoints(int32_t run, const char *lines) { DevRunT *r = (DevRunT *)handleOf(run, DevRunE); if (!r) { return; } arrsetlen(r->breakpoints, 0); for (const char *p = lines ? lines : ""; *p; ) { char *end = NULL; int32_t line = (int32_t)strtol(p, &end, DEV_DECIMAL); if (end == p) { p++; continue; } // Kept sorted as they come in (the VM searches them) if (line > 0) { int32_t at = (int32_t)arrlen(r->breakpoints); while (at > 0 && r->breakpoints[at - 1] > line) { at--; } arrins(r->breakpoints, at, line); } p = end; } // The VM keeps the array, so it learns every change of it if (r->rt) { basVmSetBreakpoints(r->rt->vm, r->breakpoints, (int32_t)arrlen(r->breakpoints)); } } int32_t DbgSetVar(int32_t run, const char *path, const char *expr) { DevRunT *r = runPaused(run); DevCaptureT cap; cap.buf = sDbgMessage; cap.cap = sizeof(sDbgMessage); cap.len = 0; sDbgMessage[0] = '\0'; bool ok = basDbgSetVar(r ? r->rt->vm : NULL, path ? path : "", expr ? expr : "", collectPrint, &cap, &sDevPump); dvxTrimRight(sDbgMessage); return ok ? BAS_TRUE : BAS_FALSE; } void DbgStepInto(int32_t run) { DevRunT *r = runPaused(run); if (r) { basVmStepInto(r->rt->vm); runResume(r); } } void DbgStepOut(int32_t run) { DevRunT *r = runPaused(run); if (r) { basVmStepOut(r->rt->vm); runResume(r); } } void DbgStepOver(int32_t run) { DevRunT *r = runPaused(run); if (r) { basVmStepOver(r->rt->vm); runResume(r); } } // Designer owner's UserControls let go (basCtlUnloadDesign), and the // module they ran given back (freed when CompModuleFree came meanwhile). // False, nothing changed, while their code runs. static bool designRelease(const BasFormRtT *owner) { if (!basCtlUnloadDesign(owner)) { return false; } for (int32_t i = 0; i < (int32_t)arrlen(sHandles); i++) { DevModuleT *m = (sHandles[i].obj && sHandles[i].kind == DevModuleE) ? (DevModuleT *)sHandles[i].obj : NULL; if (m && m->designer == owner) { m->designer = NULL; moduleRelease(m); } } return true; } // The App.Path of source set s's code run for program rt (RunCreate, // CompDesignControls): appDir when given, else the project's folder, else // rt's own. static const char *devAppDir(const DevSourceT *s, const char *appDir, const BasFormRtT *rt) { return (appDir && appDir[0]) ? appDir : (s && s->dbpPath[0]) ? s->projectDir : rt->vm->appPath; } // The child's DoEvents (and its pause loop inside a handler): pump DVX so // the caller's own events -- the Timer that steps or continues -- run. // False ends the child's code: it was stopped or freed, DVX is shutting // down, or the caller is idle with no form left open. static bool devDoEvents(void *ctx) { DevRunT *run = (DevRunT *)ctx; AppContextT *ac = run->rt->ctx; if (run->stopRequested || !ac->running) { return false; } if (run->parentRt && !basFormRtCodeOnStack(run->parentRt) && !basFormRtKeepsRunning(run->parentRt)) { return false; } run->busy++; bool ok = basFormRtUpdate(run->rt); run->busy--; return ok && !run->stopRequested; } // INPUT: what was printed so far, with the prompt, is in RunOutput$; the // answer comes from an input box. static bool devInput(void *ctx, const char *prompt, char *buf, int32_t bufSize) { DevRunT *run = (DevRunT *)ctx; if (prompt) { devPrint(run, prompt, false); } return dvxInputBox(run->rt->ctx, run->title, prompt ? prompt : DEV_INPUT_PROMPT, NULL, buf, bufSize); } // A breakpoint or step paused the child (line -1: a handler that paused // has returned, not a pause). static void devOnBreak(void *ctx, int32_t line) { DevRunT *run = (DevRunT *)ctx; if (line < 0 || run->stopRequested) { return; } run->paused = true; run->pausedLine = line; basVmSetDebugPaused(run->rt->vm, true); basRunHostTagBreak(run->rt, &run->breakTitles, true); } static void devPrint(void *ctx, const char *text, bool newline) { DevRunT *run = (DevRunT *)ctx; if (text) { run->outputLen = basDbgAppendDropping(run->output, DEV_OUTPUT_MAX, run->outputLen, text, (int32_t)strlen(text)); } if (newline) { run->outputLen = basDbgAppendDropping(run->output, DEV_OUTPUT_MAX, run->outputLen, "\n", 1); } } // The name of source set s's program: its project's, else the default. static const char *devTitle(const DevSourceT *s) { return (s && s->name[0]) ? s->name : BAS_DEFAULT_APP_NAME; } // basBuildCompile's unit callback in CompCompile and the debugger's // snippet pump: every DEV_YIELD_MS the other tasks run (the program's own // events wait, so its sources and its paused run stay as they are). static void devYield(void *ctx) { (void)ctx; if (platformClock() - sYieldAt >= PLATFORM_MS_TO_TICKS(DEV_YIELD_MS)) { basFormRtYield(); sYieldAt = platformClock(); } } static int32_t handleNew(DevKindE kind, void *obj) { if (!sCleanupHooked) { DevCleanupRegFnT reg = (DevCleanupRegFnT)dlsym(NULL, DVX_SYM(SHELL_CLEANUP_SYM)); sCleanupHooked = true; if (reg) { reg(appEnded); } } int32_t idx = 0; while (idx < (int32_t)arrlen(sHandles) && sHandles[idx].obj) { idx++; } if (idx == (int32_t)arrlen(sHandles)) { DevHandleT blank; memset(&blank, 0, sizeof(blank)); arrput(sHandles, blank); if (idx == (int32_t)arrlen(sHandles)) { return BAS_NO_HANDLE; } } sHandles[idx].obj = obj; sHandles[idx].kind = (uint8_t)kind; sHandles[idx].appId = currentAppId(); return idx + 1; } static void *handleOf(int32_t handle, DevKindE kind) { if (handle <= BAS_NO_HANDLE || handle > (int32_t)arrlen(sHandles)) { return NULL; } DevHandleT *h = &sHandles[handle - 1]; return (h->obj && h->kind == kind) ? h->obj : NULL; } // Free handle slot idx and what it holds (a run whose code is on the // stack is stopped and freed later by sweepDeferred). static void handleRelease(int32_t idx) { DevHandleT *h = &sHandles[idx]; switch (h->kind) { case DevSourceE: { DevSourceT *s = (DevSourceT *)h->obj; basBuildFreeProject(&s->ps); free(s); break; } case DevProjectE: { PrjStateT *p = (PrjStateT *)h->obj; prjClose(p); free(p); break; } case DevModuleE: { DevModuleT *m = (DevModuleT *)h->obj; // A run still executing it, or its controls on design forms, // keep it until they end if (m->runs > 0 || m->designer) { m->freeRequested = true; return; } basModuleFree(m->mod); free(m); break; } default: { DevRunT *run = (DevRunT *)h->obj; run->freeRequested = true; run->stopRequested = true; if (runOnStack(run)) { if (run->rt) { basVmStop(run->rt->vm); } return; } runEnd(run); arrfree(run->breakpoints); arrfree(run->breakTitles); free(run->output); free(run); break; } } h->obj = NULL; } // A run or a designer let go of a module: freed once nothing needs it // and CompModuleFree asked for it (one module handle holds it, so the // search ends there and never compares the freed pointer). static void moduleRelease(DevModuleT *m) { if (m->runs > 0 || m->designer || !m->freeRequested) { return; } for (int32_t i = 0; i < (int32_t)arrlen(sHandles); i++) { if (sHandles[i].kind == DevModuleE && sHandles[i].obj == m) { handleRelease(i); return; } } } // Add fileName (in the project's folder; its kind from the extension). // Its index, or -1 when it is already in the project. int32_t ProjAddFile(int32_t prj, const char *fileName) { PrjStateT *p = (PrjStateT *)handleOf(prj, DevProjectE); if (!p || !fileName || !fileName[0]) { compFail("No project or file name."); return DEV_NO_INDEX; } if (prjFileIndexByPath(p, fileName) >= 0) { compFail("The file is already in the project."); return DEV_NO_INDEX; } return prjAddFile(p, fileName, prjKindForPath(fileName)); } // Add a .DFN font the built program carries (path relative to the // project's folder). Its index, or -1 when it is listed already. int32_t ProjAddFont(int32_t prj, const char *fileName) { PrjStateT *p = (PrjStateT *)handleOf(prj, DevProjectE); if (!p || !fileName || !fileName[0]) { compFail("No project or font file name."); return DEV_NO_INDEX; } int32_t idx = prjAddFont(p, fileName); if (idx < 0) { compFail("The font is already in the project."); } return idx; } void ProjClose(int32_t prj) { if (handleOf(prj, DevProjectE)) { handleRelease(prj - 1); } } // The value of a header key of a UserControl's text ("" when the text // has no header or the key is none). const char *ProjCtlHeader(const char *text, const char *key) { char value[BAS_UC_TEXT_MAX] = ""; prjCtlHeaderGet(text, key, value, (int32_t)sizeof(value)); return basTextSet(&sText, value); } // One row per UserControl header key in the order the IDE's Properties // window lists them (prjCtlKeyAt): the key and the kind of value it takes. const char *ProjCtlKeys(void) { static const char *const sKinds[] = { [PrjCtlNameE] = "Name", [PrjCtlIntE] = "Integer", [PrjCtlBoolE] = "Boolean", [PrjCtlColorE] = "Color", [PrjCtlTextE] = "Text" }; const PrjCtlKeyT *k; basTextStart(&sText); for (int32_t i = 0; (k = prjCtlKeyAt(i)) != NULL; i++) { const char *cells[] = { k->key, sKinds[k->kind] }; rowAdd(NULL, cells, DVX_ARRAY_LEN(cells)); } return basTextEnd(&sText); } // The text of a new UserControl called name ("" with CompError$ set when // the name is no identifier). const char *ProjCtlNew(const char *name) { char *text = prjCtlNew(name); if (!text) { compFail("A UserControl's name must be an identifier."); return basTextSet(&sText, ""); } compFail(""); const char *out = basTextSet(&sText, text); free(text); return out; } // The UserControl's text with a header key set ("" with CompError$ set // when the key or value does not suit). const char *ProjCtlSetHeader(const char *text, const char *key, const char *value) { char *changed = (text && value) ? prjCtlHeaderSet(text, key, value) : NULL; if (!changed) { compFail("Not a UserControl header key, or a value it does not take."); return basTextSet(&sText, ""); } compFail(""); const char *out = basTextSet(&sText, changed); free(changed); return out; } // The file's path as the project lists it (relative to its folder). const char *ProjFile(int32_t prj, int32_t idx) { PrjStateT *p = (PrjStateT *)handleOf(prj, DevProjectE); return (p && idx >= 0 && idx < prjFileCount(p)) ? p->files[idx].path : ""; } int32_t ProjFileCount(int32_t prj) { PrjStateT *p = (PrjStateT *)handleOf(prj, DevProjectE); return p ? prjFileCount(p) : 0; } int32_t ProjFileFind(int32_t prj, const char *fileName) { PrjStateT *p = (PrjStateT *)handleOf(prj, DevProjectE); return (p && fileName) ? prjFileIndexByPath(p, fileName) : DEV_NO_INDEX; } // COMP_KIND_MODULE, COMP_KIND_FORM, COMP_KIND_CLASS or COMP_KIND_CONTROL; // -1 for no such file. int32_t ProjFileKind(int32_t prj, int32_t idx) { PrjStateT *p = (PrjStateT *)handleOf(prj, DevProjectE); return (p && idx >= 0 && idx < prjFileCount(p)) ? (int32_t)prjKindWrap((PrjFileKindE)p->files[idx].kind) : DEV_NO_INDEX; } const char *ProjFilePath(int32_t prj, int32_t idx) { PrjStateT *p = (PrjStateT *)handleOf(prj, DevProjectE); char path[DVX_MAX_PATH] = ""; if (p) { prjFullPath(p, idx, path, (int32_t)sizeof(path)); } return basTextSet(&sText, path); } // Font idx the program carries, as the project lists it (relative to its // folder; "" out of range). const char *ProjFont(int32_t prj, int32_t idx) { PrjStateT *p = (PrjStateT *)handleOf(prj, DevProjectE); const char *path = p ? prjFontPath(p, idx) : NULL; return path ? path : ""; } int32_t ProjFontCount(int32_t prj) { PrjStateT *p = (PrjStateT *)handleOf(prj, DevProjectE); return p ? prjFontCount(p) : 0; } // A [Project] / [Settings] value by its .dbp key ("" for an unknown key). const char *ProjGet(int32_t prj, const char *key) { PrjStateT *p = (PrjStateT *)handleOf(prj, DevProjectE); char value[PRJ_MAX_FIELD] = ""; if (p) { prjFieldGet(p, key, value, (int32_t)sizeof(value)); } return basTextSet(&sText, value); } // A new project handle holding p (freed when no handle can be made). static int32_t projHandle(PrjStateT *p) { int32_t h = p ? handleNew(DevProjectE, p) : BAS_NO_HANDLE; if (h == BAS_NO_HANDLE) { if (p) { prjClose(p); } free(p); compFail("Out of memory."); } return h; } // A new project called name in folder (its .dbp, name.dbp in folder, is // written by ProjSave). int32_t ProjNew(const char *name, const char *folder) { if (!name || !name[0] || !folder) { compFail("A project needs a name and a folder."); return BAS_NO_HANDLE; } PrjStateT *p = (PrjStateT *)malloc(sizeof(PrjStateT)); if (p) { prjNew(p, name, folder[0] ? folder : "."); } return projHandle(p); } int32_t ProjOpen(const char *dbpPath) { PrjStateT *p = (PrjStateT *)malloc(sizeof(PrjStateT)); if (!p) { compFail("Out of memory."); return BAS_NO_HANDLE; } prjInit(p); if (!dbpPath || !prjLoad(p, dbpPath)) { free(p); compFail("Cannot open the project file."); return BAS_NO_HANDLE; } return projHandle(p); } // The project file's (.dbp) path. const char *ProjPath(int32_t prj) { PrjStateT *p = (PrjStateT *)handleOf(prj, DevProjectE); return p ? p->projectPath : ""; } // Remove file idx from the project (the file itself stays on disk). int32_t ProjRemoveFile(int32_t prj, int32_t idx) { PrjStateT *p = (PrjStateT *)handleOf(prj, DevProjectE); if (!p || idx < 0 || idx >= prjFileCount(p)) { return BAS_FALSE; } prjRemoveFile(p, idx); return BAS_TRUE; } // Remove font idx from the ones the program carries (the file stays). int32_t ProjRemoveFont(int32_t prj, int32_t idx) { PrjStateT *p = (PrjStateT *)handleOf(prj, DevProjectE); if (!p || idx < 0 || idx >= prjFontCount(p)) { return BAS_FALSE; } prjRemoveFont(p, idx); return BAS_TRUE; } // Rename file idx on disk and in the project (the .dbp is saved at // once). False with CompError$ saying why. int32_t ProjRenameFile(int32_t prj, int32_t idx, const char *newName) { PrjStateT *p = (PrjStateT *)handleOf(prj, DevProjectE); char err[DEV_MESSAGE_LEN]; if (!p || idx < 0 || idx >= prjFileCount(p) || !newName) { compFail("No such project file."); return BAS_FALSE; } if (!prjRenameFile(p, idx, newName, err, (int32_t)sizeof(err))) { compFail(err); return BAS_FALSE; } return BAS_TRUE; } int32_t ProjSave(int32_t prj) { PrjStateT *p = (PrjStateT *)handleOf(prj, DevProjectE); if (!p || !prjSave(p)) { compFail("Cannot write the project file."); return BAS_FALSE; } p->dirty = false; return BAS_TRUE; } // Set a value by its .dbp key. False for an unknown key. int32_t ProjSet(int32_t prj, const char *key, const char *value) { PrjStateT *p = (PrjStateT *)handleOf(prj, DevProjectE); return (p && prjFieldSet(p, key, value ? value : "")) ? BAS_TRUE : BAS_FALSE; } static void rowAdd(void *ctx, const char *const *cells, int32_t cellCount) { (void)ctx; basTextRow(&sText, cells, cellCount); } int32_t RunCreate(int32_t mdl, int32_t src, const char *appDir, const char *args) { DevModuleT *m = (DevModuleT *)handleOf(mdl, DevModuleE); DevSourceT *s = (DevSourceT *)handleOf(src, DevSourceE); BasFormRtT *parent = basFormRtCurrent(); sweepDeferred(); sDbgMessage[0] = '\0'; if (!m || m->freeRequested || !parent) { snprintf(sDbgMessage, sizeof(sDbgMessage), "No compiled program to run."); return BAS_NO_HANDLE; } DevRunT *run = (DevRunT *)calloc(1, sizeof(DevRunT)); char *output = (char *)malloc(DEV_OUTPUT_MAX); if (!run || !output) { free(run); free(output); snprintf(sDbgMessage, sizeof(sDbgMessage), "Out of memory."); return BAS_NO_HANDLE; } char helpPath[DVX_MAX_PATH] = ""; const char *hostError = NULL; BasRunSpecT spec; run->output = output; run->output[0] = '\0'; run->parentRt = parent; run->module = mdl; run->phase = PhaseReadyE; snprintf(run->title, sizeof(run->title), "%s", devTitle(s)); snprintf(run->startupForm, sizeof(run->startupForm), "%s", s ? s->startupForm : ""); if (s && s->helpFile[0]) { snprintf(helpPath, sizeof(helpPath), "%s" DVX_PATH_SEP "%s", s->projectDir, s->helpFile); } // App.Path: the folder given, else the project's, else the caller's; // App.User from the program as if it were a file there char appFile[DVX_MAX_PATH]; snprintf(appFile, sizeof(appFile), "%s" DVX_PATH_SEP "%s", devAppDir(s, appDir, parent), devTitle(s)); memset(&spec, 0, sizeof(spec)); spec.appDir = devAppDir(s, appDir, parent); spec.appFile = appFile; spec.args = args; spec.title = run->title; spec.helpFile = helpPath[0] ? helpPath : NULL; spec.printFn = devPrint; spec.printCtx = run; spec.inputFn = devInput; spec.inputCtx = run; spec.doEventsFn = devDoEvents; spec.doEventsCtx = run; spec.suppressErrorDialog = true; run->rt = basRunHostCreate(parent->ctx, m->mod, &spec, &hostError); int32_t h = run->rt ? handleNew(DevRunE, run) : BAS_NO_HANDLE; if (h == BAS_NO_HANDLE) { if (run->rt) { basRunHostDestroy(run->rt); } snprintf(sDbgMessage, sizeof(sDbgMessage), "%s", hostError ? hostError : "Out of memory."); free(run->output); free(run); return BAS_NO_HANDLE; } m->runs++; basVmSetBreakpointCallback(run->rt->vm, devOnBreak, run); for (int32_t i = 0; s && i < (int32_t)arrlen(s->ps.frmTexts); i++) { basRunHostRegisterFrm(run->rt, s->ps.frmTexts[i], (int32_t)strlen(s->ps.frmTexts[i])); } // Its own UserControls, before its forms load for (int32_t i = 0; s && i < (int32_t)arrlen(s->ps.ctlTexts); i++) { basFormRtRegisterCtl(run->rt, s->ps.ctlTexts[i], (int32_t)strlen(s->ps.ctlTexts[i])); } return h; } // Destroy the child's VM and form runtime (its windows close). static void runDestroyHost(DevRunT *run) { if (!run->rt) { return; } DevModuleT *m = (DevModuleT *)handleOf(run->module, DevModuleE); // Design controls the child made (CompDesignControls) go with it, and // so does its hold on their module designRelease(run->rt); basRunHostTagBreak(run->rt, &run->breakTitles, false); basRunHostDestroy(run->rt); run->rt = NULL; if (m) { m->runs--; moduleRelease(m); } } // The child stopped running: keep its error, close it. static void runEnd(DevRunT *run) { if (run->rt && run->rt->vm->errorMsg[0] && !run->stopRequested) { snprintf(run->error, sizeof(run->error), "%s", run->rt->vm->errorMsg); run->errorLine = basVmGetErrorLine(run->rt->vm); run->phase = PhaseErrorE; } else if (run->phase != PhaseErrorE) { run->phase = PhaseEndedE; } run->paused = false; runDestroyHost(run); } const char *RunError(int32_t run) { DevRunT *r = (DevRunT *)handleOf(run, DevRunE); return r ? r->error : ""; } int32_t RunErrorLine(int32_t run) { DevRunT *r = (DevRunT *)handleOf(run, DevRunE); return r ? r->errorLine : 0; } // Module code and events are over: a normal end releases the module // variables, so their objects' Class_Terminates run (VB). static void runFinal(DevRunT *run) { if (run->phase != PhaseFinalE && !run->stopRequested && !run->rt->terminated && basVmFinish(run->rt->vm)) { run->phase = PhaseFinalE; return; } runEnd(run); } void RunFree(int32_t run) { if (handleOf(run, DevRunE)) { handleRelease(run - 1); } sweepDeferred(); } // Whether the child's code is on the stack (a handler of it, or a call // of this library into it). static bool runOnStack(const DevRunT *run) { return run->busy > 0 || (run->rt && basFormRtCodeOnStack(run->rt)); } // What the program printed since the last call. const char *RunOutput(int32_t run) { DevRunT *r = (DevRunT *)handleOf(run, DevRunE); if (!r) { return ""; } const char *text = basTextSet(&sText, r->output); r->output[0] = '\0'; r->outputLen = 0; return text; } // The run when it is paused in the debugger, else NULL. static DevRunT *runPaused(int32_t handle) { DevRunT *r = (DevRunT *)handleOf(handle, DevRunE); return (r && r->rt && r->paused) ? r : NULL; } int32_t RunPoll(int32_t run, int32_t ms) { sweepDeferred(); DevRunT *r = (DevRunT *)handleOf(run, DevRunE); if (!r) { return RUN_ENDED; } // Nothing to do while paused, not started or over; and nothing may // run the child while its own code is on the stack (this call came // from inside it) if (!r->rt || r->paused || r->phase == PhaseReadyE || runOnStack(r)) { return stateOf(r); } if (r->stopRequested) { runEnd(r); } else if (r->phase == PhaseEventsE) { // Waiting for events: the pump runs the child's handlers. It is // over when no form is left, it ENDed or an error ended it. if (!basRunHostAlive(r->rt)) { if (r->rt->vm->errorMsg[0]) { runEnd(r); } else { runFinal(r); } } } else { runSlices(r, ms); } return stateOf(r); } // Leave the pause: the step state is set; a paused handler carries on. static void runResume(DevRunT *run) { run->paused = false; run->pausedLine = 0; basRunHostTagBreak(run->rt, &run->breakTitles, false); basVmResume(run->rt->vm); } // Run module-level code (or the final Class_Terminates) for about ms // milliseconds: at least one slice, never more once the time is up. static void runSlices(DevRunT *run, int32_t ms) { PlatformTicksT start = platformClock(); for (;;) { BasVmT *vm = run->rt->vm; if (vm->errorMsg[0]) { runEnd(run); return; } run->busy++; BasVmResultE result = basRunHostSlice(run->rt); run->busy--; if (run->stopRequested) { runEnd(run); return; } if (result == BAS_VM_BREAKPOINT) { devOnBreak(run, vm->currentLine > 0 ? vm->currentLine : 0); return; } if (result == BAS_VM_HALTED) { if (run->phase == PhaseModuleE && basRunHostAlive(run->rt)) { run->phase = PhaseEventsE; } else { runFinal(run); } return; } if (result != BAS_VM_STEP_LIMIT) { runEnd(run); return; } if ((int32_t)PLATFORM_TICKS_TO_MS(platformClock() - start) >= ms) { return; } } } int32_t RunStart(int32_t run, const char *startupForm, int32_t breakOnStart) { DevRunT *r = (DevRunT *)handleOf(run, DevRunE); if (!r || !r->rt || r->phase != PhaseReadyE) { return BAS_FALSE; } if (breakOnStart) { basVmStepInto(r->rt->vm); } // Forms first (their Load code may pause here), then module code in // slices from RunPoll r->phase = PhaseModuleE; basVmSetStepLimit(r->rt->vm, BAS_VM_DEFAULT_STEP_SLICE); r->busy++; basRunHostLoadForms(r->rt, (startupForm && startupForm[0]) ? startupForm : r->startupForm); r->busy--; if (r->stopRequested) { runEnd(r); } return BAS_TRUE; } int32_t RunState(int32_t run) { DevRunT *r = (DevRunT *)handleOf(run, DevRunE); return r ? stateOf(r) : RUN_ENDED; } void RunStop(int32_t run) { DevRunT *r = (DevRunT *)handleOf(run, DevRunE); if (!r || !r->rt) { return; } r->stopRequested = true; r->paused = false; basVmStop(r->rt->vm); basRunHostTagBreak(r->rt, &r->breakTitles, false); if (!runOnStack(r)) { runEnd(r); } } static int32_t stateOf(const DevRunT *run) { if (run->paused) { return RUN_PAUSED; } switch (run->phase) { case PhaseReadyE: return RUN_READY; case PhaseModuleE: case PhaseFinalE: return RUN_RUNNING; case PhaseEventsE: return RUN_EVENTS; case PhaseErrorE: return RUN_ERROR; default: return RUN_ENDED; } } // Finish what had to wait until a child's code left the stack: stopped // runs end, freed runs are released. static void sweepDeferred(void) { for (int32_t i = 0; i < (int32_t)arrlen(sHandles); i++) { DevRunT *run = (sHandles[i].obj && sHandles[i].kind == DevRunE) ? (DevRunT *)sHandles[i].obj : NULL; if (!run || runOnStack(run)) { continue; } if (run->freeRequested) { handleRelease(i); } else if (run->stopRequested && run->rt) { runEnd(run); } } } // cppcheck-suppress constParameterCallback ; a token callback: the context stays a plain void * for callers that keep state static void tokenRow(void *ctx, BasSrcKindE kind, int32_t line, int32_t col, int32_t pos, int32_t len) { const DevTokensT *tokens = (const DevTokensT *)ctx; char row[DEV_TOKEN_ROW_MAX]; int32_t n = snprintf(row, sizeof(row), "%" PRId32 "%c%" PRId32 "%c%" PRId32 "%c%s%c", line, BAS_ROW_CELL_SEP, col, BAS_ROW_CELL_SEP, len, BAS_ROW_CELL_SEP, basSourceKindName(kind), BAS_ROW_CELL_SEP); char end = BAS_ROW_END; basTextAppendLen(&sText, row, n); cellAppend(tokens->src + pos, len); basTextAppendLen(&sText, &end, 1); } static void withEventsRow(void *ctx, const char *name, const char *className) { const char *cells[] = { name, className }; rowAdd(ctx, cells, DVX_ARRAY_LEN(cells)); }