DVX_GUI/src/apps/kpunch/dvxbasic/basBuild.h
2026-10-12 14:19:24 -05:00

254 lines
12 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.
// 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 <stdbool.h>
#include <stdint.h>
#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