/** * @file types.h * @brief Declares the interpreter's fixed budget and its primitive value type. */ #ifndef _AKBASIC_TYPES_H_ #define _AKBASIC_TYPES_H_ #include #include #include /* * The budget, transcribed from the Go reference's main.go:14-30. Everything the * interpreter uses is drawn from a pool sized by one of these; nothing calls * malloc. */ /* Per-environment pools */ #define AKBASIC_MAX_LEAVES 32 /* ~16 operations per source line */ /* * Arguments one call may carry. A line holds 32 tokens, so a call site cannot * spell more than a handful anyway; this is the ceiling on the array a call * builds them in, and it is checked rather than assumed. */ #define AKBASIC_MAX_CALL_ARGUMENTS 16 #define AKBASIC_MAX_TOKENS 32 #define AKBASIC_MAX_VALUES 64 #define AKBASIC_MAX_VARIABLES 128 /* * Whole-runtime pools. * * AKBASIC_MAX_SOURCE_LINES, AKBASIC_MAX_LINE_LENGTH, AKBASIC_MAX_ARRAY_VALUES, * AKBASIC_MAX_ENVIRONMENTS and AKBASIC_MAX_FUNCTIONS were cut from their * original values against measurements taken off examples/breakout and * examples/megademo, the two most demanding programs this interpreter runs -- * see the memory-footprint discussion this commit's PR body links. Each is * sized at roughly 1.5-2x the peak the reference corpus actually reaches, not * at the peak itself. * * AKBASIC_MAX_LINE_LENGTH in particular follows Commodore BASIC's own 80-column * line limit rather than a measurement, which is why sink_stdio.c now refuses a * line that fills the buffer with no terminator instead of silently truncating * it: at 256 bytes that failure mode was theoretical, and at 80 it is not. * * AKBASIC_MAX_SOURCE_PATH_LENGTH is its own constant rather than a reuse of * AKBASIC_MAX_LINE_LENGTH, which is where it used to come from. `sourcepath` * (runtime.h) holds a directory, not a line of BASIC, and the two ideas do not * scale together: shrinking the line limit to 80 broke every golden test in * this checkout, because this repository's own working directory is deeper * than that. PATH_MAX is the actual bound a filesystem path is subject to, so * it is the one this borrows. */ #define AKBASIC_MAX_SOURCE_LINES 2048 #define AKBASIC_MAX_LINE_LENGTH 80 /* Commodore BASIC's own line limit */ #define AKBASIC_MAX_SOURCE_PATH_LENGTH PATH_MAX #define AKBASIC_MAX_ARRAY_DEPTH 64 /* dimensions per array */ #define AKBASIC_MAX_ARRAY_ELEMENTS 1024 /* elements in one array */ #define AKBASIC_MAX_ARRAY_VALUES 2048 /* array elements across all variables */ #define AKBASIC_MAX_STRING_LENGTH 256 /* see TODO.md 1.2 */ #define AKBASIC_MAX_ENVIRONMENTS 12 /* new: Go allocated these unbounded */ #define AKBASIC_MAX_FUNCTIONS 8 /* new: Go used an unbounded map */ #define AKBASIC_MAX_LABELS 64 /* new: Go used an unbounded map */ /* * Leaves a DO/LOOP condition may use. Its own small pool rather than a second * AKBASIC_MAX_LEAVES one, because a leaf carries two 256-byte strings and this * is per environment: `DO WHILE A# = 1 AND B# = 2` is six leaves, and sixteen * leaves nobody uses on every one of 32 scopes is 250 KB of nothing. */ #define AKBASIC_MAX_CONDITION_LEAVES 16 /* * Structure types. These bound the *descriptors* -- names and slot offsets -- * and not the data: an instance's fields are ordinary values drawn from the same * AKBASIC_MAX_ARRAY_VALUES pool an array uses, because a declared TYPE has a * known slot count and so is laid out exactly as an array is. */ #define AKBASIC_MAX_STRUCT_TYPES 16 /* distinct TYPE declarations */ #define AKBASIC_MAX_STRUCT_FIELDS 16 /* fields in one TYPE */ #define AKBASIC_MAX_STRUCT_NAME 32 /* a type or field name */ /* * How deep a chain of by-value nesting may go. A TYPE cannot contain itself by * value -- that is refused at declaration -- so this is not what stops infinite * recursion; it bounds the *rendering* of a structure, where a pointer can make * the graph cyclic and PRINT would otherwise not come back. */ #define AKBASIC_MAX_STRUCT_DEPTH 4 /* Commodore convention: true is -1, not 1. */ #define AKBASIC_TRUE -1 #define AKBASIC_FALSE 0 /** @brief Runtime execution modes. */ #define AKBASIC_MODE_REPL 1 #define AKBASIC_MODE_RUN 2 #define AKBASIC_MODE_RUNSTREAM 3 #define AKBASIC_MODE_QUIT 4 /** @brief The type carried by a value, taken from an identifier's suffix. */ typedef enum { AKBASIC_TYPE_UNDEFINED = 0, AKBASIC_TYPE_INTEGER, /* 1 -- identifier suffix '#' */ AKBASIC_TYPE_FLOAT, /* 2 -- identifier suffix '%' */ AKBASIC_TYPE_STRING, /* 3 -- identifier suffix '$' */ AKBASIC_TYPE_BOOLEAN, /* 4 */ AKBASIC_TYPE_STRUCT, /* 5 -- identifier suffix '@' */ AKBASIC_TYPE_POINTER /* 6 -- identifier suffix '@', declared PTR TO */ } akbasic_Type; /* * A value. The string lives inline rather than behind a pointer so that a copy * is a struct assignment with no allocator, refcount or lifetime question -- * see TODO.md 1.2 for the size tradeoff that buys. * * The reference's BasicValue carries a `name` field. It is dead: nothing ever * writes it a non-empty string, and its only reader is BasicEnvironment.update(), * which has no callers. Both are dropped here. */ typedef struct akbasic_Value { akbasic_Type valuetype; char stringval[AKBASIC_MAX_STRING_LENGTH]; int64_t intval; double floatval; int64_t boolvalue; bool mutable_; /* * What a STRUCT or POINTER value carries. Not a payload: a *reference* to * one, because an instance is a run of slots and cannot fit in the one slot * a value occupies. * * These are their own members rather than a reuse of `intval`, and that is * deliberate -- section 6 item 5 was an operator reading a numeric field * that happened to be the wrong one, and putting a slot index somewhere * arithmetic already looks would set the same trap again. * * The distinction between the two types is what an *assignment* does with * this reference, not what the reference is: a STRUCT copies the slots it * points at, and a POINTER copies the reference itself. */ int structtype; /* index into the type table, -1 for none */ struct akbasic_Value *structbase; /* first slot of the instance */ /* * The host's own struct, when this describes a host binding. The slots above * are then a *shadow*: a read refreshes one from here and a write converts * back into here, so a script sees current values and its writes land, with * one storage model instead of two. */ void *hostbase; } akbasic_Value; #endif // _AKBASIC_TYPES_H_