Problems with skip directive and error messages.
[email protected] (John Tobin) Tue, 24 Jun 2003 21:32:30 +0100
| Newsgroups | perl.recdescent |
|---|---|
| Message-ID | <[email protected]> |
Hi all,
I've been working on this on and off for a fortnight now, I've read the
list archives, read the tutorial, read the manpage, read the FAQ,
googled, read through examples, googled . . . and I'm still not getting
anywhere. Any help would be greatly appreciated.
I'm writing a simple program to backup the servers here in work (I know
about Amanda; it's not doing what we want and it's regularly failing
here). It's pretty much complete except for parsing the config file,
which has a relatively simple syntax:
tapedevice /path/to/device
monthly yes
backup {
filesystem /
filesystem /usr
monthly no
weekly yes
}
. . .
Global directives appear outside a backup section; the global section
must be followed by a backup section; there may be multiple
(global,backup) pairs within the config file. Most directives
can be given in a either a backup or global section, but some are
specifically global or backup only.
So far I can parse any number of config files, and it all works fine,
creating my data structures etc, except for two problems.
PROBLEM 1.
I want to follow the standard Unix config file conventions of # starting
a comment, whitespace being insignificant and \ escaping a newline. I
figured I'd do this by <skip: qr/.../>, which is working. But if I try
$Parse::RecDescent::skip = qr/.../ it fails. The actual regex is:
qr/((\s*\#.*\n)|(\s*\\\n)|\s*)*/
Which broken down is:
(\s*\#.*\n) # match a comment
(\s*\\\n) # backslash escaped newline
\s* # whitespace
Match any of the three any number of times.
The shortest test case I've come up with is:
-------------------------------------------------
#!/usr/bin/perl -w
use strict;
use warnings;
use Parse::RecDescent;
$::RD_HINT = 1;
$::RD_TRACE = 1;
my $parser1 = new Parse::RecDescent ( <<'GRAMMAR1' );
config: <skip: qr/((\s*\#.*\n)|(\s*\\\n)|\s*)*/>
"tapedevice" "foo"
GRAMMAR1
rename "RD_TRACE", "RD_TRACE.1";
$Parse::RecDescent::skip = qr/((\s*\#.*\n)|(\s*\\\n)|\s*)*/;
my $parser2 = new Parse::RecDescent ( <<'GRAMMAR2' );
config:
"tapedevice" "foo"
GRAMMAR2
rename "RD_TRACE", "RD_TRACE.2";
my $text = <<'TEXT';
tapedevice \
foo
TEXT
print "before parser1: $text\n";
my $retval1 = $parser1->config ( $text );
defined $retval1 or warn "parser1 failed\n";
print "retval1: $retval1\n";
print "before parser2: $text\n";
my $retval2 = $parser2->config ( $text );
defined $retval2 or warn "parser2 failed\n";
print "retval2: $retval2\n";
-------------------------------------------------
Looking through RD_TRACE.1 and RD_TRACE.2 I see that RD_TRACE.1 has:
$skip= qr/((\s*\#.*\n)|(\s*\\\n)|\s*)*/;
whereas RD_TRACE.2 has:
$skip = '(?-xism:((\s*\#.*\n)|(\s*\\\n)|\s*)*)';
I've tried various combinations of backslashes incase it's an
interpolation issue, but that hasn't helped. At this stage I'm stuck.
Obviously I could just stick with using the skip directive instead of
$Parse::RecDescent::skip, but I've spent long enough battling with this
that I want to know why it doesn't work.
PROBLEM 2:
I'm trying to get error messages from the parser I've created and I
can't figure out how. Here's a contrived example because the real
grammar is too long to post; I'll explain my problems after it.
my $grammar = <<'GRAMMAR';
config: global_line(s?) backup /\s*/
global_lines: "foo" <commit> boolean
| "bar" <commit> string
| common
boolean: /\b(yes|true|on|1)\b/
| /\b(no|false|off|0)\b/
| <error>
string: /.*/
backup: "backup" "{" backup_line(s?) "}"
backup_line: "yellow" <commit> boolean
| "blue" <commit> string
| common
common: "burger" <commit> boolean
| "chips" <commit> boolean
| "pizza" <commit> string
| <error?>
GRAMMAR
If you feed this a correct file it's fine; feed it an incorrect file
containing something like:
yellow custard
and it fails to parse (good), but doesn't give a helpful error message.
Because global_line can match 0 times, even if the grammar generates an
error the error is ignored, until we get all the way back up the stack
and down into backup_lines, even if I stack | <error> all the way up.
When the message is eventually displayed, it says something like:
found "yellow", expecting "backup"
This is incorrect: it should say something like:
found "custard", expecting boolean
The <commit> does at least prune the tree, but I guess what I'm looking
for is a <reallycommit>, because I know if I reach the <commit> but fail
to match afterwards, there really, really is an error and it's never
going to match. There was a similar mail to this list months ago, but
nothing suggested has worked for me. Has anyone any ideas on how I can
achieve this?
Also when I do get error messages (from stacking <error> all the way up)
I get incorrect (negative) line numbers: instead of being $thisline,
it's: $thisline - number of lines in file. I'm working around this by
splitting the file into an array, splitting the non-matched text into an
array, then figuring out the line number, but it's a nasty solution.
Anyone else come across this and found a better solution?
Thanks for your time, help and reading all this way through such a long
mail :)
--
John Tobin
[Parrot] will have reflection, introspection, and Deep Meditative
Capabilities.
Dan Sugalski, 2002/07/11, [email protected]