DVX_GUI/src/apps/kpunch/dvxbasic/basdev/basDevBas.c
2026-10-20 17:24:15 -05:00

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));
}