Introduction
What is KASM?
The Kerbal Assembler, KASM, is an assembler created for the popular Kerbal Space Program mod kOS. The syntax is designed to be easy to understand and allow the user to write any code they could possibly write in kOS's native language KerbalScript, but with more flexibility. It currently supports all kOS instructions and the toolchain to create executable Kerbal Machine Code files or KSM files. It also has strong preprocessor support.
License
KASM is under the GPL 3.0 License
Installation
The Kerbal Assembler can either be installed via cargo through crates.io, as a standalone binary, or as a Windows installed executable.
Using Cargo
To install using cargo from crates.io, run the following command:
cargo install kasm
kasm should now be added to your shell's PATH and can be run from any terminal.
As a Standalone Binary
In order the install the binary file, you must download it from the GitHub releases.
- Download and extract the latest release's zip file from the Releases page
- Place the executable in the desired location
- kasm is now executable from the terminal. Powershell on Windows or your terminal on MacOS or Linux.
Windows Installer
A Windows installer program is included under the releases page for every major release of KASM
- Download and extract the latest installer from the Releases page
- Run the installer, and follow the prompts
- kasm should now be added to your PATH and available from the CMD and Powershell windows
Chapter 1: Running KASM
The Kerbal Assembler can be invoked after installation simply as kasm
Help can be accessed from the program itself by running:
kasm --help
The basic format for invoking kasm
is:
kasm [FLAGS] [OPTIONS] <INPUT> --output <OUTPUT>
Main Operation
To assemble a file of source code issue a command of the form:
kasm <filename> <options>
For example, if your code was in a file called myfile.kasm
:
kasm myfile.kasm -o myfile.ko
This will assemble myfile.kasm
into a KerbalObject file called myfile.ko
Creating an Executable
Because KASM is just an assembler, and it produces a non-executable file, readers of this may be wondering how to create a file they can run in kOS.
In order to that, another tool called KLinker is required. When installing KASM on Windows a separate "full" installer contains KLinker as well as a useful tool called KDump.
If you have KLinker installed then in order to create a file that can be run in kOS you can run the following two commands:
kasm myfile.kasm -o myfile.ko
kld myfile.ko -o program.ksm
The produced program.ksm
file can be run in kOS just like any other file.
Options
The -o
option: Specifying the output file name
KASM requires you to specify the output file path
If the output file already exists, KASM will overwrite it. KASM will by default look in the current working directory where kasm is run. Although a different directory can be specified as either an absolute or relative path:
kasm main_program.kasm -o ~/<KSP Directory>/game.ko
This will produce an output file named game.ko
in your KSP directory that you fill in.
The -i
option: Specifying the include directory path
By default when the KASM preprocessor is executing include directives it looks in the same directory in which the source file is.
If you are including many different files, it may be in your interests to organize those files into a directory called include
in your project folder.
KASM allows you to still run the kasm command where you did before, but specify the include directory as include
.
This can be done using the -i option:
MacOS/Linux:
kasm fancycode.kasm -i ./include/ ...
Windows:
kasm fancycode.kasm -i .\macros\ ...
The -f
option: Setting the file symbol name
Normally KASM outputs to the KerbalObject file format, which stores the source file's name to be referenced later in the case of any errors. This defaults to the current input file name, but in the case of implementing an intermediary between the user and KASM such as a compiler, a different source file name should be displayed.
In a hypothetical scenario let's say we have a compiler for a language called Yes Language where the files have .yl
extensions: program.yl
This compiler produces KASM source code which is assembled using kasm.
This can be specified so that later error messages use the .yl
extension using the -f option:
kasm program.kasm -f program.yl ...
Now errors in the linker will be reported as coming from program.yl
so that it is less confusing for the end user.
The -c
option: The executable comment
When the output KerbalObject file is linked and an executable file is created, a comment is left in the generated KSM file that explains how the file was created. This can be read using a utility such as KDump and then the recipient of the file can see what program compiled the code.
An example comment would currently be: Compiled by KASM 0.11.0
Any message can be displayed though with the -c
option:
kasm program.kasm -c "KASM is pretty neat, yo" ...
- Note: The comment will only show up in the final .ksm file created by KLinker if the file has the program entry point (either
_start:
or_init:
)
Flags
KASM can also be passed flags that have no value following them that tell KASM what to do.
-w
flag
In some cases KASM will notice something about your code that does not make it incorrect, but may not be what you want to do. These are printed out as warnings, and in some cases you may not want to see them.
Then the -w flag can be used to supress warnings and only display errors:
kasm -w program.kasm ...
-a
flag
KASM has a fairly capable set of preprocessor directives, but running the preprocessor may make kasm run slower if you are giving it a large amount of code. If you are not using preprocessor directives, then you may choose to tell KASM to not run the preprocessor at all.
This can be done by passing kasm the -a flag:
kasm -a program.kasm ...
-p
flag
In contrast to the -a flag, the -p flag can be used to tell KASM that you want it to only run the preprocessor. This can be useful in some cases for debugging or simply wanting all of the code to be the final code that would be in the KSM file.
kasm -p program.kasm -o output.kasm ...
Output Format
KASM outputs in a file format called KO, or Kerbal Object file format.
This format is used by the linker to link multiple parts of a program together that were in seperate source files.
These files can be viewed using a program called KDump, as well as finished KSM files.
KO files must be passed to the linker in order to become executable by kOS.
Chapter 2: KASM Language
This chapter will go over the specifics of KASM assembly language without diving into preprocessor directives.
For Beginners
Hopefully by the time someone is reading this, there will be better options for non-KerbalScript code that runs inside of kOS. If you are trying to write such a program, or just want to try to learn or practice with assembly using Kerbal Space Program and kOS, then hopefully this page can give you a brief introduction to assembly language.
If you have previous experience with assembly language, you can probably skip this section and go straight to the tutorial.
For the purposes of this explanation, KerbalScript code will be used to try to illustrate some ideas.
Code
In most programming languages, when source code is written, it has to undergo a process called compilation. This turns the code that was written by the user into instructions that the computer can understand. For a lot of programming languages, there is an intermediary step between the source code, the code written by the user, and the machine code, the binary that computers can read. This intermediary step is called assembly code.
Take this KerbalScript code for example, that just adds two numbers and stores the result:
SET X TO 2 + 4.
kOS, or most computers for that matter, have no idea what to do with this, because it is just text. Internally, kOS performs compilation to turn this code into a form that it can understand.
In this case, the machine code is something like this: (special quirks of compiled kOS machine code may be discussed later)
0x4e (2)
0x4e (4)
0x3c
0x34 (X)
This may be difficult to understand at first, but we will break it down:
Each line represents what is called an "instruction"
Computers, like kOS, can only do one thing at a time, so it cannot add 2 and 4 together and store them in a variable called X at the same time.
The first instruction has the opcode of 0x4e. 0x4e is a hexadecimal number. Each instruction is given a unique number that the computer knows,
so it knows what operation to perform. Hexadecimal is just a way of writing this number, but in our normal base-10 number system this number is 78
.
The number itself though, is not important.
The instruction 0x4e
is called the "push" instruction. Inside of kOS there is a bunch of data stacked on top of each other, aptly named the "stack".
The stack is used to store data that is used for something later, but not for storing things long-term like inside of variables.
On the first line, 0x4e (2)
means that we are "pushing" the value of 2 onto the stack. So the number 2 is stored somewhere. This corresponds
to the 2 in our KerbalScript code.
On the second line 0x4e (4)
, we do the same thing, but we push the value of 4. Now the stack contains the numbers 2 and 4.
The third line, opcode 0x3c
, means "add" and it tells the computer to add the last two values on the stack. In this case, that is 2 and 4.
The result is then stored back on the stack, and we know that the answer is 6.
The final line, 0x34 (X)
is opcode 0x34
which tells kOS to store the value on the top of the stack into the variable that is given with the
instruction, in this case that is 'X'.
At the end of this code, the numbers 2 and 4 were added, and the result was stored inside of a variable named X.
This is actually what is happening when the KerbalScript code above is run, and compiled.
Rationale
KASM is a program which allows the user to directly write these instructions instead of having to write KerbalScript. This can give the user more control over what their program does, allow them to experiment with learning new things, or to make a compiler which generates KASM assembly code which can be run inside of kOS, allowing them to create a new programming language for kOS!
A snippet of code in KASM that would perform the same function as the KerbalScript shown above is:
push 2
push 4
add
sto "$x"
As you can see it can be written as a text file, and yet performs the same function!
How to actually write this code, or the syntax of KASM, is explained in the tutorial.
Basic Syntax
Like most assemblers, each KASM source line contains some combination of four fields:
label: instruction operands ; comment
Most of these fields are optional, with restriction of the operands field based on which instruction it is.
KASM uses backslash (\) as a continuation character; if a line ends with a backslash, the next line is considered to be a part of the backslashed line.
Example:
push \
2
Would be parsed as the same as:
push 2
There are no restrictions places on whitespace, such as spaces or tabs, however except for when using the \ character, new lines are to be minded.
Basic Instructions
A valid source line may simply be:
add
It contains only the instruction, no label, operands, or comment
A comment is dictated by the character ;
and can be added at the end of any source line and will effectively be ignored.
add ; This adds the previous two numbers
Instructions that require operands, must be provided operands otherwise it is an error. An example for this could be the push
instruction:
push 2
This instruction would push a byte with a value of 2 onto the stack.
Multiple operands are seperated using a comma:
bscp 1, 0
Labels
Sometimes in a program, one might want to stop instructions from being executed in order and re-run code, or make a decision based on input, and run certain code on certain conditions.
Examples of this might be an if-statement or a while loop.
This example demonstrates simply wanting to skip over some code:
nop ; Does nothing, just a placeholder
push 2
jmp 3 ; This instruction jumps down 3 instructions, and skips all of them
push 2
add
nop ; This is where we would jump to
This would skip the two push 2
and add
instructions. This can quickly get messy though, as a typo in the number of instructions to jump
over can cause the entire program to run incorrectly. Thus, labels were created.
Labels are defined before an instruction, and they can either be in the line before it, or in front of it:
label:
nop
also_label: nop
Labels can be used in the place of specific places to jump to in the code, that previous program rewritten but using labels is as follows:
nop
push 2
jmp the_end
push 2
add
the_end:
nop
Now KASM will keep track of the exact value to jump for you, and sections of code can be named, this is illustrated best by a loop:
push 5 ; This will loop 5 times
loop:
dup ; Duplicates 5 so we can use it for an operation
push 0 ; We want to compare it against 0
cgt ; This will compare the number and 0 and see if it is greater than 0
bfa end ; If the number is equal to 0, the loop is over, so we jump to the end
push 1 ; If not, then we push a 1
sub ; And subtract it from the current number
jmp loop ; Then start the loop again
end:
jmp 0 ; Infinite loop
Label names can consist of any string of letters and numbers, including underscores. Although they cannot start with a number.
Inner Labels
All of this example code has existed in isolation, however in larger projects, one would not want to use the label "loop" because duplicate labels are not allowed. The user could name every single loop something different, but this can get in the way. Inner labels were created to solve this problem.
Inner labels are tied to a parent label, so they can be reused later in the program if the parent label is different.
Inner labels are differentiated from normal labels because they begin with a .
An example of this would be:
; This code adds 2 to the variable 'x' five times
adder: ; This is the parent label
push 5
.loop: ; Inner labels start with a '.'
dup ; Looping logic
push 0
cgt
bfa .end
push 1
sub
push "$x" ; Gets the current value inside of x
push 1
add ; Adds 1
sto "$x" ; Stores the result back into x
jmp .loop
.end:
nop
; Somewhere else in the code
subtracter:
.loop: ; This is not a duplicate!
...
Data Types
In kOS, unlike in many other computing platforms, each value has an associated type.
These types are as follows:
Argument Type Value | Type |
---|---|
0 | NULL |
1 | Bool |
2 | Byte |
3 | Int16 |
4 | Int32 |
5 | Float |
6 | Double |
7 | String |
8 | ARG MARKER |
9 | ScalarInt |
10 | ScalarDouble |
11 | BoolValue |
12 | StringValue |
- NULL is a value that is equivalent to "nothing"
- Bool is a true or false value
- Byte is an integer that can store numbers from -128 to 127
- Int16 is an integer that can store numbers from -32,768 to 32,767
- Int32 is an integer that can store numbers from -2,147,483,648 to 2,147,483,647
- Float is a number that can have a fractional component such as 2.33
- Double is a float, but with double the precision of the fractional component, see this
- String is a "string" of characters, such as "Hello, kOS!"
- ARG MARKER is a special value used inside of kOS that marks the end of function "arguments"
- ScalarInt is an integer that can store the same numbers as the Int32, but has associated functions inside of kOS
- ScalarDouble is a double, but with associated functions inside of kOS
- BoolValue is a boolean value, true or false, but with associated functions
- StringValue same as a string, but with associated functions
Inside of KASM
Data types are for the most part handled automatically by KASM
KASM chooses the smallest size data type in order to make the assembled executable file as small as possible.
For example, in the code:
push 2
That 2 would be stored using the Byte type, meanwhile in:
push 200
00 would be stored as an Int16
because it is the smallest type that can hold the value of 200
.
When trying to pass any floating-point number such as 2.33
, KASM will automatically store it as a Double
.
If you wanted to push a StringValue
instead of a String
in KASM, you simply use the pushv pseudoinstruction:
push "Hello" ; This is a regular String
pushv "kOS!" ; This will push a StringValue
If pushv
is given an integer, it will be pushed as a ScalarInt
.
Pushing Other Types
Simply writing an integer or another value to be the operand to an instruction is simple enough, however there are still two types that are not accounted for:
NULL
ARG MARKER
These are shown in KASM code as follows:
push # ; This is a NULL
push @ ; This is an ARG MARKER
Declaring Symbols
In KASM there are pieces of data that can be referenced outside of the current file called symbols. They are explained more in the advanced tutorial.
However it is important to note that when declaring symbols in KASM, the type of the symbol must be provided.
An example of how to declare each type of symbol is shown below:
.section .data
a # ; Null
b .b true ; Bool
c .i8 120 ; Byte
d .i16 300 ; Int16
e .i32 900 ; Int32
f .f64 2.33 ; Double
g .s "Hello" ; String
h @ ; Arg Marker
i .i32v 900 ; ScalarInt
j .f64v 2.33 ; ScalarDouble
k .bf false ; BoolValue
l .sv "kOS" ; StringValue
Expressions
Expressions can be used in KASM in place of constants, when it is more convenient to do so.
In KASM, expressions either produce an integer, a double, or a boolean value. An integer is not allowed to be used in place of a boolean, nor a double. Nor a boolean in place of either.
Supported Operations
Unary
- - Negate, flips the sign of a number
- ~ Flips all of the bits in a given number/value
- ! Not, flips a boolean value. If it was true, now it is false, and vice-versa
Mathematical
- + Addition
- - Subtraction
- * Multiplication
- / Division
- % Modulus (remainder operator)
Comparison
- == Equals
- != Does not equal
- < Less than
- <= Less than or equal
- > Greater than
- >= Greater than or equal
Logical
- && - Logical And
- || - Logical Or
Values
In KASM, values can be provided in a few forms.
For integers, hex, decimal, and binary literals are all supported:
push 24
push 0x18
push 0b0001_1000 ; Could also be written as 0b00011000
In Practice
Expressions can be used for any instruction operand that supports the type that is produced when the expression is evaluated
push 2 + 2
pushv (5.0 / 2.0) > 2.0
Becomes:
push 4
pushv true
Expressions become more useful when introducing macros in future sections.
Full Instruction List
This is simply a list of all instructions that are in KASM, grouped by common purpose.
For more detailed instruction descriptions and information, see here.
Stack Operations
push (any) - Pushes a value to the stack
pushv (any) - Pushes a value to the stack, but as kOS "scalar" or "value" versions
(this instruction does not exist in kOS, it is just for the purposes of user control)
pop - Removes the value from the top of the stack and discards it
dup - Copies the value from the top of the stack and pushes that copy, so there are two of the original
swap - Swaps the value on the top of the stack and the one below it
eval - Returns the value referenced by the topmost variable on the stack
argb - Asserts that the topmost thing on the stack is an argument marker (throws a kOS error if it isn't!)
targ - Tests if the topmost thing on the stack is an argument marker and pushes a true on top if it is, or false if it is not.
Mathematical Operations
These all push the result back to the stack
All of these behave in this way:
(this side is the value just below the top of the stack) - (this is the top of the stack)
add - Adds the two arguments on top of the stack
sub - Subtracts the two arguments on top of the stack
mul - Multiplies the two arguments on top of the stack
div - Divides the two arguments on top of the stack
pow - Raises the value just under the top, to the power of the top
neg - Pushes back the negative of the number on the top of the stack
Logical Operations
All of these behave in this way:
(this side is the value just below the top of the stack) > (this is the top of the stack)
cgt - Checks if one number is greater than the other
clt - Less than
cge - Greater than or equal
cle - Less than or equal
ceq - Equal to
cne - Not equal to
not - Pushes back the opposite of the boolean value on top of the stack (true -> false, false -> true)
and - Pushes true, if both values on top of the stack are true
or - Pushes true, if at least one value on top of the stack is true
Flow Control
eof - Tells kOS that this is the end of the file, so it should stop reading the program
eop - Tells kOS that this is the end of the program
nop - No-Op, does nothing
jmp (string | int | label) - Unconditionally jumps to the location specified in the operand, it can be a string or an integer (for relative jumps)
call (string | label), (string) - Calls a subroutine, leaving the result on the stack, both operands are strings
ret (int) - Returns from a subroutine and pops off the number of scopes as provided in the operand
btr (string | int | label) - If the value on the top of the stack is true, then it branches to the given location
bfa (string | int | label) - Same as btr but if the value is false
wait - Pops a duration in seconds from the stack and waits for that amount of game time
Variable Manipulation
sto (string) - Stores the value on the top of the stack in the variable specified by the operand
uns - Removes the variable named by the value on the top of the stack
stol (string) - Stores the value on the top of the stack in the variable specified by the operand, but if the variable does not exist in the current scope it won't attempt to look for it in a higher scope.
stog (string) - Stores the value on the top of the stack in the variable specified by the operand, but always stores it in a global variable.
stoe (string) - Stores the value on the top of the stack in the variable specified by the operand, but if the variable does not exist yet, it will throw an error instead of creating a new one.
exst - Tests if the identifier on the top of the stack is a variable, and is in scope. It will push true if it is, and false if it is not.
Member Values
gmb (string) - Consumes the topmost value of the stack, getting the suffix of it specified by operand and putting that value back on the stack. Ex: SHIP:ALTITUDE
smb (string) - Consumes a value and a destination object from the stack, setting the objects suffix specified by the operand to the value.
gidx - Consumes an index and an target object from the stack, getting the indexed value from the object and pushing the result back on the stack.
sidx - Consumes a value, an index, and an object from the stack, setting the specified index on the object to the given value.
gmet - Similar to gmb
except instead of getting the member as a value, it is called as if it was a function.
Ex: SHIP:NAME()
Triggers
addt (bool), (int) - Pops a function pointer from the stack and adds a trigger that will be called each cycle. The second operand contains the Interrupt Priority level of the trigger. For one trigger to interrupt another, it needs a higher priority, else it waits until the first trigger is completed before it will fire.
rmvt - Pops a function pointer from the stack and removes any triggers that call that function pointer.
Scopes
bscp (int), (int) - Pushes a new variable namespace scope (for example, when a "{" is encountered in a block-scoping language), the first operand being the new scope id, and the second one being the parent scope id.
escp (int) - Pops a variable namespace scope (for example, when a "}" is encountered in a block-scoping language), the operand being the number of scope depths to pop.
Delegates
phdl (int), (bool) - Pushes a delegate object onto the stack, optionally capturing a closure.
prl (string) - See KOS Code docs for more information.
pdrl (string), (bool) - This serves the same purpose as prl, except it's for use with UserDelegates instead of raw integer IP calls.
Misc.
lbrt (string) - It exists purely to store, as an operand, the label of the next opcode to follow it. Mostly used for internal kOS things.
This shouldn't really be used when writing KASM unless you know exactly what you are doing, as it can have unintended and hard to debug consequences.
tcan - Tests whether or not the current subroutine context on the stack that is being executed right now is one that has been flagged as cancelled by someone having called SubroutineContext.Cancel(). This pushes a True or a False on the stack to provide the answer. This should be the first thing done by triggers that wish to be cancel-able by other triggers.
Chapter 3: KASM Preprocessor
KASM contains a powerful macro processor, which supports conditional assembly, multi-level file inclusion, and two forms of macro (single-line and multi-line).
Preprocessor directives all start with a dot (.)
Single-Line Macros
Declaration
Single-line macros are defined using the .define
directive:
.define NUM 25
This creates a macro called NUM
that has the value of 25.
Single-line macros are more powerful than this though, and support expressions:
.define OTHERNUM NUM + 5
Now OTHERNUM
would be defined as NUM
plus 5.
So if NUM were redefined later to be 10:
.define NUM 10
It would change the value of OTHERNUM
because these values are found at invocation time, or when the macro is actually run.
A long example is:
.define NUM 25
.define OTHERNUM NUM + 5
push OTHERNUM
.define NUM 10
push OTHERNUM
becomes:
push 30
push 15
Not only is this useful for helpful constants, but they can be used with more than just constants:
.define PUSH2 push 2
PUSH2
becomes:
push 2
Arguments
Single-line macros can take arguments as well:
.define a(x) 1 + b(x)
.define b(x) 2 * x
push a(2)
becomes:
push 5
Definitions can have multiple arguments like so:
.define f(x, y) x + y * y
Undefinition and Overloading
It is also possible to undefine a defintion using the .undef
directive:
.undef f 2
Note that the number of arguuments that the macro takes needs to follow it. Not specifying a value defaults the number of arguments to 0.
Single-line macros can be overloaded, meaning that two macros can have the same name as long as they have different numbers of arguments:
.define f(x) x + 2
.define f(x, y) x + y
This is valid and will not cause a conflict. Which macro gets expanded depends on the number of arguments. Hence why the .undef
directive
requires the number of arguments to be specified: so that it knows which one to undefine.
Multi-Line Macros
Declaration
Multi-line macros are defined using the .macro
and .endmacro
directives:
.macro MULTI
push 2
push 2
add
.endmacro
The above macro can be invoked as such:
MULTI
Arguments
Multi-line macros can also have arguments just like definitions. The number of arguments must be specified after the macro name.
These arguments are referenced using their positions, and using the &
symbol:
.macro ADD 2
push &1
push &2
add
.endmacro
Invoked as:
ADD(2, 4)
expands to:
push 2
push 4
add
Default Arguments
Multi-Line macros can have default arguments as well, and this is specified by using a range.
The first number is the minimum number of required arguments, and the second number is the maximum number of required arguments.
A valid range would be 0-1
This is placed after the macro's identifier. But if default arguments are used, the default values must be given, separated by commas, after the range. The number of default values given must match the maximum number of require arguments, minus those that are required.
.macro RET 0-1 1
RET &1
.endmacro
This can be invoked as:
RET
; Expands to:
ret 1
or as:
RET(2)
; Expands to:
ret 2
Or with a macro such as defined below:
.macro DO_SOMETHING 0-2 24, 30
push &1
push &2
sub
.endmacro
If this is invoked with only one argument, it defaults to being the first argument:
DO_SOMETHING(2)
; Expands to:
push 2
push 30
sub
Undefinition
Just like single-line macros, multi-macros can be undefined using .unmacro
. Likewise the number of parameters must be given:
.macro NOTHING
nop
.endmacro
.unmacro NOTHING ; This works
.unmacro DO_SOMETHING 2 ; This works because DO_SOMETHING can take 2 arguments
As seen in the above example, argument ranges count as that macro taking any number of arguments in the range when being undefined as well.
Overloading
Although having a number of different arguments per macro is useful, multi-line macros can also be overloaded:
.macro PUSH 1
push &1
.endmacro
.macro PUSH 2
push &1
push &2
.endmacro
These macros do not conflict because they both have different numbers of arguments. However if the first definition had an argument range of 1-2
they would conflict.
Repeats
A repeat is similar to a macro, but it cannot take arguments. Instead after a .rep
directive, the preprocessor expects
a number of times for the tokens inside to be repeated. A repeat block is ended with a .endrep
directive.
Example
.rep 5
push 0
.endrep
This would expand to the following:
push 0
push 0
push 0
push 0
push 0
Include
The .include
directive is a powerful preprocessor directive that allows the programmer to include source code from another file into the current file.
Example
.include "macros.kasm"
PRINT "Hello, world!"
This assumes that the source file called macros.kasm
is in the same directory as the main file, or in the include directory specified by -i
and it contains (at least):
; macros.kasm
.macro PRINT 1
push @
push &1
call "", "print()"
pop
.endmacro
This causes the source code after the .include
directive is expanded to effectively be:
.macro PRINT 1
push @
push &1
call "", "print()"
pop
.endmacro
PRINT "Hello, world!"
This allows the programmer to organize macro definitions and other code much more effectively.
If* Directives
As stated previously, KASM supports conditional assembly, which means that some code could, or could not be included in the final binary if logic that KASM follows is met, or not.
These are extremely similar to C-style #if directives.
An example showing a few possible if* directives:
.define DEBUG
.define VERBOSE 2
.define PRINT_MESSAGES
.ifdef DEBUG ; If something is defined
.if VERBOSE == 1 ; Nested!
PRINT "Debug mode on!"
.elif VERBOSE == 2 ; Else if
PRINT "Extra debug mode on!"
.elifdef PRINT_MESSAGES
PRINT_MESSAGE("Oh no")
.endif ; Must show end of if
.else
; Do something else
.endif
In this case the code would end up being this:
PRINT "Extra debug mode on!"
Variants
Other variants such as .ifn (if not), .elifn (else if not), and .ifndef/.elifndef also are supported:
- if <condition>
- ifn <condition>
- ifdef <identifier>
- ifndef <identifier>
- elif <condition>
- elifn <condition>
- elifdef <identifier>
- elifndef <identifier>
- endif
Chapter 4: Assembly Directives
Besides the preprocessor directives, KASM also has a few assembly directives that are not for preprocessing purposes.
.extern
- External
This directive defines a symbol as external to the linker.
This means that whatever symbol this specifies is not in this file, but is in another file that will be linked with the output of this one.
In more general terms, it is a promise that a symbol exists somewhere else.
Example
.extern .func add_func
call add_func, #
This declares an extenal function (.func
) called add_func
, and then calls that function later.
.global
- Global
This directive is similar to .extern
but it declares that a symbol will be in this file, but it will also let it be visible to other files.
Each .extern
needs to be paired with a .global
in another file.
.global _start
.func
_start:
...
.local
- Local
This declares a symbol as local, which is the default. This is only provided to be thorough.
.local func_name ; This is redundant
.func
func_name:
...
.type
- Symbol Type
This declares a symbol's type. As you may have noticed in the .extern
example there was the type of the symbol specified (.func
).
This can be done when the symbol's binding (local, global, or extern) is specified, but it can also be done later.
An equivalent set of code to the .extern
example, but using .type
would be:
.extern add_func
.type .func add_func
To define an external symbol that is a value and not a function, just use the .value
type:
.extern number
.type .value number
.func
- Function
This directive is used to mark a certain label as a function, which is treated differently in KASM.
Otherwise KASM would have no idea if you want a label to be inside the last function, or a new one.
The usage of the .func
directive is as such:
...
.func
add_func:
....
Sections
In KASM there are two types of sections in a .kasm file:
- Text
- Data
.section
You may change the current section type by writing the .section
directive:
.section .text ; Changes to a text section
.section .data ; Changes to a data section
Text Sections
By default, KASM is already in a .text
section, so you are already familiar with what that means: you can write code in it
Example
.section .text
.func
_start:
...
Data Sections
In KASM if one wanted to declare a global symbol that was not a function, but a value that other KASM files can use without needing
preprocessor includes or copy-and-pasting, we use a .data
section.
.data
sections are used to declare symbols and their values
.section .data
PI .f64v 3.1415 ; Declares a symbol named PI
; that is a ScalarDouble with a value of 3.1415
See Data Types for information on how to declare symbols of all types
Tutorial
This tutorial assumes that you have both kasm and kld installed.
This also assumes that you have a moderate understanding of programming, and hopefully at least a small understanding of assembly language.
Hello World
Following in the footsteps of the official kOS KerboScript tutorials, the first thing that you will learn is how to print "Hello world"
There is some boilerplate (template) code that one must always write to create an executable program in KASM:
Start off by creating a new file called hello.kasm
wherever you would like.
Then write the following lines:
.global _start
.func
_start:
eop
This is actually the smallest valid KASM program that one can write.
Code Breakdown
.global _start
This declares a global symbol called _start
. _start
is a special symbol name that is reserved for a function.
This function is where the program starts, hence the name. This must be global or else the linker will not be able to find it
and will give you an error.
.func
_start:
eop
This declares a function, named _start
which only contains one instruction: eop
eop
tells kOS that the code is over, and this instruction should always go at the end of your _start
function.
Otherwise, strange and difficult to debug things happen.
Now we can resume writing our hello world program!
All we do is add the following four lines inside of our _start
:
push @
push "Hello world!"
call #, "print()"
pop
When calling a function in KASM, there are two ways to do it:
For functions that you have created:
call func_name, #
And:
call #, "func_name()"
The above syntax is used when calling built-in kOS functions. There will be a list of those at the end of the tutorials list.
When calling built-in functions, if they are not meant to return any value to you, such as print()
, then they return a NULL value.
In order for the stack to not be filled up with them we add a pop
instruction after call
to just throw it away.
Both ways of calling a function require you to push
your arguments onto the stack before you call it.
First we push @
to push a function argument marker which just is there to mark the end of the arguments to a function. Remember
push
adds things to the top of the stack.
So when we next say push "Hello world!"
with actually pushes the string we want to print onto the stack.
The stack now has our string, and our argument marker. Then call
is called.
Final code
The final code to print out hello world onto the screen in kOS is:
.global _start
.func
_start:
push @
push "Hello world!"
call #, "print()"
pop
eop
Feel free to replace the string "Hello world!"
with whatever you want.
You can even replace the string entirely, print()
prints numbers and booleans as well.
Using the code
In order to get this code into a format kOS can use, we have to run these two commands:
kasm -o hello_world.ko hello_world.kasm
kld -o hello_world.ksm hello_world.ko
Now you have a file named hello_world.ksm
that you can put into your Ships/Script
folder
that you can run in kOS by typing:
SWITCH TO 0.
RUN hello_world.ksm.
See how to run KASM and the KLinker Docs if you are confused.
Simple Launch Code
This tutorial assumes that you have a built rocket in KSP that you are trying to launch.
In this example we will make a program that will launch a vessel from the launch pad.
This example mainly follows along with the kOS KerboScript documentation.
First, in the last tutorial the kOS terminal prompt and previously run code was still showing up in the terminal. In order to make it look nicer this time, we will clear the screen.
In KS you would write the code:
CLEARSCREEN.
This would clear the screen.
It turns out that this is just a built-in function that you call inside of kOS.
We can create a macro that will call it for us, so that all we have to do is invoke that macro:
.macro CLEARSCREEN
push @
call #, "clearscreen()"
pop
.endmacro
If all we wanted our code to do was clear the screen, then this is all that we would need:
.macro CLEARSCREEN
push @
call #, "clearscreen()"
pop
.endmacro
.global _start
.func
_start:
CLEARSCREEN
eop
The objective here is to write a function that performs a countdown that is printed to the terminal.
In order to help us, we will also declare a helpful macro that will allow us to easily print whatever is on the stack.
.macro PRINT
push @
swap
call #, "print()"
pop
.endmacro
This pushes an argument marker, then swaps it with whatever is just below it on the stack (the thing we want to print) and then prints.
Now we can write the code that will allow us to do a countdown:
Working Code
.macro CLEARSCREEN
push @
call #, "clearscreen()"
pop
.endmacro
.macro PRINT
push @
swap
call #, "print()"
pop
.endmacro
.global _start
.func
_start:
CLEARSCREEN
push "Counting down:"
PRINT
push 10 ; We will count down from 10 (counter)
.countdown_loop:
dup ; Duplicate our counter so that we can compare with it
push 0 ; We will count down until we reach 0
clt ; This pushes true if (counter) <= 0
btr .countdown_end ; We jump to the end of the loop if that was true
dup ; Duplicate our counter value on the stack so
; that we print one, and use the other for counting
push "..." ; Push some dots that we will print out before each number
swap ; Swap them because concatenation happens "backwards"
add ; Concatenate "..." + counter
PRINT ; Print them
pushv 1 ; Push a value of 1
wait ; Wait, therefore waits for 1 second
push 1
sub ; We will subtract 1 from the counter
jmp .countdown_loop ; We go through the loop again
.countdown_end:
eop
In the next tutorial we will modify this example, so stay tuned!
Better Launch Code
This tutorial builds off of the last, improving our launch code.
The first order of business is to actually start the engine. As you know this is done by setting the throttle to 100%, and staging.
In order to set the throttle, we need to introduce how to access variables in KASM.
Throttle
Variables exist in a scope, which has an ID, which can be set up by two instructions:
- bscp - Begin scope
- escp - End scope
By default kOS creates a global variable scope, whose ID is 0. If we want to make variables in our "main function" scope, the scope ID is incremented, for example to create our variable scope will be 1:
bscp 1, 0
... our code
escp 1
The first operand to bscp
is the new scope ID, and the second is supposed to be the "parent ID", or the scope that this scope is in.
The operand to escp
is the number of scopes that we are ending. We have only created one variable scope, so the operand here is 1
.
This removes any variables that were declared in that scope.
If you know C-style languages, or even just KerboScript, you can think of bscp
as a {
and escp
as a }
Now that we have that, in order to set the throttle, all we have to do is set the value of a variable named throttle
. Variables in
kOS are prefixed with the $
character, so the name would be $throttle
In order to store a value on the stack to a variable, there is the sto
instruction, which stands for store.
So in order to set the throttle, the code is:
bscp 1, 0
...
push 1.0 ; The value we want the throttle to be
sto "$throttle"
... other code
escp 1
Staging
When you write the KS code STAGE.
, it actually just calls a built-in function: stage()
So this is the code you have to write to stage in KASM:
push @
call #, "stage()"
pop
Steering
We need to do the equivalent of KerboScript SET STEERING TO UP.
Once again, this is done using global variables.
The UP
is actually a global variable that contains a rotation value that is straight up.
Steering is similar to throttle in that there is a global variable named $steering
that we set
to the desired rotation.
Therefore setting the steering to up can be performed using the following code:
push "$up"
sto "$steering"
Note that push
-ing a variable puts the variable's value on the stack
Waiting
If the code were to set the throttle, set the steering, and then end, kOS would tell the throttle and steering to go back to normal. Therefore in order to make them stay, we need the program to not end.
This can be performed with an infinite loop like so:
.infinte:
push 0
wait
jmp .infinte
Note the use of push 0
and wait
. As noted in the kOS documentation, if you just have an infinite loop
it can slow down your game, and use more electric charge. Therefore we wait for 0 seconds, to provide a small but unnoticable delay.
This would work perfectly fine, but to be a little more sophisticated, we will follow more along with the kOS tutorial and wait until we are above 70,000 m.
Similar to throttle, steering, and up, the SHIP
variable it self is just a global variable named $ship
.
In KerboScript in order to get the ship's altitude, one would write: SHIP:ALTITUDE
In order to do the :
, we will use an instruction called gmb
, which stands for "get member"
To get the ship's altitude, we would write:
push "$ship"
gmb "altitude"
To check this in a loop against 70,000 we would write:
.altitude_loop:
push 0 ; Wait 0 seconds to provide a little delay
wait
push "$ship"
gmb "altitude" ; SHIP:ALTITUDE
push 70000 ; > 70,000
cgt ; Actually performs the comparison
bfa .altitude_loop ; If it isn't 70,000 yet, loop again
Putting it all together
Combining all of the code we have written so far together, the result would be:
.macro CLEARSCREEN
push @
call #, "clearscreen()"
pop
.endmacro
.macro PRINT
push @
swap
call #, "print()"
pop
.endmacro
.global _start
.func
_start:
bscp 1, 0
CLEARSCREEN
; Do the countdown
push "Counting down:"
PRINT
push 10
.countdown_loop:
dup
push 0
clt
btr .countdown_end
dup
push "..."
swap
add
PRINT
pushv 1
wait
push 1
sub
jmp .countdown_loop
.countdown_end:
; Set the throttle
push 1.0
sto "$throttle"
; Set steering
push "$up"
sto "$steering"
; Stage
push @
call #, "stage()"
pop
; Wait to reach 70km
.altitude_loop:
push 0 ; Wait 0 seconds to provide a little delay
wait
push "$ship"
gmb "altitude" ; SHIP:ALTITUDE
push 70000 ; > 70,000
cgt ; Actually performs the comparison
bfa .altitude_loop ; If it isn't 70,000 yet, loop again
escp 1
eop
Miscellaneous
Now that we have introduced many of the aspects of KASM and inner workings of kOS, this page will serve as a quick reference for implementing various things.
If statements
Let's say we had the variable $x
, and wanted to basically do:
IF X > 1 {
PRINT "IF".
} ELSE {
PRINT "ELSE".
}
The KASM implementation would be:
push "$x"
push 1
cgt ; x > 1
bfa .else
push @
push "IF"
call #, "print()"
pop
jmp .if_end
.else:
push @
push "ELSE"
call #, "print()"
pop
.if_end:
...
While loop
Let's say we wanted to implement the following while loop (the inverse of an UNITL
loop):
while SHIP:ALTITUDE < 70000 {
wait 0.
}
This would be implemented in KASM as:
.loop:
push 0
wait
push "$ship"
gmb "altitude"
push 70000
clt ; SHIP:ALTITUDE < 70000
btr .loop
For loop
Example code in C style:
for (int i = 0; i < 10; i++) {
PRINT i.
}
In KerboScript:
FROM {local i is 0.} UNTIL !(i < 10) STEP {SET i to i + 1.} DO {
PRINT i.
}
KASM (using the stack):
push 0
.loop:
dup
push 10
clt
bfa .loop_end
dup
push @
swap
call #, "print()"
pop
push 1
add
jmp .loop
.loop_end:
...
KASM (using a kOS variable $i
):
bscp 1, 0
push 0
stol "$i"
.loop:
push "$i"
push 10
clt
bfa .loop_end
push @
push "$i"
call #, "print()"
pop
push "$i"
push 1
add
sto "$i"
jmp .loop
.loop_end:
escp 1
Functions
In KASM, just like KerboScript, you can create functions that can be called later in your programs, as a way to reuse code.
Declaration
You already know how to define a function, our _start
function:
.func
_start:
...
You can use the .func
directive to define other functions as well:
.func
add_two:
...
You write code in the same manner when you are defining a user-defined function as in the _start
function:
.func
add_two:
add
...
When this function is called, this expects two numbers pushed to the stack to be added.
If you are familiar with C-style functions, or even KerboScript functions, there is the return
keyword that is
used when you want to go back to where the function is called.
This is done in KASM using the ret
instruction. ret
is tied into the kOS variable scope system and takes one
operand: the number of scopes to "pop" when returning. Any values that you want to return are first pushed to the stack.
For our function, this would be:
.func
add_two:
add
ret 0
The sum of the two numbers is already on the stack, so there is no need to push it again. In our case we didn't create any variable
scopes, so we do ret 0
. If we had created variable scopes, like in the following example:
.func
add_two:
bscp 3, 0 ; Create a new scope with ID 3
sto "$x" ; Store the first number in `x`
sto "$y" ; Store the second number in `y`
push "$x" ; Get the value back out of x
push "$y" ; Same for y
add
escp 1
ret 0
Here we create a variable scope, then we end it with escp 1
, which removes the 1 scope that we have created.
The two lines:
escp 1
ret 0
Could simply be replaced with:
ret 1
Calling a Function
In KASM we can call a function that we have declared by simply writing the name of the function as the first operand
of the call
instruction:
.global _start
.func
_start:
push @
push 2
push 3
call add_two, #
call #, "print()"
pop
eop
.func
add_two:
add
ret 0
This would push 2
, and 3
, and then call add_two
, then print out the result. In this case the result is of course:
5
This is how you call functions within the same file as the function is declared. In the next section about creating static libraries, we will go over how to make functions callable from a different file, and how to call them.
Static Libraries
Static libraries are collections of code that are put all into one file, but can be written in multiple files.
They allow you to reuse code that you have already assembled using KASM
, so that you don't need to run it each time.
This is where global
and external
symbols come into play.
Let's say we have the function from last time called add_two
, but we want to put it with a bunch of other math functions
(for some reason) so we put it in a file called math.kasm
:
math.kasm
.func
add_two:
add
ret 0
And this is where we call it:
main.kasm
.global _start
.func
_start:
push @
push 2
push 3
call add_two, #
call #, "print()"
pop
eop
If you tried to assemble main.kasm
as-is, it would give you the following error:
error: instruction references symbol `add_two`, that does not exist
--> main.kasm:8:4
|
8 | call add_two, #
| ^^^^
|
The file main.kasm
has no way of knowing that the function add_two
actually exists in another file,
it assumes you will write it in the same file, which we did not.
So you need to tell KASM that it will exist in another file, that it will be "external" to this file.
Therefore we use the .extern
keyword to declare add_two
as external:
.global _start
.extern .func add_two
You note that you also have to specify the type of add_two
as a function.
main.kasm
will now assemble! But if you try to run kld
like in the past to turn it in to a .ksm file,
you will get an error:
Unresolved external symbol error. External symbol "add_two" has no definition
It says we never defined add_two
, and that is because we never actually added the code for it!
In order to do that, we need to assemble the file we put add_two
in:
kasm -o math.ko math.kasm
Now we have math.ko
, which we can pass to the linker at the same time as main.ko
:
kld -o program.ksm main.ko add.ko
Although... this will give you the same error as before, and that is because we never told KASM to make add_two available to other files. By default KASM makes every function you have local, so that in theory you can name a function the same thing twice, and as long as they are local to two different files, then it will work as expected, each will call their own version, and you won't have to make two different names for them.
To tell KASM to make a function "global" to be seen by other files, we use the .global
keyword:
.global add_two
.func
add_two:
add
ret 0
Now we have told KASM to make add_two
global. So now if we run:
kasm -o math.ko math.kasm
kld -o program.ksm main.ko math.ko
It will not give us an error! And it will work as expected.
You can add as many static libraries as you want to a program, by specifying each one to kld
:
kld -o complex.ksm math.ko main.ko draw.ko files.ko otherstuff.ko ...
This will make one giant file named complex.ksm
that will have all of the code it needs in it.
There are certain times though, that may not want to have one large file, but instead split it up into multiple files, that we could even possible switch out with future versions and the program that relies on it doesn't need to be recompiled, a good example would be how regular KerboScript deals with functions in other files.
This is where shared libraries come in.
Shared Libraries
Shared libraries are useful because you don't have to link all code in your project over and over whenever the code changes. It also allows you to keep file sizes smaller. The same shared library can be used by multiple programs at the same time, and only one copy of it has to exist.
In KerboScript
As you probably know, in KerboScript when you want to load the code from another file, all you have to do us "run" that file. If we wanted the code to only be loaded once (because it is just a bunch of functions) we could do something like:
RUNONCEPATH("library.ksm").
And that would load our functions that we can later call. In order to understand how to do something similar in KASM, we
must first look at the disassembly of calling RUNONCEPATH("library.ksm").
This can be found by first compiling the file, in this case the file with the above code was called main.ks
and then
running KDump (kdump --disassembly main.ksm
):
push @
push @
push true
pushv "library.ksm"
eval
call "@LR00",#
pop
This may be confusing at first, but mostly all that is happening here is that there is a place in the code called "@LR00" which is loaded into kOS every time you run a program from the terminal. It exists so that each and every piece of user code doesn't need to have all the code in it required to run a program every time the user wants to.
This code is able to be changed by the kOS devs at any time because it is not meant to be written directly by users, and is always accessed by the "run" family of functions. In order to understand what we are calling however, as of kOS version 1.3.0.0, the code is:
bscp -999, 0 ; This instruction has the label "@LR00"
stol "$runonce"
stol "$filename"
push @
push "$filename"
eval
push true
push #
call #, "load()"
bfa run_program
push "$runonce"
bfa run_program
pop_loop:
pop
targ
btr after_pop
jmp pop_loop
after_pop:
pop
push 0
ret 1
run_program:
stol "$entrypoint"
call "$entrypoint", #
pop
push 0
ret 1
This is a whole lot to digest, but basically there is a built-in function called load()
that kOS calls when it wants to load
a file. It returns various things, in particular the program's "entry point" which is called like a function, and that is just
the first instruction in the program you are trying to run.
The arguments we are more interested in though, and on lines 2 and 3, there are the stol
instructions which store if the program
should only be run once, and the other one stores the filename. So these two things are the parameters to the kOS loader.
Loading
In order to do the same thing in our program, we can simply make a piece of code that calls "@LR00" the same way the KerboScript code does.
This is not too difficult to do. Here we create a macro to do it for us just in case we want to load multiple:
.global _start
.macro LOAD 1
push @
push @
push true
pushv &1 ; &1 gets replaced with the argument we give
eval
call "@LR00", #
pop
.endmacro
.func
_start:
LOAD("library.ksm)
eop
This program will "load" (aka run) our library.ksm
file.
Delegates
In KerboScript, delegates are functions that you can effectively store in a variable. As of the current version of KerboScript,
it turns out that all functions are actually used in the same way that delegates are. This allows you to assume that a function exists
and generate call
instructions for it just by using a variable name. The way that you would call a function like this would be:
call #, "$add_two*"
Because it is a function delegate and not just a normal variable, the convention is to put a * after the variable name. In this
case we call the function add_two
because that is the name of it in our code, but you can mix and match the names although that might
be confusing.
This would replace the normal:
call add_two, #
That we would do if calling the function statically.
Library Side
Now that we know how to call the function that we have loaded, we need to know how to set that up from the library's view.
This is where the special _init
function label comes into play in KASM. It is used to initialize things inside of a shared library,
which usually takes the form of setting up function delegates.
An example of the shared library code for our add_two
function would be:
.global _init
.func
_init:
pdrl add_two, true
sto "$add_two*"
push @
push "Loaded!"
call #, "print()"
pop
ret 0 ; Note this, see below
.func
add_two:
add
ret 0
As you can see, our code for add_two
stays exactly the same, but we have the new _init
function.
Here we use the special pdrl
instruction. This stands for "push delegate relocate later", and basically allows us to store a function
onto the stack, and then store it in a variable. The first argument is the function that we want to push, and the second argument is a
boolean of whether or not we would like to capture a closure or not.
Then the next instruction we do is just storing our delegate into "$add_two*"
like above.
Then just to verify that it is working, for fun we print out "Loaded!".
Note: Notice at the bottom of _init
we ret 0
. This may be confusing, but remember in the kOS file loader, it simply calls the
file as a function, so in order to tell kOS that our _init
function is over, we need to ret
, and in this case we haven't created any
variable scopes.
The main program can be linked normally (kld -o main.ksm main.ko
) because it doesn't do anything special. But KLinker needs slightly more
information in order to tread the shared library as it would need to be treated.
So in order to link a shared library, all you have to change is to add the --shared flag:
kld --shared -o library.ksm library.ko
Then you will have two files named main.ksm
and library.ksm
that when you put in the same place, you can run main.ksm
and it will
call functions inside of library.ksm
.
Final code
Here is the final code in main.kasm
:
.global _start
.macro LOAD 1
push @
push @
push true
pushv &1
eval
call "@LR00", #
pop
.endmacro
.func
_start:
LOAD("library.ksm")
push @
push @
push 2
push 3
call #, "$add_two*"
call #, "print()"
pop
eop
And library.kasm
:
.global _init
.func
_init:
pdrl add_two, true
sto "$add_two*"
push @
push "Loaded!"
call #, "print()"
pop
ret 0
.func
add_two:
add
ret 0
Calling KerboScript Functions
Because you have previously seen how KerboScript function calling actually works (using pdrl
), it is worth noting that this is both how
you can call KerboScript functions from your KASM code, and how you can make KASM functions callable from KerboScript code.
Final Notes
This is the last offical "tutorial" section of this book. The following section only lists the various kOS built-in functions. You now have enough information to do whatever you desire to do in KASM.
If after reading the list of all built-in functions, you are still confused on how to do something in KASM, I encourage you to write equivalent code in KerboScript, and then run KDump in order to see how KerboScript does it.
If there are requests to create a new tutorial section for something that is more specific, then I will definitely consider adding it.
Built-In List
Please let me know if there is any built-in function of great importance that is missing from this list.
This is the list of (as of the current kOS version) common built-in functions that you can call.
Please note that any arguments that the function takes must be provided in sort of "opposite order". If they take in a boolean as the first argument and an integer as the second, the integer would be pushed first, and the boolean would be pushed last (so that it is on the top of the stack).
Math Functions
Table of Contents
- abs
- mod
- floor
- ceiling
- round
- sqrt
- ln
- log10
- min
- max
- random
- randomseed
- char
- unchar
- sin
- cos
- tan
- arcsin
- arccos
- arctan
- arctan2
- anglediff
abs()
Takes
- Any number type
Description
- Calculates the absolute value of the provided number
Returns
- The absolute value of the provided number (as a double)
mod()
Takes
- Any number type (divisor)
- Another argument of any number type (dividend)
Description
- Calculates the remainder of a division of the two numbers, also known as a modulus. (dividend % divisor)
Returns
- The modulus of both numbers
floor()
Takes
- Any number type, the number to round
- (Optional) the number of places to round to as an integer
Description
- If one number is provided, it is rounded down to the next integer. 1.8 would be rounded down to 1.0
- If two numbers are provided, the first is rounded to the number of decimal places as the second. If given 1.887 and 2, the result would be 1.88
Returns
- The rounded value
ceiling()
Takes
- Any number type, the number to round
- (Optional) the number of places to round to as an integer
Description
- If one number is provided, it is rounded up to the next integer. 1.1 would be rounded up to 2.0
- If two numbers are provided, the first is rounded to the number of decimal places as the second. If given 1.888 and 2, the result would be 1.89
Returns
- The rounded value
round()
Takes
- Any number type, the number to round
- (Optional) the number of places to round as an integer
Description
- If one number is provided, it is rounded to to the nearest integer. 1.1 would be rounded down to 1.0, and 1.6 would be rounded up to 2.0
- If two numbers are provided, the first is rounded to the number of decimal places as the second. If given 1.888 and 2, the result would be 1.89, and if given 1.882 and 2, the result would be 1.88
Returns
- The rounded value
sqrt()
Takes
- Any number type, the number to take the square root of
Description
- If a is the number provided, performs sqrt(a)
Returns
- The square root of the value
ln()
Takes
- Any number type, the number to take the natural logarithm of
Description
- If a is the number provided, performs ln(a)
Returns
- The natural log of the value
log10()
Takes
- Any number type, the number to take the base 10 log of
Description
- If a is the number provided, performs log_10(a)
Returns
- The log base 10 of the value
min()
Takes
- 2 of any number type OR 2 of any string type
Description
- If numbers are provided, it returns the minimum of the two
- If strings are provided, it returns the one that is considered to be "less", useful for sorting
Returns
- The minimum of the two values provided
max()
Takes
- 2 of any number type OR 2 of any string type
Description
- If numbers are provided, it returns the maximum of the two
- If strings are provided, it returns the one that is considered to be "greater", useful for sorting
Returns
- The maximum of the two values provided
random()
Takes
- (Optional) a StringValue for the key for a named random number generator
Description
-
If no argument is provided, then the next number from the default random number generator is provided
-
If an argument is provided, you get the next number from a named random number generator. You can invent however many keys you like and each one is a new random number generator. Supplying a key probably only means something if you have previously used the
randomseed()
function with the same key.
Returns
- A pseudo-random number
randomseed()
Takes
- A key to identify the initialized random number generator
- Then an integer
Description
- Initializes a new random number sequence from a seed, giving it a key name you can use to refer to it in future calls to
random()
Returns
- Nothing (a useless 0)
char()
Takes
- An integer
Description
- Creates a single-character string containing the unicode character specified
Returns
- The string
unchar()
Takes
- A string
Description
- Converts the character in the string provided to a unicode number representing the character
Returns
- The integer
sin()
Takes
- Any number
Description
- Calculates the sine of a number in degrees
Returns
- The result
cos()
Takes
- Any number
Description
- Calculates the cosine of a number in degrees
Returns
- The result
tan()
Takes
- Any number
Description
- Calculates the tangent of a number in degrees
Returns
- The result
arcsin()
Takes
- Any number
Description
- Calculates the inverse sine of a number
Returns
- The result (in degrees)
arccos()
Takes
- Any number
Description
- Calculates the inverse cosine of a number
Returns
- The result (in degrees)
arctan()
Takes
- Any number
Description
- Calculates the inverse tangent of a number
Returns
- The result (in degrees)
arctan2()
Takes
- Any number, x
- Any number, y
Description
- Calculates the inverse tangent of a pair of numbers as (y / x), which resolves ambiguities in the direction of the arctangent so that direction is preserved.
Returns
- The result (in degrees)
anglediff()
Takes
- Any number, x
- Any number, y
Description
- Calculates the angle that would need to be added to x, in order to get angle y. For example, calling it with 90 and 45 would return -45, because 90 - 45 = 45.
Returns
- The difference in angle, in degrees
General
Table of Contents
print()
Takes
- Any value to print (except ArgMarker)
Description
- Prints a value on the screen
Returns
- Nothing (a useless 0)
printat()
Takes
- The row to print at
- The column to print at
- Any value to print (except ArgMarker)
Description
- Prints a value on the screen at the location specified
Returns
- Nothing (a useless 0)
clearscreen()
Takes
- Nothing
Description
- Clears the kOS terminal screen
Returns
- Nothing (a useless 0)
stage()
Takes
- Nothing
Description
- Stages the rocket, similar to hitting the spacebar
Returns
- Nothing (a useless 0)
toggleflybywire()
Takes
- A boolean of this should enable or disable fly-by-wire
- A string representing which type of control to set
Description
- This is another name for "cooked control mode", where you are locking the user out of controlling the rocket. If you set the boolean to
true
it will lock, if you set it tofalse
it will unlock. The string you provide determines which set of controls it should lock out. For example passing in"throttle"
will lock the throttle,"steering"
for steering, etc.
Returns
- Nothing (a useless 0)
selectautopilotmode()
Takes
- A string
Description
- This essentially performs normally user-operated Navball operations. If SAS is enabled, it will set its mode. All possible SAS modes are listed below:
- "maneuver"
- "prograde"
- "retrograde"
- "normal"
- "antinormal"
- "radialin"
- "radialout"
- "target"
- "antitarget"
- "stability"
- An error is thrown for invalid modes
Returns
- Nothing (a useless 0)
run()
Takes
- Arguments to itself:
- An integer (volumeId)
- A string path
- An (extra) ArgMarker!!
- Arguments to the program:
- Any arguments and any number
Description
- The arguments to this are the volume id of the file you will be running, and the path. Then it runs the file with the arguments provided (if any)
Returns
- Nothing (a useless 0)
load()
Takes
- A Null value, or a StringValue path of the output file of the compilation
- A boolean of whether or not to skip loading it if it has been previously loaded
- A StringValue path of the file to be loaded
Description
- This function is both used for compiling KerboScript files, and loading them into memory as shared libraries. Due to this, the arguments have a few different possibilities.
- If you are trying to compile a
.ks
file:- The first argument should be either
"-default-compile-out-"
if you want the output file name to be the same as the input, just with a.ksm
extension, or a file path. - The second argument should be
false
, because if the file was prevously compiled, you want to compile it again. - The third argument should be the path of the file you are trying to compile
- The first argument should be either
- If you are trying to load a
.ks
or.ksm
file:- The first argument should be Null
- The second argument should probably be
true
, although if you want it to be run a second time if you load it twice, then it should befalse
- The third argument should be the path of the file you are trying to load
Returns
- If run in compile mode:
- Nothing (a useless 0)
- If run in load mode:
- A boolean stating whether the file had been previously loaded
- An integer for where to jump to, in order to actually run the file (the entry point)
reboot()
Takes
- Nothing
Description
- Reboots the kOS CPU this is run on
Returns
- Nothing, the CPU is reset
shutdown()
Takes
- Nothing
Description
- Shuts down the kOS CPU this is run on
Returns
- Nothing, the CPU is shut down
File I/O
Table of Contents
- printlist
- logfile
- path
- scriptpath
- switch
- cd or chdir
- copypath
- movepath
- deletepath
- exists
- open
- create
- createdir
printlist()
Takes
- A string representing the type of thing to print
Description
- This is the KerboScript
LIST.
command. So it prints the contents of the current directory to the terminal. However there are many different things it can list, and the default (with no arguments) version is "files". Below is a list of all possible types to list:- "files"
- "volumes"
- "processors"
- "bodies"
- "targets"
- "resources"
- "parts"
- "engines"
- "rcs"
- "sensors"
- "config"
- "fonts"
Returns
- Nothing (a useless 0)
logfile()
Takes
- The file to log the data to
- The data to be logged
Description
- This is the KerboScript
LOG <string> TO <path>.
, so all it does is append text to a file, or it can log integers, etc as well. - Note:
.ks
is the "default file extension" inside of kOS, so if no file extension is provided on the file path, it will add.ks
to it
Returns
- Nothing (a useless 0)
path()
Takes
- A StringValue representing the path
Description
- Creates a
Path
structure representing the file path provided.
Returns
- The
Path
structure
scriptpath()
Takes
- Nothing
Description
- Creates a
Path
structure representing the currently running file.
Returns
- The
Path
structure
switch()
Takes
- An integer representing a VolumeId
Description
- The built-in
SWITCH TO <volume>.
KerboScript function, which switches the internal volume that the file system is using.
Returns
- Nothing (a useless 0)
cd() or chdir()
Takes
- (Optional) the path to change directories to
Description
- Changes the directory that kOS has internally for what the current directory is. If no path is provided, it will change to the "root" of the current volume.
Returns
- Nothing (a useless 0)
copypath()
Takes
- A destination
Path
- A source
Path
Description
- Copies a source file to a destination. If the destination is a file, it copies the contents into a new file with that name, if the destination is a folder, it simply copies the file into that folder.
Returns
- A boolean representing if the copy of multiple files was successful or not
movepath()
Takes
- A destination
Path
- A source
Path
Description
- Moves a source file to a destination. If the destination is a file, it moves the contents into a new file with that name, if the destination is a folder, it simply moves the file into that folder.
Returns
- A boolean representing if the movement of multiple files was successful or not
deletepath()
Takes
- The path of the thing to delete
Description
- Deletes a file or directory on the current volume
Returns
- A boolean representing if the deletion was successful
exists()
Takes
- A string path
Description
- Checks if a file or directory exists
Returns
- Returns true if the path exists, false if not
open()
Takes
- The String path of the file or directory to open
Description
- Opens a file or directory if it exists
Returns
- If the path exists, it will return a
VolumeItem
containing a directory or file. If the path does not exist, it returns a booleanfalse
create()
Takes
- The String path of the file to create
Description
- Creates a file with the provided path and name
Returns
- A
VolumeFile
containing the new file
createdir()
Takes
- The String path of the directory to create
Description
- Creates a directory with the provided path and name
Returns
- A
VolumeDirectory
containing the new directory
Internal
These are various "internal" kOS functions that usually no one really needs to use
profileresult()
Takes
- Nothing
Description
- If you have the runtime statistics configuration option
Config:STAT
set to true, then in addition to the summary statistics after the program run, you can also see a detailed report of the “profiling” result of your most recent program run, by calling this function.
Returns
- A StringValue containing the profile data for running the script
droppriority()
Takes
- Nothing
Description
- Drops the current trigger's running priority to the priority of the code that it interrupted
Returns
- Nothing (a useless 0)
General Guides
These are a few general guides for programming in Kerbal OS. These sort of mirror some of the regular kOS documentation but are all in Kerbal Assembly. This is meant to prevent a lot of redundant disassembly.
Lists
Table of Contents
List Construction
Numerous built-in functions in kOS return a list.
If you wish to make your own list from scratch you can do so with the list()
built-in function. You pass a varying number of arguments into it to pre-populate
the list with an initial list of items.
push @
call #, "list()"
stol "$mylist"
push @
push 10
push 20
push 30
call #, "list()"
stol "$mylist"
Anything can be stored in a list, including other lists.
Associated Suffixes
List objects are a type of Enumerable
in kOS, and therefore those suffixes apply to Lists as well.
Enumerable Suffixes
Iterator
An alternate means of iterating over an Enumerable
. Returns an Iterator
object.
push "$mylist"
gmet "iterator"
push @
call #, "<indirect>"
stol "$myiter"
Reverse Iterator
Just like Iterator, but the order of the items is reversed.
push "$mylist"
gmet "reverseiterator"
push @
call #, "<indirect>"
stol "$myreviter"
Length
Returns the number of elements in the enumerable as an integer.
push "$mylist"
gmet "length"
push @
call #, "<indirect>"
stol "$length"
Contains
Returns true if the enumerable contains an item equal to the one passed as an arguments
push "$mylist"
gmet "contains"
push @
push 2
call #, "<indirect>"
btr .somewhere
Empty
Returns true if the enumerable has zero items in it.
push "$mylist"
gmet "empty"
push @
call #, "<indirect>"
btr .somewhere
Dump
Returns a string containing a verbose dump of the enumerable’s contents.
push "$mylist"
gmet "empty"
push @
call #, "<indirect>"
stol "$dump"
List Suffixes
Add Item
Appends the new value given to the end of the list.
push "$mylist"
gmet "add"
push @
push 2
call #, "<indirect>"
pop
Insert Item
Inserts a new value at the position given, pushing all the other values in the list (if any) one spot to the right.
The code below inserts the value "Hello"
into index 0.
push "$mylist"
gmet "insert"
push @
push 0
push "Hello"
call #, "<indirect>"
pop
Remove Item
Remove the item from the list at the numeric index given, with counting starting at the first item being item zero
The code below removes the value at index 0.
push "$mylist"
gmet "remove"
push @
push 0
call #, "<indirect>"
pop
Clear List
Calling this suffix will remove all of the items currently stored in the List
.
push "$mylist"
gmet "clear"
push @
call #, "<indirect>"
pop
Copy Suffix
Sublist
Returns a new list that contains a subset of this list starting at the given index number, and running for the given length of items.
The code below stores a sublist of mylist
that starts at index 1 of mylist
and has a length of 4.
push "$mylist"
gmet "sublist"
push @
push 1
push 4
call #, "<indirect>"
stol "$mysublist"
Join List into String
Returns a string created by converting each element of the array to a string, separated by the given separator.
push "$mylist"
gmet "join"
push @
push ","
call #, "<indirect>"
stol "$mystring"
Find Item
Returns the first integer index within the list where an item equal to this
item can be found. Whatever the definition of “equals” is for this item type
will be used to decide if a match is found. This is a linear search from start
to finish so it can be slow if the list is long.
If no such item is found, -1
is returned.
The code below searches for the number 5
in mylist
and stores the result in mylocation
.
push "$mylist"
gmet "find"
push @
push 5
call #, "<indirect>"
stol "$mylocation"
Index Of Item
This is just an alias for find.
Find Last Item
This is the same as FIND(item), except that it searches backward instead of forward through the list. It finds the lastmost element that is equal to the item.
The code below searches for the number 5
in mylist
and stores the result in mylocation
.
push "$mylist"
gmet "findlast"
push @
push 5
call #, "<indirect>"
stol "$mylocation"
Last Index Of Item
This is just an alias for findlast.
Element Access
In order to access individual elements in a list, we perform an operating called indexing. All list locations start at index 0, so the first element is at index 0, and the second at index 1, etc.
In KerboScript one uses the "list index" syntax:
PRINT mylist[0].
This prints the first element in the list.
In kasm one writes:
push "$mylist"
push 0
gidx
call #, "print()"
pop
This also accesses the first element in the list and prints it.