Marpa:...d::Thin(3pm)

πŸ‘‘ NAME πŸ‘‘

Marpa::R2::Advanced::Thin β€” direct access to Libmarpa, bestie. That's the whole vibe. ✨

(this is part 1 of 6, so no, we are not done yet β€” grab a snack πŸ“)


πŸ’– About this document πŸ’–

Okay so first thing: most Marpa users can straight-up ignore this document. It is not for you, babe, and that's fine. It describes Marpa's "thin" interface, which is the efficient-access door into Marpa's core library, Libmarpa.

The thin interface is very low-level and is genuinely not designed to be convenient. Nobody promised comfy. User-friendliness is supposed to come from an upper layer sitting on top. So who's the thin interface actually for? For writing those upper layers. 🌈

And Libmarpa itself is also meant for writing applications β€” specifically when the programmer wants to eliminate the overhead of an upper layer, or wants the flexibility that comes from touching Libmarpa directly, and is willing to go to some extra effort. That's the trade. iykyk. πŸ’…


πŸŽ€ How this document is written πŸŽ€

Careful, this one's a prerequisite check πŸ’€ β€” it assumes you're already familiar with the rest of the Marpa::R2 documentation, and very familiar with the Libmarpa API document. Most of the terminology here is adopted from the Libmarpa API document. You're also gonna need some C in your brain β€” at minimum enough to follow a discussion written in terms of C functions, their parameters and return values, and C language macros.

This document avoids duplicating the material in the Libmarpa API document. Why? Because most Marpa thin interface functions correspond directly to a Libmarpa method, and their implementation follows a "general pattern". And this document describes that general pattern. That's the whole trick. 🧚

So here's how to read it:

  • Methods that follow the general pattern are usually not mentioned specifically. Assume they're there.
  • Methods that are exceptions to the general pattern are always mentioned.
  • Behaviors that deviate from the general pattern are always described in detail.
  • This document also points out those Libmarpa methods to which no Marpa thin interface method directly corresponds.

Ngl, this style of documentation is very efficient β€” which is literally one reason it's the standard for C library interfaces to Perl. Admittedly though? It is also very terse. 😭 So as a little aid, there's an example script using the Marpa thin interface presented below. It's small, but it is not trivial. And it's also full, in the sense that it contains a complete logic flow β€” starting with the definition of the grammar and going all the way through to the iteration of the values of an ambiguous parse. ate. πŸ‘‘


✨ Methods in the thin interface ✨

The thin interface has a few methods that are all its own, sure β€” but most of its methods are just wrappers for a method from the Libmarpa interface. Meanwhile, lots of Libmarpa methods have no thin interface wrapper at all. And heads up: no internal Libmarpa method is part of the Marpa thin interface. Periodt.

Also, many of the external Libmarpa methods get omitted because the thin interface itself performs their function. Case in point:

  • Nothing corresponds to the marpa_check_version() static method β€” because the Marpa thin interface handles its own version matching. 🫢
  • Nothing corresponds to any of the Libmarpa configuration class methods. No Marpa thin interface object corresponds to Libmarpa's configuration objects, either β€” configuration in the Marpa thin interface is done using Perl variables.
  • Nothing corresponds to marpa_g_ref() and marpa_g_unref(), because the thin interface handles the reference counting of the Libmarpa objects it creates. The application can just rely on Libmarpa objects being cleaned up properly as part of Perl's usual garbage collection. ♻️ And for the same exact reason, no thin interface method corresponds to the "ref" and "unref" methods of the other Libmarpa time classes.
So whenever an external Libmarpa method is not mentioned in this document, you can assume it has a wrapper implemented according to the general pattern described below. Where the implementation of an external Libmarpa method is an exception to the general pattern, its implementation will be explicitly described, and the corresponding Marpa thin interface method gets its own section spelling out exactly how it differs. no surprises πŸ’–

πŸ¦‹ Libmarpa time objects and constructors πŸ¦‹

Little reminder bestie, Libmarpa's major classes go in sequence: configuration, grammar, recognizer, bocage, ordering, tree and value. Their one-letter abbreviations are, respectively, "c", "g", "r", "b", "o", "t" and "v". ⭐

    Marpa_Config        Marpa::R2::Thin::C
    Marpa_Grammar       Marpa::R2::Thin::G
    Marpa_Recognizer    Marpa::R2::Thin::R
    Marpa_Bocage        Marpa::R2::Thin::B
    Marpa_Ordering      Marpa::R2::Thin::O
    Marpa_Tree          Marpa::R2::Thin::T
    Marpa_Value         Marpa::R2::Thin::V

The thin interface implements a Perl class for each of the Libmarpa time classes. What it does not implement is a Perl class corresponding to Libmarpa's configuration class. πŸ™ˆ

Now, real talk about how you're allowed to treat these objects:

  • Objects in the thin Marpa classes should be treated as opaque scalars.
  • Applications must not define new methods, constants or variables in a thin Marpa class.
  • Similarly, applications must not redefine, overload or remove existing elements.
  • Thin Marpa classes must not be subclassed. 🚫
  • Applications should restrict their operation on objects in the Marpa thin classes to: assignments; calling methods of the class; and β€” in those cases where this document states that it's appropriate β€” passing them as arguments.

Constructors for the time objects may be called using the "new" method of the corresponding Perl class. Like this, for example:

    my $recce = Marpa::R2::Thin::R->new($grammar);

