myxce/src/harness/autovars.c
Rongsong Shen 1812a80d19
feat(job-array-one-based-index): harness $jobid[...] substitution (task 4.2)
Auto-variable substitution moves out of test.c into its own unit-tested
module (autovars.c) with an explicit context for $jobid, $? and
$jobcount, keeping today's token rule, messages and buffer handling.
New: a token $jobid[...] expands $jobid and keeps the rest of the token,
so $jobid[3] names element 3 of the array just submitted; it fails like
an unset $jobid when none is set.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 10:56:15 +00:00

126 lines
3.9 KiB
C

/*
* autovars.c -- xce_test auto-variable substitution (see include/autovars.h).
*/
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include "autovars.h"
static void set_err(char* errbuf, size_t errlen, const char* msg)
{
if ((errbuf != NULL) && (errlen > 0)) {
snprintf(errbuf, errlen, "%s", msg);
}
}
int autovar_substitute(const char* line, const autovar_ctx_t* ctx, char* out,
size_t outlen, char* errbuf, size_t errlen)
{
const char* p = line;
const char* first;
size_t o = 0;
if ((errbuf != NULL) && (errlen > 0)) {
errbuf[0] = '\0';
}
if ((line == NULL) || (ctx == NULL) || (out == NULL) || (outlen == 0)) {
return -EINVAL;
}
/* `!` shell-escape and `#` comment lines are left untouched. */
first = line;
while ((*first == ' ') || (*first == '\t')) {
first++;
}
if ((*first == '!') || (*first == '#')) {
size_t len = strlen(line);
if (len >= outlen) {
return -ENOSPC;
}
memcpy(out, line, len + 1);
return 0;
}
while (*p != '\0') {
/* A `$` starts a candidate auto-var only when it begins a token, i.e.
* the previous char is the line start or whitespace. */
int at_token_start
= (p == line) || (*(p - 1) == ' ') || (*(p - 1) == '\t');
if ((*p == '$') && at_token_start) {
const char* name = p + 1;
const char* end = name;
const char* repl = NULL;
const char* stop; /* where the replaced text ends */
char numbuf[32];
/* Scan to the token end (whitespace or NUL). */
while ((*end != '\0') && (*end != ' ') && (*end != '\t')) {
end++;
}
size_t namelen = (size_t)(end - name);
/* `$jobid[...]` (Req 4.10): only `$jobid` is replaced; the rest
* of the token, from the `[`, is copied unchanged. */
int jobid_index
= (namelen > 5) && (strncmp(name, "jobid[", 6) == 0);
stop = end;
if ((namelen == 1) && (name[0] == '?')) {
snprintf(numbuf, sizeof(numbuf), "%d", ctx->last_status);
repl = numbuf;
} else if (jobid_index
|| ((namelen == 5)
&& (strncmp(name, "jobid", 5) == 0))) {
/* Req 4.11: an unset $jobid fails `$jobid[...]` the same
* way as `$jobid`. */
if ((ctx->jobid == NULL) || (ctx->jobid[0] == '\0')) {
set_err(errbuf, errlen,
"autovar: $jobid referenced before set");
return -EINVAL;
}
repl = ctx->jobid;
stop = name + 5;
} else if ((namelen == 8) && (strncmp(name, "jobcount", 8) == 0)) {
if (ctx->jobcount < 0) {
set_err(errbuf, errlen,
"autovar: $jobcount referenced before set");
return -EINVAL;
}
snprintf(numbuf, sizeof(numbuf), "%d", ctx->jobcount);
repl = numbuf;
}
if (repl != NULL) {
size_t rlen = strlen(repl);
if ((o + rlen) >= outlen) {
return -ENOSPC;
}
memcpy(out + o, repl, rlen);
o += rlen;
p = stop; /* consume `$name`; a `[...]` rest is copied */
continue;
}
/* Not a recognized auto-var: copy the whole token verbatim. */
while (p < end) {
if ((o + 1) >= outlen) {
return -ENOSPC;
}
out[o++] = *p++;
}
continue;
}
if ((o + 1) >= outlen) {
return -ENOSPC;
}
out[o++] = *p++;
}
out[o] = '\0';
return 0;
}