/PAWN_PP

Pre-processor macros for performing compile-time maths and concatenations.

Primary LanguagePawn

PAWN PP

PAWN_PP is a library that defines pre-processor macros useful for performing compile-time maths and concatenations.

Functions

PP_CHAIN()

The PAWN pre-processor's ordering is very specific, and can make some operations very tricky. For example, this will not work:

PP_ADD(PP_ADD(5,6),7)

That will try to add 7 to the string PP_ADD(5,6) and best-case scenario will give the answer 117 (5+6 summed, followed by 7). Getting the pre-processor to do the inner sum before the outer sum can't be done in that way since all macros are evaluated strictly left-to-right, not inner-to- outer.

PP_CHAIN is a solution for this problem, for those macros explicitly designed to support it. It is a stack machine, that uses $ as result placeholders, and POP to get a previous answer and put it in to that placeholder. The example above would thus become:

PP_CHAIN( \
	PP_ADD(5,6) \ // Pushes 11 on to the stack.
	PP_POP()    \ // Pops 11 and replaces the next "$" with it.
	PP_ADD($,7) \ // Pushes 18 on to the stack.
)

At the end of the chain, the complete stack contents are dumped out. This:

PP_CHAIN( \
	PP_ADD(5,6) \
	PP_ADD(5,7) \
)

Would give:

(12)(11)

Since there are two items on the stack and they are both enclosed in brackets automatically. The brackets can be stripped with PP_UNWRAP:

PP_CHAIN( \
	PP_ADD(5,6)  \
	PP_ADD(5,7)  \
	PP_UNWRAP($) \
)

Would give:

12

PP_UNWRAP()

PP_UNWRAP can ONLY be used inside PP_CHAIN, unlike some other functions, and thus it will automatically pop the top item off the stack, destroy the remainder of the stack, and put the popped item back on without its brackets. PP_PRINT is similar, but keeps the brackets. In addition, both can take additional parameters and will format accordingly:

PP_CHAIN( \
	PP_ADD(5,6)  \
	PP_UNWRAP(answer = $;) \
)

Would give:

answer = 11;

(To output an actual $, use PP_DOLLAR<>).

PP_ADD() [MINUS / SUB / LOG2 / POW2]