And when you're done with them? Perl applications can destroy objects of the Marpa thin classes by undefining them, or by just letting them go out of scope. ✌️ Application programmers do not need to concern themselves with the reference counting of Libmarpa objects β€” the Marpa thin interface handles this for you. πŸ’–


πŸ’… The throw setting πŸ’…

This is one of the most important jobs the Marpa thin interface has: adapting Libmarpa's error reporting to Perl. And Perl allows two kinds of error reporting:

  1. Perl methods can communicate failure via their return values.
  2. They can throw exceptions.

Each one has its place. Throwing failure as an exception is the default in the Marpa thin interface, because it's safer, and because it's convenient for prototyping and for the first cuts at a new application. On the other hand β€” and this is the key part β€” for Marpa thin applications to have access to the full flexibility and power of Libmarpa, they must be able to set the Marpa thin interface to return failure instead of throwing failure as an exception. πŸ”₯

So here's the rule. In choosing whether or not to throw a failure as an exception, almost every Marpa thin method obeys the throw setting:

  • If the throw setting is 1, failure is thrown.
  • If the throw setting is 0, failure is returned.

The throw setting is 1 in newly created grammar objects. Once created, the throw setting of a grammar object can be changed using the throw_set() method. The throw setting of other objects is that of their base grammar.

Nearly every Marpa thin method always obeys the throw setting. Only three methods never obey it. They're exceptions because their logic controls the throw setting itself β€” so a problem with them suggests the throw setting itself may not be correct. 😭 The methods which never obey the throw setting are:

  • "Marpa::R2::Thin::G->new()"
  • throw_set()
  • ruby_slippers_set()

These methods throw all failures. No exceptions. (Well. They are the exceptions. You get it. πŸ’€)

And then there's one method that allows the throw setting to be overriden. πŸ‘€ For convenience in using the Ruby Slippers technique, the behavior of the alternative() method is also affected by a separate "Ruby Slippers" flag. For details, see the description of that method.

πŸ’– Error codes, names and descriptions πŸ’–

Okay bestie, so errors in the Marpa thin interface roll in from two places: Libmarpa, and the Marpa thin interface itself. We're gonna call these Libmarpa errors and thin interface errors, respectively, no cap.

Internally, Marpa keeps two variables around to track what just went wrong. Those are the error code and the error description. And for every defined error code, there's an error name. Put the error code, the error name and the error descriptions together and you've got what this doc calls the "error variables". ✨

When the most recent error was a Libmarpa error, the error code is the Libmarpa error code, exactly like the Libmarpa API document describes it. A Libmarpa error code is a non-negative integer. But when the most recent error was a thin interface error, the error code is a Perl undef. That's it, that's the whole vibe check. πŸ™ˆ

Libmarpa's integral error codes are rarely used directly, whether you're in C or in Perl. In C, the error codes get referred to using macros. Those macro names are available through the thin interface as error names. (Details over in the section on error methods, iykyk.) Thin interface errors do not have error names, periodt.

And on top of error names, there are also error descriptions.

  • Error names are short mnemonics. Error descriptions are typically longer. πŸ“
  • Error names and error codes have a one to one correspondence (a bijection, if you wanna be fancy). For a given error code, the error name will always be the same, and vice versa. Error descriptions may contain text relating, not just to the error code, but to the specific error instance.
  • The error name is defined if and only if the error code is defined. Error descriptions always exist, whether or not there's an error code defined.
  • A thin interface error will always have an error description. A thin interface error will never have an error name. πŸ’…
  • The programmer may expect error codes and error names to remain stable, and may write code that relies on the numeric value of the error codes and the text of the error name. Applications should treat the text of an error description as suitable for the purpose of passing it on to a human user, and should otherwise regard it as opaque.

Error descriptions, while typically longer than error names, are intended for situations where it's most convenient if they fit into a single line, or at most two. The Libmarpa API document contains a section on the Libmarpa error codes, and over there the descriptions are often longer and more detailed. πŸ“

Error codes and error descriptions should be considered valid only if the most recently called Marpa thin method indicated that it set the error code. An application should assume that the error codes and error descriptions will be overwritten by the next call to any thin interface method other than the error() method. 😭 So grab 'em while they're fresh!

✨ Failure and the error variables ✨

A method indicates failure either by throwing an exception or by returning a value that indicates failure. If a method follows the general pattern, it indicates failure if and only if its return value is less than or equal to -2. Other methods indicate failure as stated in their descriptions in this document. 🫢

Whenever a method indicates failure, that also indicates that it has set the error variables. On Libmarpa failures, the error code is set to the Libmarpa error code. On thin interface failure, the error code is set to a Perl undef. For both Libmarpa and thin interface failures, the error description is set to a text that describes the error. 😌


🌸 Success and the error variables 🌸

On success, a method will take one of the following three actions with respect to the error variables:

Reset the error code : A successful method may set the error code to MARPA_ERR_NONE, together with an error description that indicates there is no error. πŸŽ€

Leave the error code as is : A successful method may leave the error code and error description as is. shrug πŸ’

Set an informational error code : A successful method may set a Libmarpa error code to a value other than MARPA_ERR_NONE. Error codes of this kind are called informational error codes. The phrase "error code" in this context is something of a misnomer. Informational error codes exist as a convenience for some applications, but typically are ignored.

