commit 5b828f7a0aa7a329a933fedb0576854a30bddd03
parent dca693c3073799b8d46cc9f6479839ea4c8d913d
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Thu, 23 Apr 2026 17:22:30 -0700
Merge feature: paren-less 0-arg macro calls (§4)
Diffstat:
6 files changed, 202 insertions(+), 24 deletions(-)
diff --git a/m1pp/m1pp.M1 b/m1pp/m1pp.M1
@@ -943,7 +943,9 @@ DEFINE EXPR_INVALID 1100000000000000
b
:proc_check_macro
- # macro = find_macro(tok); if non-zero AND tok+1 < s->end AND (tok+1)->kind == TOK_LPAREN: expand_call
+ # macro = find_macro(tok); if non-zero AND
+ # ((tok+1 < s->end AND (tok+1)->kind == TOK_LPAREN) OR macro->param_count == 0)
+ # then expand_call. (§4 paren-less 0-arg calls.)
ld_a0,sp,24
la_br &find_macro
call
@@ -956,12 +958,12 @@ DEFINE EXPR_INVALID 1100000000000000
ld_a1,a0,8
la_br &proc_macro_has_next
blt_t1,a1
- la_br &proc_emit
+ la_br &proc_macro_zero_arg
b
:proc_macro_has_next
ld_a1,t1,0
li_a2 TOK_LPAREN
- la_br &proc_emit
+ la_br &proc_macro_zero_arg
bne_a1,a2
ld_a0,sp,16
mov_a1,t2
@@ -969,6 +971,17 @@ DEFINE EXPR_INVALID 1100000000000000
call
la_br &proc_loop
b
+:proc_macro_zero_arg
+ # No trailing LPAREN. Expand only if macro->param_count == 0.
+ ld_t0,t2,16
+ la_br &proc_emit
+ bnez_t0
+ ld_a0,sp,16
+ mov_a1,t2
+ la_br &expand_call
+ call
+ la_br &proc_loop
+ b
:proc_emit
# emit_token(tok); s->pos += 24; s->line_start = 0
@@ -1875,16 +1888,21 @@ DEFINE EXPR_INVALID 1100000000000000
# lparen = call_tok + 24
addi_a0,a0,24
- # if (lparen >= limit) fatal
- la_br &err_bad_macro_header
+ # Branch split (§4 paren-less 0-arg calls):
+ # if lparen < limit AND lparen->kind == TOK_LPAREN: parse_args as usual.
+ # else if macro->param_count == 0: synthesize empty arg list, no parse_args.
+ # else: fatal "bad macro call".
+
+ # if (lparen >= limit) goto emt_try_zero_arg
+ la_br &emt_try_zero_arg
beq_a0,a1
- # In parse_args, "tok < limit" is the true bound; equality is also "out of range"
- # for an LPAREN check (no token to read). C: `call_tok + 1 >= limit` -> fatal.
+ la_br &emt_try_zero_arg
+ blt_a1,a0
- # if (lparen->kind != TOK_LPAREN) fatal
+ # if (lparen->kind != TOK_LPAREN) goto emt_try_zero_arg
ld_a2,a0,0
li_a3 TOK_LPAREN
- la_br &err_bad_macro_header
+ la_br &emt_try_zero_arg
bne_a2,a3
# parse_args(lparen, limit)
@@ -1907,6 +1925,30 @@ DEFINE EXPR_INVALID 1100000000000000
ld_t0,a0,0
la_a1 &emt_after_pos
st_t0,a1,0
+ la_br &emt_after_arg_setup
+ b
+
+:emt_try_zero_arg
+ # No trailing LPAREN. Allowed only if macro->param_count == 0.
+ la_a0 &emt_macro
+ ld_t1,a0,0
+ ld_t1,t1,16
+ la_br &err_bad_macro_header
+ bnez_t1
+
+ # arg_count = 0
+ la_a0 &arg_count
+ li_t0 %0 %0
+ st_t0,a0,0
+
+ # emt_after_pos = call_tok + 24
+ la_a0 &emt_call_tok
+ ld_t0,a0,0
+ addi_t0,t0,24
+ la_a1 &emt_after_pos
+ st_t0,a1,0
+
+:emt_after_arg_setup
# mark = pool_used; emt_mark = mark
la_a0 &pool_used
@@ -3352,19 +3394,31 @@ DEFINE EXPR_INVALID 1100000000000000
la_br &eea_int_atom
beqz_a0
- # Need (tok + 1 < limit) AND (tok+1)->kind == TOK_LPAREN
+ # §4 paren-less 0-arg atom:
+ # Take the macro-call branch if (tok+1 < limit AND (tok+1)->kind == TOK_LPAREN)
+ # OR macro->param_count == 0. Otherwise fall through to int atom (unchanged).
ld_t0,sp,16
addi_t0,t0,24
ld_t1,sp,24
- la_br &eea_int_atom
+ la_br &eea_check_zero_arg
blt_t1,t0
- la_br &eea_int_atom
+ la_br &eea_check_zero_arg
beq_t0,t1
ld_t2,t0,0
li_a3 TOK_LPAREN
- la_br &eea_int_atom
+ la_br &eea_check_zero_arg
bne_t2,a3
+ la_br &eea_do_macro
+ b
+
+:eea_check_zero_arg
+ # No trailing LPAREN. Take the macro branch only if param_count == 0.
+ ld_t0,sp,32
+ ld_t1,t0,16
+ la_br &eea_int_atom
+ bnez_t1
+:eea_do_macro
# Macro call branch:
# expand_macro_tokens(tok, limit, macro_ptr)
ld_a0,sp,16
diff --git a/m1pp/m1pp.c b/m1pp/m1pp.c
@@ -675,16 +675,20 @@ static int expand_macro_tokens(struct Token *call_tok, struct Token *limit,
struct Token *end_pos;
int mark;
- if (call_tok + 1 >= limit || (call_tok + 1)->kind != TOK_LPAREN) {
+ if (call_tok + 1 < limit && (call_tok + 1)->kind == TOK_LPAREN) {
+ if (!parse_args(call_tok + 1, limit)) {
+ return 0;
+ }
+ if (arg_count != m->param_count) {
+ return fail("wrong arg count");
+ }
+ end_pos = call_end_pos;
+ } else if (m->param_count == 0) {
+ arg_count = 0;
+ end_pos = call_tok + 1;
+ } else {
return fail("bad macro call");
}
- if (!parse_args(call_tok + 1, limit)) {
- return 0;
- }
- if (arg_count != m->param_count) {
- return fail("wrong arg count");
- }
- end_pos = call_end_pos;
mark = pool_used;
for (body_tok = m->body_start; body_tok < m->body_end; body_tok++) {
@@ -949,7 +953,9 @@ static int eval_expr_atom(struct Token *tok, struct Token *limit,
int mark;
macro = find_macro(tok);
- if (macro != NULL && tok + 1 < limit && (tok + 1)->kind == TOK_LPAREN) {
+ if (macro != NULL &&
+ ((tok + 1 < limit && (tok + 1)->kind == TOK_LPAREN) ||
+ macro->param_count == 0)) {
if (!expand_macro_tokens(tok, limit, macro, &after, &mark)) {
return 0;
}
@@ -1229,8 +1235,8 @@ static int process_tokens(void)
macro = find_macro(tok);
if (macro != NULL &&
- tok + 1 < s->end &&
- (tok + 1)->kind == TOK_LPAREN) {
+ ((tok + 1 < s->end && (tok + 1)->kind == TOK_LPAREN) ||
+ macro->param_count == 0)) {
if (!expand_call(s, macro)) {
return 0;
}
diff --git a/tests/m1pp/13-parenless-control.M1pp b/tests/m1pp/13-parenless-control.M1pp
@@ -0,0 +1,30 @@
+# Control fixture for 13-parenless: same macros and test points, but every
+# 0-arg call uses the explicit () form. The output from the two test points
+# where 13-parenless uses paren-less calls must be byte-identical here,
+# proving paren-less is semantically equivalent to paren-ful for 0-arg macros.
+
+%macro FRAME_BASE()
+16
+%endm
+
+%macro add1(x)
+%((+ x 1))
+%endm
+
+# Top-level: both forms yield `16`.
+%FRAME_BASE()
+%FRAME_BASE()
+
+# Expression atom: both yield 4-byte LE hex for 24 (16 + 8).
+%((+ %FRAME_BASE() 8))
+%((+ %FRAME_BASE() 8))
+
+# Undefined %foo passes through unchanged.
+%foo
+
+# Param-ful macro without parens does NOT match — literal text flows through.
+%add1
+
+# Param-ful macro with proper call still works (sanity).
+%add1(41)
+END
diff --git a/tests/m1pp/13-parenless-control.expected b/tests/m1pp/13-parenless-control.expected
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+16
+
+16
+
+
+
+'18000000'
+'18000000'
+
+
+%foo
+
+
+%add1
+
+
+'2A000000'
+
+END
diff --git a/tests/m1pp/13-parenless.M1pp b/tests/m1pp/13-parenless.M1pp
@@ -0,0 +1,32 @@
+# Paren-less 0-arg macro calls (M1PP-EXT §4):
+# - a zero-param macro invoked without trailing () expands the same as with ()
+# - applies at top level and as an atom inside %(...) expressions
+# - non-zero-param macros still require their (arg, ...) syntax — %add1
+# without parens must pass through unchanged
+# - a %foo where foo is not a macro still passes through unchanged
+
+%macro FRAME_BASE()
+16
+%endm
+
+%macro add1(x)
+%((+ x 1))
+%endm
+
+# Top-level: both forms yield `16`.
+%FRAME_BASE
+%FRAME_BASE()
+
+# Expression atom: both yield 4-byte LE hex for 24 (16 + 8).
+%((+ %FRAME_BASE 8))
+%((+ %FRAME_BASE() 8))
+
+# Undefined %foo passes through unchanged.
+%foo
+
+# Param-ful macro without parens does NOT match — literal text flows through.
+%add1
+
+# Param-ful macro with proper call still works (sanity).
+%add1(41)
+END
diff --git a/tests/m1pp/13-parenless.expected b/tests/m1pp/13-parenless.expected
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+16
+
+16
+
+
+
+'18000000'
+'18000000'
+
+
+%foo
+
+
+%add1
+
+
+'2A000000'
+
+END