Marpa:...antics(3pm)

๐Ÿ“ฆ
version 12.000000 ยท 2 versions
, in ubuntu stonking

โœจ NAME โœจ

Marpa::R2::Deprecated::NAIF::Semantics โ€” how the NAIF evaluates parses. (It's the Marpa:...antics(3pm) page, shipped under User Contributed Perl Documentation, and yeah bestie it's part 1 of 3, buckle up ๐ŸŽ€)


๐Ÿšจ THE NAIF INTERFACE IS DEPRECATED ๐Ÿšจ

okay so real talk first. This document is about the NAIF interface, and the NAIF interface is deprecated. PLEASE DO NOT USE IT FOR NEW DEVELOPMENT. that's not me being dramatic, that's the actual vibe of the page ๐Ÿ’€

๐ŸŒธ Synopsis ๐ŸŒธ

my $grammar = Marpa::R2::Grammar->new(
    {   start          => 'Expression',
        actions        => 'My_Actions',
        default_action => 'first_arg',
        rules          => [
            { lhs => 'Expression', rhs => [qw/Term/] },
            { lhs => 'Term',       rhs => [qw/Factor/] },
            { lhs => 'Factor',     rhs => [qw/Number/] },
            { lhs => 'Term', rhs => [qw/Term Add Term/], action => 'do_add' },
            {   lhs    => 'Factor',
                rhs    => [qw/Factor Multiply Factor/],
                action => 'do_multiply'
            },
        ],
    }
);

sub My_Actions::do_add {
    my ( undef, $t1, undef, $t2 ) = @_;
    return $t1 + $t2;
}

sub My_Actions::do_multiply {
    my ( undef, $t1, undef, $t2 ) = @_;
    return $t1 * $t2;
}

sub My_Actions::first_arg { shift; return shift; }

my $value_ref = $recce->value;
my $value = $value_ref ? ${$value_ref} : 'No Parse';

๐Ÿฆ‹ Overview ๐Ÿฆ‹

Okay so this doc is all about Marpa's low-level NAIF interface. If you're new to Marpa, or you're not sure which interface you even want, or you genuinely don't know what the Named Argment InterFace (NAIF) is, then bestie you're probably in the wrong place and you actually want the document on semantics for the SLIF interface. no cap ๐ŸŒˆ

The NAIF's semantics will feel super familiar if you've ever used traditional methods to evaluate parses. Here's the tea: a parse is seen as a parse tree. Nodes on that tree get evaluated recursively, bottom-up. Once all of a node's child values are known, that parent node is ready to be evaluated. And the value of a whole parse is just the value of the top node of the parse tree. iconic, honestly ๐Ÿ‘‘

When a Marpa grammar is created, its semantics gets specified indirectly, as action names. And to actually produce the values used in the semantics, Marpa has to do three things:

  1. Determine the action name.
  2. Resolve the action name to an action.
  3. If the action is a rule evaluation closure, call it to produce the actual result.

So an action name is either reserved, or it resolves to a Perl object. When it resolves to a Perl object, that object is usually a Perl closure โ€” like, code. If the Perl object action is code, it's a rule evaluation closure, and it will get called to produce the result. But when a Perl object action is some other kind of object, how it's treated is described down below ๐Ÿ‘‡

An action name and an action also get used to create the per-parse-tree variable, and that's described below too.


๐ŸŒณ Nodes ๐ŸŒณ

๐Ÿ“ Token nodes ๐Ÿ“

For every input token, there's an associated token node. Token nodes are the leaf nodes in the parse tree. Tokens always have a token symbol. At lexing time they can be assigned a token value. And if no token value is assigned at lex time, then the value of the token is a Perl undef. that's it, that's the whole thing ๐Ÿซถ

โญ Rule nodes โญ

If a node isn't a token node, then it's a rule node. Rule nodes are always associated with a rule. If a rule's action is a rule evaluation closure, it gets called at Node Evaluation Time.

The rule evaluation closure's arguments will be a per-parse-tree variable followed, if the rule is not nulled, by the values of its child nodes in lexical order. If the rule is nulled, the child node values get omitted entirely. And a rule evaluation closure action is always called in scalar context.

If the action is a constant, it becomes the value of the rule node. If the action is a rule evaluation closure, its return value becomes the value of the node. And if there's no action for a rule node, then the value of the rule node is a Perl undef.

๐Ÿงš Sequence rule nodes ๐Ÿงš

Some rules are sequence rules. And sequence rule nodes are also just rule nodes, so everything we said above about rule nodes applies to them too. Specifically: the arguments to the value actions for sequence rules are the per-parse-tree variable followed by the values of the child nodes in lexical order.

Now here's the difference, and it is a big one: in an ordinary rule, the right hand side is fixed in length, and that length is known when you're writing the code for the value action. But in a sequence rule, the number of right hand side symbols isn't known until node evaluation time. So the rule evaluation closure of a sequence rule must be capable of dealing with a variable number of arguments. periodt.

Sequence semantics work best when every child node in the sequence has the same semantics. When that's not the case, you should consider writing the sequence using ordinary non-sequence rules instead. as an alternative, you know?

By default, if a sequence rule has separators, those separators get thrown away before the value action is called. (Separators are described in the section introducing sequence rules.) This means separators do not appear in the @_ array of the rule evaluation closure that is the value action. But if the value of the "keep" rule property is a Perl true value, then the separators are kept, and they do appear in the value action's @_ array โœจ

๐Ÿ’œ Null nodes ๐Ÿ’œ

A null node is a special case of a rule node โ€” one where the rule derives the zero-length, or empty, string. When the rule node is a null node, the rule evaluation closure gets called with no child value arguments.

When a node is nulled, it must be as a result of a nullable rule, and the action name and action are those associated with that rule. Now, an ambiguity can arise if there's more than one nullable rule with the same LHS but a different action name. In that case, the action name for the null nodes is that of the empty rule.

And then there's the remaining case: a set of nullable rules with the same LHS where two or more of the rules have different action names, but none of the rules in the set is an empty rule. When this happens, Marpa throws an exception ๐Ÿ˜ญ To fix it, the user can add an empty rule. For more details, see the document on null semantics.


๐Ÿ”ฎ Action context ๐Ÿ”ฎ

sub do_S {
    my ($action_object) = @_;
    my $rule_id         = $Marpa::R2::Context::rule;
    my $grammar         = $Marpa::R2::Context::grammar;
    my ( $lhs, @rhs ) = $grammar->rule($rule_id);
    $action_object->{text} =
          "rule $rule_id: $lhs ::= "
        . ( join q{ }, @rhs ) . "\n"
        . "locations: "
        . ( join q{-}, Marpa::R2::Context::location() ) . "\n";
    return $action_object;
} ## end sub do_S

On top of the per-parse-tree variable and their child arguments, rule evaluation closures also get access to some context variables. here they are:

  • $Marpa::R2::Context::grammar โ€” gets set to the grammar being parsed.
  • $Marpa::R2::Context::rule โ€” the ID of the current rule. Given that rule ID, an application can find its LHS and RHS symbols using the grammar's rule() method.
  • Marpa::R2::Context::location() โ€” returns the start and end locations of the current rule.

๐Ÿ™ˆ Bailing out of parse evaluation ๐Ÿ™ˆ

my $bail_message = "This is a bail out message!";

sub do_bail_with_message_if_A {
    my ($action_object, $terminal) = @_;
    Marpa::R2::Context::bail($bail_message) if $terminal eq 'A';
}

sub do_bail_with_object_if_A {
    my ($action_object, $terminal) = @_;
    Marpa::R2::Context::bail([$bail_message]) if $terminal eq 'A';
}

The Marpa::R2::Context::bail() static method is how you "bail out" of the evaluation of a parse tree. It will cause an exception to be thrown. And here's how it decides what to throw: if its first and only argument is a reference, then that reference is the exception object. Otherwise, an exception message gets created by converting the method's arguments to strings, concatenating them, and prepending them with a message indicating the file and line number at which the Marpa::R2::Context::bail() method was called. lowkey useful for debugging, fr fr ๐Ÿ’…


๐ŸŽ€ Parse trees, parse results and parse series ๐ŸŽ€

When the semantics get applied to a parse tree, it produces a value called a parse result. And because Marpa allows ambiguous parsing, each parse can produce a parse series โ€” a series of zero or more parse trees, each with its own parse result. The first call to the recognizer's "value" method after the recognizer is created is the start of the first parse series. That first parse series continues until there's a call to the "reset_evaluation" method, or until the recognizer is destroyed. Usually an application is only interested in a single parse series, thank goodness ๐ŸŒธ

When the "reset_evaluation" method is called for a recognizer, it begins a new parse series. That new parse series continues until there's another call to the "reset_evaluation" method, or until the recognizer is destroyed.

Most applications will find that the order in which Marpa executes its semantics "just works" ๐Ÿ˜Œ There's a separate document that describes that order in detail. And those details can matter in some applications โ€” for example, the ones that exploit side effects. iykyk.


๐Ÿ‘‘ Finding the action for a rule ๐Ÿ‘‘

Marpa finds the action for each rule based on rule and symbol properties and on the grammar named arguments. Specifically, Marpa attempts the following, in order:

  • Resolving an action based on the "action" property of the rule, if one is defined.
  • If the rule is empty, and the "default_empty_action" named argument of the grammar is defined, resolving an action based on that named argument.
  • Resolving an action based on the "default_action" named argument of the grammar, if one is defined.
  • Defaulting to a Perl undef value.

Resolution of action names is described below. And heads up: if the "action" property, the "default_action" named argument, or the "default_empty_action" named argument is defined but doesn't resolve successfully, then Marpa throws an exception ๐Ÿ’€ Marpa prefers to "fast fail" in these cases, because they usually indicate a mistake that the application's author will want to correct. and honestly? fair.


๐Ÿ’ซ Resolving Action Names ๐Ÿ’ซ

okay bestie so where do action names even come from? Four sources, that's it, and here they are:

  • The default_action named argument of Marpa's grammar.
  • The default_empty_action named argument of Marpa's grammar.
  • The action property of Marpa's rules.
  • The new constructor in the package specified by the action_object named argument of the Marpa grammar.

๐Ÿšซ Reserved Action Names ๐Ÿšซ

Action names that begin with a double colon (::) are reserved. Don't touch, don't claim. At present only the ::undef reserved action is documented for use outside of the DSL-based interfaces.

  • ::undef โ€” a constant whose value is a Perl undef. Here's the tea tho: Perl is unable to distinguish reliably between a non-existent value and scalars with an undef value. Which means it's impossible to reliably distinguish resolutions to a Perl undef from resolution problems. So for indicating a constant whose value is a Perl undef, the ::undef reserved action name should be preferred. periodt ๐Ÿ’…

โœจ Explicit Resolution โœจ

The recognizer's closures named argument lets you, the user, directly control the mapping from action names to actions. The value of the closures named argument is a reference to a hash whose keys are action names and whose hash values are references. Typically (but not always) these will be CODE refs.

If an action name is the key of an entry in the closures hash, it resolves to the closure referenced by the value part of that hash entry. Resolution via the closures named argument is called explicit resolution.

When explicit resolution is the only kind of resolution that is wanted, it is best to pick a name that is very unlikely to be the name of a Perl object. Many of Marpa::HTML's action names are intended for explicit resolution only. In Marpa::HTML those action names begin with an exclamation mark (!), and that convention is recommended. okay iconic ๐ŸŒธ


๐Ÿฆ‹ Fully Qualified Action Names ๐Ÿฆ‹

If explicit resolution fails, Marpa transforms the action name into a fully qualified Perl name. An action name that contains a double colon (::) or a single quote (') is considered to be a fully qualified name. Any other action name is considered to be a bare action name.

If the action name to be resolved is already a fully qualified name, it is not further transformed. It will be resolved in the form it was received, or not at all. no glow-ups allowed fr

For bare action names, Marpa tries to qualify them by adding a package name. If the actions grammar named argument is defined, Marpa uses it as the package name. Otherwise, if the action_object grammar named argument is defined, Marpa uses it as the package name. Once Marpa has fully qualified the action name, Marpa looks for a Perl object with that name.

Marpa will not attempt to resolve an action name that it cannot fully qualify. This implies that, for an action name to resolve successfully, one of these five things must be the case:

  • The action name is one of the reserved action names.
  • The action name resolves explicitly.
  • The action name is fully qualified to begin with.
  • The actions named argument is defined.
  • The action_object named argument is defined.

Marpa's philosophy is to require that the programmer be specific about action names. This can be an inconvenience, but Marpa prefers this to silently executing unintended code. ๐Ÿ‘‘ honest king behavior tbh

If the user wants to leave the rule evaluation closures in the main namespace, she can specify main as the value of the actions named argument. But it can be good practice to keep the rule evaluation closures in their own namespace, particularly if the application is not small. ๐Ÿ’–


๐Ÿ’€ Types of Perl Actions ๐Ÿ’€

Actions resolve in three ways: to reserved actions, to Perl rule evaluation closures and to Perl variable actions. The following are tried, in order.

  • If an action name begins with a double colon (::), it will resolve to a reserved action, or not at all.
  • If the fully qualified form of an action name is the name of a Perl subroutine in the symbol table, the action name resolves to the Perl subroutine. That subroutine then becomes a Perl rule evaluation closure.
  • If the fully qualified form of an action name is the name of a Perl scalar variable in the symbol table, the action name resolves to the Perl variable. Note that, for this purpose, a Perl reference variable is considered as one type of Perl scalar.
  • Other kinds of Perl objects in the symbol table that match the fully qualified name, such as arrays and hashes, are ignored. Note that, while resolution to arrays and hashes is not allowed, resolution to references to arrays and hashes is permitted. ๐Ÿ™ˆ sneaky little loophole

Resolution to a Perl rule evaluation closure or to a Perl variable may came from explicit resolution. Explicit resolution always takes place via a reference, and requires an extra level of indirection. For resolution to a rule evaluation closure, the closure must be provided in the form of a reference to the closure. For resolution to a Perl variable, the variable has to be provided in the form of a reference to the variable. If the Perl variable is a reference, that means adding another level of indirection. it's giving inception, bestie ๐ŸŒ€


๐Ÿ˜ญ Modifying Perl Variable Actions ๐Ÿ˜ญ

When resolution is to a Perl variable, it is possible to modify the value of the variable. In practice, this will usually be a bad idea. The Perl variable reference actions should be treated as read-only constants, and never modified.

This is because multiple resolutions to a Perl variable will always point to the same contents. Any modification to those contents will be seen by other users of that Perl variable. In other words, the modification will have global effect. For this reason modifying the referents of reference actions is almost always bad practice at the least, and is often an error.

For example, assume that actions are in a package named My_Nodes, which contains a hash reference named empty_hash,

package My_Nodes;
our $empty_hash = {};

It can be tempting, in building objects which are hashes, to start with a leaf node whose action is empty_hash and to add contents to it as the object is passed up the evaluation tree. But $empty_hash points to a single hash object. This single hash object will shared by all nodes, with all nodes seeing each other's changes. Worse, all Marpa parsers which use the same My_Nodes namespace will share the same hash object. An application which needs an action which produces an empty hash should have the action resolve to a Perl rule evaluation closure that returns {}. ๐Ÿ’… don't do the temptation thing


๐Ÿงš Visibility and Resolution ๐Ÿงš

When Perl closures and variables are used for the semantics, they must be visible in the scope where the semantics are resolved. The action names are usually specified with the grammar, but action resolution takes place in the recognizer's value method. This can sometimes be a source of confusion. For example, if a Perl closure is visible when the action is specified, but goes out of scope before the action name is resolved, resolution will fail. ๐Ÿ˜ญ timing is everything fr fr


๐ŸŽ€ The Per-Parse-Tree Variable ๐ŸŽ€

In the Tree Setup Phase, Marpa creates a per-parse-tree variable. This becomes the first argument of the rule evaluation closures for the rule nodes. If the grammar's action_object named argument is not defined, the per-parse-tree variable is initialized to an empty hash ref. ๐Ÿ“

Most data for the value actions of the rules will be passed up the parse tree. The actions will see the values of the rule node's child nodes as arguments, and will return their own value to be seen as an argument by their parent node. The per-parse-tree variable can be used for data which does not conveniently fit this model. it's giving "misc bag for the weird stuff" energy

The lifetime of the per-parse-tree variable extends into the Tree Traversal Phase. During the Tree Traversal Phase, Marpa's internals never alter the per-parse-tree variable โ€” it is reserved for use by the application. ๐Ÿค untouched, as promised


๐Ÿ’– Action Object Constructor ๐Ÿ’–

If the grammar's action_object named argument has a defined value, that value is treated as the name of a class. The action object constructor is the new method in the action_object class.

The action object constructor is called in the Tree Setup Phase. The return value of the action object constructor becomes the per-parse-tree variable. It is a fatal error if the grammar's action_object named argument is defined, but does not name a class with a new method. ๐Ÿ’€ no new? no parse.

Resolution of the action object constructor is resolution of the action object constructor name. The action object constructor name is formed by concatenating the literal string ::new to the value of the action_object named argument.

All standard rules apply when resolving the action object constructor name. In particular, bypass via explicit resolution applies to the action object constructor. If the action object constructor name is a hash key in the evaluator's closures named argument, then the value referred to by that hash entry becomes the action object constructor.

If a grammar has both the actions and action_object named arguments defined, all action names except for the action object constructor will be resolved in the actions package or not at all. The action object constructor is always in the action_object class, if it is anywhere. โญ constructor stays in its own lane


๐ŸŒˆ Parse Order ๐ŸŒˆ

If a parse is ambiguous, all parses are returned, with no duplication. By default, the order is arbitrary, but it is also possible to control the order. Details are in the document on parse order. main character energy: you can be in charge of the ordering if you want ๐Ÿ˜Œ


๐Ÿ”ฅ Infinite Loops ๐Ÿ”ฅ

Grammars with infinite loops (cycles) are generally regarded as useless in practical applications, but Marpa allows them. Marpa can accurately claim to support every grammar that can be written in BNF. no cap, every single one ๐Ÿซถ

If a grammar with cycles is ambiguous, it can produce cycle-free parses and parses with finite-length cycles, as well as parses with infinite length cycles. Marpa will parse with grammars that contain cycles, and Marpa's evaluator will iterate through the values from the grammar's cycle-free parses. For those who want to know more, the details are in a separate document. ๐Ÿ‘€ go read it if you're curious


okay bestie, legal time, but make it cute ๐Ÿ’…

  • Copyright 2022 Jeffrey Kegler โ€” that's the whole copyright line, nobody else, just him ๐Ÿซถ
  • This file is part of Marpa::R2 โœจ
  • Marpa::R2 is free software ๐ŸŽ€ โ€” you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Slay, that's literally the license grant โœจ

the warranty bit (read it, it matters) ๐Ÿ˜ญ

Marpa::R2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY โ€” no cap, none โ€” without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ๐Ÿ’€ See the GNU Lesser General Public License for more details.

fr fr, if your parse goes feral, that's on the license, not on Jeffrey. The GPL said so. ๐Ÿ™ˆ

where to find the actual license ๐Ÿ‘‘

You should have received a copy of the GNU Lesser General Public License along with Marpa::R2. If not, bestie, it's right here:

http://www.gnu.org/licenses/

go get it, it's iconic โญ


๐Ÿฆ‹ sign-off ๐Ÿฆ‹

and that's the whole page!! this was Marpa:...antics(3pm) โ€” Perl v5.42.3, dated 2026-08-20. โœจ

ty for reading the entire thing, you're genuinely the main character for that ๐Ÿ’œ mwah, go write some grammars ๐Ÿ’…๐ŸŒธ

๐Ÿ“ฆ where this page comes from