Marpa:...orial2(3pm)

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

πŸ’– Marpa::R2::Tutorial2 πŸ’–

okay bestie, so we're in the Marpa:...orial2(3pm) zone β€” that's the manual for this Perl thing, filed under User Contributed Perl Documentation (iykyk, it's the community shelf, not the official one). grab a snack, we're doing the second tutorial and it's honestly kind of a glow-up. 🌸


✨ NAME ✨

Marpa::R2::Tutorial2 β€” literally just Marpa Tutorial 2. that's the whole name, no notes. πŸ’…


πŸ¦‹ SYNOPSIS πŸ¦‹

here's the whole vibe, in code, verbatim, don't touch a single character:

use Marpa::R2;

my $dsl = <<'END_OF_DSL';
:default ::= action => [name,values]
lexeme default = latm => 1
Calculator ::= Expression action => ::first

Factor ::= Number action => ::first
Term ::=
    Term '*' Factor action => do_multiply
    | Factor action => ::first
Expression ::=
    Expression '+' Term action => do_add
    | Term action => ::first
Number ~ digits
digits ~ [\d]+
:discard ~ whitespace
whitespace ~ [\s]+
END_OF_DSL

my $grammar = Marpa::R2::Scanless::G->new( { source => \$dsl } );
my $recce = Marpa::R2::Scanless::R->new(
    { grammar => $grammar, semantics_package => 'My_Actions' } );
my $input = '42 * 1 + 7';
my $length_read = $recce->read( \$input );

die "Read ended after $length_read of ", length $input, " characters"
    if $length_read != length $input;

if ( my $ambiguous_status = $recce->ambiguous() ) {
    chomp $ambiguous_status;
    die "Parse is ambiguous\n", $ambiguous_status;
}

my $value_ref = $recce->value;
my $value = ${$value_ref};

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;
}

πŸ‘‘ DESCRIPTION πŸ‘‘

🌈 Overview 🌈

so this doc is the second tutorial of the Scanless interface (aka SLIF), and it's giving... lower level. like, we're going one floor down into the method calls. πŸ”Š

and that's a good thing!! because those lower level calls are how you get at more of Marpa's features. fr fr, you're gonna need the lower-level calls:

  • to use SLIF parse events;
  • to examine the values of multiple parses of an ambiguous parse;
  • and to get finer control of the response to Marpa errors.

this whole thing uses the same extremely simple calculator as the tutorial in the landing page. most of the code is exactly the same, actually, so we're skipping it. here's what's new, bestie:

πŸ’œ Marpa::R2::Scanless::R::new πŸ’œ

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

Marpa::R2::Scanless::R::new creates a brand new SLIF recognizer. its arguments are references to hashes of named arguments β€” very "here's a grab bag of labeled stuff" energy. πŸŽ€

in this example, the first named argument is the required one: "grammar". and the value of that "grammar" named argument has got to be a Marpa::R2 SLIF grammar. non-negotiable.

the second argument is optional, but bestie, you will use it constantly. the "semantics_package" named argument tells Marpa which Perl package to go look in for the closures that implement the semantics for this grammar. it's the "where do I find your functions" flag. 🫢

🩷 Marpa::R2::Scanless::R::read 🩷

my $input = '42 * 1 + 7';
my $length_read = $recce->read( \$input );

to parse a string, we hit up the Marpa::R2::Scanless::R::read() method. in its simplest form β€” which is exactly what's happening here β€” Marpa::R2::Scanless::R::read() takes one argument: a reference to a string holding the input stream. that's it. that's the whole call. iconic. ✨

🌸 Checking for a premature end 🌸

die "Read ended after $length_read of ", length $input, " characters"
    if $length_read != length $input;

most premature endings happen when a parse is exhausted. and a parse is "exhausted" when there is no possible way for it to continue on to success β€” like, it's over, there's no path forward. πŸ’€

that kind of premature parse exhaustion gets thrown as a failure by the Marpa::R2::Scanless::R::read() method. so you do not have to check for it explicitly. it'll yell at you on its own.

but β€” and here's the plot twist β€” there are other premature endings that aren't necessarily failures, and which therefore are not thrown. those are Marpa's SLIF parse events. SLIF parse events are an advanced feature, and they're described elsewhere (not here, go look them up, bestie). in this example, any premature ending caused by the triggering of a SLIF parse event would be caused by stray parse events β€” which is to say, unexpected parse events that happen because you made a mistake writing the DSL. πŸ™ˆ

programming a stray parse event is a programming mistake you're not likely to make, so arguably this check isn't really necessary. there are no SLIF parse events, stray or otherwise, in this example. but the check is included for completeness, and as an example of a cautious programming style. it's the seatbelt you wear even though you're not planning to crash. πŸŽ€

⭐ Checking for an ambiguous parse ⭐

if ( my $ambiguous_status = $recce->ambiguous() ) {
    chomp $ambiguous_status;
    die "Parse is ambiguous\n", $ambiguous_status;
}

way more likely than stray SLIF parse events are ambiguous parses β€” parses where the input can be parsed in two or more ways. 😭

ambiguous parses are not necessarily a problem β€” some applications genuinely do not care about them. other applications, as an advanced technique, actually exploit ambiguity, which is main character energy honestly.

but beginners should regard an ambiguous parse as a sign of trouble. 🚨 the existence of ambiguous parses should be tolerated only if you understand the kind of ambiguity that exists in your grammar, and only if you know that ambiguities of that kind will not cause trouble. read that again. it's the whole rule.

the code above uses the recognizer's ambigous() method to spot ambiguous parses. (yes, that's spelled that way in the source, we're not fixing it, moving on πŸ’…)

here's what ambigous() hands back:

  • if there was exactly one parse, it returns the empty string.
  • if there was no parse, it returns a string stating that.
  • if there were two or more parses, it returns a string describing the ambiguity.

🧚 Marpa::R2::Scanless::R::value 🧚

my $value_ref = $recce->value;
my $value = ${$value_ref};

the Marpa::R2::Scanless::R::value() method returns a reference to the parse result's value, if there was a parse result. and if there was no parse result? it returns "undef". simple as that. 🌈

this code does not check for the case where there is no parse result, because we already did that check β€” back when we checked for ambiguity. two birds, one vibe check. πŸ¦‹

and the value of the parse is exactly the same, and computed in exactly the same way, as in the previous tutorial. no new math, promise. πŸ“


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

okay bestie real talk β€” that "WITHOUT ANY WARRANTY" part is not a vibe, it is a legal fact, so don't skim it. πŸ“œ

and that's the whole page!! πŸ’– this one came from perl v5.42.3, dated 2026-08-20 β€” so when future-you is reading this in the archives, hi, hope the grammar parsed clean. no cap, you ate. periodt. πŸ‘‘βœ¨

πŸ“¦ where this page comes from