Skip to main content

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.