Skip to content
VLSI Mentor

Perl Part II · Module 18 · Log Parsing and Text Processing

Tolerating Tool-Version Differences

A parser outlives the tool release it was written against. Someone upgrades the tool, a field gets a slightly different label in the report, and the parser does not fail — it finds nothing where that field used to be, returns an undefined value, and that undefined value becomes zero the moment anybody does arithmetic with it. Zero area, zero violations, zero coverage: all plausible numbers, none of them measured. This chapter shows the shape of a parser that accepts several spellings of the same fact, and spends most of its length on the harder half, which is making sure an unrecognised report is reported as unrecognised rather than as clean.

Advanced15 min readPerlPart IILog Parsing

Part II · Module 18 · Chapter 18.8 · Tolerating Tool-Version Differences

1. A Renamed Field Returns Zero

Azvya Education Pvt. Ltd.VLSI Mentor
version-tolerance.pl
use strict;
use warnings;

# Three SAMPLE spellings of the same fact. These are illustrative shapes --
# not a claim about the field names of any particular tool release.
my %report = (
  "v2021" => "  Total cell area:        21246.90\n",
  "v2023" => "  Total area (cells):     21246.90\n",
  "v2025" => "  cell_area_total =       21246.90\n",
);

# ---- A: one pattern, written against the release you had at the time
my $ONE = qr/^\s+Total cell area:\s+([\d.]+)/;
printf "%-8s %-11s %s\n", "REPORT", "MATCH?", "what the caller gets";
for my $v (sort keys %report) {
    my ($area) = $report{$v} =~ $ONE;
    printf "%-8s %-11s %s\n", $v,
        (defined $area ? "matched" : "NO MATCH"),
        (defined $area ? $area : "undef, which becomes 0 in arithmetic");
}

# ---- B: a list of accepted spellings, tried in order
my @ACCEPT = (
  [ "the 2021 spelling", qr/^\s+Total cell area:\s+([\d.]+)/      ],
  [ "the 2023 spelling", qr/^\s+Total area \(cells\):\s+([\d.]+)/ ],
  [ "the 2025 spelling", qr/^\s+cell_area_total\s*=\s*([\d.]+)/   ],
);

sub read_area {
    my ($text) = @_;
    for my $a (@ACCEPT) {
        my ($v) = $text =~ $a->[1];
        return ($v, $a->[0]) if defined $v;
    }
    return (undef, undef);            # UNRECOGNISED -- not zero
}

print "\ntolerant reader:\n";
for my $v (sort keys %report) {
    my ($area, $via) = read_area($report{$v});
    printf "  %-8s %-13s %s\n", $v, (defined $area ? $area : "UNRECOGNISED"),
        (defined $via ? "via $via" : "");
}

# ---- and a spelling nobody has seen yet
my $future = "  area.total.cells :      21246.90\n";
my ($area, $via) = read_area($future);
printf "  %-8s %-13s %s\n", "v2027?", (defined $area ? $area : "UNRECOGNISED"),
    (defined $via ? "via $via" : "<- must be an ERROR, not a zero");
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
REPORT   MATCH?      what the caller gets
v2021    matched     21246.90
v2023    NO MATCH    undef, which becomes 0 in arithmetic
v2025    NO MATCH    undef, which becomes 0 in arithmetic

tolerant reader:
  v2021    21246.90      via the 2021 spelling
  v2023    21246.90      via the 2023 spelling
  v2025    21246.90      via the 2025 spelling
  v2027?   UNRECOGNISED  <- must be an ERROR, not a zero

The single pattern matched one report in three. For the other two it returned undef — and the caller does not get an error. It gets a value that is false, that compares equal to zero, and that prints as an empty string.

So a flow step that reports "total cell area: 0.00" after a tool upgrade has not detected a problem. It has a parser that stopped working, and 0 is what a parser that stopped working returns. Chapter 14.7 measured that undef becomes 0 in arithmetic with nothing more than a warning, and Chapter 14.2 measured that the warning goes to standard error, which a flow discards.

The tolerant reader read all three and said which spelling it used — and refused the fourth.

2. A List of Accepted Spellings

The structure is a table and a loop:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
my @ACCEPT = (
  [ "label for a message", qr/.../ ],
  ...
);
for my $a (@ACCEPT) {
    my ($v) = $text =~ $a->[1];
    return ($v, $a->[0]) if defined $v;
}
return (undef, undef);

Four properties worth having:

Each spelling keeps a human label. via the 2023 spelling in a log line tells the next person what the parser thought it was reading, which is most of a diagnosis.

defined $v, not if ($v). A legitimate area of 0.00 — an empty design, a report generated before synthesis — is false. Testing truth rejects a real measurement. Chapter 2.7's definedness.

Order is deliberate. Put the spelling you expect most often first; it is also the only thing that matters when two patterns could both match, which happens more than you would like when one spelling is a prefix of another.

The fall-through returns undef, explicitly. Not 0, not "". The absence of a value has to be representable, which is the subject of section 3.

Why Not One Pattern With |?

The obvious compression is to write the three spellings as one alternation (Chapter 11.9):

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
my $ALL = qr/^\s+(?:Total cell area:|Total area \(cells\):|cell_area_total\s*=)\s+([\d.]+)/;

