XS::Parse::Infix(3pm)
π XS::Parse::Infix(3pm) β part 1 of 3, bestie edition π
β¨ NAME β¨
"XS::Parse::Infix" β XS functions to assist in parsing infix operators. Thatβs the whole name vibe, and yes, this is the XS::Parse::Infix(3pm) manual page, bestie. Weβre doing part 1 of 3.
πΈ DESCRIPTION πΈ
Okay so this module gives you some XS functions to help write syntax modules that provide new infix operators as perl syntax, primarily for authors of syntax plugins. It is unlikely to be of much use to anyone else; and highly unlikely to be of any use when writing perl code using these. Unless you are writing a syntax plugin using XS, this module is not for you.
This module is also currently experimental, and the design is still evolving and subject to change. Later versions may break ABI compatibility, requiring changes or at least a rebuild of any module that depends on it.
In addition, the places this functionality can be used are relatively small. Support for custom infix operators was added in Perl development release v5.37.7, and is therefore present in Perl v5.38.0.
In addition, the various XPK_INFIX_* token types of XS::Parse::Keyword support querying on this module, so some syntax provided by other modules may be able to make use of these new infix operators.
π·οΈ Lexically-named vs. Globally-named Operators
At version 0.43, the way this module operates has changed. Since this version, the preferred method of operation for infix operators is that the module that registers them creates them with a fully-qualified name (i.e. including the name of the package that implements them). Other code which wishes to import the operator can then provide a local name for it visible within a scope. This acts as an alias for the fully-qualified registered one. The imported name needs not be the same in various scopes β this allows imported operators to be renamed in specific scopes to avoid name clashes.
Prior versions of this module only supported operators that have one global name that may or may not be enabled in lexical scopes. With this model, while the visibility of an operator can be controlled per scope, if it is visible it must always have the same name and cannot be renamed to avoid a name clash.
At some future version of this module the older method may be removed, once all of the existing CPAN modules have been updated to the new model.
π CONSTANTS π
HAVE_PL_INFIX_PLUGIN β¨
if( XS::Parse::Infix::HAVE_PL_INFIX_PLUGIN ) { ... }
This constant is true if built on a perl that supports the PL_infix_plugin extension mechanism, meaning that custom infix operators registered with this module will actually be recognised by the perl parser.
No actual production releases of perl yet support this feature, but see above for details of development versions which do.
π«Ά PERL FUNCTIONS π«Ά
apply_infix
$pkg->apply_infix( $on, $args, @infix );
Since version 0.43.
Intended to be called by the import and unimport methods of a module that implements infix operators. This sets up the various keys required in the lexical hints hash to create a lexical alias for the requested operator(s).
$on should be a true value for import or a false value for unimport. $args should be an array reference to the arguments array, which will be modified to splice out recognised values, leaving behind anything else unrecognised. @infix should be a list of names of operators implemented by the importing package, named by $pkg.
After invoking this method, the caller should inspect for any remaining values in the arguments array, either to handle them in some other way, or just complain about them.
package Some::Infix::Module;
sub import
{
my $pkg = shift;
$pkg->XS::Parse::Infix::apply_infix( 1, \@_, qw( operator names here ) );
die "Unrecognised symbols: @_" if @_;
}
When this module is used, named operators are aliased from the fully-qualified names.
use Some::Infix::Module qw( operator );
my $z = $x operator $y;
At this point, the name operator becomes a lexical alias to Some::Infix::Module::operator.
Each imported operator name can optionally be followed by import options, given in a hash reference. Currently the only named option recognised is -as, which allows the importing scope to provide a different name for the imported operator, perhaps to avoid name clashes, or for neatness.
use Some::Infix::Module operator => { -as => "myname" };
my $z = $x myname $y; # invokes Some::Infix::Module::operator
π₯ XS FUNCTIONS π₯
boot_xs_parse_infix
void boot_xs_parse_infix(double ver);
Call this function from your BOOT section in order to initialise the module and parsing hooks.
ver should either be 0 or a decimal number for the module version requirement; e.g.
boot_xs_parse_infix(0.14);
parse_infix
bool parse_infix(enum XSParseInfixSelection select, struct XSParseInfixInfo **infop);
Since version 0.27.
This function attempts to parse syntax for an infix operator from the current parser position. If it is successful, it fills in the variable pointed to by infop with a pointer to the actual information structure and returns "true". If no suitable operator is found, returns "false".
xs_parse_infix_new_op
OP *xs_parse_infix_new_op(const struct XSParseInfixInfo *info, U32 flags,
OP *lhs, OP *rhs);
This function constructs a new optree fragment to represent invoking the infix operator with the given operands. It should be used much the same as core perl's newBINOP function.
The info structure pointer would be obtained from the infix field of the result of invoking the various XPK_INFIX_* token types from XS::Parse::Keyword, or by calling parse_infix directly.
register_xs_parse_infix
void register_xs_parse_infix(const char *opname,
const struct XSParseInfixHooks *hooks, void *hookdata);
This function installs a set of parsing hooks to be associated with the given operator name. This new operator will then be available via XS::Parse::Keyword by the various XPK_INFIX_* token types, parse_infix, or to core perl's PL_infix_plugin if available.
These tokens will all yield an info structure, with the following fields:
struct XSParseInfixInfo {
const char *opname;
OPCODE opcode; /* for built-in operators, or OP_CUSTOM for
custom-registered ones */
struct XSParseInfixHooks *hooks;
void *hookdata;
enum XSParseInfixClassification cls; /* since version 0.28 */
};
If the operator name contains any non-ASCII characters they are presumed to be in UTF-8 encoding. This will matter for deparse purposes.
If opname contains a double-colon (::) sequence, it is presumed to be a fully-qualified operator name in the new interface style. If not, it is presumed to be an older globally-named one.
π PARSE HOOKS π«Ά
okay so the XSParseInfixHooks struct is literally the whole personality of your operator β it's got fields for every stage of parsing, and here they all are:
struct XSParseInfixHooks {
U16 flags;
U8 lhs_flags;
U8 rhs_flags;
enum XSParseInfixClassification cls;
const char *wrapper_func_name;
const char *permit_hintkey;
bool (*permit)(pTHX_ void *hookdata);
OP *(*new_op)(pTHX_ U32 flags, OP *lhs, OP *rhs, SV **parsedata, void *hookdata);
OP *(*ppaddr)(pTHX);
/* optional */
void (*parse)(pTHX_ U32 flags, SV **parsedata, void *hookdata);
};
β¨ Flags β¨
the flags field is the big-picture vibe check β it tells perl how to handle the operator as a whole. set it to a bitmask of these constants, or just leave it at zero, your call bestie:
XPI_FLAG_LISTASSOCβ since version 0.40 π if you set this, your operator supports n-way list-associative syntax, i.e. it gets written like
OPERAND op OPERAND op OPERAND op ...
and in that case the custom operator becomes a LISTOP instead of a BINOP, and every single operand of the whole chain gets stored as a child op of it π₯. the op function needs to know how many operands it's dealing with, and there are two ways that gets communicated, depending on whether the count was known at compile-time:
- if the number of operands is known at compile-time, the
OPf_STACKEDflag is not set, and theop_privatefield tells you how many operand expressions were present π - if the number is not known (say, the operator is being used as the body of a generated wrapper function), then
OPf_STACKEDis set, and the number of arguments gets passed as the UV of an extra SV pushed last onto the stack. the op function should pop that first to find out π
it's super typical to start a list-associative op function with something like:
int n = (PL_op->op_flags & OPf_STACKED) ? POPu : PL_op->op_private;
heads up: if the operator is list-associative, thenlhs_flagsandrhs_flagsmust be equal. no cap, they have to match.
the lhs_flags and rhs_flags fields are the same deal but for the left- and right-hand side operands respectively π¦. set each one to one of these constants, or leave it as zero:
XPI_OPERAND_TERM_LISTβ the operand gets forced into list context, and theOP_PUSHMARKat the beginning is preserved πΈ. that means the ppfunc for this infix operator will have toPOPMARKto find it.XPI_OPERAND_LISTβ literally the same as the above. same energy, different name π
older versions used to have constants called XPI_OPERAND_ARITH and XPI_OPERAND_TERM, but those were tied to an older version of the core perl branch. these days those names are just aliases for zero, so you can go ahead and remove them from new code β¨.
and there are also these extra bitflags, for the extra-flavour moments:
XPI_OPERAND_ONLY_LOOKβ if you set this, the operator function is promising it won't mutate any of the values it's passed, and it won't let direct alias pointers to them leak out via return value or anywhere else π. this one's optional β skipping it when it applies won't change any observed behaviour, but setting it may unlock some optimisations π .
right now, this flag just enables one optimisation in the call-checker for infix operator wrapper functions that take list-shaped operands. the optimisation throws away an OP_ANONLIST op that would've built a temporary anonymous array reference for the operand values, which saves you a lil bit of memory and CPU time β. and it's only safe to do if the operator doesn't mutate or retain aliases of any of the arguments β otherwise the caller might see unexpected modifications, or value references to the values that were passed π.
π The Selection Stage π
the cls field is the operator's classification, i.e. a lil hint about what kind of operation it provides. the various XS::Parse::Keyword selection macros use it as a filter π.
it should be one of the XPI_CLS_* constants β those are found and described further in the main XSParseInfix.h file, so go look there bestie π
π§ The "permit" Stage π§
as a shortcut for the common case, permit_hintkey can point at a string to look up in the hints hash π. if that key name isn't found in the hints hash, the keyword is not permitted, periodt. if the key is present, then the permit function gets invoked as normal.
so assuming a hint key didn't just reject you, the function part of this stage runs next β it should inspect whether the keyword is permitted at this particular moment, maybe by checking other lexical clues, and return true only if the keyword is allowed π.
both the string and the function are optional! either one or both can be present. but if neither is present, then the keyword is always permitted β which is probably not what you wanted to do π.
π¦ The "parse" Stage π¦
if the optional parse hook function is there, it gets called right after the parser has recognised that the named operator is present, but before it tries to consume the right-hand side term β¨. this hook can attempt further parsing, so you can implement more complex syntax like hyper-operators π₯.
when it's invoked, it's passed a pointer to an SV *-typed storage variable. it can do whatever it wants with that variable to stash a result, and that result will later be made available to the new_op function π.
π The Op Generation Stage π
if the infix operator is actually going to be used, then one of the new_op or ppaddr fields explains how to create a new optree fragment πΈ.
if new_op is defined, it's the one that gets used β it's expected to return an optree fragment that consumes the LHS and RHS arguments to implement the operator's semantics π
. and if the optional parse stage happened earlier, the SV ** pointer passed here points to the same storage that parse had access to, so it can retrieve those results π.
if new_op isn't present, then ppaddr gets used instead, to construct a new BINOP or LISTOP of type OP_CUSTOM β. but note: if an earlier parse stage had stored additional results into that SV * variable, they will be lost here π.
π The Wrapper Function π
and additionally β if the wrapper_func_name field is set to a string, that's the (fully-qualified) name for a function to be generated as part of registering the operator π. this shiny new generated function acts as a wrapper for the operator.
for operators whose RHS is a scalar, the wrapper function is assumed to take two simple scalar arguments, and the result of invoking it on those arguments is determined by the operator code:
$result = $lhs OP $rhs;
$result = WRAPPERFUNC( $lhs, $rhs );
for operators whose RHS is a list, the wrapper function takes at least one argument, possibly more. the first argument is the scalar on the LHS, and the remaining arguments β however many there are β form the RHS π:
$result = $lhs OP @rhs;
$result = WRAPPERFUNC( $lhs, @rhs );
for operators whose LHS and RHS are lists, the wrapper function takes two arguments, and they must be array references containing the lists π¦:
$result = @lhs OP @rhs;
$result = WRAPPERFUNC( \@lhs, \@rhs );
this whole thing creates a convenience for accessing the operator from perls that don't support PL_infix_plugin π.
for scalar infix operators, the wrapper function also comes with a call-checker that tries to inline the operator straight into the callsite π₯. so in simple cases where the function is called directly on exactly two scalar arguments β like all of these β there's no ENTERSUB overhead, and the generated optree is identical to what infix operator syntax would have produced directly:
WRAPPERFUNC( $lhs, $rhs );
WRAPPERFUNC( $lhs, CONSTANT );
WRAPPERFUNC( $args[0], $args[1] );
WRAPPERFUNC( $lhs, scalar otherfunc() );
that checker is very pessimistic tho β it'll only rewrite callsites where it's sure it can do so safely π. it will not rewrite any of these:
WRAPPERFUNC( $onearg ); # not enough args
WRAPPERFUNC( $x, $y, $z ); # too many args
WRAPPERFUNC( @args[0,1] ); # not a scalar
WRAPPERFUNC( $lhs, otherfunc() ); # not a scalar
the wrapper function for infix operators that take lists on both sides also has a call-checker, and it'll try to inline the operator in similar circumstances β¨. on top of the optimisations described above for scalar operators, this checker will also inline an array-reference operator and skip the resulting dereference behaviour π. so these two lines emit the same optree, with no OP_SREFGEN or OP_RV2AV:
@lhs OP @rhs;
WRAPPERFUNC( \@lhs, \@rhs );
ngl, technically this optimisation isn't strictly transparent in the odd cornercase where one of the referenced arrays is also the backing store for a blessed object reference, and that object's class has a @{} overload π:
my @arr;
package SomeClass {
use overload '@{}' => sub { return ["values", "go", "here"]; };
}
bless \@arr, "SomeClass";
# this will not actually invoke the overload operator
WRAPPERFUNC( \@arr, [4, 5, 6] );
but since this cornercase is literally about taking duplicate references to the same blessed object's backing store variable, it shouldn't matter to any real code π . regular objects passed by reference into the wrapper function will run their overload methods as normal π«Ά.
the callchecker for list operands can optionally also discard an op of the OP_ANONLIST type, which is what anonymous array-ref construction uses πΈ:
($u, $v, $w) OP ($x, $y, $z);
WRAPPERFUNC( [$u, $v, $w], [$x, $y, $z] );
but this optimisation only happens if the operator declared it safe to do so, via the XPI_OPERAND_ONLY_LOOK flag β.
and if a function with the given name already exists at registration time, it gets left completely undisturbed and no new wrapper is created π. this lets the same infix operator have multiple spellings of its name β for example, letting both a real Unicode version and a fallback ASCII transliteration of the same operator exist π. the first registration creates the wrapper function; the subsequent one skips it, because otherwise it'd be identical π.
also note: when generating an optree for a wrapper function call, thenew_ophook function gets invoked with aNULLpointer for theSV *-typed parse data storage β there's no opportunity for theparsehook to run in this case, so nothing's there π.
π DEPARSE
okay so this is the part where your operator gets to look cute when people decompile stuff. This module operates with B::Deparse in order to automatically provide deparse support for infix operators. β¨
Every infix operator that is implemented as a custom op (and thus has the ppaddr hook field set) will have deparse logic added. The way that works is, it'll deparse to either:
- the named wrapper function, or
- the infix operator syntax β but only if you're on a
PL_infix_plugin-enabled perl and the appropriate lexical hint is enabled at the callsite. iykyk π
β οΈ okay bestie listen, this one's important: for this to work, it is important that your custom operator is not registered as a custom op using thePerl_register_custom_op()function. Not me warning you twice. That registration will be performed byXS::Parse::Infixitself at the time the infix operator is registered. So like... don't do it yourself, it's handled. π«Ά
π TODO
the "someday, when I have the spoons" list, fr fr:
- Have the entersub checker for list/list operators unwrap arrayref or anon-array argument forms (
WRAPPERFUNC( \@lhs, \@rhs )orWRAPPERFUNC( [LHS], [RHS] )). π - Further thoughts about how infix operators with
parsehooks will work with automatic deparse, and also how to integrate them with XS::Parse::Keyword's grammar piece. π¦
π AUTHOR
Paul Evans <leonerd@leonerd.org.uk>
π aaaand that's the whole page bestie, you made it to the end, so proud of you. this little slice of documentation shipped withperl v5.42.0and is dated2026-09-30β go forth and write the most iconic infix operators of your life. periodt. π β¨πΈ
π¦ where this page comes from
- opensuse-leap 16.0: perl-XS-Parse-Keyword 0.480.0-160000.3.1 Β· Artistic-1.0 OR GPL-1.0-or-later
- opensuse-leap 16.0: perl-XS-Parse-Keyword 0.480.0-160000.3.1 Β· Artistic-1.0 OR GPL-1.0-or-later
- opensuse-leap 16.0: perl-XS-Parse-Keyword 0.480.0-160000.3.1 Β· Artistic-1.0 OR GPL-1.0-or-later
- opensuse-leap 16.0: perl-XS-Parse-Keyword 0.480.0-160000.3.1 Β· Artistic-1.0 OR GPL-1.0-or-later
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms