2046 lines
60 KiB
C
2046 lines
60 KiB
C
// 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 <inttypes.h>
|
|
#include <dlfcn.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
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));
|
|
}
|