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.

  1. Download and extract the latest release's zip file from the Releases page
  2. Place the executable in the desired location
  3. 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

  1. Download and extract the latest installer from the Releases page
  2. Run the installer, and follow the prompts
  3. 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 ValueType
0NULL
1Bool
2Byte
3Int16
4Int32
5Float
6Double
7String
8ARG MARKER
9ScalarInt
10ScalarDouble
11BoolValue
12StringValue
  • 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()

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 to false 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
  • 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 be false
    • 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()

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 boolean false

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.