Marpa:...rogress(3pm)

๐Ÿ“ฆ
version 2.086000 ยท 4 versions
, in ubuntu jammy

โœจ NAME โœจ

Marpa::R2::NAIF::Progress โ€” progress reports for the NAIF ๐ŸŽ€

okay bestie so quick vibe check first: this page is Marpa:...rogress(3pm), straight out of the User Contributed Perl Documentation, and it's part of the Marpa::R2 family. Settle in, we're gonna have a whole journey. ๐Ÿ’…


About this document

This doc is all about Marpa's progress reports for its named argument interface (aka the NAIF). Now โ€” if you're a beginner, or you're not sure which interface you care about, or you straight up don't know what the NAIF interface even is? Girl, you probably want the doc on progress reports for the SLIF interface instead. No shame, we've all been there. ๐Ÿซถ

So what are progress reports? They let an application know exactly where it is in the parse at any point. For parse locations of the user's choosing, progress reports list all the rules in play, and they tell you the location where the rule started, plus how far into the rule parsing has progressed. Situational awareness, but make it โœจdetailedโœจ.

If you're new to parsing, that "situational awareness" thing might sound like something you'd expect from literally any servicable parser. Not me finding out that's false ๐Ÿ˜ญ. In reality, situation awareness in most parsers is extremely limited, and it tends to get even more limited in the parsers considered production quality. Marpa isn't just unusual in how much feedback it hands the application โ€” it breaks new ground. Periodt. ๐Ÿ‘‘

Progress reports are extremely useful for debugging grammars. And because debugging creates a clear "narrative", the detailed example in this document is a debugging situation. If you're specifically here to debug a grammar, bestie, go read the document on tracing problems before you read this one. Trust. ๐ŸŒธ


Introduction to Earley parsing

To actually read the show_progress output, you need a basic idea of what Earley items are and what the info inside them means. Everything you need to know is explained right here in this section. Let's get into it. ๐Ÿ’–

Dotted rules

The whole idea behind Earley's algorithm is that you can parse by building a table of rules and where you are in those rules. And "where" means two things, bestie:

  • location in the rule relative to the rule's symbols
  • location relative to the parse's input stream

Let's look at an example of a rule in a context-free grammar. Here's the rule for assignment from the Perl distribution's perly.y:

    termbinop -> term ASSIGNOP term

ASSIGNOP is perly.y's internal name for the assignment operator. In plain Perl terms, this is the "=" character.

In parsing this rule, we can be at any of four possible locations. One is at the beginning, before all of the symbols. The other three are immediately after each of the rule's three symbols.

Within a rule, the position relative to the symbols of the rule is traditionally indicated with a dot. In fact, the symbol-relative rule position is very often called the dot location. Taken as a pair, a rule and a dot location are called a dotted rule.

Here's our rule with a dot location indicated:

    termbinop -> X term ASSIGNOP term

The dot location in this dotted rule is at the beginning. A dot location at the beginning of a dotted rule means we haven't recognized any symbols in the rule yet. All we're doing is predicting that the rule will occur. A dotted rule with the dot before all of its symbols is called a prediction, or a predicted rule. ๐Ÿ”ฎ

Here's another dotted rule:

    termbinop -> term X ASSIGNOP term

In this one, we're saying we've seen a term, but we have not yet recognized an ASSIGNOP. Almost there, not quite.

And there's another special kind of dotted rule: a completion. A completion (also called a completed rule) is a dotted rule with the dot after all of the symbols. Here's the completion for the rule we've been using as our example:

    termbinop -> term ASSIGNOP term X

A completion indicates that a rule has been fully recognized. We ate. ๐Ÿ’…

Earley items

Dotted rules contain all but one piece of the information Earley's algorithm needs to track. The missing piece is the second of the two "wheres": where in the input stream.

To associate input stream location and dotted rules, Earley's algorithm uses what are now called Earley items.

A convenient way to think of an Earley item is as a triple, or 3-tuple, consisting of:

  • dotted rule
  • origin
  • current location

The origin is the location in the input stream where the dotted rule starts. The current location (also called the dot location) is the location in the input stream which corresponds to the dot position.

Two noteworthy consequences follow from the way origin and current location are defined:

  1. If a dotted rule is a prediction, then origin and current location will always be the same.
  2. The input stream location where a rule ends is not tracked unless the dotted rule is a completion. In other cases, an Earley item does not tell us if a rule will ever be completed, much less at which location. ๐Ÿ’€

The example

For this debugging example, we've taken a very common example of a grammar and deliberately introduced a problem. (All the code and the full trace outputs for this example are in the Appendix.) We've commented out the correct start rule:

        ## { lhs => 'Expression', rhs => [qw/Term/] },

and replaced it with another start rule โ€” one which will cause problems:

        { lhs => 'Expression', rhs => [qw/Factor/] },

In what follows, we're gonna pretend we don't already know where the problem is, and use the Marpa diagnostics and tracing facilities to "discover" it. Detective mode: activated. ๐Ÿ•ต๏ธโ™€๏ธโœจ


First warning

Right off the bat, we get two warning messages:

    Inaccessible symbol: Add
    Inaccessible symbol: Term

If we were alert, bestie, these would be enough to tell us there's a serious problem. "Inaccessible" symbols are symbols which cannot be reached from the start symbol. Which means the grammar will never produce them, and parses will never find them in the input. They're giving ghost town. ๐Ÿ‘ป

Since Add and Term are both important symbols in our application, that should tell us our grammar has a serious problem. In fact, these warning messages would often be enough to point us straight to the error. But โ€” to look at more of Marpa's tracing facilities โ€” let's pretend we have not had our morning coffee, and that we miss the significance of these warning messages entirely. ๐Ÿ˜ดโ˜•


Output from trace_terminals()

Before looking at Marpa's progress reports, it's usually best to orient yourself by looking at the output from trace_terminals. Typically, you'll be interested in the last tokens to be accepted, and maybe also in the tokens the recognizer was looking for when it didn't find what it wanted. Sometimes that information alone is enough to make it clear where the problem is. ๐Ÿ™ˆ

The full trace_terminals output for this example is in the Appendix. We see that the recognizer seems to accept "42*1" but it fails when it confronts the plus sign ("+"). The last two lines are:

    Accepted "Number" at 2-3
    Expecting "Multiply" at 3

A note in passing: Marpa shows the location of the tokens it accepts as a range of locations. For Number, the range is "2-3", indicating that the token starts at location 2 and ends at location 3. In its default input model, all tokens have length 1, so this is clearly information overkill. But Marpa allows other input models, and in those models the information about start and end location of the token is important. iykyk. ๐ŸŒˆ

Returning to the problem at hand: we notice that at location 3 we are expecting a Multiply operator, but not an Add operator. That should strike us as strange, and send us right back to the grammar. But for the sake of our example, we're gonna assume we're slow on the uptake today, and that this does not clue us in. We move on. ๐Ÿฆ‹

โœจ Output from show_progress() โœจ

okay bestie, Marpa's most powerful tool for debugging grammars is literally its progress report, which shows the Earley items being worked on. ๐Ÿ’… In the Appendix, progress reports for the entire parse are shown. Our example in this document is a very small one, so that producing progress reports for the entire parse is a reasonable thing to do in this case. But if a parse is at all large, you will usually need to be selective. no cap.

The progress report that is usually of most interest is the one for the Earley set that you were working on when the error occurred. This is called the current location. In our example the current location is location 3. By default, show_progress prints out only the progress reports for the current location. fr fr.

Here are the progress reports for the current location, location 3, from our example.

      F0 @0-3 Expression -> Factor .
      F2 @2-3 Factor -> Number .
      R4:1 x2 @0,2-3 Factor -> Factor . Multiply Factor
      F4 @0-3 Factor -> Factor Multiply Factor .

๐Ÿ“ Progress report lines ๐Ÿ“

The last field of each Progress Report line shows, in fully expanded form, the dotted rule we were working on. Since that is the most important information, it may be tempting to skip the rest of this section, and move directly forward with the debugging. ๐Ÿ˜ญ

In fact, you might want to do exactly that -- skip to the beginning of the next section. What follows talks about the details of the format of the first few fields in each progress report line. These first few fields, while helpful, are also usually one or more of obvious in their meaning, not relevant to our example, and repetitive of information which can be deduced from other fields. ๐Ÿ’€

      F0 @0-3 Expression -> Factor .

Prefixed to the dotted rule are two fields: "F0 @0-3". The "F0" says that this is a completed or final rule, and that it is rule number 0. The rule number is used in other tracing and debugging output, when displaying the whole rule would take too much space. In what follows we won't need the rule number. ๐ŸŽ€

The "@0-3" describes the location of the dotted rule in the parse. In its simplest form, the location field is two location numbers, separated by a hyphen. The first location number is the origin, the place where Marpa first started recognizing the rule. The last location number is the dot location, the location location of the dot in a dotted rule. "@0-3" say that this rule began at location 0, and that the dot is at location 3. โœจ

The current location is location 3, and this is no coincidence. Whenever we are displaying the progress report for an location, all the progress report lines will have their dot location at that location. ๐Ÿ’…

As an aside, notice that the left hand side symbol is "Expression". That is the start symbol. The presence of a completed start rule in our progress report indicates that if our input ended at location 3, it would be a valid sentence in the language of our grammar. ๐ŸŒธ

Let's look at another progress report line:

      R4:1 x2 @0,2-3 Factor -> Factor . Multiply Factor

Here the "R4:1" indicates that this is rule number 4 (the "R" stands for rule number) and that its dot position is after the first symbol on the right hand side. Symbol positions are numbered using the ordinal of the symbol just before the position. Symbols are numbered starting with 1, and symbol position 1 is the position immediately after symbol 1. ๐Ÿฆ‹

The next field ("x2") is new. It is a count. A progress report can contain multiple instances of the same dotted rule, and when there is more than one, a count field is included in the progress report line. Here the "x2" indicates that there are two instances of "Factor -> Factor . Multiply Factor" at this location. ๐Ÿ”ฅ

Multiple instances of a dotted rule will differ in their origin, and where they do, this is shown in the location field of the progress report line. Here the location field is "@0,2-3", which indicates that one instance of this dotted rule has its origin at location 0, and the other has its origin at location 2. All instances reported on a single progress report line will always have the same dot location, and in this case it is location 3. ๐Ÿ‘‘

Predicted rules also appear in progress reports:

      P2 @2-2 Factor -> . Number

Here the "P" in the summary field means "predicted". As with much of the information in the summary field, this only repeats what is obvious from the full expansion of the dotted rule later in the line. But final (or completed) and predicted rules can be important and the initial "F" and "P" make these lines easy to spot. ๐Ÿ’–

Notice that in the predicted rule, the origin is the same as the dot location. This will always be the case with predicted rules. ๐ŸŒˆ

For any given location, no predicted rule has more than one instance. For other dotted rules, there may be many instances of the dotted rule at a single location. In grammars with right recursion, the number of instances is limited only by the length of the recursion. The length of a recursion is limited primarily by the available memory. ๐Ÿ˜ญ

When there are many instances of a dotted rule at a single location, it is inconvenient to show all the origins in a comma-separated list. In that case the origins in the location field are shown as a range, with the earliest separated from the most recent by a "...". The example in this document contains no lines with a large number of instances, but here is an example from another grammar. This is the progress report line for the completed rule in a right recursion of length 20. โญ

      F1 x20 @0...19-20 Top_sequence -> Top Top_sequence .

๐Ÿ˜ค OK! Now to find the bug ๐Ÿ˜ค

Here again are progress reports at the location where things went wrong:

      F0 @0-3 Expression -> Factor .
      F2 @2-3 Factor -> Number .
      R4:1 x2 @0,2-3 Factor -> Factor . Multiply Factor
      F4 @0-3 Factor -> Factor Multiply Factor .

We see that we have completed rules for "Expression", and "Factor", as expected. We also see two Earley items that show that we are in the process of building another "Factor", and that it is expecting a "Multiply" symbol. This is not the rule we want, but it explains why the "trace_terminals" output showed that the recognizer was expecting a "Multiply" symbol. ๐Ÿ’€

What we want to know is, why is the recognizer not expecting an "Add" symbol? Looking back at the grammar, we see that only one rule uses the "Add" symbol: the rule "Term -> Term Add Term". The next step is to look at the Earley items for this rule. But there is a problem. We don't find any. ๐Ÿ˜ญ

Next, we ask ourselves, what is the earliest place the "Term -> Term Add Term" rule should be appearing? The answer is that there should be a prediction of "Term -> Term Add Term" at location 0. So we look at the predictions at location 0. ๐Ÿ”

      P0 @0-0 Expression -> . Factor
      P2 @0-0 Factor -> . Number
      P4 @0-0 Factor -> . Factor Multiply Factor

No "Term -> Term Add Term" rule. We are never even predicting a "Term -> Term Add Term" rule. We look back at the grammar, and start from the beginning. ๐Ÿ˜ค

           { lhs     => 'Expression', rhs => [qw/Factor/] },
           { 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'
           },

The start symbol is "Expression" and we do see a rule with "Expression" on the left hand side. "Expression" in turn produces a "Factor" symbol, and there are two rules with "Factor" on the left hand side. ๐ŸŽ€

But none of these rules ever produce a "Term". In fact, however far we follow the productions, no rule ever produces a "Term". At this point we see the problem: If we start at the start symbol, and follow the rules of our grammar, we will never get to a "Term" symbol. Which is exactly what that first warning message was saying. ๐Ÿคฏ

Now that we know what is wrong, we can reread our grammar, and see that our "Expression -> Factor" rule is wrong. It should be "Expression -> Term". Change that and the problem is fixed. slay! ๐Ÿ’…

๐Ÿƒ Empty rules ๐Ÿƒ

When a symbol is nulled in your parse, show_progress does not show its expansion into rules. This reduces clutter, and seems to be the intuitive way to treat nulled rules. ๐Ÿงš

๐Ÿ’– Access to the "raw" progress report information ๐Ÿ’–

Okay bestie, this part is for the extra girlies. This section is all about the progress() recognizer method, which hands you the raw, unfiltered, no-makeup progress report info. ๐ŸŽ€

Real talk: you do not need this for your normal everyday debugging and tracing. This is for the apps that wanna flex Marpa's "situational awareness" in creative, unhinged, innovative ways. iykyk. โœจ


โœจ progress() โœจ

my $report0 = $recce->progress(0);

my $latest_report = $recce->progress();

So here's the tea: you hand progress() a parse location (that's the Earley set ID, bestie) as its argument, and the progress() recognizer method gives you back a reference to an array of "report items". ๐Ÿ’…

And get this โ€” the parse location can be negative. An argument of -X gets interpreted as location N+X+1, where N is the latest Earley set. So:

  • -1 โ†’ the latest Earley set
  • -2 โ†’ the Earley set just before the latest one
  • and so on, you get the vibe ๐Ÿฆ‹

Each report item is a triple: an array of three elements. In order, they are: rule ID, dot position, and origin. The data from the two displays above, plus the data for all the other parse locations in our example, is shown below. ๐Ÿ‘‘

The rule ID is the exact same number Marpa uses to identify rules in tracing and debugging output. Once you've got a rule ID, your app can go find its LHS and RHS symbols using the grammar's rule() method. โœจ

Dot position is -1 for completions, and 0 for predictions. And if the report item isn't a completion or a prediction? Then dot position is N, where N is the number of RHS symbols successfully recognized at the parse location of the progress report. Slay. ๐Ÿ’–

Origin is the parse location (Earley set ID) where the rule application reported by the report item began. Fun fact: for a prediction, the origin will always be the same as the location of the parse report. ๐Ÿซถ


๐ŸŒธ Progress reports and efficiency ๐ŸŒธ

Okay so when progress reports get used for production parsing โ€” not just for debugging and tracing โ€” efficiency starts to matter. Let's do the good news first, because we're positive here. ๐Ÿ’–

The good news: progress() is implemented in C. So your application can usually expect calls to progress() to be extremely fast. Periodt. โšก

The potential bad news: if your app plans to call progress() a lot, you need to know that where there's a right recursion at a parse location, progress() has to follow that entire chain of recursions to build the progress report. ๐Ÿ˜ญ

How will you know this is happening? It'll always be obvious from the parse report itself โ€” it'll contain one report item for every completion in the right-recursive chain of completions. So if an app tries to produce progress reports for a large number of parse locations, and those parse locations have long right-recursive chains... it can get computationally expensive. Not the vibe, but that's the facts. ๐Ÿ’€

