Accessing Command Line Operands
Contents of Article
Except for the simplest of macros, command line operands are necessary to tailor the actions performed by your macro. So, retrieving and 'sorting out' what operands have been entered is a necessary requirement for most macros. The SPFLite macro support provides three methods for handling the operands. Which is best for you? The deciding factors are the number, order and complexity of the operands, whether any are optional, and whether any are keywords which possibly are part of a "keyword group". The chart below will help you by summarizing the differences in the three levels of support.
It is important to understand how the command line operands are handled. SPFLite does an initial parse of the command line operands, which are delimited by spaces or quotes as usual, and stores them into a table, which can be accessed with the Get_Arg$ and Get_Arg_Count functions.
These operands are not initially categorized or sorted in any way. You would simply "get" them, using the functions described next.
There are three types of macro operand access, as follows:
SUB Operand Access
Full Parse Access
This support includes handling of aliases and mutually-exclusive keywords.
Using basic access, macro operands are accessed positionally by their position in the command string, from left to right. The total number of operands available is available by calling the Get_Arg_Count function. Each individual operand is obtained by calling Get_Arg$(n) where n is the operand number; the first operand after the macro name itself is operand number 1.
The macro may have default operands specified by the macro prototype line (see Macro Prototype) For example, given the following prototype:
' sample.MACRO aaa bbb ccc
If the macro were invoked with the primary command line as Sample with no supplied arguments, then the Get_Arg_Count function would return 3. Get_Arg$(1) would be aaa, Get_Arg$(2) would be bbb, and Get_Arg$(3) would be ccc.
If the macro were invoked with the command Sample RED GREEN BLUE YELLOW, then the Get_Arg_Count function would return 4. Get_Arg$(1) would be RED, Get_Arg$(2) would be GREEN, Get_Arg$(3) would be BLUE, and Get_Arg$(4) would be YELLOW.
If the macro were invoked with the command Sample RED, then the Get_Arg_Count function would return 3. Get_Arg$(1) would be RED, Get_Arg$(2) would be bbb, and Get_Arg$(3) would be ccc.
All other validation of the operands is the responsibility of the macro itself.
If you choose to code your macro in a more "structured" design, then you can have the command line operands provided to you as passed parameters to your mainline SUB subroutine. To do this, certain considerations which must be met.
To convert your macro to a structured format, you must place an initial SUB subroutine header immediately after the macro prototype line, as described in Macro Prototype. If you do this, and you normally use your macro from the primary command line with some fixed maximum number of parameters, SPFLite will assign these for you just like any other SUB subroutine would receive them, so that calls to Get_Arg$ will not be needed.
This can be a quite useful feature, but it requires you to follow some fairly strict rules to take advantage of this:
- The initial SUB subroutine follows all the rules of thinBasic syntax, and must be ended by an END SUB statement.
- The SUB keyword must appear immediately following the SPFLite macro prototype line, meaning that the initial SUB keyword must be on line 2 of the MACRO file and no where else, and no comments may appear anywhere on the SUB statement.
- The initial SUB line may be continued on multiple lines if needed, by placing an _ underscore at the end of each continued line except the last one, as required by standard thinBasic line-continuation rules.
- The type of each parameter to the initial SUB must be individually declared with AS STRING. No other data type will work. Note this point carefully. If you fail to do this, your macro will not work correctly.
- In the body of the initial SUB subroutine, you can optionally issue the HALT statement anywhere you wish to end the macro. Otherwise, an implied HALT will be automatically issued when the subroutine issues an EXIT SUB, or passes through the normal END SUB.
- The initial SUB subroutine may have any valid name you wish.
- The initial SUB subroutine name need not match the name (if any) on the macro prototype header that appears on line 1 of the MACRO file, or the name of the MACRO file itself. However, for clarity and documentation purposes, it is recommended that you do make these names match. Future versions of SPFLite may or may not enforce such naming agreement.
- The parameters defined on the SUB statement are provided by merging the actual command line operands with the default operands which may be provided on the Macro Prototype header. When no value exists in either location, a null string "" is passed as the parameter.
- SUB subroutine statements can only define a fixed number of parameters. If you wish to access parameters that were specified on the primary command line beyond these, or need to handle a variable number of parameters, the Get_Arg$ function and other related functions are available and can still be used as documented, even though you used an initial SUB subroutine.
- When you write a macro with an initial SUB subroutine, and the macro is launched as a line-command macro, only the defaults on the macro prototype header will be passed, and if the prototype has no defaults, all of the parameters passed to the SUB will be empty (null) strings.
NOTE: Unless you strictly follow the SUB formatting rules, SPFLite may not detect that you have put an initial SUB subroutine in your macro, and it will not get called.
We have to perform a clever bit of "magic" to pull this off. SPFLite has to do a "read-ahead" and "peek" at your macro, and quickly analyze it to determine if you actually have an initial SUB subroutine like this. When it finds one, it inserts a call to that SUB, passing the appropriate parameters as needed. To do that, and do it reliably, you must ensure that the format of the SUB line strictly adheres to these rules.
In the initial SUB subroutine, you are free to call other subroutines and functions as desired. These can be built-in functions, functions imported by USES statements, or additional SUB and FUNCTION routines that you write yourself.
As with Basic Operand Access, validation of the command line operands is still entirely up to the macro code to perform.
It is important that you follow rule noted above to define all operands AS STRING. For example, this macro,
' sample.macro abc def
SUB sample (arg1,arg2,arg3 AS STRING)
' ... statements
is incorrect. The reason is that, as written, the AS STRING clause does not apply to all three parameters, but only to the last one. This is simply a thinBasic syntax rule, and we have no control over it. However, you needn't worry about forgetting this rule, SPFLite will verify this has been done properly and reject the macro if it breaks the rule.
What actually happens here is that when you have parameters written like "arg1,arg2," with no type information specified, thinBasic treats these as parameters of type VARIANT. It's too complicated to discuss here, except it's just not what you want. SPFLite will only pass you parameters as strings anyway, so you have to cooperate and do it the right way.
The correct way to write it is like this:
' sample.macro abc def
SUB sample (arg1 AS STRING, arg2 AS STRING, arg3 AS STRING)
' ... statements
If you like, you can put each parameter on a separate line, for readability and maintenance purposes, using line continuation like this:
' sample.macro abc def
SUB sample (arg1 AS STRING, _
arg2 AS STRING, _
arg3 AS STRING)
' ... statements
This support co-exists with all the Basic Access and SUB Operand Access methods for accessing macro operands. It make no changes at all to the Get_Arg$ and Get_Arg_Count functions, or to the support for SUB operand handling.
You are free to choose whatever method suits you best, and you also can combine these methods. For instance, your macro operands might have a "simple" format that does not need to be parsed, and also a more complex format. You can choose to do the full parse if you determine that the simple format is not present.
The idea is that a call to SPF_Parse is made, passing a set of parameters that describe the syntax of your macro's operands. Using these parameters, and the initially-created contents of the Get_Arg$ data array, SPF_Parse will 'sort out' and validate the macro operands according to your specifications. If all is well, it returns an RC=0, and makes all those parameters available for use. If a parsing or validation error is found, it will issue an RC=8 along with an error message which you can obtain with Get_Msg$ and display to the user.
Validation consists of the following:
- Depending on the optional validation flags, verification that the number of operands entered of that type meets the specification. Using flags, you can control whether an operand count represents an exact count, or is an upper limit (where the lower limit is zero), or if it is to be taken as an optional, "all or nothing" count.
- Where Line References are allowed, it can also validate that the line references entered are valid. For a line label like .ABC, the label must exist in your edit file to be "valid". For a line-number pseudo-label like .123, a data line on line 123 must exist, but since these are not actually labels, nothing else need be "defined".
- Where Tag operands are allowed, it can also validate that the Tag operands entered are valid Tags. A tag is valid if at least one tag of that name appears in your edit file.
- Where single keyword operands are specified, it simply tracks whether the keyword was specified or not. It is not an error if the keyword is absent.
- Where mutually exclusive keywords are specified, it validates that one and only one has been entered, This is the same principle used on SPFLite command like FIND, where you cannot say FIND FIRST LAST ABC.
- Where keywords have multiple aliases, it accepts them all and 'normalizes' them to return the first specified keyword. e.g. if the alias list were specified as (EXCL,EXCLUDE,EXC,X) then when any of them are entered it would be treated and returned as EXCL. This normalization process means that you don't have to check every possible spelling of a keyword, but only the first (left-most) one that appears in the list. This greatly simplifies the amount of work needed to check such keywords, since only value has to be tested.
On the completion of a successful SPF_Parse, there should normally be no need to examine or use the Get_Arg$ array. There will be no unaccounted-for operands 'left over' that would be the responsibility of the macro. (If there were unaccounted-for operands, these would be categorized as a syntax error. The fact that you don't have a syntax error means the parsing completed successfully).
However, since the initially-created "arg" array is not modified by Spf_Parse, nothing prevents access using the functions discussed above, if you have that need.
SPF_Parse sorts and categorizes the command line operands into five groups:
For all specified Keywords, you can call a single function to return a TRUE / FALSE indication of whether a particular keyword has been entered or not. For example, to see if the Keyword ALL had been specified, the code would be
IF Get_Arg_KW("ALL") then ...
When keywords are part of a mutually exclusive group (like WORD, PREFIX, SUFFIX) you may call a function to return which of the keywords were specified. The code for this would be
IF Get_Arg_KWGroup$("list-name") then ...
Use of the list-name and Get_Arg_KWGroup$ function does not prevent use of Get_Arg_KW for a specific keyword if you desire.
(Just to be clear, all keywords are optional. It would not really make sense to say that a keyword was mandatory every time you used a macro.)
Your macro may specify that it requires any number of Line References, there is no limit. When retrieving Line References, they are returned by relative position, left to right. Their exact operand location, and whether there are intervening operands of other types is immaterial. You request the first Line Reference, the second Line Reference, etc. The code for this would be
start-line = Get_Arg_LRef$(n)
where 'n' specifies the required relative Line Reference number.
Your macro may specify that it requires any number of Tag Operands, there is no limit. When retrieving Tag Operands, they are returned by relative position, left to right. Their exact operand location, and whether there are intervening operands of other types is immaterial. You request the first Tag Operand, the second Tag Operand, etc. The code for this would be
tag-name = Get_Arg_Tag$(n)
where 'n' specifies the required relativeTag Operand number.
Text operands are quoted strings or un-quoted strings which are not keywords nor not solely numeric. i.e. like the search and change strings of a CHANGE command.
Your macro may specify that it requires any number of Text Literals, there is no limit. When retrieving Text Literals, they are returned by relative position, left to right. Their exact operand location, and whether there are intervening operands of other types is immaterial. You request the first Text Literal, the second Text literal, etc. The code for this would be
srch-string = Get_Arg_TextLit$(n)
where 'n' specifies the required relative Text Literal number.
Numeric operands are un-quoted strings which are solely numeric digits. i.e. like the column range operands of a FIND command.
Your macro may specify that it requires any number of Numeric Literals, there is no limit. When retrieving Numeric Literals, they are returned by relative position, left to right. Their exact operand location, and whether there are intervening operands of other types is immaterial. You request the first Numeric Literal, the second Numeric literal, etc. The code for this would be
start-col = Get_Arg_NumLit$(n)
where 'n' specifies the required relative Numeric Literal number.
The SPF_Parse function may appear complicated at first, but don't let that hold you back from trying it. Let's have a look:
RC = SPF_Parse(TxtLit-number, NumLit-number, LinRef-number, Tag-number, _
[ keyword-set, ]
[ keyword-set, ]
What do we have? Four numeric values, and then a series of optional keyword definitions. Let's see the details.
The first four operands specify how many of each of the non-keyword operands your macro will allow. For example, if your macro operands consists solely of two line reference operands, the coding is simply:
RC = SPF_Parse(0, 0, 2, 0)
which is pretty straight-forward.
If your macro added an optional "ALL" keyword, the coding becomes:
RC = SPF_Parse(0, 0, 2, 0, "ALL")
again, not unduly complicated.
Many operands are optional. In our example above, the line references may be optional if the macro also allows you to mark the lines with a CC / CC line block. But if we code just the number 2 as the Line Reference operand with nothing else, that means the operands are mandatory, and there must be exactly 2 of them. To allow a varying number of arguments, it is handled by adding the ARG_VAR as a "flag" to the count value, so that this parameter to the SPF_Parse call is changed from 2 to 2+ARG_VAR.. The ARG_VAR option will allow from 0 (zero) to the number specified to be entered.
So if the TxtLit-number operand is coded as 2+ARG_VAR it means there can be from 0 to 2 Text Literals.
The flag name ARG_VAR, like the ARG_OPT and ARG_DEF flags discussed next, are predefined by SPFLite.
Some macro parameters may be used in an optional, "all or nothing" manner. This can be handled by using the ARG_OPT validation flag. When ARG_OPT is added to a count value, it indicates that for this type of parameter the number of supplied values must either be zero or the exact count specified.
For example, if the number of line references is specified as 2+ARG_OPT it indicates that there must be either no line references or exactly two line references.
You cannot combine the ARG_OPT flag and the ARG_VAR flag, since the meanings conflict. For instance, if a count value is specified as 2+ARG_VAR+ARG_OPT, the ARG_VAR implies that having one argument is valid, but ARG_OPT implies that it isn't, and these cannot both be true. If you attempt to use the flags this way, the SPF_Parse function will fail with a nonzero RC.
When Tags or Line References are used as operands, you may wish these entered values to be validated to ensure they correctly point at valid data lines. This can be handled by using the ARG_DEF validation option. When this is added to a value, it indicates that for this type of parameter data line reference must be valid. For example, if the number of line references ia entered as 2 + ARG_DEF it indicates that there must be two valid line references.
There are two basic types of keywords, the simple "I'm here / I'm not here" type of keyword (like the RAW operand of CUT), and the mutually exclusive list of keywords (like ON / OFF, or WORD / PREFIX / SUFFIX)
For the simple type, the keyword-set operand is just the keyword itself, couldn't be simpler. "ALL" or "TRUNC" etc.
For the mutually exclusive type, the various keywords are just entered, separated by commas. e.g. for an ON/OFF pair, the keyword-set would be coded as "ON,OFF". For the WORD / PREFIX / SUFFIX example it would be "WORD,PREFIX,SUFFIX"
When a keyword can be specified in various alias values, the normal single keyword value in the list is replaced by the list of aliases, enclosed in parentheses. As an example, a macro might accept either DELETE or PURGE as operands. These would normally be coded as "DELETE,PURGE". But what if you wanted to accept DEL as an alternate for DELETE and PUR as an alternate for PURGE. It would then look like "(DEL,DELETE),(PUR,PURGE)"
Almost done now, just one more wrinkle for keywords. In the above examples, any of the keywords can be tested for using the Get_Arg_KW function, but what if, for the WORD/PREFIX/SUFFIX example, you wanted to ask "Which one was entered" without having to test each one individually? This is possible, but you have to assign a 'list-name' to refer to that specific set of keywords. Then you can ask for which KW in the set was entered by using the Get_Arg_KWGroup$(list-name).
So where does the list-name go? It is inserted at the beginning of the list, followed by a colon (:). Our example here, using the list-name WTYPE, would be coded as:
Asking for 'which one was entered' would be done by
wtype = Get_Arg_KWGroup$("WTYPE")
A picture is worth a thousand words, so lets do an example. Here's a reasonably complex imaginary macro command whose syntax is similar to FIND and looks like:
[ start-col [ end-col] ]
[ start-line [ end-line] ]
[ WORD | PREFIX | SUFFIX ]
[ RED | GREEN | BLUE | YELLOW ]
[ ALL ]
Lets code the SPF_Parse request to parse this command:
IF SPF_Parse(1, 2 + ARG_VAR, 2 + ARG_VAR + ARG_DEF, 0, _
- The first operand 1 says there is 1 mandatory text literal (the search-str in the syntax diagram)
- The second operand 2 + ARG_VAR says there may be 0 to 2 optional numeric literals (the start-col and end-col in the syntax diagram)
- The third operand 2 + ARG_VAR + ARG_DEF says there are from 0 to 2 optional line references (the start-line and end-line in the syntax diagram and that if entered, the references should be validated as correct line references)
- The fourth operand 0 says there are no allowable Tag operands.
- The "WTYPE:WORD,PREFIX,SUFFIX" operand specifies 3 mutually exclusive keywords, which can be referenced as a group using the list-name value of WTYPE.
- The "COLOR:RED,GREEN,BLUE,YELLOW" operand specifies 4 mutually exclusive keywords, which can be referenced as a group using the list-name value of COLOR.
- The "ALL" operand specifies the simple keyword ALL which may be present or not present.
- The entire SPF_Parse function is coded as part of an IF ... THEN statement. Since SPF_Parse returns a non-zero value (8) if any parsing errors occur, it is usually simplest to test this way and use an SPF_SetMsg to issue the error message text provided by SPF_Parse. This text is obtained using a standard Get_ErrMsg$ function.
- Assuming SPF_Parse returns with no error status, the macro can continue at this point to use the parsed values, or to perhaps perform additional validation if required by the circumstances of the macro's function.
Here is a simple macro coded in the three different modes to show the differences. The syntax for this example is:
DEMOFIND srch-string [ ALL ]
Note: All examples below should probably use SPF_Quote$ to properly process the srch-string, but this has been left out to simplify the demonstration code.
Basic Operand Access
DIM OpAll as STRING
IF Get_Arg_Count < 1 then Halt(FAIL, "Missing search string")
OpAll = ucase$(Get_Arg$(2))
If OpAll <> "" and OpAll <> "ALL" then Halt(FAIL, "Unknown operand: " + OpAll))
SPF_CMD("FIND " + Get_Arg$(1) + " " + OpAll)
SUB Operand Access
SUB DEMOFIND(srchstr as string, OpAll as STRING)
IF srchstr = "" then Halt(FAIL, "Missing search string")
If OpAll <> "" and OpAll <> "ALL" then Halt(FAIL, "Unknown operand: " + OpAll))
SPF_CMD("FIND " + srchstr + " " + OpAll)
Full Parse Access
if SPF_Parse(1, 0, 0, 0, "ALL") then Halt(FAIL, Get_Msg$")
SPF_CMD("FIND " + Get_Arg_TextLit$(1) + iif$(Get_KW("ALL"), " ALL", "")
Created with the Personal Edition of HelpNDoc: Produce online help for Qt applications