/** * @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 #include #include #include #include #include #include #include #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. */ static bool word_is(const char *word, const char *keyword) { return (strcmp(word, keyword) == 0); } /** * @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 = strlen(name); 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"); 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; 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 && strcmp(obj->types[i].name, name) == 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]; akbasic_StructType *type = NULL; size_t used = 0; int i = 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++ ) { if ( strcmp(type->fields[i].name, name) == 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++ ) { int written = snprintf(known + used, sizeof(known) - used, "%s%s", (i == 0 ? "" : ", "), type->fields[i].name); if ( written < 0 || (size_t)written >= sizeof(known) - used ) { break; } 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; 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)); if ( word_is(word, "END") ) { (void)next_word(cursor, name, sizeof(name)); if ( !word_is(name, "TYPE") ) { 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; } if ( !word_is(word, "TYPE") ) { 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); FAIL_ZERO_RETURN(errctx, (strlen(name) < AKBASIC_MAX_STRUCT_NAME), AKBASIC_ERR_BOUNDS, "TYPE name \"%s\" exceeds the %d character limit", name, AKBASIC_MAX_STRUCT_NAME - 1); open = table->count; memset(&table->types[open], 0, sizeof(table->types[open])); /* The memset is what terminates this: bounded at size - 1, the last byte is the zero it already wrote. Refused above rather than truncated, because two long names truncating to the same prefix would collide silently -- and truncation is an error everywhere else here. */ strncpy(table->types[open].name, name, sizeof(table->types[open].name) - 1); 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; int referenced = 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 */ } if ( word_is(fieldname, "REM") ) { 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++ ) { FAIL_NONZERO_RETURN(errctx, (strcmp(type->fields[i].name, fieldname) == 0), AKBASIC_ERR_VALUE, "TYPE %s declares %s twice", type->name, fieldname); } PASS(errctx, type_from_suffix(fieldname, &valuetype)); FAIL_ZERO_RETURN(errctx, (strlen(fieldname) < 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]; memset(field, 0, sizeof(*field)); /* Terminated by the memset above; refused rather than truncated, for the same reason a type name is. */ strncpy(field->name, fieldname, sizeof(field->name) - 1); 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)); FAIL_ZERO_RETURN(errctx, word_is(word, "AS"), 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)); if ( word_is(word, "PTR") ) { cursor = next_word(cursor, word, sizeof(word)); FAIL_ZERO_RETURN(errctx, word_is(word, "TO"), 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++ ) { 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); }