The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
# Marpa::R3 is Copyright (C) 2017, Jeffrey Kegler.
#
# This module is free software; you can redistribute it and/or modify it
# under the same terms as Perl 5.10.1. For more details, see the full text
# of the licenses in the directory LICENSES.
#
# This program is distributed in the hope that it will be
# useful, but it is provided "as is" and without any express
# or implied warranties. For details, see the full text of
# of the licenses in the directory LICENSES.

package Marpa::R3::ASF;

use 5.010001;
use strict;
use warnings;
no warnings qw(recursion);

use vars qw($VERSION $STRING_VERSION);
$VERSION        = '4.001_051';
$STRING_VERSION = $VERSION;
## no critic(BuiltinFunctions::ProhibitStringyEval)
$VERSION = eval $VERSION;
## use critic

# The code in this file, for now, breaks "the rules".  It makes use
# of internal methods not documented as part of Libmarpa.
# It is intended to create documented Libmarpa methods to underlie
# this interface, and rewrite it to use them

package Marpa::R3::Internal_ASF;

use Scalar::Util qw(blessed tainted);
use English qw( -no_match_vars );

our $PACKAGE = 'Marpa::R3::ASF';

# Set those common args which are at the Perl level.
# This is more complicated that it needs to be for the current implementation.
# It allows for LHS terminals (implemented in Libmarpa but not allowed by the SLIF).
# It also assumes that every or-node which can be constructed from preceding or-nodes
# and the input will be present.  This is currently the case, but in the future
# rules and/or symbols may have extra-syntactic conditions attached making this
# assumption false.

# Terms:

# NID (Node ID): Encoded ID of either an or-node or an and-node.
#
# Extensions:
# Set "powers":  A set of power 0 is an "atom" -- a single NID.
# A set of power 1 is a set of NID's -- a nidset.
# A set of power 2 is a set of sets of NID's, also called a powerset.
# A set of power 3 is a set of powersets, etc.
#
# The whole ID of NID is the external rule id of an or-node, or -1
# if the NID is for a token and-node.
#
# Intensions:
# A Symch is a nidset, where all the NID's share the same "whole ID"
# and the same span.  NID's in a symch may differ in their internal rule,
# or have different causes.  If the symch contains and-node NID's they
# will all have the same symbol.
#
# A choicepoint is a powerset -- a set of symches all of which share
# the same set of predecessors.  (This set of predecessors is a power 3 set of
# choicepoints.)  All symches in a choicepoint also share the same span,
# and the same symch-symbol.  A symch's symbol is the LHS of the rule,
# or the symbol of the token in the token and-nodes.

# No check for conflicting usage -- value(), asf(), etc.
# at this point
sub Marpa::R3::ASF::peak {
    my ($asf)    = @_;
    die("Not yet implemented");
} ## end sub Marpa::R3::ASF::peak

our $NID_LEAF_BASE = -43;

# Range from -1 to -42 reserved for special values
sub and_node_to_nid { return -$_[0] + $NID_LEAF_BASE; }
sub nid_to_and_node { return -$_[0] + $NID_LEAF_BASE; }

# Set those common args which are at the Perl level.
sub asf_common_set {
    my ( $asf, $flat_args ) = @_;
    if ( my $value = $flat_args->{'trace_file_handle'} ) {
        $asf->[Marpa::R3::Internal_ASF::TRACE_FILE_HANDLE] = $value;
    }
    my $trace_file_handle =
      $asf->[Marpa::R3::Internal_ASF::TRACE_FILE_HANDLE];
    delete $flat_args->{'trace_file_handle'};
    return $flat_args;
}

