DVX_GUI/src/apps/kpunch/dvxbasic/basRes.h

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