The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

MarpaX::ESLIF - ESLIF is Extended ScanLess InterFace

VERSION

version 6.0.35.1

SYNOPSIS

  use MarpaX::ESLIF;

  my $eslif = MarpaX::ESLIF->new();
  printf "ESLIF library version: %s\n", $eslif->version;

With a logger, using Log::Any::Adapter::Stderr as an example:

  use MarpaX::ESLIF;
  use Log::Any qw/$log/;
  use Log::Any::Adapter ('Stderr', log_level => 'trace' );

  my $eslif = MarpaX::ESLIF->new($log);
  printf "ESLIF library version: %s\n", $eslif->version;

This class and its derivatives are thread-safe. Although there can be many ESLIF instances, in practice a single instance is enough, unless you want different logging interfaces. This is why the new method is implemented as a multiton v.s. the logger: there is one MarpaX::ESLIF perl logger.

Once created, one may want to create a grammar instance. This is provided by MarpaX::ESLIF::Grammar class. The grammar can then be used to parse some input, using a recognizer, or even to valuate it.

A recognizer is asking for an interface that you will implement that must provide some methods, e.g. on a string:

  package MyRecognizer;
  sub new {
      my ($pkg, $string) = @_;
      open my $fh, "<", \$string;
      bless { data => undef, fh => $fh }, $pkg
  }
  sub read                   { my ($self) = @_; defined($self->{data} = readline($self->{fh})) } # Reader
  sub isEof                  {  eof shift->{fh} } # End of data ?
  sub isCharacterStream      {                1 } # Character stream ?
  sub encoding               {                  } # Encoding ? Let's ESLIF guess.
  sub data                   {    shift->{data} } # data
  sub isWithDisableThreshold {                0 } # Disable threshold warning ?
  sub isWithExhaustion       {                0 } # Exhaustion event ?
  sub isWithNewline          {                1 } # Newline count ?
  sub isWithTrack            {                0 } # Absolute position tracking ?
  1;

Valuation is also asking for an implementation of your own, that must provide some methods, e.g.:

  package MyValue;
  sub new                { bless { result => undef}, shift }
  sub isWithHighRankOnly { 1 }  # When there is the rank adverb: highest ranks only ?
  sub isWithOrderByRank  { 1 }  # When there is the rank adverb: order by rank ?
  sub isWithAmbiguous    { 0 }  # Allow ambiguous parse ?
  sub isWithNull         { 0 }  # Allow null parse ?
  sub maxParses          { 0 }  # Maximum number of parse tree values
  sub getResult          { my ($self) = @_; $self->{result} }
  sub setResult          { my ($self, $result) = @_; $self->{result} = $result }
  1;

A full example of a calculator with a self-contained grammar, using the recognizer and valuation implementation above, and actions writen in Lua:

  package MyRecognizer;
  sub new {
      my ($pkg, $string) = @_;
      open my $fh, "<", \$string;
      bless { data => undef, fh => $fh }, $pkg
  }
  sub read                   { my ($self) = @_; defined($self->{data} = readline($self->{fh})) } # Reader
  sub isEof                  {  eof shift->{fh} } # End of data ?
  sub isCharacterStream      {                1 } # Character stream ?
  sub encoding               {                  } # Encoding ? Let's ESLIF guess.
  sub data                   {    shift->{data} } # data
  sub isWithDisableThreshold {                0 } # Disable threshold warning ?
  sub isWithExhaustion       {                0 } # Exhaustion event ?
  sub isWithNewline          {                1 } # Newline count ?
  sub isWithTrack            {                0 } # Absolute position tracking ?
  1;

  package MyValue;
  sub new                { bless { result => undef}, shift }
  sub isWithHighRankOnly { 1 }  # When there is the rank adverb: highest ranks only ?
  sub isWithOrderByRank  { 1 }  # When there is the rank adverb: order by rank ?
  sub isWithAmbiguous    { 0 }  # Allow ambiguous parse ?
  sub isWithNull         { 0 }  # Allow null parse ?
  sub maxParses          { 0 }  # Maximum number of parse tree values
  sub getResult          { my ($self) = @_; $self->{result} }
  sub setResult          { my ($self, $result) = @_; $self->{result} = $result }
  1;
  
  package main;
  use Log::Any qw/$log/, default_adapter => qw/Stdout/;
  use MarpaX::ESLIF;
  use Test::More;
  
  my %tests = (
      1 => [ '1',               1            ],
      2 => [ '1/2',             0.5          ],
      3 => [ 'x',               undef        ],
      4 => [ '(1*(2+3)/4**5)',  0.0048828125 ]
      );
  
  my $eslif = MarpaX::ESLIF->new($log);
  my $g = MarpaX::ESLIF::Grammar->new($eslif, do { local $/; <DATA> });
  foreach (sort { $a <=> $b} keys %tests) {
      my ($input, $value) = @{$tests{$_}};
      my $r = MyRecognizer->new($input);
      my $v = MyValue->new();
      if (defined($value)) {
          ok($g->parse($r, $v), "'$input' parse is ok");
          ok($v->getResult == $value, "'$input' value is $value");
      } else {
          ok(!$g->parse($r, $v), "'$input' parse is ko");
      }
  }
  
  done_testing();

  __DATA__
  :discard ::= /[\s]+/
  :default ::= event-action => ::luac->function()
                                         print('In event-action')
                                         return true
                                       end
  event ^exp = predicted exp
  exp ::=
      /[\d]+/                             action => ::luac->function(input) return tonumber(input) end
      |    "("  exp ")"    assoc => group action => ::luac->function(l,e,r) return e               end
     || exp (- '**' -) exp assoc => right action => ::luac->function(x,y)   return x^y             end
     || exp (-  '*' -) exp                action => ::luac->function(x,y)   return x*y             end
      | exp (-  '/' -) exp                action => ::luac->function(x,y)   return x/y             end
     || exp (-  '+' -) exp                action => ::luac->function(x,y)   return x+y             end
      | exp (-  '-' -) exp                action => ::luac->function(x,y)   return x-y             end

