Skip to content

Commit 982f942

Browse files
optimise out string copies in recursive descent
1 parent f658d56 commit 982f942

4 files changed

Lines changed: 84 additions & 16 deletions

File tree

include/string.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,29 @@ const char* gc_strdup(basic_ctx* ctx, const char* string);
229229
*/
230230
const char* gc_try_concat(struct basic_ctx *ctx, const char* s1, const char* s2);
231231

232+
/**
233+
* @brief Stream a string literal directly from the tokenizer into the GC arena.
234+
*
235+
* This function optimizes the parsing of string literals by bypassing the
236+
* intermediate stack buffer. It reads characters directly from the source
237+
* pointer, performing a single-pass copy into the BASIC string arena.
238+
*
239+
* Like gc_try_concat(), this operation is atomic: if the literal length
240+
* exceeds MAX_STRINGLEN or available arena space, the allocator pointer
241+
* is not advanced, and NULL is returned.
242+
*
243+
* @note
244+
* - This function handles the advancing of the internal tokenizer pointer
245+
* as it consumes characters.
246+
* - The resulting string is NUL-terminated and committed to the arena
247+
* only upon successful completion of the parse.
248+
*
249+
* @param ctx BASIC interpreter context.
250+
* @return const char* Pointer to the arena-allocated string literal,
251+
* or NULL on overflow/error.
252+
*/
253+
const char* gc_from_tokenizer_string(struct basic_ctx *ctx);
254+
232255
/**
233256
* @brief Reset the BASIC temporary string arena.
234257
*

src/basic/unified_expression.c

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -103,18 +103,19 @@ static up_value up_factor(struct basic_ctx *ctx) {
103103
return up_make_int(n);
104104
}
105105
case STRING: {
106-
char string[MAX_STRINGLEN];
107-
if (!tokenizer_string(string, sizeof(string), ctx)) {
108-
tokenizer_error_print(ctx, "Bad string literal");
109-
return up_make_str("");
110-
}
111-
/* IMPORTANT: do NOT consume a ')' here; that belongs to the caller (e.g., func args). */
112-
accept(STRING, ctx);
113-
const char* sv = gc_strdup(ctx, string);
106+
const char* sv = gc_from_tokenizer_string(ctx);
107+
114108
if (!sv) {
115-
tokenizer_error_print(ctx, "up_factor: Out of memory!");
109+
tokenizer_error_print(ctx, "String literal too long");
116110
return up_make_str("");
117111
}
112+
113+
/* * We don't use 'accept(STRING)' here because gc_from_tokenizer_string
114+
* has already advanced the internal pointer past the literal.
115+
* We just need to update the cached token.
116+
*/
117+
tokenizer_next(ctx);
118+
118119
return up_make_str(sv);
119120
}
120121
case VARIABLE: {
@@ -418,7 +419,11 @@ static up_value up_relation_expr(struct basic_ctx *ctx) {
418419
tokenizer_error_print(ctx, "Cannot compare string with number");
419420
result = 0;
420421
} else {
421-
int cmp = strcmp(lhs.v.s ? lhs.v.s : "", rhs.v.s ? rhs.v.s : "");
422+
/* Because empties are interned, identical ptrs match without strcmp */
423+
int cmp = 0;
424+
if (lhs.v.s != rhs.v.s) {
425+
cmp = strcmp(lhs.v.s, rhs.v.s);
426+
}
422427
if (op == LESSTHAN) {
423428
result = (mode == 1) ? (cmp <= 0) : (cmp < 0);
424429
} else if (op == GREATERTHAN) {

src/basic/variable.c

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -357,23 +357,39 @@ void basic_set_double_variable(const char* var, double value, struct basic_ctx*
357357
}
358358
}
359359

360+
static inline char get_sigil(const char *v) {
361+
if (!v || !*v) {
362+
return 0;
363+
}
364+
// Walk to the null terminator
365+
while (*v) {
366+
v++;
367+
}
368+
// Look at the character immediately preceding the null
369+
return *(v - 1);
370+
}
371+
360372
/**
361373
* @brief Returns true if 'varname' starts with FN
362374
* (is a function call)
363-
*
375+
*
364376
* @param varname variable name to check
365377
* @return char 1 if variable name is a function call, 0 if it is not
366378
*/
367379
char varname_is_function(const char* varname) {
368-
return varname && (*varname == 'F' && *(varname + 1) == 'N' && !strchr(varname, '#') && !strchr(varname, '$'));
380+
if (!varname || varname[0] != 'F' || varname[1] != 'N') {
381+
return 0;
382+
}
383+
char s = get_sigil(varname);
384+
return (s != '$' && s != '#');
369385
}
370386

371387
char varname_is_string_function(const char* varname) {
372-
return varname && (*varname == 'F' && *(varname + 1) == 'N' && strchr(varname, '$') && !strchr(varname, '#'));
388+
return varname && varname[0] == 'F' && varname[1] == 'N' && get_sigil(varname) == '$';
373389
}
374390

375391
char varname_is_double_function(const char* varname) {
376-
return varname && (*varname == 'F' && *(varname + 1) == 'N' && strchr(varname, '#') && !strchr(varname, '$'));
392+
return varname && varname[0] == 'F' && varname[1] == 'N' && get_sigil(varname) == '#';
377393
}
378394

379395
const char* basic_get_string_variable(const char* var, struct basic_ctx* ctx) {

src/string.c

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -231,8 +231,32 @@ const char* gc_strdup(basic_ctx* ctx, const char* string)
231231
return dest;
232232
}
233233

234-
int gc(basic_ctx* ctx)
235-
{
234+
const char* gc_from_tokenizer_string(struct basic_ctx *ctx) {
235+
char* dest = ctx->string_gc_storage_next;
236+
char* end = ctx->string_gc_storage + STRING_GC_AREA_SIZE;
237+
char* out = dest;
238+
size_t cur = 0;
239+
240+
if (*ctx->ptr == '"') {
241+
ctx->ptr++;
242+
}
243+
244+
while (*ctx->ptr && *ctx->ptr != '"') {
245+
if (out >= end - 1 || ++cur >= MAX_STRINGLEN) {
246+
return NULL;
247+
}
248+
*out++ = *ctx->ptr++;
249+
}
250+
251+
if (*ctx->ptr == '"') {
252+
ctx->ptr++; // Skip closing quote
253+
}
254+
*out = '\0';
255+
ctx->string_gc_storage_next = out + 1;
256+
return dest;
257+
}
258+
259+
int gc(basic_ctx* ctx) {
236260
if (ctx->string_gc_storage && ctx->string_gc_storage_next) {
237261
/* Strings start at +1 as the base ptr is for empties */
238262
ctx->string_gc_storage_next = ctx->string_gc_storage + 1;

0 commit comments

Comments
 (0)