NAME

Parse::Eyapp::defaultactionsintro - Introduction to Default Actions and Grammar Reuse

Default Actions

Default actions

When no action is specified both yapp and eyapp implicitly insert the semantic action { $_[1] }. In Parse::Eyapp you can modify such behavior using the %defaultaction { Perl code } directive. The { Perl code } clause that follows the %defaultaction directive is executed when reducing by any production for which no explicit action was specified.

Translator from Infix to Postfix

See an example that translates an infix expression like a=b*-3 into a postfix expression like a b 3 NEG * = :

# File Postfix.eyp (See the examples/ directory)
%right  '='
%left   '-' '+'
%left   '*' '/'
%left   NEG

%defaultaction { return  "$left $right $op"; }

%%
line: $exp  { print "$exp\n" }
;

exp:        $NUM  { $NUM }
        |   $VAR  { $VAR }
        |   VAR.left '='.op exp.right
        |   exp.left '+'.op exp.right
        |   exp.left '-'.op exp.right
        |   exp.left '*'.op exp.right
        |   exp.left '/'.op exp.right
        |   '-' $exp %prec NEG { "$exp NEG" }
        |   '(' $exp ')' { $exp }
;

%%

# Support subroutines as in the Synopsis example
...

The file containing the Eyapp program must be compiled with eyapp:

nereida:~/src/perl/YappWithDefaultAction/examples> eyapp Postfix.eyp

Next, you have to write a client program:

nereida:~/src/perl/YappWithDefaultAction/examples> cat -n usepostfix.pl
     1  #!/usr/bin/perl -w
     2  use strict;
     3  use Postfix;
     4
     5  my $parser = new Postfix();
     6  $parser->Run;

Now we can run the client program:

nereida:~/src/perl/YappWithDefaultAction/examples> usepostfix.pl
Write an expression: -(2*a-b*-3)
2 a * b 3 NEG * - NEG

Default Actions, %name and YYName

In eyapp each production rule has a name. The name of a rule can be explicitly given by the programmer using the %name directive. For example, in the piece of code that follows the name ASSIGN is given to the rule exp: VAR '=' exp.

When no explicit name is given the rule has an implicit name. The implicit name of a rule is shaped by concatenating the name of the syntactic variable on its left, an underscore and the ordinal number of the production rule Lhs_# as it appears in the .output file. Avoid giving names matching such pattern to production rules. The patterns /${lhs}_\d+$/ where ${lhs} is the name of the syntactic variable are reserved for internal use by eyapp.

pl@nereida:~/LEyapp/examples$ cat -n Lhs.eyp
 1  # Lhs.eyp
 2
 3  %right  '='
 4  %left   '-' '+'
 5  %left   '*' '/'
 6  %left   NEG
 7
 8  %defaultaction {
 9    my $self = shift;
10    my $name = $self->YYName();
11    bless { children => [ grep {ref($_)} @_] }, $name;
12  }
13
14  %%
15  input:
16              /* empty */
17                { [] }
18          |   input line
19                {
20                  push @{$_[1]}, $_[2] if defined($_[2]);
21                  $_[1]
22                }
23  ;
24
25  line:     '\n'       { }
26          | exp '\n'   {  $_[1] }
27  ;
28
29  exp:
30              NUM   { $_[1] }
31          |   VAR   { $_[1] }
32          |   %name ASSIGN
33              VAR '=' exp
34          |   %name PLUS
35              exp '+' exp
36          |   %name MINUS
37              exp '-' exp
38          |   %name TIMES
39              exp '*' exp
40          |   %name DIV
41              exp '/' exp
42          |   %name UMINUS
43              '-' exp %prec NEG
44          |  '(' exp ')'  { $_[2] }
45  ;

Inside a semantic action the name of the current rule can be recovered using the method YYName of the parser object.

The default action (lines 8-12) computes as attribute of the left hand side a reference to an object blessed in the name of the rule. The object has an attribute children which is a reference to the list of children of the node. The call to grep

11    bless { children => [ grep {ref($_)} @_] }, $name;

excludes children that aren't references. Notice that the lexical analyzer only returns references for the NUM and VAR terminals:

59  sub _Lexer {
60      my($parser)=shift;
61
62      for ($parser->YYData->{INPUT}) {
63          s/^[ \t]+//;
64          return('',undef) unless $_;
65          s/^([0-9]+(?:\.[0-9]+)?)//
66                  and return('NUM', bless { attr => $1}, 'NUM');
67          s/^([A-Za-z][A-Za-z0-9_]*)//
68                  and return('VAR',bless {attr => $1}, 'VAR');
69          s/^(.)//s
70                  and return($1, $1);
71      }
72      return('',undef);
73  }

follows the client program:

pl@nereida:~/LEyapp/examples$ cat -n uselhs.pl
     1  #!/usr/bin/perl -w
     2  use Lhs;
     3  use Data::Dumper;
     4
     5  $parser = new Lhs();
     6  my $tree = $parser->Run;
     7  $Data::Dumper::Indent = 1;
     8  if (defined($tree)) { print Dumper($tree); }
     9  else { print "Cadena no válida\n"; }

