Handling INFO
As seen on the previous page, the importable JS functions are given an INFO
object as an argument. This object contains some important information you can use to alter the course of compilation for various effects.
First, we'll briefly go through how this object is laid out and what each part actually signifies. Afterwards, we'll talk about how to take advantage of it to make your functions more powerful.
What the INFO
object contains
Here's the object layed out, with the descriptions of each part coming later:
{ DEF, LIST, INDEX, IMPORTS, SETTINGS, MODES, SWITCHES, VECTORS, CODE, SHARED }
We will now go through each one, one by one.
DEF - Default delays
This is an array with two numbers, with it representing the default delay currently set when the importable function was called. It is the exact same array being used in the compiler, so you can mutate it to change the delays.
The first element is the default for the holding delay, the second is the default for the waiting delay. Again, "holding" refers to how long the key(s) are held for, and "waiting" refers to how long before moving on to the next keys after the current ones are released.
LIST - Instruction array
This is an array that includes the instructions in order. These can be either calls to imported functions, conditionals, or in the form of objects for key expressions
. There are also a couple more miscellaneous onees.
Function calls
The function calls can either be an array, or just a string if it's a call to a function without any arguments or a block given. In the case of an array, the name is the first element, followed by the arguments (another array), and finally the block (if it exists).
The arguments in the array can sometimes be in the form of "getter" functions. For example, if you pass in a number in the form of the n-th element of a vector, you'll have a function that returns the n-th element of that vector instead of the number.
This might seem weird but it is to account for the order in which the compiler runs these instructions. The n-th element at the time the instruction was made might not be the same as when the instruction gets actually run.
Regardless, you can also place a number if you're injecting an instruction of the function call type.
Key expressions
On the other hand, here's how the key expression
object is represented:
{ hold, wait, keysPressed, keysHeld }
This is quite self-explanatory, so we'll skip over giving an explanation for each one individually. However, if you recall the difference between the two methods of holding keys, that can be represented by a "|" after the key in the keysHeld array.
If you add a key which is already held to the keysHeld array, it will release it.
Conditionals
Conditionals are represented by an array that has three subarrays. The first subarray includes the conditional types in order (eg. @if
, @elseif
, @else
), the second includes the conditions for each, and the third includes the blocks for each.
We will give an example of a simple conditional in Simkey
and how that would be represented as an instruction:
<MODES>
$HIGH
$MEDIUM
$LOW
<MACRO>
@if $HIGH {
a
}
@elseif $MEDIUM {
b
}
@elseif $LOW {
c
}
@else {
d
}
Here's how this would be represented as a JavaScript object, which we will have to tie the part above about key expressions in for:
const representation = [
["@if", "@elseif", "@elseif", "@else"],
[["$HIGH"], ["$MEDIUM"], ["$LOW"]],
[
[ { hold: 'DEF', wait: 'DEF', keysPressed: ['a'], keysHeld: [] } ],
[ { hold: 'DEF', wait: 'DEF', keysPressed: ['b'], keysHeld: [] } ],
[ { hold: 'DEF', wait: 'DEF', keysPressed: ['c'], keysHeld: [] } ],
[ { hold: 'DEF', wait: 'DEF', keysPressed: ['d'], keysHeld: [] } ]
]
]
The way the conditions get represented is an array which has each mode/switch name (with "!" before the name if negated), and the logical operators in between (which are |, and & respectively). There are nested arrays for each part with brackets.
@end
instruction
@end
is a sort of "builtin function" (of course, that is a little imprecise) that stops the script at that point. The instruction is just the string "@end"
.
SET instruction
Unlike @end
this is not a "builtin" function that can be used within a script. Instead, it simply offers a way to set something down the line if you need to, by adding it as an instruction at that point. You could use the builtin @set
instead, by just injecting a function call to it.
This instruction is an array with the first the string "SET"
, then the name of the variable, the index (which can either be a number, the string "ALL"
to change an entire vector, or "BOOL"
for switches), and a function which returns the value you want to change the variable to.
INDEX - Current index
This is a number which represents the index that the compiler is currently on in the instruction array (LIST). There is no way to change the index from the compiler's side.
IMPORTS - Imported functions
This is an object which has all the imported functions alongside what they take. The keys with the names of the functions without the initial @
have the actual JavaScript function as a value, while the ones with @
have what the function takes.
SETTINGS - Mode/switch values
This is an object which contains the values of each mode/switch, which are of course booleans. The keys are the names of these variables. This is the same object used in the compiler, so mutating it will affect the compilation process.
MODES/SWITCHES/VECTORS - Variables
The MODES and SWITCHES under the INFO
object are both arrays, which contain the names of each mode and switch. For the values, refer to SETTINGS above. These are the same arrays used in the compiler, so mutating it will affect the compilation process.
The VECTORS one is an object which has the names as keys and values as arrays, representing all the stored vector variables. This is the same object used in the compiler, so mutating it will affect the compilation process.
CODE - Current code
This is a string of the KeyC code from whatever has been compiled so far at that point.
Of course, you cannot mutate the string, so to change the code the compiler has, return a string (as said in the beginning of the last page, whatever string is returned from the imported function is seen as code to be added) with the first line being "START" to indicate that you are not appending.
Otherwise, you can write return code without writing "START" to have it append to the end of the KeyC.
SHARED - Shared environment
This is an object which is empty and which the imported functions get to decide the structure of. The purpose of it is to allow some sort of communication between functions or to store some information on each run of some function.
What to do with the INFO
object
These are some examples of using the INFO
object in various ways. They almost all rely on the fact that when mutating the objects within the INFO
object, you are mutating the same exact ones used by the compiler, so it will have a direct effect.
Injecting instructions directly
You can push an instruction straight into the LIST
array under INFO
to inject instructions. So long as the index the instruction is at comes after the index the compiler's at (refer to INDEX), and there is no @end
instruction before it, it will eventually run it.
Changing switch/vector values
This can be done with use of the SETTINGS
object under INFO
, by changing the value under a key (the key being the switch/vector's name). Be careful not to change it into an invalid value for the type.
Setting variables using instruction
Sometimes you may want to set variables at some later point within the instructions. For example, the @for
builtin function (which is built as an importable function) repeatedly injects an instruction to set the index variable, then injects the block that it has.
There are two ways you can set, one is with the SET
instruction which is not a builtin function, and the other is simply by adding an instruction in the form of a function call to the @set
builtin function. Look above for information on how those instructions are structured.
Changing default delay
You can directly change the default delays for both hold and wait by mutating the array given under DEF
in the INFO
object. The first index is the hold delay, the second is the wait delay.
Using shared space
You can use the SHARED
object under INFO
to save things when a function is called so that you can share them among repeated function calls or other functions that you build.
For example, if you were making a framework you could add a new construct to Simkey and save information about it that you will use across these functions, such as implementing a different kind of variable and keeping track of stored variables.
Want function examples?
You can look at how the builtin functions have been implemented under the simkey-imports/std
directory. It can be insightful, and you can certainly build upon them to make more powerful functions.