269 lines
9.2 KiB
C
269 lines
9.2 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.
|
|
|
|
// 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 <stdbool.h>
|
|
#include <stddef.h> // NULL
|
|
#include <stdint.h>
|
|
#include <strings.h> // 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 <name>" 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 "Begin<ws>Form<ws>" 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
|