#!/bin/sh set -eu base="${TMPDIR-/tmp}/mcpu-cpp-va-opt-$$" mkdir -p "$base" trap 'rm -rf "$base"' EXIT HUP INT TERM cat > "$base/main.c" <<'EOT' #define EMPTY #define X 123 #define xy RESCANNED #define HAS(...) __VA_OPT__(yes) #define COMMA(format, ...) call(format __VA_OPT__(,) __VA_ARGS__) #define STR(...) #__VA_OPT__(__VA_ARGS__) #define STRFIX(a, ...) #__VA_OPT__(a __VA_ARGS__) #define STRPASTE(a, ...) #__VA_OPT__(a ## z) #define LEFT(...) pre ## __VA_OPT__(__VA_ARGS__) #define RIGHT(...) __VA_OPT__(__VA_ARGS__) ## post #define INNER(a, ...) __VA_OPT__(a ## y) #define BALANCED(...) __VA_OPT__((a, (b, c))) #define EMPTY_LEFT(...) x ## __VA_OPT__() #define EMPTY_RIGHT(...) __VA_OPT__() ## y #define TEXT(...) "__VA_OPT__(not syntax)" __VA_OPT__(ok) [HAS()] [HAS(EMPTY)] [HAS(token)] COMMA("zero") COMMA("two", X, 7) STR() STR(X) STRFIX(X, y) STRPASTE(X, y) LEFT() LEFT(X) RIGHT() RIGHT(X) INNER(x, token) BALANCED(token) EMPTY_LEFT(token) EMPTY_RIGHT(token) TEXT() TEXT(token) EOT "$MCPU_CPP" --no-config "$base/main.c" -o "$base/out" sed '/^#/d; /^[[:space:]]*$/d; s/[[:space:]]//g' "$base/out" > "$base/norm" cat > "$base/expected" <<'EOT' [] [] [yes] call("zero") call("two",123,7) "" "123" "123y" "Xz" pre pre123 post 123post RESCANNED (a,(b,c)) x y "__VA_OPT__(notsyntax)" "__VA_OPT__(notsyntax)"ok EOT diff -u "$base/expected" "$base/norm" # __VA_OPT__ remains visible in macro dumps as part of the replacement list. "$MCPU_CPP" --no-config -dM "$base/main.c" > "$base/dump" grep -Fx '#define HAS(...) __VA_OPT__(yes)' "$base/dump" >/dev/null grep -Fx '#define COMMA(format,...) call(format __VA_OPT__(,) __VA_ARGS__)' "$base/dump" >/dev/null grep -Fx '#define STR(...) #__VA_OPT__(__VA_ARGS__)' "$base/dump" >/dev/null # __VA_OPT__ is structural syntax, not a general identifier-like extension. cat > "$base/nonvariadic.c" <<'EOT' #define BAD(x) __VA_OPT__(x) EOT if "$MCPU_CPP" --no-config "$base/nonvariadic.c" -o "$base/nonvariadic.out" 2>"$base/nonvariadic.err"; then echo '__VA_OPT__ unexpectedly accepted in a non-variadic macro' >&2 exit 1 fi grep "'__VA_OPT__' may appear only in a variadic macro replacement list" "$base/nonvariadic.err" >/dev/null cat > "$base/object.c" <<'EOT' #define BAD __VA_OPT__(x) EOT if "$MCPU_CPP" --no-config "$base/object.c" -o "$base/object.out" 2>"$base/object.err"; then echo '__VA_OPT__ unexpectedly accepted in an object-like macro' >&2 exit 1 fi grep "'__VA_OPT__' may appear only in a variadic macro replacement list" "$base/object.err" >/dev/null cat > "$base/nested.c" <<'EOT' #define BAD(...) __VA_OPT__(a __VA_OPT__(b)) EOT if "$MCPU_CPP" --no-config "$base/nested.c" -o "$base/nested.out" 2>"$base/nested.err"; then echo 'nested __VA_OPT__ unexpectedly accepted' >&2 exit 1 fi grep "'__VA_OPT__' may not appear inside another '__VA_OPT__'" "$base/nested.err" >/dev/null cat > "$base/no-open.c" <<'EOT' #define BAD(...) __VA_OPT__ x EOT if "$MCPU_CPP" --no-config "$base/no-open.c" -o "$base/no-open.out" 2>"$base/no-open.err"; then echo '__VA_OPT__ without opening parenthesis unexpectedly accepted' >&2 exit 1 fi grep "'__VA_OPT__' must be followed by '('" "$base/no-open.err" >/dev/null cat > "$base/unterminated.c" <<'EOT' #define BAD(...) __VA_OPT__((x) EOT if "$MCPU_CPP" --no-config "$base/unterminated.c" -o "$base/unterminated.out" 2>"$base/unterminated.err"; then echo 'unterminated __VA_OPT__ unexpectedly accepted' >&2 exit 1 fi grep "unterminated '__VA_OPT__'" "$base/unterminated.err" >/dev/null cat > "$base/paste-first.c" <<'EOT' #define BAD(...) __VA_OPT__(## x) EOT if "$MCPU_CPP" --no-config "$base/paste-first.c" -o "$base/paste-first.out" 2>"$base/paste-first.err"; then echo 'leading ## inside __VA_OPT__ unexpectedly accepted' >&2 exit 1 fi grep "'##' cannot appear at the beginning of '__VA_OPT__'" "$base/paste-first.err" >/dev/null cat > "$base/paste-last.c" <<'EOT' #define BAD(...) __VA_OPT__(x ##) EOT if "$MCPU_CPP" --no-config "$base/paste-last.c" -o "$base/paste-last.out" 2>"$base/paste-last.err"; then echo 'trailing ## inside __VA_OPT__ unexpectedly accepted' >&2 exit 1 fi grep "'##' cannot appear at the end of '__VA_OPT__'" "$base/paste-last.err" >/dev/null # The historical GNU comma-swallow extension remains deliberately absent. # __VA_OPT__ is the supported way to make a separator conditional. cat > "$base/no-gnu-comma.c" <<'EOT' #define OLD(format, ...) call(format, ## __VA_ARGS__) OLD("x") EOT "$MCPU_CPP" --no-config "$base/no-gnu-comma.c" -o "$base/no-gnu-comma.out" 2>"$base/no-gnu-comma.err" sed '/^#/d; /^[[:space:]]*$/d; s/[[:space:]]//g' "$base/no-gnu-comma.out" > "$base/no-gnu-comma.norm" grep -Fx 'call("x",)' "$base/no-gnu-comma.norm" >/dev/null