// 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. // basRes.h -- single source of truth for BASIC project / binary layout // // Keys used in .dbp project files and names of resources written into the // compiled .app DXE. These were previously scattered as raw string literals // across the IDE (ideMain.c, ideProject.c) and the standalone compiler // (bascomp.c), making it easy for one side to drift from the other. // All readers and writers now include this header. #ifndef BAS_RES_H #define BAS_RES_H #include #include // NULL #include #include // strncasecmp #include "formrt/frmParser.h" // FRM_KW_* .frm keyword spellings // dvxSkipWs is declared in platform/dvxPlat.h, which every translation // unit that includes this header also includes. We forward-declare it // here (a redundant, compatible declaration is legal C) so the inline // helper below can use it without pulling the whole platform header into // basRes.h's include surface. const char *dvxSkipWs(const char *s); // ------------------------------------------------------------ // [Project] section of a .dbp file // ------------------------------------------------------------ #define BAS_INI_SECTION_PROJECT "Project" #define BAS_INI_KEY_NAME "Name" #define BAS_INI_KEY_AUTHOR "Author" #define BAS_INI_KEY_PUBLISHER "Publisher" #define BAS_INI_KEY_VERSION "Version" #define BAS_INI_KEY_COPYRIGHT "Copyright" #define BAS_INI_KEY_DESCRIPTION "Description" #define BAS_INI_KEY_ICON "Icon" #define BAS_INI_KEY_HELPFILE "HelpFile" #define BAS_INI_KEY_COMMANDARGS "CommandArgs" // IDE only: COMMAND$ when run from the IDE // [Settings] / [Modules] / [Forms] sections of a .dbp file #define BAS_INI_SECTION_SETTINGS "Settings" #define BAS_INI_KEY_STARTUPFORM "StartupForm" #define BAS_INI_KEY_OPTIONEXPLICIT "OptionExplicit" #define BAS_INI_SECTION_MODULES "Modules" #define BAS_INI_SECTION_FORMS "Forms" // ------------------------------------------------------------ // Resource names inside a compiled .app DXE // ------------------------------------------------------------ // Display name of an app whose project sets no Name (resource fallback // and the stub's descriptor before the NAME resource is read). #define BAS_DEFAULT_APP_NAME "BASIC App" // Text metadata -- mirrors the [Project] keys above but lowercase // (resource names are case-sensitive; apps query these at runtime). #define BAS_RES_NAME "name" #define BAS_RES_AUTHOR "author" #define BAS_RES_PUBLISHER "publisher" #define BAS_RES_VERSION "version" #define BAS_RES_COPYRIGHT "copyright" #define BAS_RES_DESCRIPTION "description" #define BAS_RES_HELPFILE "helpfile" // Icon -- 32x32 application icon #define BAS_RES_ICON32 "icon32" // Binary payload -- bytecode + debug info #define BAS_RES_MODULE "MODULE" #define BAS_RES_DEBUG "DEBUG" // Stub DXE embedded in the IDE app for release builds #define BAS_RES_STUB "STUB" #define BAS_RES_NOICON "noicon" // fallback ICON32 source in the IDE / bascomp // Per-form binary resource name, e.g. FORM0, FORM1, ... Shared by the // basBuild writer and the basstub reader so the naming cannot drift; %ld is // the dense form index. #define BAS_RES_FORM_FMT "FORM%ld" #define BAS_RES_FORM_NAME_LEN 16 // buffer for a formatted FORMn resource name // ------------------------------------------------------------ // Form resource scan limit // ------------------------------------------------------------ // Upper bound on FORM0..FORMn resources written by the compiler // (bascomp/basBuild) and scanned by the stub at load time. Shared // so the writer and reader cannot drift. #define BAS_MAX_FORM_RESOURCES 256 // ------------------------------------------------------------ // .frm scanners (shared by bascomp, basstub, basBuild and the obfuscator) // ------------------------------------------------------------ static inline bool basExtractFormName(const char *frmText, char *nameBuf, int32_t nameBufSize); static inline int32_t basFindFormEndPos(const char *text, int32_t len); static inline int32_t basMatchBeginForm(const char *s, int32_t len); static inline int32_t basMatchKeywordWs(const char *s, int32_t len, const char *kw, int32_t kwLen); // Extract the form name from a "Begin Form " line in .frm text. // Writes a NUL-terminated name into nameBuf (max nameBufSize bytes, // including the terminator) and returns true if a name was found. // Shared verbatim by bascomp (concat injection) and basstub (lazy // frm registration); they previously duplicated this loop with only // the destination buffer size differing. static inline bool basExtractFormName(const char *frmText, char *nameBuf, int32_t nameBufSize) { if (nameBuf == NULL || nameBufSize <= 0) { return false; } nameBuf[0] = '\0'; if (frmText == NULL) { return false; } const char *p = frmText; while (*p) { p = dvxSkipWs(p); int32_t lineLen = 0; while (p[lineLen] && p[lineLen] != '\r' && p[lineLen] != '\n') { lineLen++; } int32_t nameOff = basMatchBeginForm(p, lineLen); if (nameOff >= 0) { p += nameOff; int32_t ni = 0; while (*p && *p != ' ' && *p != '\t' && *p != '\r' && *p != '\n' && ni < nameBufSize - 1) { nameBuf[ni] = *p; ni++; p++; } nameBuf[ni] = '\0'; return nameBuf[0] != '\0'; } while (*p && *p != '\n') { p++; } if (*p == '\n') { p++; } } return false; } // Returns the offset just past the line holding the End that closes the // outermost Begin Form block in text[0..len), i.e. the length of the pure // form-definition part. Everything after it is the .frm's BASIC code // section. Returns len when no Begin Form block is found or closed. // Lines are delimited by LF or CR LF; End lines follow frmIsEndLine, the // parser's own rule. static inline int32_t basFindFormEndPos(const char *text, int32_t len) { int32_t nesting = 0; bool inForm = false; int32_t pos = 0; while (pos < len) { int32_t lineStart = pos; while (pos < len && text[pos] != '\n' && text[pos] != '\r') { pos++; } int32_t lineEnd = pos; if (pos < len && text[pos] == '\r') { pos++; } if (pos < len && text[pos] == '\n') { pos++; } int32_t l = lineStart; while (l < lineEnd && (text[l] == ' ' || text[l] == '\t')) { l++; } int32_t rest = lineEnd - l; if (basMatchKeywordWs(text + l, rest, FRM_KW_BEGIN, FRM_KW_BEGIN_LEN) >= 0) { if (!inForm && basMatchBeginForm(text + l, rest) >= 0) { inForm = true; } nesting++; } else if (frmIsEndLine(text + l, rest)) { nesting--; if (inForm && nesting == 0) { return pos; } } } return len; } // Match "BeginForm" at s[0..len) (s already left-trimmed). The // single definition of the Begin Form syntax for basExtractFormName and // basFindFormEndPos. Returns the offset of the form name, or -1. static inline int32_t basMatchBeginForm(const char *s, int32_t len) { int32_t b = basMatchKeywordWs(s, len, FRM_KW_BEGIN, FRM_KW_BEGIN_LEN); if (b < 0) { return -1; } int32_t f = basMatchKeywordWs(s + b, len - b, FRM_KW_FORM, FRM_KW_FORM_LEN); return (f < 0) ? -1 : b + f; } // Match a keyword followed by at least one space or tab at s[0..len). // Returns the offset of the first character after that whitespace, or // -1 when the keyword does not match. static inline int32_t basMatchKeywordWs(const char *s, int32_t len, const char *kw, int32_t kwLen) { if (len <= kwLen || strncasecmp(s, kw, kwLen) != 0 || (s[kwLen] != ' ' && s[kwLen] != '\t')) { return -1; } int32_t i = kwLen; while (i < len && (s[i] == ' ' || s[i] == '\t')) { i++; } return i; } #endif // BAS_RES_H