The Libmarpa API document sometimes specifies what a Libmarpa method does with the error code on success. The Libmarpa API document always specifies if and when a method sets an informational error code. If the Libmarpa API document is silent, the application should regard it as unspecified whether the error code is reset or left as is. πŸ¦‹


πŸ‘‘ The general pattern πŸ‘‘

Most Marpa thin interface methods correspond directly to a Libmarpa method, and their behaviors are in most cases exactly the same. These behaviors are called the "general pattern" in this document. To avoid repetition, Marpa thin interface methods that follow the general pattern exactly are usually not described explicitly in this document. So if you're looking for an option list that isn't there β€” that's why, bestie. πŸ’–

πŸŽ€ Method names

The name of a general pattern method is that of its corresponding Libmarpa method, minus the 8-letter prefix which indicates its Libmarpa class. For example, the Marpa thin interface method that corresponds to marpa_g_start_symbol_set is $g->start_symbol_set(). The class of the general pattern method will be the Marpa thin class corresponding to the time class of the Libmarpa method. For example, $g->start_symbol_set() will be in the Marpa::R2::Thin:G Perl class. Slay. πŸ’…

πŸ¦‹ Arguments

Libmarpa's class instance methods take an object of their class as their first ("self") argument. Zero or more non-self arguments follow the self argument. The arguments of the corresponding general pattern Marpa thin method will be the same, converted from C types to Perl as described next. (This discussion follows the convention used in perlobj, and considers the "self" object to be a Perl method's first argument.)

In the general pattern, every argument whose type is one of Libmarpa's time classes is converted to the corresponding Marpa thin interface class. Arguments which belong to Libmarpa's numbered classes (Marpa_Earley_Set_ID, Marpa_Rank, Marpa_Rule_ID and Marpa_Symbol_ID) are converted to Perl scalar integers. C language int's are also converted to Perl scalar integers.

The Marpa thin interface does not recognize booleans, either in C or in Perl. For example, if a Perl true value is not a numeric 1, it will not be converted to a numeric 1 in C, even in a situation where the Libmarpa method is clearly looking for a boolean. The intent is to allow for future extensions to the Libmarpa interface that accept and interpret other numeric values. πŸ”₯

πŸ’œ Return values

In the general pattern, the return value from a Libmarpa method will always either belong to one of Libmarpa's numbered classes, or be a C language int. If the Libmarpa return value is a non-negative integer, the corresponding general pattern Marpa thin method will return a numeric Perl scalar. If the Libmarpa method returns -1, its corresponding general pattern Marpa thin method will return a Perl undef. 🫢

⭐ General pattern failures

General pattern methods consider failure to be a Libmarpa return value of -2 or less. Failure is thrown if the throw setting is 1. On unthrown failure, the return value of the Libmarpa method will be returned by the Marpa thin method as a numeric Perl scalar. 😌

🌈 An example of a general pattern method

Here's an example of a Libmarpa function whose corresponding Marpa thin method follows the general pattern:

marpa_g_start_symbol_set (grammar, symbol_S);

and here is the corresponding thin Marpa call:

$grammar->start_symbol_set($symbol_S);

And that's literally the whole pattern, bestie β€” iconic behavior fr fr. πŸ’–

πŸ’… Error recovery πŸ’…

okay bestie, error recovery in the thin interface works just like it does in the Libmarpa API, except we've tweaked it so it actually fits into a Perl module. cozy. 🫢

So real quick, let's bring back two concepts from the Libmarpa API: application behaviors and diagnostic behaviors. Application behaviors are the ones normal applications can expect β€” they're spelled out in this doc and in the Libmarpa API document. Diagnostic behaviors are a subset of the application behaviors. They're the ones diagnostic or testing code can reasonably try to do after an irrecoverable error. Lowkey the vibe is: application behaviors are promised, diagnostic behaviors are just hoped for. 😭 If you want the full deep dive on application vs. diagnostic behaviors, go back to the Libmarpa API document, it's all there.

Same as with the Libmarpa API, we call the object that caused a failure the failure object. Here's the tea:

  • If the failure object is a time object, its base grammar object is the grammar object it was created from β€” whether directly, indirectly, or trivially. And yes, a grammar object counts as created trivially from itself. ✨
  • If the failure object is a time object, the failure grammar is the base grammar object of the failure object.

And in the sections coming up, we've taken the definitions of library-recoverable, ancestry-recoverable, and fully recoverable failure and adapted them to be analogous to how the Libmarpa API defines those same terms. iykyk. πŸ’–


🌸 Library-recoverable failure 🌸

So in the thin interface, "library-recoverable failure" would honestly be more accurate if we called it "module-recoverable failure". But we're keeping the name "library-recoverable failure" anyway, just for convenience, so it's easy to cross-reference back to the Libmarpa API document. no cap.

When a thin wrapper that corresponds to a Libmarpa method hits a library-recoverable failure, these application behaviors are no longer available:

  • Thin interface mutator and constructor method calls on objects whose base grammar is the failure grammar.

And these application behaviors are still available:

  • All behaviors that aren't in the thin interface, including the Perl environment itself, and all modules other than the thin interface.
  • All thin interface static method calls.
  • All thin interface accessor method calls, and thin interface object destruction.
  • All thin interface constructor and mutator method calls for objects whose base grammar is not the failure grammar.

For more details on library-recoverability, refer to the Libmarpa API document. πŸ’œ


πŸ¦‹ Ancestry-recoverable failure πŸ¦‹

