// 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/compact.h" #include "../compiler/lexer.h" #include "../compiler/obfuscate.h" #include "../compiler/parser.h" #include "../compiler/strip.h" #include "../compiler/opcodes.h" #include "../runtime/vm.h" #include "../runtime/values.h" #include "../runtime/serialize.h" #include "../../core/dvxRes.h" #include "../../core/dvxPrefs.h" #include "../../core/dvxTypes.h" #include "../../tools/dvxResWrite.h" #include #include #include #include // ============================================================ // Limits // ============================================================ #define MAX_FILES 64 #define MAX_PATH_LEN 260 #define MAX_NAME 64 // ============================================================ // Prototypes // ============================================================ static void concatGrow(char **buf, int32_t *cap, int32_t need); static char *readFile(const char *path, int32_t *outLen); static const char *extractFormCode(const char *frmText); static void usage(void); // ============================================================ // extractFormCode -- skip past the form layout to the code section // ============================================================ static void concatGrow(char **buf, int32_t *cap, int32_t need) { while (*cap < need) { *cap *= 2; } *buf = (char *)realloc(*buf, *cap); } static const char *extractFormCode(const char *frmText) { if (!frmText) { return NULL; } const char *p = frmText; int32_t depth = 0; bool inForm = false; while (*p) { // Skip leading whitespace while (*p == ' ' || *p == '\t') { p++; } if (strncasecmp(p, "Begin ", 6) == 0) { if (!inForm && strncasecmp(p + 6, "Form ", 5) == 0) { inForm = true; } depth++; } else if (strncasecmp(p, "End", 3) == 0 && (p[3] == '\0' || p[3] == '\r' || p[3] == '\n' || p[3] == ' ')) { depth--; if (depth <= 0 && inForm) { // Skip past this line while (*p && *p != '\n') { p++; } if (*p == '\n') { p++; } return p; } } // Skip to next line while (*p && *p != '\n') { p++; } if (*p == '\n') { p++; } } return NULL; } // ============================================================ // readFile // ============================================================ static char *readFile(const char *path, int32_t *outLen) { FILE *f = fopen(path, "rb"); if (!f) { return NULL; } fseek(f, 0, SEEK_END); long size = ftell(f); fseek(f, 0, SEEK_SET); char *buf = (char *)malloc(size + 1); if (!buf) { fclose(f); return NULL; } int32_t n = (int32_t)fread(buf, 1, size, f); fclose(f); buf[n] = '\0'; if (outLen) { *outLen = n; } return buf; } // ============================================================ // usage // ============================================================ 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"); } // ============================================================ // main // ============================================================ int main(int argc, char **argv) { if (argc < 2) { usage(); return 1; } const char *dbpPath = NULL; const char *outputPath = NULL; bool release = false; for (int 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 PrefsHandleT *prefs = prefsLoad(dbpPath); if (!prefs) { fprintf(stderr, "Error: cannot open project file: %s\n", dbpPath); return 1; } // Derive project directory char projectDir[MAX_PATH_LEN]; snprintf(projectDir, sizeof(projectDir), "%s", dbpPath); char *sep = strrchr(projectDir, '/'); char *sep2 = strrchr(projectDir, '\\'); if (sep2 > sep) { sep = sep2; } if (sep) { *sep = '\0'; } else { projectDir[0] = '.'; projectDir[1] = '\0'; } // Read project metadata const char *projName = prefsGetString(prefs, "Project", "Name", "App"); const char *author = prefsGetString(prefs, "Project", "Author", ""); const char *company = prefsGetString(prefs, "Project", "Company", ""); const char *version = prefsGetString(prefs, "Project", "Version", ""); const char *copyright = prefsGetString(prefs, "Project", "Copyright", ""); const char *description = prefsGetString(prefs, "Project", "Description", ""); const char *iconPath = prefsGetString(prefs, "Project", "Icon", ""); const char *helpFile = prefsGetString(prefs, "Project", "HelpFile", ""); const char *startupForm = prefsGetString(prefs, "Settings", "StartupForm", ""); (void)startupForm; // used implicitly by stub's basFormRtLoadAllForms // Derive output path char outBuf[MAX_PATH_LEN]; 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"); // Collect source files typedef struct { char path[MAX_PATH_LEN]; bool isForm; } SrcFileT; SrcFileT files[MAX_FILES]; int32_t fileCount = 0; // Modules for (int32_t i = 0; i < MAX_FILES; i++) { char key[16]; snprintf(key, sizeof(key), "File%d", (int)i); const char *val = prefsGetString(prefs, "Modules", key, NULL); if (!val) { break; } snprintf(files[fileCount].path, MAX_PATH_LEN, "%s/%s", projectDir, val); files[fileCount].isForm = false; fileCount++; } // Forms for (int32_t i = 0; i < MAX_FILES; i++) { char key[16]; snprintf(key, sizeof(key), "File%d", (int)i); const char *val = prefsGetString(prefs, "Forms", key, NULL); if (!val) { break; } snprintf(files[fileCount].path, MAX_PATH_LEN, "%s/%s", projectDir, val); files[fileCount].isForm = true; fileCount++; } if (fileCount == 0) { fprintf(stderr, "Error: project has no source files.\n"); prefsClose(prefs); return 1; } // Concatenate sources (modules first, then form code) int32_t concatCap = 8192; char *concatBuf = (char *)malloc(concatCap); if (!concatBuf) { fprintf(stderr, "Error: out of memory.\n"); prefsClose(prefs); return 1; } int32_t pos = 0; // Pass 0: .bas modules, Pass 1: .frm code sections for (int32_t pass = 0; pass < 2; pass++) { for (int32_t i = 0; i < fileCount; i++) { if (pass == 0 && files[i].isForm) { continue; } if (pass == 1 && !files[i].isForm) { continue; } int32_t srcLen = 0; char *srcBuf = readFile(files[i].path, &srcLen); if (!srcBuf) { fprintf(stderr, "Error: cannot read %s\n", files[i].path); free(concatBuf); prefsClose(prefs); return 1; } const char *code = srcBuf; if (files[i].isForm) { // Extract form name from "Begin Form " char formName[MAX_NAME] = ""; const char *bp = srcBuf; while (*bp) { while (*bp == ' ' || *bp == '\t') { bp++; } if (strncasecmp(bp, "Begin Form ", 11) == 0) { bp += 11; while (*bp == ' ' || *bp == '\t') { bp++; } int32_t ni = 0; while (*bp && *bp != ' ' && *bp != '\t' && *bp != '\r' && *bp != '\n' && ni < MAX_NAME - 1) { formName[ni++] = *bp++; } formName[ni] = '\0'; break; } while (*bp && *bp != '\n') { bp++; } if (*bp == '\n') { bp++; } } code = extractFormCode(srcBuf); if (!code) { code = ""; } int32_t codeLen = (int32_t)strlen(code); concatGrow(&concatBuf, &concatCap, pos + codeLen + 128); // Inject BEGINFORM directive before form code if (formName[0]) { pos += snprintf(concatBuf + pos, concatCap - pos, "BEGINFORM \"%s\"\n", formName); } memcpy(concatBuf + pos, code, codeLen); pos += codeLen; if (pos > 0 && concatBuf[pos - 1] != '\n') { concatBuf[pos++] = '\n'; } // Inject ENDFORM directive after form code if (formName[0]) { pos += snprintf(concatBuf + pos, concatCap - pos, "ENDFORM\n"); } } else { int32_t codeLen = (int32_t)strlen(code); concatGrow(&concatBuf, &concatCap, pos + codeLen + 2); memcpy(concatBuf + pos, code, codeLen); pos += codeLen; if (pos > 0 && concatBuf[pos - 1] != '\n') { concatBuf[pos++] = '\n'; } } free(srcBuf); } } concatBuf[pos] = '\0'; // Compile printf("Compiling %d file(s)...\n", (int)fileCount); BasParserT *parser = (BasParserT *)malloc(sizeof(BasParserT)); if (!parser) { fprintf(stderr, "Error: out of memory.\n"); free(concatBuf); prefsClose(prefs); return 1; } basParserInit(parser, concatBuf, pos); if (!basParse(parser)) { fprintf(stderr, "Compile error at line %d: %s\n", (int)parser->errorLine, parser->error); basParserFree(parser); free(parser); free(concatBuf); prefsClose(prefs); return 1; } free(concatBuf); BasModuleT *mod = basParserBuildModule(parser); basParserFree(parser); free(parser); if (!mod) { fprintf(stderr, "Error: failed to build module.\n"); prefsClose(prefs); return 1; } printf(" code: %d bytes, %d procs, %d constants\n", (int)mod->codeLen, (int)mod->procCount, (int)mod->constCount); // Strip for release if (release) { basStripModule(mod); printf(" stripped debug info\n"); } // Read all .frm texts up front; they're used for obfuscation and // then embedded as FORM0, FORM1, ... resources below. int32_t frmCount = 0; char *frmData[MAX_FILES]; int32_t frmLens[MAX_FILES]; int32_t frmFileIdx[MAX_FILES]; // index into files[] for this frm for (int32_t i = 0; i < fileCount; i++) { if (!files[i].isForm) { continue; } int32_t flen = 0; char *fdata = readFile(files[i].path, &flen); if (!fdata) { continue; } // Strip comments from the .frm text unconditionally. Comments // are source-only; they shouldn't ship in the embedded resource // for either debug or release builds. int32_t stripCap = flen + 16; uint8_t *stripped = (uint8_t *)malloc(stripCap); if (!stripped) { free(fdata); continue; } int32_t strippedLen = basStripFrmComments(fdata, flen, stripped, stripCap); free(fdata); frmData[frmCount] = (char *)stripped; frmLens[frmCount] = strippedLen; frmFileIdx[frmCount] = i; frmCount++; } // Obfuscate form/control names in release mode BasObfFrmT obfFrms[MAX_FILES]; for (int32_t i = 0; i < MAX_FILES; i++) { obfFrms[i].data = NULL; obfFrms[i].len = 0; } if (release && frmCount > 0) { const char *frmTexts[MAX_FILES]; for (int32_t i = 0; i < frmCount; i++) { frmTexts[i] = frmData[i]; } basObfuscateNames(mod, frmTexts, frmLens, frmCount, obfFrms); printf(" obfuscated %d form(s)\n", (int)frmCount); } // Remove OP_LINE instructions and compact the bytecode. if (release) { int32_t removed = basCompactBytecode(mod); if (removed > 0) { printf(" compacted bytecode (-%d bytes)\n", (int)removed); } } // Serialize module int32_t modLen = 0; uint8_t *modData = basModuleSerialize(mod, &modLen); if (!modData) { fprintf(stderr, "Error: failed to serialize module.\n"); basModuleFree(mod); prefsClose(prefs); return 1; } // Serialize debug info int32_t dbgLen = 0; uint8_t *dbgData = NULL; if (!release) { dbgData = basDebugSerialize(mod, &dbgLen); } basModuleFree(mod); // Find the stub -- look in the compiler's own directory char compilerDir[MAX_PATH_LEN]; snprintf(compilerDir, sizeof(compilerDir), "%s", argv[0]); sep = strrchr(compilerDir, '/'); sep2 = strrchr(compilerDir, '\\'); if (sep2 > sep) { sep = sep2; } if (sep) { *sep = '\0'; } else { compilerDir[0] = '.'; compilerDir[1] = '\0'; } char stubPath[MAX_PATH_LEN]; snprintf(stubPath, sizeof(stubPath), "%s/BASSTUB.APP", compilerDir); int32_t stubLen = 0; char *stubData = readFile(stubPath, &stubLen); if (!stubData) { fprintf(stderr, "Error: cannot find stub at %s\n", stubPath); free(modData); free(dbgData); prefsClose(prefs); return 1; } // Write stub to output file FILE *out = fopen(outputPath, "wb"); if (!out) { fprintf(stderr, "Error: cannot create %s\n", outputPath); free(stubData); free(modData); free(dbgData); prefsClose(prefs); return 1; } fwrite(stubData, 1, stubLen, out); fclose(out); free(stubData); // Attach resources dvxResAppendEntry(outputPath, "name", DVX_RES_TEXT, projName, (uint32_t)strlen(projName) + 1); if (author[0]) { dvxResAppendEntry(outputPath, "author", DVX_RES_TEXT, author, (uint32_t)strlen(author) + 1); } if (company[0]) { dvxResAppendEntry(outputPath, "company", DVX_RES_TEXT, company, (uint32_t)strlen(company) + 1); } if (version[0]) { dvxResAppendEntry(outputPath, "version", DVX_RES_TEXT, version, (uint32_t)strlen(version) + 1); } if (copyright[0]) { dvxResAppendEntry(outputPath, "copyright", DVX_RES_TEXT, copyright, (uint32_t)strlen(copyright) + 1); } if (description[0]) { dvxResAppendEntry(outputPath, "description", DVX_RES_TEXT, description, (uint32_t)strlen(description) + 1); } // Icon if (iconPath[0]) { char iconFullPath[MAX_PATH_LEN]; snprintf(iconFullPath, sizeof(iconFullPath), "%s/%s", projectDir, iconPath); int32_t iconLen = 0; char *iconData = readFile(iconFullPath, &iconLen); if (iconData) { dvxResAppendEntry(outputPath, "icon32", DVX_RES_ICON, iconData, (uint32_t)iconLen); free(iconData); } } // Help file name (file is expected alongside the .app) if (helpFile[0]) { const char *helpBase = helpFile; const char *hs = strrchr(helpBase, '/'); const char *hs2 = strrchr(helpBase, '\\'); if (hs2 > hs) { hs = hs2; } if (hs) { helpBase = hs + 1; } dvxResAppendEntry(outputPath, "helpfile", DVX_RES_TEXT, helpBase, (uint32_t)strlen(helpBase) + 1); // Copy help file to output directory char helpSrc[MAX_PATH_LEN]; snprintf(helpSrc, sizeof(helpSrc), "%s/%s", projectDir, helpFile); char outDir[MAX_PATH_LEN]; snprintf(outDir, sizeof(outDir), "%s", outputPath); char *outSep = strrchr(outDir, '/'); char *outSep2 = strrchr(outDir, '\\'); if (outSep2 > outSep) { outSep = outSep2; } if (outSep) { *outSep = '\0'; } else { outDir[0] = '.'; outDir[1] = '\0'; } char helpDst[MAX_PATH_LEN]; snprintf(helpDst, sizeof(helpDst), "%s/%s", outDir, helpBase); int32_t hLen = 0; char *hData = readFile(helpSrc, &hLen); if (hData) { FILE *hf = fopen(helpDst, "wb"); if (hf) { fwrite(hData, 1, hLen, hf); fclose(hf); } free(hData); } } // MODULE resource dvxResAppendEntry(outputPath, "MODULE", DVX_RES_BINARY, modData, (uint32_t)modLen); free(modData); // DEBUG resource if (dbgData) { dvxResAppendEntry(outputPath, "DEBUG", DVX_RES_BINARY, dbgData, (uint32_t)dbgLen); free(dbgData); } // Form resources -- use obfuscated bytes in release mode, raw otherwise for (int32_t fi = 0; fi < frmCount; fi++) { char resName[16]; snprintf(resName, sizeof(resName), "FORM%d", (int)fi); if (release && obfFrms[fi].data) { dvxResAppendEntry(outputPath, resName, DVX_RES_BINARY, obfFrms[fi].data, (uint32_t)obfFrms[fi].len); } else { dvxResAppendEntry(outputPath, resName, DVX_RES_BINARY, frmData[fi], (uint32_t)frmLens[fi]); } } // Free .frm buffers for (int32_t i = 0; i < frmCount; i++) { free(frmData[i]); free(obfFrms[i].data); } (void)frmFileIdx; // unused (kept in case of future per-file metadata) prefsClose(prefs); printf("Created %s (%d bytes)\n", outputPath, (int)stubLen + (int)modLen); return 0; }