samp.directory
← Scripts y librerías

Y-Less/code-parse.inc

Pre-processor macros for analysing PAWN functions.

Librería★ 26actualizado jul 2023OtherPawnsampctl
Abrir en GitHub

Instalar con sampctl

sampctl package install Y-Less/code-parse.inc

Ejecútalo en la carpeta de tu proyecto. sampctl descarga el paquete y sus dependencias.

readme.md

code-parse.inc

Contents

Introduction

This library is designed for generating code at compile-time based on existing code. It can be thought of as a macro equivalent to amx-assembly - used for advanced manipulation of the compiler for creating libraries and keywords, more than end-user code. As a simple example we will create a macro to count the number of array parameters in a function, walking through each step in turn. After that we will demonstrate a more in-depth example to create a y_timers clone.

Example 1 - Count Array Parameters

Full Code

// The entry point.  Defines our parser.
#define ARRAY_COUNT:%1(%2) FUNC_PARSER(ARRAY_COUNT,ARR:NUM:)(%1(%2)) stock const countOf_%1 = 0; %1(%2)

// Default.  Called for parameters that didn't match any other type.
#define ARRAY_COUNT_NUM(%9)%8$ %8$

// Arrays.  Called when an array is matched.
#define ARRAY_COUNT_ARR(%9)%8$%0=%1; %8$%0=%1 + 1;

// Ending.  Called when the parsing is complete.
#define ARRAY_COUNT_END(%9)%8$ %8$

// Ending.  Called when there are no function parameters.
#define ARRAY_COUNT_NUL(%9)%8$ %8$

// Use the macro on a function.
ARRAY_COUNT:my_test_function(a, b[], c, const d[], &e = 6, string:f[] = "hi")
{
}

main()
{
	printf("There are %d array parameters to \"my_test_function\"", countOf_my_test_function);
}

Console Output

There are 3 array parameters to "my_test_function"

Explanation

To understand the code, we need a clear idea of the code inputs and outputs (and a good understanding of the pre-processor helps as well).

This is the code input (input):

my_test_function(a, b[], c, const d[], &e = 6, string:f[] = "hi")

This is the code output (output):

stock const countOf_my_test_function = 0 + 1 + 1 + 1;
my_test_function(a, b[], c, const d[], &e = 6, string:f[] = "hi")

The bit of the output we really care about is just the first part:

stock const countOf_my_test_function = 0 + 1 + 1 + 1;

This starts out as:

stock const countOf_my_test_function = 0;

And has + 1 appended every time an array parameter is encountered, with other parameters getting ignored (0 + 1 + 1 + 1 can be done by the compiler, so will only generate 3 in the AMX, not a string of sums).

The library handles the hard part of understanding all the function parameter types (the input), but you still need to understand and split up your own code (the output).

Line By Line

#define ARRAY_COUNT:%1(%2) FUNC_PARSER(ARRAY_COUNT,ARR:NUM:)(%1(%2)) stock const countOf_%1 = 0; %1(%2)
#define ARRAY_COUNT:%1(%2)

This is just the macro name and match as normal. Will match anything that looks like a function with a tag of ARRAY_COUNT:.

FUNC_PARSER(ARRAY_COUNT,ARR:NUM:)(%1(%2))

Create a new function parser called ARRAY_COUNT, define which parameter types it will analyse, and pass it the input. The parameter type parsers are all followed by colons and not separated by commas (or any spaces - that is VERY important).

DO NOT PUT ANY SPACES IN THIS PART!

ARR: will detect any array. NUM: is essentially other - it will detect anything not detected by any other parser. There is also EXT: (extended) which will detect ..., REF: (reference) which will detect &a, and STR: (string) which will detect string:a[]. This last point is important - because strings look exactly like arrays (a[] and a[] - because they ARE arrays) but are frequently handled differently (for example using s instead of a in CallRemoteFunction) this library uses the YSI convention of prefixing all strings with the string: tag. This does not affect any other code in ANY way

  • you won't even get any tag mismatch warnings because the tag is removed again later on in the compilation process. All it means is that the two types can be differentiated.

Exclude Strings

To modify the example so that strings are not included in the array count would look like:

#define ARRAY_COUNT:%1(%2) FUNC_PARSER(ARRAY_COUNT,ARR:NUM:STR:)(%1(%2)) stock const countOf_%1 = 0; %1(%2)
#define ARRAY_COUNT_STR(%9)%8$ %8$

