Problem to get Parse::Yapp and Parse:Flex working together
am 02.11.2005 15:15:01 von tonl
A) I did:
# cd ~/.cpan/build/Parse-Flex-0.03
# more src/default.y
%{
#define YY_DECL char* yyylex YY_PROTO(( void ))
#undef yywrap
int yywrap(void) { return 1 ;}
char buffer[15];
%}
%%
[a-z]+ { return strcpy(buffer, "VAR"); }
[0-9]+ { return strcpy(buffer, "NUM"); }
[ \t]+ ;
.. { return strcpy(buffer, yytext); }
\n { return strcpy(buffer, yytext); }
<> return "" ;
%%
And then make and install. A quick test
$ perl -MParse::Flex -e 'print join " ", yylex(),"\n"'
abc
VAR abc
I borrowed the Calc example from Parse::Yapp, but want to use Flex
instead, thus
# cat Calc
use Calc;
use Parse::Flex;
sub yapp_error {
my $self = shift;
warn "Error: found ", $self->YYCurtok,
" and expecting one of ", join(" or ",$self->YYExpect);
}
my $calc = new Calc;
$calc->YYParse( yylex => yylex, yyerror => \&yapp_error );
This however results in:
Invalid value for parameter 'YYLEX' at Calculator line 12
I think the reason is that the yylex is also invoking the Parse::Flex
the first time.
I changed Parse::Flex, and renames yylex in flex:
$calc->YYParse( yylex => flex, yyerror => \&yapp_error );
to no avail. I can do
($token,$value) = flex;
What do I miss/oversee here ??
B) For sure I wrote my own My::FLex, with a different approach. In a
nutshell (I'm happy to append details on request)
In Flex.pm:
sub flex {
my $token = yylex();
if ($token) {
my $value = yytext();
# print $token, " = ", $value, "\n";
return ($token, $value);
}
else {
# print "Undefined token\n";
return (undef, "");
}
}
yylex()
OUTPUT:
RETVAL
char*
yytext()
CODE:
RETVAL = yytext;
OUTPUT:
RETVAL
Make and install the module and simply working.
my $calc = new Calc;
$calc->YYParse( yylex => flex, yyerror => \&yapp_error );
and that is the same invokation as above.
Best Regards,
Ton 't Lam
Getting Parse::Yapp and Parse:Flex working together
am 03.11.2005 11:50:24 von tonl
General
Parse::Yapp and flex is a powerful combination to do grammar and pattern
matching. You have the joy of the fast lexical analyzer, and within Perl
you create a program easily.
Yapp expects two variables, and per consequence the flex module should
provide these. The problem is that both Parse::Yapp, and flex use yylex.
I couldn't find Perl syntax to force the first yylex to be from Parse::Yapp.
$calc->YYParse( yylex => yylex, ...);
To go short: I changed Parse::Flex, as well created My::Flex. The fatest
method is to use Parse::Flex.
Parse::Flex
About the man page
The Parse::Flex manual says:
yylex() Get the name of next token, and indirectly sets yytext . Returns
undef for end of input.
You should read this as:
yylex() Get the name of next token, and indirectly sets yytext . Returns
undef for end of input.
Two variables are returned.
yying = 'datafile'
should be:
yyin = 'datafile'
Modifying Parse::Flex
Modify Flex.xs:
void
flex()
PPCODE:
char* id = 0;
if (id = yyylex() ) {
XPUSHs (sv_2mortal(newSVpv(id,0)));
XPUSHs (sv_2mortal(newSVpv( yytext, 0)));
XSRETURN(2);
}
XSRETURN_EMPTY;
Modify Flex.pm
our @EXPORT = qw( yyin yyout flex );
and src/default.y contains e.g.:
%{
#define YY_DECL char* yyylex YY_PROTO(( void ))
#undef yywrap
int yywrap(void) { return 1 ;}
%}
%%
[a-z]+ { return "VAR"; }
[0-9]+ { return "NUM"; }
[ \t]+ ;
.. ECHO;
\n ECHO;
<> return "" ;
%%
# make
# make install
A quick test
$ perl -MParse::Flex -e 'print join " ", flex(),"\n"'
abc
VAR abc
Yapp
Install Parse::Yapp
# cpan -i Parse::Yapp
Take the Calc.yp example from Parse::Yapp. We will use this example, but
of course use the flex lexer instead. Parse::Yapp expects a yylex
function that returns a pair: the name of the token and the matched text.
From the Calc.yp keep
#
# Calc.yp
#
# Parse::Yapp input grammar example.
#
# This file is PUBLIC DOMAIN
#
#
%right '='
%left '-' '+'
%left '*' '/'
%left NEG
%right '^'
%%
input: #empty
| input line { push(@{$_[1]},$_[2]); $_[1] }
;
line: '\n' { $_[1] }
| exp '\n' { print "$_[1]\n" }
| error '\n' { $_[0]->YYErrok }
;
exp: NUM
| VAR { $_[0]->YYData->{VARS}{$_[1]} }
| VAR '=' exp { $_[0]->YYData->{VARS}{$_[1]}=$_[3] }
| exp '+' exp { $_[1] + $_[3] }
| exp '-' exp { $_[1] - $_[3] }
| exp '*' exp { $_[1] * $_[3] }
| exp '/' exp {
$_[3]
and return($_[1] / $_[3]);
$_[0]->YYData->{ERRMSG}
= "Illegal division by zero.\n";
$_[0]->YYError;
undef
}
| '-' exp %prec NEG { -$_[2] }
| exp '^' exp { $_[1] ** $_[3] }
| '(' exp ')' { $_[2] }
;
%%
sub yapp_error {
my $self = shift;
warn "Error: found ", $self->YYCurtok,
" and expecting one of ", join(" or ",$self->YYExpect);
}
# yapp Calc.yp
The result is a Calc.pm in your current directory.
Yapp invokes flex
# cat Calc
use Calc;
use Parse::Flex;
sub yapp_error {
my $self = shift;
warn "Error: found ", $self->YYCurtok,
" and expecting one of ", join(" or ",$self->YYExpect);
}
my $calc = new Calc;
# $calc->YYParse( yylex => \&My::Flex::flex, yyerror => \&Calc::yapp_error);
$calc->YYParse( yylex => \&flex, yyerror => \&Calc::yapp_error);
# perl Calc
12+34
46
12++34
Error: found + and expecting one of - or NUM or VAR or ( at Calc.yp line 48.
My::Flex
Here are steps to create your own flex module My::Flex
Here:
* Parse::Flex supplies flex (Flex.pm)
* flex (Flex.pm) calls yylex, and yytext (Flex.xs).
* Flex.xs calls the functions from lex.yy.o, thus Flex.l.
Next step is to build an XS module, that invokes flex to do the pattern
matching.
Create the module structure:
# h2xs -O -n My::Flex
# cd My-Flex
Create a file MyFlex.h
# more MyFlex.h
extern char* yylex();
extern char* yytext;
extern FILE *yyin, *yyout;
Modify Flex.xs, so that
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "ppport.h"
#include "const-c.inc"
#include "myFlex.h"
MODULE = My::Flex PACKAGE = My::Flex
INCLUDE: const-xs.inc
char*
yylex()
CODE:
RETVAL = yylex();
OUTPUT:
RETVAL
char*
yytext()
CODE:
RETVAL = yytext;
OUTPUT:
RETVAL
void
yyin( file)
char* file
CODE:
yyin = fopen( file, "r");
void
yyout( file)
char* file
CODE:
yyout = fopen( file, "w");
and the myFlex.l
# more myFlex.l
%{
#define YY_DECL char* yylex() void;
char buffer[15];
%}
%%
apple ECHO;
pear ECHO;
[a-z]+ { return strcpy(buffer, "VAR"); }
[0-9]+ { return strcpy(buffer, "NUM"); }
[ \t]+ ;
\n { return strcpy(buffer, yytext); }
.. { return strcpy(buffer, yytext); }
%%
int perl_yywrap(void) {
return 1;
}
We have to map the data types from C to Perl. Next map translates the
pointers to characters to the built-in T_PV Perl type.
# cat typemap
char* T_PV
In lib/My/Flex.pm enter
# Preloaded methods go here.
sub flex {
my $token = yylex();
if ($token) {
my $value = yytext();
# print $token, " = ", $value, "\n";
return ($token, $value);
}
else {
# print "Undefined token\n";
return (undef, "");
}
}
Now the lib/My/Flex.pm
# our %EXPORT_TAGS = ( 'all' => [ qw( ) ] );
# our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
our @EXPORT = qw(yyin yyout flex );
....
sub flex {
my $token = yylex();
if ($token) {
my $value = yytext();
# print $token, " = ", $value, "\n";
return ($token, $value);
}
else {
# print "Undefined token\n";
return (undef, "");
}
}
Next step is to modify the Makefile.PL
WriteMakefile(
NAME => 'My::Flex',
VERSION_FROM => 'lib/My/Flex.pm', # finds $VERSION
PREREQ_PM => {}, # e.g., Module::Name => 1.1
($] >= 5.005 ? ## Add these new keywords supported since 5.005
(ABSTRACT_FROM => 'lib/My/Flex.pm', # retrieve abstract from module
AUTHOR => 'Ton ') : ()),
LIBS => ['-lfl'], # e.g., '-lm'
DEFINE => '', # e.g., '-DHAVE_SOMETHING'
INC => '-I.', # e.g., '-I. -I/usr/include/other'
MYEXTLIB => 'myFlex.so' # our Flexer (note there is no comma)
# Un-comment this if you add C files to link with later:
# OBJECT => '$(O_FILES)', # link all the C files too
);
sub MY::postamble {
"
\$(MYEXTLIB): lex.yy.c Flex.o
\t\$(CC) -c lex.yy.c
\t\$(AR) cr \$(MYEXTLIB) lex.yy.o
\tranlib \$(MYEXTLIB)
lex.yy.c: myFlex.l
\tflex myFlex.l
";
}
It is time to compile
# perl Makefile.PL
# make
# make test # FWIW
# make install
If you don have root access, then do
$ perl Makefile.PL PREFIX=/home/tonl
$ make
$ make install
If you want to re-compile then remove the *.[co] files first, or make clean.
Test if the module is installed.
# perl -MMy::Flex -e 1
A quick test
# perl -MMy::Flex -e 'print join " ", My::Flex::flex(),"\n"'
abc
VAR abc
And finally invoke the modules, and do the calculation ...
# use lib "/home/tonl"; # if you installed locally
use Calc;
use My::Flex;
My::Flex::yyin("file");
My::Flex::yyout "res";
My::Flex::flex();
print "===\n";
my $calc = new Calc;
$calc->YYParse( yylex => \&flex, yyerror => \&Calc::yapp_error);
and file contains
apple
pear
1+2
abc=4
def=5
abc+def
apple
# perl Calc
===
3
4
5
9
and res contains
applepearapple
Note about yyout:
flex its ECHO directs output to yyout, and the other output is return to
the Perl program.
I could have done eqally the following:
void
flex()
PPCODE:
char* id = 0;
if (id = yylex() ) {
XPUSHs (sv_2mortal(newSVpv(id,0)));
XPUSHs (sv_2mortal(newSVpv( yytext, 0)));
XSRETURN(2);
}
XSRETURN_EMPTY;
This will cause an Perl array to be returned. Then you don't need to
modify lib/My/Flex.pm.
$ perl -MMy::Flex -e 'print join " ", My::Flex::flex(),"\n"'
abc
VAR abc
use Calc;
use My::Flex;
my $calc = new Calc;
$calc->YYParse( yylex => \&My::Flex::flex(), yyerror => \&Calc::yapp_error);
References
* Parse::Yapp
* Parse::Flex
* perlxstut
Author
Ton 't Lam
Getting Parse::Yapp and Parse:Flex working together (appendix)
am 04.11.2005 18:46:29 von tonl
You may come accross next error message:
Usage: Parse::Flex::flex() at
/opt/perl/lib/site_perl/5.8.2/Parse/Yapp/Driver.pm line 271.
and if you try that
Invalid value for parameter 'YYLEX' at Calc line 11
The reason is that in Driver.pm next invokation is made:
($$token,$$value)=&$lex($self);
In other words, a parameter is expected. flex() doesn't accept this. The
workaround is to have next:
use Calc;
use Parse::Flex;
sub _Flex {
flex; # or Parse::Flex::flex;
}
my $calc = new Calc;
$calc->YYParse( yylex => \&_Flex, yyerror => \&Calc::yapp_error);
Another easy solution is to have flex accept the variable.
void
flex(path)
SV * path;
PPCODE:
and then you can have:
my $calc = new Calc;
$calc->YYParse( yylex => \&flex, yyerror => \&Calc::yapp_error );
Remark: This happened with Parse::Flex, but not with My::Flex.
Linefeed not accepted
Unknown why this is. Entering a second linefeed is accepted.
# echo "1+2" | perl Calc
works as expected. Also if using
yyin "file";
Remark: This happened with Parse::Flex, but not with My::Flex.