Marpa:...::Null(3pm)

πŸ“¦
version 12.000000 Β· 2 versions
, in ubuntu stonking

πŸ’– Marpa::R2::Deprecated::NAIF::Semantics::Null πŸ’–

okay so bestie, this is the User Contributed Perl Documentation page for Marpa::R2::Deprecated::NAIF::Semantics::Null β€” it's the one that explains how the NAIF evaluates null rules and symbols. let's get into it πŸ¦‹


✨ NAME ✨

Marpa::R2::Deprecated::NAIF::Semantics::Null β€” How the NAIF evaluates null rules and symbols 😌


🚨 THE NAIF INTERFACE IS DEPRECATED 🚨

bestie I need you to hear me on this one. This document describes the NAIF interface, which is deprecated. PLEASE DO NOT USE IT FOR NEW DEVELOPMENT. like. at all. it's giving legacy. it's giving "we keep it here for the archives". don't build new stuff on it fr fr πŸ’€

🌸 Overview 🌸

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 actually want, or you literally don't know what the Named Argment InterFace (NAIF) even is β€” you probably want to go look at the document on semantics for the SLIF interface instead. that one's the one you want, trust πŸ‘‘

Now. In Marpa parses, rules and symbols can be nulled β€” meaning they can derive the zero-length, or null, string. Which symbols can be nulled, or are nulled, depends on the grammar and the input. And when a symbol or rule is not nulled, we say that symbol is visible. πŸ‘€

Even the start symbol can be nulled, and if that happens, the entire parse derives the null string. A parse where the start symbol is nulled is called a null parse. iconic honestly.

When you're evaluating a parse, nulled rules and symbols get assigned values the way the semantics document says. And this document is the extra detail on assigning values to nulled symbols. πŸ’…


πŸŽ€ Description πŸŽ€

πŸ’œ Null values come from rules πŸ’œ

Every single null value for a symbol comes from rules that have that symbol on their LHS. For a symbol to be nulled, it has to be on the LHS of at least one nullable rule. And the action of one of those nullable rules is what becomes the action for the nulled symbol.

  • If the action is a constant? Then that constant is the value of the nulled symbol. easy.
  • If the action is a rule evaluation closure? Then that closure gets called with no child arguments, and whatever the closure returns is the value of the nulled symbol. periodt.

So here's the tea though: it's possible that more than one nullable rule has that symbol on its LHS, and that those rules have different action names. When that happens, the action for the empty rule with that LHS is the one that applies. And heads up β€”

⚠️ it is a fatal error if the nullable rules for a LHS symbol have different action names, and there is no empty rule for that LHS symbol. 😭

A simple way to fix that problem is to just create such an empty rule. slay, problem solved 🫢

πŸ¦‹ Null subtrees πŸ¦‹

A null subtree is a subtree where literally all of its symbols and rules are nulled. Marpa prunes all null subtrees back to their topmost nulled symbol. βœ‚οΈ

The "lost" semantics of the non-topmost symbols and rules of null subtrees is usually not missed, ngl. Nulled subtrees can't contain input, so they don't contain token symbols β€” which means no token values are lost when nulled subtrees get pruned. As bushy as a null subtree might be, every one of its symbols and rules is nulled. it's giving ✨nothing✨

And since nulled symbols and rules correspond to zero-length strings, we are literally dealing here with the "semantics of nothing". In theory, the semantics of nothing can be arbitrarily complex. In practice, it should be possible to keep them simple. πŸ’…


🌈 Example 🌈

Like I said: Marpa prunes every null subtree back to its topmost null symbol. Here's an example:

sub do_L {
    shift;
    return 'L(' . ( join q{;}, map { $_ // '[ERROR!]' } @_ ) . ')';
}

sub do_R {
    return 'R(): I will never be called';
}

sub do_S {
    shift;
    return 'S(' . ( join q{;}, map { $_ // '[ERROR!]' } @_ ) . ')';
}

sub do_X { return 'X(' . $_[1] . ')'; }
sub do_Y { return 'Y(' . $_[1] . ')'; }

## no critic (Variables::ProhibitPackageVars)
our $null_A = 'null A';
our $null_B = 'null B';
our $null_L = 'null L';
our $null_R = 'null R';
our $null_X = 'null X';
our $null_Y = 'null Y';
## use critic

my $grammar = Marpa::R2::Grammar->new(
    {   start   => 'S',
        actions => 'main',
        rules   => [
            [ 'S', [qw/L R/],   'do_S' ],
            [ 'L', [qw/A B X/], 'do_L' ],
            [ 'L', [], 'null_L' ],
            [ 'R', [qw/A B Y/], 'do_R' ],
            [ 'R', [], 'null_R' ],
            [ 'A', [], 'null_A' ],
            [ 'B', [], 'null_B' ],
            [ 'X', [], 'null_X' ],
            [ 'X', [qw/x/], 'do_X' ],
            [ 'Y', [], 'null_Y' ],
            [ 'Y', [qw/y/], 'do_Y' ],
        ],
    }
);

$grammar->precompute();

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

$recce->read( 'x', 'x' );

If we write the unpruned parse tree in pre-order, depth-first, indenting children below their parents, we get something like this:

        0: Visible Rule: S := L R
             1: Visible Rule L := A B X
                 1.1: Nulled Symbol A
                 1.2: Nulled Symbol B
                 1.3: Token, Value is 'x'
             2: Nulled Rule, Rule R := A B Y
                 2.1: Nulled Symbol A
                 2.2: Nulled Symbol B
                 2.3: Nulled Symbol Y

In this example, five symbols and a rule are nulled. The rule and three of the symbols are all in a single subtree: 2, 2.1, 2.2 and 2.3. Marpa prunes every null subtree back to its topmost symbol, which in this case is the LHS of the rule numbered 2. βœ‚οΈβœ¨

The pruned tree looks like this:

        0: Visible Rule: S := L R
             1: Visible Rule L := A B X
                 1.1: Nulled Symbol A
                 1.2: Nulled Symbol B
                 1.3: Token, Value is 'x'
             2: LHS of Nulled Rule, Symbol R

And here's the output:

S(L(null A;null B;X(x));null R)

In that output we see:

  • The null value for symbol 1.1: "null A". This comes from the empty rule for A. πŸ’–
  • The null value for symbol 1.2: "null B". This comes from the empty rule for B. πŸ’–
  • The token value for symbol 1.3: "x". πŸ”₯
  • An application of the semantic Perl closure for the rule L := A B X. πŸ’…
  • The null value for rule 2: "null R". This comes from the empty rule for R. πŸ’–
  • An application of the semantic Perl closure for the rule S := L R. πŸ’…

And what we do not see, bestie:

We don't see any output for symbols 2.1 (A), 2.2 (B), or 2.3 (Y) β€” because they were not topmost in the pruned subtree. πŸ™ˆ And we don't see an application of the rule evaluation closure for rule R := A B Y β€” because there is an empty rule for R, and that takes priority. πŸ‘‘


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 the whole page, bestie 🫢 this one shipped in perl v5.42.3 on 2026-08-20. go forth, prune those null subtrees, stay hydrated, and remember: NAIF is deprecated, so don't be out here starting new projects on it πŸ’œβœ¨

πŸ“¦ where this page comes from