boot2

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

M1pp.c (63137B)


      1 /*
      2  * Tiny single-pass M1pp macro expander. Output is consumed directly by
      3  * hex2pp -- there is no intermediate M0/hex2 stage. All emission is in
      4  * the byte/label/directive vocabulary hex2pp accepts.
      5  *
      6  * Syntax:
      7  *   %macro NAME(a, b)
      8  *   ... body ...
      9  *   %endm
     10  *
     11  *   %struct NAME { f1 f2 ... }   fixed-layout 8-byte-field aggregate
     12  *   %enum   NAME { l1 l2 ... }   incrementing integer constants
     13  *   %reset-output BASE           discard output so far and seed the
     14  *                                macro-hygiene expansion id
     15  *
     16  *   %NAME(x, y)      function-like macro call
     17  *   ##               token pasting inside macro bodies
     18  *   !(expr)          evaluate an integer S-expression, emit LE 8-bit hex
     19  *   @(expr)          evaluate an integer S-expression, emit LE 16-bit hex
     20  *   %(expr)          evaluate an integer S-expression, emit LE 32-bit hex
     21  *   $(expr)          evaluate an integer S-expression, emit LE 64-bit hex
     22  *   %select(c,t,e)   evaluate condition S-expression; expand t if nonzero else e
     23  *   %str(IDENT)      stringify a single WORD token into a "..."-quoted literal
     24  *   %bytes(STR)      emit the raw bytes of STR as contiguous hex digits
     25  *
     26  *   %frame NAME / %endframe   set/clear a single-slot "current frame"
     27  *   %local(NAME)              expand to the body of <frame>_FRAME.<NAME>
     28  *
     29  * Lexical scoping for control-flow labels is delegated to hex2pp's
     30  * `.scope` / `.endscope` (which nest). M1pp itself only handles
     31  * per-expansion macro hygiene labels (`:@name` / `&@name`).
     32  *
     33  * Expression syntax is intentionally Lisp-shaped:
     34  *   atoms: decimal or 0x-prefixed integer literals
     35  *   calls: (+ a b), (- a b), (* a b), (/ a b), (% a b), (<< a b), (>> a b)
     36  *          (& a b), (| a b), (^ a b), (~ a), (= a b), (!= a b),
     37  *          (< a b), (<= a b), (> a b), (>= a b)
     38  *
     39  * Flow:
     40  *   1. lex_source(): scan input_buf into source_tokens[]. Tokens are words,
     41  *      strings, newlines, parens, commas, and ## paste markers. Whitespace
     42  *      (excluding newlines) is dropped; # and ; comments are dropped.
     43  *
     44  *   2. process_tokens(): main loop driven by a stream stack (streams[]).
     45  *      The source token array is pushed as the initial stream. Each iteration
     46  *      pops a token from the top stream:
     47  *
     48  *        %macro NAME(p,...) / %endm
     49  *          -> define_macro(): consume header + body tokens into macros[] and
     50  *             macro_body_tokens[]; register name and param list. Header is
     51  *             whitespace-insensitive (newlines inside (...) are skipped);
     52  *             %endm is recognized anywhere and must be followed by NEWLINE.
     53  *             A directive that started at line_start consumes its trailing
     54  *             newline; mid-line directives leave it for the main loop.
     55  *
     56  *        !(e) / @(e) / %(e) / $(e) / %select(c,t,e)
     57  *          -> expand_builtin_call(): parse arg spans, eval S-expression(s) via
     58  *             eval_expr_range(), emit LE hex or push the chosen token span.
     59  *             Only fuses when ( is tight against the name (no whitespace).
     60  *
     61  *        %NAME(...) matching a defined macro
     62  *          -> expand_call() -> expand_macro_tokens(): substitute arguments,
     63  *             apply ## paste via paste_pool_range(), write result into
     64  *             expand_pool[], then push that slice as a new stream (rescan).
     65  *             Tight ( required for paren-form; otherwise treated as 0-arg.
     66  *
     67  *        Anything else
     68  *          -> emit_token() / emit_newline() directly into output_buf.
     69  *
     70  *      When a stream is exhausted it is popped; pool_used is rewound to the
     71  *      stream's pool_mark, reclaiming the expand_pool space it used.
     72  *
     73  *   3. Write output_buf to the output file.
     74  *
     75  * Notes:
     76  *   - Macros are define-before-use. There is no prescan.
     77  *   - Expansion rescans by pushing expanded tokens back through the same loop.
     78  *   - There is no cycle detection. Recursive macros will loop until a limit.
     79  *   - Only recognized %NAME(...) calls expand. Other text passes through.
     80  *   - Output formatting is normalized to tokens plus '\n', not preserved.
     81  */
     82 
     83 #include <errno.h>
     84 #include <stdio.h>
     85 #include <stdlib.h>
     86 #include <string.h>
     87 
     88 /* Caps chosen to mirror the M1pp.P1 BSS layout, sized so the cc.scm
     89  * emission of tcc.flat.c (~6.5 MB of macro-rich .P1pp) lexes cleanly.
     90  * The native binary is host-side, so static globals at these sizes
     91  * just live in .bss / anonymous mmap without any of the ELF-segment
     92  * sizing dance the bootstrap m1pp has to do. */
     93 #define MAX_INPUT             16777216    /* 16 MiB */
     94 #define MAX_OUTPUT            134217728   /* 128 MiB */
     95 #define MAX_TEXT              67108864    /* 64 MiB:
     96                                            * paste tokens, hex literals from
     97                                            * %(EXPR) evaluation, and per-call
     98                                            * @local label rewrites all live
     99                                            * here for the run's lifetime. cc.scm
    100                                            * triggers hundreds of thousands of
    101                                            * each across the tcc.c expansion. */
    102 #define MAX_TOKENS            8388608     /*  8 M slots × 32 B = 256 MiB */
    103 #define MAX_MACROS            1024
    104 #define MAX_PARAMS            16
    105 #define MAX_MACRO_BODY_TOKENS MAX_TOKENS
    106 #define MAX_EXPAND            524288      /* 512 K × 32 B = 16 MiB:
    107                                             * cc.scm wraps each C function in
    108                                             * %fn(... { body }), and m1pp's
    109                                             * expand_macro_tokens copies the
    110                                             * argument tokens into the pool —
    111                                             * so the entire body of a long
    112                                             * function is resident in the pool
    113                                             * while its outer %fn is active.
    114                                             * tcc.c's next_nomacro1 (~5900
    115                                             * lines × ~13 m1pp tokens/line ≈
    116                                             * 77 K tokens, ~2.5 MiB) plus
    117                                             * inner expansions sit comfortably
    118                                             * under 16 MiB. */
    119 #define MAX_STACK             64
    120 #define MAX_EXPR_FRAMES       256
    121 
    122 enum {
    123     TOK_WORD,
    124     TOK_STRING,
    125     TOK_NEWLINE,
    126     TOK_LPAREN,
    127     TOK_RPAREN,
    128     TOK_COMMA,
    129     TOK_PASTE,
    130     TOK_LBRACE,
    131     TOK_RBRACE
    132 };
    133 
    134 enum ExprOp {
    135     EXPR_ADD,
    136     EXPR_SUB,
    137     EXPR_MUL,
    138     EXPR_DIV,
    139     EXPR_MOD,
    140     EXPR_SHL,
    141     EXPR_SHR,
    142     EXPR_AND,
    143     EXPR_OR,
    144     EXPR_XOR,
    145     EXPR_NOT,
    146     EXPR_EQ,
    147     EXPR_NE,
    148     EXPR_LT,
    149     EXPR_LE,
    150     EXPR_GT,
    151     EXPR_GE,
    152     EXPR_STRLEN,
    153     EXPR_INVALID
    154 };
    155 
    156 struct TextSpan {
    157     const char *ptr;
    158     int len;
    159 };
    160 
    161 struct Token {
    162     int kind;
    163     int tight;
    164     int line;
    165     struct TextSpan text;
    166 };
    167 
    168 struct TokenSpan {
    169     struct Token *start;
    170     struct Token *end;
    171 };
    172 
    173 struct Macro {
    174     struct TextSpan name;
    175     int param_count;
    176     int has_paste;
    177     struct TextSpan params[MAX_PARAMS];
    178     struct Token *body_start;
    179     struct Token *body_end;
    180 };
    181 
    182 struct Stream {
    183     struct Token *start;
    184     struct Token *end;
    185     struct Token *pos;
    186     int line_start;
    187     int pool_mark;
    188 };
    189 
    190 struct ExprFrame {
    191     enum ExprOp op;
    192     long long args[MAX_PARAMS];
    193     int argc;
    194 };
    195 
    196 static char input_buf[MAX_INPUT + 1];
    197 static char output_buf[MAX_OUTPUT + 1];
    198 static char text_buf[MAX_TEXT];
    199 
    200 static struct Token source_tokens[MAX_TOKENS];
    201 static struct Token macro_body_tokens[MAX_MACRO_BODY_TOKENS];
    202 /* Per-body-token classification cached at %macro definition time, so
    203  * expand_macro_tokens never re-runs find_param / is_local_label_token in
    204  * its hot loop. param_idx: 0 = not a param, k = params[k-1]. */
    205 static unsigned char macro_body_param_idx[MAX_MACRO_BODY_TOKENS];
    206 static unsigned char macro_body_is_local_label[MAX_MACRO_BODY_TOKENS];
    207 static struct Token expand_pool[MAX_EXPAND];
    208 static struct Macro macros[MAX_MACROS];
    209 static struct Stream streams[MAX_STACK];
    210 static struct TextSpan current_frame;
    211 static int frame_active;
    212 
    213 static int text_used;
    214 static int source_count;
    215 static int macro_count;
    216 static int macro_body_used;
    217 static int pool_used;
    218 static int output_used;
    219 static int output_need_space;
    220 static int stream_top;
    221 static int next_expansion_id;
    222 static int current_line;
    223 static int error_line;
    224 static const char *input_path;
    225 
    226 static struct Token *arg_starts[MAX_PARAMS];
    227 static struct Token *arg_ends[MAX_PARAMS];
    228 static int arg_count;
    229 static struct Token *call_end_pos;
    230 static int args_have_paste;
    231 
    232 static const char *error_msg;
    233 
    234 static int fail(const char *msg)
    235 {
    236     error_msg = msg;
    237     error_line = current_line;
    238     return 0;
    239 }
    240 
    241 static int is_space_no_nl(int c)
    242 {
    243     return c == ' ' || c == '\t' || c == '\r' || c == '\f' || c == '\v';
    244 }
    245 
    246 static char *append_text_len(const char *s, int len)
    247 {
    248     int start;
    249 
    250     if (text_used + len + 1 > MAX_TEXT) {
    251         fail("text overflow");
    252         return NULL;
    253     }
    254     start = text_used;
    255     memcpy(text_buf + text_used, s, (size_t)len);
    256     text_used += len;
    257     text_buf[text_used++] = '\0';
    258     return text_buf + start;
    259 }
    260 
    261 static int push_token(struct Token *buf, int *count, int max_count,
    262                       int kind, int tight, int line, struct TextSpan text)
    263 {
    264     if (*count >= max_count) {
    265         return fail("token overflow");
    266     }
    267     buf[*count].kind = kind;
    268     buf[*count].tight = tight;
    269     buf[*count].line = line;
    270     buf[*count].text = text;
    271     *count += 1;
    272     return 1;
    273 }
    274 
    275 static int push_pool_token(struct Token tok)
    276 {
    277     if (pool_used >= MAX_EXPAND) {
    278         return fail("expansion overflow");
    279     }
    280     expand_pool[pool_used++] = tok;
    281     return 1;
    282 }
    283 
    284 static int token_text_eq(const struct Token *tok, const char *s)
    285 {
    286     int len = (int)strlen(s);
    287 
    288     return tok->text.len == len &&
    289            memcmp(tok->text.ptr, s, (size_t)len) == 0;
    290 }
    291 
    292 static int span_eq_token(struct TextSpan span, const struct Token *tok)
    293 {
    294     return span.len == tok->text.len &&
    295            memcmp(span.ptr, tok->text.ptr, (size_t)span.len) == 0;
    296 }
    297 
    298 static int lex_source(const char *src)
    299 {
    300     /* Track whether whitespace (space, tab, comment, OR newline) precedes
    301      * the next token. tight=1 means "no whitespace before me"; only
    302      * LPAREN's tight bit is consulted, to decide whether %FOO(...) /
    303      * !(...) etc. are paren-call forms. */
    304     int i = 0;
    305     int line = 1;
    306     int saw_separator = 1;
    307 
    308     while (src[i] != '\0') {
    309         int start;
    310         int len;
    311         int tight;
    312 
    313         current_line = line;
    314 
    315         if (is_space_no_nl((unsigned char)src[i])) {
    316             saw_separator = 1;
    317             i++;
    318             continue;
    319         }
    320         if (src[i] == '\n') {
    321             if (!push_token(source_tokens, &source_count, MAX_TOKENS,
    322                             TOK_NEWLINE, 0, line, (struct TextSpan){src + i, 1})) {
    323                 return 0;
    324             }
    325             line++;
    326             saw_separator = 1;
    327             i++;
    328             continue;
    329         }
    330         if (src[i] == '"' || src[i] == '\'') {
    331             int quote = src[i];
    332 
    333             tight = !saw_separator;
    334             start = i;
    335             i++;
    336             while (src[i] != '\0' && src[i] != quote) {
    337                 if (src[i] == '\\' && src[i + 1] != '\0') {
    338                     /* Skip backslash + next char as a unit so the
    339                      * close-quote test doesn't fire on `\"`, and so
    340                      * `\\` doesn't leave the trailing `\` to start a
    341                      * spurious escape. The escape's *meaning* is
    342                      * decoded later (e.g. by %bytes); the lexer only
    343                      * cares about token boundaries. */
    344                     if (src[i + 1] == '\n') {
    345                         line++;
    346                     }
    347                     i += 2;
    348                     continue;
    349                 }
    350                 if (src[i] == '\n') {
    351                     line++;
    352                 }
    353                 i++;
    354             }
    355             if (src[i] == quote) {
    356                 i++;
    357             }
    358             len = i - start;
    359             if (!push_token(source_tokens, &source_count, MAX_TOKENS,
    360                             TOK_STRING, tight, current_line, (struct TextSpan){src + start, len})) {
    361                 return 0;
    362             }
    363             saw_separator = 0;
    364             continue;
    365         }
    366         if (src[i] == '#' && src[i + 1] == '#') {
    367             tight = !saw_separator;
    368             if (!push_token(source_tokens, &source_count, MAX_TOKENS,
    369                             TOK_PASTE, tight, line, (struct TextSpan){src + i, 2})) {
    370                 return 0;
    371             }
    372             i += 2;
    373             saw_separator = 0;
    374             continue;
    375         }
    376         if (src[i] == '#' || src[i] == ';') {
    377             saw_separator = 1;
    378             while (src[i] != '\0' && src[i] != '\n') {
    379                 i++;
    380             }
    381             continue;
    382         }
    383         if (src[i] == '(') {
    384             tight = !saw_separator;
    385             if (!push_token(source_tokens, &source_count, MAX_TOKENS,
    386                             TOK_LPAREN, tight, line, (struct TextSpan){src + i, 1})) {
    387                 return 0;
    388             }
    389             i++;
    390             saw_separator = 0;
    391             continue;
    392         }
    393         if (src[i] == ')') {
    394             tight = !saw_separator;
    395             if (!push_token(source_tokens, &source_count, MAX_TOKENS,
    396                             TOK_RPAREN, tight, line, (struct TextSpan){src + i, 1})) {
    397                 return 0;
    398             }
    399             i++;
    400             saw_separator = 0;
    401             continue;
    402         }
    403         if (src[i] == ',') {
    404             tight = !saw_separator;
    405             if (!push_token(source_tokens, &source_count, MAX_TOKENS,
    406                             TOK_COMMA, tight, line, (struct TextSpan){src + i, 1})) {
    407                 return 0;
    408             }
    409             i++;
    410             saw_separator = 0;
    411             continue;
    412         }
    413         if (src[i] == '{') {
    414             tight = !saw_separator;
    415             if (!push_token(source_tokens, &source_count, MAX_TOKENS,
    416                             TOK_LBRACE, tight, line, (struct TextSpan){src + i, 1})) {
    417                 return 0;
    418             }
    419             i++;
    420             saw_separator = 0;
    421             continue;
    422         }
    423         if (src[i] == '}') {
    424             tight = !saw_separator;
    425             if (!push_token(source_tokens, &source_count, MAX_TOKENS,
    426                             TOK_RBRACE, tight, line, (struct TextSpan){src + i, 1})) {
    427                 return 0;
    428             }
    429             i++;
    430             saw_separator = 0;
    431             continue;
    432         }
    433 
    434         tight = !saw_separator;
    435         start = i;
    436         while (src[i] != '\0' &&
    437                !is_space_no_nl((unsigned char)src[i]) &&
    438                src[i] != '\n' &&
    439                src[i] != '#' &&
    440                src[i] != ';' &&
    441                src[i] != '(' &&
    442                src[i] != ')' &&
    443                src[i] != ',' &&
    444                src[i] != '{' &&
    445                src[i] != '}' &&
    446                !(src[i] == '#' && src[i + 1] == '#')) {
    447             i++;
    448         }
    449         len = i - start;
    450         if (!push_token(source_tokens, &source_count, MAX_TOKENS,
    451                         TOK_WORD, tight, line, (struct TextSpan){src + start, len})) {
    452             return 0;
    453         }
    454         saw_separator = 0;
    455     }
    456 
    457     return 1;
    458 }
    459 
    460 static const struct Macro *find_macro(const struct Token *tok)
    461 {
    462     int i;
    463 
    464     if (tok->kind != TOK_WORD || tok->text.len < 2) {
    465         return NULL;
    466     }
    467     if (tok->text.ptr[0] != '%') {
    468         return NULL;
    469     }
    470     for (i = 0; i < macro_count; i++) {
    471         if (macros[i].name.len == tok->text.len - 1 &&
    472             memcmp(tok->text.ptr + 1,
    473                    macros[i].name.ptr,
    474                    (size_t)macros[i].name.len) == 0) {
    475             return &macros[i];
    476         }
    477     }
    478     return NULL;
    479 }
    480 
    481 static int find_param(const struct Macro *m, const struct Token *tok)
    482 {
    483     int i;
    484 
    485     if (tok->kind != TOK_WORD) {
    486         return 0;
    487     }
    488     for (i = 0; i < m->param_count; i++) {
    489         if (span_eq_token(m->params[i], tok)) {
    490             return i + 1;
    491         }
    492     }
    493     return 0;
    494 }
    495 
    496 static int emit_newline(void)
    497 {
    498     if (output_used + 1 >= MAX_OUTPUT) {
    499         return fail("output overflow");
    500     }
    501     output_buf[output_used++] = '\n';
    502     output_need_space = 0;
    503     return 1;
    504 }
    505 
    506 static int emit_string_as_bytes(const struct Token *tok);
    507 static int emit_hex_value(unsigned long long value, int bytes);
    508 static int is_local_label_token(const struct Token *tok);
    509 
    510 static int emit_token(const struct Token *tok)
    511 {
    512     if (tok->kind == TOK_LBRACE || tok->kind == TOK_RBRACE) {
    513         return 1;
    514     }
    515     if (tok->kind == TOK_STRING) {
    516         return emit_string_as_bytes(tok);
    517     }
    518     if (output_need_space) {
    519         if (output_used + 1 >= MAX_OUTPUT) {
    520             return fail("output overflow");
    521         }
    522         output_buf[output_used++] = ' ';
    523     }
    524     if (output_used + tok->text.len >= MAX_OUTPUT) {
    525         return fail("output overflow");
    526     }
    527     memcpy(output_buf + output_used, tok->text.ptr,
    528            (size_t)tok->text.len);
    529     output_used += tok->text.len;
    530     output_need_space = 1;
    531     return 1;
    532 }
    533 
    534 /* Decode a "..." or '...' literal and emit one TOK_WORD per byte
    535  * (each token's text is the two hex digits for that byte). Recognised
    536  * escapes inside the literal: \n \t \r \0 \\ \" \xNN. No NUL is
    537  * appended; user code writes one explicitly if needed. */
    538 static int emit_string_as_bytes(const struct Token *tok)
    539 {
    540     const char *src;
    541     int src_len;
    542     int src_i;
    543 
    544     if (tok->text.len < 2) {
    545         return fail("bad string");
    546     }
    547     src = tok->text.ptr + 1;
    548     src_len = tok->text.len - 2;
    549     src_i = 0;
    550     while (src_i < src_len) {
    551         unsigned int b;
    552         char c = src[src_i++];
    553         if (c == '\\') {
    554             char e;
    555             if (src_i >= src_len) {
    556                 return fail("bad escape");
    557             }
    558             e = src[src_i++];
    559             if (e == 'n')       b = 0x0A;
    560             else if (e == 't')  b = 0x09;
    561             else if (e == 'r')  b = 0x0D;
    562             else if (e == '0')  b = 0x00;
    563             else if (e == '\\') b = 0x5C;
    564             else if (e == '"')  b = 0x22;
    565             else if (e == 'x') {
    566                 int hi, lo, hv, lv;
    567                 if (src_i + 2 > src_len) {
    568                     return fail("bad escape");
    569                 }
    570                 hi = (unsigned char)src[src_i++];
    571                 lo = (unsigned char)src[src_i++];
    572                 hv = (hi >= '0' && hi <= '9') ? hi - '0' :
    573                      (hi >= 'a' && hi <= 'f') ? hi - 'a' + 10 :
    574                      (hi >= 'A' && hi <= 'F') ? hi - 'A' + 10 : -1;
    575                 lv = (lo >= '0' && lo <= '9') ? lo - '0' :
    576                      (lo >= 'a' && lo <= 'f') ? lo - 'a' + 10 :
    577                      (lo >= 'A' && lo <= 'F') ? lo - 'A' + 10 : -1;
    578                 if (hv < 0 || lv < 0) {
    579                     return fail("bad escape");
    580                 }
    581                 b = (unsigned int)((hv << 4) | lv);
    582             } else {
    583                 return fail("bad escape");
    584             }
    585         } else {
    586             b = (unsigned char)c;
    587         }
    588         if (!emit_hex_value((unsigned long long)b, 1)) {
    589             return 0;
    590         }
    591     }
    592     return 1;
    593 }
    594 
    595 static int push_stream_span(struct TokenSpan span, int pool_mark)
    596 {
    597     struct Stream *s;
    598 
    599     if (stream_top >= MAX_STACK) {
    600         return fail("stream overflow");
    601     }
    602     s = &streams[stream_top++];
    603     s->start = span.start;
    604     s->end = span.end;
    605     s->pos = span.start;
    606     s->line_start = 1;
    607     s->pool_mark = pool_mark;
    608     return 1;
    609 }
    610 
    611 static struct Stream *current_stream(void)
    612 {
    613     if (stream_top <= 0) {
    614         return NULL;
    615     }
    616     return &streams[stream_top - 1];
    617 }
    618 
    619 static void pop_stream(void)
    620 {
    621     if (stream_top <= 0) {
    622         return;
    623     }
    624     stream_top--;
    625     if (streams[stream_top].pool_mark >= 0) {
    626         pool_used = streams[stream_top].pool_mark;
    627     }
    628 }
    629 
    630 static int copy_span_to_pool(struct TokenSpan span)
    631 {
    632     struct Token *tok;
    633 
    634     for (tok = span.start; tok < span.end; tok++) {
    635         if (!push_pool_token(*tok)) {
    636             return 0;
    637         }
    638     }
    639     return 1;
    640 }
    641 
    642 static int push_pool_stream_from_mark(int mark)
    643 {
    644     if (pool_used == mark) {
    645         pool_used = mark;
    646         return 1;
    647     }
    648     return push_stream_span((struct TokenSpan){expand_pool + mark, expand_pool + pool_used},
    649                             mark);
    650 }
    651 
    652 static void skip_newlines(struct Token **pos, struct Token *end)
    653 {
    654     while (*pos < end && (*pos)->kind == TOK_NEWLINE) {
    655         *pos += 1;
    656     }
    657 }
    658 
    659 static int emit_decimal_text(long long value, struct TextSpan *out)
    660 {
    661     /* Render a non-negative integer as decimal into text_buf and
    662      * return the span. No snprintf; plain reverse-fill. */
    663     char digits[24];
    664     int digit_count = 0;
    665     long long v = value;
    666     int start;
    667     int i;
    668 
    669     if (v < 0) {
    670         return fail("bad directive");
    671     }
    672     if (v == 0) {
    673         digits[digit_count++] = '0';
    674     } else {
    675         while (v > 0) {
    676             digits[digit_count++] = (char)('0' + (v % 10));
    677             v /= 10;
    678         }
    679     }
    680 
    681     if (text_used + digit_count + 1 > MAX_TEXT) {
    682         return fail("text overflow");
    683     }
    684     start = text_used;
    685     for (i = digit_count - 1; i >= 0; i--) {
    686         text_buf[text_used++] = digits[i];
    687     }
    688     text_buf[text_used++] = '\0';
    689     out->ptr = text_buf + start;
    690     out->len = digit_count;
    691     return 1;
    692 }
    693 
    694 static int emit_dotted_name(struct TextSpan base, const char *suffix,
    695                             int suffix_len, struct TextSpan *out)
    696 {
    697     int total = base.len + 1 + suffix_len;
    698     int start;
    699 
    700     if (text_used + total + 1 > MAX_TEXT) {
    701         return fail("text overflow");
    702     }
    703     start = text_used;
    704     memcpy(text_buf + text_used, base.ptr, (size_t)base.len);
    705     text_used += base.len;
    706     text_buf[text_used++] = '.';
    707     memcpy(text_buf + text_used, suffix, (size_t)suffix_len);
    708     text_used += suffix_len;
    709     text_buf[text_used++] = '\0';
    710     out->ptr = text_buf + start;
    711     out->len = total;
    712     return 1;
    713 }
    714 
    715 static int define_fielded_macro(struct TextSpan base, const char *suffix,
    716                                 int suffix_len, long long value)
    717 {
    718     struct Macro *m;
    719     struct Token body_tok;
    720 
    721     if (macro_count >= MAX_MACROS) {
    722         return fail("too many macros");
    723     }
    724     if (macro_body_used >= MAX_MACRO_BODY_TOKENS) {
    725         return fail("macro body overflow");
    726     }
    727     m = &macros[macro_count];
    728     memset(m, 0, sizeof(*m));
    729     if (!emit_dotted_name(base, suffix, suffix_len, &m->name)) {
    730         return 0;
    731     }
    732     m->param_count = 0;
    733     body_tok.kind = TOK_WORD;
    734     body_tok.tight = 0;
    735     body_tok.line = current_line;
    736     if (!emit_decimal_text(value, &body_tok.text)) {
    737         return 0;
    738     }
    739     m->body_start = macro_body_tokens + macro_body_used;
    740     macro_body_param_idx[macro_body_used] = 0;
    741     macro_body_is_local_label[macro_body_used] = 0;
    742     macro_body_tokens[macro_body_used++] = body_tok;
    743     m->body_end = macro_body_tokens + macro_body_used;
    744     macro_count++;
    745     return 1;
    746 }
    747 
    748 static int define_fielded(struct Stream *s, long long stride,
    749                           const char *total_name, int total_name_len)
    750 {
    751     /* Parses `%struct NAME { f1 f2 ... }` or `%enum NAME { ... }` and
    752      * synthesizes N+1 zero-parameter macros:
    753      *   NAME.field_k  -> k * stride
    754      *   NAME.<total>  -> N * stride    (SIZE for struct, COUNT for enum)
    755      * The closing } must be immediately followed by TOK_NEWLINE. The
    756      * newline is consumed iff the directive started at line_start. */
    757     struct TextSpan base;
    758     long long index = 0;
    759     int started_at_line_start = s->line_start;
    760 
    761     s->pos++;
    762     skip_newlines(&s->pos, s->end);
    763     if (s->pos >= s->end || s->pos->kind != TOK_WORD) {
    764         return fail("bad directive");
    765     }
    766     base = s->pos->text;
    767     s->pos++;
    768 
    769     skip_newlines(&s->pos, s->end);
    770     if (s->pos >= s->end || s->pos->kind != TOK_LBRACE) {
    771         return fail("bad directive");
    772     }
    773     s->pos++;
    774 
    775     for (;;) {
    776         while (s->pos < s->end &&
    777                (s->pos->kind == TOK_COMMA || s->pos->kind == TOK_NEWLINE)) {
    778             s->pos++;
    779         }
    780         if (s->pos >= s->end) {
    781             return fail("unterminated directive");
    782         }
    783         if (s->pos->kind == TOK_RBRACE) {
    784             s->pos++;
    785             break;
    786         }
    787         if (s->pos->kind != TOK_WORD) {
    788             return fail("bad directive");
    789         }
    790         if (!define_fielded_macro(base, s->pos->text.ptr, s->pos->text.len,
    791                                   index * stride)) {
    792             return 0;
    793         }
    794         s->pos++;
    795         index++;
    796     }
    797 
    798     if (!define_fielded_macro(base, total_name, total_name_len, index * stride)) {
    799         return 0;
    800     }
    801 
    802     if (s->pos >= s->end || s->pos->kind != TOK_NEWLINE) {
    803         return fail("expected newline after struct/enum");
    804     }
    805     if (started_at_line_start) {
    806         s->pos++;
    807         s->line_start = 1;
    808     }
    809     return 1;
    810 }
    811 
    812 static int define_macro(struct Stream *s)
    813 {
    814     /* Header is whitespace-insensitive: newlines inside (...) and around
    815      * the keywords are skipped. Body collection skips newlines that fall
    816      * between `)` and the first body token (so `%macro N()\nbody\n%endm`
    817      * has body=[WORD body, NEWLINE], same as the old required-newline form).
    818      * %endm is recognized anywhere in the body; the next token must be
    819      * TOK_NEWLINE. The newline is consumed only when the directive started
    820      * at s->line_start — that way mid-line directives leave the user's
    821      * trailing newline in the stream for the main loop to emit. */
    822     struct Macro *m;
    823     int started_at_line_start = s->line_start;
    824 
    825     if (macro_count >= MAX_MACROS) {
    826         return fail("too many macros");
    827     }
    828     if (macro_body_used >= MAX_MACRO_BODY_TOKENS) {
    829         return fail("macro body overflow");
    830     }
    831 
    832     m = &macros[macro_count];
    833     memset(m, 0, sizeof(*m));
    834     s->pos++;
    835 
    836     skip_newlines(&s->pos, s->end);
    837     if (s->pos >= s->end || s->pos->kind != TOK_WORD) {
    838         return fail("bad macro header");
    839     }
    840     m->name = s->pos->text;
    841     s->pos++;
    842 
    843     skip_newlines(&s->pos, s->end);
    844     if (s->pos >= s->end || s->pos->kind != TOK_LPAREN) {
    845         return fail("bad macro header");
    846     }
    847     s->pos++;
    848 
    849     skip_newlines(&s->pos, s->end);
    850     if (s->pos < s->end && s->pos->kind != TOK_RPAREN) {
    851         while (1) {
    852             if (m->param_count >= MAX_PARAMS) {
    853                 return fail("bad macro header");
    854             }
    855             if (s->pos >= s->end || s->pos->kind != TOK_WORD) {
    856                 return fail("bad macro header");
    857             }
    858             m->params[m->param_count] = s->pos->text;
    859             m->param_count++;
    860             s->pos++;
    861             skip_newlines(&s->pos, s->end);
    862             if (s->pos < s->end && s->pos->kind == TOK_COMMA) {
    863                 s->pos++;
    864                 skip_newlines(&s->pos, s->end);
    865                 continue;
    866             }
    867             break;
    868         }
    869     }
    870 
    871     if (s->pos >= s->end || s->pos->kind != TOK_RPAREN) {
    872         return fail("bad macro header");
    873     }
    874     s->pos++;
    875     skip_newlines(&s->pos, s->end);
    876 
    877     m->body_start = macro_body_tokens + macro_body_used;
    878     while (s->pos < s->end) {
    879         int idx;
    880 
    881         if (s->pos->kind == TOK_WORD && token_text_eq(s->pos, "%endm")) {
    882             s->pos++;
    883             if (s->pos >= s->end || s->pos->kind != TOK_NEWLINE) {
    884                 return fail("expected newline after %endm");
    885             }
    886             if (started_at_line_start) {
    887                 s->pos++;
    888                 s->line_start = 1;
    889             }
    890             m->body_end = macro_body_tokens + macro_body_used;
    891             macro_count++;
    892             return 1;
    893         }
    894         if (macro_body_used >= MAX_MACRO_BODY_TOKENS) {
    895             return fail("macro body overflow");
    896         }
    897         idx = macro_body_used;
    898         macro_body_tokens[idx] = *s->pos;
    899         macro_body_param_idx[idx] = (unsigned char)find_param(m, s->pos);
    900         macro_body_is_local_label[idx] =
    901             is_local_label_token(s->pos) ? 1 : 0;
    902         if (s->pos->kind == TOK_PASTE) {
    903             m->has_paste = 1;
    904         }
    905         macro_body_used++;
    906         s->pos++;
    907     }
    908 
    909     return fail("unterminated macro");
    910 }
    911 
    912 static int parse_args(struct Token *lparen, struct Token *limit)
    913 {
    914     struct Token *tok = lparen + 1;
    915     struct Token *arg_start = tok;
    916     int depth = 1;
    917     int brace_depth = 0;
    918     int arg_index = 0;
    919 
    920     args_have_paste = 0;
    921 
    922     while (tok < limit) {
    923         if (tok->kind == TOK_PASTE) {
    924             args_have_paste = 1;
    925         }
    926         if (tok->kind == TOK_LPAREN) {
    927             depth++;
    928             tok++;
    929             continue;
    930         }
    931         if (tok->kind == TOK_RPAREN) {
    932             depth--;
    933             if (depth == 0) {
    934                 if (brace_depth != 0) {
    935                     return fail("unbalanced braces");
    936                 }
    937                 if (arg_start == tok && arg_index == 0) {
    938                     arg_count = 0;
    939                 } else {
    940                     if (arg_index >= MAX_PARAMS) {
    941                         return fail("too many args");
    942                     }
    943                     arg_starts[arg_index] = arg_start;
    944                     arg_ends[arg_index] = tok;
    945                     arg_count = arg_index + 1;
    946                 }
    947                 call_end_pos = tok + 1;
    948                 return 1;
    949             }
    950             tok++;
    951             continue;
    952         }
    953         if (tok->kind == TOK_LBRACE) {
    954             brace_depth++;
    955             tok++;
    956             continue;
    957         }
    958         if (tok->kind == TOK_RBRACE) {
    959             if (brace_depth <= 0) {
    960                 return fail("unbalanced braces");
    961             }
    962             brace_depth--;
    963             tok++;
    964             continue;
    965         }
    966         if (tok->kind == TOK_COMMA && depth == 1 && brace_depth == 0) {
    967             if (arg_index >= MAX_PARAMS) {
    968                 return fail("too many args");
    969             }
    970             arg_starts[arg_index] = arg_start;
    971             arg_ends[arg_index] = tok;
    972             arg_index++;
    973             arg_start = tok + 1;
    974             tok++;
    975             continue;
    976         }
    977         tok++;
    978     }
    979 
    980     return fail("unterminated macro call");
    981 }
    982 
    983 static int arg_is_braced(struct TokenSpan span)
    984 {
    985     struct Token *tok;
    986     int depth;
    987 
    988     if (span.end - span.start < 2) {
    989         return 0;
    990     }
    991     if (span.start->kind != TOK_LBRACE ||
    992         (span.end - 1)->kind != TOK_RBRACE) {
    993         return 0;
    994     }
    995     depth = 0;
    996     for (tok = span.start; tok < span.end; tok++) {
    997         if (tok->kind == TOK_LBRACE) {
    998             depth++;
    999         } else if (tok->kind == TOK_RBRACE) {
   1000             depth--;
   1001             if (depth == 0 && tok != span.end - 1) {
   1002                 return 0;
   1003             }
   1004         }
   1005     }
   1006     return depth == 0;
   1007 }
   1008 
   1009 static int copy_arg_tokens_to_pool(struct TokenSpan span)
   1010 {
   1011     if (span.start == span.end) {
   1012         return fail("bad macro argument");
   1013     }
   1014     if (arg_is_braced(span)) {
   1015         struct TokenSpan inner;
   1016         inner.start = span.start + 1;
   1017         inner.end = span.end - 1;
   1018         if (inner.start == inner.end) {
   1019             return 1;
   1020         }
   1021         return copy_span_to_pool(inner);
   1022     }
   1023     return copy_span_to_pool(span);
   1024 }
   1025 
   1026 static int copy_paste_arg_to_pool(struct TokenSpan span)
   1027 {
   1028     if (arg_is_braced(span)) {
   1029         return fail("bad macro argument");
   1030     }
   1031     if (span.end - span.start != 1) {
   1032         return fail("bad macro argument");
   1033     }
   1034     return copy_span_to_pool(span);
   1035 }
   1036 
   1037 static int append_pasted_token(struct Token *dst,
   1038                                const struct Token *left,
   1039                                const struct Token *right)
   1040 {
   1041     char tmp[512];
   1042     char *text_ptr;
   1043     int n;
   1044 
   1045     n = snprintf(tmp, sizeof(tmp), "%.*s%.*s",
   1046                  left->text.len, left->text.ptr,
   1047                  right->text.len, right->text.ptr);
   1048     if (n < 0 || n >= (int)sizeof(tmp)) {
   1049         return fail("bad paste");
   1050     }
   1051     text_ptr = append_text_len(tmp, n);
   1052     if (text_ptr == NULL) {
   1053         return 0;
   1054     }
   1055     dst->kind = TOK_WORD;
   1056     dst->tight = 0;
   1057     dst->text.ptr = text_ptr;
   1058     dst->text.len = n;
   1059     return 1;
   1060 }
   1061 
   1062 static int paste_pool_range(int mark)
   1063 {
   1064     /* Skip newlines on both sides of TOK_PASTE: a body like `foo ##\n bar`
   1065      * pastes to `foobar`, discarding the intervening newline. The left
   1066      * operand is the rightmost non-newline already copied to `out`; the
   1067      * right operand is the next non-newline past PASTE in `in`. */
   1068     struct Token *start = expand_pool + mark;
   1069     struct Token *in = start;
   1070     struct Token *out = start;
   1071     struct Token *end = expand_pool + pool_used;
   1072 
   1073     while (in < end) {
   1074         if (in->kind == TOK_PASTE) {
   1075             struct Token *left = out;
   1076             struct Token *right = in + 1;
   1077 
   1078             while (left > start && (left - 1)->kind == TOK_NEWLINE) {
   1079                 left--;
   1080             }
   1081             if (left == start) {
   1082                 pool_used = mark;
   1083                 return fail("bad paste");
   1084             }
   1085             left--;
   1086             if (left->kind == TOK_PASTE) {
   1087                 pool_used = mark;
   1088                 return fail("bad paste");
   1089             }
   1090             while (right < end && right->kind == TOK_NEWLINE) {
   1091                 right++;
   1092             }
   1093             if (right >= end || right->kind == TOK_PASTE) {
   1094                 pool_used = mark;
   1095                 return fail("bad paste");
   1096             }
   1097             if (!append_pasted_token(left, left, right)) {
   1098                 pool_used = mark;
   1099                 return 0;
   1100             }
   1101             out = left + 1;
   1102             in = right + 1;
   1103             continue;
   1104         }
   1105         if (out != in) {
   1106             *out = *in;
   1107         }
   1108         out++;
   1109         in++;
   1110     }
   1111 
   1112     pool_used = (int)(out - expand_pool);
   1113     return 1;
   1114 }
   1115 
   1116 static int is_local_label_token(const struct Token *tok)
   1117 {
   1118     if (tok->kind != TOK_WORD || tok->text.len < 3) {
   1119         return 0;
   1120     }
   1121     if (tok->text.ptr[0] != ':' && tok->text.ptr[0] != '&') {
   1122         return 0;
   1123     }
   1124     if (tok->text.ptr[1] != '@') {
   1125         return 0;
   1126     }
   1127     return 1;
   1128 }
   1129 
   1130 static int push_local_label_token(const struct Token *tok, int expansion_id)
   1131 {
   1132     /* Rewrite ":@name" -> ":name__NN", "&@name" -> "&name__NN".
   1133      * Build the text directly in text_buf so the resulting span is stable. */
   1134     char digits[16];
   1135     int digit_count = 0;
   1136     int unsigned_id;
   1137     int start;
   1138     int total;
   1139     int i;
   1140     struct Token out;
   1141 
   1142     unsigned_id = expansion_id;
   1143     if (unsigned_id == 0) {
   1144         digits[digit_count++] = '0';
   1145     } else {
   1146         while (unsigned_id > 0) {
   1147             digits[digit_count++] = (char)('0' + (unsigned_id % 10));
   1148             unsigned_id /= 10;
   1149         }
   1150     }
   1151 
   1152     /* Reserve: sigil(1) + tail(len-2) + "__"(2) + digits + NUL. */
   1153     total = 1 + (tok->text.len - 2) + 2 + digit_count;
   1154     if (text_used + total + 1 > MAX_TEXT) {
   1155         return fail("text overflow");
   1156     }
   1157     start = text_used;
   1158     text_buf[text_used++] = tok->text.ptr[0];
   1159     memcpy(text_buf + text_used, tok->text.ptr + 2, (size_t)(tok->text.len - 2));
   1160     text_used += tok->text.len - 2;
   1161     text_buf[text_used++] = '_';
   1162     text_buf[text_used++] = '_';
   1163     for (i = digit_count - 1; i >= 0; i--) {
   1164         text_buf[text_used++] = digits[i];
   1165     }
   1166     text_buf[text_used++] = '\0';
   1167 
   1168     out.kind = TOK_WORD;
   1169     out.tight = 0;
   1170     out.line = current_line;
   1171     out.text.ptr = text_buf + start;
   1172     out.text.len = total;
   1173     return push_pool_token(out);
   1174 }
   1175 
   1176 static int expand_macro_tokens(struct Token *call_tok, struct Token *limit,
   1177                                const struct Macro *m, struct Token **after_out,
   1178                                int *mark_out)
   1179 {
   1180     struct Token *body_tok;
   1181     struct Token *end_pos;
   1182     int mark;
   1183     int expansion_id;
   1184     int saw_arg_paste = 0;
   1185 
   1186     if (call_tok + 1 < limit && (call_tok + 1)->kind == TOK_LPAREN &&
   1187         (call_tok + 1)->tight) {
   1188         if (!parse_args(call_tok + 1, limit)) {
   1189             return 0;
   1190         }
   1191         if (arg_count != m->param_count) {
   1192             return fail("wrong arg count");
   1193         }
   1194         end_pos = call_end_pos;
   1195         saw_arg_paste = args_have_paste;
   1196     } else if (m->param_count == 0) {
   1197         arg_count = 0;
   1198         end_pos = call_tok + 1;
   1199     } else {
   1200         return fail("bad macro call");
   1201     }
   1202 
   1203     expansion_id = ++next_expansion_id;
   1204     mark = pool_used;
   1205     for (body_tok = m->body_start; body_tok < m->body_end; body_tok++) {
   1206         int idx = (int)(body_tok - macro_body_tokens);
   1207         int param_idx = macro_body_param_idx[idx];
   1208         int pasted = 0;
   1209         int ok;
   1210 
   1211         if (param_idx != 0) {
   1212             struct TokenSpan arg = {arg_starts[param_idx - 1], arg_ends[param_idx - 1]};
   1213             pasted = (body_tok > m->body_start && (body_tok - 1)->kind == TOK_PASTE) ||
   1214                      (body_tok + 1 < m->body_end && (body_tok + 1)->kind == TOK_PASTE);
   1215             ok = pasted ? copy_paste_arg_to_pool(arg) : copy_arg_tokens_to_pool(arg);
   1216             if (!ok) {
   1217                 pool_used = mark;
   1218                 return 0;
   1219             }
   1220             continue;
   1221         }
   1222         if (macro_body_is_local_label[idx]) {
   1223             if (!push_local_label_token(body_tok, expansion_id)) {
   1224                 pool_used = mark;
   1225                 return 0;
   1226             }
   1227             continue;
   1228         }
   1229         if (!push_pool_token(*body_tok)) {
   1230             pool_used = mark;
   1231             return 0;
   1232         }
   1233     }
   1234 
   1235     if ((m->has_paste || saw_arg_paste) && !paste_pool_range(mark)) {
   1236         return 0;
   1237     }
   1238     *after_out = end_pos;
   1239     *mark_out = mark;
   1240     return 1;
   1241 }
   1242 
   1243 static int parse_int_token(const struct Token *tok, long long *out)
   1244 {
   1245     char tmp[128];
   1246     char *end;
   1247     unsigned long long uv;
   1248     long long sv;
   1249 
   1250     if (tok->kind != TOK_WORD || tok->text.len <= 0 || tok->text.len >= (int)sizeof(tmp)) {
   1251         return fail("bad integer");
   1252     }
   1253     memcpy(tmp, tok->text.ptr, (size_t)tok->text.len);
   1254     tmp[tok->text.len] = '\0';
   1255 
   1256     errno = 0;
   1257     if (tmp[0] == '-') {
   1258         sv = strtoll(tmp, &end, 0);
   1259         if (errno != 0 || *end != '\0') {
   1260             return fail("bad integer");
   1261         }
   1262         *out = sv;
   1263         return 1;
   1264     }
   1265 
   1266     uv = strtoull(tmp, &end, 0);
   1267     if (errno != 0 || *end != '\0') {
   1268         return fail("bad integer");
   1269     }
   1270     *out = (long long)uv;
   1271     return 1;
   1272 }
   1273 
   1274 static enum ExprOp expr_op_code(const struct Token *tok)
   1275 {
   1276     if (tok->kind != TOK_WORD) {
   1277         return EXPR_INVALID;
   1278     }
   1279     if (token_text_eq(tok, "+")) {
   1280         return EXPR_ADD;
   1281     }
   1282     if (token_text_eq(tok, "-")) {
   1283         return EXPR_SUB;
   1284     }
   1285     if (token_text_eq(tok, "*")) {
   1286         return EXPR_MUL;
   1287     }
   1288     if (token_text_eq(tok, "/")) {
   1289         return EXPR_DIV;
   1290     }
   1291     if (token_text_eq(tok, "%")) {
   1292         return EXPR_MOD;
   1293     }
   1294     if (token_text_eq(tok, "<<")) {
   1295         return EXPR_SHL;
   1296     }
   1297     if (token_text_eq(tok, ">>")) {
   1298         return EXPR_SHR;
   1299     }
   1300     if (token_text_eq(tok, "&")) {
   1301         return EXPR_AND;
   1302     }
   1303     if (token_text_eq(tok, "|")) {
   1304         return EXPR_OR;
   1305     }
   1306     if (token_text_eq(tok, "^")) {
   1307         return EXPR_XOR;
   1308     }
   1309     if (token_text_eq(tok, "~")) {
   1310         return EXPR_NOT;
   1311     }
   1312     if (token_text_eq(tok, "=")) {
   1313         return EXPR_EQ;
   1314     }
   1315     if (token_text_eq(tok, "!=")) {
   1316         return EXPR_NE;
   1317     }
   1318     if (token_text_eq(tok, "<")) {
   1319         return EXPR_LT;
   1320     }
   1321     if (token_text_eq(tok, "<=")) {
   1322         return EXPR_LE;
   1323     }
   1324     if (token_text_eq(tok, ">")) {
   1325         return EXPR_GT;
   1326     }
   1327     if (token_text_eq(tok, ">=")) {
   1328         return EXPR_GE;
   1329     }
   1330     if (token_text_eq(tok, "strlen")) {
   1331         return EXPR_STRLEN;
   1332     }
   1333     return EXPR_INVALID;
   1334 }
   1335 
   1336 static int apply_expr_op(enum ExprOp op, const long long *args, int argc, long long *out)
   1337 {
   1338     int i;
   1339 
   1340     switch (op) {
   1341     case EXPR_ADD:
   1342         if (argc < 1) {
   1343             return fail("bad expression");
   1344         }
   1345         *out = args[0];
   1346         for (i = 1; i < argc; i++) {
   1347             *out += args[i];
   1348         }
   1349         return 1;
   1350     case EXPR_SUB:
   1351         if (argc < 1) {
   1352             return fail("bad expression");
   1353         }
   1354         *out = (argc == 1) ? -args[0] : args[0];
   1355         for (i = 1; i < argc; i++) {
   1356             *out -= args[i];
   1357         }
   1358         return 1;
   1359     case EXPR_MUL:
   1360         if (argc < 1) {
   1361             return fail("bad expression");
   1362         }
   1363         *out = args[0];
   1364         for (i = 1; i < argc; i++) {
   1365             *out *= args[i];
   1366         }
   1367         return 1;
   1368     case EXPR_DIV:
   1369         if (argc != 2 || args[1] == 0) {
   1370             return fail("bad expression");
   1371         }
   1372         *out = args[0] / args[1];
   1373         return 1;
   1374     case EXPR_MOD:
   1375         if (argc != 2 || args[1] == 0) {
   1376             return fail("bad expression");
   1377         }
   1378         *out = args[0] % args[1];
   1379         return 1;
   1380     case EXPR_SHL:
   1381         if (argc != 2) {
   1382             return fail("bad expression");
   1383         }
   1384         *out = (long long)((unsigned long long)args[0] << args[1]);
   1385         return 1;
   1386     case EXPR_SHR:
   1387         if (argc != 2) {
   1388             return fail("bad expression");
   1389         }
   1390         *out = args[0] >> args[1];
   1391         return 1;
   1392     case EXPR_AND:
   1393         if (argc < 1) {
   1394             return fail("bad expression");
   1395         }
   1396         *out = args[0];
   1397         for (i = 1; i < argc; i++) {
   1398             *out &= args[i];
   1399         }
   1400         return 1;
   1401     case EXPR_OR:
   1402         if (argc < 1) {
   1403             return fail("bad expression");
   1404         }
   1405         *out = args[0];
   1406         for (i = 1; i < argc; i++) {
   1407             *out |= args[i];
   1408         }
   1409         return 1;
   1410     case EXPR_XOR:
   1411         if (argc < 1) {
   1412             return fail("bad expression");
   1413         }
   1414         *out = args[0];
   1415         for (i = 1; i < argc; i++) {
   1416             *out ^= args[i];
   1417         }
   1418         return 1;
   1419     case EXPR_NOT:
   1420         if (argc != 1) {
   1421             return fail("bad expression");
   1422         }
   1423         *out = ~args[0];
   1424         return 1;
   1425     case EXPR_EQ:
   1426         if (argc != 2) {
   1427             return fail("bad expression");
   1428         }
   1429         *out = (args[0] == args[1]);
   1430         return 1;
   1431     case EXPR_NE:
   1432         if (argc != 2) {
   1433             return fail("bad expression");
   1434         }
   1435         *out = (args[0] != args[1]);
   1436         return 1;
   1437     case EXPR_LT:
   1438         if (argc != 2) {
   1439             return fail("bad expression");
   1440         }
   1441         *out = (args[0] < args[1]);
   1442         return 1;
   1443     case EXPR_LE:
   1444         if (argc != 2) {
   1445             return fail("bad expression");
   1446         }
   1447         *out = (args[0] <= args[1]);
   1448         return 1;
   1449     case EXPR_GT:
   1450         if (argc != 2) {
   1451             return fail("bad expression");
   1452         }
   1453         *out = (args[0] > args[1]);
   1454         return 1;
   1455     case EXPR_GE:
   1456         if (argc != 2) {
   1457             return fail("bad expression");
   1458         }
   1459         *out = (args[0] >= args[1]);
   1460         return 1;
   1461     case EXPR_STRLEN:
   1462     case EXPR_INVALID:
   1463         break;
   1464     }
   1465 
   1466     return fail("bad expression");
   1467 }
   1468 
   1469 static int eval_expr_range(struct TokenSpan span, long long *out);
   1470 
   1471 static int expand_local_into_pool(struct Token *call_tok, struct Token *limit,
   1472                                   struct Token **after_out, int *mark_out)
   1473 {
   1474     /* Resolve %local(NAME) against the current frame: build the lookup
   1475      * key "<frame>_FRAME.<NAME>" and copy the matching macro's body
   1476      * into the pool. NAME must be exactly one WORD token. The pool
   1477      * mark and the position past the call's `)` are returned so the
   1478      * caller can either push the body as a stream (process_tokens) or
   1479      * recursively eval it as an expression (eval_expr_atom). */
   1480     char name[256];
   1481     int frame_len;
   1482     int arg_len;
   1483     int name_len;
   1484     int i;
   1485     const struct Macro *m = NULL;
   1486     struct Token *arg_tok;
   1487     int mark = pool_used;
   1488 
   1489     if (call_tok + 1 >= limit || (call_tok + 1)->kind != TOK_LPAREN ||
   1490         !(call_tok + 1)->tight) {
   1491         return fail("bad builtin");
   1492     }
   1493     if (!parse_args(call_tok + 1, limit)) {
   1494         return 0;
   1495     }
   1496     if (arg_count != 1) {
   1497         return fail("bad builtin");
   1498     }
   1499     if (arg_ends[0] - arg_starts[0] != 1) {
   1500         return fail("bad builtin");
   1501     }
   1502     arg_tok = arg_starts[0];
   1503     if (arg_tok->kind != TOK_WORD) {
   1504         return fail("bad builtin");
   1505     }
   1506     if (!frame_active) {
   1507         return fail("local outside frame");
   1508     }
   1509 
   1510     frame_len = current_frame.len;
   1511     arg_len = arg_tok->text.len;
   1512     name_len = frame_len + 7 /* _FRAME. */ + arg_len;
   1513     if (name_len >= (int)sizeof(name)) {
   1514         return fail("local name too long");
   1515     }
   1516     memcpy(name, current_frame.ptr, (size_t)frame_len);
   1517     memcpy(name + frame_len, "_FRAME.", 7);
   1518     memcpy(name + frame_len + 7, arg_tok->text.ptr, (size_t)arg_len);
   1519 
   1520     for (i = 0; i < macro_count; i++) {
   1521         if (macros[i].name.len == name_len &&
   1522             memcmp(macros[i].name.ptr, name, (size_t)name_len) == 0) {
   1523             m = &macros[i];
   1524             break;
   1525         }
   1526     }
   1527     if (m == NULL) {
   1528         return fail("unknown local");
   1529     }
   1530 
   1531     if (!copy_span_to_pool((struct TokenSpan){m->body_start, m->body_end})) {
   1532         pool_used = mark;
   1533         return 0;
   1534     }
   1535     *after_out = call_end_pos;
   1536     *mark_out = mark;
   1537     return 1;
   1538 }
   1539 
   1540 static int eval_expr_atom(struct Token *tok, struct Token *limit,
   1541                           struct Token **after_out, long long *out)
   1542 {
   1543     const struct Macro *macro;
   1544     struct Token *after;
   1545     int mark;
   1546 
   1547     if (tok->kind == TOK_WORD && token_text_eq(tok, "%local")) {
   1548         if (!expand_local_into_pool(tok, limit, &after, &mark)) {
   1549             return 0;
   1550         }
   1551         if (pool_used == mark) {
   1552             pool_used = mark;
   1553             return fail("bad expression");
   1554         }
   1555         if (!eval_expr_range((struct TokenSpan){expand_pool + mark, expand_pool + pool_used}, out)) {
   1556             pool_used = mark;
   1557             return 0;
   1558         }
   1559         pool_used = mark;
   1560         *after_out = after;
   1561         return 1;
   1562     }
   1563 
   1564     macro = find_macro(tok);
   1565     if (macro != NULL &&
   1566         ((tok + 1 < limit && (tok + 1)->kind == TOK_LPAREN &&
   1567           (tok + 1)->tight) ||
   1568          macro->param_count == 0)) {
   1569         if (!expand_macro_tokens(tok, limit, macro, &after, &mark)) {
   1570             return 0;
   1571         }
   1572         if (pool_used == mark) {
   1573             pool_used = mark;
   1574             return fail("bad expression");
   1575         }
   1576         if (!eval_expr_range((struct TokenSpan){expand_pool + mark, expand_pool + pool_used}, out)) {
   1577             pool_used = mark;
   1578             return 0;
   1579         }
   1580         pool_used = mark;
   1581         *after_out = after;
   1582         return 1;
   1583     }
   1584 
   1585     if (!parse_int_token(tok, out)) {
   1586         return 0;
   1587     }
   1588     *after_out = tok + 1;
   1589     return 1;
   1590 }
   1591 
   1592 static int eval_expr_range(struct TokenSpan span, long long *out)
   1593 {
   1594     struct ExprFrame frames[MAX_EXPR_FRAMES];
   1595     int frame_top = 0;
   1596     struct Token *pos = span.start;
   1597     long long value = 0;
   1598     long long result = 0;
   1599     int have_value = 0;
   1600     int have_result = 0;
   1601 
   1602     for (;;) {
   1603         if (have_value) {
   1604             if (frame_top > 0) {
   1605                 struct ExprFrame *frame = &frames[frame_top - 1];
   1606 
   1607                 if (frame->argc >= MAX_PARAMS) {
   1608                     return fail("bad expression");
   1609                 }
   1610                 frame->args[frame->argc++] = value;
   1611                 have_value = 0;
   1612                 continue;
   1613             }
   1614             if (have_result) {
   1615                 return fail("bad expression");
   1616             }
   1617             result = value;
   1618             have_result = 1;
   1619             have_value = 0;
   1620             continue;
   1621         }
   1622 
   1623         skip_newlines(&pos, span.end);
   1624         if (pos >= span.end) {
   1625             break;
   1626         }
   1627         if (pos->line > 0) {
   1628             current_line = pos->line;
   1629         }
   1630 
   1631         if (pos->kind == TOK_LPAREN) {
   1632             enum ExprOp op;
   1633 
   1634             pos++;
   1635             skip_newlines(&pos, span.end);
   1636             if (pos >= span.end) {
   1637                 return fail("bad expression");
   1638             }
   1639             op = expr_op_code(pos);
   1640             if (op == EXPR_INVALID) {
   1641                 return fail("bad expression");
   1642             }
   1643             pos++;
   1644             if (op == EXPR_STRLEN) {
   1645                 /* strlen is degenerate: argument is a TOK_STRING atom,
   1646                  * not a recursive expression. Handle inline and yield
   1647                  * the string's raw byte count (span.len - 2). */
   1648                 skip_newlines(&pos, span.end);
   1649                 if (pos >= span.end || pos->kind != TOK_STRING) {
   1650                     return fail("bad expression");
   1651                 }
   1652                 if (pos->text.len < 2 || pos->text.ptr[0] != '"') {
   1653                     return fail("bad expression");
   1654                 }
   1655                 value = (long long)(pos->text.len - 2);
   1656                 pos++;
   1657                 skip_newlines(&pos, span.end);
   1658                 if (pos >= span.end || pos->kind != TOK_RPAREN) {
   1659                     return fail("bad expression");
   1660                 }
   1661                 pos++;
   1662                 have_value = 1;
   1663                 continue;
   1664             }
   1665             if (frame_top >= MAX_EXPR_FRAMES) {
   1666                 return fail("expression overflow");
   1667             }
   1668             frames[frame_top].op = op;
   1669             frames[frame_top].argc = 0;
   1670             frame_top++;
   1671             continue;
   1672         }
   1673 
   1674         if (pos->kind == TOK_RPAREN) {
   1675             if (frame_top <= 0) {
   1676                 return fail("bad expression");
   1677             }
   1678             if (!apply_expr_op(frames[frame_top - 1].op,
   1679                                frames[frame_top - 1].args,
   1680                                frames[frame_top - 1].argc,
   1681                                &value)) {
   1682                 return 0;
   1683             }
   1684             frame_top--;
   1685             pos++;
   1686             have_value = 1;
   1687             continue;
   1688         }
   1689 
   1690         if (!eval_expr_atom(pos, span.end, &pos, &value)) {
   1691             return 0;
   1692         }
   1693         have_value = 1;
   1694     }
   1695 
   1696     if (frame_top != 0 || !have_result) {
   1697         return fail("bad expression");
   1698     }
   1699     if (pos != span.end) {
   1700         return fail("bad expression");
   1701     }
   1702 
   1703     *out = result;
   1704     return 1;
   1705 }
   1706 
   1707 static int emit_hex_value(unsigned long long value, int bytes)
   1708 {
   1709     /* Emit the bytes as bare little-endian hex digits. hex2pp's byte-
   1710      * stream parser groups every two hex digits into one byte; no
   1711      * quoting or separators are needed. */
   1712     char tmp[17];
   1713     static const char hex[] = "0123456789ABCDEF";
   1714     struct Token tok;
   1715     int i;
   1716     char *text_ptr;
   1717     int total_len = 2 * bytes;
   1718 
   1719     for (i = 0; i < bytes; i++) {
   1720         unsigned int b = (unsigned int)((value >> (8 * i)) & 0xFF);
   1721         tmp[2 * i] = hex[b >> 4];
   1722         tmp[2 * i + 1] = hex[b & 0x0F];
   1723     }
   1724     tmp[total_len] = '\0';
   1725 
   1726     text_ptr = append_text_len(tmp, total_len);
   1727     if (text_ptr == NULL) {
   1728         return 0;
   1729     }
   1730     tok.kind = TOK_WORD;
   1731     tok.tight = 0;
   1732     tok.line = current_line;
   1733     tok.text.ptr = text_ptr;
   1734     tok.text.len = total_len;
   1735     return emit_token(&tok);
   1736 }
   1737 
   1738 static int expand_builtin_call(struct Stream *s, const struct Token *tok)
   1739 {
   1740     long long value;
   1741 
   1742     if (tok + 1 >= s->end || (tok + 1)->kind != TOK_LPAREN) {
   1743         return fail("bad builtin");
   1744     }
   1745     if (!parse_args((struct Token *)tok + 1, s->end)) {
   1746         return 0;
   1747     }
   1748 
   1749     if (token_text_eq(tok, "!") || token_text_eq(tok, "@") ||
   1750         token_text_eq(tok, "%") || token_text_eq(tok, "$")) {
   1751         struct TokenSpan arg;
   1752         struct Token *end_pos;
   1753         int bytes;
   1754 
   1755         if (arg_count != 1) {
   1756             return fail("bad builtin");
   1757         }
   1758         arg.start = arg_starts[0];
   1759         arg.end = arg_ends[0];
   1760         end_pos = call_end_pos;
   1761         if (!eval_expr_range(arg, &value)) {
   1762             return 0;
   1763         }
   1764         s->pos = end_pos;
   1765         s->line_start = 0;
   1766         bytes = token_text_eq(tok, "!") ? 1 :
   1767                 token_text_eq(tok, "@") ? 2 :
   1768                 token_text_eq(tok, "%") ? 4 : 8;
   1769         return emit_hex_value((unsigned long long)value, bytes);
   1770     }
   1771 
   1772     if (token_text_eq(tok, "%select")) {
   1773         struct TokenSpan cond_arg, then_arg, else_arg, chosen;
   1774         struct Token *end_pos;
   1775         int mark;
   1776 
   1777         if (arg_count != 3) {
   1778             return fail("bad builtin");
   1779         }
   1780         cond_arg.start = arg_starts[0]; cond_arg.end = arg_ends[0];
   1781         then_arg.start = arg_starts[1]; then_arg.end = arg_ends[1];
   1782         else_arg.start = arg_starts[2]; else_arg.end = arg_ends[2];
   1783         end_pos = call_end_pos;
   1784         if (!eval_expr_range(cond_arg, &value)) {
   1785             return 0;
   1786         }
   1787         chosen = (value != 0) ? then_arg : else_arg;
   1788         s->pos = end_pos;
   1789         s->line_start = 0;
   1790         if (chosen.start == chosen.end) {
   1791             return 1;
   1792         }
   1793         mark = pool_used;
   1794         if (!copy_span_to_pool(chosen)) {
   1795             pool_used = mark;
   1796             return 0;
   1797         }
   1798         return push_pool_stream_from_mark(mark);
   1799     }
   1800 
   1801     if (token_text_eq(tok, "%local")) {
   1802         struct Token *after;
   1803         int mark;
   1804 
   1805         if (!expand_local_into_pool((struct Token *)tok, s->end, &after, &mark)) {
   1806             return 0;
   1807         }
   1808         s->pos = after;
   1809         s->line_start = 0;
   1810         return push_pool_stream_from_mark(mark);
   1811     }
   1812 
   1813     if (token_text_eq(tok, "%str")) {
   1814         struct Token *arg_tok;
   1815         struct Token *end_pos;
   1816         struct Token out_tok;
   1817         char *text_ptr;
   1818         int orig_len;
   1819         int out_len;
   1820 
   1821         if (arg_count != 1) {
   1822             return fail("bad builtin");
   1823         }
   1824         if (arg_ends[0] - arg_starts[0] != 1) {
   1825             return fail("bad builtin");
   1826         }
   1827         arg_tok = arg_starts[0];
   1828         if (arg_tok->kind != TOK_WORD) {
   1829             return fail("bad builtin");
   1830         }
   1831         end_pos = call_end_pos;
   1832 
   1833         orig_len = arg_tok->text.len;
   1834         out_len = orig_len + 2;
   1835         if (text_used + out_len + 1 > MAX_TEXT) {
   1836             return fail("text overflow");
   1837         }
   1838         text_ptr = text_buf + text_used;
   1839         text_buf[text_used++] = '"';
   1840         memcpy(text_buf + text_used, arg_tok->text.ptr, (size_t)orig_len);
   1841         text_used += orig_len;
   1842         text_buf[text_used++] = '"';
   1843         text_buf[text_used++] = '\0';
   1844 
   1845         out_tok.kind = TOK_STRING;
   1846         out_tok.tight = 0;
   1847         out_tok.line = current_line;
   1848         out_tok.text.ptr = text_ptr;
   1849         out_tok.text.len = out_len;
   1850         s->pos = end_pos;
   1851         s->line_start = 0;
   1852         return emit_token(&out_tok);
   1853     }
   1854 
   1855     return fail("bad builtin");
   1856 }
   1857 
   1858 static int expand_call(struct Stream *s, const struct Macro *macro)
   1859 {
   1860     struct Token *after;
   1861     int mark;
   1862 
   1863     if (!expand_macro_tokens(s->pos, s->end, macro, &after, &mark)) {
   1864         return 0;
   1865     }
   1866     s->pos = after;
   1867     s->line_start = 0;
   1868     return push_pool_stream_from_mark(mark);
   1869 }
   1870 
   1871 static int push_frame(struct Stream *s)
   1872 {
   1873     /* %frame NAME sets the single-slot current frame, used by %local
   1874      * lookup. Frames do not nest: a second %frame before %endframe is
   1875      * an error. The header behaves like %scope (newlines after the
   1876      * name are absorbed when the directive appeared at line_start). */
   1877     int started_at_line_start = s->line_start;
   1878 
   1879     s->pos++;
   1880     skip_newlines(&s->pos, s->end);
   1881     if (s->pos >= s->end || s->pos->kind != TOK_WORD) {
   1882         return fail("bad frame header");
   1883     }
   1884     if (frame_active) {
   1885         return fail("frame already active");
   1886     }
   1887     current_frame = s->pos->text;
   1888     frame_active = 1;
   1889     s->pos++;
   1890     if (started_at_line_start) {
   1891         skip_newlines(&s->pos, s->end);
   1892         s->line_start = 1;
   1893     }
   1894     return 1;
   1895 }
   1896 
   1897 static int pop_frame(struct Stream *s)
   1898 {
   1899     /* %endframe must be immediately followed by TOK_NEWLINE; the newline
   1900      * is consumed iff %endframe itself appeared at line_start. */
   1901     int started_at_line_start = s->line_start;
   1902 
   1903     s->pos++;
   1904     if (!frame_active) {
   1905         return fail("frame underflow");
   1906     }
   1907     frame_active = 0;
   1908     if (s->pos >= s->end || s->pos->kind != TOK_NEWLINE) {
   1909         return fail("expected newline after %endframe");
   1910     }
   1911     if (started_at_line_start) {
   1912         s->pos++;
   1913         s->line_start = 1;
   1914     }
   1915     return 1;
   1916 }
   1917 
   1918 static int reset_output(struct Stream *s)
   1919 {
   1920     long long base;
   1921 
   1922     if (!s->line_start) {
   1923         return fail("bad reset-output directive");
   1924     }
   1925     s->pos++;
   1926     if (s->pos >= s->end || s->pos->kind != TOK_WORD ||
   1927         !parse_int_token(s->pos, &base) || base < 0 || base > 2147483646LL) {
   1928         return fail("bad reset-output directive");
   1929     }
   1930     s->pos++;
   1931     if (s->pos >= s->end || s->pos->kind != TOK_NEWLINE) {
   1932         return fail("bad reset-output directive");
   1933     }
   1934     s->pos++;
   1935     s->line_start = 1;
   1936     output_used = 0;
   1937     output_need_space = 0;
   1938     next_expansion_id = (int)base;
   1939     return 1;
   1940 }
   1941 
   1942 static int process_tokens(void)
   1943 {
   1944     if (!push_stream_span((struct TokenSpan){source_tokens, source_tokens + source_count}, -1)) {
   1945         return 0;
   1946     }
   1947 
   1948     /* Per-token dispatch is gated on the first byte of WORD tokens.
   1949      * Plain pass-through tokens (e.g. hex literals, bare identifiers)
   1950      * fail the c0=='%' / c0 in {!,@,$} test in one byte compare and go
   1951      * straight to emit_token. Within the c0=='%' branch we dispatch on
   1952      * the second byte to pick the matching directive/builtin without
   1953      * walking ~9 token_text_eq probes. */
   1954     for (;;) {
   1955         struct Stream *s;
   1956         struct Token *tok;
   1957 
   1958         s = current_stream();
   1959         if (s == NULL) {
   1960             break;
   1961         }
   1962         if (s->pos >= s->end) {
   1963             pop_stream();
   1964             continue;
   1965         }
   1966 
   1967         tok = s->pos;
   1968         if (tok->line > 0) {
   1969             current_line = tok->line;
   1970         }
   1971 
   1972         if (tok->kind == TOK_NEWLINE) {
   1973             s->pos++;
   1974             s->line_start = 1;
   1975             if (!emit_newline()) {
   1976                 return 0;
   1977             }
   1978             continue;
   1979         }
   1980 
   1981         if (tok->kind == TOK_WORD && tok->text.len >= 1) {
   1982             const char *p = tok->text.ptr;
   1983             int len = tok->text.len;
   1984             char c0 = p[0];
   1985             int has_paren = (tok + 1 < s->end &&
   1986                              (tok + 1)->kind == TOK_LPAREN &&
   1987                              (tok + 1)->tight);
   1988 
   1989             if (c0 == '%' && len >= 2) {
   1990                 char c1 = p[1];
   1991                 const struct Macro *macro;
   1992                 int handled = 0;
   1993 
   1994                 switch (c1) {
   1995                 case 'm':
   1996                     if (len == 6 && memcmp(p + 2, "acro", 4) == 0) {
   1997                         if (!define_macro(s)) return 0;
   1998                         handled = 1;
   1999                     }
   2000                     break;
   2001                 case 's':
   2002                     if (len == 7 && memcmp(p + 2, "truct", 5) == 0) {
   2003                         if (!define_fielded(s, 8, "SIZE", 4)) return 0;
   2004                         handled = 1;
   2005                     } else if (has_paren && len == 7 &&
   2006                                memcmp(p + 2, "elect", 5) == 0) {
   2007                         if (!expand_builtin_call(s, tok)) return 0;
   2008                         handled = 1;
   2009                     } else if (has_paren && len == 4 &&
   2010                                memcmp(p + 2, "tr", 2) == 0) {
   2011                         if (!expand_builtin_call(s, tok)) return 0;
   2012                         handled = 1;
   2013                     }
   2014                     break;
   2015                 case 'e':
   2016                     if (len == 5 && memcmp(p + 2, "num", 3) == 0) {
   2017                         if (!define_fielded(s, 1, "COUNT", 5)) return 0;
   2018                         handled = 1;
   2019                     } else if (len == 9 &&
   2020                                memcmp(p + 2, "ndframe", 7) == 0) {
   2021                         if (!pop_frame(s)) return 0;
   2022                         handled = 1;
   2023                     }
   2024                     break;
   2025                 case 'f':
   2026                     if (len == 6 && memcmp(p + 2, "rame", 4) == 0) {
   2027                         if (!push_frame(s)) return 0;
   2028                         handled = 1;
   2029                     }
   2030                     break;
   2031                 case 'b':
   2032                     if (has_paren && len == 6 &&
   2033                         memcmp(p + 2, "ytes", 4) == 0) {
   2034                         if (!expand_builtin_call(s, tok)) return 0;
   2035                         handled = 1;
   2036                     }
   2037                     break;
   2038                 case 'l':
   2039                     if (has_paren && len == 6 &&
   2040                         memcmp(p + 2, "ocal", 4) == 0) {
   2041                         if (!expand_builtin_call(s, tok)) return 0;
   2042                         handled = 1;
   2043                     }
   2044                     break;
   2045                 case 'r':
   2046                     if (len == 13 &&
   2047                         memcmp(p + 2, "eset-output", 11) == 0) {
   2048                         if (!reset_output(s)) return 0;
   2049                         handled = 1;
   2050                     }
   2051                     break;
   2052                 }
   2053 
   2054                 if (handled) {
   2055                     continue;
   2056                 }
   2057 
   2058                 macro = find_macro(tok);
   2059                 if (macro != NULL &&
   2060                     (has_paren || macro->param_count == 0)) {
   2061                     if (!expand_call(s, macro)) return 0;
   2062                     continue;
   2063                 }
   2064             } else if (len == 1 &&
   2065                        (c0 == '!' || c0 == '@' ||
   2066                         c0 == '$' || c0 == '%')) {
   2067                 if (has_paren) {
   2068                     if (!expand_builtin_call(s, tok)) return 0;
   2069                     continue;
   2070                 }
   2071             }
   2072         }
   2073 
   2074         s->pos++;
   2075         s->line_start = 0;
   2076         if (!emit_token(tok)) {
   2077             return 0;
   2078         }
   2079     }
   2080 
   2081     if (frame_active) {
   2082         return fail("frame not closed");
   2083     }
   2084 
   2085     if (output_used >= MAX_OUTPUT) {
   2086         return fail("output overflow");
   2087     }
   2088     output_buf[output_used] = '\0';
   2089     return 1;
   2090 }
   2091 
   2092 int main(int argc, char **argv)
   2093 {
   2094     FILE *in;
   2095     FILE *out;
   2096     size_t nread;
   2097 
   2098     if (argc != 3) {
   2099         fprintf(stderr, "usage: %s input.M1 output.M1\n", argv[0]);
   2100         return 1;
   2101     }
   2102 
   2103     input_path = argv[1];
   2104     in = fopen(argv[1], "rb");
   2105     if (in == NULL) {
   2106         perror(argv[1]);
   2107         return 1;
   2108     }
   2109     nread = fread(input_buf, 1, MAX_INPUT, in);
   2110     if (ferror(in)) {
   2111         perror(argv[1]);
   2112         fclose(in);
   2113         return 1;
   2114     }
   2115     fclose(in);
   2116     if (nread >= MAX_INPUT) {
   2117         fprintf(stderr, "input too large\n");
   2118         return 1;
   2119     }
   2120     input_buf[nread] = '\0';
   2121 
   2122     if (!lex_source(input_buf) || !process_tokens()) {
   2123         fprintf(stderr, "%s:%d: m1macro: %s\n",
   2124                 input_path != NULL ? input_path : "?",
   2125                 error_line,
   2126                 error_msg != NULL ? error_msg : "failed");
   2127         return 1;
   2128     }
   2129 
   2130     out = fopen(argv[2], "wb");
   2131     if (out == NULL) {
   2132         perror(argv[2]);
   2133         return 1;
   2134     }
   2135     if (fwrite(output_buf, 1, (size_t)output_used, out) != (size_t)output_used) {
   2136         perror(argv[2]);
   2137         fclose(out);
   2138         return 1;
   2139     }
   2140     fclose(out);
   2141     fprintf(stderr, "text_used=%d output_used=%d\n", text_used, output_used);
   2142     return 0;
   2143 }