You can make/write/define your own commands. A command that you made consists of invocations of other commands. To be precise, the implementation of your command (the Command Implementation) consists of invocations of other commands.
Being able to make your own commands is essential. Without this important capability, you would only be able to write rather small programs. Large programs would quickly become unmanageable. For example, if you make a command that consists of 20 invocations of other commands, you can invoke your command in multiple places instead of repeating those 20 invocations in each one of those places.
Remember that the entire source code consists of a list of invocations of commands (command invocations), with each command invocation having 0, 1, or more parameters. So everything is done using commands. You even use a command to define a new command.
Thus to define your new Command Implementation, use the command named "cmd", followed by the commands you want to be invoked when your command is invoked, and end with the command named "ecmd". In the following example, a new Command Implementation is defined with the name "Test", consisting of an invocation of the command "MCLI.Output".
cmd Test;
MCLI.Output "Hello";
ecmd;
The "ecmd" command can optionally have 1 parameter, the name of the new Command Implementation ending there (example follows). This must be exactly the same name as was provided in the corresponding "cmd" command (the compiler will report an error if the name at the beginning does not match the name at the end). This optional feature can help catch mistakes, and make the source code easier to read. It reminds the reader WHICH Command Implementation is being ended there, and that can be especially helpful when the "cmd" command is a far distance away from the "ecmd". It may also assist in nesting situations.
cmd Test;
MCLI.Output "Hello";
ecmd Test;
Command Implementations actually need to be grouped into modules. Modules help you organize your program. A Command Implementation cannot be defined outside of a module. In the following example, 2 Command Implementations named "Test" and "AnotherCmd" are defined inside the module "MyProgram", and the "AnotherCmd" command invokes the "Test" command.
module MyProgram;
cmd Test;
MCLI.Output "Hello";
ecmd;
cmd AnotherCmd;
MyProgram.Test;
ecmd;
emodule MyProgram;
Like "ecmd", the "emodule" command can optionally include the name of the module being ended there, for the same reasons.
In the above example, when "AnotherCmd" is invoking "Test", "AnotherCmd" is said to be the "invoker" and "Test" is said to be the "invokee" or invoked command. Likewise, when "Test" is invoking "MCLI.Output", "Test" is said to be the invoker, and "MCLI.Output" is said to be the invokee.
To make a Command Implementation really useful, you need to be able to pass information to it, and receive information from it. This is achieved using parameters. A parameter is defined to be 1 of 3 possible directions:
- Input -- Unidirectional incoming.
- Output -- Unidirectional outgoing.
- In/Out -- Bidirectional incoming and outgoing.
When a parameter is said to be an "input parameter", this means specifically and only input parameters, NOT including in/out parameters. Likewise, "output parameter" means specifically and only output parameters, NOT including in/out parameters. Thus an in/out parameter is considered to be a type distinct from input parameters and output parameters.
However, when a parameter is said to be "incoming" (as opposed to "input"), then incoming means either an input parameter or an in/out parameter. Likewise, outgoing means either an output parameter or an in/out parameter. Thus "incoming" and "outgoing" are used with a more general meaning than "input" and "output".
A command can have multiple input parameters, multiple output parameters, and multiple in/out parameters.
As explained previously, remember that everything in the source code is done using commands. Thus a command named "cmd" is used to define a new command, as explained above. And a command named "prm" is used to define a new parameter.
Following, our example "Test" command is extended with 2 input parameters named "inNumA" and "inNumB" defined using the command named "prm". The 2 parameters are defined but not actually used in this example.
cmd Test;
prm inNumA, uint;
prm inNumB, uint;
MCLI.Output "Hello";
ecmd;
As we said, the "prm" command defines a new parameter in your Command Implementation. Because the "prm" command is a command, it has parameters of its own. The first is the name of your new parameter (frex "inNumA"), the second is the type (frex "uint"), and the third (optional) is the initial/default value.
Parameter types are the same as variable and attribute types. For information about types, see the Variable/Attribute Types section of the documentation.
Now that you have defined the "Test" command to have 2 parameters, when you invoke it you must supply 2 values for those parameters. The values will be copied into the "Test" command. In the following example, the value "123" is being supplied for "inNumA", and the value "46" is being supplied for "inNumB" (supply the values in the same order as the parameters are defined).
cmd Test;
prm inNumA, uint;
prm inNumB, uint;
{...}
ecmd;
cmd AnotherCmd;
MyProgram.Test 123, 46;
ecmd;
The value of a variable can also be supplied for a parameter. In the following example, a variable named "x" is defined, then it is set to contain a value of "123", and then "x" is supplied for "inNumA", and "46" is supplied for "inNumB". The value of "x" is copied into "inNumA", thus both "x" and "inNumA" will contain "123".
cmd Test;
prm inNumA, uint;
prm inNumB, uint;
{...}
ecmd;
cmd AnotherCmd;
var x, uint;
set x, 123;
MyProgram.Test x, 46;
ecmd;
To make output and in/out parameters, use the "Out" or "InOut" operator in the type expression (as described in the Variable/Attribute Types section of the documentation). For example:
cmd Test;
prm inNum, uint; {INPUT}
prm outFoo, Out[uint]; {OUTPUT}
prm ioBlah, InOut[uint]; {IN/OUT}
{...}
ecmd;
Now to discuss what happens when you modify a parameter (change the value stored in a parameter). You can modify an input parameter. It has no effect on the invoker because input parameters are copies. Example:
cmd Test;
prm inNum, uint;
set inNum, 666;
ecmd;
cmd AnotherCmd;
var x, uint;
set x, 123;
MyProgram.Test x;
{x is unchanged by "Test", stays as "123".}
ecmd;
But what if you did want "x" to be modified by "Test", allowing you to receive some information from "Test"? This is easily done by changing the "inNum" parameter from input to output (or to in/out). Example:
cmd Test;
prm outNum, Out[uint];
set outNum, 666;
ecmd;
cmd AnotherCmd;
var x, uint;
set x, 123;
MyProgram.Test x;
{Now x contains "666".}
ecmd;
The "set outNum, 666;" command does not directly modify "x". Rather the value of "outNum" is copied into "x" when "Test" returns/finishes.
The parameters of the "var" command (that defines a local variable) are the same as the parameters of the "prm" command (that defines a parameter). In other words, the syntax of the "prm" command is the same as the "var" command. A parameter in a Command Implementation is the same as a local variable in a Command Implementation, but with the additional ability of copying values into and out of the variable (parameter) when the Command Implementation is invoked.
Incoming and outgoing parameters of references types are more difficult to understand than parameters of integer types. Reference and integer parameters actually operate in the SAME manner regarding incoming and outgoing, but nevertheless reference parameters can be confusing if you fail to have a clear distinction in your mind between a value and a reference to a value.
Remember that a reference refers/points to a value known as the target of the reference. If a reference parameter is defined to be incoming, this means that the reference to the value (not the value itself) is incoming. A reference is copied from the invoker to the invoked command. The value itself (the target) is NOT copied, only the reference is copied.
If a reference parameter is defined to be outgoing, this means that the reference to the value (not the value itself) is outgoing. A reference is copied/transferred from the invoked command to the invoker. The value itself (the target) is NOT copied.
If the invoked command sets one of its input reference parameters to some other reference, it has no effect on the invoker (the corresponding variable in the invoker will be unchanged). However if the invoked command modifies the target, then this modification is visible to the invoker. An invoked command cannot create a new value, set one of its input reference parameters to contain a reference to that new value, and then expect the invoker to have access to the new value.
If the invoked command sets one of its output reference parameters to some other reference, then when the invoked command finishes, the corresponding variable in the invoker will be set to that same other reference. An invoked command can create a new value and then pass/transfer the new value to the invoker via an output or in/out reference parameter.
Note that it is possible to use an input reference parameter for outgoing data. This is slightly confusing, but the design is correct. For example, if the parameter is defined to be an input text reference, then this means the REFERENCE is incoming, but it says nothing about whether the TARGET text is for incoming or outgoing purposes, and thus the invoked command can use the incoming text reference to generate and store some text into the target for the invoker.
After the invoked command finishes, the invoker can access the new text in the text variable, and thus text seemingly travelled in an outgoing direction using an incoming reference. Thus "input reference parameter" means an incoming reference, it does NOT necessarily mean incoming data. Be careful to avoid confusing the concept of an input reference parameter with the concept of an incoming data value, they are different.
If a parameter is named "inBlah", then by convention it should be defined as an input parameter.
If a parameter is named "outBlah", then by convention it should be defined as an output parameter.
If a parameter is named "ioBlah", then by convention it should be defined as an in/out parameter.
To clarify, "inBlah" means the compiler was instructed that the parameter is an input parameter. It means specifically input, not generally "incoming" or "data source" (it might also be those things but that is not what the "in" prefix means).
If a parameter is named "srcBlah", then by convention it should be defined as an input reference parameter, and the invoked command should only read the target (of the reference), not modify it. The parameter is a source of data.
If a parameter is named "dstBlah", then by convention it should be defined as an input reference parameter, and the invoked command should only store data to the target, not read it. The parameter is a destination for data.
If a parameter is named "sdBlah", then by convention it should be defined as an input reference parameter, and the invoked command can both read and modify the target (it can, but is not required or guaranteed to). The parameter is both a source of data and a destination for data.
| inBlah | Input parameter. Incoming value or reference. |
| outBlah | Output parameter. Outgoing value or reference. |
| ioBlah | In/out parameter. Incoming and then outgoing value or reference. |
| srcBlah | Source of data. Input reference parameter. Target read but not modified. |
| dstBlah | Destination for data. Input reference parameter. Data stored to target. Target not read. |
| sdBlah | Source and destination. Input reference parameter. Target can be both read and modified. |
To omit a parameter value when invoking a command, either write "Omit" or leave it blank. In the following 2 equivalent examples, the second parameter value is omitted:
CookBurrito cookingTime, Omit, temperature; CookBurrito cookingTime, , temperature;
If an incoming (input or in/out) parameter is omitted, then in the invoked command the parameter will not be changed from its initial value (its value will initially be whatever its initial value was defined to be). Initial values can be specified for parameters like how they can be for variables. If no initial value is specified, the initial value defaults to zero or null (depending on the type of the parameter).
If an outgoing (output or in/out) parameter is omitted, then the invoked command can still set it to some value, but the value is not transferred to the invoker. This is very convenient when you are uninterested in one or more of the outputs of a command you invoke -- there is no need to define a dummy unused variable to receive the unwanted output value.
In the invoked command, there is no way to determine whether a parameter was omitted (as opposed to being supplied with a variable containing a value identical to the initial value of the parameter). This ability could potentially be supported in a future version, if it is sufficiently useful.
When invoking a Command Implementation, you can optionally match supplied values to parameters by specifying the names of parameters, instead of relying on the order of parameters. For example:
cmd Test;
prm inNumA, uint;
prm inNumB, uint;
{...}
ecmd;
cmd AnotherCmd;
MyProgram.Test inNumB: 46, inNumA: 123;
ecmd;
Values for parameters can be supplied in any order if the parameter names are specified. Whereas if the parameter names are not specified, the values must be supplied in the same order as the parameters are defined in the invoked Command Implementation.
It is possible to specify the names for only some of the parameters, and rely on the order for the rest, but in this mixed case ALL the parameter values must be in the same order as the parameters are defined (including the parameter values with the name specified). Thus you can specify only some of the names for clarification purposes but not for re-ordering.
The command named "Command" is used to begin a Command Implementation. The first parameter is the name of the new Command Implementation. The second parameter is optional. If supplied, it specifies some options for the Command Implementation. It contains one or more of the following words separated by spaces, in any order.
| Name | Description |
|---|---|
| Inline | Instructs the compiler to preferably eliminate invocations of this Command Implementation by copying and integrating the code into the invokee. This is an optimization sometimes useful for small Command Implementations, to eliminate the overhead/cost of an invocation at the expense of potentially larger program size. |
| Unchecked | Instructs the compiler to not generate code that checks for arithmetic overflow within this Command Implementation. Makes the code faster and smaller but potentially unsafe. |
| OverloadName | Instructs the compiler to allow definition of multiple Command Implementations with exactly the same name as this one but different parameters (distinguished from each other by the parameters). Must be specified on each and every Command Implementation with the same name, not only the first one. |
| Export | For use when making a library (either statically or dynamically linked). Instructs the compiler to make this Command Implementation visible to users of the library (as opposed to an internal/private command). |
| Import | Instructs the compiler that the code for this Command Implementation should be found elsewhere, such as in an imported library. Use with "Stub". |
| Stub | Instructs the compiler that this Command Implementation contains no code. The compiler will disallow the invoking of commands within this Command Implementation, other than parameter definitions. Means that this Command Implementation exists only for the purpose of providing the definition of an external Command Implementation, to allow it to be invoked. Use with "Import". |
| Predefined | Used to give the compiler knowledge of the name and parameters of a command/function that is already defined elsewhere in another environment such as a C compiler, for the purpose of allowing the command/function to be invoked. Also enables the "Import" and "Stub" options. Typically used for low-level OS-specific programming. Suppresses generation of a C function prototype. |
For example:
Command Test, Unchecked Export;
{...}
EndCommand;
The "emodule" command is used to end a module section, the "erepeat" command is used to end a repeat section, the "eswitch" command is used to end a switch section, the "eif" command is used to end an "if" section, the "ecmd" command is used to end a Command Implementation, etc.
The question may be asked, why not just use a single generic "end" command?
Consider the C/C++ programming language. In that language, the close-curly-bracket character "}" is used as a generic end. It often results in obnoxious source code like this:
void Test()
{
while (true) {
if (x) {
switch (y) {
...many lines of code here...
}
}
}
}
When looking at the above example, imagine that there are 50 or 100 lines of source code in the middle, and thus the beginnings are a far distance away from the endings. When looking at those 4 endings far away from their corresponding beginnings, it is awkward/inconvenient/slow/tedious to determine which ending corresponds to which beginning (or vice-versa).
The equivalent KL source code is:
cmd Test;
repeat;
if x;
switch y;
...many lines of code here...
eswitch;
eif;
erepeat;
ecmd;
As you can see, the KL example is easier and faster to read and understand than the C/C++ example. Also, using different ending commands assists the compiler in giving better (more accurate and understandable) error messages, and in catching mistakes.