1378 lines
56 KiB
C
1378 lines
56 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.
|
|
|
|
// testBasDev.c -- the basdev library suite
|
|
//
|
|
// caseBasicIde runs the DOS fixture PARIDE (src/test/dos/fixtures/paride)
|
|
// on the host: a BASIC program that compiles, debugs and builds the
|
|
// project in its kid folder through compiler.bas and debug.bas, with its
|
|
// DECLARE LIBRARY calls made for real. The other cases drive the shared
|
|
// C pieces directly: the build pipeline's compile and source map, the run
|
|
// host, the debugger's view of a paused program and the entry points of
|
|
// compiler.bas.
|
|
|
|
#include "dvxtest.h"
|
|
#include "../../apps/kpunch/dvxbasic/basBuild.h"
|
|
#include "../../apps/kpunch/dvxbasic/basdev/basDebug.h"
|
|
#include "../../apps/kpunch/dvxbasic/basdev/basProject.h"
|
|
#include "../../apps/kpunch/dvxbasic/basdev/basValidate.h"
|
|
#include "../../apps/kpunch/dvxbasic/compiler/lexer.h"
|
|
#include "../../apps/kpunch/dvxbasic/formrt/basRunHost.h"
|
|
#include "../../apps/kpunch/dvxbasic/formrt/formrt.h"
|
|
#include "dvxPlat.h"
|
|
#include "dvxRes.h"
|
|
#include "dvxTypes.h"
|
|
#include "pmModel.h"
|
|
#include "../../libs/kpunch/audio/audioDrv.h"
|
|
#include "../../libs/kpunch/joystick/joystick.h"
|
|
#include "../../libs/kpunch/fileops/fileOps.h"
|
|
#include "taskSwch.h"
|
|
#include "dvxWm.h"
|
|
|
|
#include "thirdparty/stb_ds_wrap.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <strings.h>
|
|
#include <sys/stat.h>
|
|
|
|
#define SCREEN_W 800
|
|
#define SCREEN_H 600
|
|
#define SCREEN_BPP 32
|
|
#define BDT_TEXT_MAX 4096 // captured PRINT text / collected rows
|
|
#define BDT_TICK_MS 60 // the fake clock per Timer round
|
|
#define BDT_MAX_TICKS 200 // rounds before the fixture counts as hung
|
|
#define BDT_BP_FILE_LINE 38 // "total = total + i" in kid/kid.bas
|
|
#define BDT_PASS_TITLE "PARIDE PASS 109 built"
|
|
#define BDT_PARENT_FORM "ParIde"
|
|
#define BDT_KID_FORM "KidForm"
|
|
#define BDT_KID_TITLE "Kid"
|
|
#define BDT_PREFIX "PARIDE "
|
|
#define BDT_RENAME_REFS 70000 // references to rename ...
|
|
#define BDT_RENAME_NEWLEN 31000 // ... to a name this long: a result past INT32_MAX bytes
|
|
#define BDT_RENAME_REF "a.x\n" // one reference
|
|
#define BAS_TRUE (-1)
|
|
#define KIND_PROGRAM 0 // compiler.bas COMP_KIND_PROGRAM
|
|
#define KIND_MODULE 1 // compiler.bas COMP_KIND_MODULE
|
|
#define KIND_FORM 2 // compiler.bas COMP_KIND_FORM
|
|
#define KIND_CONTROL 4 // compiler.bas COMP_KIND_CONTROL
|
|
#define BDT_CTL_KEYS 8 // UserControl header keys (Name, Width ... DefaultEvent)
|
|
#define BDT_RUN_ENDED 4 // debug.bas RUN_ENDED
|
|
#define BDT_RUN_ERROR 5 // debug.bas RUN_ERROR
|
|
#define BDT_DIAL_ICON "BM a dial"
|
|
#define KW_CLASS_TYPE 1 // compiler.bas COMP_KW_TYPE
|
|
#define KW_CLASS_LITERAL 2 // compiler.bas COMP_KW_LITERAL
|
|
#define NO_INDEX (-1)
|
|
#define CONST_MISSING (-999999) // includeConst: no such CONST
|
|
#define DECIMAL 10
|
|
#define HEXADECIMAL 16
|
|
#define PERCENT 100 // sound.bas pitches are percent
|
|
#define BDT_KW_CONST "CONST "
|
|
|
|
// A CONST of an include file and the C value it must equal
|
|
typedef struct {
|
|
const char *file;
|
|
const char *name;
|
|
int32_t value;
|
|
} BdtConstT;
|
|
|
|
// The compiler.bas / debug.bas entry points the C cases call directly
|
|
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 CompDesignControls(int32_t mdl, int32_t src, const char *appDir);
|
|
int32_t CompDesignRelease(void);
|
|
const char *CompError(void);
|
|
const char *CompErrorFile(void);
|
|
int32_t CompErrorFileLine(void);
|
|
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 *ident);
|
|
const char *CompKeyword(int32_t idx);
|
|
int32_t CompKeywordClass(int32_t idx);
|
|
int32_t CompKeywordCount(void);
|
|
int32_t CompKeywordReserved(int32_t idx);
|
|
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 *CompTokens(const char *source);
|
|
const char *CompWithEvents(const char *source);
|
|
const char *DbgExec(int32_t run, const char *line);
|
|
int32_t ProjAddFile(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 *ProjGet(int32_t prj, const char *key);
|
|
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 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);
|
|
int32_t RunCreate(int32_t mdl, int32_t src, const char *appDir, const char *args);
|
|
void RunFree(int32_t run);
|
|
const char *RunOutput(int32_t run);
|
|
int32_t RunPoll(int32_t run, int32_t ms);
|
|
int32_t RunStart(int32_t run, const char *startupForm, int32_t breakOnStart);
|
|
|
|
// Text gathered from print callbacks and row callbacks
|
|
typedef struct {
|
|
char text[BDT_TEXT_MAX];
|
|
int32_t len;
|
|
} BdtTextT;
|
|
|
|
// The UserControl of caseDesignControls (a source set's and a project's):
|
|
// its header (a built control file's CTL0), then its code
|
|
#define BDT_DIAL_HEADER \
|
|
"VERSION DVX 1.00\n" \
|
|
"Begin UserControl Dial\n" \
|
|
" ToolboxIcon = \"dial.bmp\"\n" \
|
|
"End\n"
|
|
|
|
static const char sDialCtl[] =
|
|
BDT_DIAL_HEADER
|
|
"Private Sub UserControl_Paint()\n"
|
|
"End Sub\n";
|
|
|
|
// The child program of designProject: compiler.bas (the first %s), then
|
|
// its own design controls from the project (the second), its module freed
|
|
// while they are in use; then it ends.
|
|
#define BDT_DESIGN_CHILD_FMT \
|
|
"%s\n" \
|
|
"Dim s As Long\n" \
|
|
"Dim m As Long\n" \
|
|
"s = CompSourceCreate()\n" \
|
|
"If CompSourceFromProject(s, \"%s\") Then m = CompCompile(s, 0, 0)\n" \
|
|
"Print m\n" \
|
|
"Print CompDesignControls(m, s, \"\")\n" \
|
|
"CompSourceFree s\n" \
|
|
"CompModuleFree m\n"
|
|
|
|
// Prototypes (alphabetical; main last)
|
|
static void caseBasicIde(void);
|
|
static void caseCDebugger(void);
|
|
static void caseCompileApi(void);
|
|
static void caseDesignControls(void);
|
|
static void caseIncludeConsts(void);
|
|
static void caseProjectApi(void);
|
|
static void caseProjectControls(void);
|
|
static void caseSourceApi(void);
|
|
static bool copyKid(const char *kidDir);
|
|
static void designProject(const BasFormRtT *rt);
|
|
static BasFormT *formOf(const BasFormRtT *rt, const char *formName);
|
|
static const char *formTitle(const BasFormRtT *rt, const char *formName);
|
|
static int32_t includeConst(const char *text, const char *name);
|
|
static bool includeConstIs(const char *text, const char *name, const char *value);
|
|
static const char *includeConstValue(const char *text, const char *name);
|
|
static int32_t keywordIndex(const char *word);
|
|
static BasModuleT *loadProject(const char *dbpPath, BasProjectSourceT *ps, BasValidatorT *validator);
|
|
static bool resHas(DvxResHandleT *res, const char *name, const char *want);
|
|
static void textPrint(void *ctx, const char *text, bool newline);
|
|
static void textRow(void *ctx, const char *const *cells, int32_t cellCount);
|
|
int main(void);
|
|
|
|
|
|
// The DOS fixture on the host: the parent runs as a compiled program would
|
|
// (its forms loaded, then its Timer events), its library calls are real.
|
|
static void caseBasicIde(void) {
|
|
tCase("a BASIC program compiles, debugs and builds another (compiler.bas, debug.bas)");
|
|
tInit(SCREEN_W, SCREEN_H, SCREEN_BPP);
|
|
|
|
char kidDir[DVX_MAX_PATH];
|
|
char carrierDir[DVX_MAX_PATH];
|
|
char kidApp[DVX_MAX_PATH];
|
|
|
|
// The kid project where runDos stages it (TEST\PARIDE\KID beside the
|
|
// app), and the STUB carrier CompBuildApp reads (bascomp has one)
|
|
snprintf(kidDir, sizeof(kidDir), "%s/paride/kid", tTempDir());
|
|
snprintf(kidApp, sizeof(kidApp), "%s/kid.app", tTempDir());
|
|
platformPathDirName(BAS_BUILD_STUB_CARRIER, carrierDir, sizeof(carrierDir));
|
|
platformMkdirRecursive(carrierDir);
|
|
remove(kidApp);
|
|
TEST_TRUE(copyKid(kidDir));
|
|
TEST_TRUE(platformCopyFile(BDT_BASCOMP, BAS_BUILD_STUB_CARRIER));
|
|
|
|
BasProjectSourceT ps;
|
|
BasModuleT *mod = loadProject(BDT_FIXTURE_DIR "/paride.dbp", &ps, NULL);
|
|
|
|
TEST_TRUE(mod != NULL);
|
|
|
|
if (!mod) {
|
|
basBuildFreeProject(&ps);
|
|
tShutdown();
|
|
return;
|
|
}
|
|
|
|
const char *hostError = NULL;
|
|
BasRunSpecT spec;
|
|
|
|
memset(&spec, 0, sizeof(spec));
|
|
spec.appDir = tTempDir();
|
|
spec.title = BDT_PREFIX;
|
|
spec.suppressErrorDialog = true;
|
|
|
|
BasFormRtT *rt = basRunHostCreate(tCtx(), mod, &spec, &hostError);
|
|
|
|
TEST_TRUE(rt != NULL);
|
|
|
|
if (rt) {
|
|
for (int32_t i = 0; i < (int32_t)arrlen(ps.frmTexts); i++) {
|
|
TEST_TRUE(basRunHostRegisterFrm(rt, ps.frmTexts[i], (int32_t)strlen(ps.frmTexts[i])));
|
|
}
|
|
|
|
// ParIde_Load compiles the kid, makes the run and starts it
|
|
basNativeHostSetLive(true);
|
|
basRunHostLoadForms(rt, BDT_PARENT_FORM);
|
|
basVmSetStepLimit(rt->vm, BAS_VM_DEFAULT_STEP_SLICE);
|
|
|
|
while (basRunHostSlice(rt) == BAS_VM_STEP_LIMIT) {
|
|
}
|
|
|
|
// The Timer polls the kid through its stops to the end
|
|
for (int32_t i = 0; i < BDT_MAX_TICKS && strncmp(formTitle(rt, BDT_PARENT_FORM), BDT_PREFIX, strlen(BDT_PREFIX)) != 0; i++) {
|
|
tAdvanceClock(BDT_TICK_MS);
|
|
}
|
|
|
|
TEST_STR_EQ(formTitle(rt, BDT_PARENT_FORM), BDT_PASS_TITLE);
|
|
struct stat st;
|
|
|
|
TEST_TRUE(stat(kidApp, &st) == 0 && st.st_size > 0);
|
|
|
|
// Unloading frees the kid's run, module and source set
|
|
BasFormT *form = formOf(rt, BDT_PARENT_FORM);
|
|
|
|
if (form) {
|
|
basFormRtUnloadForm(rt, form);
|
|
}
|
|
|
|
basNativeHostSetLive(false);
|
|
basRunHostDestroy(rt);
|
|
}
|
|
|
|
basModuleFree(mod);
|
|
basBuildFreeProject(&ps);
|
|
tShutdown();
|
|
}
|
|
|
|
|
|
// The shared C pieces the IDE and the library use: a run with a
|
|
// breakpoint, the paused program's variables, evaluation, assignment,
|
|
// stepping and the [break] captions.
|
|
static void caseCDebugger(void) {
|
|
tCase("C: run host + debugger view of a paused program");
|
|
tInit(SCREEN_W, SCREEN_H, SCREEN_BPP);
|
|
|
|
BasProjectSourceT ps;
|
|
BasValidatorT validator;
|
|
BasModuleT *mod = loadProject(BDT_FIXTURE_DIR "/kid/kid.dbp", &ps, &validator);
|
|
|
|
TEST_TRUE(mod != NULL);
|
|
TEST_EQ(arrlen(validator.badTypes), 0);
|
|
TEST_TRUE(arrlen(validator.entries) > 0);
|
|
basValidateFree(&validator);
|
|
|
|
if (!mod) {
|
|
basBuildFreeProject(&ps);
|
|
tShutdown();
|
|
return;
|
|
}
|
|
|
|
// The breakpoint: kid.bas (the first file) line 38, both ways
|
|
int32_t bp = basBuildMapFileLine(ps.map, ps.map[0].path, BDT_BP_FILE_LINE);
|
|
const char *mapPath = NULL;
|
|
int32_t fileLine = 0;
|
|
|
|
TEST_TRUE(bp > 0);
|
|
TEST_TRUE(basBuildMapLine(ps.map, bp, &mapPath, &fileLine));
|
|
TEST_EQ(fileLine, BDT_BP_FILE_LINE);
|
|
|
|
BdtTextT out;
|
|
BdtTextT rows;
|
|
const char *hostError = NULL;
|
|
BasRunSpecT spec;
|
|
|
|
memset(&out, 0, sizeof(out));
|
|
memset(&spec, 0, sizeof(spec));
|
|
spec.appDir = tTempDir();
|
|
spec.printFn = textPrint;
|
|
spec.printCtx = &out;
|
|
spec.suppressErrorDialog = true;
|
|
|
|
BasFormRtT *rt = basRunHostCreate(tCtx(), mod, &spec, &hostError);
|
|
BasVmT *vm = rt ? rt->vm : NULL;
|
|
BasBreakTitleT *titles = NULL;
|
|
|
|
TEST_TRUE(rt != NULL);
|
|
|
|
if (!rt) {
|
|
basModuleFree(mod);
|
|
basBuildFreeProject(&ps);
|
|
tShutdown();
|
|
return;
|
|
}
|
|
|
|
for (int32_t i = 0; i < (int32_t)arrlen(ps.frmTexts); i++) {
|
|
basRunHostRegisterFrm(rt, ps.frmTexts[i], (int32_t)strlen(ps.frmTexts[i]));
|
|
}
|
|
|
|
basVmSetBreakpoints(vm, &bp, 1);
|
|
basRunHostLoadForms(rt, BDT_KID_FORM);
|
|
basVmSetStepLimit(vm, BAS_VM_DEFAULT_STEP_SLICE);
|
|
|
|
BasVmResultE result;
|
|
|
|
do {
|
|
result = basRunHostSlice(rt);
|
|
} while (result == BAS_VM_STEP_LIMIT);
|
|
|
|
TEST_EQ(result, BAS_VM_BREAKPOINT);
|
|
TEST_EQ(basVmGetCurrentLine(vm), bp);
|
|
TEST_STR_EQ(basDbgProcNameForPc(vm, vm->pc), "AddUp");
|
|
TEST_TRUE(basDbgCurProcIdx(vm) >= 0);
|
|
|
|
// Locals: the parameter, the local and the shared module variables
|
|
memset(&rows, 0, sizeof(rows));
|
|
TEST_TRUE(basDbgLocals(vm, textRow, &rows) >= 4);
|
|
TEST_TRUE(strstr(rows.text, "n|Long|4\n") != NULL);
|
|
TEST_TRUE(strstr(rows.text, "i|Long|1\n") != NULL);
|
|
TEST_TRUE(strstr(rows.text, "total|Long|0\n") != NULL);
|
|
TEST_TRUE(strstr(rows.text, "label|String|\"sum\"\n") != NULL);
|
|
|
|
// Call stack: AddUp at the line, then the module frame
|
|
char expect[BDT_TEXT_MAX];
|
|
|
|
memset(&rows, 0, sizeof(rows));
|
|
TEST_EQ(basDbgCallStack(vm, bp, textRow, &rows), 2);
|
|
snprintf(expect, sizeof(expect), "AddUp|%d\n" BAS_DBG_MODULE_FRAME "|\n", (int)bp);
|
|
TEST_STR_EQ(rows.text, expect);
|
|
|
|
// Watch: a variable path is read directly, an expression is evaluated
|
|
char value[BAS_DBG_VALUE_LEN];
|
|
|
|
TEST_TRUE(basDbgEval(vm, "n", value, sizeof(value)));
|
|
TEST_STR_EQ(value, "4");
|
|
TEST_TRUE(basDbgEval(vm, "total + n * 10", value, sizeof(value)));
|
|
TEST_EQ(atoi(value), 40);
|
|
TEST_TRUE(!basDbgEval(vm, "nosuch(", value, sizeof(value)));
|
|
TEST_TRUE(!basDbgEval(NULL, "n", value, sizeof(value)));
|
|
|
|
// [break] on the program's form captions while paused
|
|
basRunHostTagBreak(rt, &titles, true);
|
|
TEST_STR_EQ(formTitle(rt, BDT_KID_FORM), BDT_KID_TITLE BAS_RUN_BREAK_TAG);
|
|
basRunHostTagBreak(rt, &titles, false);
|
|
TEST_STR_EQ(formTitle(rt, BDT_KID_FORM), BDT_KID_TITLE);
|
|
arrfree(titles);
|
|
|
|
// Immediate: assignment, statement, expression, error; and SetVar
|
|
memset(&out, 0, sizeof(out));
|
|
TEST_TRUE(basDbgExec(vm, "total = 3", textPrint, &out));
|
|
TEST_STR_EQ(out.text, "total = 3\n");
|
|
memset(&out, 0, sizeof(out));
|
|
TEST_TRUE(!basDbgExec(vm, "PRINT total * 2", textPrint, &out));
|
|
TEST_EQ(atoi(out.text), 6);
|
|
memset(&out, 0, sizeof(out));
|
|
TEST_TRUE(!basDbgExec(NULL, "6 * 7", textPrint, &out));
|
|
TEST_EQ(atoi(out.text), 42);
|
|
memset(&out, 0, sizeof(out));
|
|
basDbgExec(vm, "label = ", textPrint, &out);
|
|
TEST_TRUE(strncmp(out.text, "Error: ", strlen("Error: ")) == 0);
|
|
memset(&out, 0, sizeof(out));
|
|
TEST_TRUE(basDbgSetVar(vm, "total", "total + 4", textPrint, &out));
|
|
TEST_STR_EQ(out.text, "total = 7\n");
|
|
memset(&out, 0, sizeof(out));
|
|
TEST_TRUE(!basDbgSetVar(vm, "nosuch", "1", textPrint, &out));
|
|
TEST_TRUE(!basDbgSetVar(vm, "label", "1 +", textPrint, &out));
|
|
|
|
// Step over: the breakpoint line ran once (7 + 1)
|
|
basVmStepOver(vm);
|
|
basVmResume(vm);
|
|
|
|
do {
|
|
result = basRunHostSlice(rt);
|
|
} while (result == BAS_VM_STEP_LIMIT);
|
|
|
|
TEST_EQ(result, BAS_VM_BREAKPOINT);
|
|
TEST_TRUE(basDbgEval(vm, "total", value, sizeof(value)));
|
|
TEST_STR_EQ(value, "8");
|
|
|
|
// Run free to the end: 8 + 2 + 3 + 4
|
|
basVmSetBreakpoints(vm, NULL, 0);
|
|
basVmClearStepState(vm);
|
|
basVmResume(vm);
|
|
memset(&out, 0, sizeof(out));
|
|
|
|
do {
|
|
result = basRunHostSlice(rt);
|
|
} while (result == BAS_VM_STEP_LIMIT);
|
|
|
|
TEST_EQ(result, BAS_VM_HALTED);
|
|
TEST_TRUE(strstr(out.text, "17") != NULL);
|
|
TEST_TRUE(!basRunHostAlive(rt));
|
|
|
|
basRunHostDestroy(rt);
|
|
basModuleFree(mod);
|
|
basBuildFreeProject(&ps);
|
|
tShutdown();
|
|
}
|
|
|
|
|
|
// compiler.bas from C: source sets, error mapping, procedure lists,
|
|
// identifiers, keywords, the validator's unknown types and member typos.
|
|
static void caseCompileApi(void) {
|
|
tCase("compiler.bas entry points: errors, maps, procs, validation");
|
|
tInit(SCREEN_W, SCREEN_H, SCREEN_BPP);
|
|
|
|
// A compile error maps back to its file and line
|
|
int32_t src = CompSourceCreate();
|
|
|
|
TEST_TRUE(src > 0);
|
|
TEST_EQ(CompSourceAdd(src, "first.bas", "DIM a AS LONG\na = 1\n", KIND_MODULE), BAS_TRUE);
|
|
TEST_EQ(CompSourceAdd(src, "second.bas", "DIM b AS LONG\nb = \n", KIND_MODULE), BAS_TRUE);
|
|
TEST_EQ(CompCompile(src, 0, 0), 0);
|
|
TEST_TRUE(CompError()[0] != '\0');
|
|
TEST_TRUE(strncmp(CompError(), "Line", strlen("Line")) != 0);
|
|
TEST_STR_EQ(CompErrorFile(), "second.bas");
|
|
TEST_EQ(CompErrorFileLine(), 2);
|
|
TEST_TRUE(strstr(CompSourceFiles(src), "first.bas\t1\t") != NULL);
|
|
CompSourceFree(src);
|
|
|
|
// A form whose control type no widget library registers
|
|
src = CompSourceCreate();
|
|
TEST_EQ(CompSourceAdd(src, "f.frm", "Begin Form F\n Begin Bogus b1\n End\nEnd\n", -1), BAS_TRUE);
|
|
TEST_EQ(CompCompile(src, 0, BAS_TRUE), 0);
|
|
TEST_TRUE(strstr(CompError(), "type 'Bogus' is not registered") != NULL);
|
|
CompSourceFree(src);
|
|
|
|
// A member typo on a known control fails at compile time when
|
|
// validating, and compiles without validation
|
|
const char *frm = "Begin Form F\n Begin Label lbl\n End\nEnd\n\nSUB F_Load\n lbl.Captoin = \"x\"\nEND SUB\n";
|
|
|
|
src = CompSourceCreate();
|
|
TEST_EQ(CompSourceAdd(src, "f.frm", frm, KIND_FORM), BAS_TRUE);
|
|
TEST_EQ(CompCompile(src, 0, BAS_TRUE), 0);
|
|
TEST_STR_EQ(CompErrorFile(), "f.frm");
|
|
TEST_EQ(CompErrorFileLine(), 7);
|
|
|
|
int32_t mdl = CompCompile(src, 0, 0);
|
|
|
|
TEST_TRUE(mdl > 0);
|
|
TEST_STR_EQ(CompProcs(mdl), "F_Load\tSub\tF\n");
|
|
CompModuleFree(mdl);
|
|
CompSourceFree(src);
|
|
|
|
// Procedure blocks of source text, identifiers, keywords
|
|
TEST_STR_EQ(CompProcList("x = 1\nSUB A\nEND SUB\nPrivate Function B(n)\n B = n\nEnd Function\n"), "Sub\tA\t2\t3\nFunction\tB\t4\t6\n");
|
|
TEST_EQ(CompIsIdent("abc_1"), BAS_TRUE);
|
|
TEST_EQ(CompIsIdent("1abc"), 0);
|
|
TEST_TRUE(CompKeywordCount() > 0);
|
|
TEST_TRUE(CompKeyword(0)[0] != '\0');
|
|
TEST_STR_EQ(CompKeyword(-1), "");
|
|
|
|
// The Immediate window without a program
|
|
TEST_EQ(atoi(DbgExec(0, "6 * 7")), 42);
|
|
tShutdown();
|
|
}
|
|
|
|
|
|
// compiler.bas for a form designer: a source set's UserControls made
|
|
// control types of the calling program alone (CompDesignControls), their
|
|
// module kept past CompModuleFree until they go (CompDesignRelease), a
|
|
// second call replacing the first, another program's module refused; a
|
|
// control project's (designProject); the header keys (ProjCtlKeys$).
|
|
static void caseDesignControls(void) {
|
|
tCase("compiler.bas designer: CompDesignControls, CompDesignRelease, CompBuildApp of a control project, ProjCtlKeys$");
|
|
tInit(SCREEN_W, SCREEN_H, SCREEN_BPP);
|
|
|
|
static const char host[] = "Dim x As Integer\n";
|
|
|
|
TEST_STR_EQ(ProjCtlKeys(), "Name\tName\nWidth\tInteger\nHeight\tInteger\nCanGetFocus\tBoolean\nBackColor\tColor\nToolboxIcon\tText\nDescription\tText\nDefaultEvent\tText\n");
|
|
|
|
char dir[DVX_MAX_PATH];
|
|
char want[DVX_MAX_PATH];
|
|
char path[DVX_MAX_PATH];
|
|
|
|
snprintf(dir, sizeof(dir), "%s/dsgnctl", tTempDir());
|
|
snprintf(want, sizeof(want), "%s/dial.bmp", dir);
|
|
|
|
int32_t src = CompSourceCreate();
|
|
|
|
TEST_EQ(CompSourceAdd(src, "dial.ctl", sDialCtl, KIND_CONTROL), BAS_TRUE);
|
|
|
|
int32_t mdl = CompCompile(src, 0, 0);
|
|
|
|
TEST_TRUE(mdl > 0);
|
|
|
|
// Outside a program there is no designer to register for
|
|
TEST_EQ(CompDesignControls(mdl, src, dir), 0);
|
|
TEST_TRUE(CompError()[0] != '\0');
|
|
TEST_EQ(CompDesignRelease(), BAS_TRUE);
|
|
|
|
// The calling program: a runtime made current
|
|
BasCompileErrorT err;
|
|
BasModuleT *hostMod = basBuildCompile(host, (int32_t)strlen(host), false, NULL, NULL, &err);
|
|
const char *hostError = NULL;
|
|
BasRunSpecT spec;
|
|
|
|
memset(&spec, 0, sizeof(spec));
|
|
spec.appDir = tTempDir();
|
|
spec.title = BDT_PARENT_FORM;
|
|
spec.suppressErrorDialog = true;
|
|
|
|
BasFormRtT *rt = hostMod ? basRunHostCreate(tCtx(), hostMod, &spec, &hostError) : NULL;
|
|
BasFormRtT *prev = basFormRtSetCurrent(rt);
|
|
|
|
TEST_TRUE(rt != NULL);
|
|
TEST_EQ(CompDesignControls(mdl, src, dir), 1);
|
|
TEST_STR_EQ(CompError(), "");
|
|
|
|
const WgtIfaceT *iface = basUcOwnIface(rt, "Dial");
|
|
|
|
TEST_TRUE(iface != NULL);
|
|
TEST_PTR_EQ(basUcOwnIface(NULL, "Dial"), NULL);
|
|
TEST_TRUE(basUcIconPath(iface, path, sizeof(path)));
|
|
TEST_STR_EQ(path, want);
|
|
|
|
// Freed while its controls are in use: kept, and no new use of it
|
|
CompModuleFree(mdl);
|
|
TEST_TRUE(basUcOwnIface(rt, "Dial") != NULL);
|
|
TEST_EQ(CompDesignControls(mdl, src, dir), 0);
|
|
TEST_TRUE(basUcOwnIface(rt, "Dial") != NULL);
|
|
TEST_EQ(CompDesignRelease(), BAS_TRUE);
|
|
TEST_PTR_EQ(basUcOwnIface(rt, "Dial"), NULL);
|
|
TEST_EQ(CompDesignControls(mdl, src, dir), 0);
|
|
|
|
// Another module's controls, App.Path the caller's, then replaced
|
|
int32_t next = CompCompile(src, 0, 0);
|
|
|
|
snprintf(want, sizeof(want), "%s/dial.bmp", tTempDir());
|
|
TEST_EQ(CompDesignControls(next, src, ""), 1);
|
|
TEST_TRUE(basUcIconPath(basUcOwnIface(rt, "Dial"), path, sizeof(path)));
|
|
TEST_STR_EQ(path, want);
|
|
TEST_EQ(CompDesignControls(next, src, dir), 1);
|
|
TEST_TRUE(basUcOwnIface(rt, "Dial") != NULL);
|
|
TEST_EQ(CompDesignRelease(), BAS_TRUE);
|
|
CompModuleFree(next);
|
|
|
|
// Another program's module is refused: it would go when that one ends
|
|
int32_t *ownApp = dvxMemAppIdPtr;
|
|
int32_t otherApp = (ownApp ? *ownApp : 0) + 1;
|
|
|
|
dvxMemAppIdPtr = &otherApp;
|
|
|
|
int32_t foreign = CompCompile(src, 0, 0);
|
|
|
|
dvxMemAppIdPtr = ownApp;
|
|
TEST_TRUE(foreign > 0);
|
|
TEST_EQ(CompDesignControls(foreign, src, dir), 0);
|
|
TEST_PTR_EQ(basUcOwnIface(rt, "Dial"), NULL);
|
|
CompModuleFree(foreign);
|
|
CompSourceFree(src);
|
|
|
|
if (rt) {
|
|
designProject(rt);
|
|
}
|
|
|
|
basFormRtSetCurrent(prev);
|
|
|
|
if (rt) {
|
|
basRunHostDestroy(rt);
|
|
}
|
|
|
|
basModuleFree(hostMod);
|
|
tShutdown();
|
|
}
|
|
|
|
|
|
// The CONSTs of the include files that mirror C values: one source of
|
|
// truth, checked here (a C value changed without its CONST fails).
|
|
static void caseIncludeConsts(void) {
|
|
static const BdtConstT consts[] = {
|
|
{ "resource.bas", "RES_NAME_MAX", DVX_RES_NAME_MAX },
|
|
{ "system.bas", "MOUSE_WHEEL_STEP_MIN", MOUSE_WHEEL_STEP_MIN },
|
|
{ "system.bas", "MOUSE_WHEEL_STEP_MAX", MOUSE_WHEEL_STEP_MAX },
|
|
{ "system.bas", "MOUSE_WHEEL_STEP_DEFAULT", MOUSE_WHEEL_STEP_DEFAULT },
|
|
{ "system.bas", "MOUSE_DBLCLICK_DEFAULT_MS", MOUSE_DBLCLICK_DEFAULT_MS },
|
|
{ "system.bas", "MOUSE_SPEED_DEFAULT", MOUSE_SPEED_DEFAULT },
|
|
{ "system.bas", "SYS_VIDEO_DEFAULT_WIDTH", DVX_DEFAULT_VIDEO_W },
|
|
{ "system.bas", "SYS_VIDEO_DEFAULT_HEIGHT", DVX_DEFAULT_VIDEO_H },
|
|
{ "system.bas", "SYS_VIDEO_DEFAULT_BPP", DVX_DEFAULT_VIDEO_BPP },
|
|
{ "shell.bas", "SHELL_VIEW_LARGE", PmViewLargeE },
|
|
{ "shell.bas", "SHELL_VIEW_SMALL", PmViewSmallE },
|
|
{ "shell.bas", "SHELL_VIEW_LIST", PmViewListE },
|
|
{ "shell.bas", "SHELL_PRIORITY_LOW", TS_PRIORITY_LOW },
|
|
{ "shell.bas", "SHELL_PRIORITY_NORMAL", TS_PRIORITY_NORMAL },
|
|
{ "shell.bas", "SHELL_PRIORITY_HIGH", TS_PRIORITY_HIGH },
|
|
{ "fileops.bas", "FILEOPS_ATTR_READONLY", FILEOPS_ATTR_READONLY },
|
|
{ "fileops.bas", "FILEOPS_ATTR_HIDDEN", FILEOPS_ATTR_HIDDEN },
|
|
{ "fileops.bas", "FILEOPS_ATTR_SYSTEM", FILEOPS_ATTR_SYSTEM },
|
|
{ "fileops.bas", "FILEOPS_ATTR_ARCHIVE", FILEOPS_ATTR_ARCHIVE },
|
|
{ "fileops.bas", "FILEOPS_ATTR_EDITABLE", FILEOPS_ATTR_EDITABLE },
|
|
{ "fileops.bas", "FILEOPS_DRIVE_FIXED", PlatformDriveFixedE },
|
|
{ "fileops.bas", "FILEOPS_DRIVE_REMOVABLE", PlatformDriveRemovableE },
|
|
{ "fileops.bas", "FILEOPS_DRIVE_REMOTE", PlatformDriveRemoteE },
|
|
{ "fileops.bas", "FILEOPS_SORT_NAME", FileOpsSortNameE },
|
|
{ "fileops.bas", "FILEOPS_SORT_SIZE", FileOpsSortSizeE },
|
|
{ "fileops.bas", "FILEOPS_SORT_TYPE", FileOpsSortTypeE },
|
|
{ "fileops.bas", "FILEOPS_SORT_DATE", FileOpsSortDateE },
|
|
{ "fileops.bas", "FILEOPS_COPY", FileOpsCopyE },
|
|
{ "fileops.bas", "FILEOPS_MOVE", FileOpsMoveE },
|
|
{ "fileops.bas", "FILEOPS_DELETE", FileOpsDeleteE },
|
|
{ "fileops.bas", "FILEOPS_STEP_BUSY", FileOpsStepBusyE },
|
|
{ "fileops.bas", "FILEOPS_STEP_ASK", FileOpsStepAskE },
|
|
{ "fileops.bas", "FILEOPS_STEP_ERROR", FileOpsStepErrorE },
|
|
{ "fileops.bas", "FILEOPS_STEP_DONE", FileOpsStepDoneE },
|
|
{ "fileops.bas", "FILEOPS_STEP_CANCELLED", FileOpsStepCancelledE },
|
|
{ "fileops.bas", "FILEOPS_ANSWER_YES", FileOpsAnswerYesE },
|
|
{ "fileops.bas", "FILEOPS_ANSWER_NO", FileOpsAnswerNoE },
|
|
{ "fileops.bas", "FILEOPS_ANSWER_ALL", FileOpsAnswerAllE },
|
|
{ "fileops.bas", "FILEOPS_ANSWER_CANCEL", FileOpsAnswerCancelE },
|
|
{ "fileops.bas", "FILEOPS_ANSWER_RETRY", FileOpsAnswerRetryE },
|
|
{ "fileops.bas", "FILEOPS_ANSWER_SKIP", FileOpsAnswerSkipE },
|
|
{ "compiler.bas", "COMP_KW_OTHER", BAS_KW_CLASS_OTHER },
|
|
{ "compiler.bas", "COMP_KW_TYPE", BAS_KW_CLASS_TYPE },
|
|
{ "compiler.bas", "COMP_KW_LITERAL", BAS_KW_CLASS_LITERAL },
|
|
{ "compiler.bas", "COMP_KIND_MODULE", BAS_BUILD_WRAP_MODULE },
|
|
{ "compiler.bas", "COMP_KIND_FORM", BAS_BUILD_WRAP_FORM },
|
|
{ "compiler.bas", "COMP_KIND_CLASS", BAS_BUILD_WRAP_CLASS },
|
|
{ "compiler.bas", "COMP_KIND_CONTROL", BAS_BUILD_WRAP_CONTROL },
|
|
{ "sound.bas", "SOUND_EVENT_DEFAULT", SoundDefaultE },
|
|
{ "sound.bas", "SOUND_EVENT_START", SoundStartE },
|
|
{ "sound.bas", "SOUND_EVENT_EXIT", SoundExitE },
|
|
{ "sound.bas", "SOUND_EVENT_ERROR", SoundErrorE },
|
|
{ "sound.bas", "SOUND_EVENT_WARNING", SoundWarningE },
|
|
{ "sound.bas", "SOUND_EVENT_QUESTION", SoundQuestionE },
|
|
{ "sound.bas", "SOUND_EVENT_INFO", SoundInfoE },
|
|
{ "sound.bas", "SOUND_EVENT_WINDOW_OPEN", SoundWindowOpenE },
|
|
{ "sound.bas", "SOUND_EVENT_WINDOW_CLOSE", SoundWindowCloseE },
|
|
{ "sound.bas", "SOUND_PITCH_NORMAL", AUDIO_PITCH_NORMAL * PERCENT / AUDIO_PITCH_NORMAL },
|
|
{ "sound.bas", "SOUND_PITCH_MIN", AUDIO_PITCH_MIN * PERCENT / AUDIO_PITCH_NORMAL },
|
|
{ "sound.bas", "SOUND_PITCH_MAX", AUDIO_PITCH_MAX * PERCENT / AUDIO_PITCH_NORMAL },
|
|
{ "sound.bas", "SOUND_RATE_MIN", AUDIO_RATE_MIN },
|
|
{ "sound.bas", "SOUND_RATE_MAX", AUDIO_RATE_MAX },
|
|
{ "sound.bas", "SOUND_RATE_DEFAULT", AUDIO_RATE_DEFAULT },
|
|
{ "sound.bas", "SOUND_STREAM_MS_MIN", AUDIO_STREAM_MS_MIN },
|
|
{ "sound.bas", "SOUND_STREAM_MS_DEFAULT", AUDIO_STREAM_MS_DEFAULT },
|
|
{ "sound.bas", "SOUND_STREAM_MS_MAX", AUDIO_STREAM_MS_MAX },
|
|
{ "sound.bas", "FM_VOICES_MAX", AUDIO_FM_VOICES_MAX },
|
|
{ "sound.bas", "FM_CHIP_HZ", AUDIO_FM_CHIP_HZ },
|
|
{ "sound.bas", "FM_FNUM_MAX", AUDIO_FM_FNUM_MAX },
|
|
{ "sound.bas", "FM_BLOCK_MAX", AUDIO_FM_BLOCK_MAX },
|
|
{ "sound.bas", "FM_FNUM_HI_SHIFT", AUDIO_FM_FNUM_HI_SHIFT },
|
|
{ "sound.bas", "FM_BLOCK_SHIFT", AUDIO_FM_BLOCK_SHIFT },
|
|
{ "sound.bas", "FM_MOD_CHAR", AUDIO_FM_MOD_CHAR },
|
|
{ "sound.bas", "FM_CAR_CHAR", AUDIO_FM_CAR_CHAR },
|
|
{ "sound.bas", "FM_MOD_LEVEL", AUDIO_FM_MOD_LEVEL },
|
|
{ "sound.bas", "FM_CAR_LEVEL", AUDIO_FM_CAR_LEVEL },
|
|
{ "sound.bas", "FM_MOD_ATTACK", AUDIO_FM_MOD_ATTACK },
|
|
{ "sound.bas", "FM_CAR_ATTACK", AUDIO_FM_CAR_ATTACK },
|
|
{ "sound.bas", "FM_MOD_SUSTAIN", AUDIO_FM_MOD_SUSTAIN },
|
|
{ "sound.bas", "FM_CAR_SUSTAIN", AUDIO_FM_CAR_SUSTAIN },
|
|
{ "sound.bas", "FM_FNUM_LOW", AUDIO_FM_FNUM_LOW },
|
|
{ "sound.bas", "FM_KEY_BLOCK", AUDIO_FM_KEY_BLOCK },
|
|
{ "sound.bas", "FM_FEEDBACK", AUDIO_FM_FEEDBACK },
|
|
{ "sound.bas", "FM_MOD_WAVE", AUDIO_FM_MOD_WAVE },
|
|
{ "sound.bas", "FM_CAR_WAVE", AUDIO_FM_CAR_WAVE },
|
|
{ "sound.bas", "FM_KEY_ON", AUDIO_FM_KEY_ON },
|
|
{ "sound.bas", "FM_OUT_LEFT", AUDIO_FM_OUT_LEFT },
|
|
{ "sound.bas", "FM_OUT_RIGHT", AUDIO_FM_OUT_RIGHT },
|
|
{ "sound.bas", "FM_OUT_BOTH", AUDIO_FM_OUT_BOTH },
|
|
{ "sound.bas", "FM_ADDITIVE", AUDIO_FM_ADDITIVE },
|
|
{ "sound.bas", "FM_BANK_REGS", AUDIO_FM_BANK_REGS },
|
|
{ "joystick.bas", "JOY_CAL_MIN", JOY_CAL_MIN },
|
|
{ "joystick.bas", "JOY_CAL_CENTER", JOY_CAL_CENTER },
|
|
{ "joystick.bas", "JOY_CAL_MAX", JOY_CAL_MAX },
|
|
{ "joystick.bas", "JOY_DEADZONE_DEFAULT", JOY_DEADZONE_DEFAULT },
|
|
{ "joystick.bas", "JOY_DEADZONE_MAX", JOY_DEADZONE_MAX },
|
|
{ "joystick.bas", "JOY_POLL_HZ_DEFAULT", JOY_POLL_HZ_DEFAULT },
|
|
{ "joystick.bas", "JOY_POLL_HZ_MIN", JOY_POLL_HZ_MIN },
|
|
{ "joystick.bas", "JOY_POLL_HZ_MAX", JOY_POLL_HZ_MAX },
|
|
{ "joystick.bas", "JOY_HAT_CENTERED", JOY_HAT_CENTERED },
|
|
{ "joystick.bas", "JOY_AXIS_MIN", JOY_AXIS_MIN },
|
|
{ "joystick.bas", "JOY_AXIS_MAX", JOY_AXIS_MAX },
|
|
{ "joystick.bas", "JOY_CHOICE_GAMEPORT", JoyChoiceGamePortE },
|
|
{ "joystick.bas", "JOY_CHOICE_FALLBACK", JoyChoiceFallbackE }
|
|
};
|
|
|
|
tCase("include-file CONSTs equal their C values");
|
|
|
|
for (int32_t i = 0; i < DVX_ARRAY_LEN(consts); i++) {
|
|
char path[DVX_MAX_PATH];
|
|
int32_t len = 0;
|
|
|
|
snprintf(path, sizeof(path), BDT_INCLUDE_DIR "/%s", consts[i].file);
|
|
|
|
char *text = platformReadFile(path, &len);
|
|
|
|
TEST_TRUE(text != NULL);
|
|
|
|
if (text) {
|
|
TEST_EQ(includeConst(text, consts[i].name), consts[i].value);
|
|
free(text);
|
|
}
|
|
}
|
|
|
|
// String CONSTs
|
|
int32_t resLen = 0;
|
|
char *resBas = platformReadFile(BDT_INCLUDE_DIR "/resource.bas", &resLen);
|
|
|
|
TEST_TRUE(resBas != NULL);
|
|
|
|
if (resBas) {
|
|
TEST_TRUE(includeConstIs(resBas, "RES_NAME_ICON", DVX_RES_NAME_ICON));
|
|
TEST_TRUE(includeConstIs(resBas, "RES_NAME_WIDGET_ICON", DVX_RES_NAME_WIDGET_ICON));
|
|
free(resBas);
|
|
}
|
|
}
|
|
|
|
|
|
// The .dbp model from BASIC: a new project with files and settings,
|
|
// saved, opened again, a file renamed and removed.
|
|
static void caseProjectApi(void) {
|
|
tCase("compiler.bas projects: ProjNew, ProjSave, ProjOpen, files, settings");
|
|
tInit(SCREEN_W, SCREEN_H, SCREEN_BPP);
|
|
|
|
char dir[DVX_MAX_PATH];
|
|
char dbp[DVX_MAX_PATH];
|
|
|
|
snprintf(dir, sizeof(dir), "%s/projapi", tTempDir());
|
|
snprintf(dbp, sizeof(dbp), "%s/demo.dbp", dir);
|
|
platformMkdirRecursive(dir);
|
|
remove(dbp);
|
|
|
|
int32_t prj = ProjNew("demo", dir);
|
|
|
|
TEST_TRUE(prj > 0);
|
|
TEST_EQ(ProjAddFile(prj, "main.bas"), 0);
|
|
TEST_EQ(ProjAddFile(prj, "form1.frm"), 1);
|
|
TEST_EQ(ProjAddFile(prj, "MAIN.BAS"), NO_INDEX);
|
|
TEST_EQ(ProjFileKind(prj, 1), KIND_FORM);
|
|
TEST_EQ(ProjFileKind(prj, 0), KIND_MODULE);
|
|
TEST_EQ(ProjFileKind(prj, 2), NO_INDEX);
|
|
TEST_EQ(ProjSet(prj, "Author", "Somebody"), BAS_TRUE);
|
|
TEST_EQ(ProjSet(prj, "optionexplicit", " -1"), BAS_TRUE);
|
|
TEST_EQ(ProjSet(prj, "StartupForm", "Form1"), BAS_TRUE);
|
|
TEST_EQ(ProjSet(prj, "Bogus", "x"), 0);
|
|
TEST_EQ(ProjSave(prj), BAS_TRUE);
|
|
ProjClose(prj);
|
|
|
|
TEST_EQ(ProjOpen("no-such-project.dbp"), 0);
|
|
prj = ProjOpen(dbp);
|
|
TEST_TRUE(prj > 0);
|
|
TEST_STR_EQ(ProjGet(prj, "author"), "Somebody");
|
|
TEST_STR_EQ(ProjGet(prj, "Name"), "demo");
|
|
TEST_STR_EQ(ProjGet(prj, "OptionExplicit"), "true");
|
|
TEST_STR_EQ(ProjGet(prj, "MultiInstance"), "false");
|
|
TEST_STR_EQ(ProjGet(prj, "StartupForm"), "Form1");
|
|
TEST_STR_EQ(ProjGet(prj, "Bogus"), "");
|
|
TEST_STR_EQ(ProjPath(prj), dbp);
|
|
TEST_EQ(ProjFileCount(prj), 2);
|
|
TEST_STR_EQ(ProjFile(prj, 0), "main.bas");
|
|
TEST_EQ(ProjFileFind(prj, "FORM1.FRM"), 1);
|
|
TEST_TRUE(strstr(ProjFilePath(prj, 1), "projapi") != NULL);
|
|
|
|
// A rename keeps the kind's extension and saves the project at once
|
|
TEST_EQ(ProjRenameFile(prj, 0, "x.frm"), 0);
|
|
TEST_TRUE(CompError()[0] != '\0');
|
|
TEST_EQ(ProjRenameFile(prj, 0, "app"), BAS_TRUE);
|
|
TEST_STR_EQ(ProjFile(prj, 0), "app.bas");
|
|
TEST_EQ(ProjRemoveFile(prj, 1), BAS_TRUE);
|
|
TEST_EQ(ProjRemoveFile(prj, 1), 0);
|
|
TEST_EQ(ProjFileCount(prj), 1);
|
|
ProjClose(prj);
|
|
|
|
prj = ProjOpen(dbp);
|
|
TEST_EQ(ProjFileFind(prj, "app.bas"), 0);
|
|
ProjClose(prj);
|
|
TEST_EQ(ProjFileCount(prj), 0);
|
|
tShutdown();
|
|
}
|
|
|
|
|
|
// A control project and a UserControl's header: [Project] Type = Control
|
|
// and the [Controls] section written, read and kept by renames; the
|
|
// header keys read with their defaults and set key by key, the code after
|
|
// the header untouched (compiler.bas and the C model).
|
|
static void caseProjectControls(void) {
|
|
tCase("compiler.bas projects: control projects, [Controls], UserControl headers");
|
|
tInit(SCREEN_W, SCREEN_H, SCREEN_BPP);
|
|
|
|
static const char ctl[] =
|
|
"VERSION DVX 1.00\n"
|
|
"Begin UserControl Gauge\n"
|
|
" Width = 120\n"
|
|
" CanGetFocus = False\n"
|
|
"End\n"
|
|
"Option Explicit\n"
|
|
"Private Sub UserControl_Paint()\n"
|
|
"End Sub\n";
|
|
static const char ctlCode[] = "Option Explicit\nPrivate Sub UserControl_Paint()\nEnd Sub\n";
|
|
|
|
// The header keys and their defaults
|
|
TEST_STR_EQ(ProjCtlHeader(ctl, "name"), "Gauge");
|
|
TEST_STR_EQ(ProjCtlHeader(ctl, "width"), "120");
|
|
TEST_STR_EQ(ProjCtlHeader(ctl, "Height"), "64");
|
|
TEST_STR_EQ(ProjCtlHeader(ctl, "CanGetFocus"), "False");
|
|
TEST_STR_EQ(ProjCtlHeader(ctl, "BackColor"), "");
|
|
TEST_STR_EQ(ProjCtlHeader(ctl, "Description"), "");
|
|
TEST_STR_EQ(ProjCtlHeader(ctl, "Bogus"), "");
|
|
TEST_STR_EQ(ProjCtlHeader("x = 1\n", "Width"), "");
|
|
TEST_TRUE(prjCtlKeyAt(0) != NULL);
|
|
TEST_STR_EQ(prjCtlKeyAt(0)->key, BAS_PROP_NAME);
|
|
TEST_STR_EQ(prjCtlKeyAt(1)->key, BAS_UC_KEY_WIDTH);
|
|
TEST_STR_EQ(prjCtlKeyAt(BDT_CTL_KEYS - 1)->key, BAS_UC_KEY_DEFAULTEVENT);
|
|
TEST_TRUE(prjCtlKeyAt(BDT_CTL_KEYS) == NULL);
|
|
TEST_TRUE(prjCtlKeyAt(-1) == NULL);
|
|
TEST_EQ(prjCtlKeyFind("backcolor")->kind, PrjCtlColorE);
|
|
TEST_EQ(prjCtlKeyFind("CANGETFOCUS")->kind, PrjCtlBoolE);
|
|
TEST_EQ(prjCtlKeyFind("ToolboxIcon")->kind, PrjCtlTextE);
|
|
TEST_EQ(prjCtlKeyFind("NAME")->kind, PrjCtlNameE);
|
|
TEST_TRUE(prjCtlKeyFind("Caption") == NULL);
|
|
|
|
// A key's line changed in place, added before End, removed
|
|
TEST_STR_EQ(ProjCtlSetHeader(ctl, "WIDTH", " 200 "), "VERSION DVX 1.00\nBegin UserControl Gauge\n Width = 200\n CanGetFocus = False\nEnd\nOption Explicit\nPrivate Sub UserControl_Paint()\nEnd Sub\n");
|
|
TEST_STR_EQ(CompError(), "");
|
|
TEST_STR_EQ(ProjCtlSetHeader(ctl, "CanGetFocus", "-1"), "VERSION DVX 1.00\nBegin UserControl Gauge\n Width = 120\n CanGetFocus = True\nEnd\nOption Explicit\nPrivate Sub UserControl_Paint()\nEnd Sub\n");
|
|
TEST_STR_EQ(ProjCtlSetHeader(ctl, "BackColor", "&hff0000"), "VERSION DVX 1.00\nBegin UserControl Gauge\n Width = 120\n CanGetFocus = False\n BackColor = &H00FF0000&\nEnd\nOption Explicit\nPrivate Sub UserControl_Paint()\nEnd Sub\n");
|
|
TEST_STR_EQ(ProjCtlSetHeader(ctl, "Description", "A gauge"), "VERSION DVX 1.00\nBegin UserControl Gauge\n Width = 120\n CanGetFocus = False\n Description = \"A gauge\"\nEnd\nOption Explicit\nPrivate Sub UserControl_Paint()\nEnd Sub\n");
|
|
TEST_STR_EQ(ProjCtlSetHeader(ctl, "CanGetFocus", ""), "VERSION DVX 1.00\nBegin UserControl Gauge\n Width = 120\nEnd\nOption Explicit\nPrivate Sub UserControl_Paint()\nEnd Sub\n");
|
|
TEST_STR_EQ(ProjCtlSetHeader(ctl, "Height", ""), ctl);
|
|
TEST_STR_EQ(ProjCtlSetHeader(ctl, "Name", "Meter"), "VERSION DVX 1.00\nBegin UserControl Meter\n Width = 120\n CanGetFocus = False\nEnd\nOption Explicit\nPrivate Sub UserControl_Paint()\nEnd Sub\n");
|
|
|
|
// Read back as set (a decimal color too), the code kept
|
|
char colored[sizeof(ctl) * 2];
|
|
|
|
snprintf(colored, sizeof(colored), "%s", ProjCtlSetHeader(ctl, "BackColor", "255"));
|
|
TEST_STR_EQ(ProjCtlHeader(colored, "BackColor"), "&H000000FF&");
|
|
TEST_TRUE(strstr(colored, ctlCode) != NULL);
|
|
TEST_STR_EQ(ProjCtlHeader(ProjCtlSetHeader(ctl, "DefaultEvent", "Changed"), "DefaultEvent"), "Changed");
|
|
|
|
// Values a key does not take
|
|
TEST_STR_EQ(ProjCtlSetHeader(ctl, "Width", "wide"), "");
|
|
TEST_TRUE(CompError()[0] != '\0');
|
|
TEST_STR_EQ(ProjCtlSetHeader(ctl, "Width", "-5"), "");
|
|
TEST_STR_EQ(ProjCtlSetHeader(ctl, "BackColor", "&HXYZ"), "");
|
|
TEST_STR_EQ(ProjCtlSetHeader(ctl, "Description", "say \"hi\""), "");
|
|
TEST_STR_EQ(ProjCtlSetHeader(ctl, "Description", "two\nlines"), "");
|
|
TEST_STR_EQ(ProjCtlSetHeader(ctl, "Bogus", "1"), "");
|
|
TEST_STR_EQ(ProjCtlSetHeader(ctl, "Name", ""), "");
|
|
TEST_STR_EQ(ProjCtlSetHeader(ctl, "Name", "2Bad"), "");
|
|
TEST_STR_EQ(ProjCtlSetHeader(ctl, "Name", "Two Words"), "");
|
|
TEST_STR_EQ(ProjCtlSetHeader(ctlCode, "Width", "1"), "");
|
|
|
|
// A new control's text, and it compiles
|
|
static const char fresh[] = "VERSION DVX 1.00\nBegin UserControl Dial\n Width = 64\n Height = 64\nEnd\n\nOption Explicit\n\nPrivate Sub UserControl_Paint ()\n\nEnd Sub\n";
|
|
|
|
TEST_STR_EQ(ProjCtlNew("Dial"), fresh);
|
|
TEST_STR_EQ(CompError(), "");
|
|
TEST_STR_EQ(ProjCtlNew("9Dial"), "");
|
|
TEST_TRUE(CompError()[0] != '\0');
|
|
TEST_STR_EQ(ProjCtlNew(""), "");
|
|
|
|
int32_t src = CompSourceCreate();
|
|
|
|
TEST_EQ(CompSourceAdd(src, "dial.ctl", fresh, KIND_CONTROL), BAS_TRUE);
|
|
|
|
int32_t mdl = CompCompile(src, BAS_TRUE, 0);
|
|
|
|
TEST_TRUE(mdl > 0);
|
|
CompModuleFree(mdl);
|
|
CompSourceFree(src);
|
|
|
|
// A control project: Type and [Controls] saved and read back
|
|
char dir[DVX_MAX_PATH];
|
|
char dbp[DVX_MAX_PATH];
|
|
|
|
snprintf(dir, sizeof(dir), "%s/projctl", tTempDir());
|
|
snprintf(dbp, sizeof(dbp), "%s/gauge.dbp", dir);
|
|
platformMkdirRecursive(dir);
|
|
remove(dbp);
|
|
|
|
int32_t prj = ProjNew("gauge", dir);
|
|
|
|
TEST_STR_EQ(ProjGet(prj, "Type"), "");
|
|
TEST_EQ(ProjSet(prj, "type", BAS_PROJECT_TYPE_CONTROL), BAS_TRUE);
|
|
TEST_EQ(ProjAddFile(prj, "gauge.ctl"), 0);
|
|
TEST_EQ(ProjAddFile(prj, "util.bas"), 1);
|
|
TEST_EQ(ProjFileKind(prj, 0), KIND_CONTROL);
|
|
TEST_EQ(ProjSave(prj), BAS_TRUE);
|
|
ProjClose(prj);
|
|
|
|
int32_t len = 0;
|
|
char *text = platformReadFile(dbp, &len);
|
|
|
|
TEST_TRUE(text != NULL);
|
|
TEST_TRUE(text && strstr(text, "[" BAS_INI_SECTION_CONTROLS "]") != NULL);
|
|
TEST_TRUE(text && strstr(text, "gauge.ctl") != NULL);
|
|
TEST_TRUE(text && strstr(text, BAS_INI_KEY_TYPE) != NULL);
|
|
free(text);
|
|
|
|
PrjStateT model;
|
|
|
|
prjInit(&model);
|
|
TEST_TRUE(prjLoad(&model, dbp));
|
|
TEST_TRUE(prjIsControlProject(&model));
|
|
TEST_EQ(prjFileCount(&model), 2);
|
|
TEST_EQ(model.files[prjFileIndexByPath(&model, "gauge.ctl")].kind, PrjKindControlE);
|
|
TEST_EQ(model.files[0].kind, PrjKindModuleE);
|
|
TEST_EQ(prjKindForPath("X.CTL"), PrjKindControlE);
|
|
TEST_EQ(prjKindWrap(PrjKindControlE), BAS_BUILD_WRAP_CONTROL);
|
|
TEST_EQ(prjFieldSet(&model, BAS_INI_KEY_TYPE, ""), true);
|
|
TEST_TRUE(!prjIsControlProject(&model));
|
|
prjClose(&model);
|
|
|
|
// Modules load before controls (the compile order)
|
|
prj = ProjOpen(dbp);
|
|
TEST_STR_EQ(ProjGet(prj, "Type"), BAS_PROJECT_TYPE_CONTROL);
|
|
TEST_EQ(ProjFileFind(prj, "GAUGE.CTL"), 1);
|
|
TEST_EQ(ProjFileKind(prj, 1), KIND_CONTROL);
|
|
TEST_EQ(ProjRenameFile(prj, 1, "meter.bas"), 0);
|
|
TEST_EQ(ProjRenameFile(prj, 1, "meter"), BAS_TRUE);
|
|
TEST_STR_EQ(ProjFile(prj, 1), "meter.ctl");
|
|
ProjClose(prj);
|
|
|
|
prj = ProjOpen(dbp);
|
|
TEST_EQ(ProjFileKind(prj, ProjFileFind(prj, "meter.ctl")), KIND_CONTROL);
|
|
TEST_EQ(ProjRemoveFile(prj, 1), BAS_TRUE);
|
|
TEST_EQ(ProjSave(prj), BAS_TRUE);
|
|
ProjClose(prj);
|
|
|
|
prj = ProjOpen(dbp);
|
|
TEST_EQ(ProjFileCount(prj), 1);
|
|
TEST_EQ(ProjFileKind(prj, 0), KIND_MODULE);
|
|
ProjClose(prj);
|
|
|
|
// The validator: a project's own control type is known, its members
|
|
// are left to the run time (a registered one may be an older build)
|
|
BasValidatorT v;
|
|
|
|
memset(&v, 0, sizeof(v));
|
|
basValidateAddType(&v, "Gauge");
|
|
TEST_TRUE(basValidateIsPropValid(&v, "gauge", "Needle"));
|
|
TEST_TRUE(basValidateIsMethodValid(&v, "Gauge", "Spin"));
|
|
TEST_TRUE(!basValidateIsPropValid(&v, "CommandButton", "Needle"));
|
|
TEST_TRUE(!basValidateIsMethodValid(NULL, "CommandButton", "Spin"));
|
|
basValidateFree(&v);
|
|
tShutdown();
|
|
}
|
|
|
|
|
|
// Source text as the compiler reads it: tokens, code positions, renaming
|
|
// in code, WithEvents variables, class Events, help topics, keyword
|
|
// classes.
|
|
static void caseSourceApi(void) {
|
|
tCase("compiler.bas source text: tokens, in-code, rename, events, help topics, keyword flags");
|
|
|
|
TEST_STR_EQ(CompTokens("x = \"a\tb\" ' note\nIF y THEN\n"), "1\t1\t1\tIdentifier\tx\n1\t3\t1\tSymbol\t=\n1\t5\t5\tString\t\"a b\"\n1\t11\t6\tComment\t' note\n2\t1\t2\tKeyword\tIF\n2\t4\t1\tIdentifier\ty\n2\t6\t4\tKeyword\tTHEN\n");
|
|
TEST_STR_EQ(CompTokens("REM all of it\n"), "1\t1\t13\tComment\tREM all of it\n");
|
|
TEST_STR_EQ(CompTokens("s = \"open\n"), "1\t1\t1\tIdentifier\ts\n1\t3\t1\tSymbol\t=\n1\t5\t5\tError\t\"open\n");
|
|
|
|
const char *code = "a = \"Cmd1.x\" ' Cmd1.y\nCmd1.z = 1\ns = \"abc\n";
|
|
|
|
TEST_EQ(CompInCode(code, 1), BAS_TRUE);
|
|
TEST_EQ(CompInCode(code, 4), BAS_TRUE);
|
|
TEST_EQ(CompInCode(code, 6), 0);
|
|
TEST_EQ(CompInCode(code, 16), 0);
|
|
TEST_EQ(CompInCode(code, 23), BAS_TRUE);
|
|
TEST_EQ(CompInCode(code, 39), 0);
|
|
TEST_EQ(CompInCode(code, 0), 0);
|
|
|
|
const char *frm = "Command1.Caption = \"Command1.x\"\nSub Command1_Click\n' Command1.y\nMyCommand1.z = 1\n";
|
|
|
|
TEST_STR_EQ(CompRenameInCode(frm, "command1", "Go", BAS_TRUE), "Go.Caption = \"Command1.x\"\nSub Go_Click\n' Command1.y\nMyCommand1.z = 1\n");
|
|
TEST_STR_EQ(CompRenameInCode(frm, "Command1", "Go", 0), "Go.Caption = \"Go.x\"\nSub Go_Click\n' Go.y\nMyCommand1.z = 1\n");
|
|
TEST_STR_EQ(CompRenameInCode(frm, "Label1", "Go", BAS_TRUE), frm);
|
|
|
|
// A result too long for an int32_t length leaves the text unchanged
|
|
// (its size overflowed int32_t before)
|
|
size_t refLen = strlen(BDT_RENAME_REF);
|
|
char *many = (char *)malloc(BDT_RENAME_REFS * refLen + 1);
|
|
char *newName = (char *)malloc(BDT_RENAME_NEWLEN + 1);
|
|
|
|
if (many && newName) {
|
|
for (int32_t i = 0; i < BDT_RENAME_REFS; i++) {
|
|
memcpy(many + (size_t)i * refLen, BDT_RENAME_REF, refLen);
|
|
}
|
|
|
|
many[BDT_RENAME_REFS * refLen] = '\0';
|
|
memset(newName, 'b', BDT_RENAME_NEWLEN);
|
|
newName[BDT_RENAME_NEWLEN] = '\0';
|
|
TEST_STR_EQ(CompRenameInCode(many, "a", newName, 0), many);
|
|
}
|
|
|
|
free(many);
|
|
free(newName);
|
|
|
|
TEST_STR_EQ(CompWithEvents("Dim WithEvents a As Foo, b As Long\nPrivate WithEvents c As Bar\nx = 1 ' Dim WithEvents d As Baz\n"), "a\tFoo\nc\tBar\n");
|
|
TEST_STR_EQ(CompClassEvents("Public Event Changed(Value As Integer)\nEvent Done\nx = \"Event Nope\"\n"), "Changed\tValue As Integer\nDone\t\n");
|
|
|
|
TEST_STR_EQ(CompHelpTopic("withevents"), "lang.events");
|
|
TEST_STR_EQ(CompHelpTopic("NoSuchWord"), "");
|
|
TEST_STR_EQ(CompHelpCtrlTopic("CommandButton"), "ctrl.commandbutton");
|
|
|
|
TEST_EQ(CompKeywordClass(keywordIndex("INTEGER")), KW_CLASS_TYPE);
|
|
TEST_EQ(CompKeywordReserved(keywordIndex("INTEGER")), BAS_TRUE);
|
|
TEST_EQ(CompKeywordClass(keywordIndex("BYTE")), KW_CLASS_TYPE);
|
|
TEST_EQ(CompKeywordReserved(keywordIndex("BYTE")), 0);
|
|
TEST_EQ(CompKeywordClass(keywordIndex("TRUE")), KW_CLASS_LITERAL);
|
|
TEST_EQ(CompKeywordClass(NO_INDEX), NO_INDEX);
|
|
TEST_EQ(CompKeywordReserved(CompKeywordCount()), 0);
|
|
}
|
|
|
|
|
|
// Copy the fixture's kid project into kidDir.
|
|
static bool copyKid(const char *kidDir) {
|
|
static const char *const files[] = { "kid.dbp", "kid.bas", "kidform.frm" };
|
|
bool ok = platformMkdirRecursive(kidDir) == 0;
|
|
|
|
for (int32_t i = 0; ok && i < DVX_ARRAY_LEN(files); i++) {
|
|
char from[DVX_MAX_PATH];
|
|
char to[DVX_MAX_PATH];
|
|
|
|
snprintf(from, sizeof(from), BDT_FIXTURE_DIR "/kid/%s", files[i]);
|
|
snprintf(to, sizeof(to), "%s/%s", kidDir, files[i]);
|
|
ok = platformCopyFile(from, to);
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
|
|
// caseDesignControls with program rt current: a control project's set
|
|
// (App.Path its folder; built into a control file by CompBuildApp) and a
|
|
// child program's design controls, which go when it ends along with its
|
|
// hold on their module, so that handle is free again.
|
|
static void designProject(const BasFormRtT *rt) {
|
|
char dir[DVX_MAX_PATH];
|
|
char dbp[DVX_MAX_PATH];
|
|
char ctlPath[DVX_MAX_PATH];
|
|
char iconPath[DVX_MAX_PATH];
|
|
char wgt[DVX_MAX_PATH];
|
|
char path[DVX_MAX_PATH];
|
|
char ctl0[BAS_RES_FORM_NAME_LEN];
|
|
|
|
snprintf(dir, sizeof(dir), "%s/dsgnprj", tTempDir());
|
|
snprintf(dbp, sizeof(dbp), "%s/dial.dbp", dir);
|
|
snprintf(ctlPath, sizeof(ctlPath), "%s/dial.ctl", dir);
|
|
snprintf(iconPath, sizeof(iconPath), "%s/dial.bmp", dir);
|
|
snprintf(wgt, sizeof(wgt), "%s/dial.wgt", tTempDir());
|
|
snprintf(ctl0, sizeof(ctl0), BAS_RES_CTL_FMT, (long)0);
|
|
platformMkdirRecursive(dir);
|
|
remove(dbp);
|
|
remove(wgt);
|
|
TEST_TRUE(tWriteFile(ctlPath, sDialCtl, (int32_t)strlen(sDialCtl)));
|
|
TEST_TRUE(tWriteFile(iconPath, BDT_DIAL_ICON, (int32_t)sizeof(BDT_DIAL_ICON)));
|
|
|
|
int32_t prj = ProjNew("dial", dir);
|
|
|
|
TEST_EQ(ProjSet(prj, "Type", BAS_PROJECT_TYPE_CONTROL), BAS_TRUE);
|
|
TEST_EQ(ProjAddFile(prj, "dial.ctl"), 0);
|
|
TEST_EQ(ProjSave(prj), BAS_TRUE);
|
|
ProjClose(prj);
|
|
|
|
// A project's set: App.Path (and the ToolboxIcon) its folder
|
|
int32_t src = CompSourceCreate();
|
|
|
|
TEST_EQ(CompSourceFromProject(src, dbp), BAS_TRUE);
|
|
|
|
int32_t mdl = CompCompile(src, 0, 0);
|
|
|
|
TEST_EQ(CompDesignControls(mdl, src, ""), 1);
|
|
TEST_TRUE(basUcIconPath(basUcOwnIface(rt, "Dial"), path, sizeof(path)));
|
|
TEST_STR_EQ(path, iconPath);
|
|
TEST_EQ(CompDesignRelease(), BAS_TRUE);
|
|
|
|
// A control project builds a control file: the module, the header
|
|
// text, the toolbox picture and name (no Description: the name)
|
|
TEST_EQ(CompBuildApp(mdl, src, wgt, 0), BAS_TRUE);
|
|
CompModuleFree(mdl);
|
|
CompSourceFree(src);
|
|
|
|
DvxResHandleT *res = dvxResOpen(wgt);
|
|
|
|
TEST_TRUE(res != NULL);
|
|
TEST_TRUE(resHas(res, BAS_RES_MODULE, NULL));
|
|
TEST_TRUE(resHas(res, ctl0, BDT_DIAL_HEADER));
|
|
TEST_TRUE(resHas(res, BAS_CTL_RES_ICON, BDT_DIAL_ICON));
|
|
TEST_TRUE(resHas(res, BAS_CTL_RES_NAME, "Dial"));
|
|
|
|
if (res) {
|
|
dvxResClose(res);
|
|
}
|
|
|
|
// A child program's own design controls
|
|
int32_t len = 0;
|
|
char *inc = platformReadFile(BDT_INCLUDE_DIR "/compiler.bas", &len);
|
|
size_t size = (size_t)len + sizeof(BDT_DESIGN_CHILD_FMT) + strlen(dbp);
|
|
char *text = inc ? (char *)malloc(size) : NULL;
|
|
int32_t kidSrc = CompSourceCreate();
|
|
|
|
TEST_TRUE(text != NULL);
|
|
|
|
if (text) {
|
|
snprintf(text, size, BDT_DESIGN_CHILD_FMT, inc, dbp);
|
|
TEST_EQ(CompSourceAdd(kidSrc, "child.bas", text, KIND_PROGRAM), BAS_TRUE);
|
|
}
|
|
|
|
int32_t kidMdl = CompCompile(kidSrc, 0, 0);
|
|
int32_t run = RunCreate(kidMdl, kidSrc, "", "");
|
|
int32_t state = BDT_RUN_ERROR;
|
|
|
|
basNativeHostSetLive(true);
|
|
TEST_EQ(RunStart(run, "", 0), BAS_TRUE);
|
|
|
|
for (int32_t i = 0; i < BDT_MAX_TICKS && (state = RunPoll(run, BDT_TICK_MS)) != BDT_RUN_ENDED && state != BDT_RUN_ERROR; i++) {
|
|
}
|
|
|
|
basNativeHostSetLive(false);
|
|
TEST_EQ(state, BDT_RUN_ENDED);
|
|
|
|
char *end = NULL;
|
|
int32_t freed = (int32_t)strtol(RunOutput(run), &end, DECIMAL);
|
|
int32_t designed = (int32_t)strtol(end, NULL, DECIMAL);
|
|
|
|
TEST_EQ(designed, 1);
|
|
|
|
// Its module went with it: handles are given out lowest free first,
|
|
// so at most "freed" new ones reach its number
|
|
int32_t *probes = NULL;
|
|
bool reused = false;
|
|
|
|
for (int32_t i = 0; i < freed && !reused; i++) {
|
|
int32_t h = CompSourceCreate();
|
|
|
|
arrput(probes, h);
|
|
reused = (h == freed);
|
|
}
|
|
|
|
TEST_TRUE(reused);
|
|
|
|
for (int32_t i = 0; i < (int32_t)arrlen(probes); i++) {
|
|
CompSourceFree(probes[i]);
|
|
}
|
|
|
|
arrfree(probes);
|
|
RunFree(run);
|
|
CompModuleFree(kidMdl);
|
|
CompSourceFree(kidSrc);
|
|
free(text);
|
|
free(inc);
|
|
}
|
|
|
|
|
|
static BasFormT *formOf(const BasFormRtT *rt, const char *formName) {
|
|
for (int32_t i = 0; i < (int32_t)arrlen(rt->forms); i++) {
|
|
if (strcasecmp(rt->forms[i]->name, formName) == 0) {
|
|
return rt->forms[i];
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
// The caption of a program's form window ("" when it has none).
|
|
static const char *formTitle(const BasFormRtT *rt, const char *formName) {
|
|
const BasFormT *form = formOf(rt, formName);
|
|
|
|
return (form && form->window) ? form->window->title : "";
|
|
}
|
|
|
|
|
|
// The value of "CONST name = value" in BASIC source text, CONST_MISSING
|
|
// when it has none.
|
|
static int32_t includeConst(const char *text, const char *name) {
|
|
const char *p = includeConstValue(text, name);
|
|
|
|
if (!p) {
|
|
return CONST_MISSING;
|
|
}
|
|
|
|
// &H20 is hexadecimal, as in BASIC
|
|
if (p[0] == '&' && (p[1] == 'H' || p[1] == 'h')) {
|
|
return (int32_t)strtol(p + 2, NULL, HEXADECIMAL);
|
|
}
|
|
|
|
return (int32_t)strtol(p, NULL, DECIMAL);
|
|
}
|
|
|
|
|
|
// Whether "CONST name = "value"" in BASIC source text is that string.
|
|
static bool includeConstIs(const char *text, const char *name, const char *value) {
|
|
const char *p = includeConstValue(text, name);
|
|
size_t len = strlen(value);
|
|
|
|
return p && p[0] == '"' && strncmp(p + 1, value, len) == 0 && p[len + 1] == '"';
|
|
}
|
|
|
|
|
|
// Where the value of "CONST name = value" starts in BASIC source text,
|
|
// NULL when it has none.
|
|
static const char *includeConstValue(const char *text, const char *name) {
|
|
size_t nameLen = strlen(name);
|
|
|
|
for (const char *line = text; *line; ) {
|
|
const char *p = dvxSkipWs(line);
|
|
const char *end = strchr(line, '\n');
|
|
|
|
if (strncasecmp(p, BDT_KW_CONST, strlen(BDT_KW_CONST)) == 0) {
|
|
p = dvxSkipWs(p + strlen(BDT_KW_CONST));
|
|
|
|
if (strncasecmp(p, name, nameLen) == 0 && !basIsIdentChar(p[nameLen])) {
|
|
p = dvxSkipWs(p + nameLen);
|
|
|
|
if (*p == '=') {
|
|
return dvxSkipWs(p + 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
line = end ? end + 1 : line + strlen(line);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
// The index of keyword word in CompKeyword$'s list, -1 when it is not one.
|
|
static int32_t keywordIndex(const char *word) {
|
|
for (int32_t i = 0; i < CompKeywordCount(); i++) {
|
|
if (strcmp(CompKeyword(i), word) == 0) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return NO_INDEX;
|
|
}
|
|
|
|
|
|
// Read and compile a project as bascomp does (validator: also scan its
|
|
// forms and check Control.Member names). NULL when it does not compile.
|
|
static BasModuleT *loadProject(const char *dbpPath, BasProjectSourceT *ps, BasValidatorT *validator) {
|
|
PrefsHandleT *prefs = NULL;
|
|
char dir[DVX_MAX_PATH];
|
|
BasCompileErrorT err;
|
|
BasCtrlValidatorT cb;
|
|
|
|
memset(ps, 0, sizeof(*ps));
|
|
|
|
if (validator) {
|
|
memset(validator, 0, sizeof(*validator));
|
|
basValidateCallbacks(validator, &cb);
|
|
}
|
|
|
|
if (!basBuildOpenProject(dbpPath, &prefs, dir, sizeof(dir))) {
|
|
return NULL;
|
|
}
|
|
|
|
bool ok = basBuildConcatProject(prefs, dir, ps);
|
|
BasModuleT *mod = NULL;
|
|
|
|
prefsClose(prefs);
|
|
|
|
if (!ok) {
|
|
return NULL;
|
|
}
|
|
|
|
for (int32_t i = 0; validator && i < (int32_t)arrlen(ps->frmTexts); i++) {
|
|
basValidateScanFrm(validator, ps->frmTexts[i], (int32_t)strlen(ps->frmTexts[i]), NULL, NULL);
|
|
}
|
|
|
|
mod = basBuildCompile(ps->source, ps->sourceLen, ps->optionExplicit, validator ? &cb : NULL, validator ? &validator->compiler : NULL, &err);
|
|
|
|
if (!mod) {
|
|
printf(" compile: %s\n", err.message);
|
|
}
|
|
|
|
return mod;
|
|
}
|
|
|
|
|
|
// True when res has resource name and, unless want is NULL, it is the
|
|
// text want (with its NUL, as the build writes text).
|
|
static bool resHas(DvxResHandleT *res, const char *name, const char *want) {
|
|
uint32_t size = 0;
|
|
char *data = res ? (char *)dvxResRead(res, name, &size) : NULL;
|
|
bool same = data && (!want || (size == strlen(want) + 1 && memcmp(data, want, size) == 0));
|
|
|
|
free(data);
|
|
return same;
|
|
}
|
|
|
|
|
|
static void textPrint(void *ctx, const char *text, bool newline) {
|
|
BdtTextT *t = (BdtTextT *)ctx;
|
|
|
|
if (text) {
|
|
t->len = basDbgAppendDropping(t->text, BDT_TEXT_MAX, t->len, text, (int32_t)strlen(text));
|
|
}
|
|
|
|
if (newline) {
|
|
t->len = basDbgAppendDropping(t->text, BDT_TEXT_MAX, t->len, "\n", 1);
|
|
}
|
|
}
|
|
|
|
|
|
// A debugger row as "cell|cell|cell\n".
|
|
static void textRow(void *ctx, const char *const *cells, int32_t cellCount) {
|
|
BdtTextT *t = (BdtTextT *)ctx;
|
|
|
|
for (int32_t i = 0; i < cellCount; i++) {
|
|
t->len = basDbgAppendDropping(t->text, BDT_TEXT_MAX, t->len, i > 0 ? "|" : "", i > 0 ? 1 : 0);
|
|
t->len = basDbgAppendDropping(t->text, BDT_TEXT_MAX, t->len, cells[i], (int32_t)strlen(cells[i]));
|
|
}
|
|
|
|
t->len = basDbgAppendDropping(t->text, BDT_TEXT_MAX, t->len, "\n", 1);
|
|
}
|
|
|
|
|
|
int main(void) {
|
|
setvbuf(stdout, NULL, _IOLBF, 0);
|
|
tSuiteBegin("basdev");
|
|
tProcessInit(DVXTEST_WIDGET_DIR);
|
|
|
|
caseCompileApi();
|
|
caseDesignControls();
|
|
caseIncludeConsts();
|
|
caseSourceApi();
|
|
caseProjectApi();
|
|
caseProjectControls();
|
|
caseCDebugger();
|
|
caseBasicIde();
|
|
|
|
return tSuiteEnd();
|
|
}
|