// 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. // basBuild.h -- shared "compile a project into an .app" back end // // Both the DVX BASIC IDE (ideMain.c, Make Executable) and the standalone // command-line compiler (bascomp.c) turn a compiled module plus a set of // .frm texts into a finished .app. The whole back half of that job -- // release stripping, form/control name obfuscation, bytecode compaction, // serialization, stub extraction, resource emission, help-file copy and // the "noicon" fallback -- lives here so both front ends produce // byte-identical artifacts. // // Callers own every buffer they pass in via BasBuildSpecT; the module is // never modified (release builds work on a private copy). #ifndef BAS_BUILD_H #define BAS_BUILD_H #include "compiler/compiler.h" #include "runtime/vm.h" #include "dvxPrefs.h" #include "dvxTypes.h" #include #include #ifdef __cplusplus extern "C" { #endif // Progress callback (may be NULL); receives one line per pipeline stage. typedef void (*BasBuildLogFnT)(const char *msg); typedef struct { // Project metadata (any NULL/empty is skipped; projName falls back // to a generic label). const char *projName; const char *author; const char *publisher; const char *version; const char *copyright; const char *description; // Project directory; iconPath and helpFile are relative to it. const char *projectDir; const char *iconPath; // NULL/empty: embed the NOICON fallback const char *helpFile; // NULL/empty: no help file // A file carrying the STUB and NOICON resources (bascomp: itself); // NULL or "" means BAS_BUILD_STUB_CARRIER. const char *selfPath; // Compiled module. Never modified. const BasModuleT *module; // Raw .frm texts (NUL-terminated), one per form. Comments are // stripped here; release builds additionally obfuscate them. const char *const *frmSources; int32_t frmCount; // .ctl header texts (the "Begin UserControl ... End" block of each, as // basBuildAddSource keeps them), one per UserControl: CTLn resources. const char *const *ctlSources; int32_t ctlCount; bool release; bool multiInstance; // [Settings] MultiInstance: more than one copy may run BasBuildLogFnT log; // may be NULL } BasBuildSpecT; // Where a build inside DVX finds the STUB and NOICON resources: the // basdev library carries them (it is installed wherever programs can be // compiled), relative to the DVX folder like every installed path. #define BAS_BUILD_STUB_CARRIER "libs" DVX_PATH_SEP "kpunch" DVX_PATH_SEP "basdev" DVX_PATH_SEP "basdev.lib" // Build outPath from spec. Returns NULL on success, otherwise a static // error message. On failure outPath may be left partially written. const char *basBuildApp(const char *outPath, const BasBuildSpecT *spec); // Longest error text basBuildConcatProject reports (a path plus a reason). #define BAS_BUILD_ERROR_LEN (DVX_MAX_PATH + 64) // One source file's span in the concatenated project source: lines // [startLine, startLine + lineCount) of the concatenation are lines // [firstFileLine, firstFileLine + lineCount) of path. typedef struct { char path[DVX_MAX_PATH]; int32_t startLine; int32_t lineCount; int32_t firstFileLine; // 1 for a module; the first code line of a .frm } BasSourceMapT; // A project's BASIC source as the compiler consumes it: every [Modules] // file in order wrapped in BEGINMODULE "name" / ENDMODULE, then the code // of every [Classes] file wrapped in BEGINCLASS "name" / ENDCLASS, then // the code of every [Controls] file wrapped in BEGINCLASS "name" // USERCONTROL / ENDCLASS, then the code section of every [Forms] file // wrapped in BEGINFORM "name" / ENDFORM, each file ending on a newline. // frmPaths and frmTexts list the .frm files (path and full text, as read) // in the same order for the resource step, ctlTexts the .ctl headers; // map locates each file's lines. typedef struct { char *source; // NUL-terminated, malloc'd int32_t sourceLen; int32_t sourceCap; int32_t lineCount; // newlines in source (next text starts on lineCount + 1) char **frmPaths; // stb_ds array of malloc'd paths char **frmTexts; // stb_ds array of malloc'd .frm texts char **ctlTexts; // stb_ds array of malloc'd .ctl header texts (Begin UserControl ... End) BasSourceMapT *map; // stb_ds array, one entry per file int32_t fileCount; // modules + classes + controls + forms bool optionExplicit; // [Settings] OptionExplicit char error[BAS_BUILD_ERROR_LEN]; } BasProjectSourceT; // What basBuildAppendFile wraps a file's text in. typedef enum { BAS_BUILD_WRAP_NONE, // the text as it is (a lone program) BAS_BUILD_WRAP_MODULE, // a project's module: BEGINMODULE "name" / ENDMODULE (its Private CONST / ENUM stay in it) BAS_BUILD_WRAP_FORM, // a form's code: BEGINFORM "name" / ENDFORM BAS_BUILD_WRAP_CLASS, // a class module's code: BEGINCLASS "name" / ENDCLASS BAS_BUILD_WRAP_CONTROL // a UserControl's code: BEGINCLASS "name" USERCONTROL / ENDCLASS } BasBuildWrapE; // Appends one file's text to *ps (a zeroed struct is a valid empty // project) and makes sure it ends on a newline, wrapped as wrap says in // the directives naming name; a form's BEGINFORM line also lists its // controls, ctrlList (basFrmControlList's text; NULL or "" for none), // and a UserControl's BEGINCLASS line its USERCONTROL mark (ctrlList is // not used). // textLine and textLines (either may be // NULL) receive the 1-based line the text starts on and the number of // lines it occupies, wrapper lines excluded -- what an IDE source map // needs. Returns false with ps->error set on failure. bool basBuildAppendFile(BasProjectSourceT *ps, const char *text, int32_t len, BasBuildWrapE wrap, const char *name, const char *ctrlList, int32_t *textLine, int32_t *textLines); // Reads the source files named by an open .dbp (paths relative to // projectDir) into *out. Returns false with out->error set on failure; // call basBuildFreeProject either way. bool basBuildConcatProject(PrefsHandleT *prefs, const char *projectDir, BasProjectSourceT *out); // Writes the directory part of path into out, or "." when path has none. void basBuildDirOf(const char *path, char *out, int32_t outSize); // Releases everything basBuildConcatProject allocated. void basBuildFreeProject(BasProjectSourceT *out); // Why basBuildCompile failed. typedef enum { BAS_COMPILE_OK, // compiled BAS_COMPILE_ERROR, // an error in the source (line / col set) BAS_COMPILE_NO_MEMORY, // out of memory BAS_COMPILE_NO_MODULE // compiled, but the module could not be built } BasCompileResultE; typedef struct { BasCompileResultE result; int32_t line; // 1-based line of the source (0: none) int32_t col; // 1-based column (0: not known) char message[BAS_BUILD_ERROR_LEN]; // basCompilerError's text, or the failure } BasCompileErrorT; // Compiles source (len bytes) into a module (the caller frees it with // basModuleFree), with OPTION EXPLICIT for the whole program when // optionExplicit is set and validator (may be NULL) checking // CtrlName.Member references. *liveCompiler (may be NULL) holds the // compiler while it runs: a validator reads its form scope from it. // NULL on failure, with *err telling why. BasModuleT *basBuildCompile(const char *source, int32_t len, bool optionExplicit, const BasCtrlValidatorT *validator, const BasCompilerT **liveCompiler, BasCompileErrorT *err); // A compiler message without its "Line N, Col M: " prefix. const char *basBuildErrorText(const char *message); // Appends one source file (path names it in the source map; text is its // whole text, len bytes, NUL-terminated) the way a project is built: a // module wrapped in BEGINMODULE, a class module's code after its header // in BEGINCLASS, a UserControl's code after its header in BEGINCLASS ... // USERCONTROL (the header is kept in ctlTexts), a form's code section in // BEGINFORM with its controls (the .frm text is kept in frmPaths / // frmTexts for the resources and the run), a lone program // (BAS_BUILD_WRAP_NONE) as it is. Returns false // with ps->error set on failure. bool basBuildAddSource(BasProjectSourceT *ps, const char *path, const char *text, int32_t len, BasBuildWrapE wrap); // Maps a 1-based line of the concatenated source to the file and 1-based // line it came from, using a source map (stb_ds array, e.g. ps->map). // Returns false (outputs untouched) for a line outside every file, e.g. // a BEGINFORM/ENDFORM wrapper line. bool basBuildMapLine(const BasSourceMapT *map, int32_t line, const char **path, int32_t *fileLine); // The inverse of basBuildMapLine: the 1-based line of the concatenated // source holding line fileLine of path (compared as DOS compares names: // letters in any case, '/' and '\\' alike), // or 0 when path is not in the map or the line was not compiled (a class // module's header, a .frm's layout). int32_t basBuildMapFileLine(const BasSourceMapT *map, const char *path, int32_t fileLine); // Opens a .dbp project: *prefs receives the loaded INI and projectDir // (projectDirSize bytes) the directory holding it. Returns false when // the file cannot be opened (prefsLoad alone would open a missing file // as an empty project). bool basBuildOpenProject(const char *dbpPath, PrefsHandleT **prefs, char *projectDir, int32_t projectDirSize); // Fills spec's project metadata (name, author, publisher, version, // copyright, description, icon, help file, MultiInstance) and projectDir // from an open .dbp; the strings stay owned by prefs. The other fields // are left alone. void basBuildSpecFromProject(PrefsHandleT *prefs, const char *projectDir, BasBuildSpecT *spec); // Build outPath, a control file (.wgt holding only resources: the marker // DVX_RES_ONLY_MAGIC, then the module, each UserControl's header as CTLn // with its toolbox icon24 and name resources -- "-2", "-3" ... for the // second and later -- the project's metadata and help file) from spec, a // control project's (no forms; ctlSources needed). NULL on success, // else a static error message. const char *basBuildControl(const char *outPath, const BasBuildSpecT *spec); // True when the open .dbp is a control project ([Project] Type = Control). bool basBuildIsControlProject(PrefsHandleT *prefs); // True when type, a [Project] Type value, makes a control project (any // case). bool basBuildIsControlType(const char *type); // The kind of project file path names, by its extension: a form (.frm), // a class module (.cls), a UserControl (.ctl) or a module (anything // else). BasBuildWrapE basBuildWrapForPath(const char *path); #ifdef __cplusplus } #endif #endif // BAS_BUILD_H