File Coverage

blib/lib/Lab/SCPI.pm
Criterion Covered Total %
statement 272 308 88.3
branch 129 168 76.7
condition 7 9 77.7
subroutine 19 19 100.0
pod 6 9 66.6
total 433 513 84.4


line stmt bran cond sub pod time code
1             package Lab::SCPI;
2             $Lab::SCPI::VERSION = '3.881';
3             #ABSTRACT: Match L<SCPI|http://www.ivifoundation.org/scpi/> headers and parameters against keywords
4              
5 10     10   116733 use v5.20;
  10         46  
6              
7 10     10   53 use warnings;
  10         19  
  10         301  
8 10     10   89 no warnings 'recursion';
  10         21  
  10         363  
9 10     10   65 use strict;
  10         21  
  10         217  
10              
11 10     10   50 use Carp;
  10         19  
  10         660  
12 10     10   5232 use English; # avoid editor nonsense with odd special variables
  10         19352  
  10         69  
13 10     10   4775 use Exporter 'import';
  10         22  
  10         37011  
14              
15             our @EXPORT = qw( scpi_match scpi_parse scpi_canon
16             scpi_flat scpi_parse_sequence );
17              
18             our $WS = qr/[\x00-\x09\x0b-\x20]/; # whitespace std488-2 7.4.1.2
19              
20              
21             sub scpi_match {
22 93     93 1 18162 my $header = shift;
23 93         141 my $keyword = shift;
24 93         265 my @keywords = split '\|', $keyword, -1;
25 93         172 for my $part (@keywords) {
26 187 100       343 if ( match_keyword( $header, $part ) ) {
27 66         258 return 1;
28             }
29             }
30 27         135 return 0;
31             }
32              
33             sub parse_keyword {
34 347     347 0 524 my $keyword = shift;
35              
36             # For the first part, the colon is optional.
37 347         835 my $start_mnemonic_regex = qr/(?<mnemonic>:?[a-z][a-z0-9_]*)/i;
38 347         684 my $mnemonic_regex = qr/(?<mnemonic>:[a-z][a-z0-9_]*)/i;
39 347         1348 my $keyword_regex = qr/\[$mnemonic_regex\]|$mnemonic_regex/;
40 347         1169 my $start_regex = qr/\[$start_mnemonic_regex\]|$start_mnemonic_regex/;
41              
42             # check if keyword is valid
43 347 50       713 if ( length($keyword) == 0 ) {
44 0         0 croak "keyword with empty length";
45             }
46              
47 347 50       2078 if ( $keyword !~ /^${start_regex}${keyword_regex}*$/ ) {
48 0         0 croak "invalid keyword: '$keyword'";
49             }
50              
51 347 100       808 if ( $keyword !~ /\[/ ) {
52              
53             # no more optional parts
54 267         1330 return $keyword;
55             }
56              
57             #recurse
58             return (
59 80         371 parse_keyword( $keyword =~ s/\[(.*?)\]/$1/r ),
60             parse_keyword( $keyword =~ s/\[(.*?)\]//r )
61             );
62             }
63              
64              
65             sub scpi_shortform {
66 199     199 1 960 my $string = shift;
67 199         947 $string =~ s/^${WS}*//; # strip leading spaces
68 199 100       482 if ( length($string) <= 4 ) {
69 76         246 return $string;
70             }
71              
72             # common mnemonics start with '*' and are not shortenable
73             # note that standard IEEE488 common mnemonics are length 4,
74             # but some extensions result in longer common mnemonics
75              
76 123 50       252 if ( $string =~ /^\*/ ) {
77 0         0 return $string;
78             }
79              
80             # mnemonics can have following digits (ex: CHANNEL3)
81             # the digits should be kept
82             # if followed by a '?', keep that too
83              
84             # mnemonics in the form (letter)(letter|digit|underscore)*
85             # but need to separate the "digit" part at end
86              
87 123 50       418 if ( $string =~ /^([a-z]\w*[a-z_])(\d*)(\??)/i ) {
88 123         302 $string = substr( $1, 0, 4 );
89 123         222 my $n = $2;
90 123         226 my $q = $3;
91 123 100       303 if ( $string =~ /^...[aeiou]/i ) {
92 8         23 $string = substr( $string, 0, 3 );
93             }
94 123         551 return $string . $n . $q;
95             }
96             else { # not a standard form mnemonic, bail
97 0         0 return $string;
98             }
99              
100             }
101              
102             # Return 1 for equal, 0 if not.
103             sub compare_headers {
104 261     261 0 358 my $a = shift;
105 261         354 my $b = shift;
106              
107 261         504 my @a = split( /:/, $a, -1 );
108 261         391 my @b = split( /:/, $b, -1 );
109              
110 261 100       540 if ( @a != @b ) {
111 92         260 return 0;
112             }
113 169         350 while (@a) {
114 186         305 my $a = shift @a;
115 186         249 my $b = shift @b;
116 186         291 $a = "\L$a";
117 186         278 $b = "\L$b";
118 186 100 100     465 if ( $b ne $a and $b ne scpi_shortform($a) ) {
119 103         322 return 0;
120             }
121             }
122 66         153 return 1;
123             }
124              
125             # Return 1 for match, 0 for no match.
126             sub match_keyword {
127 187     187 0 266 my $header = shift;
128 187         258 my $keyword = shift;
129              
130             # strip leading and trailing whitespace
131 187         601 $header =~ s/^\s*//;
132 187         626 $header =~ s/\s*$//;
133              
134 187         374 my @combinations = parse_keyword($keyword);
135 187         349 for my $combination (@combinations) {
136 261 100       444 if ( compare_headers( $combination, $header ) ) {
137 66         162 return 1;
138             }
139             }
140 121         279 return 0;
141             }
142              
143              
144             sub scpi_parse {
145 4     4 1 13662 my $str = shift;
146 4         7 my $d = shift;
147 4 50       14 $d = {} unless defined($d);
148 4         13 _gMem( $str, 0, $d, $d );
149 4         32 return $d;
150             }
151              
152             # "get Mnemonic"
153             # recursive parse _gMem(string,level,treetop,treebranch)
154             # level = 0 is the top of the tree, descend as elements
155             # of the scpi command are parsed: :lev0:lev1:lev2;lev2;lev2:lev3;lev3 ...
156              
157             sub _gMem {
158 105     105   181 my $str = shift;
159 105         130 my $level = shift;
160 105         131 my $dtop = shift;
161 105         123 my $d = shift;
162              
163 105 50       471 if ( $str =~ /^${WS}*(;|\s*$)/ ) {
164 0         0 return '';
165             }
166              
167 105         171 while (1) {
168 284         899 $str =~ s/^${WS}*//;
169 284 100       750 last if $str =~ /^\s*$/;
170              
171 182 100       351 if ( $level == 0 ) {
172 35 50       278 if ( $str =~ /^(\*\w+\??)${WS}*(;|\s*$)/i ) { #common
    100          
    100          
173 0 0       0 $dtop->{$1} = {} unless exists $dtop->{$1};
174 0         0 $str = $POSTMATCH;
175 0         0 next;
176             }
177             elsif ( $str =~ /^(\*\w+\??)${WS}+/i ) { # common with params
178 1 50       11 $dtop->{$1} = {} unless exists $dtop->{$1};
179 1         4 $str = _scpi_value( $POSTMATCH, $dtop->{$1} );
180 1 50       45 if ( $str =~ /^${WS}*(;|\s*$)/ ) {
181 1         4 $str = $POSTMATCH;
182 1         4 next;
183             }
184             else {
185 0         0 croak("parse error after common command");
186             }
187             }
188             elsif ( $str =~ /^:/ ) { # leading :
189 26         41 $d = $dtop;
190 26         76 $str =~ s/^://;
191             }
192             }
193             else {
194 147 50       293 if ( $str =~ /^\*/ ) {
195 0         0 croak("common command on level>0");
196             }
197 147 50       242 if ( $str =~ /^:/ ) {
198 0         0 croak("leading : on level > 0");
199             }
200             }
201              
202 181         607 $str =~ s/^${WS}*//;
203 181 50       484 last if $str =~ /^\s*$/;
204              
205 181 100       344 if ( $str =~ /^;/ ) { # another branch, same or top level
206 77         359 $str =~ s/^;${WS}*//;
207 77 100       194 last if $str =~ /^\s*$/;
208 74         107 my $nlev = $level;
209 74 100       138 $nlev = 0 if $str =~ /^[\*\:]/;
210              
211             # print "level=$level nlev=$nlev str=$str\n";
212 74         244 $str = _gMem( $str, $nlev, $dtop, $d );
213 74         137 next;
214             }
215              
216 104 50       1235 if ( $str =~ /^(\w+\??)${WS}*(;|\s*$)/i ) { # leaf, no params
    100          
    50          
217 0 0       0 $d->{$1} = {} unless exists $d->{$1};
218 0         0 return $POSTMATCH;
219             }
220             elsif ( $str =~ /^(\w+)${WS}*:/i ) { # branch, go down a level
221 27 100       137 $d->{$1} = {} unless exists $d->{$1};
222 27         133 $str = _gMem( $POSTMATCH, $level + 1, $dtop, $d->{$1} );
223             }
224             elsif ( $str =~ /^(\w+\??)${WS}+/i ) { # leaf with params
225 77 50       336 $d->{$1} = {} unless exists $d->{$1};
226 77         172 $str = $POSTMATCH;
227 77         178 $str = _scpi_value( $str, $d->{$1} );
228             }
229             else {
230 0         0 croak("parse error on '$str'");
231             }
232             }
233 105         201 return $str;
234             }
235              
236             sub _scpi_value {
237 181     181   319 my $str = shift;
238 181         215 my $d = shift;
239              
240 181         355 $d->{_VALUE} = '';
241 181         221 my $lastsp = 0;
242 181         767 while ( $str !~ /^${WS}*$/ ) {
243 379         1341 $str =~ s/^${WS}*//;
244              
245 379 100       2572 if ( $str =~ /^;/ ) {
    100          
    50          
    100          
    50          
    50          
246 181 100       696 $d->{_VALUE} =~ s/\s*$// if $lastsp;
247              
248 181         516 return $str;
249             }
250             elsif ( $str =~ /^\#([1-9])/ ) { # counted arbitrary
251 2         6 my $nnd = $1;
252 2         7 my $nd = substr( $str, 2, $nnd );
253 2         9 $d->{_VALUE} .= substr( $str, 0, $nd + 2 + $nnd );
254 2 50       9 if ( length($str) > $nd + 2 + $nnd ) {
255 2         6 $str = substr( $str, $nd + 2 + $nnd );
256             }
257             else {
258 0         0 $str = '';
259             }
260 2         5 $lastsp = 0;
261             }
262             elsif ( $str =~ /^\#0/ ) { #uncounted arbitrary
263 0         0 $d->{_VALUE} .= $str;
264 0         0 $str = '';
265 0         0 return $str;
266             }
267             elsif ( $str =~ /^(\"(?:([^\"]+|\"\")*)\")${WS}*/ )
268             { # double q string
269 13         39 $d->{_VALUE} .= $1 . ' ';
270 13         31 $str = $POSTMATCH;
271 13         22 $lastsp = 1;
272             }
273             elsif ( $str =~ /^(\'(?:([^\']+|\'\')*)\')${WS}*/ )
274             { # single q string
275 0         0 $d->{_VALUE} .= $1 . ' ';
276 0         0 $str = $POSTMATCH;
277 0         0 $lastsp = 1;
278             }
279             elsif ( $str =~ /^([\w\-\+\.\%\!\#\~\=\*]+)${WS}*/i )
280             { #words, numbers
281 183         470 $d->{_VALUE} .= $1 . ' ';
282 183         348 $str = $POSTMATCH;
283 183         242 $lastsp = 1;
284             }
285             else {
286 0         0 croak("parse error, parameter not matched with '$str'");
287             }
288 198 100       1144 if ( $str =~ /^${WS}*,/ ) { #parameter separator
289 14         31 $str = $POSTMATCH;
290 14 50       115 $d->{_VALUE} =~ s/${WS}*$// if $lastsp;
291 14         31 $d->{_VALUE} .= ',';
292 14         53 $lastsp = 0;
293             }
294             }
295 0 0       0 $d->{_VALUE} =~ s/\s*$// if $lastsp;
296 0         0 return $str;
297             }
298              
299              
300             sub scpi_parse_sequence {
301 18     18 1 31229 my $str = shift;
302 18         28 my $d = shift;
303 18 100       40 $d = [] unless defined($d);
304              
305 18         105 $str =~ s/^${WS}+//;
306 18 100       60 $str = ':' . $str unless $str =~ /^[\*:]/;
307 18 100       85 $str = $str . ';' unless $str =~ /;$/; # :string; form
308              
309 18         31 my (@cur) = ();
310 18         25 my $level = 0;
311              
312 18         24 while (1) {
313 142         389 $str =~ s/^${WS}+//;
314 142 100       362 if ( $str =~ /^;/ ) {
315 115         414 $str =~ s/^;${WS}*//;
316 115         197 my $ttop = {};
317 115         149 my $t = $ttop;
318              
319 115         241 for ( my $j = 0; $j <= $#cur; $j++ ) {
320 328         447 my $k = $cur[$j];
321 328 100       503 if ( $k eq '_VALUE' ) {
322 103         208 $t->{$k} = $cur[ $j + 1 ];
323 103         153 last;
324             }
325             else {
326 225         414 $t->{$k} = undef;
327 225 100       458 $t->{$k} = {} if $j < $#cur;
328 225         473 $t = $t->{$k};
329             }
330             }
331 115         147 push( @{$d}, $ttop );
  115         233  
332             }
333              
334 142 100       406 last if $str =~ /^\s*;?\s*$/; # handle trailing newline too
335              
336             # print "lev=$level str='$str'\n";
337 124 100       228 if ( $level == 0 ) {
338              
339             # starting from prev command
340 115 100       242 if ( $str =~ /^\w/i ) { # prev A:b or A:b:_VALUE:v
341 72         92 pop(@cur);
342 72         104 my $v = pop(@cur);
343 72 50 33     221 if ( defined($v) && $v eq '_VALUE' ) {
344 72         99 pop(@cur);
345             }
346             else {
347 0 0       0 push( @cur, $v ) if defined($v);
348             }
349 72         101 $level = 1;
350              
351             }
352             else {
353 43 100       95 if ( $str =~ /^:/ ) {
354 36         187 $str =~ s/^:${WS}*//;
355             }
356 43 50       132 next if $str =~ /^\s*;?\s*$/;
357 43         75 @cur = ();
358 43 100       606 if ( $str =~ /^(\*\w+\??)${WS}*;/i ) {
    50          
    100          
    100          
    50          
359              
360             # common, no arg
361 7         20 push( @cur, $1 );
362 7         18 $str = ';' . $POSTMATCH;
363              
364             }
365             elsif ( $str =~ /^(\*\w+\??)${WS}+/i ) {
366              
367             # common, arguments
368 0         0 push( @cur, $1 );
369 0         0 my $tmp = {};
370 0         0 $str = _scpi_value( $POSTMATCH, $tmp );
371 0         0 push( @cur, '_VALUE' );
372 0         0 push( @cur, $tmp->{_VALUE} );
373              
374             }
375             elsif ( $str =~ /^(\w+)${WS}*:/i ) {
376              
377             # start of tree, more coming
378 23         62 push( @cur, $1 );
379 23         50 $str = $POSTMATCH;
380 23         36 $level = 1;
381              
382             }
383             elsif ( $str =~ /^(\w+\??)${WS}*;/i ) {
384              
385             # tree end
386 3         9 push( @cur, "$1" );
387 3         9 $str = ';' . $POSTMATCH;
388              
389             }
390             elsif ( $str =~ /^(\w+\??)${WS}*/i ) {
391              
392             # tree end, args
393 10         30 push( @cur, $1 );
394 10         17 my $tmp = {};
395 10         24 $str = _scpi_value( $POSTMATCH, $tmp );
396 10         20 push( @cur, '_VALUE' );
397 10         27 push( @cur, $tmp->{_VALUE} );
398              
399             }
400             else {
401 0         0 croak("parse error str='$str'");
402             }
403             }
404              
405             }
406 124         373 $str =~ s/^${WS}+//;
407 124 100       343 next if $str =~ /^\s*;?\s*$/;
408              
409 112 100       212 if ( $level > 0 ) { # level > 0
410 104 50       207 if ( $str =~ /^[\*:]/ ) {
411 0         0 croak("common|root at level > 0");
412             }
413 104 100       990 if ( $str =~ /^(\w+)${WS}*:/i ) { #down another level
    100          
    50          
414 9         20 push( @cur, $1 );
415 9         21 $str = $POSTMATCH;
416              
417             # $level++;
418              
419             }
420             elsif ( $str =~ /^(\w+\??)${WS}*;/i ) { # end tree
421 2         5 push( @cur, $1 );
422 2         4 $str = ';' . $POSTMATCH;
423 2         5 $level = 0;
424              
425             }
426             elsif ( $str =~ /^(\w+\??)${WS}+/i ) { #arguments
427              
428 93         217 push( @cur, $1 );
429 93         141 my $tmp = {};
430 93         177 $str = _scpi_value( $POSTMATCH, $tmp );
431 93         145 push( @cur, '_VALUE' );
432 93         157 push( @cur, $tmp->{_VALUE} );
433 93         203 $level = 0;
434              
435             }
436             else {
437 0         0 croak("parse error str='$str'");
438             }
439             }
440              
441             }
442              
443 18         102 return $d;
444             }
445              
446              
447             sub scpi_canon {
448 188     188 1 241 my $h = shift;
449 188         218 my $override = shift;
450 188         231 my $top = shift;
451 188 100       357 $override = {} unless defined $override;
452 188 100       302 $top = 1 unless defined $top;
453 188         258 my $n = {};
454 188         219 my $s;
455              
456 188         214 foreach my $k ( keys( %{$h} ) ) {
  188         488  
457              
458 206 100       362 if ( $k eq '_VALUE' ) {
459 71         161 $n->{$k} = $h->{$k};
460             }
461             else {
462 135 100       216 if ($top) {
463 65 100       137 if ( $k =~ /^(\*\w+\??)/i ) { #common
464 6         16 $n->{ uc($1) } = undef;
465 6 50       14 if ( defined( $h->{$k} ) ) {
466 0         0 croak("common command with subcommand");
467             }
468 6         10 next;
469             }
470             }
471              
472 129 50       749 if ( $k =~ /^([a-z]\w*[a-z_])${WS}*(\d*)(\??)/i ) {
473 129         275 my $m = $1;
474 129         191 my $num = $2;
475 129 50       218 $num = '' unless defined $num;
476 129         220 my $q = $3;
477 129 50       193 $q = '' unless defined $q;
478              
479 129         159 my $ov = 0;
480 129         156 foreach my $ko ( keys( %{$override} ) ) {
  129         378  
481 682         928 my $shorter = $ko;
482 682         1605 $shorter =~ s/[a-z]\w*$//;
483 682 100 100     2213 if ( uc($ko) eq uc($m) || $shorter eq uc($m) ) {
484 94         131 $m = $shorter;
485 94         199 $s = "$m$num$q";
486             $n->{$s}
487 94         200 = scpi_canon( $h->{$k}, $override->{$ko}, 0 );
488 94         124 $ov = 1;
489 94         126 last;
490             }
491             }
492 129 100       300 next if $ov;
493              
494 35         62 $s = uc( scpi_shortform($m) ) . $num . $q;
495             $n->{$s}
496 35         91 = scpi_canon( $h->{$k}, {}, 0 ); # no override lower too
497             }
498             else {
499 0         0 croak("parse error, mnemonic '$k'");
500             }
501              
502             }
503              
504             }
505 188         391 return $n;
506             }
507              
508              
509             sub scpi_flat {
510 24     24 1 156 my $h = shift;
511 24         32 my $ov = shift;
512              
513 24 100       53 if ( ref($h) eq 'HASH' ) {
    50          
514 23         32 my $f = {};
515 23         42 my $c = scpi_canon( $h, $ov );
516 23         49 _scpi_fnode( '', $f, $c );
517 23         101 return $f;
518             }
519             elsif ( ref($h) eq 'ARRAY' ) {
520 1         3 my $fa = [];
521 1         1 foreach my $hx ( @{$h} ) {
  1         4  
522 36         50 my $f = {};
523 36         61 my $c = scpi_canon( $hx, $ov );
524 36         76 _scpi_fnode( '', $f, $c );
525 36         49 push( @{$fa}, $f );
  36         96  
526             }
527 1         4 return $fa;
528             }
529             else {
530 0         0 croak( "wrong type passed to scpi_flat:" . ref($h) );
531             }
532              
533             }
534              
535             sub _scpi_fnode {
536 265     265   363 my $fk = shift;
537 265         312 my $f = shift;
538 265         318 my $h = shift;
539              
540 265         308 my (@keys);
541 265 100       444 if ( ref($h) eq '' ) {
542 77         237 $fk =~ s/\:_VALUE$//;
543 77         181 $f->{$fk} = $h;
544 77         222 return;
545             }
546             else {
547 188         210 @keys = keys( %{$h} );
  188         460  
548 188 100       308 if (@keys) {
549 185 100       340 $fk .= ':' if $fk ne '';
550 185         259 foreach my $k (@keys) {
551 206         459 _scpi_fnode( "$fk$k", $f, $h->{$k} );
552             }
553             }
554             else {
555 3         23 $f->{$fk} = undef;
556             }
557             }
558             }
559              
560             1;
561              
562             __END__
563              
564             =pod
565              
566             =encoding UTF-8
567              
568             =head1 NAME
569              
570             Lab::SCPI - Match L<SCPI|http://www.ivifoundation.org/scpi/> headers and parameters against keywords
571              
572             =head1 VERSION
573              
574             version 3.881
575              
576             =head1 Interface
577              
578             This module exports a single function:
579              
580             =head2 scpi_match($header, $keyword)
581              
582             Return true, if C<$header> matches the SCPI keyword expression C<$keyword>.
583              
584             =head3 Examples
585              
586             The calls
587              
588             scpi_match($header, 'voltage[:APERture]')
589             scpi_match($header, 'voltage|CURRENT|resistance')
590             scpi_match($header, '[:abcdef]:ghi[:jkl]')
591              
592             are convenient replacements for
593              
594             $header =~ /^(voltage:aperture|voltage:aper|voltage|volt:aperture|volt:aper|volt)$/i
595             $header =~ /^(voltage|volt|current|curr|resistance|res)$/i
596             $header =~ /^(:abcdef:ghi:jkl|:abcdef:ghi|:abcd:ghi:jkl|:abcd:ghi|:ghi:jkl|:ghi)$/i
597              
598             respectively.
599              
600             Leading and trailing whitespace is removed from the first argument, before
601             matching against the keyword.
602              
603             =head3 Keyword Structure
604              
605             See Sec. 6 "Program Headers" in the SCPI spec. Always give the long form of a
606             keyword; the short form will be derived automatically. The colon is optional
607             for the first mnemonic. There must be at least one non-optional mnemonic in the
608             keyword.
609              
610             C<scpi_match> will throw, if it is given an invalid keyword.
611              
612             =head2 scpi_shortform($keyword)
613              
614             returns the "short form" of the input keyword, according to the
615             SCPI spec. Note that the keyword can have an appended number,
616             that needs to be preserved: sweep1 -> SWE1. Any trailing '?' is
617             also preserved, which is useful for general SCPI parsing purposes.
618              
619             BEWARE: some instruments have ambivalant 'shortform' when
620             constructed using normal rules:
621             (Tektronix DPO4104 ACQUIRE:NUMENV and ACQUIRE:NUMAVG)
622             you have to be aware of the mnemonic heirarchy for this,
623             so "scpi_canon" has a way to deal with such special cases.
624              
625             "Common" keywords (that start with '*') are returned unchanged.
626              
627             SCPI 6.2.1:
628             The short form mnemonic is usually the first four characters of the long form
629             command header. The exception to this is when the long form consists of more
630             than four characters and the fourth character is a vowel. In such cases, the
631             vowel is dropped and the short form becomes the first three characters of
632             the long form.
633              
634             Got to watch out for that "usually". See scpi_canon for how to handle
635             the more general case.
636              
637             =head2 scpi_parse(string [,hash])
638              
639             $hash = scpi_parse(string [,hash])
640             parse scpi command or response string, create
641             a tree structure with hash keys for the mnemonic
642             components, entries for the values.
643              
644             example $string = ":Source:Voltage:A 3.0 V;B 2.7V;:Source:Average ON"
645             results in $hash{Source}->{Voltage}->{A}->{_VALUE} = '3.0 V'
646             $hash{Source}->{Voltage}->{B}->{_VALUE} = '2.7V'
647             $hash{Source}->{Average}->{_VALUE} = 'ON'
648              
649             If a hash is given as a parameter of the call, the
650             information parsed from the string is combined with
651             the input hash.
652              
653             =head2 arrayref = scpi_parse_sequence(string[,arrayref])
654              
655             returns an array of hashes, each hash is a tree structure
656             corresponding to a single scpi command (like scpi_parse)
657             Useful for when the sequence of commands is significant.
658              
659             If an arrayref is passed in, the parsed string results are
660             appended as new entries.
661              
662             =head2 $canonhash = scpi_canon($hash[,$overridehash])
663              
664             revise a hash tree of scpi mnemonics to use
665             the 'shorter' forms, in uppercase
666              
667             The "override" hash has the same form as the mnemonic
668             hash (but with no _VALUE leaves on the tree), but each
669             key is in the form 'MESSage' where uppercase is the
670             shorter form. This is to allow shortening of mnemonics
671             where the normal shortening rules don't work.
672              
673             =head2 $flat = scpi_flat($thing[,$override])
674              
675             convert the tree structure to a 'flat'
676             key space: h->{a}->{b}->{INPUT3} -> f{A:B:INP3}, canonicalizing the keys
677             This is useful for comparing values between two hash structures
678              
679             if $thing = hash ref -> flat is corresponding hash
680             if $thing = array ref -> flat is an array ref to flat hashes
681              
682             =head1 COPYRIGHT AND LICENSE
683              
684             This software is copyright (c) 2023 by the Lab::Measurement team; in detail:
685              
686             Copyright 2016 Charles Lane, Simon Reinhardt
687             2017 Andreas K. Huettel
688             2019 Simon Reinhardt
689             2020 Andreas K. Huettel
690              
691              
692             This is free software; you can redistribute it and/or modify it under
693             the same terms as the Perl 5 programming language system itself.
694              
695             =cut