When a thin wrapper that corresponds to a Libmarpa method hits an ancestry-recoverable failure, these application behaviors are no longer available:

  • Thin interface mutator and constructor method calls on the failure object, or on one of the descendants of the failure object.

And these application behaviors are still available:

  • All behaviors that aren't in the thin interface, including the Perl environment itself, and all packages other than the thin interface.
  • All thin interface static method calls.
  • All thin interface accessor method calls, and thin interface object destruction.
  • All thin interface constructor and mutator method calls for objects that are not the failure object, or one of its descendants.

For more details on ancestry-recoverability, refer to the Libmarpa API document. ✨


πŸŽ€ Fully recoverable failure πŸŽ€

After a fully recoverable failure, all application behaviors remain available. That's it, that's the whole thing. We love a clean recovery. 🫢 For more details on fully recoverable failure, refer to the Libmarpa API document.


πŸ’– Error methods πŸ’–

The thin interface to Libmarpa gives you error methods that fit the Perl environment way better than Libmarpa's own do. Heads up though: calls to these methods are diagnostic behaviors. πŸ‘‘

$g->error()

my ( $error_code, $error_description ) = $grammar->error();
my @error_names = Marpa::R2::Thin::error_names();
my $error_name = $error_names[$error_code];

In scalar context, the error() method hands you back the error description. In array context, it gives you a 2-element array β€” first element is the error code, second element is the error description. 🌈

⚠️ Applications should assume that a call to any other Marpa thin method will overwrite the error code and error description. So if you want error() to successfully query the error code or error description of a method, error() has to be the very next Marpa thin interface method you call. no pressure bestie 😭

$g->error_clear()

The error_clear() method follows the general pattern. that's the whole note. πŸ’…

$g->error_names()

For a synopsis, see the section on the $g->error() method. πŸ‘† The error_names() method returns a reference to an array of error names, indexed by Libmarpa error code. cute, organized, love that for us. πŸŽ€

$g->throw_set()

$grammar->throw_set(0);

The throw_set() method turns the throw flag for the grammar on or off, depending on whether its argument is 1 or 0. throw_set() fails if its argument is not a numeric 0 or 1.

And here's the little plot twist: throw_set() itself never returns failure β€” it always throws an exception. always. every time. iconic behavior honestly. πŸ”₯


🌟 Omitted configuration methods 🌟

All of the methods in Libmarpa's configuration class are omitted from the Marpa thin interface. The stuff those Libmarpa configuration methods do gets handled in a much more Perl-centric way by the Marpa thin interface instead. bye to the old way, slay to the new way. πŸ’–

Marpa::R2::Thin::G->new()

my $grammar = Marpa::R2::Thin::G->new( { if => 1 } );

okay bestie, so the grammar constructor takes literally one argument: a reference to a hash of named args πŸŽ€ On success it hands you back a thin interface grammar object, slay. And important vibe check β€” new() does not obey the throw setting, it always throws when something goes wrong. no exceptions (well, only exceptions lol 😭).

Most applications should call force_valued() like, immediately after new() returns. If you don't, you enable unvalued terminals, and that's a deprecated feature β€” in the thin interface and in the Libmarpa API both. so like, don't 🫢

Right now the only named argument allowed is if β€” the interface number. It's required, and currently it has to be 1, which means interface 1. The whole point of the if argument is to keep things backward compatible in the future, iykyk.

Now here's the tea: there's no error message and no warning if you omit the hash ref argument, but new code should still treat that hash ref as required. Calling new() with no argument is deprecated πŸ’€ If you do omit the hash ref, the thin layer falls back to interface 0. And interface 0 cannot be specified directly β€” it's deprecated too. The difference between interface 0 and interface 1? In interface 0, the default throw setting of the freshly created grammar object is unspecified. (fr fr, the interface 0 throw setting depends on an undocumented and deprecated global variable. iconic in the worst way πŸ™ˆ)

$g->force_valued()

$grammar->force_valued();

force_valued() follows the general pattern, periodt. Most applications should call it right after the grammar's new() constructor. Skip it and you enable unvalued terminals, which β€” say it with me β€” is deprecated in both the thin interface and the Libmarpa API πŸ’…

$g->event()

my ( $event_type, $value ) = $grammar->event( $event_ix++ );

event() returns a two-element array on success ✨ First element is a string naming the event type, second is a scalar representing its value. The string for an event type is its macro name, exactly as given in the Libmarpa API document.

Some event types have an event "value". All event values are numeric Perl scalars. The number is either a symbol ID or a count, as described in the Libmarpa API document.

The permissible range of event indexes? You find that with the thin interface's event_count() grammar method, which corresponds to Libmarpa's marpa_g_event_count(). And event_count() follows the general pattern, of course.

Since event() returns the event value whenever it exists, the Libmarpa marpa_g_event_value() method is unneeded πŸŽ€ There's no corresponding Marpa thin interface method for it.

event() obeys the throw setting. On unthrown failure, it returns a Perl undef.

$g->rule_new()

my $start_rule_id = $grammar->rule_new( $symbol_S, [$symbol_E] );

rule_new() is the thin interface method that maps to marpa_g_rule_new(). Two arguments, both required, no skips 🌸 First argument is a symbol ID for the rule's LHS, second is a reference to an array of symbol IDs. Those IDs inside the array represent the RHS. On success you get back the ID of the new rule.

rule_new() obeys the throw setting. On unthrown failure, it returns -2.