PP_ADD, PP_MINUS, PP_SUB, PP_LOG2, and PP_POW2 are all stand-alone maths operations and will work without being in PP_CHAIN. Thus, they will not automatically pop symbols in lieu of $ (this is a limitation I tried to fix, but couldn't), so sadly they need an explicit PP_POP_1, PP_POP_2, or PP_POP_3 (for which there is currently no true need):

PP_CHAIN( \
	PP_ADD(5,6)   \
	PP_ADD(40,80) \
	PP_POP_2()    \
	PP_SUB($,$)   \
)

Would give:

(-109)

Results are popped from the bottom of the stack (last in, first out), and pushed in reverse parameter order (as in PAWN), resulting in the code above being equivalent to (5 + 6) - (40 + 80).

It should also be noted that because the vast majority of the calculations done in this way are done destructively in-place, there are no line-length limitations to worry about (unless your equation itself is stupidly long).

PP_TOKENISE()

PP_TOKENISE is a synonym for PP_UNWRAP, meaning you can do:

#define MY_MACRO_1(%0) %0 from MY_MACRO one
#define MY_MACRO_2(%0) %0 from MY_MACRO two
#define MY_MACRO_3(%0) %0 from MY_MACRO three

PP_CHAIN( \
	PP_ADD(1,2)               \
	PP_TOKENISE(MY_MACRO_$)   \
)(hi)

Would give:

hi from MY_MACRO three

In that case, the entire chain would evaluate to exactly MY_MACRO_3, which would then be parse and be seen to be preceeding another set of brackets, thus matching and being expanded. Again, this is different to doing 1+2, which would result in MY_MACRO_1+2(hi), not MY_MACRO_3(hi), and thus fail to match even the first MY_MACRO_1.

As stated elsewhere, this technique only works on macros explicitly designed for being used in PP_CHAIN, mainly those that use a _THEN variant. To write a standard value macro to work here would look like:

#define MAX_PLAYERS  MAX_PLAYERS_() // Standard entry point - note `()`s.
#define MAX_PLAYERS_ MACRO(500)     // Define the true value.

That would (somewhat clunkily) enable:

PP_CHAIN( \
	PP_MACRO(MAX_PLAYERS) \
	PP_POP()              \
	PP_ADD(1,$)           \
)

Someone using MAX_PLAYERS would get:

MAX_PLAYERS
MAX_PLAYERS_()
MACRO(500)()
_PP_MACRO(500)
500

Someone using PP_MACRO(MAX_PLAYERS) inside PP_CHAIN() would get:

PP_MACRO(MAX_PLAYERS)
MAX_PLAYERS_(_THEN)
MACRO(500)(_THEN)
_PP_MACRO_THEN(500)
_PP_MACRO(500)

The trick being that PP_CHAIN replaces all the functions inside it with _THEN suffixed versions. This is the only way I could find to let macros use information calculated by other macros before they were evaluated.

I could not get this to work for constants as well as macros, so this will not work:

PP_CHAIN( \
	PP_MACRO(100)         \
	PP_POP()              \
	PP_ADD(1,$)           \
)

Instead use PP_PUSH (or PP_CONST in exactly the same way):

PP_CHAIN( \
	PP_PUSH(100)          \
	PP_POP()              \
	PP_ADD(1,$)           \
)

This is slightly awkward, as it does mean that a macro like this will not work in all generic cases:

#define Dialog[%1](%2) \
	PP_CHAIN( \
		PP_MACRO(%1)       \
		PP_TOKENISE(@dlg$) \
	)(%2)

This will work:

#define DIALOG_LOGIN_ID DIALOG_LOGIN_ID_()
#define DIALOG_LOGIN_ID_ MACRO(101)

Dialog[DIALOG_LOGIN_ID](playerid, dialogid, response)
{
}

This will not:

Dialog[101](playerid, dialogid, response)
{
}

Sadly, you would need a separate version for the two cases, like DialogNumber and DialogMacro.

PP_ADD()

Adds two numbers (each up to +/-256) together in the pre-processor. This is different to using the compiler, which would just output 7+8, or some sort of sum generation, which would give 7+1+1+1+1+1+1+1+1. The output is a pure value of 15, making it usable in other macros. Strictly speaking, the value ranges are not exactly +/-256 - anything between those values (inclusive) are guaranteed to work; however, some other values in certain positions may work if the result is still within +/-512.

PP_SUB()

Subtracts two numbers (each up to +/-256) together in the pre-processor. This is different to using the compiler, which would just output 7-8, or some sort of sum generation, which would give 7-1-1-1-1-1-1-1-1. The output is a pure value of -1, making it usable in other macros. Strictly speaking, the value ranges are not exactly +/-256 - anything between those values (inclusive) are guaranteed to work; however, some other values in certain positions may work if the result is still within +/-512.

PP_MINUS()

Return a number with a minus sign attached. This is used internally when a valid symbol representing a negative number is required, instead of a true negative number that would not be a valid macro name. Exposed here because why not?

Deferred operators

When you want, say, a bracket in the output but don't want it to be matched by a macro use PP_LEFT_BRACKET<> instead. That way anything preceeding that will not see or match against a bracket, but once that macro is reached it will be replaced with just (.

PP_DROP() [ID / ENCLOSE / TAG]

  • PP_DROP just removes itself and its parameter.
  • PP_ID returns its parameter.
  • PP_ENCLOSE returns its parameter wrapped in brackets.
  • PP_TAG returns its parameter with a default tag override.

PP_AMP()

true when non-zero (truthy), false when zero (or falsy).

PP_NOT()

false when non-zero (truthy), true when zero (or falsy).

PP_LOG2()

Finds the log-2 base of a power of 2 number. Only goes up to 2**73 due to symbol length restrictions, but that is still vastly more than can be fit in to 32-bit or 64-bit cells.

PP_POW2()

Finds 2 raised to the given power. Resolves to just a number. Can't do it any other way, since there's no point supporting digit separators (because the support range isn't large enough), and the result for negatives will be a floating point number.

PP_PREVENT_EXPANSION(A_MACRO(macro, parameters))

Stops the specified macro expanding, so this code below will NOT give 4:

#define MY_MACRO() 4
PP_PREVENT_EXPANSION(MY_MACRO())

Note that this ONLY works on function-like macros, this will always expand:

PP_PREVENT_EXPANSION(MAX_PLAYERS)

PP_WARNING("Your warning message")

Gives a compile-time warning. It will be an unused symbol warning which looks vaguely like the specified warning. Something like:

Symbol is never used: WARNING_Your_warning_message

That should be enough for the user to go on. Otherwise, they can look at the given file and line number (which will be correct) and will see the more useful code from the example (PP_ERROR("Your error message")).

PP_ERROR("Your error message")

Gives a compile-time error. It will be an undefined symbol error which looks vaguely like the specified error. Something like:

Undefined symbol: ERROR_Your_error_message

That should be enough for the user to go on. Otherwise, they can look at the given file and line number (which will be correct) and will see the more useful code from the example (PP_ERROR("Your error message")).

PP_STRIP_SPACES(something goes here)

Gives:

somethinggoeshere

You probably want to combine this with PP_TAG.

PP_REPLACE_SPACES(_)(something goes here)

Gives:

something_goes_here

The first parameter is the separator to replace spaces with. You probably want to combine this with PP_TAG.

PP_PACKED()

1 when "hello" is a packed string, 0 when it isn't. This could be because the compiler is version 4.x (in which case ''hello'' is an unpacked string, or because #pragma pack 1 has been used, in which case !"hello" is a packed string (opposite of default).

PP_STRINGISE() & PP_STRINGIZE_PACKED()

Gives a consistent stringize operator across multiple compiler versions.

This is not perfect. The SA-MP PAWN branch reports its version as 3.2.3664, which is 0x0302 in the __Pawn macro, but the real 3.2.3664 doesn't have native string concatenation abilities so needs the same hack as older versions; however, the two can't be distinguished (or maybe they can but I've not figured out a way yet). Thus, since the most likely use of 3.2 is for SA-MP I'm assuming that.

In PAWN version 4, the strings changed "string" became default packed, and unpacked became ''string'', as opposed to !"string" and "string" before (unless #pragma pack 1 is used). I also think this is when the official compiler introduced ... and # itself, since I can't find earlier mentions, but my set of documentation is incomplete. For this reason, I have no implementation for the 3.3.xxxx branch.

Additionally, in the SA-MP version, this is legal:

new str[] = "Hello " #world;

But in the 4.x branch, only macro parameters can be stringised, and must be explicitly concatenated with ...:

#define CAT(%0,%1) %0 ... #%1
new str[] = CAT("Hello ", world);

This code unites the two by making everything macro parameters and always having the ... operator, even when it isn't required:

new str[] = PP_STRINGISE("Hello " ... #world);

I'm not too sure how the explicit # there will fare, since that may result in ## in some compilers - fine for SA-MP but I don't know about 4.x.

Installation

Simply install to your project:

sampctl package install Y-Less/PAWN_PP

Include in your code and begin using the library:

#include <pp>