commit 48971a6c16a856b6b0fd6beafa72d2a170cab648
parent dca693c3073799b8d46cc9f6479839ea4c8d913d
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Thu, 23 Apr 2026 17:04:31 -0700
m1pp: add local labels (:@name, &@name)
Diffstat:
3 files changed, 136 insertions(+), 0 deletions(-)
diff --git a/m1pp/m1pp.c b/m1pp/m1pp.c
@@ -160,6 +160,7 @@ static int pool_used;
static int output_used;
static int output_need_space;
static int stream_top;
+static int next_expansion_id;
static struct Token *arg_starts[MAX_PARAMS];
static struct Token *arg_ends[MAX_PARAMS];
@@ -667,6 +668,64 @@ static int paste_pool_range(int mark)
return 1;
}
+static int is_local_label_token(const struct Token *tok)
+{
+ if (tok->kind != TOK_WORD || tok->text.len < 3) {
+ return 0;
+ }
+ if (tok->text.ptr[0] != ':' && tok->text.ptr[0] != '&') {
+ return 0;
+ }
+ if (tok->text.ptr[1] != '@') {
+ return 0;
+ }
+ return 1;
+}
+
+static int push_local_label_token(const struct Token *tok, int expansion_id)
+{
+ /* Rewrite ":@name" -> ":name__NN", "&@name" -> "&name__NN".
+ * Build the text directly in text_buf so the resulting span is stable. */
+ char digits[16];
+ int digit_count = 0;
+ int unsigned_id;
+ int start;
+ int total;
+ int i;
+ struct Token out;
+
+ unsigned_id = expansion_id;
+ if (unsigned_id == 0) {
+ digits[digit_count++] = '0';
+ } else {
+ while (unsigned_id > 0) {
+ digits[digit_count++] = (char)('0' + (unsigned_id % 10));
+ unsigned_id /= 10;
+ }
+ }
+
+ /* Reserve: sigil(1) + tail(len-2) + "__"(2) + digits + NUL. */
+ total = 1 + (tok->text.len - 2) + 2 + digit_count;
+ if (text_used + total + 1 > MAX_TEXT) {
+ return fail("text overflow");
+ }
+ start = text_used;
+ text_buf[text_used++] = tok->text.ptr[0];
+ memcpy(text_buf + text_used, tok->text.ptr + 2, (size_t)(tok->text.len - 2));
+ text_used += tok->text.len - 2;
+ text_buf[text_used++] = '_';
+ text_buf[text_used++] = '_';
+ for (i = digit_count - 1; i >= 0; i--) {
+ text_buf[text_used++] = digits[i];
+ }
+ text_buf[text_used++] = '\0';
+
+ out.kind = TOK_WORD;
+ out.text.ptr = text_buf + start;
+ out.text.len = total;
+ return push_pool_token(out);
+}
+
static int expand_macro_tokens(struct Token *call_tok, struct Token *limit,
const struct Macro *m, struct Token **after_out,
int *mark_out)
@@ -674,6 +733,7 @@ static int expand_macro_tokens(struct Token *call_tok, struct Token *limit,
struct Token *body_tok;
struct Token *end_pos;
int mark;
+ int expansion_id;
if (call_tok + 1 >= limit || (call_tok + 1)->kind != TOK_LPAREN) {
return fail("bad macro call");
@@ -686,6 +746,7 @@ static int expand_macro_tokens(struct Token *call_tok, struct Token *limit,
}
end_pos = call_end_pos;
+ expansion_id = ++next_expansion_id;
mark = pool_used;
for (body_tok = m->body_start; body_tok < m->body_end; body_tok++) {
int param_idx = find_param(m, body_tok);
@@ -703,6 +764,13 @@ static int expand_macro_tokens(struct Token *call_tok, struct Token *limit,
}
continue;
}
+ if (is_local_label_token(body_tok)) {
+ if (!push_local_label_token(body_tok, expansion_id)) {
+ pool_used = mark;
+ return 0;
+ }
+ continue;
+ }
if (!push_pool_token(*body_tok)) {
pool_used = mark;
return 0;
diff --git a/tests/m1pp/11-local-labels.M1pp b/tests/m1pp/11-local-labels.M1pp
@@ -0,0 +1,37 @@
+# Local labels (ยง1): `:@name` / `&@name` inside macro bodies rewrite to
+# `:name__NN` / `&name__NN` where NN is a fresh monotonic id per expansion.
+# Scoping: body-native only; param-substituted tokens pass through untouched.
+#
+# Scenarios:
+# 1) a single macro using `:@end` called twice -> end__1, end__2 distinct
+# 2) nested macros each using `:@done` -> outer/inner get separate ids
+# 3) `&@label` address form rewrites the same way
+# 4) a `:@name` literal passed as an argument is NOT rewritten
+
+%macro ONCE()
+ jne &@end
+ :@end
+%endm
+
+%macro OUTER()
+ :@done
+ %INNER()
+ jmp &@done
+%endm
+
+%macro INNER()
+ :@done
+ body
+%endm
+
+%macro ARGPASS(lbl)
+ lbl
+%endm
+
+%ONCE()
+%ONCE()
+
+%OUTER()
+
+%ARGPASS(:@kept)
+END
diff --git a/tests/m1pp/11-local-labels.expected b/tests/m1pp/11-local-labels.expected
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+jne &end__1
+:end__1
+
+jne &end__2
+:end__2
+
+
+:done__3
+:done__4
+body
+
+jmp &done__3
+
+
+:@kept
+
+END