When executed with input a=(2+3)*b the parser produces the following tree:

ASSIGN(TIMES(PLUS(NUM[2],NUM[3]), VAR[b]))

See the result of an execution:

pl@nereida:~/LEyapp/examples$ uselhs.pl
a=(2+3)*b
$VAR1 = [
  bless( {
    'children' => [
      bless( { 'attr' => 'a' }, 'VAR' ),
      bless( {
        'children' => [
          bless( {
            'children' => [
              bless( { 'attr' => '2' }, 'NUM' ),
              bless( { 'attr' => '3' }, 'NUM' )
            ]
          }, 'PLUS' ),
          bless( { 'attr' => 'b' }, 'VAR' )
        ]
      }, 'TIMES' )
    ]
  }, 'ASSIGN' )
];

The name of a production rule can be changed at execution time. See the following example:

29  exp:
30              NUM   { $_[1] }
31          |   VAR   { $_[1] }
32          |   %name ASSIGN
33              VAR '=' exp
34          |   %name PLUS
35              exp '+' exp
36          |   %name MINUS
37              exp '-' exp
38                {
39                  my $self = shift;
40                  $self->YYName('SUBSTRACT'); # rename it
41                  $self->YYBuildAST(@_); # build the node
42                }
43          |   %name TIMES
44              exp '*' exp
45          |   %name DIV
46              exp '/' exp
47          |   %name UMINUS
48              '-' exp %prec NEG
49          |  '(' exp ')'  { $_[2] }
50  ;

When the client program is executed we can see the presence of the SUBSTRACT nodes:

pl@nereida:~/LEyapp/examples$ useyynamedynamic.pl
2-b
$VAR1 = [
  bless( {
    'children' => [
      bless( {
        'attr' => '2'
      }, 'NUM' ),
      bless( {
        'attr' => 'b'
      }, 'VAR' )
    ]
  }, 'SUBSTRACT' )
];

Grammar Reuse

An Action Method for each Production

Default actions provide a way to write reusable grammars. Here is one solution:

pl@europa:~/LEyapp/examples/recycle$ cat -n Noactions.eyp
   1  %left   '+'
   2  %left   '*'
   3
   4  %defaultaction {
   5    my $self = shift;
   6
   7    my $class = $self->YYName;
   8
   9    $class->action(@_);
  10  }
  11
  12  %%
  13  exp:        %name NUM
  14                NUM
  15          |   %name PLUS
  16                exp '+' exp
  17          |   %name TIMES
  18                exp '*' exp
  19          |   '(' exp ')'
  20                { $_[2] }
  21  ;
  22
  23  %%
  24
  25  sub _Error {
  26    my($token)=$_[0]->YYCurval;
  27    my($what)= $token ? "input: '$token'" : "end of input";
  28    my @expected = $_[0]->YYExpect();
  29
  30    local $" = ', ';
  31    die "Syntax error near $what. Expected one of these tokens: @expected\n";
  32  }
  33
  34
  35  my $x = '';
  36
  37  sub _Lexer {
  38    my($parser)=shift;
  39
  40    for ($x) {
  41      s/^\s+//;
  42      $_ eq '' and return('',undef);
  43
  44      s/^([0-9]+(?:\.[0-9]+)?)//   and return('NUM',$1);
  45      s/^([A-Za-z][A-Za-z0-9_]*)// and return('VAR',$1);
  46      s/^(.)//s                    and return($1,$1);
  47    }
  48  }
  49
  50  sub Run {
  51    my($self)=shift;
  52    $x = shift;
  53    my $debug = shift;
  54
  55    $self->YYParse(
  56      yylex => \&_Lexer,
  57      yyerror => \&_Error,
  58      yydebug => $debug,
  59    );
  60  }

This grammar can be reused by a program that implements a calculator:

pl@europa:~/LEyapp/examples/recycle$ cat -n calcu.pl
   1  #!/usr/bin/perl
   2  use warnings;
   3  use Noactions;
   4
   5  sub NUM::action {
   6    return $_[1];
   7  }
   8
   9  sub PLUS::action {
  10    $_[1]+$_[3];
  11  }
  12
  13  sub TIMES::action {
  14    $_[1]*$_[3];
  15  }
  16
  17  my $parser = Noactions->new();
  18  print "Write an expression: ";
  19  my $x;
  20  {
  21    local $/ = undef;
  22    $x = <>;
  23  }
  24  my $t = $parser->Run($x);
  25
  26  print "$t\n";

But it also can be used by a program that translates infix to postfix expressions:

pl@europa:~/LEyapp/examples/recycle$ cat -n postf.pl
   1  #!/usr/bin/perl
   2  use warnings;
   3  use Noactions;
   4
   5  sub NUM::action {
   6    return $_[1];
   7  }
   8
   9  sub PLUS::action {
  10    "$_[1] $_[3] +";
  11  }
  12
  13  sub TIMES::action {
  14    "$_[1] $_[3] *";
  15  }
  16
  17  my $parser = Noactions->new();
  18  print "Write an expression: ";
  19  my $x;
  20  {
  21    local $/ = undef;
  22    $x = <>;
  23  }
  24  my $t = $parser->Run($x);
  25
  26  print "$t\n";

