SPFLite determines that a command is a macro when a command name is not recognized as a built-in command, but a file having the name of macroname.MACRO is found the in the SPFLite macro directory. This design means that (unlike mainframe ISPF) it is not possible to replace a built-in primary command with a macro.
It is possible to make a macro used as a primary command with the same name as built-in line command. For example, a primary-command macro named CC.MACRO is valid. However, that could be confusing, and for most people, it will be best if you avoid any built-in command names, whether line-mode or command mode. This is especially true in case you write a macro that could be used both as a primary-command macro or as a line-command macro.
When SPFLite detects a macro is being invoked, it triggers the thinBasic interpreter and passes it two things: the source of your macro from the macroname.MACRO file and the list of available SPFLite interface functions being made available.
The number of available interface functions is extensive (approaching 100) but don't let this put you off. Just like SPFLite's extensive list of Primary commands, Line commands, and Keyboard primitives, nobody will ever need or use all of them. We're just trying to ensure that we provide as extensive an interface as possible. Nothing is worse than developing a macro and saying "Gee, if there was only a function that would ....". We don't want that to happen to you.
thinBasic parses and analyzes your source code and begins execution of the interpreted code. At this point, the macro source code has still been provided no information whatsoever about SPFLite, or the file being edited.
All information passed between the macro and SPFLite is handled via those custom functions which SPFLite made available when invoking thinBasic.
If you're a compiler geek or something, what SPFLite does is "inject" the SPFLite interface functions into the thinBasic namespace, so that you don't have to declare or "include" them. (If you're not a compiler geek, then it's magic - and don't nobody ask no more questions.)
SPFLite allows you to write macros that can be used on the primary command line, or in the line command area. The macro has the same structure either way. You would have to determine how your macro is getting called, by making queries against some SPFLite built-in functions.
NOTE: You cannot have an SPFLite interaction in which you have macros used as line commands, and any command on the primary command line (whether built-in SPFLite primary command or a primary-command macro), at the same time.
When you use a macro as primary command, and you have any pending line commands at the same time, these would usually consist of an A or B and/or a CC block, to define the source lines and optional destination line.
When a macro is being used as a primary command
- Is_Primary_Cmd will return TRUE
- Is_Line_Cmd will return FALSE
- Get_Src1_LPTR and Get_Src2_LPTR return non-zero values if you have a line range defined using a CC or MM block, otherwise these will return zero.
- Get_Src_LCMD$ will return a C/CC or M/MM line command if present, otherwise an empty (null) string value
- Get_Dest1_LPTR, GET_Dest2_LPTR and Get_Dest_LCMD$ will be set to appropriate values if you have defined a destination line range using A, AA, B, BB, H, HH, W, WW, O, OO, OR or ORR line commands, otherwise these will be zero or null.
When a macro is being used as a line command
- Is_Line_Cmd will return TRUE
- Is_Primary_Cmd will return FALSE
- Get_Src1_LPTR and Get_Src2_LPTR will return non-zero values
- Get_Src_LCMD$ will return the contents of the line command used to invoke the macro. For example, a macro AX is invoked in response to a line command of AX or AXX.
- Get_Dest1_LPTR, GET_Dest2_LPTR and Get_Dest_LCMD$ will be set to appropriate values if you have defined a destination line range using A, AA, B, BB, H, HH, W, WW, O, OO, OR or ORR line commands in addition to the macro defined source range, otherwise these will be zero or null.
The name of a line-command macro is the name you'd use for a single line. So, for example, if you wanted to create a line command called AX, your macro would be called AX.MACRO. If you want to use this command as a block-mode line-command macro, you can do it any of the normal ways you'd expect with an SPFLite built-in line command:
- Define the beginning and end of the block with AXX
- Put AX on a line, and follow it with an n, / or \ modifier
- Create a SET name of the form SET MACROMODE.AX = BLOCK if you only want to use AX as a block-mode command
To accomplish its desired actions, your macro must use the provided SPFLite functions to both fetch and to store data back and forth between SPFLite's data areas and the macro's own variable areas.
There are four main types of provided functions:
The Get_xxxx function calls will return the value of the requested SPFLite data item to the macro. These Get_xxxx function calls can effectively be used as if they are variables. Get_xxxx functions ending in a $ sign (like Get_RECFM$) return character strings, those without a $ sign return numeric values. (This is a standard BASIC naming convention.)
The Set_xxxx function calls are used to alter the value of a specified SPFLite data item using the provided macro value.
The IS_xxxx function calls are used to test the basic characteristics of the environment, of data items, and to test the line type of a specified line within the Edit session. SPFLite provides access to all of the different line types (including "special" ones), not just text data lines. Access is provided to NOTE lines, MASK lines, etc. The Is_xxxx function calls will return TRUE if the line is the requested type, and FALSE if it is any other type. For example, to test if a specific line is a normal text data line you would code IF Is_DATA(line-ptr) THEN ....
Because special lines are not data lines, they only can have a line pointer to them - but never a line number; whereas a data line can have both a line pointer and a line number.
The SPF_xxxx function calls are used to request specific SPFLite services of a more complex nature, or ones which cannot be strictly described as a GET or SET operation. SPF_CMD for example is used to pass a primary command to SPFLite for execution.
Each of these function calls is described in the Function Overview and Function Details sections.
To inspect or modify the lines in an Edit file, you must be able to identify which line of the file you are referring to when using the supplied SPFLite interface functions. This seems simple enough - couldn't we just use the visible line number itself to reference a line? No.
We need to be able to access all the different line types within an edit session, not just the text data lines. An edit session may have "special" lines such as NOTE, BNDS, COLS, TABS, etc. that we might want access, and these lines don't have line numbers.
The solution is to use something called a line pointer as the key to accessing the data. As long as you keep in mind that a line pointer can sometimes point to non-text lines, most of the time you can think of it as a line number. To simplify things, most (but not all) of the custom interface functions work with line pointer parameters. We have been careful with naming conventions so you'll know what functions take what kind of arguments, and there are ways of converting between line numbers and line pointers.
The difference between a line number and a line pointer becomes an issue when calling the SPF_CMD function, and when interfacing with the external user (yourself) since the user never sees and is not aware of the line pointer values. So, line number values are provided by the user, and you may want to pass back line number values to the user, for example, in messages. To do this, you will need the conversion functions.
Conversion between a line number and a line pointer is a simple process. There are two companion functions whose sole purpose is to convert line pointers to line numbers, and line numbers to line pointers. A side benefit of the line number to line pointer conversion function is that it incorporates automatic support for line labels as well as line numbers. In that sense, it doesn't just accept line numbers but more generic line references, of which line numbers are one type.
But the most powerful reason for using line pointers over line numbers is that line pointers are amenable to being adjusted arithmetically while line numbers are not. Why so?
Line numbers, especially if using the Quick Renum option, are not guaranteed to be consecutive. Line-Number + 1 simply cannot be guaranteed to point at the next line number. (e.g. lines may be numbered in increments of 10, or 5, or 1; there is no way of knowing)
Line pointers however, will always point at a valid line of some type. (lets ignore the Top and Bottom boundary conditions for now). So you can always do a line-pointer + 1 calculation and you will always end up with a valid line pointer. This is the main reason that the macro interface functions are set up to use line pointers almost exclusively. It's just easier in nearly all respects.
A line number is the numeric value of the sequence number you see displayed on a data line. So, if you see a data line with a sequence number 000123 then the line number is 123. A valid line number will never be zero.
A line pointer may be thought of as a numeric index to every visible line (special or ordinary) of the entire edit file, starting with 1 for the Top of Data line, and incremented by 1 for each subsequent line (special or ordinary) until the Bottom of Data line is reached.
That is an important fact to remember: The Top of Data line will always have the known Line Pointer value of 1. You can use that fact when you are scanning the lines in your file, and you need a Line Pointer value as a starting point.
This means that you can scan a range of lines (special or ordinary) using a FOR/NEXT loop, checking the "type" of each line with an Is_xxxx function, incrementing the line pointer by 1 each time, and taking appropriate action as needed. The most common such test will be checking for Is_Data, passing the function a line pointer argument.
Like line numbers, valid line pointers will never be zero. However, whatever other values might be taken on by a line pointer, you should always let SPFLite inform you, and not make assumptions about the value you may get. Future releases of SPFLite could change those assumptions, and if you were relying on a particular implementation, your macro might not work right in a future release. Don't try to be too 'tricky' with line pointers; just use them as the documentation and examples show you.
What a line pointer definitely is not is a true memory pointer to some dynamically-allocated storage area within thinBasic. Don't even think of trying to use it that way.
When you have the Fast Renumber General option (within SPFLite Global Options) enabled, it may affect how the conversion functions handle converting a line pointer to a line number. Again, use the functions for what they are, and don't try to be too tricky with them or make assumptions about what the value is going to be.
The SPF_CMD function is used to issue SPFLite primary commands. The command string you pass to the function looks just like one you would type in yourself on the primary command line. There are a few minor differences:
- SPF_CMD only takes one command. You cannot use a command separator character (like a ; semicolon) to perform multiple commands.
- The special ! notation that erases the primary command area prior to executing the command is not supported. That feature is primarily in place to assist key-mapped commands to function properly in case there were any "left over" commands or operands in the primary command area, an issue that doesn't apply to a macro.
- Presently, you cannot issue a macro call from a macro. So, in AX.MACRO, you cannot issue a call to SPF_CMD("BX") for instance.
When you create a command string for SPF_CMD using string constants and expressions, and the command contains a line reference, you must determine if the reference is a line number or a line pointer. Most macros will use line pointers almost exclusively.
If you have a Line Number value, you create the command string so that it appears the same as you would type it in on the primary command line. Recall that SPFLite allows what are called "temporary line labels" or "line number pseudo-labels", which are line numbers with a preceding dot.
For example, if you wanted to change ABCD to WXYZ on line 4 of the edit file, you could issue
SPF_CMD ("C ABCD WXZY .4 ALL")
However, if you have a Line Pointer value, you can't (directly) use such notation. For example, the line with ABCD might have a line number of 4, but a line pointer of 7. You may have gotten this value of 7 from a function call like GET_FIND_LPTR. Now, if you created a command like this:
SPF_CMD ("C ABCD WXZY ." + TSTR$(GET_FIND_LPTR) + " ALL")
it would evaluate to this:
SPF_CMD ("C ABCD WXZY .7 ALL")
but that won't work, because it's referring to the wrong line. Since most SPFLite macro interface functions that deal with lines use line pointers, somehow we have to make SPF_CMD understand what we mean.
There's two ways to do this. First, you can use the conversion function GET_LNUM. In our example, that would convert the 7 into a 4:
SPF_CMD ("C ABCD WXZY ." + TSTR$(GET_LNUM (GET_FIND_LPTR)) + " ALL")
Second, SPF_CMD allows an alternative notation. If you use an ! exclamation point instead of a dot, SPF_CMD will understand the value to be for a line pointer rather than a line number. When you do this, SPF_CMD will internally convert the line pointer for you into a line number.
In our example, you would do this:
SPF_CMD ("C ABCD WXZY !" + TSTR$(GET_FIND_LPTR) + " ALL")
and it would evaluate to this:
SPF_CMD ("C ABCD WXZY !7 ALL")
The SPF_CMD function would then see the exclamation point, take the number 7, internally call GET_LNUM itself to obtain the line number 4, and finally treat the command as if you issued:
SPF_CMD ("C ABCD WXZY .4 ALL")
Some points to keep in mind if you use the exclamation point notation:
- This notation only works for the SPF_CMD function, and no where else.
- This notation only applies to macros. You cannot issue this syntax on the regular primary command line.
- The exclamation point must be preceded by a blank, and the number after it must be followed by a blank or the end of the string
- Exclamation points are only recognized if not in SPFLite quotes.
So, a function call of SPF_CMD ("C ABCD '!7' ALL") would have nothing to do with this notation; the !7 is just an ordinary string.
Since there are two possible ways to refer to a line, which way is "better" ? To (probably) no one's surprise, the answer is, "it depends".
A more appropriate question is, better for what? It depends on what you're doing in your macro.
This question is similar to asking which is better, Celsius or Fahrenheit? It depends where you live. (Since George lives in Canada, and I live in the States, we each have our own preferences. Basically, whether it's 100 C or 212 F, you know you're in hot water ! - RH)
There are several factors to consider when making this decision:
- The SPFLite macro interface functions take line pointers. So, this is the form you will use most often, and if line pointers are what you have, this will be the most efficient method. For the most common cases, you will get a line number (or label) as a macro argument, convert it to a line pointer using Get_LPTR, and use that value from then on.
- The SPF_CMD function will accept either notation (as discussed above).
- If you have one form, but need another, you can call a conversion function to get the desired value.
- Line pointers may be somewhat transient, depending on what you're doing. For example, suppose you have several non-data lines in your file. These could be =PROF> lines, =NOTE> lines, =TABS> lines, etc. You may also have excluded-line placeholders present. If you obtain a line pointer, and then these special lines disappear as a result of issuing a RESET command or by direct deletion, the line pointer may no longer be valid. A line number will remain valid across a RESET command.
- It is important to understand that when a line pointer becomes "invalid" it does not mean you can't use it. For example, a line pointer might contain 5, meaning it's the fifth line from the beginning, counting the Top of Data as line-pointer line 1, but it would be the fourth data line. Now, if you inserted a =NOTE> or =TABS> line somewhere between the Top of Data and data line 4, or removed one already there, a line pointer of 5 won't point to data line number 4 any more. But that's won't stop you from using it. You can still pass a value of 5 to any of the macro interface functions that take line pointer arguments - the function won't stop you or claim the number 5 was "invalid". However, if you do that, the 5 wouldn't refer to data line 4 any more, but to some other line. That would almost certainly not be what you wanted.
The point is, line pointers and line numbers both have their place. You need to know what you're doing, and use them for what they are good for.
For line pointers, you must understand the rules that affect the validity of a line pointer reference, and stay within those rules. The main rule is that you must not "rock the boat" by doing anything that will change the relative line-pointer positions of data lines. Things that cause that are inserting and deleting (other) data lines, inserting or deleting special lines, and excluding or unexcluding lines. Special and excluded lines are affected by the RESET command. Provided you don't do anything to invalidate a pointer, you can continue to use it without problems. (They are not "fragile" and won't "break" by themselves.)
If you are forced to "rock the boat" so to speak, then you may have to recalculate new line pointers. Suppose you needed to do a RESET command that would impact the line pointer to a data line. You could "save" the pointer by (a) converting it to a line number, (b) issuing the RESET command, and finally (c) converting the line number back to a line pointer. You could also put a label on the line, and retrieve the label's line pointer later on.
As long as you keep these rules in mind and live within them, everything will work just fine.
A macro will often need to refer to a line range. The way in which a macro obtains line-range information depends on whether is was launched as a line-command macro or as a primary-command macro.
Primary command macros may include line-range information as operands, such as line labels (including pseudo-label line numbers) and line tags. However, SPFLite will not interpret such operands. It merely passes them along to the macro, which must retrieve them with the Get_Arg$ function and then interpret them as it sees fit. For line label operands, you would use the Get_LPTR function to convert them to a Line Pointer value, which is what most line-referencing macros use.
This means that if you create a macro called ABC.MACRO, you can issue a primary macro command like ABC .AA .BB, but you would have to write thinBasic code to interpret arguments 1 and 2 so that you could utilize the provided labels.
This may seem like an inconvenience (and to a certain extent, it is) but this design allows you complete control over how you would use these labels. For example, you might wish to write a macro that took 3 or even 4 labels. Standard SPFLite primary command processing would treat that as a syntax error, but your macro code could do something to address your unique requirements. If SPFLite recognized a standard line-range operand on macros (and nothing else), it would simplify some tasks, but would hold you back from doing other things. For now, the design seems like a good compromise.
While you don't have direct support for line labels on primary macro commands, you do have the ability to query other line commands that may be pending when the macro is launched. These are source line ranges and destination line ranges. Specifying source and destination line ranges is optional when launching a macro.
Technically, a line-command macro always defines its own source line range by its very presence in the line command area, so when you use a line-command macro, you always have an implicit source line range; it isn't optional.
For a primary-command macro, a source line range can be a CC or MM block. Such blocks can be single lines, a range of lines defined by a C or M followed by n or a / or \ modifier, or a pair of CC or MM commands to define a block.
For a line-command macro, the macro name itself appears on one or two data lines to define the source block. As discussed elsewhere, SPFLite automatically detects the single-line form vs. the block-mode form of a line command. For example, if you write a macro called AX, then you could do any of the following to define a source block: AX, AXn, AX/, AX\, AXX or AXAX.
For both primary-command and line-command macros, you can specify a destination line range using any of the following line commands:
A, AA, B, BB, H, HH, W, WW, O, OO, OR and ORR.
The non-block forms A, B, H, W, O and OR can take the usual n, / and \ modifiers. You can also specify a trailing + or - modifier.
SPFLite does not act upon source or destination line command operands when used by a macro. That means that CC will not copy lines, MM will not move lines, OO will not overlay lines, AA will not intersperse copied data every few lines, HH will not replace lines, and so on.
But if that's true, what does it do? What good is it to use any of these things? These source and destination line ranges pass along information to the macro, which you can query and act on yourself. You can decide which of any of these line commands you want to recognize in your macro, and decide what they will do if used. What SPFLite does is (a) ensures that only these line commands can coexist with your macro's execution, and no other ones, (b) makes sure that they are in the proper format, and properly matched if used in pairs, and (c) will extract any addition information like n values and + or - and K modifiers that may be present, so you can look at them and act accordingly.
For the source line range, you have the following functions available:
Get a string containing the name of the source line command. For line-command macros, this is the macro name (possibly a plural/block form) of the macro name. For primary-command macros, this may be a C/CC or M/MM command, or may be an empty (null) string if no C/CC/M/MM was used with a primary-command macro.
Get the line pointer of the first (or only) line in the source line range, or 0 if not applicable.
Get the line pointer of the last (or only) line in the source line range, or 0 if not applicable.
Get optional n value on the source line range. For example, if you have a source line range of C5, Get_Src_Op will return the number 5. The function returns 0 when no explicit command operand number is specified. For instance, if you define a source block such as CC which happens to be 5 lines long, the function will still return 0, because the number 5 was not actually specified on the CC block.
Gets the + or - (post-unexclude or post-exclude) modifier and & (Retain) modifier if one is present on the source line range. The returned value is always two characters long. Position 1 will contain + or - or blank, and position 2 will contain a R or blank.
The / and \ line command modifiers are processed, if present, and this gets reflected in first and last LPTR values for the line range, but the / and \ codes themselves are not returned as LMOD values.
For the destination line range, you have the following functions available:
Get a string containing the name of the destination line command. This may be an A/AA, B/BB, H/HH, W/WW, O/OO, or OR/ORR command, or may be an empty (null) string if no destination line range was specified.
Get the line pointer of the first (or only) line in the destination line range, or 0 if not applicable.
Get the line pointer of the last (or only) line in the destination line range, or 0 if not applicable.
Get optional n value on the destination line range. For example, if you have a destination line range of H5, Get_Dest_Op will return the number 5. The function returns 0 when no explicit command operand number is specified. For instance, if you define a destination block such as HH which happens to be 5 lines long, the function will still return 0, because the number 5 was not actually specified on the HH block.
Gets the + or - (post-unexclude or post-exclude) modifier and & (Retain) modifier if one is present on the destination line range. The returned value is always two characters long. Position 1 will contain + or - or blank, and position 2 will contain a R or blank.
The / and \ line command modifiers are processed, if present, and this gets reflected in first and last LPTR values for the line range, but the / and \ codes themselves are not returned as LMOD values.
Note that when you use line macros in block form, or use destinations such as OR, you can define a 'block' by repeating the last character. (This is the same rule that ISPF uses, and we follow the ISPF rules on this.) So, the block form of a macro AX is AXX. When you ask for the line command name of the source or destination using Get_Src_LCmd$ or Get_Dest_Lcmd$, you get the actual command used, which in this case is AXX.
Created with the Personal Edition of HelpNDoc: Produce online help for Qt applications