Programming can contain many layers of abstraction. A useful layer is to change manner in which the computer actually sees your program. Language ‘directives’ do just that. In this article we will explore three of the commonly used program directives and discuss how they can be used with Legato.
Friday, November 17. 2017
LDC #59: The Program Before the Program
Directives
Directives tell the script processor to perform certain tasks during the initial preprocess parsing. Directives are useful in a number of important ways: to allow aliases to be defined, to include other sections of programs, and finally to direct certain sections of the program to be included or not included in the final executable portion of the script.
Like many other programming languages, Legato uses the ‘#’ character before a statement to indicate that it is a directive. The # must be the first item on the line and the statement encompasses the complete text line. Directives are not actually “run” or “executed”. Rather, they tell the script engine do things, like pretend the string IDCANCEL is really the number 2. Directives are all processed by the script engine in the first stage of program execution known as the preprocess phase.
There are a number of directives we will cover in this article:
#define — Defines a term to be replaced in the code.
#pragma — Specifies an action to be performed during script preprocessing.
#include — Directs the script engine to include a file as if it were part of the main file.
In a later articles I will cover conditional directives and resources.
#define — A Programmer’s Best Friend
We’ve used the #define directive in previous blog posts, but let’s examine it more formally. The #define directive is actually very simple: a string of characters is defined to be another string of characters. For example (expressed in various places in the script):
#define MY_NAME 201 ... (in resource) ... CONTROL "", MY_NAME, "edit", ES_LEFT|WS_CHILD|WS_VISIBLE|WS_BORDER|WS_TABSTOP, 53, 15, 119, 12, 0 ... (in procedure) ... name = EditGetText(MY_NAME);
At every point that MY_NAME appears, the value 201 will be substituted. This is useful in that a named value can be used in both a resource definition for a dialog and within the procedures to reference the dialog control. While during the development process numbers could be used, defined terms are better for creating easily organized and readable code. In addition, if a value ever needs to be changed, using a define allows you to change that value once in the original definition rather than everywhere it appears in the code.
The format is as follows:
#define name replacement_text
Where the name is the name or text of the item to be replaced. The name must follow the variable naming rules and cannot contain spaces, operators or other information. The replacement_text is then the text to replace the name with. The text can be a complex series of data containing variables, expressions and any data desired by the programmer. The name may also contain other references to defined terms. The replacement can be recursive and the use of parenthesis is recommended for anything that might be ambiguous, Also note that the define directive does not support complex macros with parameters.
While the text of a define can be upper and lower case, it is general practice to use all capitals to help differentiate it from other variables and information. So readers know that “MY_NAME” is a definition and “my_name” is a variable or function name.
A define statement can be repeated within the code so long as the replacement text is the same. Defines can also be used in resource editors.
Improperly defined defines will result in errors at the point of definition. For example, redefining a term with a different value will generate an error. Syntax errors within the defined text (or caused by the defined text’s insertion) will be displayed at the point of parsing where the term was replaced.
Finally, defines can be placed in code sections or, as stated above, within resource sections.
The SDK Definition
Unless specifically directed otherwise, the SDK header file “LegatoDefaultSDK.h” is automatically included in the script engine. It contains thousands of definitions ranging from language-function specific definitions as well as selected conventional definitions from the Windows SDK.
An example of of language-function definitions is flag information for the Word Parse Object. It has the prototype:
handle = WordParseCreate ( [int mode], [string data] );
In the SDK the following is defined for the function:
#define WP_GENERAL 0x00000000 /* General – Stops on Word Spaces */ #define WP_SGML_TAG 0x00000001 /* Tags – Stops on SGML Tags */ #define WP_PROGRAM 0x00000002 /* Program */ #define WP_PROGRAM_GROUP 0x00000003 /* Program Groups */ #define WP_TAG_ENCAPSULATE 0x00000004 /* Parse Tags <> Adobe/Encapsulated */
If you compare this to the documentation, you will notice some similarities and differences. First, there is an additional mode, for parsing Adobe (which is undocumented). Second. the modes are defined as hexadecimal in the SDK and integer in the documentation. This is because under the hood, the mode becomes part of a larger set of definitions:
/****************************************/ /* ** Parse Data */ /* * Modes */ #define WP_MODE_MASK 0x0000000F /* Mode Mask */ /* o Parse Type */ #define WP_GENERAL 0x00000000 /* General – Stops on Word Spaces */ #define WP_SGML_TAG 0x00000001 /* Tags – Stops on SGML Tags */ #define WP_PROGRAM 0x00000002 /* Program */ #define WP_PROGRAM_GROUP 0x00000003 /* Program Groups */ #define WP_TAG_ENCAPSULATE 0x00000004 /* Parse Tags <> Adobe/Encapsulated */ /* o Flags */ #define WP_BIG_WORD_BUFFER 0x00000010 /* Use 1mb Word Buffer */ /* * Output Flags */ #define WP_OUT_FLAG_MASK 0xFFFF0000 /* Mask for Output Flags */ /* o General */ #define WP_EOB 0x00010000 /* End of Buffer */ #define WP_LEADER_BREAK 0x00020000 /* Stopped for Leader or Ellipse */ #define WP_STOP_POSITION 0x00040000 /* Stop Position Hit (passed) */ #define WP_OVERFLOW 0x00080000 /* Buffer Overflow */ /* o Tag Mode */ #define WP_TAG_BREAK 0x02000000 /* Word Broke on Tag Syntax */ /* o Program Mode */ #define WP_PROGRAM_SPACE 0x01000000 /* Broke on Program White Space */ #define WP_PROGRAM_BREAK 0x02000000 /* Broke on Program Syntax */ #define WP_PROGRAM_BREAK_SET 0x04000000 /* Item Is Program Syntax */ #define WP_PROGRAM_SYNTAX_ERROR 0x08000000 /* Program Syntax Error */ #define WP_PROGRAM_STRING 0x10000000 /* Return Expression Group */ #define WP_PROGRAM_PARAMETER 0x20000000 /* Function Group - () */ #define WP_PROGRAM_ARRAY 0x40000000 /* Array Group - [] */ #define WP_PROGRAM_FAIL_CLOSE 0x80000000 /* Failed to Close Expression */ /* */ /* ** Program Parse Results */ /* * Control */ #define WP_RESULT_MASK 0x0000FFFF /* Parse Result Mask */ #define WP_RESULT_GROUP_MASK 0x0000FF00 /* Parse Result Mask */ #define WP_RESULT_CALLER_MASK 0xFFFF0000 /* Reserved (Caller Bits/Space) */ /* * Comments */ #define WP_RESULT_COMMENT 0x00000100 /* Comment Group */ #define WP_RESULT_COMMENT_EOL 0x00000101 /* Comment to EOL / / */ #define WP_RESULT_COMMENT_OPEN 0x00000102 /* Comment to Open / * */ #define WP_RESULT_COMMENT_CLOSE 0x00000103 /* Comment to Close * / */ /* * Control */ #define WP_RESULT_CONTROL 0x00000200 /* Control Group */ #define WP_RESULT_END 0x00000201 /* Expression End ; */ #define WP_RESULT_NEXT 0x00000202 /* Next Item/Parameter , */ #define WP_RESULT_BLOCK_OPEN 0x00000203 /* Block/Group Open { */ #define WP_RESULT_BLOCK_CLOSE 0x00000204 /* Block/Group Close } */ #define WP_RESULT_LABEL 0x00000205 /* Label / ID : */ /* * Logical Operators */ #define WP_RESULT_LOGICAL_OPERATOR 0x00000300 /* Logical Group */ #define WP_RESULT_LOGICAL_NOT 0x00000301 /* Logical NOT ! */ #define WP_RESULT_LOGICAL_AND 0x00000302 /* Logical AND && */ #define WP_RESULT_LOGICAL_OR 0x00000303 /* Logical OR || */ #define WP_RESULT_EQUAL_TO 0x00000304 /* Equal to == */ #define WP_RESULT_GREATER_EQUAL 0x00000305 /* Greater Than >= */ #define WP_RESULT_LESS_EQUAL 0x00000306 /* Less Than Equal <= */ #define WP_RESULT_NOT_EQUAL_TO 0x00000307 /* Not Equal != */ #define WP_RESULT_GREATER 0x00000308 /* Greater Than > */ #define WP_RESULT_LESS 0x00000309 /* Less Than < */ /* * Math */ #define WP_RESULT_MATH_OPERATOR 0x00000400 /* Math Group */ #define WP_RESULT_MATH_ASSIGN 0x00000401 /* Assign (Equal) = */ #define WP_RESULT_MATH_PLUS 0x00000402 /* Plus + */ #define WP_RESULT_MATH_MINUS 0x00000403 /* Minus - */ #define WP_RESULT_MATH_MULTIPLY 0x00000404 /* Multiply (pointer) * */ #define WP_RESULT_MATH_DIVIDE 0x00000405 /* Divide / */ #define WP_RESULT_MATH_MODULUS 0x00000406 /* Modulus % */ #define WP_RESULT_MATH_INVERT 0x00000407 /* Invert ~ */ /* * Bitwise Math */ #define WP_RESULT_BITWISE_OPERATOR 0x00000500 /* Bitwise Group */ #define WP_RESULT_MATH_AND 0x00000501 /* Math And & */ #define WP_RESULT_MATH_OR 0x00000502 /* Math OR | */ #define WP_RESULT_MATH_XOR 0x00000503 /* Math XOR ^ */ #define WP_RESULT_SHIFT_LEFT 0x00000504 /* Shift Left << */ #define WP_RESULT_SHIFT_RIGHT 0x00000505 /* Shift Right >> */ /* * Postfix/Prefix */ #define WP_RESULT_POSTFIX 0x00000600 /* Postfix Group */ #define WP_RESULT_INCREMENT 0x00000601 /* Increment ++ */ #define WP_RESULT_DECREMENT 0x00000602 /* Decrement -- */ #define WP_RESULT_NAMESPACE 0x00000603 /* Namespace :: */ #define WP_RESULT_MEMBER 0x00000604 /* Member . */ #define WP_RESULT_MEMBER_POINTER 0x00000605 /* Member as Pointer -> */ /* * Assignment */ #define WP_RESULT_ASSIGNMENT 0x00000700 /* Assignment Group */ #define WP_RESULT_ASSIGN_PLUS 0x00000701 /* Assign w/Plus += */ #define WP_RESULT_ASSIGN_MINUS 0x00000702 /* Assign w/Minus -= */ #define WP_RESULT_ASSIGN_MULTIPLY 0x00000703 /* Assign w/Times *= */ #define WP_RESULT_ASSIGN_DIVIDE 0x00000704 /* Assign w/Divide /= */ #define WP_RESULT_ASSIGN_MODULUS 0x00000705 /* Assign w/Modulus %= */ #define WP_RESULT_ASSIGN_AND 0x00000706 /* Assign w/Bitwise AND &= */ #define WP_RESULT_ASSIGN_OR 0x00000707 /* Assign w/Bitwise OR |= */ #define WP_RESULT_ASSIGN_XOR 0x00000708 /* Assign w/Bitwise XOR ^= */ #define WP_RESULT_ASSIGN_SHIFT_LEFT 0x00000709 /* Assign w/Shift Left <<= */ #define WP_RESULT_ASSIGN_SHIFT_RIGHT 0x0000070A /* Assign w/Shift Right >>= */ /* * Non-C */ #define WP_RESULT_NON_C 0x00000800 /* Non-C Group */ #define WP_RESULT_NC_NOT_EQUAL 0x00000801 /* Not Equal <> */ #define WP_RESULT_NC_ASSIGN_CAT 0x00000802 /* Assign and Cat .= */ #define WP_RESULT_NC_EQUAL_TYPE 0x00000803 /* Equal Strict === */ #define WP_RESULT_NC_NOT_EQUAL_TYPE 0x00000804 /* Not Equal Strict !== */
It would be near impossible, even for the biggest nerd, to remember all the bit positions when taken together with the thousands of other definitions.
If you want to use your own SDK, you can by using a pragma to direct the script engine not to load the preprocessed information. There’s more on that below.
The #pragma Statement
Pragma (or from “pragmatic”) is a directive that tells the script engine to process or not process certain events, errors, or in a certain environment. The form is:
#pragma directive [options]
where the directive specifies a predefined action and options specifies an optional set of parameters which may or may not be required depending on the directive.
The actions types are as follows (they are case insensitive):
AllowBreak
Allows the script to be broken with the Pause/Break key. When running in IDE mode, this is the default.
AppVersion version
Checks the ‘version’ string against the application. On failure, a message box is displayed and forces the script to exit. The version is in the form ‘0.0a’ or major version number, minor version number, and release letter. This is one of only a few directives or functions that will result in an uncommanded message box on error:
C
Specifies a pragma comment. Pragma comments survive code crunching to allow non-program data to be included in the source file. The ScriptGetComments SDK function can be used to read pragma comments from a file.
DebugTrace
When present, a debug trace file is created. If no additional parameter is provided, the debug trace file will be placed into the “LegatoLog” folder for the vendor in the application data folder (%appdata%). If a name is provided without qualified path, the path is added to the LegatoLog folder. If a full path is provided, the trace file is created at the specified location (see Section 2.7 Debugging Tools of the reference manual for more information). Adding “Robust” prior to the filename will cause the file to be flushed on each log entry. This is useful if the application isn’t crashing but significantly slows down the script engine.
Disable
When present, the script will exit as if an exit statement was processed. This is useful to quickly disable a script.
NoSDK
Directs the script engine to remove the predefined terms with the SDK. If for some reason a developer decided that the SDK defaults were not appropriate, using this pragma resets the predefined values. Note that since this action resets the defined terms in the preprocessor, no defines can be placed prior to this directive. This directive does not remove the predefined data for resources.
ScriptVersion version
Checks the ‘version’ string against the script engine. On failure, a message box is displayed and forces the script to exit. The version is in the form ‘0.0a’ or major version number, minor version number and release letter. This is one of only a few directives or functions that will result in an uncommanded message box on error:
The #include Statement
We’ve also touched on the #include directive before, particularly when we’ve dealt with external resource files. The #include directive allows a program to be assembled from a series of files. This makes organizing a program much simpler by allowing resources, defines, and even various sections of a program to be broken into separate files. This can also allow for subroutines to be placed in discrete files, which in turn permits them to be used in multiple separate script programs.
The format is as follows:
#include "filename"
#include <filename>
Where filename is the reference name. If in quotes, specifies a relative filename or an absolute filename. This cannot be a variable and must be in double quotes. If in angle brackets, the file is expected to be located in the application program path at “Script\Include\”. Note that this is not a preprocessed string literal and therefore does not process backslashes as escape characters. When a path is relative, it is relative to the file containing the directive.
Includes can be used to hold common data and definitions, code segments, or resources, If the extension is “.rc”, the content is considered a resource script. Defines within such files are processed for both resources and script code.
Finally, for the sake of conditional directives, a pseudo definition will appear only in included files called “_INCLUDE_FILE”. When defined this indicates the current script file has been included in another file. This can be used specify whether code should included on a conditional basis.
Conclusion
Understanding and using directives opens many opportunities to increase the utility of your programs. Probably the simplest is the #define directive, which is pretty much a necessity to create easy to read and maintain code, but the other directives allow you as the programmer a great deal of versatility behind the scenes, so to speak.
Scott Theis is the President of Novaworks and the principal developer of the Legato scripting language. He has extensive expertise with EDGAR, HTML, XBRL, and other programming languages. |
Additional Resources