/** * @file structtype.h * @brief Declares the table of `TYPE` declarations and how a field is found in one. * * BASIC 7.0 has no records at all, so none of this is a port -- see TODO.md * section 5 for what was invented and why. * * **Declaring the type is what buys the storage model.** Because a `TYPE` states * its fields up front, an instance has a known slot count and is laid out * exactly as an array is: one contiguous run taken from the runtime's value pool * by akbasic_valuepool_take(). A field access is then offset arithmetic against * an offset this table already knows, and no new pool is needed for the data -- * only this table of descriptors, which holds names and offsets rather than * values. * * A field nested **by value** flattens into its parent's run at an offset. A * field declared `PTR TO` occupies one slot holding a reference. That is the * whole difference, and it is why a `TYPE` may refer to itself only through a * pointer: by value it would have no finite size, which is refused at * declaration rather than discovered when the pool runs out. */ #ifndef _AKBASIC_STRUCTTYPE_H_ #define _AKBASIC_STRUCTTYPE_H_ #include #include #include #include struct akbasic_Runtime; /** @brief What a field holds, which is what says how much room it needs. */ typedef enum { AKBASIC_FIELD_PRIMITIVE = 0, /* one slot, type from the name's suffix */ AKBASIC_FIELD_STRUCT, /* nested by value; flattened into the parent */ AKBASIC_FIELD_POINTER /* one slot holding a reference */ } akbasic_FieldKind; /** @brief One declared field: what it is called, what it holds, and where it sits. */ typedef struct { char name[AKBASIC_MAX_STRUCT_NAME]; /** as written, suffix included */ akbasic_FieldKind kind; akbasic_Type valuetype; /** for a primitive, from the suffix */ int typeindex; /** for STRUCT and POINTER */ int offset; /** slots from the start of the instance */ int slotcount; /** slots this field occupies */ /* * Where this field's bytes live in the *host's* struct, when the type came * from akbasic_host_register_type(). Unused for a type the script declared, * whose fields are value slots and have no other representation. */ akbasic_HostFieldKind hostkind; size_t hostoffset; size_t hostwidth; } akbasic_StructField; /** * @brief One `TYPE` declaration. * * `firstline` and `lastline` are the `TYPE` and `END TYPE` source lines. They * are kept because the block has to be *skipped* at run time: its field lines * are declarations, and letting them execute would evaluate each field name as a * bare identifier and quietly create a global of that name. */ typedef struct { char name[AKBASIC_MAX_STRUCT_NAME]; akbasic_StructField fields[AKBASIC_MAX_STRUCT_FIELDS]; int fieldcount; int slotcount; /** total slots for one instance */ int64_t firstline; int64_t lastline; bool used; /* * True when the host described this type rather than the script declaring * it. The difference is only in where a field's bytes are; everything the * language does with a structure is the same either way, which is the point * of putting both in one table. */ bool ishost; size_t hostsize; } akbasic_StructType; /** @brief Every `TYPE` a program declared. Lives on the runtime. */ typedef struct { akbasic_StructType types[AKBASIC_MAX_STRUCT_TYPES]; int count; } akbasic_StructTypeTable; /** * @brief Empty the table. * @param obj Object to initialize, inspect, or modify. * @return `NULL` on success, otherwise an error context owned by the caller. * @throws AKERR_NULLPOINTER When `obj` is NULL. */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_structtype_table_init(akbasic_StructTypeTable *obj); /** * @brief Find a declared type by name. * @param obj Table to search. * @param name Type name, without a suffix. * @param dest Output destination populated by the function; the index, or -1. * @return `NULL` on success, otherwise an error context owned by the caller. * @throws AKERR_NULLPOINTER When any argument is NULL. */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_structtype_find(akbasic_StructTypeTable *obj, const char *name, int *dest); /** * @brief Resolve a field of a declared type, refusing one it does not declare. * * The refusal lists the fields the type *does* have, because the whole reason a * misspelled field can be caught at all is that the set is closed and written * down -- so the diagnostic may as well show it. * * @param obj Table the type lives in. * @param typeindex Which type. * @param name Field name, suffix included. * @param dest Output destination populated by the function. * @return `NULL` on success, otherwise an error context owned by the caller. * @throws AKERR_NULLPOINTER When `obj`, `name` or `dest` is NULL. * @throws AKBASIC_ERR_BOUNDS When `typeindex` names no type. * @throws AKBASIC_ERR_UNDEFINED When the type does not declare that field. */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_structtype_field(akbasic_StructTypeTable *obj, int typeindex, const char *name, akbasic_StructField **dest); /** * @brief Read every `TYPE` block out of the program before it runs. * * Called from akbasic_runtime_set_mode() beside the label and `DATA` prescans, * and for the same reason all three are prescans: a declaration has to be in * effect wherever control happens to go, including when a branch skips over the * lines that made it. It also means `DIM P@ AS RECT` can never run before the * type it names exists. * * @param obj The runtime, whose source is read and whose table is filled. * @return `NULL` on success, otherwise an error context owned by the caller. * @throws AKERR_NULLPOINTER When `obj` is NULL. * @throws AKBASIC_ERR_SYNTAX When a block is malformed or unterminated. * @throws AKBASIC_ERR_VALUE When a type contains itself by value, or redeclares a name. * @throws AKBASIC_ERR_BOUNDS When the table or a field list is full. */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_structtype_scan(struct akbasic_Runtime *obj); /** * @brief Which `TYPE` block a source line opens, or -1. * * How `TYPE` skips its own body at run time: the verb asks what block it is * looking at and jumps past its `END TYPE`. * * @param obj Table to search. * @param lineno The line the interpreter is on. * @param dest Output destination populated by the function; the index, or -1. * @return `NULL` on success, otherwise an error context owned by the caller. * @throws AKERR_NULLPOINTER When `obj` or `dest` is NULL. */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_structtype_block_at(akbasic_StructTypeTable *obj, int64_t lineno, int *dest); /* --------------------------------------------------- the runtime half --- */ /** * @brief Fill @p dest with a description of an instance: which type, and where. * * What a structure-valued *expression* evaluates to. Not the data -- an instance * is a run of slots and a value is one slot -- so this is the reference that * lets assignment find the slots to copy, or lets `.` step into them. */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_struct_describe(struct akbasic_Runtime *obj, int typeindex, akbasic_Value *base, bool pointer, akbasic_Value *dest); /** * @brief Copy one instance's slots onto another's. * * Deep through nested values, and stops at pointers -- as it does for a C struct * holding a pointer. A `TYPE` cannot contain itself by value, so the depth is * fixed by the type graph before the program runs and this cannot recurse away. */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_struct_copy(struct akbasic_Runtime *obj, int typeindex, akbasic_Value *src, akbasic_Value *dest); /** * @brief Resolve a field-access leaf to the slot it addresses. * * Enforces the strict-pointer rule on the way: `.` requires a structure on its * left and `->` requires a pointer, and neither stands in for the other. */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_struct_resolve(struct akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_StructField **fielddest, akbasic_Value **slotdest, void **hostdest); /** @brief Evaluate a field access to the value it yields. */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_struct_evaluate_field(struct akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value **dest); /** * @brief Give every slot of a fresh instance the type its field declared. * * So a record that has only been `DIM`med prints as zeros rather than as * undefined values -- an answer instead of a puzzle. Recurses over the type * graph, which is finite because a `TYPE` cannot contain itself by value. */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_struct_init_slots(struct akbasic_Runtime *obj, int typeindex, akbasic_Value *base); /** @brief Render an instance the way PRINT does, bounded by depth. */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_struct_to_string(struct akbasic_Runtime *obj, int typeindex, akbasic_Value *base, int depth, char *dest, size_t len); /* ------------------------------------ host-backed instances (src/host.c) --- */ /** @brief Read one host field into a value, converting from its C type. */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_host_read_field(struct akbasic_Runtime *obj, akbasic_StructField *field, void *hostbase, akbasic_Value *dest); /** @brief Write a value back into one host field, refusing what will not fit. */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_host_write_field(struct akbasic_Runtime *obj, akbasic_StructField *field, void *hostbase, akbasic_Value *src); /** @brief Refresh a binding's whole shadow run from the host's struct. */ akerr_ErrorContext AKERR_NOIGNORE *akbasic_host_refresh(struct akbasic_Runtime *obj, int typeindex, void *hostbase, akbasic_Value *slots); #endif // _AKBASIC_STRUCTTYPE_H_