fluffos/docs/lpc/preprocessor/define.md
Yucong Sun d38dc2c833
lexer: strip comments from directive payloads before parsing (#1240) (#1241)
A '//' comment after a #define body was captured INTO the stored macro
body. Expansion buffers carry no newline to end it, so when the macro
expanded inside a spliced line (a function-like macro's substituted
body, as in the report's MIN(credits, m[e][CREDITS])), the '//' ate the
rest of the splice and the parse failed with a baffling 'unexpected ;'
attributed to the outer macro. The same missed strip made
'#undef X // why' erase nothing and '#ifdef X // why' look up the
wrong name and silently take the false branch.

Comments are whitespace (C translation phase 3). dispatch_directive now
strips them from the payload before parsing for #define (name, params,
body -- body also right-trimmed so a stripped comment can't turn '1'
vs '1 ' into a spurious redefinition warning), #undef, #ifdef, #ifndef,
and #pragma (word-list payload); #if/#elif already stripped.
strip_directive_comments() now folds a block comment to ONE space
instead of nothing, so '1 -/*c*/-1' keeps its token boundaries instead
of pasting '--'. #error/#warn/#echo payloads stay raw.

Covered by five new Preprocessor unit tests (the report's repro shape,
paste-prevention via #if branch selection, param-list comment,
testsuite/single/tests/compiler/preprocessor.lpc; documented in
docs/lpc/preprocessor/define.md.

Fixes #1240


Claude-Session: https://claude.ai/code/session_016FMBJLkpkpVpZdz6PWzeWe

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-11 12:15:19 -04:00

2.8 KiB

title
preprocessor / define

#define and #undef

#define creates a macro: a name that is replaced by a body of text wherever it appears later in the file.

Object-like macros

#define MAX_HP 100
#define GREETING "Welcome to " MUD_NAME

Every later occurrence of the name (as a whole identifier — not inside strings, comments, or longer identifiers) is replaced by the body.

Function-like macros

A parameter list attached directly to the name (no space before () makes the macro function-like:

#define SQUARE(x) ((x) * (x))
#define MSG(who, text) tell_object(who, text "\n")

Arguments are collected with full nesting awareness — commas inside parentheses, strings, or character literals do not split arguments. Parenthesize parameters in the body (as above) to avoid precedence surprises at the use site.

Inside a macro body, #param produces the argument's text as a string literal, and a ## b pastes two tokens together. ## may not appear at the very start or end of a body.

Expansion and rescan

A macro's body is rescanned after substitution, so macros may reference other macros freely. A macro that (directly or indirectly) references itself stops expanding at the self-reference instead of recursing forever, as in C.

Because #if expressions are evaluated over the same token stream, arithmetic keeps C precedence across macro boundaries:

#define X 1+1
#if X*2 == 3   // 1+1*2 -- true

Multi-line macros

End a line with \ to continue the definition:

#define LONG_MACRO(x) do { \
    write(x);              \
} while (0)

A block comment opened on a directive line may also span lines — it reads as whitespace and does not end the directive, so both of these define WARNING_LEVEL as 1:

#define WARNING_LEVEL 1 /* change to a higher value to
                           show more warnings */

#define WARNING_LEVEL 1 // single-line form

Text after the comment's close on its final line still belongs to the directive, matching C.

Comments on a directive line are whitespace, never part of the macro: a trailing // or /* */ after the body (or after the name on #undef/#ifdef) is stripped before the directive is parsed, and a block comment inside the body separates tokens like a space would:

#define CREDITS "credits" // key into the economy mapping

Redefinition

Redefining a macro with a different body is allowed: the compiler emits a warning (with a note pointing at the previous definition) and the new definition takes effect. Redefining with an identical body is silent. Redefining or #undef-ing a predefined macro (__FILE__, FLUFFOS, ...) is a compile error.

#undef

#undef MAX_HP

Removes a user macro definition; the name is ordinary text again. #undef of an unknown name is harmless.