// 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. // bascomp.c -- DVX BASIC command-line compiler // // Compiles a .dbp project into a standalone .app file. // // Usage: BASCOMP project.dbp [-o output.app] [-release] // // The project file and all referenced source files (.bas, .frm) // are loaded relative to the directory containing the .dbp file. // The stub (basstub.app) is read from the same directory as the // compiler executable. #include "../compiler/parser.h" #include "../compiler/symtab.h" #include "../runtime/vm.h" #include "../../../../libs/kpunch/libdvx/dvxPrefs.h" #include "../../../../libs/kpunch/libdvx/dvxTypes.h" #include "../../../../libs/kpunch/libdvx/platform/dvxPlat.h" #include "../basBuild.h" #include "../basRes.h" #include "stb_ds_wrap.h" #include #include #include #include #include // Kernel view of the running image on hosted OSes. #define SELF_EXE_PATH "/proc/self/exe" // Function prototypes (alphabetical) static void buildLog(const char *msg); static const char *selfPath(const char *argv0); static void usage(void); int main(int argc, char **argv); static void buildLog(const char *msg) { printf("%s\n", msg); } // Path of this executable, which carries the embedded STUB and NOICON // resources. On a hosted OS argv[0] is whatever the shell typed (a bare // name when invoked through PATH), so the kernel's view of the running // image is preferred; DJGPP always passes a full path in argv[0]. static const char *selfPath(const char *argv0) { #ifndef __DJGPP__ if (access(SELF_EXE_PATH, R_OK) == 0) { return SELF_EXE_PATH; } #endif return argv0; } static void usage(void) { fprintf(stderr, "DVX BASIC Compiler\n\n"); fprintf(stderr, "Usage: BASCOMP project.dbp [-o output.app] [-release]\n\n"); fprintf(stderr, " project.dbp DVX BASIC project file\n"); fprintf(stderr, " -o output.app Output file (default: project name + .app)\n"); fprintf(stderr, " -release Strip debug information\n"); } int main(int argc, char **argv) { if (argc < 2) { usage(); return 1; } const char *dbpPath = NULL; const char *outputPath = NULL; bool release = false; for (int32_t i = 1; i < argc; i++) { if (strcmp(argv[i], "-o") == 0 && i + 1 < argc) { outputPath = argv[++i]; } else if (strcmp(argv[i], "-release") == 0) { release = true; } else if (argv[i][0] != '-') { dbpPath = argv[i]; } else { fprintf(stderr, "Unknown option: %s\n", argv[i]); usage(); return 1; } } if (!dbpPath) { fprintf(stderr, "Error: no project file specified.\n"); usage(); return 1; } // Load the project file. prefsLoad treats a missing file as an // empty INI, so probe it first or a bad path would surface as a // confusing "no modules" build error. FILE *dbpProbe = fopen(dbpPath, "rb"); if (!dbpProbe) { fprintf(stderr, "Error: cannot open project file: %s\n", dbpPath); return 1; } fclose(dbpProbe); PrefsHandleT *prefs = prefsLoad(dbpPath); if (!prefs) { fprintf(stderr, "Error: cannot open project file: %s\n", dbpPath); return 1; } // Derive project directory char projectDir[DVX_MAX_PATH]; snprintf(projectDir, sizeof(projectDir), "%s", dbpPath); char *sep = platformPathDirEnd(projectDir); if (sep) { *sep = '\0'; } else { projectDir[0] = '.'; projectDir[1] = '\0'; } // Read project metadata const char *projName = prefsGetString(prefs, BAS_INI_SECTION_PROJECT, BAS_INI_KEY_NAME, "App"); const char *author = prefsGetString(prefs, BAS_INI_SECTION_PROJECT, BAS_INI_KEY_AUTHOR, ""); const char *publisher = prefsGetString(prefs, BAS_INI_SECTION_PROJECT, BAS_INI_KEY_PUBLISHER, ""); const char *version = prefsGetString(prefs, BAS_INI_SECTION_PROJECT, BAS_INI_KEY_VERSION, ""); const char *copyright = prefsGetString(prefs, BAS_INI_SECTION_PROJECT, BAS_INI_KEY_COPYRIGHT, ""); const char *description = prefsGetString(prefs, BAS_INI_SECTION_PROJECT, BAS_INI_KEY_DESCRIPTION, ""); const char *iconPath = prefsGetString(prefs, BAS_INI_SECTION_PROJECT, BAS_INI_KEY_ICON, ""); const char *helpFile = prefsGetString(prefs, BAS_INI_SECTION_PROJECT, BAS_INI_KEY_HELPFILE, ""); // Derive output path char outBuf[DVX_MAX_PATH]; if (!outputPath) { snprintf(outBuf, sizeof(outBuf), "%s/%s.app", projectDir, projName); outputPath = outBuf; } printf("Project: %s\n", projName); printf("Output: %s (%s)\n", outputPath, release ? "release" : "debug"); // Concatenate the project's sources (modules first, then form code) BasProjectSourceT project; if (!basBuildConcatProject(prefs, projectDir, &project)) { fprintf(stderr, "Error: %s\n", project.error); basBuildFreeProject(&project); prefsClose(prefs); return 1; } int32_t fileCount = project.fileCount; // Compile printf("Compiling %d file(s)...\n", (int)fileCount); BasParserT *parser = (BasParserT *)malloc(sizeof(BasParserT)); if (!parser) { fprintf(stderr, "Error: out of memory.\n"); goto failProject; } basParserInit(parser, project.source, project.sourceLen); parser->optionExplicit = project.optionExplicit; if (!basParse(parser)) { fprintf(stderr, "Compile error at line %d: %s\n", (int)parser->errorLine, parser->error); basParserFree(parser); free(parser); goto failProject; } BasModuleT *mod = basParserBuildModule(parser); basParserFree(parser); free(parser); if (!mod) { fprintf(stderr, "Error: failed to build module.\n"); goto failProject; } printf(" code: %d bytes, %d procs, %d constants\n", (int)mod->codeLen, (int)mod->procCount, (int)mod->constCount); // Raw .frm texts; the shared build pipeline strips comments and, for // release builds, obfuscates them. char **frmSources = NULL; // stb_ds: owned form text for (int32_t i = 0; i < (int32_t)arrlen(project.frmPaths); i++) { char *fdata = platformReadFile(project.frmPaths[i], NULL); if (fdata) { arrput(frmSources, fdata); } } BasBuildSpecT spec; memset(&spec, 0, sizeof(spec)); spec.projName = projName; spec.author = author; spec.publisher = publisher; spec.version = version; spec.copyright = copyright; spec.description = description; spec.projectDir = projectDir; spec.iconPath = iconPath; spec.helpFile = helpFile; spec.selfPath = selfPath(argv[0]); spec.module = mod; spec.frmSources = (const char *const *)frmSources; spec.frmCount = (int32_t)arrlen(frmSources); spec.release = release; spec.log = buildLog; const char *failure = basBuildApp(outputPath, &spec); for (int32_t i = 0; i < (int32_t)arrlen(frmSources); i++) { free(frmSources[i]); } arrfree(frmSources); basModuleFree(mod); basBuildFreeProject(&project); prefsClose(prefs); if (failure) { fprintf(stderr, "Error: %s\n", failure); return 1; } // Report the true on-disk size of the finished app (stub, bytecode, // debug info, icon, metadata and every FORMn resource). int32_t outBytes = 0; FILE *szf = fopen(outputPath, "rb"); if (szf) { fseek(szf, 0, SEEK_END); long endPos = ftell(szf); if (endPos > 0) { outBytes = (int32_t)endPos; } fclose(szf); } printf("Created %s (%d bytes)\n", outputPath, (int)outBytes); return 0; // ----- Error cleanup path ----- // The caller has already printed an appropriate error message. failProject: basBuildFreeProject(&project); prefsClose(prefs); return 1; }