The same but with actions writen in Perl:

  package MyRecognizer;
  sub new {
      my ($pkg, $string) = @_;
      open my $fh, "<", \$string;
      bless { data => undef, fh => $fh }, $pkg
  }
  sub read                   { my ($self) = @_; defined($self->{data} = readline($self->{fh})) } # Reader
  sub isEof                  {  eof shift->{fh} } # End of data ?
  sub isCharacterStream      {                1 } # Character stream ?
  sub encoding               {                  } # Encoding ? Let's ESLIF guess.
  sub data                   {    shift->{data} } # data
  sub isWithDisableThreshold {                0 } # Disable threshold warning ?
  sub isWithExhaustion       {                0 } # Exhaustion event ?
  sub isWithNewline          {                1 } # Newline count ?
  sub isWithTrack            {                0 } # Absolute position tracking ?
  1;

  package MyValue::Perl;
  sub new                { bless { result => undef}, shift }
  sub isWithHighRankOnly { 1 }  # When there is the rank adverb: highest ranks only ?
  sub isWithOrderByRank  { 1 }  # When there is the rank adverb: order by rank ?
  sub isWithAmbiguous    { 0 }  # Allow ambiguous parse ?
  sub isWithNull         { 0 }  # Allow null parse ?
  sub maxParses          { 0 }  # Maximum number of parse tree values
  sub getResult          { my ($self) = @_; $self->{result} }
  sub setResult          { my ($self, $result) = @_; $self->{result} = $result }
  #
  # Here the actions are writen in Perl, they all belong to the valuator namespace 'MyValue'
  #
  sub tonumber           { shift; $_[0] }
  sub e                  { shift; $_[1] }
  sub power              { shift; $_[0] ** $_[1] }
  sub mul                { shift; $_[0]  * $_[1] }
  sub div                { shift; $_[0]  / $_[1] }
  sub plus               { shift; $_[0]  + $_[1] }
  sub minus              { shift; $_[0]  - $_[1] }
  1;
  
  package main;
  use Log::Any qw/$log/, default_adapter => qw/Stdout/;
  use MarpaX::ESLIF;
  use Test::More;
  
  my %tests = (
      1 => [ '1',               1            ],
      2 => [ '1/2',             0.5          ],
      3 => [ 'x',               undef        ],
      4 => [ '(1*(2+3)/4**5)',  0.0048828125 ]
      );
  
  my $eslif = MarpaX::ESLIF->new($log);
  my $g = MarpaX::ESLIF::Grammar->new($eslif, do { local $/; <DATA> });
  foreach (sort { $a <=> $b} keys %tests) {
      my ($input, $value) = @{$tests{$_}};
      my $r = MyRecognizer->new($input);
      my $v = MyValue::Perl->new();
      if (defined($value)) {
          ok($g->parse($r, $v), "'$input' parse is ok");
          ok($v->getResult == $value, "'$input' value is $value");
      } else {
          ok(!$g->parse($r, $v), "'$input' parse is ko");
      }
  }
  
  done_testing();

DESCRIPTION

ESLIF is derived from perl's Marpa::R2, and has its own BNF, documented in MarpaX::ESLIF::BNF.

The main features of this BNF are:

Embedded Lua language

Actions can be writen directly in the grammar.

Regular expressions

Matching supports natively regular expression using the PCRE2 library.

Streaming

Native support of streaming input.

Sub-grammars

The number of sub grammars is unlimited.

Beginners might want to look at MarpaX::ESLIF::Introduction.

