boot2

Playing with the boostrap
git clone https://git.ryansepassi.com/git/boot2.git
Log | Files | Refs

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:
Mm1pp/m1pp.M1 | 80++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Mm1pp/m1pp.c | 28+++++++++++++++++-----------
Atests/m1pp/13-parenless-control.M1pp | 30++++++++++++++++++++++++++++++
Atests/m1pp/13-parenless-control.expected | 27+++++++++++++++++++++++++++
Atests/m1pp/13-parenless.M1pp | 32++++++++++++++++++++++++++++++++
Atests/m1pp/13-parenless.expected | 29+++++++++++++++++++++++++++++
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