Some checks failed
akbasic CI Build / akgl_build (push) Has been cancelled
akbasic CI Build / mutation_test (push) Has been cancelled
akbasic CI Build / coverage (push) Has been cancelled
akbasic CI Build / cmake_build (push) Has been cancelled
akbasic CI Build / sanitizers (push) Has been cancelled
526 lines
19 KiB
C
526 lines
19 KiB
C
/**
|
|
* @file renumber.c
|
|
* @brief Walking a program's branch targets, and the two things that do it.
|
|
*
|
|
* **RENUMBER** moves every line and rewrites every reference to one. Moving the
|
|
* lines is easy -- source is filed under its line number in `source[]`, so it is
|
|
* a permutation. Rewriting every `GOTO`, `GOSUB`, `RUN`, `RESTORE`, `TRAP` and
|
|
* `COLLISION` target to match is the job, and it is why this was deferred rather
|
|
* than written early: a `RENUMBER` that moved lines without fixing their
|
|
* references would silently break every branch in the program, which is worse
|
|
* than not having the verb at all.
|
|
*
|
|
* **akbasic_runtime_check_targets()** walks the same targets without moving
|
|
* anything, refusing a program that branches by number to a line the program
|
|
* never numbered. It shares this file because it needs exactly the same walk,
|
|
* and a second copy of it would drift.
|
|
*
|
|
* The walk is textual, and the only thing it has to understand about the rest of
|
|
* the language is where a string literal starts and stops -- `PRINT "GOTO 10"`
|
|
* must come through untouched. That is the same statement-walking the label and
|
|
* DATA prescans do. What the two callers differ in is one function pointer:
|
|
* given a target, say what text should stand in its place, and raise if it is not
|
|
* acceptable.
|
|
*/
|
|
|
|
#include <ctype.h>
|
|
#include <inttypes.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <akerror.h>
|
|
#include <akstdlib.h>
|
|
|
|
#include <akbasic/error.h>
|
|
#include <akbasic/runtime.h>
|
|
|
|
/**
|
|
* @brief Verbs whose numeric arguments name a line.
|
|
*
|
|
* `THEN` and `ELSE` are deliberately absent. Commodore BASIC lets `IF X THEN 100`
|
|
* stand for `THEN GOTO 100`; this dialect does not -- the corpus writes
|
|
* `THEN GOTO 100` -- so a number after `THEN` here is an expression, not a target.
|
|
*
|
|
* `LIST` and `DELETE` are absent too, and for a different reason: their
|
|
* arguments are ranges typed at a prompt, not references stored in a program.
|
|
* Renumbering a `DELETE` inside a listing would be rewriting something nobody
|
|
* meant as a branch.
|
|
*/
|
|
static const char *BRANCH_VERBS[] = { "GOTO", "GOSUB", "RUN", "RESTORE", "TRAP" };
|
|
/** @brief How many entries #BRANCH_VERBS has. */
|
|
#define BRANCH_VERB_COUNT ((int)(sizeof(BRANCH_VERBS) / sizeof(BRANCH_VERBS[0])))
|
|
|
|
/**
|
|
* @brief What a walk does with each numeric branch target it finds.
|
|
*
|
|
* The one thing RENUMBER and the target check differ in. `visit` is handed a
|
|
* target and writes into @p dest the text that should stand in its place --
|
|
* the new number for RENUMBER, the same number for a check -- and may raise to
|
|
* refuse the line outright.
|
|
*/
|
|
typedef struct akbasic_TargetWalk akbasic_TargetWalk;
|
|
struct akbasic_TargetWalk
|
|
{
|
|
void *self;
|
|
akerr_ErrorContext *(*visit)(akbasic_TargetWalk *walk, int64_t target, char *dest, size_t len);
|
|
};
|
|
|
|
/**
|
|
* @brief Where a line moved to, or the line itself when it did not move.
|
|
*
|
|
* A target naming a line that does not exist is left alone rather than
|
|
* 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 int16_t *map, int64_t line)
|
|
{
|
|
if ( line < 0 || line >= AKBASIC_MAX_SOURCE_LINES ) {
|
|
return line;
|
|
}
|
|
return (map[line] >= 0 ? map[line] : line);
|
|
}
|
|
|
|
/**
|
|
* @brief Copy a run of comma-separated line numbers, visiting each.
|
|
*
|
|
* `ON X GOTO 100, 200, 300` is why this takes a list rather than one number: the
|
|
* targets follow the `GOTO`, so handling `GOTO` handles `ON` for free.
|
|
*
|
|
* @param walk What to substitute for each target, and whether to allow it.
|
|
* @param src Reads from here; advanced past what was consumed.
|
|
* @param dest Writes to here; advanced past what was written.
|
|
* @param limit One past the last byte @p dest may write.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKBASIC_ERR_BOUNDS When the rewritten line does not fit.
|
|
*/
|
|
static akerr_ErrorContext *rewrite_targets(akbasic_TargetWalk *walk, const char **src, char **dest, const char *limit)
|
|
{
|
|
PREPARE_ERROR(errctx);
|
|
const char *p = *src;
|
|
char *out = *dest;
|
|
|
|
for ( ;; ) {
|
|
char replacement[32];
|
|
int64_t line = 0;
|
|
size_t written = 0;
|
|
|
|
while ( *p == ' ' || *p == '\t' ) {
|
|
FAIL_ZERO_RETURN(errctx, (out < limit), AKBASIC_ERR_BOUNDS,
|
|
"RENUMBER: a rewritten line does not fit");
|
|
*out = *p;
|
|
out += 1;
|
|
p += 1;
|
|
}
|
|
if ( !isdigit((unsigned char)*p) ) {
|
|
/*
|
|
* Not a number: a label, an expression, or nothing at all. Labels are
|
|
* the reason RENUMBER is survivable in the first place -- a program
|
|
* written with `GOTO DONE` needs no rewriting and cannot be broken by
|
|
* one.
|
|
*/
|
|
break;
|
|
}
|
|
while ( isdigit((unsigned char)*p) ) {
|
|
line = (line * 10) + (*p - '0');
|
|
p += 1;
|
|
}
|
|
PASS(errctx, walk->visit(walk, line, replacement, sizeof(replacement)));
|
|
/*
|
|
* The fit is decided before the copy rather than read back out of a
|
|
* return value afterwards: aksl_strcpy refuses rather than truncates,
|
|
* so the bounds check has to come first to keep the diagnosis this
|
|
* function already gives.
|
|
*/
|
|
PASS(errctx, aksl_strlen(replacement, &written));
|
|
FAIL_ZERO_RETURN(errctx, (written > 0 && out + written < limit), AKBASIC_ERR_BOUNDS,
|
|
"RENUMBER: a rewritten line does not fit");
|
|
PASS(errctx, aksl_strcpy(out, (size_t)(limit - out), replacement));
|
|
out += written;
|
|
|
|
/*
|
|
* A comma continues the list; anything else ends it. The spaces are
|
|
* skipped to find out which, and put back if the answer is "ends" --
|
|
* without that, `THEN GOTO 9 ELSE ...` came back as `THEN GOTO 30ELSE`.
|
|
*/
|
|
{
|
|
const char *afterdigits = p;
|
|
|
|
while ( *p == ' ' || *p == '\t' ) {
|
|
p += 1;
|
|
}
|
|
if ( *p != ',' ) {
|
|
p = afterdigits;
|
|
break;
|
|
}
|
|
}
|
|
FAIL_ZERO_RETURN(errctx, (out < limit), AKBASIC_ERR_BOUNDS,
|
|
"RENUMBER: a rewritten line does not fit");
|
|
*out = ',';
|
|
out += 1;
|
|
p += 1;
|
|
}
|
|
*src = p;
|
|
*dest = out;
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
|
|
/**
|
|
* @brief Rewrite every branch target in one line of source.
|
|
*
|
|
* @param walk What to substitute for each target, and whether to allow it.
|
|
* @param code The line to read.
|
|
* @param dest Where the rewritten line goes.
|
|
* @param len Capacity of @p dest.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKBASIC_ERR_BOUNDS When the rewritten line does not fit.
|
|
*/
|
|
static akerr_ErrorContext *rewrite_line(akbasic_TargetWalk *walk, const char *code, char *dest, size_t len)
|
|
{
|
|
PREPARE_ERROR(errctx);
|
|
const char *p = code;
|
|
char *out = dest;
|
|
const char *limit = dest + len - 1;
|
|
bool statementstart = true;
|
|
bool instring = false;
|
|
int cmp = 0;
|
|
int i = 0;
|
|
|
|
while ( *p != '\0' ) {
|
|
size_t verblen = 0;
|
|
bool matched = false;
|
|
|
|
FAIL_ZERO_RETURN(errctx, (out < limit), AKBASIC_ERR_BOUNDS,
|
|
"RENUMBER: a rewritten line does not fit");
|
|
if ( instring ) {
|
|
instring = (*p != '"');
|
|
*out = *p;
|
|
out += 1;
|
|
p += 1;
|
|
continue;
|
|
}
|
|
if ( *p == '"' ) {
|
|
/* A string literal is copied through untouched: `PRINT "GOTO 10"`. */
|
|
instring = true;
|
|
statementstart = false;
|
|
*out = *p;
|
|
out += 1;
|
|
p += 1;
|
|
continue;
|
|
}
|
|
if ( *p == ':' ) {
|
|
statementstart = true;
|
|
*out = *p;
|
|
out += 1;
|
|
p += 1;
|
|
continue;
|
|
}
|
|
if ( *p == ' ' || *p == '\t' ) {
|
|
*out = *p;
|
|
out += 1;
|
|
p += 1;
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* **Both ends, not just the trailing one.** The check below used to look
|
|
* only at the character *after* a match, so a name ending in a branch
|
|
* verb matched inside itself: `RCOLLISION(1, 1)` found `COLLISION` at its
|
|
* second character, read the `(1,` that followed as a handler argument,
|
|
* and refused the whole program with "branch to line 1, which the program
|
|
* did not number" -- naming a line that contains no branch at all.
|
|
*
|
|
* The comment below is right that the trailing check is what protects
|
|
* `GOTOX#`. Nothing protected `XGOTO#`, and `RCOLLISION` is the first
|
|
* name in the language to reach it.
|
|
*/
|
|
for ( i = 0; i < BRANCH_VERB_COUNT; i++ ) {
|
|
PASS(errctx, aksl_strlen(BRANCH_VERBS[i], &verblen));
|
|
if ( p > code && isalnum((unsigned char)p[-1]) ) {
|
|
break;
|
|
}
|
|
PASS(errctx, aksl_strncasecmp(p, BRANCH_VERBS[i], verblen, &cmp));
|
|
if ( cmp == 0 && !isalnum((unsigned char)p[verblen]) ) {
|
|
matched = true;
|
|
break;
|
|
}
|
|
}
|
|
/*
|
|
* A branch verb may appear anywhere a statement may, and `GOTO` also
|
|
* follows `THEN`, `ELSE` and `ON X` -- none of which is a statement
|
|
* start. So the match is not gated on statementstart; what protects
|
|
* `A$ = "GOTO"` is the string check above, and what protects a variable
|
|
* called `GOTOX#` is the alphanumeric check here.
|
|
*/
|
|
if ( matched ) {
|
|
FAIL_ZERO_RETURN(errctx, (out + verblen < (size_t)(limit - dest) + dest),
|
|
AKBASIC_ERR_BOUNDS, "RENUMBER: a rewritten line does not fit");
|
|
PASS(errctx, aksl_memcpy(out, p, verblen));
|
|
out += verblen;
|
|
p += verblen;
|
|
PASS(errctx, rewrite_targets(walk, &p, &out, limit));
|
|
statementstart = false;
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* COLLISION's handler is its *second* argument, so the list rewrite
|
|
* cannot be pointed at it directly. Copy the type and the comma, then
|
|
* rewrite what follows.
|
|
*/
|
|
PASS(errctx, aksl_strncasecmp(p, "COLLISION", 9, &cmp));
|
|
if ( cmp == 0 && !isalnum((unsigned char)p[9])
|
|
&& !(p > code && isalnum((unsigned char)p[-1])) ) {
|
|
PASS(errctx, aksl_memcpy(out, p, 9));
|
|
out += 9;
|
|
p += 9;
|
|
while ( *p != '\0' && *p != ',' && *p != ':' ) {
|
|
FAIL_ZERO_RETURN(errctx, (out < limit), AKBASIC_ERR_BOUNDS,
|
|
"RENUMBER: a rewritten line does not fit");
|
|
*out = *p;
|
|
out += 1;
|
|
p += 1;
|
|
}
|
|
if ( *p == ',' ) {
|
|
*out = ',';
|
|
out += 1;
|
|
p += 1;
|
|
PASS(errctx, rewrite_targets(walk, &p, &out, limit));
|
|
}
|
|
statementstart = false;
|
|
continue;
|
|
}
|
|
|
|
statementstart = false;
|
|
*out = *p;
|
|
out += 1;
|
|
p += 1;
|
|
}
|
|
*out = '\0';
|
|
(void)statementstart;
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
|
|
/* ----------------------------------------------------------- RENUMBER -- */
|
|
|
|
/** @brief RENUMBER's visitor: substitute the number the line moved to. */
|
|
static akerr_ErrorContext *visit_renumber(akbasic_TargetWalk *walk, int64_t target, char *dest, size_t len)
|
|
{
|
|
PREPARE_ERROR(errctx);
|
|
const int16_t *map = (const int16_t *)walk->self;
|
|
int written = 0;
|
|
|
|
PASS(errctx, aksl_snprintf(&written, dest, len, "%" PRId64, mapped(map, target)));
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
|
|
akerr_ErrorContext *akbasic_renumber(akbasic_Runtime *obj, int64_t newstart, int64_t increment, int64_t oldstart)
|
|
{
|
|
PREPARE_ERROR(errctx);
|
|
int16_t *map = obj == NULL ? NULL : obj->renumber_map;
|
|
akbasic_TargetWalk walk = { map, visit_renumber };
|
|
int64_t next = newstart;
|
|
int64_t i = 0;
|
|
|
|
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL runtime in renumber");
|
|
FAIL_ZERO_RETURN(errctx, (increment > 0), AKBASIC_ERR_VALUE,
|
|
"RENUMBER's increment must be positive, not %" PRId64, increment);
|
|
FAIL_ZERO_RETURN(errctx, (newstart > 0), AKBASIC_ERR_VALUE,
|
|
"RENUMBER cannot start at line %" PRId64, newstart);
|
|
|
|
for ( i = 0; i < AKBASIC_MAX_SOURCE_LINES; i++ ) {
|
|
map[i] = -1;
|
|
}
|
|
|
|
/*
|
|
* Build the whole map before touching anything. A rewrite needs to know
|
|
* where *every* line ended up, including ones it has not reached yet --
|
|
* a backward `GOTO` is as common as a forward one.
|
|
*/
|
|
for ( i = 0; i < AKBASIC_MAX_SOURCE_LINES; i++ ) {
|
|
if ( obj->source[i].code[0] == '\0' || i < oldstart ) {
|
|
continue;
|
|
}
|
|
FAIL_ZERO_RETURN(errctx, (next < AKBASIC_MAX_SOURCE_LINES), AKBASIC_ERR_BOUNDS,
|
|
"RENUMBER: line %" PRId64 " would become %" PRId64 ", past the %d line limit",
|
|
i, next, AKBASIC_MAX_SOURCE_LINES - 1);
|
|
/*
|
|
* A renumbered line must not land on a kept one. Refused rather than
|
|
* overwritten: losing a line to a renumbering is not recoverable.
|
|
*/
|
|
FAIL_ZERO_RETURN(errctx, (next >= oldstart || obj->source[next].code[0] == '\0'),
|
|
AKBASIC_ERR_VALUE,
|
|
"RENUMBER: line %" PRId64 " would become %" PRId64 ", which already exists",
|
|
i, next);
|
|
map[i] = next;
|
|
next += increment;
|
|
}
|
|
|
|
PASS(errctx, aksl_memset(obj->renumber_visited, 0, sizeof(obj->renumber_visited)));
|
|
for ( i = 0; i < AKBASIC_MAX_SOURCE_LINES; i++ ) {
|
|
if ( obj->source[i].code[0] == '\0' ) {
|
|
continue;
|
|
}
|
|
/*
|
|
* Every line is rewritten, not just the moved ones: a line before
|
|
* `oldstart` can branch into the region that moved.
|
|
*/
|
|
PASS(errctx, rewrite_line(&walk, obj->source[i].code,
|
|
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.
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
|
|
/* ------------------------------------------------- the target prescan -- */
|
|
|
|
/** @brief What visit_check() needs to name the line it refused. */
|
|
typedef struct
|
|
{
|
|
const akbasic_Runtime *runtime;
|
|
int64_t lineno;
|
|
} CheckState;
|
|
|
|
/**
|
|
* @brief The prescan's visitor: allow the target, or refuse the program.
|
|
*
|
|
* A number that names a line the program never numbered is refused. In a script
|
|
* written without line numbers `GOTO 100` finds the hundredth line and branches
|
|
* there -- plausible, silent, and wrong, which is the worst kind of defect to be
|
|
* handed at run time.
|
|
*
|
|
* A target naming an *empty* slot is allowed through, for the same reason
|
|
* mapped() leaves one alone: `GOTO 9999` in a program with no line 9999 is
|
|
* already broken, and refusing it here would be inventing a rule about lines
|
|
* that do not exist. The substitution is the number itself, because nothing is
|
|
* being rewritten.
|
|
*/
|
|
static akerr_ErrorContext *visit_check(akbasic_TargetWalk *walk, int64_t target, char *dest, size_t len)
|
|
{
|
|
PREPARE_ERROR(errctx);
|
|
CheckState *state = (CheckState *)walk->self;
|
|
int written = 0;
|
|
|
|
PASS(errctx, aksl_snprintf(&written, dest, len, "%" PRId64, target));
|
|
if ( target >= 0 && target < AKBASIC_MAX_SOURCE_LINES ) {
|
|
FAIL_NONZERO_RETURN(errctx,
|
|
(state->runtime->source[target].code[0] != '\0'
|
|
&& !state->runtime->source[target].numbered),
|
|
AKBASIC_ERR_SYNTAX,
|
|
"Line %" PRId64 ": branch to line %" PRId64
|
|
", which the program did not number. Branch by LABEL, or RENUMBER first",
|
|
state->lineno, target);
|
|
}
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
|
|
akerr_ErrorContext *akbasic_runtime_check_targets(akbasic_Runtime *obj)
|
|
{
|
|
PREPARE_ERROR(errctx);
|
|
/*
|
|
* Static for the same reason akbasic_renumber()'s buffers are: a line's worth
|
|
* of rewrite does not belong on a default stack alongside the rest of a
|
|
* step(). Nothing is read back out of it -- the walk needs somewhere to put
|
|
* the text it would have written, and this is it.
|
|
*/
|
|
CheckState state = { NULL, 0 };
|
|
akbasic_TargetWalk walk = { &state, visit_check };
|
|
int64_t entry = 0;
|
|
int64_t i = 0;
|
|
|
|
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL runtime in check_targets");
|
|
state.runtime = obj;
|
|
entry = obj->environment->lineno;
|
|
|
|
for ( i = 0; i < AKBASIC_MAX_SOURCE_LINES; i++ ) {
|
|
if ( obj->source[i].code[0] == '\0' ) {
|
|
continue;
|
|
}
|
|
state.lineno = i;
|
|
/*
|
|
* Point the runtime at the line being walked, so the `? N :` prefix
|
|
* akbasic_runtime_error() builds from environment->lineno names the
|
|
* offending line rather than whichever one the loader stopped on. The
|
|
* message repeats the number because the other three prescans do not do
|
|
* this and get it wrong -- see TODO.md.
|
|
*
|
|
* PASS inside the loop, never CATCH: CATCH expands to a break, which
|
|
* would leave this loop rather than an enclosing ATTEMPT. That also means
|
|
* the cursor is deliberately *not* restored on the failure path, which is
|
|
* the whole point of setting it.
|
|
*/
|
|
obj->environment->lineno = i;
|
|
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;
|
|
SUCCEED_RETURN(errctx);
|
|
}
|