In both cases, the output will be:

  ok 1 - '1' parse is ok
  ok 2 - '1' value is 1
  ok 3 - '1/2' parse is ok
  ok 4 - '1/2' value is 0.5
  --------------------------------------------
  Recognizer progress (grammar level 0 (Grammar level 0)):
  [P1@0..0] exp ::= . exp[0]
  [P2@0..0] exp[0] ::= . exp[1]
  [P3@0..0] exp[1] ::= . exp[2]
  [P4@0..0] exp[2] ::= . exp[3]
  [P10@0..0] exp[3] ::= . /[\d]+/
  [P11@0..0] exp[3] ::= . "("
  [P11@0..0]            exp[0]
  [P11@0..0]            ")"
  [P13@0..0] exp[2] ::= . exp[3]
  [P13@0..0]            Internal[5]
  [P13@0..0]            exp[2]
  [P15@0..0] exp[1] ::= . exp[1]
  [P15@0..0]            Internal[6]
  [P15@0..0]            exp[2]
  [P17@0..0] exp[1] ::= . exp[1]
  [P17@0..0]            Internal[7]
  [P17@0..0]            exp[2]
  [P19@0..0] exp[0] ::= . exp[0]
  [P19@0..0]            Internal[8]
  [P19@0..0]            exp[1]
  [P21@0..0] exp[0] ::= . exp[0]
  [P21@0..0]            Internal[9]
  [P21@0..0]            exp[1]
  Expected symbol: /[\d]+/ (symbol No 7)
  Expected symbol: "(" (symbol No 8)
  <<<<<< FAILURE AT LINE No 1 COLUMN No 1, HERE: >>>>>>
  UTF-8 converted data after the failure (1 bytes) at 1:1:
  0x000000: 78                                              x
  --------------------------------------------
  ok 5 - 'x' parse is ko
  ok 6 - '(1*(2+3)/4**5)' parse is ok
  ok 7 - '(1*(2+3)/4**5)' value is 0.0048828125
  1..7

MarpaX::ESLIF also provide native JSON encoder/decoder:

  use Log::Any qw/$log/, default_adapter => qw/Stdout/;
  use MarpaX::ESLIF;
  
  my $eslif = MarpaX::ESLIF->new($log);
  my $json = MarpaX::ESLIF::JSON->new($eslif);
  
  my $perl_hash = $json->encode({data => { 1 => [ 2, "3" ] } });
  $log->infof('JSON Encoder: %s', $perl_hash);
  
  my $json_string = $json->decode($perl_hash);
  $log->infof('JSON decoder: %s', $json_string);

  # Output: 
  # JSON Encoder: {"data":{"1":[2,"3"]}}
  # JSON decoder: {data => {1 => [2,3]}}

METHODS

MarpaX::ESLIF->new($loggerInterface)

  my $loggerInterface = My::Logger::Interface->new();
  my $eslif = MarpaX::ESLIF->new();

Returns an instance of MarpaX::ESLIF, noted $eslif below.

$loggerInterface is an optional parameter that, when its exists, must be an object instance that can do the methods documented in MarpaX::ESLIF::Logger::Interface, or undef.

An example of logging implementation can be a Log::Any adapter.

MarpaX::ESLIF->getInstance($loggerInterface)

Alias to new.

$eslif->version()

  printf "ESLIF library version: %s\n", $eslif->version;

Returns a string containing the current underlying ESLIF library version.

NOTES

BOOLEAN TYPE

ESLIF has a boolean type, perl has not. In order to not reinvent the wheel, the widely JSON's Perl's boolean utilities via JSON::MaybeXS wrapper are used, i.e.:

true

A true value. You may localize $MarpaX::ESLIF::true before using ESLIF to change it.

Defaults to JSON::MaybeXS::true().

false

A false value. You may localize $MarpaX::ESLIF::false before using ESLIF to change it.

Defaults to JSON::MaybeXS::false().

is_bool($value)

Returns a true value if $value is a boolean. You may localize MarpaX::ESLIF::is_bool() function before using ESLIF to change it. ESLIF always requires at least that $value is an object, object nature then defaults to JSON::MaybeXS::is_bool($value)

INTEGER TYPE

ESLIF consider scalars that have only the internal IV flag.

FLOAT TYPE

ESLIF consider scalars that have only the internal NV flag.

STRING TYPE

ESLIF consider scalars that have only the internal PV flag.

SEE ALSO

MarpaX::ESLIF::Introduction, PCRE2, MarpaX::ESLIF::BNF, MarpaX::ESLIF::Logger::Interface, MarpaX::ESLIF::Grammar, MarpaX::ESLIF::Recognizer, Types::Standard, JSON::MaybeXS.

AUTHOR

Jean-Damien Durand <jeandamiendurand@free.fr>

COPYRIGHT AND LICENSE

This software is copyright (c) 2017 by Jean-Damien Durand.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.