ARRAY_COUNT is used as a prefix to all other macros, so is prepended whenever a macro callback is called (with a _ separator):

#define ARRAY_COUNT_NUM(%9)%8$ %8$

ARRAY_COUNT is the custom prefix specified in the FUNC_PARSER call. _NUM is the suffix for any normal number variables (or anything not otherwise matched). (%9) is the parameters for this macro - things like name of the variable, array size, default values, etc. These are not used here, so they can just be ignored. %8$ is required in both the input and the output, but is exclusively part of the parser and is what makes the iteration work. The contents of %8 are just the current parser state, $ is the delimiter after which comes your stuff (if desired, here it isn't).

#define ARRAY_COUNT_ARR(%9)%8$%0=%1; %8$%0=%1 + 1;

This is a more complex callback. Everything up to the $ is the same - parameters and internal state, with the %8$ also appearing in the output because it is always required (not putting that there will just break the compilation).

In the #define ARRAY_COUNT: line, the first thing that came after the call to FUNC_PARSER was stock const countOf_%1 = 0;. This is the start of user- generated output, and is what now appears after the $. So because we want to change this to stock const countOf_%1 = 0 + 1;, we match against that code with: %0=%1; (technically even the %0= isn't required as %1; will just match everything up to the first semi-colon). Copying that to the output gives %0=%1 + 1;. We don't need the function definition at the very end, so there's no point matching against it.

#define ARRAY_COUNT_END(%9)%8$ %8$

Called when the parsing is complete.

#define ARRAY_COUNT_NUL(%9)%8$ %8$

Called when there are no function parameters.

Example 2 - Count All Parameters

We can extend the example again to count all parameter types. For this, we will set up some naming rules first:

Substitution parameterUse
%0The current reference count (&a).
%1The current varargs count (...) - should only be 0 or 1 but the parser doesn't check that, it can parse quite incorrect code.
%2The current array count (a[]).
%3The current string count (string:a[]).
%4The current other count (a).
%8The parser state (black box).
%9Unused other parameters.
// Entry.  Parse all parameter types.
#define ALL_COUNT:%1(%2) FUNC_PARSER(ALL_COUNT,ARR:NUM:EXT:STR:REF:)(%1(%2)) \
	stock const \
		refCount_%1 = 0, \
		extCount_%1 = 0, \
		arrCount_%1 = 0, \
		strCount_%1 = 0, \
		numCount_%1 = 0; \
	%1(%2)

// Counts.  Add one to the relevant number.
#define ALL_COUNT_REF(%9)%8$%0,%1,%2,%3,%4; %8$%0 + 1,%1,%2,%3,%4;
#define ALL_COUNT_EXT(%9)%8$%0,%1,%2,%3,%4; %8$%0,%1 + 1,%2,%3,%4;
#define ALL_COUNT_ARR(%9)%8$%0,%1,%2,%3,%4; %8$%0,%1,%2 + 1,%3,%4;
#define ALL_COUNT_STR(%9)%8$%0,%1,%2,%3,%4; %8$%0,%1,%2,%3 + 1,%4;
#define ALL_COUNT_NUM(%9)%8$%0,%1,%2,%3,%4; %8$%0,%1,%2,%3,%4 + 1;

// Endings
#define ALL_COUNT_END(%9)%8$ %8$
#define ALL_COUNT_NUL(%9)%8$ %8$

The first macro is spread over multiple lines for clarity, but doesn't have to be.

Use:

ALL_COUNT:all_test_function(a, b[], c, const d[], &e = 6, string:f[] = "hi")
{
}

main()
{
	printf("There are %d reference parameters to all_test_function", refCount_all_test_function);
	printf("There are %d extended parameters to all_test_function",  extCount_all_test_function);
	printf("There are %d array parameters to all_test_function",     arrCount_all_test_function);
	printf("There are %d string parameters to all_test_function",    strCount_all_test_function);
	printf("There are %d other parameters to all_test_function",     numCount_all_test_function);
}

Example 3 - Count Array Parameters And Sum Their Size

Lets go back to the first example and modify it to show not just the number of array parameters, but also their combined size. First a side-note - function array parameters are often written as [], which we will count as a size of 0. However, writing 1 + + 10 is not valid syntax - we actually need a number there. Writing (size +0) when there is a size will resolve to (size), when there isn't a size it will resolve to (+0), the unary plus and legal, giving (1+0) + (+0) + (1+0). This is the simplest method of dealing with potentially empty macros.

How do we get the size though?

Previously the addition macro was:

#define ARRAY_COUNT_ARR(%9)%8$%0=%1; %8$%0=%1 + 1;

The (%9) was a placeholder for parameters we didn't care about - it will consume them all and drop them. However, each of the _ARR, _STR, _NUM, _EXT, and _REF macros take several parameters:

Substitution parameterUse
%0const (if it exists).
%1The tag (if there is one - and only the first if there are many, includes the :).
%2The variable name (only always present value).
%3Length (strings only).
%4Default value (if it exists, not on arrays).
%3+Dimension sizes (arrays only).

These parameters:

const Float:pos[3]
string:name[] = "Bob"
&v
Float:v = 0

Would call as:

ARRAY_COUNT_ARR(const ,Float:,pos,3)
ARRAY_COUNT_STR(,,name,,"Bob")
ARRAY_COUNT_REF(,,v,)
ARRAY_COUNT_NUM(,Float:,v,0)

Note that the string: tag does NOT get passed - it is special and purely used for internal differentiation.

We can thus change the macro to use one parameter and just ignore the others:

#define ARRAY_COUNT_ARR(%0,%1,%2,%3)%8$%6;%7; %8$%6 + 1;%7 + (%3 +0);

If you didn't know, you can also use the same substitution variable multiple times in one macro, with the value getting overridden each time. I like to do this on values that are getting ignored, with %9 for it:

#define ARRAY_COUNT_ARR(%9,%9,%9,%4)%8$%0;%1; %8$%0 + 1;%1 + (%4 +0);

This makes the full thing:

// The entry point.  Defines our parser.
#define ARRAY_COUNT:%1(%2) FUNC_PARSER(ARRAY_COUNT,ARR:NUM:)(%1(%2)) stock const countOf_%1 = 0; stock const sizeOf_%1 = 0; %1(%2)
#define ARRAY_COUNT_NUM(%9)%8$ %8$
#define ARRAY_COUNT_ARR(%9,%9,%9,%3)%8$%0;%1; %8$%0 + 1;%1 + (%3 +0);
#define ARRAY_COUNT_END(%9)%8$ %8$
#define ARRAY_COUNT_NUL(%9)%8$ %8$

// Use the macro on a function.
ARRAY_COUNT:my_test_function(a, b[], c, const d[], &e = 6, string:f[] = "hi")
{
}

main()
{
	printf("There are %d array parameters with size %d to \"my_test_function\"", countOf_my_test_function, sizeOf_my_test_function);
}

Tags are always included with their :. This could be detected and ignored with:

#define PREFIX_ARR(%0,%1:,%2,%3)%8$ %8$

But it should be noted that while that will return tags without their colon, it will now fail to work for any variable without a tag at all.

Example 4 - Auto-Generate CallRemoteFunction Specifier Strings

Time for a more advanced example, using the parameter names more. The end result will be a new keyword remote which behaves like public, but also generates code to call CallRemoteFunction directly with the correct string specifier for out function. Doing:

remote my_func(a, b[], c)
{
}

Will generate:

my_func(a, b[], c) CallRemoteFunction("remote_my_func", "iai", a, b, c);
forward remote_my_func(a, b[], c);
public remote_my_func(a, b[], c)
{
}

Thus just doing: my_func(10, arr, sizeof (arr)); in code will call all scripts at once, not just this one.

A normal macro would not be able to determine the exact string iai to use in this situation, and just using the parameters directly in CallRemoteFunction would result in the invalid call a, b[], c.

First, we define the entry point and bulk of the output:

#define remote%0(%1) FUNC_PARSER(REMOTE,ARR:STR:NUM:)(%0(%1)) \
	%0(%1) CallRemoteFunction("remote_"#%0, ##); \
	forward remote_%0(%1); \
	public remote_%0(%1)

We also define a helper macro to turn remote_ my_func in to remote_my_func:

#define remote_%0\32; remote_%0

For each of the parameter types, we add a letter to the end of the specifier string, and add the parameter name to the end of the CallRemoteFunction parameter list:

#define REMOTE_ARR(%9,%9,%2,%9)%8$%0(%1)%3(%4#%5#%6#%7) %8$%0(%1)%3(%4#%5#%6a#%7,%2)
#define REMOTE_STR(%9,%9,%2,%9)%8$%0(%1)%3(%4#%5#%6#%7) %8$%0(%1)%3(%4#%5#%6s#%7,%2)
#define REMOTE_NUM(%9,%9,%2)%8$%0(%1)%3(%4#%5#%6#%7) %8$%0(%1)%3(%4#%5#%6i#%7,%2)

You could abstract this further, since the only tiny difference between those three macros is the letter inserted at the end between the two #s. The reason why %0(%1)%3(%4# was used instead of just %0# to skip over everything up to the first # is that the # is inside some brackets, and they are handled very specially by the pre-processor unless you explicitly match against the (.

Finally, we need the two end macros because they are required but don't do anything in this instance:

#define REMOTE_END(%9)%8$ %8$
#define REMOTE_NUL(%9)%8$ %8$

Three Facts

  1. The forward keyword is optional.

  2. A public function with only a semi-colon after it is a forward.

  3. An @ prefix declares a function public.

We can thus shrink this code to:

#define remote%0(%1) FUNC_PARSER(REMOTE,ARR:STR:NUM:)(%0(%1)) stock%0(%1)CallRemoteFunction("@r_"#%0,##);@r_%0(%1);@r_%0(%1)
#define @r_%0\32; @r_

As a side-effect of point interesting fact 2, writing this:

remote truly_remote(x, y, z);

Will declare the function but not define it, allowing a function in a different script to be easilly called from here.

Tags

You may have noticed that all normal variables are passed as i, never f. PAWN is typeless - the tags are only a hint to the compiler, and only matter at all for some operators, which can be overloaded based on tag. Using i instead of f makes no difference at all here. To be really strict you could use:

#define REMOTE_NUM(%9,%9,%2)%8$%0(%1)%3(%4#%5#%6#%7) %8$%0(%1)%3(%4#%5#%6i#%7,_:%2)

Detecting only the Float: tag and leaving all others is much more involved, and just not worth it.

Example 5 - Output Input Exactly

For the next example, we are going to write a set of macros that perfectly output their input... This sounds pointless - and in code it would be, but is important here for demonstrating every parameter of every callback macro, including some I slightly lied about previously (or rather simplified). The first lie was about default parameters.

Lie 1

Earlier I stated that:

const Float:pos[3]
string:name[] = "Bob"
&v
Float:v = 0

Gave:

ARRAY_COUNT_ARR(const ,Float:,pos,3)
ARRAY_COUNT_STR(,,name,,"Bob")
ARRAY_COUNT_REF(,,v)
ARRAY_COUNT_NUM(,Float:,v,0)

In actual fact, when a default value is present an extra bit is appended to the macro name to aid in processing. So the real output is:

ARRAY_COUNT_ARR(const ,Float:,pos,3)
ARRAY_COUNT_STR_DEF(,,name,,"Bob")
ARRAY_COUNT_REF(,,v)
ARRAY_COUNT_NUM_DEF(,Float:,v,0)

Note that const has a space after it (the reason for which will be seen shortly).

The other potential suffix is for multi-dimensional arrays:

const Float:pos[][3][MY_ENUM]

Becomes:

ARRAY_COUNT_ARR_ARR_ARR(const,Float:,pos,,3,MY_NUM)

The number of _ARR suffixes determine the number of dimensions.

The parameters might be a little confusing, so:

PREFIX_NUM(const, tag, name)
PREFIX_NUM_DEF(const, tag, name, default)
PREFIX_REF(, tag, name)
PREFIX_REF_DEF(, tag, name, default)
PREFIX_EXT(, tag,)
PREFIX_STR(const,, name, size)
PREFIX_STR_DEF(const,, name, size, default)
PREFIX_ARR(const, tag, name, size_1)
PREFIX_ARR_ARR(const, tag, name, size_1, size_2)
PREFIX_ARR_ARR_ARR(const, tag, name, size_1, size_2, size_3)
PREFIX_ARR_ARR_ARR_ARR(const, tag, name, size_1, size_2, size_3, size_4)

Leer el resto en GitHub

pawnpawn-packagesa-mpsa-mp-developmentsamp

Esta página se genera con lo que GitHub publica sobre el repositorio. samp.directory no aloja el código. Informar de un problema con esta ficha

Y-Less/code-parse.inc: Librería de SA-MP | samp.directory