Beefy Boxes and Bandwidth Generously Provided by pair Networks
Your skill will accomplish
what the force of many cannot
 
PerlMonks  

Re^2: Parsing Motorola S-Rec file

by gri6507 (Deacon)
on Sep 03, 2014 at 12:16 UTC ( [id://1099395]=note: print w/replies, xml ) Need Help??


in reply to Re: Parsing Motorola S-Rec file
in thread Parsing Motorola S-Rec file

In the interest of saving someone else in the future this problem, here's what I came up with. Your comments and criticism are welcome!
# Parse a single line of S-record, validate the line, and extract the +address # and data fields. Return a list of three scalars: # 1. TRUE if extraction was successful, FALSE otherwise (i.e. not a v +alid data # carrying S-record, bad s-record length, bad s-record checksum). # 2. The address of the line's payload, as a string. # 3. The data of the line's payload, as a string. sub ProcessSrecLine { my $line = shift; # Get rid of all line endings, regardless of which flavor they are $line =~ s/(\n|\r)//g; # be a pessimist my $status = 0; my $type; my $count; my $address; my $data; my $cs; # does this look like a S1, S2, or S3 record? These are the only o +nes that # carry an actual data payload. Other record types do not, so just + ignore # them. if ( ($line =~ /^s(1)([0-9a-f]{2})([0-9a-f]{4})([0-9a-f]+)([0-9a-f] +{2})$/i) || ($line =~ /^s(2)([0-9a-f]{2})([0-9a-f]{6})([0-9a-f]+)([0-9a-f] +{2})$/i) || ($line =~ /^s(3)([0-9a-f]{2})([0-9a-f]{8})([0-9a-f]+)([0-9a-f] +{2})$/i) ) { $type = $1; $count = $2; $address = $3; $data = $4; $cs = $5; # Make sure the COUNT field makes sense. It should represent t +he sum of # address, data, and checksum fields, in bytes if ( hex($count) == ((length($address) + length($data) + lengt +h($cs)) / 2) ) { # Make sure the checksum makes sense. It is the two's comp +lement of # the sum of count, address, and data fields my $compCs = hex($count); for (my $i=0; $i<length($address); $i+=2) { $compCs += hex(substr($address, $i, 2)); } for (my $i=0; $i<length($data); $i+=2) { $compCs += hex(substr($data, $i, 2)); } $compCs %= 256; $compCs = 255 - $compCs; # if the checksum actually matches, then call it a good S- +record if ($compCs == hex($cs)) { $status = 1; } } return ($status, $address, $data); } }

Replies are listed 'Best First'.
Re^3: Parsing Motorola S-Rec file
by AppleFritter (Vicar) on Sep 03, 2014 at 13:17 UTC

    Looks pretty good to me already! That said, here's a few suggestion for what I'd do differently, personally:

    • You can assign to a variable and apply a substitution in one step. Also, I personally find character classes a bit more readable when pruning several different characters from a string, so I'd write the assignment to $line like this:

      # Get rid of all line endings, regardless of which flavor they are (my $line = shift) =~ s/[\r\n]//g;
    • There's no need to declare variables until you use them - this will save some typing later on.

    • The checks for S1/S2/S3 records are all very similar; you could combine them into one (and then apply an extra sanity check to make sure that you have the right address length for your record).

    • Speaking of checks, I'd also just return from the sub if a check doesn't pass - less indentation later on. :)

    • What's more, there is a Unicode property for hex digits that you can use - \p{IsXDigit}, which is equivalent to [0-9a-fA-F]:

      # does this look like a S1, S2, or S3 record? These are the only o +nes that # carry an actual data payload. Other record types do not, so just + ignore # them. return unless $line =~ m/^s([1-3])(\p{IsXDigit}{2})(\p{IsXDigit}{4 +,6,8})(\p{IsXDigit}+)(\p{IsXDigit}{2})$/; my ($type, $count, $address, $data, $cs) = ($1, $2, $3, $4, $5); # does the address length match the record type? return unless length $address == 2 * ($type + 1); # Make sure the COUNT field makes sense. It should represent the s +um of # address, data, and checksum fields, in bytes return unless hex($count) == (length($address) + length($data) + l +ength($cs)) / 2;
    • Your $compCs calculation loops are pretty C-ish. You could write them more idiomatically by using m//g to split the string (in list context, it returns all matches); this would also allow you to combine them into one:

      my $compCs = hex($count); foreach my $nibble ($address =~ m/../g, $data =~ m/../g) { $compCs += hex($nibble); }

      Of course you could also use a statement modifier:

      my $compCs = hex($count); $compCs += hex($_) foreach($address =~ m/../g, $data =~ m/../g);

      If you do this, you should localize $_ in your subroutine so that it doesn't get clobbered in the calling routine.

      Personally, I find using map and reduce (from List::Util) the most readable:

      my $compCs = reduce { $a + $b } map { hex $_ } ($count, $address =~ m/../g, $data =~ m/../g);
    • You can set $status to the result of the comparison. In fact, this will also remove the need to explicitely set it to zero above; if the comparison fails, it'll be set to the empty string (a false value):

      my $status = $compCs == hex($cs);

    So that's how I'd do it. Just for the sake of convenience, here's my version of this subroutine, in full:

    use List::Util; # ... # Parse a single line of S-record, validate the line, and extract the +address # and data fields. Return a list of three scalars: # 1. TRUE if extraction was successful, FALSE otherwise (i.e. not a v +alid data # carrying S-record, bad s-record length, bad s-record checksum). # 2. The address of the line's payload, as a string. # 3. The data of the line's payload, as a string. sub ProcessSrecLine { # Get rid of all line endings, regardless of which flavor they are (my $line = shift) =~ s/[\r\n]//g; # does this look like a S1, S2, or S3 record? These are the only o +nes that # carry an actual data payload. Other record types do not, so just + ignore # them. return unless $line =~ m/^s([1-3])(\p{IsXDigit}{2})(\p{IsXDigit}{4 +,6,8})(\p{IsXDigit}+)(\p{IsXDigit}{2})$/; my ($type, $count, $address, $data, $cs) = ($1, $2, $3, $4, $5); # does the address length match the record type? return unless length $address == 2 * ($type + 1); # Make sure the COUNT field makes sense. It should represent the s +um of # address, data, and checksum fields, in bytes return unless hex($count) == (length($address) + length($data) + l +ength($cs)) / 2; # Make sure the checksum makes sense. It is the two's complement o +f # the sum of count, address, and data fields my $compCs = reduce { $a + $b } map { hex $_ } ($count, $address =~ m/../g, $data =~ m/../g); $compCs %= 256; $compCs = 255 - $compCs; # if the checksum actually matches, then call it a good S-record my $status = $compCs == hex($cs); return ($status, $address, $data); }

    All of this is just my personal preferences and style, of course. As always, TIMTOWTDI (there is more than one way to do it), and I'm hardly an experienced Perl programmer anyway, so any advice I give may well fly in the face of established best practices. Caveat lector. :)

    Another note - all this is completely untested, too.

    EDIT: of course, there's also no need for a $status variable if you only assign to it once and then return, too:

    # ... # if the checksum actually matches, then call it a good S-record return ($compCs == hex($cs), $address, $data);

      Why not take the last step too?

      $ cat test.pl use 5.16.2; use warnings; use Benchmark qw( cmpthese ); use List::Util qw( reduce sum ); my $count = 12; my $address = "A42187B56F"; my $data = "39AD0D96D51CD3"; my $r = sub { reduce { $a + $b } map { hex $_ } ($count, $address =~ m/../g, $data =~ m/../g); }; my $s = sub { sum map { hex } $count, (unpack "(A2)*", $address), (unpack "(A2)*", $data); }; say $r->(); say $s->(); cmpthese (-2, { LUreduce => $r, LUsum => $s }); $ perl test.pl 1487 1487 Rate LUreduce LUsum LUreduce 216392/s -- -40% LUsum 359162/s 66% --

      Enjoy, Have FUN! H.Merijn
        Ah, good point! I'm so used to using reduce for everything that sum didn't even occur to me. (And I've never been as comfortable with (un)pack as with regular expressions; time to remedy that, perhaps.)
        I'm sorry, but my Perl understanding is not quite up to par to understand what's going on here, or why this is better. Can you please clarify?

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://1099395]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others examining the Monastery: (2)
As of 2024-04-26 02:20 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found