# Returns undef if no parse
sub Marpa::R3::ASF::new {
    my ( $class, @args ) = @_;
    my $asf = bless [], $class;

    my $end_of_parse;

    my ( $flat_args, $error_message ) = Marpa::R3::flatten_hash_args( \@args );
    Marpa::R3::exception( sprintf $error_message, '$asf->new' )
      if not $flat_args;
    $flat_args = asf_common_set( $asf, $flat_args );

    my $slr = $flat_args->{recognizer};
    Marpa::R3::exception(
        qq{Marpa::R3::ASF::new() called without a "recognizer" argument} )
      if not defined $slr;
    $asf->[Marpa::R3::Internal_ASF::SLR] = $slr;
    delete $flat_args->{recognizer};

    my $slr_class = 'Marpa::R3::Recognizer';
    if ( not blessed $slr or not $slr->isa($slr_class) ) {
        my $ref_type = ref $slr;
        my $desc = $ref_type ? "a ref to $ref_type" : 'not a ref';
        Marpa::R3::exception(
            qq{'recognizer' named argument to new() is $desc\n},
            "  It should be a ref to $slr_class\n"
        );
    }

    $asf->[Marpa::R3::Internal_ASF::TRACE_FILE_HANDLE] //=
      $slr->[Marpa::R3::Internal_R::TRACE_FILE_HANDLE];

    my $trace_file_handle =
      $asf->[Marpa::R3::Internal_ASF::TRACE_FILE_HANDLE];

    my $lua = $slr->[Marpa::R3::Internal_R::L];
    $asf->[Marpa::R3::Internal_ASF::L] = $lua;

        ARG: for my $arg ( keys %{$flat_args} ) {
            if ( $arg eq 'factoring_max' ) {
                $asf->[Marpa::R3::Internal_ASF::FACTORING_MAX] =
                    $flat_args->{$arg};
                delete $flat_args->{$arg};
                next ARG;
            }
        }

    my ( $regix ) = $slr->coro_by_tag(
        ( '@' . __FILE__ . ':' . __LINE__ ),
        {
            signature => 's',
            args      => [$flat_args],
            handlers  => {
                trace => sub {
                    my ($msg) = @_;
                    say {$trace_file_handle} $msg;
                    return 'ok';
                },
            }
        },
        <<'END_OF_LUA');
        local slr, flat_args = ...
        _M.wrap(function ()
            local asf = slr:asf_new(flat_args)
            if not asf then return 'ok', -1 end
            local bocage = asf.lmw_b
            if bocage:is_null() == 1 then
                error([[
        An attempt was make to create an ASF for a null parse\n\a
        \u{20}  A null parse is a successful parse of a zero-length string\n\z
        \u{20}  ASF's are not defined for null parses\n\z
        ]])
            end
            return 'ok', asf.regix
        end)
END_OF_LUA

    return if $regix < 0;
    $asf->[Marpa::R3::Internal_ASF::REGIX]  = $regix;

    $asf->[Marpa::R3::Internal_ASF::FACTORING_MAX] //= 42;

    return $asf;

} ## end sub Marpa::R3::ASF::new

sub Marpa::R3::ASF::DESTROY {
    # say STDERR "In Marpa::R3::ASF::DESTROY before test";
    my $asf = shift;
    my $lua = $asf->[Marpa::R3::Internal_ASF::L];

    # If we are destroying the Perl interpreter, then all the Marpa
    # objects will be destroyed, including Marpa's Lua interpreter.
    # We do not need to worry about cleaning up the
    # recognizer is an orderly manner, because the Lua interpreter
    # containing the recognizer will be destroyed.
    # In fact, the Lua interpreter may already have been destroyed,
    # so this test is necessary to avoid a warning message.
    return if not $lua;
    # say STDERR "In Marpa::R3::ASF::DESTROY after test";

    my $regix = $asf->[Marpa::R3::Internal_ASF::REGIX];
    $asf->call_by_tag(
        ('@' . __FILE__ . ':' . __LINE__),
        <<'END_OF_LUA', '');
    local asf = ...
    local regix = asf.regix
    _M.unregister(_M.registry, regix)
END_OF_LUA
}

sub Marpa::R3::ASF::grammar {
    my ($asf)   = @_;
    my $slr     = $asf->[Marpa::R3::Internal_ASF::SLR];
    my $slg = $slr->[Marpa::R3::Internal_R::SLG];
    return $slg;
} ## end sub Marpa::R3::ASF::grammar

