akbasic's src/ now calls libakstdlib 313 times and raw libc 7 -- 2.2% bypassed, against 86.4% on the same tree before this. The submodule bump 669b2b3 -> 2b79aca needed no source change of its own: the release is drop-in for what akbasic already used. Seven of the eight sites the earlier port left on raw libc change their own signature rather than swallowing an error, per andrew's ruling on libakstdlib#38. word_is, the is_waiting_for pair, the scanner's is_at_end, peek, peek_next and match_next_char, format.c's overflow, and sink_akgl's scroll/newline/putchar_at/echo_line/edit_key chain all return an akerr_ErrorContext * and hand the answer back through an out parameter. is_waiting_for and is_waiting_for_any are a public header change; every call site that used one as a term in a condition hoists it into a statement first. verb_compare is the eighth and stays on strcmp. bsearch(3) fixes the comparator's signature, so there is no out parameter to report through -- which is what libakstdlib#38 concluded. It carries a comment saying so and saying why the bypass is safe there. Six snprintf sites stay raw because they want truncation as an answer rather than an error, and aksl_snprintf cannot express that until libakstdlib#34 hands the required length back. Each of the six says so at the site. Two of them, in host.c, are a latent defect rather than a decision: a host type name over 31 characters truncates silently and two sharing a prefix then collide, where structtype.c refuses the same case. DLOAD leaked a file descriptor. Its read loop sat inside an ATTEMPT and the PASS in it returned past CLEANUP, so a scan error left the file open. Hoisting the loop into its own helper to convert fgets fixes it. Refs libakstdlib#26, libakstdlib#38 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
535 lines
19 KiB
C
535 lines
19 KiB
C
/**
|
|
* @file structtype.c
|
|
* @brief Implements the `TYPE` table and the prescan that fills it.
|
|
*
|
|
* The prescan is textual and runs before the program does, exactly as the label
|
|
* and `DATA` prescans do and for the same reason: a declaration has to be in
|
|
* effect wherever control goes, including when a branch skips the lines that
|
|
* made it.
|
|
*
|
|
* **It is three passes, and each one exists for a case that cannot be done in
|
|
* the pass before it.** Pass 1 registers names and line ranges, so a field can
|
|
* name a type declared further down the program. Pass 2 parses field lists, now
|
|
* able to resolve every type reference. Pass 3 computes sizes and offsets by
|
|
* repeated resolution, because a type nested by value must know its own size
|
|
* before its container can -- and a group of types that never resolve is
|
|
* precisely a cycle of by-value containment, which is what makes "a TYPE cannot
|
|
* contain itself by value" a diagnosis rather than an assumption.
|
|
*/
|
|
|
|
#include <ctype.h>
|
|
#include <inttypes.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include <akerror.h>
|
|
#include <akstdlib.h>
|
|
|
|
#include <akbasic/error.h>
|
|
#include <akbasic/runtime.h>
|
|
#include <akbasic/structtype.h>
|
|
|
|
#include "verbs.h"
|
|
|
|
/** @brief Step over spaces and tabs. */
|
|
static const char *skip_space(const char *cursor)
|
|
{
|
|
while ( *cursor != '\0' && isspace((unsigned char)*cursor) ) {
|
|
cursor += 1;
|
|
}
|
|
return cursor;
|
|
}
|
|
|
|
/**
|
|
* @brief Step over a stored line's own line number, if it still carries one.
|
|
*
|
|
* Every path inside the library files what the scanner already stripped, but
|
|
* akbasic_runtime_store_line() is public and a host may hand it a raw line, so
|
|
* both spellings are real and both are handled here -- the same allowance
|
|
* scan_line_labels() makes.
|
|
*/
|
|
static const char *skip_lineno(const char *cursor)
|
|
{
|
|
cursor = skip_space(cursor);
|
|
if ( isdigit((unsigned char)*cursor) ) {
|
|
while ( isdigit((unsigned char)*cursor) ) {
|
|
cursor += 1;
|
|
}
|
|
}
|
|
return skip_space(cursor);
|
|
}
|
|
|
|
/*
|
|
* Room to read a word that is *too long* and still see that it was.
|
|
*
|
|
* Reading into a buffer the size of the limit truncates before anything can
|
|
* check, so an over-long name would arrive already trimmed to fit and be
|
|
* accepted -- which is how the length check below was unreachable when it was
|
|
* first written. Twice the limit is enough to tell the two cases apart, and a
|
|
* word longer than this is still longer than the limit, so it is refused either
|
|
* way.
|
|
*/
|
|
#define STRUCT_WORD_SCRATCH (AKBASIC_MAX_STRUCT_NAME * 2)
|
|
|
|
/** @brief Copy the next whitespace-delimited word, uppercased. Empty when the line ends. */
|
|
static const char *next_word(const char *cursor, char *dest, size_t len)
|
|
{
|
|
size_t used = 0;
|
|
|
|
dest[0] = '\0';
|
|
cursor = skip_space(cursor);
|
|
while ( *cursor != '\0' && !isspace((unsigned char)*cursor) ) {
|
|
if ( used + 1 < len ) {
|
|
dest[used] = (char)toupper((unsigned char)*cursor);
|
|
used += 1;
|
|
}
|
|
cursor += 1;
|
|
}
|
|
dest[used] = '\0';
|
|
return cursor;
|
|
}
|
|
|
|
/**
|
|
* @brief Compare a word against a keyword. Verbs are case-insensitive here as everywhere.
|
|
*
|
|
* The answer leaves through @p dest rather than the return value because the
|
|
* comparison itself can fail, and a `bool` has nowhere to put that. See
|
|
* libakstdlib #38.
|
|
*/
|
|
static akerr_ErrorContext *word_is(const char *word, const char *keyword, bool *dest)
|
|
{
|
|
PREPARE_ERROR(errctx);
|
|
int cmp = 0;
|
|
|
|
FAIL_ZERO_RETURN(errctx, (dest != NULL), AKERR_NULLPOINTER, "NULL dest in word_is");
|
|
PASS(errctx, aksl_strcmp(word, keyword, &cmp));
|
|
*dest = (cmp == 0);
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
|
|
/**
|
|
* @brief The type a field name's suffix declares.
|
|
*
|
|
* The same rule every other name in the language follows, which is the reason a
|
|
* field list needs no type column: `W#` is an integer because it says so.
|
|
*/
|
|
static akerr_ErrorContext *type_from_suffix(const char *name, akbasic_Type *dest)
|
|
{
|
|
PREPARE_ERROR(errctx);
|
|
size_t len = 0;
|
|
|
|
PASS(errctx, aksl_strlen(name, &len));
|
|
FAIL_ZERO_RETURN(errctx, (len >= 2), AKBASIC_ERR_SYNTAX,
|
|
"Field name \"%s\" carries no type suffix", name);
|
|
switch ( name[len - 1] ) {
|
|
case '#': *dest = AKBASIC_TYPE_INTEGER; break;
|
|
case '%': *dest = AKBASIC_TYPE_FLOAT; break;
|
|
case '$': *dest = AKBASIC_TYPE_STRING; break;
|
|
case '@': *dest = AKBASIC_TYPE_STRUCT; break;
|
|
default:
|
|
FAIL_RETURN(errctx, AKBASIC_ERR_SYNTAX,
|
|
"Field name \"%s\" carries no type suffix (# integer, %% float, $ string, @ structure)",
|
|
name);
|
|
}
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
|
|
akerr_ErrorContext *akbasic_structtype_table_init(akbasic_StructTypeTable *obj)
|
|
{
|
|
PREPARE_ERROR(errctx);
|
|
|
|
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL table in structtype init");
|
|
PASS(errctx, aksl_memset(obj, 0, sizeof(*obj)));
|
|
obj->count = 0;
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
|
|
akerr_ErrorContext *akbasic_structtype_find(akbasic_StructTypeTable *obj, const char *name, int *dest)
|
|
{
|
|
PREPARE_ERROR(errctx);
|
|
int i = 0;
|
|
int cmp = 0;
|
|
|
|
FAIL_ZERO_RETURN(errctx, (obj != NULL && name != NULL && dest != NULL), AKERR_NULLPOINTER,
|
|
"NULL argument in structtype find");
|
|
*dest = -1;
|
|
for ( i = 0; i < obj->count; i++ ) {
|
|
if ( !obj->types[i].used ) {
|
|
continue;
|
|
}
|
|
PASS(errctx, aksl_strcmp(obj->types[i].name, name, &cmp));
|
|
if ( cmp == 0 ) {
|
|
*dest = i;
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
}
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
|
|
akerr_ErrorContext *akbasic_structtype_field(akbasic_StructTypeTable *obj, int typeindex, const char *name, akbasic_StructField **dest)
|
|
{
|
|
PREPARE_ERROR(errctx);
|
|
char known[AKBASIC_MAX_STRING_LENGTH];
|
|
const char *separator = NULL;
|
|
akbasic_StructType *type = NULL;
|
|
size_t used = 0;
|
|
size_t namelen = 0;
|
|
size_t seplen = 0;
|
|
int written = 0;
|
|
int i = 0;
|
|
int cmp = 0;
|
|
|
|
FAIL_ZERO_RETURN(errctx, (obj != NULL && name != NULL && dest != NULL), AKERR_NULLPOINTER,
|
|
"NULL argument in structtype field");
|
|
FAIL_ZERO_RETURN(errctx, (typeindex >= 0 && typeindex < obj->count), AKBASIC_ERR_BOUNDS,
|
|
"Structure type index %d is outside 0..%d", typeindex, obj->count - 1);
|
|
type = &obj->types[typeindex];
|
|
|
|
for ( i = 0; i < type->fieldcount; i++ ) {
|
|
PASS(errctx, aksl_strcmp(type->fields[i].name, name, &cmp));
|
|
if ( cmp == 0 ) {
|
|
*dest = &type->fields[i];
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* List what the type does have. The only reason a misspelled field can be
|
|
* caught at all is that the set is closed and the program wrote it down, so
|
|
* the diagnostic may as well show it -- this is the message that turns "why
|
|
* is my total zero" into a one-line fix.
|
|
*/
|
|
known[0] = '\0';
|
|
for ( i = 0; i < type->fieldcount; i++ ) {
|
|
separator = (i == 0 ? "" : ", ");
|
|
PASS(errctx, aksl_strlen(separator, &seplen));
|
|
PASS(errctx, aksl_strlen(type->fields[i].name, &namelen));
|
|
/*
|
|
* The list is advisory, so a full buffer ends it rather than failing the
|
|
* lookup. aksl_snprintf treats truncation as an error, which is right
|
|
* everywhere the output matters -- so the fit is decided here, before the
|
|
* call, instead of being read back out of a return value afterwards.
|
|
*/
|
|
if ( used + seplen + namelen >= sizeof(known) ) {
|
|
break;
|
|
}
|
|
PASS(errctx, aksl_snprintf(&written, known + used, sizeof(known) - used, "%s%s",
|
|
separator, type->fields[i].name));
|
|
used += (size_t)written;
|
|
}
|
|
FAIL_RETURN(errctx, AKBASIC_ERR_UNDEFINED, "%s has no field %s (%s)",
|
|
type->name, name, (type->fieldcount > 0 ? known : "it has none"));
|
|
}
|
|
|
|
akerr_ErrorContext *akbasic_structtype_block_at(akbasic_StructTypeTable *obj, int64_t lineno, int *dest)
|
|
{
|
|
PREPARE_ERROR(errctx);
|
|
int i = 0;
|
|
|
|
FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER,
|
|
"NULL argument in structtype block_at");
|
|
*dest = -1;
|
|
for ( i = 0; i < obj->count; i++ ) {
|
|
if ( obj->types[i].used && obj->types[i].firstline == lineno ) {
|
|
*dest = i;
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
}
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
|
|
/* ----------------------------------------------------------------- pass 1 -- */
|
|
|
|
/**
|
|
* @brief Register every type's name and line range.
|
|
*
|
|
* Names first and fields later, so a field may name a type declared further down
|
|
* the program. A linked list needs that even for itself -- `NEXT@ AS PTR TO
|
|
* NODE` inside `TYPE NODE` refers to a type that is not finished being read.
|
|
*/
|
|
static akerr_ErrorContext *scan_names(akbasic_Runtime *obj)
|
|
{
|
|
PREPARE_ERROR(errctx);
|
|
akbasic_StructTypeTable *table = &obj->structtypes;
|
|
char word[STRUCT_WORD_SCRATCH];
|
|
char name[STRUCT_WORD_SCRATCH];
|
|
const char *cursor = NULL;
|
|
int64_t i = 0;
|
|
int open = -1;
|
|
int existing = 0;
|
|
size_t namelen = 0;
|
|
bool matched = false;
|
|
const akbasic_Verb *verb = NULL;
|
|
|
|
for ( i = 0; i < AKBASIC_MAX_SOURCE_LINES; i++ ) {
|
|
if ( obj->source[i].code[0] == '\0' ) {
|
|
continue;
|
|
}
|
|
cursor = next_word(skip_lineno(obj->source[i].code), word, sizeof(word));
|
|
|
|
PASS(errctx, word_is(word, "END", &matched));
|
|
if ( matched ) {
|
|
(void)next_word(cursor, name, sizeof(name));
|
|
PASS(errctx, word_is(name, "TYPE", &matched));
|
|
if ( !matched ) {
|
|
continue; /* an ordinary END, not our terminator */
|
|
}
|
|
FAIL_ZERO_RETURN(errctx, (open >= 0), AKBASIC_ERR_SYNTAX,
|
|
"END TYPE on line %" PRId64 " closes nothing", i);
|
|
table->types[open].lastline = i;
|
|
open = -1;
|
|
continue;
|
|
}
|
|
PASS(errctx, word_is(word, "TYPE", &matched));
|
|
if ( !matched ) {
|
|
continue;
|
|
}
|
|
|
|
FAIL_ZERO_RETURN(errctx, (open < 0), AKBASIC_ERR_SYNTAX,
|
|
"TYPE on line %" PRId64 " opens inside %s, which is not closed",
|
|
i, table->types[open].name);
|
|
(void)next_word(cursor, name, sizeof(name));
|
|
FAIL_ZERO_RETURN(errctx, (name[0] != '\0'), AKBASIC_ERR_SYNTAX,
|
|
"TYPE on line %" PRId64 " has no name", i);
|
|
PASS(errctx, akbasic_structtype_find(table, name, &existing));
|
|
FAIL_NONZERO_RETURN(errctx, (existing >= 0), AKBASIC_ERR_VALUE,
|
|
"TYPE %s is declared twice", name);
|
|
/*
|
|
* A type name is a bare word, and so is every verb, so the two share a
|
|
* namespace whether we like it or not. Refused here with a message that
|
|
* says so -- left to the parser it comes out as "Expected expression or
|
|
* literal", pointing at the line rather than at the problem. This is the
|
|
* same check the scanner already makes for variable names.
|
|
*/
|
|
PASS(errctx, akbasic_verb_lookup(name, &verb));
|
|
FAIL_NONZERO_RETURN(errctx, (verb != NULL), AKBASIC_ERR_VALUE,
|
|
"TYPE %s: %s is a reserved word and cannot name a type", name, name);
|
|
FAIL_ZERO_RETURN(errctx, (table->count < AKBASIC_MAX_STRUCT_TYPES), AKBASIC_ERR_BOUNDS,
|
|
"More than %d TYPE declarations", AKBASIC_MAX_STRUCT_TYPES);
|
|
|
|
PASS(errctx, aksl_strlen(name, &namelen));
|
|
FAIL_ZERO_RETURN(errctx, (namelen < AKBASIC_MAX_STRUCT_NAME), AKBASIC_ERR_BOUNDS,
|
|
"TYPE name \"%s\" exceeds the %d character limit",
|
|
name, AKBASIC_MAX_STRUCT_NAME - 1);
|
|
open = table->count;
|
|
PASS(errctx, aksl_memset(&table->types[open], 0, sizeof(table->types[open])));
|
|
/* aksl_strcpy always terminates and refuses rather than truncates, which
|
|
is the behaviour this site already wanted -- two long names truncating
|
|
to the same prefix would collide silently. The length check above stays
|
|
because it names the limit in the message. */
|
|
PASS(errctx, aksl_strcpy(table->types[open].name, sizeof(table->types[open].name), name));
|
|
table->types[open].used = true;
|
|
table->types[open].firstline = i;
|
|
table->types[open].lastline = -1;
|
|
table->types[open].slotcount = -1; /* unresolved until pass 3 */
|
|
table->count += 1;
|
|
}
|
|
|
|
FAIL_NONZERO_RETURN(errctx, (open >= 0), AKBASIC_ERR_SYNTAX,
|
|
"TYPE %s is never closed with END TYPE", table->types[open >= 0 ? open : 0].name);
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
|
|
/* ----------------------------------------------------------------- pass 2 -- */
|
|
|
|
/** @brief Parse one field declaration line into a descriptor. */
|
|
static akerr_ErrorContext *parse_field(akbasic_StructTypeTable *table, akbasic_StructType *type,
|
|
const char *line, int64_t lineno)
|
|
{
|
|
PREPARE_ERROR(errctx);
|
|
char fieldname[STRUCT_WORD_SCRATCH];
|
|
char word[STRUCT_WORD_SCRATCH];
|
|
const char *cursor = NULL;
|
|
akbasic_StructField *field = NULL;
|
|
akbasic_Type valuetype = AKBASIC_TYPE_UNDEFINED;
|
|
size_t namelen = 0;
|
|
bool matched = false;
|
|
int referenced = 0;
|
|
int cmp = 0;
|
|
int i = 0;
|
|
|
|
cursor = next_word(skip_lineno(line), fieldname, sizeof(fieldname));
|
|
if ( fieldname[0] == '\0' || fieldname[0] == '\'' ) {
|
|
SUCCEED_RETURN(errctx); /* a blank line inside the block */
|
|
}
|
|
PASS(errctx, word_is(fieldname, "REM", &matched));
|
|
if ( matched ) {
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
|
|
FAIL_ZERO_RETURN(errctx, (type->fieldcount < AKBASIC_MAX_STRUCT_FIELDS), AKBASIC_ERR_BOUNDS,
|
|
"TYPE %s declares more than %d fields", type->name, AKBASIC_MAX_STRUCT_FIELDS);
|
|
for ( i = 0; i < type->fieldcount; i++ ) {
|
|
PASS(errctx, aksl_strcmp(type->fields[i].name, fieldname, &cmp));
|
|
FAIL_NONZERO_RETURN(errctx, (cmp == 0), AKBASIC_ERR_VALUE,
|
|
"TYPE %s declares %s twice", type->name, fieldname);
|
|
}
|
|
|
|
PASS(errctx, type_from_suffix(fieldname, &valuetype));
|
|
PASS(errctx, aksl_strlen(fieldname, &namelen));
|
|
FAIL_ZERO_RETURN(errctx, (namelen < AKBASIC_MAX_STRUCT_NAME), AKBASIC_ERR_BOUNDS,
|
|
"Field name \"%s\" exceeds the %d character limit",
|
|
fieldname, AKBASIC_MAX_STRUCT_NAME - 1);
|
|
field = &type->fields[type->fieldcount];
|
|
PASS(errctx, aksl_memset(field, 0, sizeof(*field)));
|
|
/* Refused rather than truncated, for the same reason a type name is. */
|
|
PASS(errctx, aksl_strcpy(field->name, sizeof(field->name), fieldname));
|
|
field->valuetype = valuetype;
|
|
field->typeindex = -1;
|
|
field->offset = -1;
|
|
|
|
if ( valuetype != AKBASIC_TYPE_STRUCT ) {
|
|
/* A primitive needs no more words, and anything after it is a typo. */
|
|
cursor = next_word(cursor, word, sizeof(word));
|
|
FAIL_NONZERO_RETURN(errctx, (word[0] != '\0'), AKBASIC_ERR_SYNTAX,
|
|
"Line %" PRId64 ": %s is a %s field and takes no AS clause",
|
|
lineno, fieldname,
|
|
(valuetype == AKBASIC_TYPE_INTEGER ? "integer" :
|
|
valuetype == AKBASIC_TYPE_FLOAT ? "float" : "string"));
|
|
field->kind = AKBASIC_FIELD_PRIMITIVE;
|
|
field->slotcount = 1;
|
|
type->fieldcount += 1;
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
|
|
/*
|
|
* An `@` field is a structure, and `@` alone does not say which -- three
|
|
* primitive types fit in three suffix characters and N declared types do
|
|
* not fit in one. So the declaration has to name it.
|
|
*/
|
|
cursor = next_word(cursor, word, sizeof(word));
|
|
PASS(errctx, word_is(word, "AS", &matched));
|
|
FAIL_ZERO_RETURN(errctx, matched, AKBASIC_ERR_SYNTAX,
|
|
"Line %" PRId64 ": %s must name its type -- %s AS TYPENAME, or %s AS PTR TO TYPENAME",
|
|
lineno, fieldname, fieldname, fieldname);
|
|
cursor = next_word(cursor, word, sizeof(word));
|
|
PASS(errctx, word_is(word, "PTR", &matched));
|
|
if ( matched ) {
|
|
cursor = next_word(cursor, word, sizeof(word));
|
|
PASS(errctx, word_is(word, "TO", &matched));
|
|
FAIL_ZERO_RETURN(errctx, matched, AKBASIC_ERR_SYNTAX,
|
|
"Line %" PRId64 ": expected PTR TO TYPENAME", lineno);
|
|
cursor = next_word(cursor, word, sizeof(word));
|
|
field->kind = AKBASIC_FIELD_POINTER;
|
|
field->valuetype = AKBASIC_TYPE_POINTER;
|
|
field->slotcount = 1; /* a reference, whatever it points at */
|
|
} else {
|
|
field->kind = AKBASIC_FIELD_STRUCT;
|
|
field->slotcount = -1; /* the nested type's size, in pass 3 */
|
|
}
|
|
|
|
FAIL_ZERO_RETURN(errctx, (word[0] != '\0'), AKBASIC_ERR_SYNTAX,
|
|
"Line %" PRId64 ": %s names no type", lineno, fieldname);
|
|
PASS(errctx, akbasic_structtype_find(table, word, &referenced));
|
|
FAIL_ZERO_RETURN(errctx, (referenced >= 0), AKBASIC_ERR_UNDEFINED,
|
|
"Line %" PRId64 ": %s names TYPE %s, which is not declared",
|
|
lineno, fieldname, word);
|
|
field->typeindex = referenced;
|
|
type->fieldcount += 1;
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
|
|
/* ----------------------------------------------------------------- pass 3 -- */
|
|
|
|
/**
|
|
* @brief Resolve sizes and offsets, and diagnose by-value recursion.
|
|
*
|
|
* A type whose fields are all sized can be sized. Repeating that until nothing
|
|
* changes resolves every legal arrangement however the declarations were
|
|
* ordered -- and whatever is left unresolved is a group of types that contain
|
|
* each other by value, which has no finite size. That is the diagnosis rather
|
|
* than a stack overflow later.
|
|
*/
|
|
static akerr_ErrorContext *resolve_sizes(akbasic_StructTypeTable *table)
|
|
{
|
|
PREPARE_ERROR(errctx);
|
|
bool progress = true;
|
|
int i = 0;
|
|
int f = 0;
|
|
|
|
while ( progress ) {
|
|
progress = false;
|
|
for ( i = 0; i < table->count; i++ ) {
|
|
akbasic_StructType *type = &table->types[i];
|
|
int offset = 0;
|
|
bool ready = true;
|
|
|
|
if ( type->slotcount >= 0 ) {
|
|
continue;
|
|
}
|
|
for ( f = 0; f < type->fieldcount; f++ ) {
|
|
akbasic_StructField *field = &type->fields[f];
|
|
if ( field->kind == AKBASIC_FIELD_STRUCT ) {
|
|
if ( table->types[field->typeindex].slotcount < 0 ) {
|
|
ready = false;
|
|
break;
|
|
}
|
|
field->slotcount = table->types[field->typeindex].slotcount;
|
|
}
|
|
field->offset = offset;
|
|
offset += field->slotcount;
|
|
}
|
|
if ( ready ) {
|
|
type->slotcount = offset;
|
|
progress = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
for ( i = 0; i < table->count; i++ ) {
|
|
FAIL_NONZERO_RETURN(errctx, (table->types[i].slotcount < 0), AKBASIC_ERR_VALUE,
|
|
"TYPE %s contains itself by value, so it has no size. "
|
|
"A type may only refer to itself through PTR TO",
|
|
table->types[i].name);
|
|
FAIL_NONZERO_RETURN(errctx, (table->types[i].slotcount == 0), AKBASIC_ERR_VALUE,
|
|
"TYPE %s declares no fields", table->types[i].name);
|
|
}
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
|
|
akerr_ErrorContext *akbasic_structtype_scan(akbasic_Runtime *obj)
|
|
{
|
|
PREPARE_ERROR(errctx);
|
|
akbasic_StructTypeTable *table = NULL;
|
|
int64_t i = 0;
|
|
int t = 0;
|
|
|
|
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL runtime in structtype scan");
|
|
table = &obj->structtypes;
|
|
|
|
/*
|
|
* Drop what the *script* declared and keep what the *host* registered.
|
|
*
|
|
* This prescan runs again on every RUN, so wiping the table outright would
|
|
* unregister a host's types the first time a script was run -- and the host
|
|
* registered them before loading anything, with no way to know it had to do
|
|
* it again. Host types are registered before a program exists, so they are
|
|
* always a prefix; truncating to that prefix keeps every index a field
|
|
* already recorded pointing at the same type.
|
|
*/
|
|
for ( t = 0; t < table->count; t++ ) {
|
|
if ( !table->types[t].ishost ) {
|
|
break;
|
|
}
|
|
}
|
|
for ( i = t; i < table->count; i++ ) {
|
|
PASS(errctx, aksl_memset(&table->types[i], 0, sizeof(table->types[i])));
|
|
}
|
|
table->count = t;
|
|
|
|
PASS(errctx, scan_names(obj));
|
|
|
|
for ( t = 0; t < table->count; t++ ) {
|
|
akbasic_StructType *type = &table->types[t];
|
|
for ( i = type->firstline + 1; i < type->lastline; i++ ) {
|
|
if ( obj->source[i].code[0] == '\0' ) {
|
|
continue;
|
|
}
|
|
PASS(errctx, parse_field(table, type, obj->source[i].code, i));
|
|
}
|
|
}
|
|
|
|
PASS(errctx, resolve_sizes(table));
|
|
SUCCEED_RETURN(errctx);
|
|
}
|