ID:1758601
 
(See the best response by Tom.)
Since afaik BYOND doesn't provide this, it's probably necessary to define one manually. For example:

/proc/doubleIncrement(X)
x++ EOL X++
return

//This would be expanded to:

/proc/doubleIncrement(X)
X++
X++
return

This is obviously a trivial example, the actual use cases here mainly involve implementing metaprogramming functionality and building workarounds for compiler limitations (eg the #include statement's "convenient addition", which gives a textbook example of abstraction inversion due to framework design flaw by breaking every single one of its code reuse applications).

Already tried newline in multi-line comment (this works in GCC/G++): the compiler strips comments before expansion and has no options to change this behavior. Also tried all of the ASCII control sequences: the compiler strips them all except for SUB (which it interprets as EOF). Also tried inserting the numerical value in ASCII in various formats: no dice there either. Tried brackets, the pre-processor doesn't recognize them. Tried docstrings, they're treated like the multi-line comments. Finally, also tried #including a file with one or more EOLs in it: the BYOND pre-processor does not provide any way to store the result in a var or define.

Any ideas?



Best response
Can yo explain what you are actually trying to do without using so much abstract terminology?

If you just want to insert two statements on the same line, use a semicolon.

/proc/doubleIncrement(X)
X++;X++
return X // since X is passed by value you'll want this

I think the example sort of doesn't explain his predicament pretty well. He was in BYONDIRC a little while ago and he wanted to use multiple #defines on one line. Currently it seems to only be possible with multi-line comments in a hacky way, from what he told me.
Yeah, sorry about the buzzword crap. Replaces swearing in polite contexts. And there's lots of swearing to do when it comes to pre-processor hacks :)

Semicolons work just fine for regular DM code, but not for the pre-processor, since it only recognizes EOLs as statement separators. Problem is that it also uses them to terminate directives, and on top of that most of the directives handle source directly: this puts the pre-processor in the unfortunate position of not being able to read or write its own code, produce multi-line output, etc.

However, if we escaped the newline inside a define, then it could handle it safely. For example:

//So let's say we have this multi-define operation:

#define pp_define #define
//Aliases for the defaults are necessary to prevent the pp from
//reading the "#" before a directive as the "stringify" operator

#define DEF_2(A,AVAL,B,BVAL) \
pp_define A AVAL \
pp_define B BVAL
//When we write this:
DEF_2(TESTA,10,TESTB,20)

//It expands to this (in phase 1/3?):
pp_define TESTA 10 pp_define TESTB 20

//Which expands to this (in phase 2/3?):
#define TESTA 10 #define TESTB 20

//Which isn't what we want in this case.

//To the pre-processor, that means this (with parens):
(#define((TESTA)(10 #define TESTB 20)))

//In pseudo function calls:

preprocess(
#define(
(TESTA),
(10 #define TESTB 20)
)
)

//Which is not what we want. We want this:
(#define((TESTA)(10)))(#define((TESTB)(20)))

//In pseudo function calls:

preprocess(
#define(
(TESTA),
(10)
),
#define(
(TESTB),
(20)
)
)

So basically that needs a semicolon equivalent to get the pre-processor to interpret it in the intended way. But the PP only understands EOLs as statement separators/terminators. So we need a way to insert one without breaking the statement prematurely (same as the problem with handling a string with \0s in it using functions designed for null-terminated lists) this should work if it's done in the same pass that converts "pp_define" to "#define".

That's especially true for the BYOND PP since as far as i can tell most directives don't ever evaluate their arguments (!). This makes it an even more miserable failure of a C PP, but also makes it quite a bit more powerful: it makes composition trivial at any level of depth (!!). So we can do things like this:

//So let's say we have "EOL" which expands to the appropriate
//EOL sequence ("[EOL]" = "\n"). First we wrap the directives.

//E=expression,T=statement(s),I=identifier,A=any,N=number,
//S=string,V=void(should never be evaluated)
//XYZ=typeX,Y,orZ

#define pp_define #define
#define pp_if #if
etc

#define DEFINE(I_VAR,A_VALUE) pp_define I_VAR A_VALUE EOL
#define DEF(I_VAR) pp_define I_VAR EOL
#define IF(EINS_NUM) pp_if (EINS_NUM) EOL
etc
//Now for the actual non-trivial applications. NOTE that this
//is not completely serious, mainly showing how important the
//EOL is in the pre-processor language. There's no real limit
//on the number of valid use cases for a statement separator.
//This allows suspending preprocessing at any arbitrary point
//and seeing the code as blocks with names that can be grep'd
//to find their definition in the source. It exploits the way
//the BYOND #define directive does not evaluate its arguments
//(its C counterpart does). Thus they're preserved regardless
//of how many times they're nested, wrapped, recursed, etc...
//Also allows getting the value of PSEUDOFUNCTION to see what
//macro broke the preprocessor and failed to terminate, or to
//implement a pseudo-callstack for the truly insane among us.
//That gets rid of of the complaints about C macro abuse :^).

#define BEGIN(S_NAME) EOL DEFINE(PSEUDOFUNCTION,S_NAME ) EOL
#define END(S_NAME) EOL UNDEF(PSEUDOFUNCTION) EOL

#define LAMBDA(T_CODE) EOL T_CODE EOL

//If E_TEST is true, do S_DO
#define COND(E_TEST,S_DO)\
BEGIN("COND")\
IF(E_TEST)\
S_DO\
ENDIF\
END("COND")


//If EINS_EXP is false, throw an error. Very useful!
#define CASSERT(EXP)\
BEGIN("CASSERT")\
COND(\
(!(EXP)),\
LAMBDA(\
ERROR(Compile-time assert failed <here>!)\
/* Printing the expression to stdout needs
more gymnastics (#warn#error are comically
awful) so imagine some wacky hacks here */

)\
)\
END("CASSERT")

//Define I_VAR as NI_VALUE and "declare" it as a number.
#define DEF_AS_NUM(I_VAR,NI_VALUE)\
BEGIN("DEF_AS_NUM")\
DEFINE(I_VAR,((NI_VALUE)*(1)))
/* unwrap and test it */\
DEF(I_VAR ## _ISNUM)
/* This is static typing right? */\
END("DEF_AS_NUM")


//Is I_CHECK "declared" as a number?
#define ISDECNUM(I_CHECK) (defined(I_CHECK ## _ISNUM))

//Looping
#define REPEAT_2(T_CODE) T_CODE EOL T_CODE
#define REPEAT_3(T_CODE) T_CODE EOL T_CODE EOL
etc

#define WHILE( //Oh baby, here we go...
...

And so on. Right now I'm working on putting together a sort of stdlib for DM, and this is a huge brick wall as far as the preprocessor goes. No statement separator means no code reuse: every single operation has to be typed out manually. Can't even provide the most basic compile-time operations without this, so any suggestions help, no matter how hacky.