Parsing series of optional token classes
[email protected] (Marcel Grunauer)
| Newsgroups | perl.recdescent |
|---|---|
| Message-ID | <20011028102718.TNMJ9577.viefep11-int.chello.at@localhost> |
I've had occasion to parse some weather observation formats - METAR,
SYNOP, ACMOS; formats that seem to have been organically grown
rather than well-designed. Each of a few thousand weather stations
seems to use their own variation of a somewhat standardized format.
Here's a general overview of the format: There is a series of token
classes that can appear in a certain sequence. Each token class is
represented by a rule (usually of the same name). Some classes only
have one possibility, such as the 'metar' class, where the 'METAR'
token is either there or not. Some classes can have several tokens,
such as weather groups or sky groups.
So you can have
METAR BKN050 OVC110 09/ =
That would mean 'broken clouds at 5000 feet, overcast at 11000
feet', temperature 9 degrees, and '=' is the end-of-report marker.
All of the classes are optional. That is, for example, the 'sky_groups'
class doesn't have to be there, but if it is there, it has to be
between the 'weather_groups' and 'temperature' classes (which are
themselves optional).
Now I was looking for a way of parsing such a situation. This is
what this posting is about, and I'd appreciate feedback - is there
a better way (probably)?
I've divided the parse in phases. The local variable $phase is used
to keep track of token classes we've already seen.
If we can't parse a given token, we note an error but move on. That
is, we don't just stop parsing altogether but act as if the erroneous
token simply wasn't there. To get that effect, we use the following
technique: There's a parametrized rule called 'token' that takes
as arguments a rule name and a phase number. All the possible tokens
are called in an alternation, and the allowed sequence defines the
phase number assigned to each token class. See the production below
for how this works.
Now the 'token' dynamic rule checks whether the phase is higher
than the last one we've seen, then tries to match the current token
against the appropriate rule, and if successful, sets the phase to
the given level. That way, as each new token is received, it is
checked against all the alternation's options, but the 'token' rule
only lets through those options with a phase higher than the last
one.
If none of the options of the alternation matched, we accept the
token as an error and move on.
That way we have the flexibility of skipping erroneous tokens but
make sure that tokens are only checked in the right order.
report : <rulevar: local $phase = 0>
report :
( token[rule => 'metar', phase => 1]
| token[rule => 'icao_code', phase => 2]
| token[rule => 'rep_date_time', phase => 3]
| token[rule => 'rep_version', phase => 4]
| token[rule => 'auto', phase => 5]
| token[rule => 'wind', phase => 6]
| token[rule => 'visibility', phase => 7]
| token[rule => 'runway_visi_groups', phase => 8]
| token[rule => 'weather_groups', phase => 9]
| token[rule => 'sky_groups', phase => 10]
| token[rule => 'temperature', phase => 11]
| token[rule => 'altimeter', phase => 12]
| token[rule => 'nosig', phase => 13]
| token[rule => 'remarks', phase => 14]
| token[rule => 'nil', phase => 15]
| m![A-Z0-9/]+! { error("unknown token <$item[-1]>") }
)(s)
'='
token : <reject: do{ $phase >= $arg{phase} }> <matchrule:$arg{rule}>
{ $phase = $arg{phase} }
metar : 'METAR'
icao_code : ... some subrules ...
rep_date_time : ...some subrules...
# etc.
What do you think about this? Useful, too complicated?
Marcel
--
Aspect-Oriented Perl http://codewerk.unixbeard.net/aspects/
cpan> install Aspect