diff --git a/include/akbasic/runtime.h b/include/akbasic/runtime.h index 27f3562..7b9cda8 100644 --- a/include/akbasic/runtime.h +++ b/include/akbasic/runtime.h @@ -127,6 +127,12 @@ typedef struct akbasic_Runtime { akbasic_SourceLine source[AKBASIC_MAX_SOURCE_LINES]; + /* Scratch owned by this runtime for RENUMBER and its target prescan. */ + int16_t renumber_map[AKBASIC_MAX_SOURCE_LINES]; + uint8_t renumber_visited[AKBASIC_MAX_SOURCE_LINES]; + akbasic_SourceLine renumber_line; + char renumber_discard[AKBASIC_MAX_LINE_LENGTH * 2]; + /* Pools. Nothing here is malloc'd; everything is drawn from and returned. */ akbasic_Environment environments[AKBASIC_MAX_ENVIRONMENTS]; akbasic_Variable variables[AKBASIC_MAX_VARIABLES]; diff --git a/src/renumber.c b/src/renumber.c index 692d23c..54f5aec 100644 --- a/src/renumber.c +++ b/src/renumber.c @@ -72,7 +72,7 @@ struct akbasic_TargetWalk * rewritten: `GOTO 9999` in a program with no line 9999 is already broken, and * inventing a destination for it would hide that. */ -static int64_t mapped(const int64_t *map, int64_t line) +static int64_t mapped(const int16_t *map, int64_t line) { if ( line < 0 || line >= AKBASIC_MAX_SOURCE_LINES ) { return line; @@ -306,7 +306,7 @@ static akerr_ErrorContext *rewrite_line(akbasic_TargetWalk *walk, const char *co static akerr_ErrorContext *visit_renumber(akbasic_TargetWalk *walk, int64_t target, char *dest, size_t len) { PREPARE_ERROR(errctx); - const int64_t *map = (const int64_t *)walk->self; + const int16_t *map = (const int16_t *)walk->self; int written = 0; PASS(errctx, aksl_snprintf(&written, dest, len, "%" PRId64, mapped(map, target))); @@ -316,8 +316,7 @@ static akerr_ErrorContext *visit_renumber(akbasic_TargetWalk *walk, int64_t targ akerr_ErrorContext *akbasic_renumber(akbasic_Runtime *obj, int64_t newstart, int64_t increment, int64_t oldstart) { PREPARE_ERROR(errctx); - static int64_t map[AKBASIC_MAX_SOURCE_LINES]; - static akbasic_SourceLine rewritten[AKBASIC_MAX_SOURCE_LINES]; + int16_t *map = obj == NULL ? NULL : obj->renumber_map; akbasic_TargetWalk walk = { map, visit_renumber }; int64_t next = newstart; int64_t i = 0; @@ -356,10 +355,8 @@ akerr_ErrorContext *akbasic_renumber(akbasic_Runtime *obj, int64_t newstart, int next += increment; } - PASS(errctx, aksl_memset(rewritten, 0, sizeof(rewritten))); + PASS(errctx, aksl_memset(obj->renumber_visited, 0, sizeof(obj->renumber_visited))); for ( i = 0; i < AKBASIC_MAX_SOURCE_LINES; i++ ) { - int64_t target = 0; - if ( obj->source[i].code[0] == '\0' ) { continue; } @@ -367,20 +364,77 @@ akerr_ErrorContext *akbasic_renumber(akbasic_Runtime *obj, int64_t newstart, int * Every line is rewritten, not just the moved ones: a line before * `oldstart` can branch into the region that moved. */ - target = mapped(map, i); PASS(errctx, rewrite_line(&walk, obj->source[i].code, - rewritten[target].code, sizeof(rewritten[target].code))); - rewritten[target].lineno = target; + obj->renumber_line.code, sizeof(obj->renumber_line.code))); + obj->renumber_line.lineno = i; /* * Every line comes out numbered, whether or not it went in that way. * Asking for numbers is what RENUMBER is, and a program that has been * through it can be branched into by number -- which is the whole point * of running it over source that arrived without any. */ - rewritten[target].numbered = true; + obj->renumber_line.numbered = true; + PASS(errctx, aksl_strncpy(obj->source[i].code, AKBASIC_MAX_LINE_LENGTH, + obj->renumber_line.code, AKBASIC_MAX_LINE_LENGTH)); + obj->source[i].lineno = obj->renumber_line.lineno; + obj->source[i].numbered = obj->renumber_line.numbered; } - PASS(errctx, aksl_memcpy(obj->source, rewritten, sizeof(obj->source))); + /* Move the already-rewritten lines in place. The map is a partial + * permutation: a chain ends at an empty slot, while a cycle closes back + * on its starting line. A single displaced line is sufficient for both. */ + for ( i = 0; i < AKBASIC_MAX_SOURCE_LINES; i++ ) { + int64_t current = i; + akbasic_SourceLine displaced; + akbasic_SourceLine next_line; + + if ( map[i] < 0 || obj->renumber_visited[i] ) { + continue; + } + PASS(errctx, aksl_strncpy(displaced.code, AKBASIC_MAX_LINE_LENGTH, + obj->source[i].code, AKBASIC_MAX_LINE_LENGTH)); + displaced.lineno = obj->source[i].lineno; + displaced.numbered = obj->source[i].numbered; + for ( ;; ) { + int64_t destination = map[current]; + + obj->renumber_visited[current] = 1; + if ( destination == i ) { + displaced.lineno = destination; + PASS(errctx, aksl_strncpy(obj->source[destination].code, AKBASIC_MAX_LINE_LENGTH, + displaced.code, AKBASIC_MAX_LINE_LENGTH)); + obj->source[destination].lineno = displaced.lineno; + obj->source[destination].numbered = displaced.numbered; + break; + } + if ( map[destination] < 0 ) { + displaced.lineno = destination; + PASS(errctx, aksl_strncpy(obj->source[destination].code, AKBASIC_MAX_LINE_LENGTH, + displaced.code, AKBASIC_MAX_LINE_LENGTH)); + obj->source[destination].lineno = displaced.lineno; + obj->source[destination].numbered = displaced.numbered; + PASS(errctx, aksl_memset(&obj->source[current], 0, + sizeof(obj->source[current]))); + break; + } + PASS(errctx, aksl_strncpy(next_line.code, AKBASIC_MAX_LINE_LENGTH, + obj->source[destination].code, AKBASIC_MAX_LINE_LENGTH)); + next_line.lineno = obj->source[destination].lineno; + next_line.numbered = obj->source[destination].numbered; + displaced.lineno = destination; + PASS(errctx, aksl_strncpy(obj->source[destination].code, AKBASIC_MAX_LINE_LENGTH, + displaced.code, AKBASIC_MAX_LINE_LENGTH)); + obj->source[destination].lineno = displaced.lineno; + obj->source[destination].numbered = displaced.numbered; + PASS(errctx, aksl_memset(&obj->source[current], 0, + sizeof(obj->source[current]))); + PASS(errctx, aksl_strncpy(displaced.code, AKBASIC_MAX_LINE_LENGTH, + next_line.code, AKBASIC_MAX_LINE_LENGTH)); + displaced.lineno = next_line.lineno; + displaced.numbered = next_line.numbered; + current = destination; + } + } SUCCEED_RETURN(errctx); } @@ -435,7 +489,6 @@ akerr_ErrorContext *akbasic_runtime_check_targets(akbasic_Runtime *obj) * step(). Nothing is read back out of it -- the walk needs somewhere to put * the text it would have written, and this is it. */ - static char discard[AKBASIC_MAX_LINE_LENGTH * 2]; CheckState state = { NULL, 0 }; akbasic_TargetWalk walk = { &state, visit_check }; int64_t entry = 0; @@ -463,7 +516,8 @@ akerr_ErrorContext *akbasic_runtime_check_targets(akbasic_Runtime *obj) * the whole point of setting it. */ obj->environment->lineno = i; - PASS(errctx, rewrite_line(&walk, obj->source[i].code, discard, sizeof(discard))); + PASS(errctx, rewrite_line(&walk, obj->source[i].code, + obj->renumber_discard, sizeof(obj->renumber_discard))); } /* Nothing was refused, so leave the cursor as the caller had it. */ obj->environment->lineno = entry;