// 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. // formcfm.c -- Compiled form format implementation // // Serializes the layout portion of a .frm file (the Begin/End block // structure) into a compact binary format. The BASIC code that follows // the form layout is NOT included -- it's already compiled into the // bytecode module. // // Binary format: // magic 4 bytes "DCFM" // version uint16 1 // formName len-prefixed string // caption len-prefixed string // layout len-prefixed string ("VBox", "HBox", etc.) // width int16 // height int16 // left int16 // top int16 // flags uint8 (bit 0=resizable, 1=centered, 2=autoSize) // controlCount uint16 // menuItemCount uint16 // // Controls[controlCount]: // typeName len-prefixed string // ctrlName len-prefixed string // parentIndex int16 (-1 = form root) // arrayIndex int16 (-1 = not array) // propertyCount uint16 // properties[]: // key len-prefixed string // valueType uint8 (0=string, 1=int, 2=bool) // value type-dependent // // MenuItems[menuItemCount]: // name len-prefixed string // caption len-prefixed string // level uint8 // flags uint8 (bit 0=checked, 1=radioCheck, 2=enabled) #include "formcfm.h" #include "../compiler/opcodes.h" #include "thirdparty/stb_ds_wrap.h" #include #include #include #include #include // ============================================================ // Write helpers (same pattern as serialize.c) // ============================================================ typedef struct { uint8_t *buf; int32_t len; int32_t cap; } CfmBufT; static void cbufInit(CfmBufT *b) { b->cap = 2048; b->buf = (uint8_t *)malloc(b->cap); b->len = 0; } static void cbufGrow(CfmBufT *b, int32_t need) { while (b->len + need > b->cap) { b->cap *= 2; } b->buf = (uint8_t *)realloc(b->buf, b->cap); } static void cbufWrite(CfmBufT *b, const void *data, int32_t len) { cbufGrow(b, len); memcpy(b->buf + b->len, data, len); b->len += len; } static void cbufU8(CfmBufT *b, uint8_t v) { cbufWrite(b, &v, 1); } static void cbufU16(CfmBufT *b, uint16_t v) { cbufWrite(b, &v, 2); } static void cbufI16(CfmBufT *b, int16_t v) { cbufWrite(b, &v, 2); } static void cbufStr(CfmBufT *b, const char *s) { uint16_t len = s ? (uint16_t)strlen(s) : 0; cbufU16(b, len); if (len > 0) { cbufWrite(b, s, len); } } // ============================================================ // Read helpers // ============================================================ typedef struct { const uint8_t *data; int32_t len; int32_t pos; } CfmReaderT; static bool crOk(const CfmReaderT *r, int32_t need) { return r->pos + need <= r->len; } static uint8_t crU8(CfmReaderT *r) { return crOk(r, 1) ? r->data[r->pos++] : 0; } static uint16_t crU16(CfmReaderT *r) { if (!crOk(r, 2)) { return 0; } uint16_t v; memcpy(&v, r->data + r->pos, 2); r->pos += 2; return v; } static int16_t crI16(CfmReaderT *r) { if (!crOk(r, 2)) { return 0; } int16_t v; memcpy(&v, r->data + r->pos, 2); r->pos += 2; return v; } static int32_t crI32(CfmReaderT *r) { if (!crOk(r, 4)) { return 0; } int32_t v; memcpy(&v, r->data + r->pos, 4); r->pos += 4; return v; } static char *crStr(CfmReaderT *r) { uint16_t len = crU16(r); if (len == 0 || !crOk(r, len)) { char *s = (char *)malloc(1); s[0] = '\0'; return s; } char *s = (char *)malloc(len + 1); memcpy(s, r->data + r->pos, len); s[len] = '\0'; r->pos += len; return s; } // ============================================================ // .frm text parser for compilation // ============================================================ #define MAX_LINE 512 typedef struct { char typeName[BAS_MAX_CTRL_NAME]; char ctrlName[BAS_MAX_CTRL_NAME]; int16_t parentIndex; int16_t arrayIndex; // Properties struct { char key[64]; uint8_t valueType; // 0=string, 1=int, 2=bool char strVal[256]; int32_t intVal; } props[32]; int32_t propCount; } CfmControlT; typedef struct { char name[BAS_MAX_CTRL_NAME]; char caption[256]; uint8_t level; uint8_t flags; // bit 0=checked, 1=radioCheck, 2=enabled } CfmMenuItemT; static void parseFrmKeyVal(const char *line, char *key, char *value) { key[0] = '\0'; value[0] = '\0'; while (*line == ' ' || *line == '\t') { line++; } int32_t ki = 0; while (*line && *line != ' ' && *line != '\t' && *line != '=' && ki < 63) { key[ki++] = *line++; } key[ki] = '\0'; while (*line == ' ' || *line == '\t') { line++; } if (*line == '=') { line++; } while (*line == ' ' || *line == '\t') { line++; } // Value: strip surrounding quotes if present int32_t vi = 0; bool inQ = false; if (*line == '"') { inQ = true; line++; } while (*line && vi < 255) { if (inQ && *line == '"') { break; } value[vi++] = *line++; } value[vi] = '\0'; } // ============================================================ // basFormCompile // ============================================================ uint8_t *basFormCompile(const char *frmSource, int32_t frmLen, int32_t *outLen) { if (!frmSource || frmLen <= 0 || !outLen) { return NULL; } // Parse the .frm text to extract form layout char formName[BAS_MAX_CTRL_NAME] = ""; char caption[256] = ""; char layout[BAS_MAX_CTRL_NAME] = "VBox"; int16_t width = 0; int16_t height = 0; int16_t left = 0; int16_t top = 0; bool resizable = true; bool centered = true; bool autoSize = true; CfmControlT *controls = NULL; int32_t controlCount = 0; int32_t controlCap = 32; controls = (CfmControlT *)calloc(controlCap, sizeof(CfmControlT)); CfmMenuItemT *menuItems = NULL; int32_t menuCount = 0; int32_t menuCap = 16; menuItems = (CfmMenuItemT *)calloc(menuCap, sizeof(CfmMenuItemT)); // Parent index stack for nested containers int16_t parentStack[BAS_MAX_FRM_NESTING]; int32_t nestDepth = 0; int32_t menuNestDepth = 0; bool inForm = false; bool inMenu = false; int32_t currentCtrl = -1; const char *pos = frmSource; const char *end = frmSource + frmLen; while (pos < end) { const char *lineStart = pos; while (pos < end && *pos != '\n' && *pos != '\r') { pos++; } int32_t lineLen = (int32_t)(pos - lineStart); if (pos < end && *pos == '\r') { pos++; } if (pos < end && *pos == '\n') { pos++; } char line[MAX_LINE]; if (lineLen >= MAX_LINE) { lineLen = MAX_LINE - 1; } memcpy(line, lineStart, lineLen); line[lineLen] = '\0'; char *trimmed = line; while (*trimmed == ' ' || *trimmed == '\t') { trimmed++; } if (*trimmed == '\0' || *trimmed == '\'') { continue; } // Stop at code section (first SUB/FUNCTION/DIM/executable statement after End Form) if (!inForm && formName[0]) { break; } // VERSION line -- skip if (strncasecmp(trimmed, "VERSION ", 8) == 0) { continue; } // Begin block if (strncasecmp(trimmed, "Begin ", 6) == 0) { char *rest = trimmed + 6; char typeName[BAS_MAX_CTRL_NAME]; char ctrlName[BAS_MAX_CTRL_NAME]; int32_t ti = 0; while (*rest && *rest != ' ' && *rest != '\t' && ti < BAS_MAX_CTRL_NAME - 1) { typeName[ti++] = *rest++; } typeName[ti] = '\0'; while (*rest == ' ' || *rest == '\t') { rest++; } int32_t ci = 0; while (*rest && *rest != ' ' && *rest != '\t' && *rest != '\r' && *rest != '\n' && ci < BAS_MAX_CTRL_NAME - 1) { ctrlName[ci++] = *rest++; } ctrlName[ci] = '\0'; if (strcasecmp(typeName, "Form") == 0) { snprintf(formName, BAS_MAX_CTRL_NAME, "%s", ctrlName); snprintf(caption, sizeof(caption), "%s", ctrlName); inForm = true; nestDepth = 0; for (int32_t i = 0; i < BAS_MAX_FRM_NESTING; i++) { parentStack[i] = -1; } } else if (strcasecmp(typeName, "Menu") == 0 && inForm) { if (menuCount >= menuCap) { menuCap *= 2; menuItems = (CfmMenuItemT *)realloc(menuItems, menuCap * sizeof(CfmMenuItemT)); } CfmMenuItemT *mi = &menuItems[menuCount++]; memset(mi, 0, sizeof(CfmMenuItemT)); snprintf(mi->name, BAS_MAX_CTRL_NAME, "%s", ctrlName); mi->level = (uint8_t)menuNestDepth; mi->flags = 0x04; // enabled by default inMenu = true; menuNestDepth++; currentCtrl = -1; } else if (inForm) { if (controlCount >= controlCap) { controlCap *= 2; controls = (CfmControlT *)realloc(controls, controlCap * sizeof(CfmControlT)); } CfmControlT *ctrl = &controls[controlCount]; memset(ctrl, 0, sizeof(CfmControlT)); snprintf(ctrl->typeName, BAS_MAX_CTRL_NAME, "%s", typeName); snprintf(ctrl->ctrlName, BAS_MAX_CTRL_NAME, "%s", ctrlName); ctrl->parentIndex = (nestDepth > 0) ? parentStack[nestDepth - 1] : -1; ctrl->arrayIndex = -1; currentCtrl = controlCount; controlCount++; // Check if this is a container -- push onto parent stack const char *wgtName = wgtFindByBasName(typeName); if (wgtName) { const WgtIfaceT *iface = wgtGetIface(wgtName); if (iface && iface->isContainer) { if (nestDepth < BAS_MAX_FRM_NESTING) { parentStack[nestDepth] = (int16_t)currentCtrl; } nestDepth++; } } } continue; } // End block if (strncasecmp(trimmed, "End", 3) == 0 && (trimmed[3] == '\0' || trimmed[3] == ' ' || trimmed[3] == '\t' || trimmed[3] == '\r' || trimmed[3] == '\n')) { if (inMenu) { menuNestDepth--; if (menuNestDepth <= 0) { inMenu = false; menuNestDepth = 0; } } else if (nestDepth > 0) { nestDepth--; } else { // End Form inForm = false; } currentCtrl = -1; continue; } // Property assignment inside a block if (inForm) { char key[64]; char value[256]; parseFrmKeyVal(trimmed, key, value); if (!key[0]) { continue; } // Form-level properties if (currentCtrl < 0 && !inMenu) { if (strcasecmp(key, "Caption") == 0) { snprintf(caption, sizeof(caption), "%s", value); } else if (strcasecmp(key, "Layout") == 0) { snprintf(layout, BAS_MAX_CTRL_NAME, "%s", value); } else if (strcasecmp(key, "Width") == 0) { width = (int16_t)atoi(value); } else if (strcasecmp(key, "Height") == 0) { height = (int16_t)atoi(value); } else if (strcasecmp(key, "Left") == 0) { left = (int16_t)atoi(value); } else if (strcasecmp(key, "Top") == 0) { top = (int16_t)atoi(value); } else if (strcasecmp(key, "Resizable") == 0) { resizable = (strcasecmp(value, "True") == 0); } else if (strcasecmp(key, "Centered") == 0) { centered = (strcasecmp(value, "True") == 0); } else if (strcasecmp(key, "AutoSize") == 0) { autoSize = (strcasecmp(value, "True") == 0); } } else if (inMenu && menuCount > 0) { CfmMenuItemT *mi = &menuItems[menuCount - 1]; if (strcasecmp(key, "Caption") == 0) { snprintf(mi->caption, sizeof(mi->caption), "%s", value); } else if (strcasecmp(key, "Checked") == 0) { if (strcasecmp(value, "True") == 0) { mi->flags |= 0x01; } } else if (strcasecmp(key, "Enabled") == 0) { if (strcasecmp(value, "False") == 0) { mi->flags &= ~0x04; } } } else if (currentCtrl >= 0 && currentCtrl < controlCount) { CfmControlT *ctrl = &controls[currentCtrl]; // Index property for control arrays if (strcasecmp(key, "Index") == 0) { ctrl->arrayIndex = (int16_t)atoi(value); continue; } if (ctrl->propCount < 32) { int32_t pi = ctrl->propCount++; snprintf(ctrl->props[pi].key, 64, "%s", key); // Detect value type if (strcasecmp(value, "True") == 0) { ctrl->props[pi].valueType = 2; ctrl->props[pi].intVal = 1; } else if (strcasecmp(value, "False") == 0) { ctrl->props[pi].valueType = 2; ctrl->props[pi].intVal = 0; } else if (value[0] == '-' || isdigit((unsigned char)value[0])) { ctrl->props[pi].valueType = 1; ctrl->props[pi].intVal = atoi(value); } else { ctrl->props[pi].valueType = 0; snprintf(ctrl->props[pi].strVal, 256, "%s", value); } } } } } // Serialize to binary CfmBufT b; cbufInit(&b); cbufWrite(&b, "DCFM", 4); cbufU16(&b, 1); // version cbufStr(&b, formName); cbufStr(&b, caption); cbufStr(&b, layout); cbufI16(&b, width); cbufI16(&b, height); cbufI16(&b, left); cbufI16(&b, top); uint8_t flags = 0; if (resizable) { flags |= 0x01; } if (centered) { flags |= 0x02; } if (autoSize) { flags |= 0x04; } cbufU8(&b, flags); cbufU16(&b, (uint16_t)controlCount); cbufU16(&b, (uint16_t)menuCount); for (int32_t i = 0; i < controlCount; i++) { CfmControlT *ctrl = &controls[i]; cbufStr(&b, ctrl->typeName); cbufStr(&b, ctrl->ctrlName); cbufI16(&b, ctrl->parentIndex); cbufI16(&b, ctrl->arrayIndex); cbufU16(&b, (uint16_t)ctrl->propCount); for (int32_t j = 0; j < ctrl->propCount; j++) { cbufStr(&b, ctrl->props[j].key); cbufU8(&b, ctrl->props[j].valueType); switch (ctrl->props[j].valueType) { case 0: // string cbufStr(&b, ctrl->props[j].strVal); break; case 1: // int cbufWrite(&b, &ctrl->props[j].intVal, 4); break; case 2: // bool cbufU8(&b, ctrl->props[j].intVal ? 1 : 0); break; } } } for (int32_t i = 0; i < menuCount; i++) { CfmMenuItemT *mi = &menuItems[i]; cbufStr(&b, mi->name); cbufStr(&b, mi->caption); cbufU8(&b, mi->level); cbufU8(&b, mi->flags); } free(controls); free(menuItems); *outLen = b.len; return b.buf; } // ============================================================ // basFormLoadCompiled // ============================================================ BasFormT *basFormLoadCompiled(BasFormRtT *rt, const uint8_t *data, int32_t dataLen) { if (!rt || !data || dataLen < 10) { return NULL; } CfmReaderT r = { data, dataLen, 0 }; // Check magic if (data[0] != 'D' || data[1] != 'C' || data[2] != 'F' || data[3] != 'M') { return NULL; } r.pos = 4; uint16_t version = crU16(&r); if (version != 1) { return NULL; } char *formName = crStr(&r); char *caption = crStr(&r); char *layout = crStr(&r); int16_t width = crI16(&r); int16_t height = crI16(&r); int16_t left = crI16(&r); int16_t top = crI16(&r); uint8_t flags = crU8(&r); bool resizable = (flags & 0x01) != 0; bool centered = (flags & 0x02) != 0; bool autoSize = (flags & 0x04) != 0; uint16_t controlCount = crU16(&r); uint16_t menuCount = crU16(&r); // Create the form WidgetT *root; WidgetT *contentBox; WindowT *win = basFormRtCreateFormWindow(rt->ctx, caption, layout, resizable, centered, autoSize, width, height, left, top, &root, &contentBox); if (!win) { free(formName); free(caption); free(layout); return NULL; } BasFormT *form = (BasFormT *)calloc(1, sizeof(BasFormT)); snprintf(form->name, BAS_MAX_CTRL_NAME, "%s", formName); snprintf(form->frmLayout, sizeof(form->frmLayout), "%s", layout); form->frmWidth = width; form->frmHeight = height; form->frmLeft = left; form->frmTop = top; form->frmResizable = resizable; form->frmCentered = centered; form->frmAutoSize = autoSize; form->window = win; form->root = root; form->contentBox = contentBox; form->ctx = rt->ctx; form->vm = rt->vm; form->module = rt->module; // Synthetic control for form-level property access memset(&form->formCtrl, 0, sizeof(form->formCtrl)); snprintf(form->formCtrl.name, BAS_MAX_CTRL_NAME, "%s", formName); form->formCtrl.widget = root; form->formCtrl.form = form; // Window callbacks win->onClose = NULL; // stub sets these after win->onResize = NULL; win->onFocus = NULL; win->onBlur = NULL; // Create controls for (uint16_t i = 0; i < controlCount; i++) { char *typeName = crStr(&r); char *ctrlName = crStr(&r); int16_t parentIndex = crI16(&r); int16_t arrayIndex = crI16(&r); uint16_t propCount = crU16(&r); // Determine parent widget WidgetT *parent = contentBox; if (parentIndex >= 0 && parentIndex < (int16_t)i) { // Find the parent control's widget BasControlT *parentCtrl = form->controls[parentIndex]; parent = parentCtrl->widget; } // Resolve widget type and create const char *wgtTypeName = wgtFindByBasName(typeName); WidgetT *widget = NULL; if (wgtTypeName) { widget = createWidget(wgtTypeName, parent); } if (!widget) { // Skip properties for (uint16_t j = 0; j < propCount; j++) { free(crStr(&r)); // key uint8_t vt = crU8(&r); if (vt == 0) { free(crStr(&r)); } else if (vt == 1) { crI32(&r); } else { crU8(&r); } } free(typeName); free(ctrlName); continue; } wgtSetName(widget, ctrlName); // Create control entry BasControlT *ctrl = (BasControlT *)calloc(1, sizeof(BasControlT)); snprintf(ctrl->name, BAS_MAX_CTRL_NAME, "%s", ctrlName); snprintf(ctrl->typeName, BAS_MAX_CTRL_NAME, "%s", typeName); ctrl->index = arrayIndex; ctrl->widget = widget; ctrl->form = form; ctrl->iface = wgtGetIface(wgtTypeName); arrput(form->controls, ctrl); widget->userData = ctrl; // Apply properties for (uint16_t j = 0; j < propCount; j++) { char *key = crStr(&r); uint8_t vt = crU8(&r); BasValueT val; memset(&val, 0, sizeof(val)); switch (vt) { case 0: { // string char *sv = crStr(&r); val = basValStringFromC(sv); free(sv); break; } case 1: // int val = basValLong(crI32(&r)); break; case 2: // bool val = basValBool(crU8(&r) != 0); break; } basFormRtSetProp(rt, ctrl, key, val); basValRelease(&val); free(key); } free(typeName); free(ctrlName); } // TODO: menu items (skip for now, read and discard) for (uint16_t i = 0; i < menuCount; i++) { free(crStr(&r)); // name free(crStr(&r)); // caption crU8(&r); // level crU8(&r); // flags } // Add form to runtime arrput(rt->forms, form); free(formName); free(caption); free(layout); return form; } bool basFormGetCompiledName(const uint8_t *data, int32_t dataLen, char *nameBuf, int32_t bufSize) { if (!data || dataLen < 10 || !nameBuf || bufSize < 2) { return false; } if (data[0] != 'D' || data[1] != 'C' || data[2] != 'F' || data[3] != 'M') { return false; } // Skip magic (4) + version (2) = offset 6 // Form name is the first length-prefixed string if (dataLen < 8) { return false; } uint16_t nameLen; memcpy(&nameLen, data + 6, 2); if (nameLen == 0 || 8 + nameLen > dataLen) { return false; } int32_t copyLen = (nameLen < bufSize - 1) ? nameLen : bufSize - 1; memcpy(nameBuf, data + 8, copyLen); nameBuf[copyLen] = '\0'; return true; }