Agree that narg()
self-documents well.
But subtle are the differences betwixt $#
and narg($*)
. These differences stem from G’MIC setting up $#
directly, conveying the command line item count datum straight from the underlying environment, while narg($*)
performs computation on a vector. It is this vector computation from which differences arise, particularly the role that commas play in vector string representations: commas are the item separators of vectors.
Consider this custom command defined in foobar.gmic
:
foobar :
e {narg($*)}
e {$#}
Doris Diligence runs this to test a typical argument list for a G’MIC command she is (diligently) developing:
$ gmic foobar.gmic foobar 1,3,2.718,"Oh, yes, We have no bananas."
[gmic]-0./ Start G'MIC interpreter.
[gmic]-0./ Input custom command file 'foobar.gmic' (1 new, total: 4422).
[gmic]-0./foobar/ 6
[gmic]-0./foobar/ 6
[gmic]-0./ End G'MIC interpreter.
The two command line counters are in agreement, but Doris is unsettled. She has been thinking that her argument list has four elements: “1”, “3”, “2.718” and “Oh, yes, We have no bananas.”
Being a diligent cheat sheet reader, she has read Cheat #4: You’ve Moved Fast and Broken Things. Now What?. So Doris runs the script under -debug
to understand how G’MIC sees the command line, which may differ from her view. Here is the -debug
excerpt of G’MIC’s pipeline decomposition for entry into the foobar/
scope:
<gmic>-0./ Command 'foobar': arguments = '1,3,2.718,Oh, yes, We have no bananas.'.
<gmic>-0./ Found 6 given arguments for command 'foobar':
<gmic>-0./ $1 = '1'
<gmic>-0./ $2 = '3'
<gmic>-0./ $3 = '2.718'
<gmic>-0./ $4 = 'Oh'
<gmic>-0./ $5 = ' yes'
<gmic>-0./ $6 = ' We have no bananas.'
“Oh — foo!!!” cries Doris “I must bar the parsing of my last string argument into separate items! Of course! I have forgotten to escape the double quotes!”
So Doris — in a rare lapse of diligence — fails to consider the significance of initial spaces in arguments $5
and $6
; she just escapes the double quotes and runs the command again.
G’MIC’s pipeline decomposition for entry into the foobar/
scope appears to have changed for the better:
<gmic>-0./ Command 'foobar': arguments = '1,3,2.718,Oh\, yes\, We have no bananas.'.
<gmic>-0./ Found 4 given arguments for command 'foobar':
<gmic>-0./ $1 = '1'
<gmic>-0./ $2 = '3'
<gmic>-0./ $3 = '2.718'
<gmic>-0./ $4 = 'Oh\, yes\, We have no bananas.'
“Success!” cries Doris. “Escaping the quotes did the trick. Now my string argument comes across intact!”
Whistling a merry tune, Doris scrolls further down to examine the substitutions going on within the foobar/
scope.
<gmic>-0./foobar/ Enter scope 'foobar/'.
<gmic>-0./foobar/#2 Item 'e', selection [].
<gmic>-0./foobar/#2 Command 'echo': arguments = '{narg(1,3,2.718,Oh, yes, We have no bananas.)}' -> '6'.
[gmic]-0./foobar/ 6
<gmic>-0./foobar/#3 Item 'e', selection [].
<gmic>-0./foobar/#3 Command 'echo': arguments = '{4}' -> '4'.
[gmic]-0./foobar/ 4
<gmic>-0./foobar/#3 Exit scope 'foobar/'.
“Oh! Triple foo with cherries on top — and whipped cream!!! I have six arguments and four arguments. Now — bar it all! — what am I to believe?”
This:
narg()
is a math function that counts elements of string-represented vectors. Commas separate elements in these representations, so the five commas in “1,3,2.718,Oh, yes, We have no bananas.” begat six elements:["1", "3", "2.718", "Oh", " yes", " We have no bananas."]
. Observe that the penultimate and last vector elements have leading spaces; these follow the commas upon which elements were split. Had Doris noticed these leading spaces earlier on, she would have been cued to the fact as to how her argument list was going off the rails.$#
is a substitution G’MIC maintains for custom commands, relaying how many items it discovered when blocking out the pipeline, a part of scope-entry setup. When G’MIC reports"Found 4 given arguments for command 'foobar':"
, then$#
relays the number following “Found”: the item count of the blocked-out, incoming pipeline, less the custom command name itself.
The construct:
$=citems_
induces G’MIC to provide the items themselves, exactly as to how G’MIC blocked them out. G’MIC expands this pseudo assignment into a series of actual assignments, one for each item of the blocked-out incoming pipeline plus the command name itself.
foobar
modified:
foobar :
$=citems_
e "Narg sez: "{narg($*)}
e "Dollar Hash sez: "{$#}
e ${citems_0}"'s arguments are:"
repeat $#,k
e "Argument "{$k+1}" is: "${citems_{$k+1}}"."
done
Running it yields:
$ gmic foobar.gmic foobar 1,3,2.718,\"Oh, yes, We have no bananas.\"
[gmic]-0./ Start G'MIC interpreter.
[gmic]-0./ Input custom command file 'foobar.gmic' (1 new, total: 4422).
[gmic]-0./foobar/ Narg sez: 6
[gmic]-0./foobar/ Dollar Hash sez: 4
[gmic]-0./foobar/ foobar's arguments are:
[gmic]-0./foobar/*repeat/ Argument 1 is: 1.
[gmic]-0./foobar/*repeat/ Argument 2 is: 3.
[gmic]-0./foobar/*repeat/ Argument 3 is: 2.718.
[gmic]-0./foobar/*repeat/ Argument 4 is: Oh, yes, We have no bananas..
[gmic]-0./ End G'MIC interpreter.
The offsetting of local variable $k
by one accommodates the fact that the first element of the blocked out pipeline contains the command name, useful should one be contemplating conditional command name processing.
The takeaway here is not that narg()
is bad and $#
is good; they are different tools that serve closely-related, but different purposes, and their operating mechanisms are different. That they almost always report the same results could lull us into thinking they are the same tools just spelled differently. They’re not. Corner cases exist and need to be understood — and navigated.