$g->sequence_new()

my $sequence_rule_id = $grammar->sequence_new(
        $symbol_S,
        $symbol_a,
        {   separator => $symbol_sep,
            proper    => 0,
            min       => 1
        }
    );

sequence_new() is the thin interface method corresponding to marpa_g_sequence_new(). Three arguments, all required πŸ’– First is a symbol ID for the sequence's LHS. Second is a symbol ID for the sequence's RHS. Third is a reference to a hash of named arguments.

That hash of named arguments may be empty. But if it's not empty, its keys and values must be one of these:

  • separator β€” the value gets treated as an integer and passed as the separator ID argument to marpa_g_sequence_new(). It defaults to -1.
  • proper β€” if the value is a Perl true value, the MARPA_PROPER_SEPARATION flag gets set in the flags passed to marpa_g_sequence_new(). Otherwise, MARPA_PROPER_SEPARATION is not set.
  • min β€” the value gets treated as an integer and passed as the min argument to marpa_g_sequence_new(). min is the minimum number of repetitions of the sequence that are required. It defaults to 1.

On success, the return value is the rule ID of the new sequence ⭐

Heads up though: at the Marpa thin interface level, all sequences are "keep separation". That's different from the higher-level interface, which discards separators by default. At this level, throwing away separators is on you, the programmer β€” do it yourself if that's the vibe you want.

sequence_new() obeys the throw setting. On unthrown failure, it returns -2.

$g->precompute()

$grammar->precompute();

precompute() follows the general pattern πŸ¦‹ And on top of errors, it also reports events. You query events with the grammar's event() method.

As in the Libmarpa API, parsing with cycles is deprecated. Which means treating MARPA_ERR_GRAMMAR_HAS_CYCLE as a fully recoverable failure is also deprecated. Instead, MARPA_ERR_GRAMMAR_HAS_CYCLE should be treated as a library-recoverable failure. yes this is a real distinction, no I will not let it slide πŸ’…

On success, precompute() returns an event count. But β€” plot twist β€” even when there's an error, precompute() often reports one or more events 😭 So it is not safe to assume no events happened unless precompute() succeeds and reports an event count of zero.

$g->rule_rank()

rule_rank() is based on Libmarpa's marpa_g_rule_rank(). Its argument is the rule ID, and its return value is the rank, or a -2 to indicate an unthrown error.

Careful bestie: a return value of -2 is ambiguous πŸ‘€ It can mean the rank was -2, or that a failure happened. To tell the cases apart, look at the error code β€” it'll be MARPA_ERR_NONE if and only if the call succeeded. You get the error code from the error() method. Applications might find it more convenient to just have rule_rank() always throw its errors.

$g->rule_rank_set()

rule_rank_set() is based on Libmarpa's marpa_g_rule_rank_set(). Its two arguments are the rule ID and a rule rank. Return value is the new value of the rank, or a -2 to indicate an unthrown error.

Same ambiguous -2 situation, same energy 😭 To distinguish, check the error code β€” MARPA_ERR_NONE if and only if the call succeeded. The error code lives in the error() method. And again, some applications may find it more convenient to have rule_rank_set() always throw its errors.

$g->symbol_is_terminal_set()

$grammar->symbol_is_terminal_set( $symbol_seq, 1 );

symbol_is_terminal_set() is deprecated πŸ’€ It enables the LHS terminals feature, which is deprecated in both the thin interface and the Libmarpa API. It follows the general pattern.


Omitted grammar methods

  • marpa_g_ref() and marpa_g_unref() are omitted because the Marpa thin interface does their job for you.
  • marpa_g_event_value() is omitted because its function got absorbed into the thin interface's event() grammar method.

General pattern methods

All grammar methods that are part of the Libmarpa external interface but aren't explicitly named in this document are implemented following the general pattern, as described above. so if you're looking for one and it's not here, that's the move πŸŽ€

🌸 Recognizer methods 🌸

πŸ’– Marpa::R2::Thin::R->new()

my $recce = Marpa::R2::Thin::R->new($grammar);

okay so new() takes one argument and it's a Marpa thin grammar object. That's it, that's the whole vibe. Glow it up, and what you get back is a Marpa thin recognizer object ✨

And yes bestie, new() obeys the throw setting. If it fails and the error isn't thrown, you just get a Perl undef and no drama. πŸ’€

πŸ¦‹ $r->ruby_slippers_set()

$recce->ruby_slippers_set(1);

Give it 1 and the ruby_slippers_set() method turns "Ruby Slippers" mode ON. Give it 0 and it turns Ruby Slippers mode OFF. And by default? Ruby Slippers mode is disabled. 🚫

🚨 not me having to say this twice but: this default (disabled) is the opposite of the default in the higher level Marpa::R2 interface. Do not get caught slipping. πŸ™ˆ

So what does the mode even do? The alternative() method will only throw exceptions when "Ruby Slippers" mode is disabled AND the throw flag is on. One way of describing Ruby Slippers mode is as an override of the throw setting β€” an override that only applies to the alternative() method. Very specific. Very main character. πŸ‘‘

And a very important lil detail: the ruby_slippers_set() method itself does not obey the throw setting. All failures by ruby_slippers_set() are thrown as exceptions, periodt. πŸ’…

✨ $r->alternative()

$recce->alternative( $symbol_number, 2, 1 );