It works, and it costs you three things worth more than the lines it saves.

You lose which spelling matched, so the diagnostic in section 3 cannot say "via the 2023 spelling" — and that string is the fastest way to tell a renamed field from a genuinely absent one.

The capture numbering becomes fragile. Add a spelling with a group of its own and every $2 downstream shifts. The list keeps each pattern's $1 as its own $1.

A long alternation is unreviewable. Nobody in a code review spots a missing backslash in a 120-character pattern; everybody spots a wrong line in a table.

Keep each spelling in its own qr// — Chapter 11.18 measured that a compiled pattern costs nothing to store and can be built once and reused — and keep the table as data. Where the dispatch needs to differ per spelling, not just the pattern, store a code reference beside it: Chapter 7.11's dispatch table, with the pattern and its handler in the same row.

3. UNRECOGNISED Is Not Zero

This is the half that makes the chapter Critical, and it is the same argument Chapter 18.2 made about a missing log.

A value you could not find is not a value of zero. The three outcomes a field reader can have are:

  • a number — the field was found and parsed
  • a documented absence — the field is legitimately not in this report, which happens: a timing report has no area section
  • unrecognised — the field should be here and nothing matched

Collapsing the third into either of the others is the bug. And the third needs to be loud:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
my ($area, $via) = read_area($text);
if (!defined $area) {
    print STDERR "error: no recognised 'total cell area' field in $file\n";
    print STDERR "       tried: " . join(", ", map { $_->[0] } @ACCEPT) . "\n";
    exit 3;                             # 15.4: could not reach a verdict
}

Three things in that handler earn their place:

It names the file. In a flow processing forty reports, "no recognised field" without a filename is not actionable.

It lists what it tried. The person reading this message has the report open. Telling them the three spellings you looked for lets them add the fourth in one minute; telling them only that it failed costs an hour.

It exits 3, not 1. Chapter 15.4's distinction: this is not "the design failed", it is "I could not tell". A flow that treats them the same reports a design failure for a tool upgrade.

4. Version-Keyed or Version-Blind

There are two designs here and the less obvious one is better.

Version-keyed: read the banner, look up the patterns for that version, use them.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
my %BY_VERSION = ( "v2021" => { area => qr/.../ }, "v2023" => { area => qr/.../ } );
my $pat = $BY_VERSION{$version}{area} or die "unsupported version $version\n";

This looks rigorous and has two real problems. It fails on an unknown version even when the format has not changed — a v2024 report whose area line is spelled exactly as v2023's is rejected for no reason. And the version string itself is not reliably parseable; patch releases, site-local builds and vendor rebrands all change it.

Version-blind: try every spelling you know, regardless of the version.

This is the design measured in section 1. A v2024 report is read correctly by the v2023 pattern because the spelling is the same, and the version is used only in the message when nothing matches.

So: match on the format, report with the version. The patterns decide what you can read; the version tells a human why you could not.

One caveat worth knowing. Version-blind fails where two releases use the same label for different things — a field renamed in meaning rather than in spelling. That is rare and it is also undetectable by any method short of reading the release notes, so the defence is not a different parser design. It is a cross-check: Chapter 18.4's comparison of your own count against the report's summary, and Chapter 18.11's point that a number you cannot sanity-check is a number you should not publish.

And one habit that catches most of it cheaply: assert a range on anything you parse.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
if (defined $area && ($area <= 0 || $area > 1e9)) {
    print STDERR "warning: implausible total cell area '$area' from $file\n";
}

An area of 0.00 passed a defined test and is still almost certainly a parse failure. A bound you choose from knowing the design is the last line of defence, and unlike the parser it does not care which release produced the report.

5. What To Remember

  • A parser outlives the tool release it was written against.
  • Measured: one pattern matched 1 report in 3 and returned undef for the others.
  • undef becomes 0 in arithmetic (14.7), and the warning goes to stderr, which a flow discards (14.2).
  • "Total cell area: 0.00" after an upgrade is a broken parser, not a measurement.
  • Measured: a list of accepted spellings read all three and named which one it used.
  • Keep a human label with each spelling — "via the 2023 spelling" is most of a diagnosis.
  • Test defined, not truth — a legitimate 0.00 is false.
  • Order the list by expected frequency, and mind the case where one spelling is a prefix of another.
  • Return undef explicitly on fall-through — never 0 or "".
  • Use a hash of lists for several fields, so adding a release is one table line.
  • Three outcomes, not two: a number, a documented absence, and unrecognised.
  • Make unrecognised loud: name the file, list every spelling you tried, and exit 3 — "could not tell", not "failed".
  • Capture the tool version from the banner, first match only, and put it in the failure message.
  • Prefer version-blind: match on the format, report with the version. Version-keyed rejects an unchanged format from an unknown release.
  • Version-blind cannot detect a field renamed in meaning — defend with a summary cross-check (18.4) instead.
  • Range-check every parsed value. An area of 0.00 is defined and still almost certainly a failure.

Chapter 18.9 takes the inputs that are not reports at all. A log can be absent, empty, truncated in the middle of the run, or written by a tool that never started — and all four currently look like a clean pass.

Where this fits

Part of the Perl curriculum.