Reusing Grammars Using Inheritance

An laternative method is to use inheritance. The grammar inherits the action methods from the client:

pl@europa:~/LEyapp/examples/recycle$ cat -n NoacInh.eyp
   1  %left   '+'
   2  %left   '*'
   3
   4  %defaultaction {
   5    my $self = shift;
   6
   7    my $action = $self->YYName;
   8
   9    $self->$action(@_);
  10  }
  11
  12  %%
  13  exp:        %name NUM
  14                NUM
  15          |   %name PLUS
  16                exp '+' exp
  17          |   %name TIMES
  18                exp '*' exp
  19          |   '(' exp ')'
  20                { $_[2] }
  21  ;
  22
  23  %%
  24
  25  sub _Error {
  26    my($token)=$_[0]->YYCurval;
  27    my($what)= $token ? "input: '$token'" : "end of input";
  28    my @expected = $_[0]->YYExpect();
  29
  30    local $" = ', ';
  31    die "Syntax error near $what. Expected one of these tokens: @expected\n";
  32  }
  33
  34
  35  my $x = '';
  36
  37  sub _Lexer {
  38    my($parser)=shift;
  39
  40    for ($x) {
  41      s/^\s+//;
  42      $_ eq '' and return('',undef);
  43
  44      s/^([0-9]+(?:\.[0-9]+)?)//   and return('NUM',$1);
  45      s/^([A-Za-z][A-Za-z0-9_]*)// and return('VAR',$1);
  46      s/^(.)//s                    and return($1,$1);
  47    }
  48  }
  49
  50  sub Run {
  51    my($self)=shift;
  52    $x = shift;
  53    my $debug = shift;
  54
  55    $self->YYParse(
  56      yylex => \&_Lexer,
  57      yyerror => \&_Error,
  58      yydebug => $debug,
  59    );
  60  }

This programs icalcu.pl injects the actions into the parser package, implementing this way a calculator:

pl@europa:~/LEyapp/examples/recycle$ cat -n icalcu.pl
   1  #!/usr/bin/perl
   2  use warnings;
   3  use NoacInh;
   4
   5  package Actions;
   6  push @NoacInh::ISA, 'Actions';
   7
   8  sub NUM {
   9    return $_[1];
  10  }
  11
  12  sub PLUS {
  13    $_[1]+$_[3];
  14  }
  15
  16  sub TIMES {
  17    $_[1]*$_[3];
  18  }
  19
  20  package main;
  21
  22  my $parser = NoacInh->new();
  23  print "Write an expression: ";
  24  my $x;
  25  {
  26    local $/ = undef;
  27    $x = <>;
  28  }
  29  my $t = $parser->Run($x);
  30
  31  print "$t\n";

In the same way this program implements the translator:

pl@europa:~/LEyapp/examples/recycle$ cat -n ipostf.pl
   1  #!/usr/bin/perl
   2  use warnings;
   3  use NoacInh;
   4
   5  package Actions;
   6  push @NoacInh::ISA, 'Actions';
   7
   8  sub NUM {
   9    return $_[1];
  10  }
  11
  12  sub PLUS {
  13    "$_[1] $_[3] +";
  14  }
  15
  16  sub TIMES {
  17    "$_[1] $_[3] *";
  18  }
  19
  20  package main;
  21
  22  my $parser = NoacInh->new();
  23  print "Write an expression: ";
  24  my $x;
  25  {
  26    local $/ = undef;
  27    $x = <>;
  28  }
  29  my $t = $parser->Run($x);
  30
  31  print "$t\n";

SEE ALSO

REFERENCES

  • The classic Dragon's book Compilers: Principles, Techniques, and Tools by Alfred V. Aho, Ravi Sethi and Jeffrey D. Ullman (Addison-Wesley 1986)

AUTHOR

Casiano Rodriguez-Leon (casiano@ull.es)

ACKNOWLEDGMENTS

This work has been supported by CEE (FEDER) and the Spanish Ministry of Educacion y Ciencia through Plan Nacional I+D+I number TIN2005-08818-C04-04 (ULL::OPLINK project http://www.oplink.ull.es/). Support from Gobierno de Canarias was through GC02210601 (Grupos Consolidados). The University of La Laguna has also supported my work in many ways and for many years.

A large percentage of code is verbatim taken from Parse::Yapp 1.05. The author of Parse::Yapp is Francois Desarmenien.

I wish to thank Francois Desarmenien for his Parse::Yapp module, to my students at La Laguna and to the Perl Community. Special thanks to my family and Larry Wall.

LICENCE AND COPYRIGHT

Copyright (c) 2006-2008 Casiano Rodriguez-Leon (casiano@ull.es). All rights reserved.

Parse::Yapp copyright is of Francois Desarmenien, all rights reserved. 1998-2001

These modules are free software; you can redistribute it and/or modify it under the same terms as Perl itself. See perlartistic.

This program 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.

1 POD Error

The following errors were encountered while parsing the POD:

Around line 176:

Non-ASCII character seen before =encoding in 'válida\n";'. Assuming UTF-8