Marpa:...antics(3pm)

πŸ’– NAME πŸ’–

Marpa::R2::Semantics β€” the tea on how the SLIF evaluates parses πŸ«–βœ¨

(oh and psst, this is from the User Contributed Perl Documentation, 3pm section, if you were wondering where it lives πŸ‘‘


🌸 Synopsis 🌸

okay bestie, here's the whole vibe in one go, no edits, copy it exactly:

use Marpa::R2;

my $grammar = Marpa::R2::Scanless::G->new(
    {   bless_package => 'My_Nodes',
        source        => \(<<'END_OF_SOURCE'),
:default ::= action => [values] bless => ::lhs
lexeme default = action => [ start, length, value ]
    bless => ::name latm => 1

:start ::= Script
Script ::= Expression+ separator => comma
comma ~ [,]
Expression ::=
    Number bless => primary
    | '(' Expression ')' bless => paren assoc => group
   || Expression '**' Expression bless => exponentiate assoc => right
   || Expression '*' Expression bless => multiply
    | Expression '/' Expression bless => divide
   || Expression '+' Expression bless => add
    | Expression '-' Expression bless => subtract

Number ~ [\d]+
:discard ~ whitespace
whitespace ~ [\s]+
# allow comments
:discard ~ <hash comment>
<hash comment> ~ <terminated hash comment> | <unterminated
   final hash comment>
<terminated hash comment> ~ '#' <hash comment body> <vertical space char>
<unterminated final hash comment> ~ '#' <hash comment body>
<hash comment body> ~ <hash comment char>*
<vertical space char> ~ [\x{A}\x{B}\x{C}\x{D}\x{2028}\x{2029}]
<hash comment char> ~ [^\x{A}\x{B}\x{C}\x{D}\x{2028}\x{2029}]
END_OF_SOURCE
    }
);

my $recce = Marpa::R2::Scanless::R->new( { grammar => $grammar } );

my $input = '42*2+7/3, 42*(2+7)/3, 2**7-3, 2**(7-3)';
$recce->read(\$input);
my $value_ref = $recce->value();
die "No parse was found\n" if not defined $value_ref;

# Result will be something like "86.33... 126 125 16"
# depending on the floating point precision
my $result = ${$value_ref}->doit();

package My_Nodes;

sub My_Nodes::primary::doit { return $_[0]->[0]->doit() }
sub My_Nodes::Number::doit  { return $_[0]->[2] }
sub My_Nodes::paren::doit   { my ($self) = @_; $self->[1]->doit() }

sub My_Nodes::add::doit {
    my ($self) = @_;
    $self->[0]->doit() + $self->[2]->doit();
}

sub My_Nodes::subtract::doit {
    my ($self) = @_;
    $self->[0]->doit() - $self->[2]->doit();
}

sub My_Nodes::multiply::doit {
    my ($self) = @_;
    $self->[0]->doit() * $self->[2]->doit();
}

sub My_Nodes::divide::doit {
    my ($self) = @_;
    $self->[0]->doit() / $self->[2]->doit();
}

sub My_Nodes::exponentiate::doit {
    my ($self) = @_;
    $self->[0]->doit()**$self->[2]->doit();
}

sub My_Nodes::Script::doit {
    my ($self) = @_;
    return join q{ }, map { $_->doit() } @{$self};
}

πŸ¦‹ About this document πŸ¦‹

So this doc is literally the explainer for semantics in Marpa's main interface, the SLIF. main character energy, honestly πŸ’…


✨ What is semantics? ✨

okay so picture a parser. it's just an algorithm that takes a string of symbols (could be tokens, could be characters) and finds some structure hiding in there. traditionally that structure is a tree 🌳

but like… who cares about a tree by itself?? rare. usually the whole point is that the string means something β€” that it has a semantics. traditionally, and most of the time, the tree is just the middle step on the way to producing a value, and that value is the thing that stands for the "meaning" / "semantics" of the string 🫢

"Evaluating" a tree = finding its semantics. that's it, that's the whole definition. the rest of this doc is Marpa's methods for evaluating trees. and if you've messed with the classic parsers like yacc and bison? you'll find Marpa's whole approach familiar, no cap πŸ’―


🌈 Instances 🌈

At the start of evaluation, semantics gets associated with instances β€” instances of rule alternatives, or of lexemes. An instance is an occurrence, and "occurrence" here means in terms of G1 locations. Every instance has two locations: a start location and an end location πŸ“πŸ“

A rule alternative is the LHS of a rule, plus one of its RHS alternatives. Unless a rule is a prioritized rule, it has exactly one rule alternative, periodt.

Prioritized rules very often only have one rule alternative β€” when that happens they're called trivial prioritized rules. But lowkey, prioritized rules can also have many rule alternatives.

When a rule has just one rule alternative, or when the context makes it obvious what we mean, a rule alternative gets casually called just a "rule". Specifically: a rule alternative instance is almost always called just a rule instance. casual ✨


πŸŽ€ Nodes πŸŽ€

In a parse tree, nodes are the spots where the tree branches or terminates. Terminations are also called terminals or "leaves" πŸƒ

Every rule instance in a Marpa parse is represented by a branch point (aka a "node") in the tree. The very top node of a tree is its "root node". (fun fact: trees are easiest to draw upside down, which is why in programming the top of a tree is traditionally the root β€” it's giving hanging plant πŸͺ΄)

A node, aka branch point, "branches" into zero or more "child nodes". The node sitting just above a child node β€” the one the child branches out from β€” is called its parent node.

Here's the breakdown, bestie:

  • If the node is for a non-quantified rule instance, the parent node is the LHS of the rule, and the child nodes are the RHS of the rule alternative.
  • If the node is for a quantified rule, the parent node is the LHS of the quantified rule, and the child nodes are the items of the sequence of symbols on the right hand side.
  • If the node is for a lexeme, the node represents the lexeme's symbol, and there will be no child nodes.

A parent node can have zero or more children. Rule instances with zero children are nulled rule instances, and they're "leaf nodes". Leaf nodes = terminals. In Marpa's parse trees, every terminal is either a lexeme or a nulled rule instance 🌸

And here's the important bit: in Marpa, evaluation only takes place within the structural (G1) subgrammar, and all the descriptions of rule and lexeme instance behavior below apply only to the G1 subgrammar. L0 rule alternatives and terminal symbols do not become nodes in the parse tree, and are never evaluated. don't come for them, they're not invited πŸ’€


πŸ”₯ The order of node evaluation πŸ”₯

The nodes of a Marpa parse tree get evaluated recursively, left-to-right and bottom-up. Which means: by the time a parent node is evaluated, the values of all its child nodes are known and ready for the semantics to use. the final value of a parse is the value of the top node of the parse tree πŸ‘‘


πŸ’œ Parse trees πŸ’œ

Every time a SLIF recognizer calls the value() method, you get a series of zero or more parse trees β€” that series is called a parse series. A recognizer will have only one parse series, unless it calls the series_restart() method.

  • There may be zero parses in a parse series, because there may be no valid parse of a virtual input.
  • There may be more than one parse in a parse series, because Marpa allows ambiguous parsing.

Full details about the life cycle of a Marpa recognizer, including a full treatment of parse series, live in another document πŸ“š


🧚 Nulled subtrees 🧚

A nulled subtree is a subtree of the parse tree formed by a nulled node and its direct and indirect child nodes. (And yes, all of those child nodes will also be nulled nodes.)

Before evaluation, Marpa prunes all nulled subtrees back to their topmost nulled node. Out of all the possible ways to deal with nulled subtrees, this is the simplest one, and Marpa's users have found it to be a natural approach. More detail on the semantics of nulled symbols and subtrees is in a separate document βœ‚οΈ


πŸ’… Actions and how Marpa finds them πŸ’…

The way the SLIF finds the value of a node is called that node's action. Actions can be explicit or implicit.

  • An explicit action is one that's explicitly specified by the application, using one of the ways described below.
  • A node's implicit action is the one it performs if it has no explicit action.

πŸ“ Lexeme actions

The implicit action for a lexeme is to return its literal value in the input stream, as a string.

An explicit default action name for lexemes can be set with the lexeme default statement.

⚠️ heads up: a lexeme action cannot be a Perl closure action β€” it has to be one of the built-in actions that are appropriate for lexemes. no custom closures allowed, sorry babe πŸ™ˆ

⭐ Rule actions

The implicit action for a rule instance is to return a Perl undef.

An explicit action for a RHS alternative can be specified using the "action" adverb for that RHS alternative. And a default explicit action for RHS alternatives can be specified with a default pseudo-rule.

πŸ‘‘ Nulled symbol actions

Like we said: nulled subtrees get pruned back to their topmost symbol. Lexemes are never nulled, so a nulled symbol is always the LHS of a rule instance, and the action is determined from the rule alternative, exactly as described above.

Now here's where it gets spicy 🌢️ β€” a complication shows up if the symbol appears on the LHS of more than one nullable rule alternative. Because the symbol is nulled, the input is zero help in figuring out which rule alternative to use. So the rule alternative whose semantics are used for a nulled symbol is determined like this:

  • If all nullable rule alternatives have the same semantics, that semantics is used.
  • If one of the nullable rule alternatives is empty (that is, has a zero-length RHS), then the empty alternative's semantics are used.
  • In the remaining case, two or more of the rule alternatives have different action names, but none of the alternatives has a zero-length RHS. When this happens, Marpa throws an exception 😭 One easy way to fix the issue is to add an empty rule with the intended semantics.

And when we're deciding whether the semantics of two nullable rule alternatives are "the same", the blessing is taken into account. Two rule alternatives are considered to have different semantics if they're blessed differently. The SLIF's null semantics get described in more detail in a separate document πŸ“–


πŸ’– Blessings πŸ’–

okay so here's the tea, bestie πŸ«– β€” sometimes part of a rule alternative's action, or a lexeme's action, is a blessing. And a blessing is literally just the name of a Perl package. That's it. That's the whole thing. ✨

If the action is a rule evaluation closure, then the argument that holds its child values gets blessed into that package. No cap.

But hold on β€” not every action is a rule evaluation closure! 😭 An action could be, like, an array descriptor action (we'll get there, stay with me). And in cases where the action is not a rule evaluation closure, it's the value of the action that gets blessed into that package instead.

⚠️ Only Perl objects pointed to by references can be blessed. Trying to use a blessing with an inappropriate action is a fatal error. πŸ’€

By default β€” like, if no blessing was explicitly specified β€” an action is not blessed. And listen: the implicit action itself cannot be blessed, and an attempt to do so is a fatal error. Periodt.

Explicit blessings are made using the bless adverb. The bless adverb is allowed:

  • for RHS alternatives;
  • for lexemes;
  • for the default lexeme statement;
  • and for the default pseudo-rule.

An L0 RHS alternative cannot have a bless adverb. Tragic for it, honestly. πŸ™ˆ

The value of a bless adverb is called a blessing. If the blessing is a Perl word (a string of alphanumerics or underscores), then the name of the class will be formed by prepending the value of the bless_package named argument, followed by a double colon ("::").

If the blessing begins with a double colon ("::"), it's a reserved blessing. The reserved blessings are as follows πŸ‘‡

πŸŽ€ ::undef

The RHS alternatives or lexemes will not be blessed. And when this document states that a RHS alternative or lexeme has a blessing of ::undef, it means exactly the same thing as when it states that a RHS alternative or lexeme will not be blessed. For both RHS alternatives and lexemes, the implicit blessing is ::undef. (So it's the no-thoughts-just-vibes default. πŸ¦‹)

🌸 ::lhs

The RHS alternative is blessed into a class whose name is based on the LHS of the RHS alternative.

🚫 A blessing of ::lhs is not allowed for a lexeme. Facts are facts.

The class will be the name of the LHS with whitespace changed to an underscore. (As a reminder β€” and bestie, you know this β€” the whitespace in symbol names will have been normalized, with leading and trailing whitespace removed, and all other whitespace sequences changed to a single ASCII space.)

When a ::lhs blessing value applies to a rule alternative, it is a fatal error if the LHS contains anything other than alphanumerics and whitespace. In particular, the LHS cannot already contain an underscore ("_"). The ::lhs blessing is most useful in a default pseudo-rule. πŸ‘‘

πŸ¦‹ ::name

The lexeme is blessed into a class whose name is based on the name of the lexeme.

🚫 The ::name blessing is not allowed for a RHS alternative.

The class is derived from the symbol name in the same way, and subject to the same restrictions, as described above for deriving a class name from the LHS of a rule alternative. The ::name reserved blessing is most useful in the lexeme default statement. ✨


Oh, and one more thing! If any rule alternative or lexeme of a SLIF grammar has a blessing other than ::undef, a bless_package is required, and failure to specify one results in a fatal error. πŸ’€

πŸ”₯ Explicit actions πŸ”₯

There are four kinds of explicit action names:

  • Array descriptors
  • Reserved action names
  • Perl identifiers
  • Perl names

An explicit action is either a built-in action or a Perl closure action. Array descriptors and reserved action names are built-in actions. The other actions are Perl closure actions. Simple as that, iykyk.

πŸ“ Array descriptor actions

lexeme default = action => [ start, length, value ]
    bless => ::name latm => 1

If an action is enclosed in square brackets, it's an array descriptor, and the value of the lexeme or rule alternative will be an array. Inside the array descriptor is a comma separated list of zero or more array item descriptors. The array item descriptors are keywords that describe how the array is to be filled out.

If the array descriptor is an empty pair of square brackets ("[]"), then there are zero array item descriptors, and the value will be an empty array. Otherwise the array item descriptors are interpreted as lists and those lists are used to fill out the array.

  • g1len β€” The g1len array item descriptor puts a single-element list into the array. That one element will be the length of the rule or lexeme instance, in G1 locations.
  • g1length β€” THE g1length ARRAY ITEM DESCRIPTOR IS DEPRECATED. It exists for historical reasons. The g1length array item descriptor puts a single-element list into the array. That one element is g1len plus one.
  • g1start β€” The g1start array item descriptor puts a single-element list into the array. That one element will be the G1 start location of the rule or lexeme instance. Together the g1length and g1start array item descriptors describe a G1 location span.

Typical applications will prefer to use the start and length array item descriptors, which report their results in terms of physical input stream locations, instead of G1 locations. G1 locations are useful in special cases, for example with application which do not scan monotonically forward in the physical input, but instead jump backwards in it. G1 locations are described in detail in another document.

  • length β€” The length array item descriptor puts a single-element list into the array. That one element will be the length of the rule or lexeme instance. Length is in characters.
  • lhs β€” The lhs array item descriptor puts a single-element list into the array. That one element will be the LHS symbol ID of the rule. Because of historical reasons, for a lexeme instance, it will the symbol ID, but for a nulling symbol it will be a Perl undef.
  • name β€” The name array item descriptor puts a single-element list into the array. This will always be a string. For a rule whose name is defined, that one element will be the rule name. For an unnamed rule, it will be the name of the LHS symbol. For a lexeme, it will be the symbol name of the lexeme. For a nulling symbol it will be the name of that symbol.
  • rule β€” The rule array item descriptor puts a single-element list into the array. For a rule, that one element will be the rule ID. In other cases, that one element will be a Perl undef.
  • start β€” The start array item descriptor puts a single-element list into the array. That one element will be the start location of the rule or lexeme instance. The start location is an offset in the input string. The elements of the length and start item descriptors are defined such that the end location is always start location plus length.
  • symbol β€” The symbol array item descriptor puts a single-element list into the array. This will always be the name of a symbol. For a rule, it will be the name of the LHS symbol. For a lexeme, it will be the symbol name of the lexeme. For a nulling symbol it will be the name of that symbol.
  • value β€” For a rule alternative, the value array item descriptor puts a list of zero or more elements into the array. The list will contain the values of the rule instance's children, in left-to-right order.

For a lexeme, the value array item descriptor puts a single-element list into the array. That one element will be a list containing a single element, the token value of the lexeme.

  • values β€” The value and values array item descriptors are synonyms, and may be used interchangeably for both rule alternatives and lexemes.

🌈 Example

The array item descriptors fill out the array in the order in which they appear in the array descriptor. For example, if we are dealing with a rule, and the array descriptor is "[ start, length, value ]", then the return value is an reference to an array, whose length will vary, but which will contain at least two elements. The first element will be the start location in the input string of this rule instance, and the second will be its length. The remaining elements will be the values of the rule instance's RHS children, in lexical order. If the rule instance is nulled, the array will contain only two elements: start location and length. 🎯

πŸ’œ Reserved action names

If the action value begins with a double colon ("::"), it is a reserved action. The following are recognized:

  • ::array β€” ::array is equivalent to [values]. This means that, for both lexeme and rule instances, the actions [values], [value] and ::array will do exactly the same thing. Slay, honestly. πŸ’…
  • ::first β€” The value of the rule instance is that of the rule instance's first child. If there is no such child, the value is a Perl undef. It is a fatal error if a RHS alternative with a ::first action is blessed. It is also a fatal error to use a ::first action with a lexeme.
  • ::undef β€” The value of the rule or lexeme instance will be a Perl undef. It is a fatal error if a RHS alternative with an ::undef action is blessed.

⭐ Perl identifiers as action names

An action name is considered to be a Perl identifier if it is a sequence of one or more alphanumerics and underscores. If the action name is a Perl identifier, it is treated as the name of a Perl variable. To successfully resolve to actions, Perl identifiers must be resolved to Perl names, as described below. πŸ‘‡

πŸ’« Perl names as action names

For this purpose, a Perl name is a series of two or more Perl identifiers separated by double colons ("::"). Note that, by this definition, a Perl name cannot start with a double colon. Action names starting with double colons are always treated as reserved action names.

Action names which are Perl names by this definition are treated as if they were fully qualified Perl names. Fully qualified Perl names are resolved to variables in Perl's namespace, as described below. 🧚

✨ The semantics package ✨

Okay so to resolve Perl identifiers into Perl names, you NEED a semantics package defined. No package = no resolution, that's just the vibe. You can define it with the SLIF recognizer's semantics_package named argument, or it can be taken from the argument to the first value() call of the parse series. (reminder bestie: all of a recognizer's value() method calls refer to a single parse series unless the recognizer calls the series_restart() method.) And if both of those exist? The semantics_package named argument takes precedence. It wins. πŸ‘‘

If you're specifying the semantics package via the arguments to the value() method, then within a parse series they must consistently specify the same package. Same package, every time, no switching up. For the full tea, see the description of SLIF recognizer's value() method.

And if you want the Perl variables implementing the semantics to live in the main namespace? Just say main is your semantics package, easy. That's fine for small scripts and applications β€” adorable, no notes. But for a large project, it's usually good practice to keep the Perl variables meant for Marpa's semantics in their own namespace. Give them their own lil home. 🏠

πŸ”€ Resolving Perl identifiers to Perl names

A Perl identifier becomes a Perl name by prepending the semantic package, followed by a double colon ("::"). And for a Perl identifier to resolve successfully to a Perl name, a semantics package must be defined. No package, no slay.

Say the action name is "some_var" β€” boom, that gets treated as a Perl identifier. If the semantics package is "My_Actions", Marpa converts the action name to "My_Actions::some_var", and hands it on for processing as a fully qualified Perl name. That's the whole glow-up. ✨

πŸ•΅οΈ Resolving Perl names to Perl variables

Once Marpa has a fully qualified Perl name, it goes digging in Perl's symbol tables for a Perl variable with that name β€” either the name of a subroutine, or of a scalar. Important thing nobody tells you: for the purposes of Perl's symbol tables, and therefore for the purposes of Marpa's resolution of Perl names, references are scalars. Say it with me.

If Marpa finds a Perl subroutine with that fully qualified Perl name, the action name resolves to that subroutine, which then becomes a rule evaluation closure. But if there's no subroutine with that name, and there is a Perl scalar with that name? Then the action name resolves to that Perl scalar. (And again β€” for this purpose, a Perl reference is a kind of Perl scalar. We do not make the rules.) 🫠

πŸƒ Executing rule evaluation closures

A rule evaluation closure action is always called in scalar context, and its return value gets used as the value of its node. And the arguments to the rule evaluation closure are as follows:

  • If the rule instance is not nulled and the rule alternative is not blessed, the second and subsequent arguments are the values of its child nodes, in lexical order.
  • If the rule instance is nulled, there will be only one argument: the per-parse argument.
  • If the rule alternative is blessed, and the rule instance is not nulled, the closure will always have exactly two arguments. The first will be the per-parse argument, and the second will be a blessed array that contains the child values in lexical order. (The grouping of child values into an array is required in order to allow the blessing to take effect.)

And note this, because it's the one constant in this chaotic world: in every case, the first argument of a rule evaluation closure is the per-parse argument. πŸ’…

πŸ”’ Quantified rule nodes

Everything we just said about rule nodes applies to nodes for quantified rules too. But there IS a difference between quantified rules and the others, and it's a big one if you're the one writing a rule evaluation closure.

In other rules, the right hand side is fixed in length, so the number of child nodes is known in advance. Adorable. Predictable. Not the case with a quantified rule though β€” for those, your rule evaluation closure must be capable of dealing with a variable number of child nodes. Be flexible bestie, it's giving adaptability. πŸ¦‹

🎬 Action context

sub do_S {
    my ($action_object) = @_;
    my $rule_id         = $Marpa::R2::Context::rule;
    my $slg             = $Marpa::R2::Context::slg;
    my ( $lhs, @rhs ) =
        map { $slg->symbol_display_form($_) } $slg->rule_expand($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

Besides the per-parse argument and their child values, rule evaluation closures also get access to context variables. Here's the lineup:

  • $Marpa::R2::Context::slg is set to the SLIF grammar being parsed.
  • $Marpa::R2::Context::rule is the ID of the current rule alternative. Given that rule alternative ID, an application can find its LHS and RHS symbols using the SLIF grammar's rule_expand() method.
  • Marpa::R2::Context::location() returns the start and end G1 locations of the current rule instance. And heads up β€” these are G1 locations, not input stream locations. Different thing. Don't get it twisted. πŸ™ˆ

πŸšͺ 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 your "bail out" button for the evaluation of a parse tree. It will cause an exception to be thrown. And how the exception looks depends on the args: if its first and only argument is a reference, 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. We love a paper trail. πŸŽ€

🧊 Perl scalars as actions

If a Perl scalar is the action, it becomes the value of the node, as is. And remember β€” references are scalars in this context, so for example, the value of the node could be a reference to an array.

Another possibility is that the Perl scalar action is a reference to code. And what happens there is very different from when the action is a rule evaluation closure. A rule evaluation closure is executed to produce the value of the node. In contrast, the reference to a subroutine is NOT executed β€” it becomes the value of the node directly. No call, no args, just vibes and identity. πŸ’€

Assuming no trickery, like use of Perl's local keyword, takes place, resolution to a Perl scalar will always resolve to a single, global scalar. Any modification of this scalar will be seen by other nodes of the current parse, and by other parses. Which all suggests that, as a matter of good practice, Perl scalar actions should only be used as constants. Please. For me.

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

package My_Actions;
our $empty_hash = {};

It can be tempting, when building objects which are hashes, to start with a left 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. That single hash object will be shared by all nodes, with all nodes seeing each other's changes. Worse β€” all Marpa parsers which use the same My_Actions namespace will share the same hash object. Chaos. Actual shared-state chaos. 😭

The correct way to define an empty_hash action that initializes an empty hash is as a rule evaluation closure that returns {}:

sub My_Actions::empty_hash { return {}; }

πŸ‘€ Visibility of Perl object actions

Most applications do not manipulate the Perl symbol table at runtime, and do not make use of Perl's local keyword for declarations. If your app uses the Perl global namespace in conventional ways, and uses the same names to point to the same variables throughout Marpa execution, you can just... ignore all the questions about the visibility of the Perl variables used in actions. Lucky you, main character. 🌈

But less conventional applications should be aware that, for resolution from a Perl name to a Perl variable to take place, that Perl name must refer to the intended variable, and this variable must be visible, at the time when actions are resolved. And the timing of action resolution is specified in a separate document. Not here, bestie, go read that one.

🎁 The per-parse argument

The first argument of every rule evaluation closure is the per-parse argument. And this is how it gets initialized:

  • To the argument to the SLIF recognizer's value() method, if that argument is defined.
  • Otherwise, to the result returned by the per-parse constructor, if there is a per-parse constructor.
  • Otherwise, as a last resort, to an empty hashref.

The per-parse argument is destroyed once the evaluation of the parse tree is finished. But between creation and destruction, the per-parse argument is not touched by Marpa's internals β€” it is reserved for use by the application. It's yours, exclusively. 🫢

The primary way of passing data while evaluating a parse tree is purely functional β€” results from child nodes are passed up to parent nodes. Applications can use the per-parse argument for data which does not conveniently fit the functional model. Symbol tables are one common example of data that is best handled outside the functional model. Do what works.

πŸ‘Ά The per-parse constructor

The per-parse constructor is the new() method of the semantics package. If there is no semantics package, or if it has no new() method, there is no per-parse constructor. RIP. It's called with one argument: the name of the semantics package.

The per-parse constructor is called in the Parse Tree Setup Subphase. And the Parse Tree Setup Subphase occurs during the first call to a recognizer's value() method in a parse series. (Say it with me: all of a recognizer's value() method calls refer to a single parse series unless the recognizer calls the series_restart() method.) More details about the Parse Tree Setup Subphase can be found in the document that describes the processing phases of Marpa's semantics. πŸ“š

πŸ”€ 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. So if you care about the order, go get it, you have options. πŸ’…

♾️ Infinite loops

Grammars with infinite loops (cycles) are generally regarded as useless in practical applications. And due to lack of interest, the SLIF does not support them β€” although Libmarpa itself and Marpa's thin interface do. So like, technically possible elsewhere, but here? Not the move. 🚫

okay bestie, final section, and it's the one nobody reads but everyone should 🫢 so let's go, no skipping, i'm watching πŸ‘€

Copyright 2022 Jeffrey Kegler

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. fr fr, version 3 or later, your choice 🌈

Marpa::R2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. (yes that's the scary all-caps part, and yes it's real, and yes it means what it says πŸ’€ no take-backs, no "but it worked on my machine" β€” it's giving "you're on your own, bestie" 😭)

You should have received a copy of the GNU Lesser General Public License along with Marpa::R2. If not, see http://www.gnu.org/licenses/.


Copyright 2022 Jeffrey Kegler 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. Marpa::R2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with Marpa::R2. If not, see http://www.gnu.org/licenses/.

and that's a wrap on the whole page, bestie πŸŽ€ you read the entire thing, ate every single section, and honestly? main character energy πŸ”₯ go parse something iconic βœ¨πŸ’… periodt.

this page is Marpa:...antics(3pm), straight out of perl v5.42.3, dated 2026-08-20 β€” old page, timeless vibes πŸŒΈπŸ¦‹
πŸ“¦ where this page comes from