So in the Libmarpa API, the alternative() method returns an error code, and MARPA_ERR_NONE is the code you get when there was no error at all. πŸŽ€

Here's the tea: alternative() will throw the error code as an exception if and only if all three of these are true:

  • The base grammar's throw flag is on.
  • The Ruby Slippers flag is off.
  • The error code is not MARPA_ERR_NONE.

Three-for-three, bestie. No partial credit. 😀

The one everybody cares about is the error code MARPA_ERR_UNEXPECTED_TOKEN_ID, which means a token was not accepted because its token ID was not one of the ones expected. And like... catching this error and recovering from it? That's literally the entire basis of the Ruby Slippers parsing technique. πŸ’– For more on the Ruby Slippers flag, go look at ruby_slippers_set(). iykyk 🫢

πŸŽ€ $r->terminals_expected()

my @terminals = $recce->terminals_expected();

terminals_expected() takes no arguments. On success it hands you an array with the symbol ID's of the expected terminals. 🌈

😭 ok this is the trap, listen: the array of expected terminal ID's may be empty, so an empty array is NOT a failure indicator. An empty array is just an empty array. Do not read into it. Do not text it back. πŸ™ˆ

terminals_expected() obeys the throw setting, and on unthrown failure it returns a Perl undef. πŸ’€

πŸ”₯ $r->progress_item()

my $ordinal = $recce->latest_earley_set();
$recce->progress_report_start($ordinal);
ITEM: while (1) {
    my ($rule_id, $dot_position, $origin) = $recce->progress_item();
    last ITEM if not defined $rule_id;
    push @{$report}, [$rule_id, $dot_position, $origin];
}
$recce->progress_report_finish();

progress_item() takes no arguments either. On success, it returns an array of 3 elements: the rule ID, the dot position, and the earley set ID of the origin. And if there are no more items? It just returns a Perl undef β€” that's your signal to stop looping, bestie, the report is done. πŸ¦‹

progress_item() obeys the throw setting. And on unthrown failure, the rule ID element in the array returned by progress_item() will have a value of -2. Yeah. -2. Not undef. -2. Mark it down. πŸ’…


πŸ’œ Omitted recognizer methods

Because the Marpa thin interface handles reference counting internally, it does not implement methods directly corresponding to Libmarpa's marpa_r_ref() and marpa_r_unref() methods. It's taking care of itself, as it should. πŸ’…

There are Marpa thin methods corresponding to Libmarpa's marpa_r_earley_set_value() and marpa_r_earley_set_value_set() methods β€” but not to Libmarpa's marpa_r_earley_set_values() and marpa_r_earley_set_values_set() methods. What's the difference? The values form lets you set an integer and a pointer value, while the value form only lets you set an integer. So if you're a Perl application that wants to associate non-integer data with an Earley set, the move is: make an array, and use that integer to index the array. The elements of the array can hold arbitrary data. Problem solved, slay. ✨

And the thin interface does not implement a way to set the Earley set pointer value, because doing so would not add value. πŸ’€ The thin interface would have to track the reference count of a pointer, and honestly that can be done just as easily and efficiently, and with more flexibility, at the Perl level. So why bother. πŸ‘‘

🌸 Methods not mentioned

All recognizer methods that are part of the Libmarpa external interface, but that are not mentioned explicitly in this document, are implemented following the general pattern, as described above. Yeah bestie, they all follow the pattern. Nothing is being left out, it's just the pattern. 🫢


πŸ¦‹ Bocage methods πŸ¦‹

πŸ’– Marpa::R2::Thin::B->new()

my $latest_earley_set_ID = $recce->latest_earley_set();
my $bocage = Marpa::R2::Thin::B->new( $recce, $latest_earley_set_ID );

new() takes a Marpa thin recognizer object as its one argument. On success, it returns a Marpa thin bocage object. new() obeys the throw setting, and on unthrown failure it returns a Perl undef. Standard. Iconic. πŸ’…

πŸ’œ Omitted bocage methods

Because the Marpa thin interface handles reference counting internally, it does not implement methods directly corresponding to Libmarpa's marpa_b_ref() and marpa_b_unref() methods. Same energy as the recognizer, bestie. πŸŽ€

🌸 Methods not mentioned

All bocage methods that are part of the Libmarpa external interface, but that are not mentioned explicitly in this document, are implemented following the general pattern, as described above. Periodt. ✨


πŸ‘‘ Ordering methods πŸ‘‘

πŸ’– Marpa::R2::Thin::O->new()

my $order = Marpa::R2::Thin::O->new($bocage);

new() takes a Marpa thin bocage object as its one argument. On success, it returns a Marpa thin ordering object 🌈. new() obeys the throw setting, and on unthrown failure it returns a Perl undef. You know the drill. πŸ’–

πŸ’œ Omitted ordering methods

Because the Marpa thin interface handles reference counting internally, it does not implement methods directly corresponding to Libmarpa's marpa_o_ref() and marpa_o_unref() methods. The thin interface is doing the counting so you don't have to. 🫢

🌸 Methods not mentioned

All ordering methods that are part of the Libmarpa external interface, but that are not mentioned explicitly in this document, are implemented following the general pattern, as described above. πŸŽ€


⭐ Tree methods ⭐

πŸ’– Marpa::R2::Thin::T->new()

my $tree = Marpa::R2::Thin::T->new($order);

