Standard macros just interpolate expressions literally. But phrep also allows for "complex macros", which utilize PHP callbacks for more clever code generation.
They can be defined by attaching an @
to macro names. The declaration body
becomes a PHP code generation callback then.
Declaration:
#define MSTR@(i,str) {return '"' . str_repeat($str,$i) . '"';}
Note that the macro name requires literal parameter names. The PHP replacement code however uses PHP sigil
$
prefixed variables.Macro usage:
$var = MSTR@(3, abc);
Would be replaced to:
$var = "abcabcabc";
The callback obviously took care of returning the literal abc
string
enclosed in double quotes for PHP code context.
Function body
The substitution code should be enclosed in {
…}
curly braces. This
is mostly for legibility / decorative purposes. Technically it's not
required:
#macro XY@(i,str) return $i \
. " . 'and' . " \
. $str;
Multi-line #defines however strictly need backslash-escaped \ ↲ linebreaks.
Macro parameters
The macro arguments, $i
and $str
here, are stringified expression
literals. The above example macro reference used 3
as integer parameter.
Would the macro have been called as MSTR@(2+1, abc)
, then $i
would
contain 2+1
however.
Which is exactly how standard macros receive their parameters. This approach can incur extra work for code generation callbacks. With some extra work, it however permits to act on more involving expressions.
For example an EACH@($a && $b && $c)
macro could explode its argument
just on &&
, then reshuffle the remaing expressions.
See the NULLSAFE@ example.
Variadic functions
You can also declare a variadic parameter.
#define LIT@(x,y,args...) { return var_export($args,1); }
There $args
could hold an array of stringified extra arguments or
expressions, such as ['arg3', '$expr+4', '"arg5"']
.
args...
instead of PHPs ...args
.
Token parameters
Apart from named parameters $i
, $str
and $args
, there's always an
implied $token
parameter to complex macro callbacks as well.
It holds a list of parser tokens from all macro arguments.
- [
T_VARIABLE
$i, +,T_LNUMBER
2, ,,T_BOOL_CAST
,T_VARIABLE
$bool, ,,T_STRING
floatval,T_WHITESPACE
, (,T_DNUMBER
1.002, )]
Working on a token string is of course more involving. Therefore you often want to split out the processing to an externalized helper function even:
#define CONFIGMACRO@(expr) { return \my\macro\func($expr); }
Utilize the config file for declaring any handler funcs.
They could later be converted back to inline #defines
and .def
includes
for distribution.
Macro helpers
There are a few built-in helper functions for token lists:
Call
io\Token::args()
to split the linear$token
list into arguments. It breaks the token stream on , comma separators into sub-arrays, one per macro argument. (This was changed in 0.3.0; as the presplitted list seemed less commonly needed.)With
io\Token::join()
you can stringify the literals from a token list again.And
io\Token::split_expr()
generates a list of expression pairs. Each array entry holds a conflated literal and a variable (if any exists, last may be empty). Which usually looks like:- [ "raw...",
$var
] - [ "&literal=",
$var2
]
This is used by the
HTML@
macro for example. (Future versions might allow to separate out more diverse token types as expressions.)- [ "raw...",
It's also planned to prepare a hook to nikic/PHP-Parser in later versions. To simplify working with expression trees, instead of plain token lists.
Why @
for macro names?
The @
suffix is used for complex macro names in phrep. And
that's both for visualization and because it's easier to uncover combos
of T_STRING
and @
tokens. They also can't overlap with existing code/syntax.
In plain PHP, the
@
is just the silencing operator - and can only prefix expressions all by itself. → Think of@$var
more like@($var)
with a silently impliedsilence@($var)
.In phrep the
@
becomes more of a "shield operator". It still masks expressions, but in essence applies a decorator. → Now literally written asnullsafe@($expr)
.Since PHP likely won't be getting decorator wrappers and syntax support anytime soon, implementing it in phrep seems like the next best thing. Should there ever be a decorator interface (with userland AST hooks even?!), complex MACRO@s could be obsoleted mostly.
Other uses
Now complex macros can also be utilized to implement some more weird syntax constructs. For instance they can semi-parse raw expression sections.
For example a
SQL@( SELECT * FROM x WHERE y = $y )
could be expanded into a prepared statement list / array / or query building chain even.Or provide safer
HTML@( <tag>$var<mix $ing> )
mixing rather easily. (Utilizes theio\Token::split_expr()
helper.)
See examples/config/.