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.900';
3             #ABSTRACT: Match L<SCPI|http://www.ivifoundation.org/scpi/> headers and parameters against keywords
4              
5 7     7   111163 use v5.20;
  7         38  
6              
7 7     7   46 use warnings;
  7         14  
  7         207  
8 7     7   35 no warnings 'recursion';
  7         13  
  7         263  
9 7     7   36 use strict;
  7         11  
  7         132  
10              
11 7     7   31 use Carp;
  7         13  
  7         482  
12 7     7   3499 use English; # avoid editor nonsense with odd special variables
  7         13068  
  7         41  
13 7     7   3152 use Exporter 'import';
  7         17  
  7         25762  
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 24     24 1 15004 my $header = shift;
23 24         40 my $keyword = shift;
24 24         68 my @keywords = split '\|', $keyword, -1;
25 24         43 for my $part (@keywords) {
26 27 100       52 if ( match_keyword( $header, $part ) ) {
27 13         53 return 1;
28             }
29             }
30 11         41 return 0;
31             }
32              
33             sub parse_keyword {
34 53     53 0 88 my $keyword = shift;
35              
36             # For the first part, the colon is optional.
37 53         134 my $start_mnemonic_regex = qr/(?<mnemonic>:?[a-z][a-z0-9_]*)/i;
38 53         115 my $mnemonic_regex = qr/(?<mnemonic>:[a-z][a-z0-9_]*)/i;
39 53         247 my $keyword_regex = qr/\[$mnemonic_regex\]|$mnemonic_regex/;
40 53         206 my $start_regex = qr/\[$start_mnemonic_regex\]|$start_mnemonic_regex/;
41              
42             # check if keyword is valid
43 53 50       119 if ( length($keyword) == 0 ) {
44 0         0 croak "keyword with empty length";
45             }
46              
47 53 50       384 if ( $keyword !~ /^${start_regex}${keyword_regex}*$/ ) {
48 0         0 croak "invalid keyword: '$keyword'";
49             }
50              
51 53 100       131 if ( $keyword !~ /\[/ ) {
52              
53             # no more optional parts
54 40         195 return $keyword;
55             }
56              
57             #recurse
58             return (
59 13         67 parse_keyword( $keyword =~ s/\[(.*?)\]/$1/r ),
60             parse_keyword( $keyword =~ s/\[(.*?)\]//r )
61             );
62             }
63              
64              
65             sub scpi_shortform {
66 64     64 1 854 my $string = shift;
67 64         322 $string =~ s/^${WS}*//; # strip leading spaces
68 64 100       171 if ( length($string) <= 4 ) {
69 19         60 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 45 50       96 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 45 50       140 if ( $string =~ /^([a-z]\w*[a-z_])(\d*)(\??)/i ) {
88 45         117 $string = substr( $1, 0, 4 );
89 45         78 my $n = $2;
90 45         111 my $q = $3;
91 45 100       122 if ( $string =~ /^...[aeiou]/i ) {
92 8         28 $string = substr( $string, 0, 3 );
93             }
94 45         207 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 37     37 0 53 my $a = shift;
105 37         50 my $b = shift;
106              
107 37         81 my @a = split( /:/, $a, -1 );
108 37         57 my @b = split( /:/, $b, -1 );
109              
110 37 100       75 if ( @a != @b ) {
111 11         45 return 0;
112             }
113 26         55 while (@a) {
114 34         48 my $a = shift @a;
115 34         52 my $b = shift @b;
116 34         52 $a = "\L$a";
117 34         48 $b = "\L$b";
118 34 100 100     94 if ( $b ne $a and $b ne scpi_shortform($a) ) {
119 13         43 return 0;
120             }
121             }
122 13         31 return 1;
123             }
124              
125             # Return 1 for match, 0 for no match.
126             sub match_keyword {
127 27     27 0 47 my $header = shift;
128 27         37 my $keyword = shift;
129              
130             # strip leading and trailing whitespace
131 27         119 $header =~ s/^\s*//;
132 27         114 $header =~ s/\s*$//;
133              
134 27         60 my @combinations = parse_keyword($keyword);
135 27         55 for my $combination (@combinations) {
136 37 100       64 if ( compare_headers( $combination, $header ) ) {
137 13         29 return 1;
138             }
139             }
140 14         33 return 0;
141             }
142              
143              
144             sub scpi_parse {
145 4     4 1 13256 my $str = shift;
146 4         6 my $d = shift;
147 4 50       13 $d = {} unless defined($d);
148 4         13 _gMem( $str, 0, $d, $d );
149 4         27 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   177 my $str = shift;
159 105         126 my $level = shift;
160 105         134 my $dtop = shift;
161 105         125 my $d = shift;
162              
163 105 50       442 if ( $str =~ /^${WS}*(;|\s*$)/ ) {
164 0         0 return '';
165             }
166              
167 105         156 while (1) {
168 284         881 $str =~ s/^${WS}*//;
169 284 100       717 last if $str =~ /^\s*$/;
170              
171 182 100       325 if ( $level == 0 ) {
172 35 50       222 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       5 $dtop->{$1} = {} unless exists $dtop->{$1};
179 1         3 $str = _scpi_value( $POSTMATCH, $dtop->{$1} );
180 1 50       30 if ( $str =~ /^${WS}*(;|\s*$)/ ) {
181 1         3 $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         38 $d = $dtop;
190 26         83 $str =~ s/^://;
191             }
192             }
193             else {
194 147 50       235 if ( $str =~ /^\*/ ) {
195 0         0 croak("common command on level>0");
196             }
197 147 50       251 if ( $str =~ /^:/ ) {
198 0         0 croak("leading : on level > 0");
199             }
200             }
201              
202 181         596 $str =~ s/^${WS}*//;
203 181 50       446 last if $str =~ /^\s*$/;
204              
205 181 100       331 if ( $str =~ /^;/ ) { # another branch, same or top level
206 77         304 $str =~ s/^;${WS}*//;
207 77 100       194 last if $str =~ /^\s*$/;
208 74         104 my $nlev = $level;
209 74 100       148 $nlev = 0 if $str =~ /^[\*\:]/;
210              
211             # print "level=$level nlev=$nlev str=$str\n";
212 74         232 $str = _gMem( $str, $nlev, $dtop, $d );
213 74         113 next;
214             }
215              
216 104 50       1157 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       119 $d->{$1} = {} unless exists $d->{$1};
222 27         174 $str = _gMem( $POSTMATCH, $level + 1, $dtop, $d->{$1} );
223             }
224             elsif ( $str =~ /^(\w+\??)${WS}+/i ) { # leaf with params
225 77 50       317 $d->{$1} = {} unless exists $d->{$1};
226 77         169 $str = $POSTMATCH;
227 77         158 $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   324 my $str = shift;
238 181         208 my $d = shift;
239              
240 181         348 $d->{_VALUE} = '';
241 181         223 my $lastsp = 0;
242 181         754 while ( $str !~ /^${WS}*$/ ) {
243 379         1408 $str =~ s/^${WS}*//;
244              
245 379 100       2573 if ( $str =~ /^;/ ) {
    100          
    50          
    100          
    50          
    50          
246 181 100       722 $d->{_VALUE} =~ s/\s*$// if $lastsp;
247              
248 181         503 return $str;
249             }
250             elsif ( $str =~ /^\#([1-9])/ ) { # counted arbitrary
251 2         6 my $nnd = $1;
252 2         9 my $nd = substr( $str, 2, $nnd );
253 2         10 $d->{_VALUE} .= substr( $str, 0, $nd + 2 + $nnd );
254 2 50       8 if ( length($str) > $nd + 2 + $nnd ) {
255 2         5 $str = substr( $str, $nd + 2 + $nnd );
256             }
257             else {
258 0         0 $str = '';
259             }
260 2         7 $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         38 $d->{_VALUE} .= $1 . ' ';
270 13         36 $str = $POSTMATCH;
271 13         21 $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         471 $d->{_VALUE} .= $1 . ' ';
282 183         402 $str = $POSTMATCH;
283 183         222 $lastsp = 1;
284             }
285             else {
286 0         0 croak("parse error, parameter not matched with '$str'");
287             }
288 198 100       1181 if ( $str =~ /^${WS}*,/ ) { #parameter separator
289 14         30 $str = $POSTMATCH;
290 14 50       120 $d->{_VALUE} =~ s/${WS}*$// if $lastsp;
291 14         30 $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 30935 my $str = shift;
302 18         25 my $d = shift;
303 18 100       38 $d = [] unless defined($d);
304              
305 18         99 $str =~ s/^${WS}+//;
306 18 100       57 $str = ':' . $str unless $str =~ /^[\*:]/;
307 18 100       64 $str = $str . ';' unless $str =~ /;$/; # :string; form
308              
309 18         31 my (@cur) = ();
310 18         25 my $level = 0;
311              
312 18         21 while (1) {
313 142         395 $str =~ s/^${WS}+//;
314 142 100       351 if ( $str =~ /^;/ ) {
315 115         412 $str =~ s/^;${WS}*//;
316 115         210 my $ttop = {};
317 115         174 my $t = $ttop;
318              
319 115         266 for ( my $j = 0; $j <= $#cur; $j++ ) {
320 328         473 my $k = $cur[$j];
321 328 100       485 if ( $k eq '_VALUE' ) {
322 103         215 $t->{$k} = $cur[ $j + 1 ];
323 103         157 last;
324             }
325             else {
326 225         780 $t->{$k} = undef;
327 225 100       450 $t->{$k} = {} if $j < $#cur;
328 225         461 $t = $t->{$k};
329             }
330             }
331 115         154 push( @{$d}, $ttop );
  115         226  
332             }
333              
334 142 100       413 last if $str =~ /^\s*;?\s*$/; # handle trailing newline too
335              
336             # print "lev=$level str='$str'\n";
337 124 100       218 if ( $level == 0 ) {
338              
339             # starting from prev command
340 115 100       257 if ( $str =~ /^\w/i ) { # prev A:b or A:b:_VALUE:v
341 72         110 pop(@cur);
342 72         96 my $v = pop(@cur);
343 72 50 33     221 if ( defined($v) && $v eq '_VALUE' ) {
344 72         89 pop(@cur);
345             }
346             else {
347 0 0       0 push( @cur, $v ) if defined($v);
348             }
349 72         116 $level = 1;
350              
351             }
352             else {
353 43 100       93 if ( $str =~ /^:/ ) {
354 36         169 $str =~ s/^:${WS}*//;
355             }
356 43 50       120 next if $str =~ /^\s*;?\s*$/;
357 43         74 @cur = ();
358 43 100       627 if ( $str =~ /^(\*\w+\??)${WS}*;/i ) {
    50          
    100          
    100          
    50          
359              
360             # common, no arg
361 7         19 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         59 push( @cur, $1 );
379 23         45 $str = $POSTMATCH;
380 23         41 $level = 1;
381              
382             }
383             elsif ( $str =~ /^(\w+\??)${WS}*;/i ) {
384              
385             # tree end
386 3         11 push( @cur, "$1" );
387 3         8 $str = ';' . $POSTMATCH;
388              
389             }
390             elsif ( $str =~ /^(\w+\??)${WS}*/i ) {
391              
392             # tree end, args
393 10         31 push( @cur, $1 );
394 10         18 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       329 next if $str =~ /^\s*;?\s*$/;
408              
409 112 100       203 if ( $level > 0 ) { # level > 0
410 104 50       200 if ( $str =~ /^[\*:]/ ) {
411 0         0 croak("common|root at level > 0");
412             }
413 104 100       963 if ( $str =~ /^(\w+)${WS}*:/i ) { #down another level
    100          
    50          
414 9         19 push( @cur, $1 );
415 9         20 $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         4 $level = 0;
424              
425             }
426             elsif ( $str =~ /^(\w+\??)${WS}+/i ) { #arguments
427              
428 93         232 push( @cur, $1 );
429 93         139 my $tmp = {};
430 93         189 $str = _scpi_value( $POSTMATCH, $tmp );
431 93         172 push( @cur, '_VALUE' );
432 93         147 push( @cur, $tmp->{_VALUE} );
433 93         220 $level = 0;
434              
435             }
436             else {
437 0         0 croak("parse error str='$str'");
438             }
439             }
440              
441             }
442              
443 18         97 return $d;
444             }
445              
446              
447             sub scpi_canon {
448 188     188 1 238 my $h = shift;
449 188         226 my $override = shift;
450 188         230 my $top = shift;
451 188 100       315 $override = {} unless defined $override;
452 188 100       340 $top = 1 unless defined $top;
453 188         241 my $n = {};
454 188         229 my $s;
455              
456 188         250 foreach my $k ( keys( %{$h} ) ) {
  188         411  
457              
458 206 100       334 if ( $k eq '_VALUE' ) {
459 71         163 $n->{$k} = $h->{$k};
460             }
461             else {
462 135 100       235 if ($top) {
463 65 100       144 if ( $k =~ /^(\*\w+\??)/i ) { #common
464 6         18 $n->{ uc($1) } = undef;
465 6 50       14 if ( defined( $h->{$k} ) ) {
466 0         0 croak("common command with subcommand");
467             }
468 6         8 next;
469             }
470             }
471              
472 129 50       685 if ( $k =~ /^([a-z]\w*[a-z_])${WS}*(\d*)(\??)/i ) {
473 129         346 my $m = $1;
474 129         178 my $num = $2;
475 129 50       226 $num = '' unless defined $num;
476 129         174 my $q = $3;
477 129 50       207 $q = '' unless defined $q;
478              
479 129         155 my $ov = 0;
480 129         154 foreach my $ko ( keys( %{$override} ) ) {
  129         368  
481 660         948 my $shorter = $ko;
482 660         1537 $shorter =~ s/[a-z]\w*$//;
483 660 100 100     2077 if ( uc($ko) eq uc($m) || $shorter eq uc($m) ) {
484 94         136 $m = $shorter;
485 94         146 $s = "$m$num$q";
486             $n->{$s}
487 94         206 = scpi_canon( $h->{$k}, $override->{$ko}, 0 );
488 94         122 $ov = 1;
489 94         125 last;
490             }
491             }
492 129 100       293 next if $ov;
493              
494 35         68 $s = uc( scpi_shortform($m) ) . $num . $q;
495             $n->{$s}
496 35         88 = 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         407 return $n;
506             }
507              
508              
509             sub scpi_flat {
510 24     24 1 181 my $h = shift;
511 24         29 my $ov = shift;
512              
513 24 100       56 if ( ref($h) eq 'HASH' ) {
    50          
514 23         33 my $f = {};
515 23         43 my $c = scpi_canon( $h, $ov );
516 23         50 _scpi_fnode( '', $f, $c );
517 23         82 return $f;
518             }
519             elsif ( ref($h) eq 'ARRAY' ) {
520 1         4 my $fa = [];
521 1         2 foreach my $hx ( @{$h} ) {
  1         3  
522 36         57 my $f = {};
523 36         57 my $c = scpi_canon( $hx, $ov );
524 36         79 _scpi_fnode( '', $f, $c );
525 36         43 push( @{$fa}, $f );
  36         93  
526             }
527 1         3 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   349 my $fk = shift;
537 265         318 my $f = shift;
538 265         328 my $h = shift;
539              
540 265         308 my (@keys);
541 265 100       448 if ( ref($h) eq '' ) {
542 77         242 $fk =~ s/\:_VALUE$//;
543 77         201 $f->{$fk} = $h;
544 77         221 return;
545             }
546             else {
547 188         221 @keys = keys( %{$h} );
  188         448  
548 188 100       325 if (@keys) {
549 185 100       318 $fk .= ':' if $fk ne '';
550 185         266 foreach my $k (@keys) {
551 206         437 _scpi_fnode( "$fk$k", $f, $h->{$k} );
552             }
553             }
554             else {
555 3         10 $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.900
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