# TODO -- Document this method
sub Marpa::R3::ASF::recognizer {
    my ($asf)   = @_;
    my $slr     = $asf->[Marpa::R3::Internal_ASF::SLR];
    return $slr;
}

# not to be documented
sub Marpa::R3::ASF::call_by_tag {
    my ( $asf, $tag, $codestr, $signature, @args ) = @_;
    my $lua   = $asf->[Marpa::R3::Internal_ASF::L];
    my $regix = $asf->[Marpa::R3::Internal_ASF::REGIX];

    my @results;
    my $eval_error;
    my $eval_ok;
    {
        local $@;
        $eval_ok = eval {
            @results =
              $lua->call_by_tag( $regix, $tag, $codestr, $signature, @args );
            return 1;
        };
        $eval_error = $@;
    }
    if ( not $eval_ok ) {
        Marpa::R3::exception($eval_error);
    }
    return @results;
}

# not to be documented
sub Marpa::R3::ASF::coro_by_tag {
    my ( $asf, $tag, $args, $codestr ) = @_;
    my $lua        = $asf->[Marpa::R3::Internal_ASF::L];
    my $regix      = $asf->[Marpa::R3::Internal_ASF::REGIX];
    my $handler    = $args->{handlers} // {};
    my $resume_tag = $tag . '[R]';
    my $signature  = $args->{signature} // '';
    my $p_args     = $args->{args} // [];

    my @results;
    my $eval_error;
    my $eval_ok;
    {
        local $@;
        $eval_ok = eval {
            $lua->call_by_tag( $regix, $tag, $codestr, $signature, @{$p_args} );
            my @resume_args = ('');
            my $signature = 's';
          CORO_CALL: while (1) {
                my ( $cmd, $yield_data ) =
                  $lua->call_by_tag( $regix, $resume_tag,
                    'local asf, resume_arg = ...; return _M.resume(resume_arg)',
                    $signature, @resume_args ) ;
                if (not $cmd) {
                   @results = @{$yield_data};
                   return 1;
                }
                my $handler = $handler->{$cmd};
                Marpa::R3::exception(qq{No coro handler for "$cmd"})
                  if not $handler;
                $yield_data //= [];
                my ($handler_cmd, $new_resume_args) = $handler->(@{$yield_data});
                Marpa::R3::exception(qq{Undefined return command from handler for "$cmd"})
                   if not defined $handler_cmd;
                if ($handler_cmd eq 'ok') {
                   $signature = 's';
                   @resume_args = ($new_resume_args);
                   if (scalar @resume_args < 1) {
                       @resume_args = ('');
                   }
                   next CORO_CALL;
                }
                if ($handler_cmd eq 'sig') {
                   @resume_args = @{$new_resume_args};
                   $signature = shift @resume_args;
                   next CORO_CALL;
                }
                Marpa::R3::exception(qq{Bad return command ("$handler_cmd") from handler for "$cmd"})
            }
            return 1;
        };
        $eval_error = $@;
    }
    if ( not $eval_ok ) {
        # if it's an object, just die
        die $eval_error if ref $eval_error;
        Marpa::R3::exception($eval_error);
    }
    return @results;
}

sub Marpa::R3::ASF::ambiguity_level {
    my ($asf) = @_;

    my ($metric) = $asf->call_by_tag(
    ('@' . __FILE__ . ':' . __LINE__),
    <<'END__OF_LUA', '>*' );
    local asf = ...
    return asf:ambiguity_level()
END__OF_LUA
    return $metric;
}

sub Marpa::R3::ASF::g1_pos {
    my ( $asf ) = @_;
    my ($g1_pos) = $asf->call_by_tag(
    ('@' . __FILE__ . ':' . __LINE__),
    <<'END__OF_LUA', '>*' );
    local asf = ...
    return asf:g1_pos()
END__OF_LUA
    return $g1_pos;
}

# not to be documented
sub Marpa::R3::ASF::regix {
    my ( $asf ) = @_;
    my $regix = $asf->[Marpa::R3::Internal_ASF::REGIX];
    return $regix;
}

1;

# vim: expandtab shiftwidth=4: