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