Good news again though โ€” this efficiency thing about following right recursions? Not an issue for left recursions. Left recursions only produce at most two report items per parse location, and they're extremely fast to process. And it's not an issue for Marpa's sequence rules either, because sequence rules are implemented internally as left recursions. Iconic. ๐Ÿ‘‘


๐Ÿ“š Appendix: full code and output for the example ๐Ÿ“š

Below is the code, the trace outputs, and the progress report for the example we've been using this whole doc. ๐Ÿ’œ


๐Ÿงš Code ๐Ÿงš

my $grammar = Marpa::R2::Grammar->new(
    {   start          => 'Expression',
        actions        => 'My_Actions',
        default_action => 'first_arg',
        rules          => [
            ## This is a deliberate error in the grammar
            ## The next line should be:
            ## { lhs => 'Expression', rhs => [qw/Term/] },
            ## I have changed the Term to 'Factor' which
            ## will cause problems.
            { lhs => 'Expression', rhs => [qw/Factor/] },
            { 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'
            },
        ],
    }
);

$grammar->precompute();

my @tokens = (
    [ 'Number',   42 ],
    [ 'Multiply', q{*} ],
    [ 'Number',   1 ],
    [ 'Add',      q{+} ],
    [ 'Number',   7 ],
);

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 $recce = Marpa::R2::Recognizer->new(
    { grammar => $grammar, trace_terminals => 2 } );

my $token_ix = 0;

TOKEN: for my $token_and_value (@tokens) {
    last TOKEN if not defined $recce->read( @{$token_and_value} );
}

$progress_report = $recce->show_progress( 0, -1 );

๐Ÿฆ‹ Trace output ๐Ÿฆ‹

Inaccessible symbol: Add
Inaccessible symbol: Term
Setting trace_terminals option
Expecting "Number" at earleme 0
Accepted "Number" at 0-1
Expecting "Multiply" at 1
Accepted "Multiply" at 1-2
Expecting "Number" at 2
Accepted "Number" at 2-3
Expecting "Multiply" at 3
Rejected "Add" at 3-4

Quick note bestie: see the term "earleme"? If you're using the default input model, you can just assume earleme means "location" โ€” the earleme and the location will always be exactly the same. ๐Ÿซถ But advanced users running alternative input models may set it up so that earleme and location are two different things, and in that case, the distinction actually matters. ๐Ÿ’…


๐ŸŽ€ show_progress() output ๐ŸŽ€

P0 @0-0 Expression -> . Factor
P2 @0-0 Factor -> . Number
P4 @0-0 Factor -> . Factor Multiply Factor
F0 @0-1 Expression -> Factor .
F2 @0-1 Factor -> Number .
R4:1 @0-1 Factor -> Factor . Multiply Factor
P2 @2-2 Factor -> . Number
P4 @2-2 Factor -> . Factor Multiply Factor
R4:2 @0-2 Factor -> Factor Multiply . Factor
F0 @0-3 Expression -> Factor .
F2 @2-3 Factor -> Number .
R4:1 x2 @0,2-3 Factor -> Factor . Multiply Factor
F4 @0-3 Factor -> Factor Multiply Factor .

๐Ÿ’œ progress() outputs ๐Ÿ’œ

This section contains the output of the progress() method โ€” the progress reports in their "raw" format. The output is shown in Data::Dumper format, with Data::Dumper::Indent set to 0 and Data::Dumper::Terse set to 1. โœจ

The Data::Dumper output from progress() at location 0:

[[0,0,0],[2,0,0],[4,0,0]]

The Data::Dumper output from progress() at location 1:

[[0,-1,0],[2,-1,0],[4,1,0]]

The Data::Dumper output from progress() at location 2:

[[2,0,2],[4,0,2],[4,2,0]]

And the default progress() output is for the latest Earley set, which is location 3 in our example. Here's the progress() output for location 3:

[[0,-1,0],[2,-1,2],[4,-1,0],[4,1,0],[4,1,2]]

Copyright 2014 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/.

That's a wrap on the whole page, bestie! ๐ŸŽ‰ You just read the Marpa:...rogress(3pm) page from the User Contributed Perl Documentation, generated by perl v5.34.0 and dated 2022-02-06. Go forth and parse, you absolute legend. ๐Ÿ’–โœจ๐Ÿฆ‹
๐Ÿ“ฆ where this page comes from