new() takes a Marpa thin ordering object as its one argument. On success, it returns a Marpa thin tree object ✨. new() obeys the throw setting, and on unthrown failure it returns a Perl undef. And then you have a tree. Look at you go. 🌸

πŸ’œ Omitted tree methods

Because the Marpa thin interface handles reference counting internally, it does not implement methods directly corresponding to Libmarpa's marpa_t_ref() and marpa_t_unref() methods. Same story, different object. πŸ’…

🌸 Methods not mentioned

All tree methods that are part of the Libmarpa external interface, but that are not mentioned explicitly in this document, are implemented following the general pattern, as described above. πŸ¦‹


πŸ’… Value methods πŸ’…

okay bestie, this is the part where the tree turns into actual values 🌸 the valuator is the little engine that walks your tree and hands you back real answers. let's go.

Marpa::R2::Thin::V->new()

my $valuator = Marpa::R2::Thin::V->new($tree);

new() takes exactly one argument, and it's a Marpa thin tree object πŸŽ€ slay. If it works, you get a Marpa thin value object back β€” gorgeous. And yes, new() obeys the throw setting: if it fails and nothing gets thrown, you get a Perl undef back and you just have to deal with it 😭

$v->location()

$type = $valuator->step_type();
my ( $start, $end ) = $valuator->location();
if ( $type eq 'MARPA_STEP_RULE' ) {
    my ($rule_id) = @step_data;
    $locations_report .= "Rule $rule_id is from $start to $end\n";
}
if ( $type eq 'MARPA_STEP_TOKEN' ) {
    my ($token_id) = @step_data;
    $locations_report .= "Token $token_id is from $start to $end\n";
}
if ( $type eq 'MARPA_STEP_NULLING_SYMBOL' ) {
    my ($symbol_id) = @step_data;
    $locations_report
        .= "Nulling symbol $symbol_id is from $start to $end\n";
}

location() takes no arguments, and it always succeeds β€” no throw drama here, she's unbothered πŸ’… what you get back is either an empty array or an array with two elements in it. which one? depends:

  • if the last step was MARPA_STEP_RULE, the array has the spots where the rule starts and ends, exactly like the Libmarpa methods marpa_v_rule_start_es_id() and marpa_v_es_id() return them ✨
  • if the last step was MARPA_STEP_TOKEN, the array has the spots where the token starts and ends, as returned by the Libmarpa methods marpa_v_token_start_es_id() and marpa_v_es_id() πŸ¦‹
  • if the last step was MARPA_STEP_NULLING_SYMBOL, the array has the spots where the token starts and ends, as returned by the Libmarpa methods marpa_v_token_start_es_id() and marpa_v_es_id() 🌸
  • literally any other case? empty array. nothing. silence. πŸ™ˆ

$v->step()

my ( $type, @step_data ) = $valuator->step();

step() takes no arguments either β€” she needs nothing from you, main character energy πŸ‘‘ on success you get an array back, and what's in it depends on the step type:

MARPA_STEP_RULE

if the step type is MARPA_STEP_RULE, step() hands you an array of 4 elements. in order, they are:

  • the string "MARPA_STEP_RULE"
  • the rule id, as returned by the Libmarpa method marpa_v_rule_id()
  • the stack location where the child values of the rule begin, as returned by the Libmarpa method marpa_v_token_arg_0(). fun fact: this is also the stack location where the result is supposed to be written 🫢
  • the stack location where the child values of the rule end, as returned by the Libmarpa method marpa_v_token_arg_n()

MARPA_STEP_TOKEN

if the step type is MARPA_STEP_TOKEN, step() hands you an array of 4 elements. in order, they are:

  • the string "MARPA_STEP_TOKEN"
  • the token id, as returned by the Libmarpa method marpa_v_token()
  • the token value, as returned by the Libmarpa method marpa_v_token_value()
  • the stack location where the token's value should be written, as returned by the Libmarpa method marpa_v_result()
quick reminder bestie, and this one matters: Libmarpa's token values are always integers. periodt. your app probably wants richer or totally different semantics for token values β€” and a super common move is to use Libmarpa's token values as indexes into an array. iykyk πŸ’–

MARPA_STEP_NULLING_SYMBOL

if the step type is MARPA_STEP_NULLING_SYMBOL, step() hands you an array of 3 elements. in order, they are:

  • the string "MARPA_STEP_NULLING_SYMBOL"
  • the ID of the nulling symbol, as returned by the Libmarpa method marpa_v_symbol()
  • the stack location where the nulling symbol's value should be written, as returned by the Libmarpa method marpa_v_result()

MARPA_STEP_INACTIVE

if the step type is MARPA_STEP_INACTIVE, step() returns an empty array. that's it, that's the whole vibe πŸ’€

and yes, step() obeys the throw setting too. on unthrown failure, step() returns an array whose only element is a string not reserved by Libmarpa. and what counts as "not reserved"? a string that does not begin with "MARPA_" in one of its capitalization variants. the string will usually be a description of the error 🌈

$v->step_type()

$type = $valuator->step_type();

step_type() takes no arguments ✨ on success it gives you the string saying what type the last Libmarpa valuator step was. and if your last call to step() succeeded, the string you get from step_type() is exactly the same one that was the first element of that array step() gave you. consistent queen πŸ‘‘

step_type() obeys the throw setting. on unthrown failure, step() returns an array whose only element is a string not reserved by Libmarpa β€” so, one that does not begin with "MARPA_" in one of its capitalization variants. the string will usually be a description of the error πŸ’€

