mirror of
https://github.com/fluffos/fluffos
synced 2026-08-12 18:26:06 -04:00
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>
143 lines
4 KiB
Text
143 lines
4 KiB
Text
// LPC-level pins for the preprocessor (the unit tests in
|
|
// src/tests/test_compiler.cc cover these paths in isolation; this file
|
|
// proves them end-to-end through a real object compile).
|
|
// Reference: docs/lpc/preprocessor/.
|
|
|
|
#define STR(x) #x
|
|
#define GLUE(a, b) a##b
|
|
#define TWICE(x) ((x) + (x))
|
|
#define PAIR(a, b) ({ a, b })
|
|
#define SELF SELF
|
|
#define ML(a) ((a) \
|
|
* 3)
|
|
|
|
// Redefining with a different body is a WARNING (non-fatal); the new
|
|
// definition takes effect.
|
|
#define REDEF 1
|
|
#define REDEF 2
|
|
|
|
#warn preprocessor.lpc exercising #warn -- expected, not a failure
|
|
|
|
#if defined(FLUFFOS) && defined(__VERSION__)
|
|
#define COND_OK 1
|
|
#else
|
|
#define COND_OK 0
|
|
#endif
|
|
|
|
#if !defined(SURELY_NOT_DEFINED_ANYWHERE)
|
|
#define NOTDEF_OK 1
|
|
#else
|
|
#define NOTDEF_OK 0
|
|
#endif
|
|
|
|
// Token-based #if arithmetic keeps C precedence across macro bodies.
|
|
#define ONE_PLUS_ONE 1+1
|
|
#if ONE_PLUS_ONE * 2 == 3
|
|
#define PRECEDENCE_OK 1
|
|
#else
|
|
#define PRECEDENCE_OK 0
|
|
#endif
|
|
|
|
// #1236: a block comment opened on a directive line may close on a
|
|
// LATER line -- the comment reads as whitespace, it neither ends the
|
|
// directive nor leaves its continuation lines to be compiled as code.
|
|
#define WARNING_LEVEL 1 /* Change this to higher values to
|
|
show more warnings. */
|
|
|
|
// Text after the close on the final line still belongs to the directive.
|
|
#define CMT_TAIL 10 /* spans
|
|
one line */ + 5
|
|
|
|
// On a live #if...
|
|
#if 1 /* comment on a live #if,
|
|
closed here */
|
|
#define CMT_IF_OK 1
|
|
#else
|
|
#define CMT_IF_OK 0
|
|
#endif
|
|
|
|
// ...and on a dead one: the branch below the comment is still skipped.
|
|
#if 0 /* opened on the #if line,
|
|
closed here */
|
|
this is not code and must never be compiled
|
|
#endif
|
|
|
|
// A string literal on the directive line must not open a comment.
|
|
#define CMT_STR "a/*b"
|
|
|
|
// #1240: comments are whitespace in a directive's payload -- a '//'
|
|
// tail must NOT be captured into the body (it would comment out the
|
|
// rest of whatever spliced line the macro expands into), and trailing
|
|
// comments on #ifdef/#undef must not corrupt the name.
|
|
#define CREDITS_KEY "credits" // Current number of credits
|
|
#define MIN2(x, y) ((x) < (y) ? (x) : (y))
|
|
|
|
// A body-internal block comment is ONE space: the two '-' below must
|
|
// not paste into '--'.
|
|
#define DOUBLE_NEG 1 -/*minus*/-1
|
|
|
|
#ifdef CREDITS_KEY // trailing comment here too
|
|
#define IFDEF_CMT_OK 1
|
|
#else
|
|
#define IFDEF_CMT_OK 0
|
|
#endif
|
|
|
|
#define UNDEF_ME 5
|
|
#undef UNDEF_ME // gone
|
|
|
|
void do_tests() {
|
|
// ## token paste builds an identifier.
|
|
int GLUE(fo, o) = 42;
|
|
ASSERT_EQ(42, foo);
|
|
|
|
// # stringizes the raw argument spelling.
|
|
ASSERT_EQ("hello", STR(hello));
|
|
ASSERT_EQ("1+2", STR(1+2));
|
|
|
|
// Function-like expansion; parenthesized body.
|
|
ASSERT_EQ(8, TWICE(4));
|
|
|
|
// Commas nested in parentheses do not split arguments.
|
|
ASSERT_EQ(({ ({ 1, 2 }), 3 }), PAIR(({ 1, 2 }), 3));
|
|
|
|
// Multi-line (backslash-continued) macro.
|
|
ASSERT_EQ(21, ML(7));
|
|
|
|
// Self-referencing macro stops expanding at the self-reference, so
|
|
// SELF is usable as an ordinary identifier.
|
|
int SELF = 7;
|
|
ASSERT_EQ(7, SELF);
|
|
|
|
// Redefinition took effect (and did not fail the compile).
|
|
ASSERT_EQ(2, REDEF);
|
|
|
|
// Conditionals evaluated as expected.
|
|
ASSERT_EQ(1, COND_OK);
|
|
ASSERT_EQ(1, NOTDEF_OK);
|
|
ASSERT_EQ(1, PRECEDENCE_OK);
|
|
|
|
// #1236 -- directive-line comments spanning physical lines.
|
|
ASSERT_EQ(1, WARNING_LEVEL);
|
|
ASSERT_EQ(15, CMT_TAIL);
|
|
ASSERT_EQ(1, CMT_IF_OK);
|
|
ASSERT_EQ("a/*b", CMT_STR);
|
|
|
|
// #1240 -- trailing comments on directives are whitespace.
|
|
mapping economies = ([ "e" : ([ "credits" : 100 ]) ]);
|
|
ASSERT_EQ(42, MIN2(42, economies["e"][CREDITS_KEY]));
|
|
ASSERT_EQ(2, DOUBLE_NEG);
|
|
ASSERT_EQ(1, IFDEF_CMT_OK);
|
|
int UNDEF_ME = 9;
|
|
ASSERT_EQ(9, UNDEF_ME);
|
|
|
|
// Builtin macros: __FILE__ / __DIR__ / __LINE__.
|
|
ASSERT_EQ("/single/tests/compiler/preprocessor.lpc", __FILE__);
|
|
ASSERT_EQ("/single/tests/compiler/", __DIR__);
|
|
ASSERT(__LINE__ > 60);
|
|
|
|
// #undef makes the name ordinary again.
|
|
#define GONE 5
|
|
#undef GONE
|
|
int GONE = 6;
|
|
ASSERT_EQ(6, GONE);
|
|
}
|