Omitted value methods

so, why isn't there a whole pile of tiny methods here? because the Marpa thin interface handles reference counting internally, she does not implement methods directly corresponding to Libmarpa's marpa_v_ref() and marpa_v_unref() methods πŸ™ˆ and the step accessor macros are folded into the thin interface's $v->step() and $v->location() methods. that's why no thin interface macro corresponds directly to most of the individual step accessors β€” they all got absorbed into the big two 🫢

Methods not mentioned

every value method that's part of the Libmarpa external interface but that isn't called out explicitly in this document is implemented following the general pattern, as described above. no surprises, no cap πŸ’–


πŸŽ€ Example πŸŽ€

okay this is the moment, bestie β€” a whole little calculator, end to end:

my $grammar = Marpa::R2::Thin::G->new( { if => 1 } );
$grammar->force_valued();
my $symbol_S = $grammar->symbol_new();
my $symbol_E = $grammar->symbol_new();
$grammar->start_symbol_set($symbol_S);
my $symbol_op     = $grammar->symbol_new();
my $symbol_number = $grammar->symbol_new();
my $start_rule_id = $grammar->rule_new( $symbol_S, [$symbol_E] );
my $op_rule_id =
    $grammar->rule_new( $symbol_E, [ $symbol_E, $symbol_op, $symbol_E ] );
my $number_rule_id = $grammar->rule_new( $symbol_E, [$symbol_number] );
$grammar->precompute();

my $recce = Marpa::R2::Thin::R->new($grammar);
$recce->start_input();

# The numbers from 1 to 3 are themselves --
# that is, they index their own token value.
# Important: zero cannot be itself!

my @token_values         = ( 0 .. 3 );
my $zero                 = -1 + push @token_values, 0;
my $minus_token_value    = -1 + push @token_values, q{-};
my $plus_token_value     = -1 + push @token_values, q{+};
my $multiply_token_value = -1 + push @token_values, q{*};

$recce->alternative( $symbol_number, 2, 1 );
$recce->earleme_complete();
$recce->alternative( $symbol_op, $minus_token_value, 1 );
$recce->earleme_complete();
$recce->alternative( $symbol_number, $zero, 1 );
$recce->earleme_complete();
$recce->alternative( $symbol_op, $multiply_token_value, 1 );
$recce->earleme_complete();
$recce->alternative( $symbol_number, 3, 1 );
$recce->earleme_complete();
$recce->alternative( $symbol_op, $plus_token_value, 1 );
$recce->earleme_complete();
$recce->alternative( $symbol_number, 1, 1 );
$recce->earleme_complete();

my $latest_earley_set_ID = $recce->latest_earley_set();
my $bocage        = Marpa::R2::Thin::B->new( $recce, $latest_earley_set_ID );
my $order         = Marpa::R2::Thin::O->new($bocage);
my $tree          = Marpa::R2::Thin::T->new($order);
my @actual_values = ();
while ( $tree->next() ) {
    my $valuator = Marpa::R2::Thin::V->new($tree);
    my @stack = ();
    STEP: while ( 1 ) {
        my ( $type, @step_data ) = $valuator->step();
        last STEP if not defined $type;
        if ( $type eq 'MARPA_STEP_TOKEN' ) {
            my ( undef, $token_value_ix, $arg_n ) = @step_data;
            $stack[$arg_n] = $token_values[$token_value_ix];
            next STEP;
        }
        if ( $type eq 'MARPA_STEP_RULE' ) {
            my ( $rule_id, $arg_0, $arg_n ) = @step_data;
            if ( $rule_id == $start_rule_id ) {
                my ( $string, $value ) = @{ $stack[$arg_n] };
                $stack[$arg_0] = "$string == $value";
                next STEP;
            }
            if ( $rule_id == $number_rule_id ) {
                my $number = $stack[$arg_0];
                $stack[$arg_0] = [ $number, $number ];
                next STEP;
            }
            if ( $rule_id == $op_rule_id ) {
                my $op = $stack[ $arg_0 + 1 ];
                my ( $right_string, $right_value ) = @{ $stack[$arg_n] };
                my ( $left_string,  $left_value )  = @{ $stack[$arg_0] };
                my $value;
                my $text = '(' . $left_string . $op . $right_string . ')';
                if ( $op eq q{+} ) {
                    $stack[$arg_0] = [ $text, $left_value + $right_value ];
                    next STEP;
                }
                if ( $op eq q{-} ) {
                    $stack[$arg_0] = [ $text, $left_value - $right_value ];
                    next STEP;
                }
                if ( $op eq q{*} ) {
                    $stack[$arg_0] = [ $text, $left_value * $right_value ];
                    next STEP;
                }
                die "Unknown op: $op";
            } ## end if ( $rule_id == $op_rule_id )
            die "Unknown rule $rule_id";
        } ## end if ( $type eq 'MARPA_STEP_RULE' )
        die "Unexpected step type: $type";
    } ## end while ( my ( $type, @step_data ) = $valuator->step() )
    push @actual_values, $stack[0];
} ## end while ( $tree->next() )

she EATS πŸ“ (literally β€” numbers, ops, and all)


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 🧚 we did it β€” perl v5.42.3, dated 2026-08-20, from Marpa:...d::Thin(3pm). go forth and value your trees, you absolute icon πŸ‘‘πŸ’œ

πŸ“¦ where this page comes from