Welcome to the KSL Docs!
Hopefully you learn something useful about KSL!
Think something is missing or could be improved? Contribute Now!
Here by accident? KSL Hopepage.
Introduction
KSL is a language designed to be strongly typed, aot-compiled, and memory safe. Along with some other neat features like function attributes and pretty much whatever else seems worthy of having.
Getting Started
Installing KSL
Configuring KSL
Tutorials
Hello World in KSL
Ah yes, the traditional "hello world" program. Luckily for you, my dear programmer, we have a fun little cheat for the world's most basic program. The hello world program seems to be something that a lot of developers end up building, and KSL wants to make everything easier.
So we decided to make a standard library module for the hello world program!
using std.t;
fn main() -> void {
std.t.hw();
return;
}
And that's it. The hw()
function will print "Hello, World!"
Ok, but what if I want to actually say "World, Hello?"
Now there we have some innovation! Some progress! You're going to do incredible things in the future, that's truly some outside the box thinking!
Let's do it!
So first, we're going to need a main function, our entry point.
fn main() -> void {}
Since we don't need this function to return anything we can just set the return type to void.
We're also writing to the console, so we're going to need our good old
standard io module. Go ahead and add it with: using std.io;
That should give us access to the writeln
function, so let's finally
finish off your "World, Hello?" program.
using std.io;
fn main() -> void {
std.io.writeln("World, Hello?");
return;
}
If Statement
For Loops
If you're here you probably want to learn how to make a for loop in KSL. If that's the case, welcome! Otherwise, uh, leave?
So, we want to make something interesting that expresses how KSL works. A great idea for that would be to make a simple program that generates a random decimal value, we'll then round that and count the number of 1's and 0's in a sample number!
The first thing we need to do, is consider our requirements. We need to
generate a random number, round it, and we'll want to display the results
at the end. So we need std.random
, std.math
, and std.io
.
Let's start our new file, you can call it whatever you want but I'll call
mine super_epic_for_loop_example_for_the_ksl_docs_tutorial.k
! Without
further adieu, let's begin.
using std.io;
using std.math;
using std.random;
This should get all our needs taken care of in regards to the standard library modules! Now we need to define our entry point.
fn main() -> int { return 0; }
As covered in some other tutorials (hopefully) the main
function is
the entry point and the -> int
defines the function derivative
(basically the expected function return type, but KSL has to be
sooooo special.)
Now let's define our zero and one counts. By default all variables in KSL are mutable, this is great news for us because that means we don't have to do any extra work to update these values!
fn main() -> int {
int zeros = 0;
int ones = 0;
return 0;
}
We've made so much progress so far! Go ahead and get a drink of water, because this is where it gets real.
We need to add the famed for loop now.
fn main() -> int {
int zeros = 0;
int ones = 0;
for (int i = 0; i < 100; i++;) {
}
return 0;
}
So, let's disect this a little bit. The first statement is our initializer, we're setting the i variable to an integer with a value of 0. The next is an expression, it's expected to be a boolean. If the value is true then it will run again, otherwise it will move on in the program. Finally, we have the iterative statement, the i++, which is just a shorthand to increase the variable i by 1 in this case.
Great, that was a lot, but it only gets better from here! Let's add the random number generation function!
fn main() -> int {
int zeros = 0;
int ones = 0;
for (int i = 0; i < 100; i++;) {
f64 random_float = random.rand() -> f64;
}
return 0;
}
This line of code actually covers a lot of topics in KSL, for now most of them
aren't super necessary to know. If you want to read more later check out
inline function calls, specifically inline hints.
All you need to know for this tutorial is that this is getting a random number
and saving it to the random_float
variable, which we have set to a f64
.
Now we need to round this value to figure out if it will be a 1
or a 0
!
We can do this with the following code, go ahead and add it after the
random_float
variable line.
int random_rounded = math.round(random_float) -> int;
If you've figured that out we can move on to the if statement, which will be the part of our program that actually tells us which variable to increment (ones or zeros, if you've already forgotten.)
#![allow(unused)] fn main() { if (random_rounded == 1) { ones++; } else { zeros++; } }
What this is doing is checking if the random_rounded
variable is equal to
1
, if it is, then we increment the ones
variable (remember that shorthand
from earlier?) Otherwise, we add 1
to the zeros
variable. Luckily, since
the result of our random number rounding can only be 1
or 0
in this situation,
we don't have to add any other logic for counting!
If you ran this code right now it would work! But you wouldn't be able to see the
results :( and that's kind of the point of this whole thing. So, let's use our
awesome io
module from the standard library to show our counts!
#![allow(unused)] fn main() { io.writeln(zeros); io.writeln(ones); }
Great, now it will print our counts to the console!
For those of you that don't care about learning the language and just want to make something quickly without thinking of the potential consequences, here is the full code:
using std.io;
using std.math;
using std.random;
fn main() -> int {
int zeros = 0;
int ones = 0;
for (int i = 0; i < 100; i++;) {
f64 random_float = random.rand() -> f64;
int random_rounded = math.round(random_float) -> int;
if (random_rounded == 1) {
ones++;
} else {
zeros++;
}
}
io.writeln(zeros);
io.writeln(ones);
return 0;
}
Oh yeah, if you wanted to check your work that's also good, no hate on that.
While Loops
Rock, Paper, Scissors
Comments
Comments in KSL are similar to any other modern language.
For a single line comment you can do //
and for a multi-
line comment, the usual /*
*/
syntax.
// Below is a function, and this is a comment!
fn main() -> void {
return;
}
/*
Below is a function that returns void,
and this is a multi-line comment!
*/
fn main() -> void {
return;
}
You can even use this inline! (If you're crazy...)
fn /* create a function */ main /* call it `main` */ () /* no args */ -> void /* return nothing */ {
return /* see, nothing! */;
}
note
Due to some current limitations, KSL has a hard time handling nested multi-line comments.
Understanding Types
Integers
Floats
Booleans
References
Others
Variables
Variable declarations are extremely simple. Simply pick a variable type and identifier, then set it to a value.
int x = 20;
important
You cannot defined a variable with no value, for example: int x;
is not valid syntax. However, variables are treated as mutable, so to achieve the same effect just initialize the variable with dummy data.
Currently the supported types in KSL are:
Type | Identifier | Size (Bits) |
---|---|---|
int8 | i8 | 8 |
int16 | i16 | 16 |
int32 | i32 | 32 |
int64 | i64 || int | 64 |
float32 | f32 | 32 |
float | f64 || float | 64 |
bool | bool | 1 |
null | null | N|A |
void | void | N|A |
note
KSL is designed for programmers of all skill levels. Some programmers might not care (or know) about int's of different sizes, so int
and float
are aliases for the largest supported int and float types. It makes it easier for beginners to understand and it's more readable for simple projects that don't need to worry about tight memory constraints.
warning
Documentation incomplete, pulled from unfinished ksl/ksl_syntax.md file.
Expressions
Operations (Binary and Unary)
Higher precedences are executed first.
Supported Binary Operations (and Precedence):
Operation | Symbol | Precedence |
---|---|---|
Multiply | * | 80 |
Divide | / | 80 |
Add | + | 70 |
Subtract | - | 70 |
Less or Equal | <= | 60 |
Greater or Equal | >= | 60 |
Less Than | < | 50 |
Greater Than | > | 50 |
Equal | == | 40 |
Not Equal | != | 40 |
Not (Bool) | ! | 30 |
And (Bool) | && | 20 |
Or (Bool) | || | 10 |
Supported Unary Operations:
Operation | Symbol |
---|---|
Negative | -<num> |
Not | !<bool> |
Cast | <type>'<expr> |
Operations are only allowed between supported types. KSL may automatically promote expressions (ex. 12 + 300 will turn into i16) but it will not demote them, in order to set a variable to a value smaller than the type size in the resulting expression please downcast.
Other examples of expression promotion:
// Since we're working with an integer
// and a float, we promote to a float
float x = 10.0 + 5;
#![allow(unused)] fn main() { // Since we're working with an i8 (10) // and an i16 (400) the expression is // promoted to an i16, we can still assign // the expression result to an i32 though // because the variable storage type is // larger. If `x` was an i8 it would cause // an error. i32 x = 10 + 400; }
warning
Information and methodology about expression promotion is subject to change as we evaluate other type safety and memory safety strategies.
warning
Documentation incomplete, pulled from unfinished ksl/ksl_syntax.md file.
Functions
Definition
For now, functions will be defined with the fn
keyword,
then comes the function identifier and typed parameters (inside
parenthesis). As KSL is a strongly typed language, you will also
need to define the function return type (or function derivative.)
This can be done with ->
and then the type identifier.
fn main(int a) -> int {
return a;
}
warning
Documentation incomplete, pulled from unfinished ksl/ksl_syntax.md file.
Returns
Attributes
Inline Calls
Currently, functions in KSL are defined internally in two ways,
by the function body declaration or a forward declaration. In the
case of a forward declaration, KSL will attempt to interpret the
most likely return type. For example, if a function is called inside
of an integer operation then it's likely to be an integer. In order
to override this behavior and define a return type for an inline
function call you can use the derive keyword (->
) on the function
call. See below:
Example of inline call:
int x = add(1, 3) + 5;
Example of inline call w/ type hint:
int x = add(1, 3) -> int + 5;
Sometimes it can be easier to read if you move the type hint to right after the funciton:
int x = add(1, 3)->int + 5;
important
You cannot use this syntax to change the function return type. If you wish to use a different type (post-return) then look into type casting. This is only to hint to the compiler what the expected return type is.
caution
Using this syntax to claim a function returns void
for a function that returns an int
will result in an unresolved symbol. The same applies for other type differences as well.
Similarly, if a function call doesn't appear to have any context
then KSL might assume it returns nothing (void
or null
.) This
might cause an error so you can use the type hint syntax outside of
algebraic operations as well.
Example:
add(1, 3) -> int;
warning
Documentation incomplete, pulled from unfinished ksl/ksl_syntax.md file.
Conditionals
Loops
For
While
The syntax for while loops is absurdly easy. All you need is the
while
keyword with some parenthesis and an expression.
while (<expression>) {
// Do something? Maybe?
}
The only real requirement is that the expression type is a boolean. KSL will give you a nice error if you don't. If this is a problem, you can always cast to a boolean.
while (bool'127) {
// Do something? Probably?
}
For those of you focused on performance, look, I get it. You want the fastest loop option, for loop or while loop? Which is it?
Well, the answer is pretty simple. Both the for loop and while loop are compiled down to the exact same code. So the speed of your program mostly lies with the quality of the code.
Type Casting
KSL has a potentially unintuitive type casting syntax. Just use a type identifier followed by an apostrophe:
float x = 10.0;
int y = int'x;
There are some type casts that will result in errors, for example, casting to null or void. But why do that anyway? It should be noted that casting only works for the term immediately right to the cast. See below:
Example where the entire expression is calcuated then cast:
// This turns into 2.0 and is then turned into 2.
int x = int'(1.0 + 1.0);
Example where only the first number value is cast:
// This is turned into 1 + 1 then turned into 2.
int x = int'1.0 + 1;
A quick note on casting to booleans:
In some languages casting integers or floats to a boolean may be different.
In KSL all integers other than 0
are cast to true
. Similarly, all floats
other than 0.0
are cast to true
:
bool'1; // true
bool'0; // false
bool'252466642; // true
bool'-35; // true
bool'0.0; // false
bool'135.03111; // true
bool'-135.77; // true
warning
Documentation incomplete, pulled from unfinished ksl/ksl_syntax.md file.
Function Attributes
Entry
No Fail
Libraries
The syntax for this is strange so pay attention. Both libraries and
files use the using
keyword to be imported, but what follows the
using
keyword is different.
In order to import another .k or .ksl file, follow up the using
keyword with a string that contains the relative path to the file:
using "src/api.k"; // Imports the contents of src/api.k to be used.
using "src/abi.k"; // Imports the contents of src/abi.k to be used.
using "lib/ffi.k"; // Imports the contents of lib/ffi.k to be used.
To use a library (like a standard-lib module) the syntax is a
little different, follow up the using
keyword with an extended
identifier instead:
using std.io; // Links the standard io library.
using std.fs; // Links the standard file system library.
using std.env; // Links the standard environment library.
note
The biggest difference is that imports (using "") are .k or .ksl files, most likely part of your own codebase. Links, on the other hand (using <iden>) link object files to your final executable. These object files are expected to be in the same directory as the compiler itself, but project-specific support may be added in the future. Each period represents a directory. So std.io
would link the std/io.o
object file into the final executable.
warning
Documentation incomplete, pulled from unfinished ksl/ksl_syntax.md file.
Import
Links
Namespaces
FFI Concepts
Currently we're discussing adding a no mangle attribute. This would do exactly what it sounds like, it would maintain it's function name regardless of the name mangling schema.
We're also thinking about adding some kind of syntax that would allow raw/manual forward declarations without KSL doing any kind of type predictions.
extern fn func_in_another_language(int, float, float) -> void;
This is still pretty close to KSL syntax and it's relatively intuitive so it's likely.
This would enable developers to call functions in non-officially supported linked object files while maintaining the type and memory safety of KSL (assuming they don't get type translations wrong.)
Additionally, for declaring multiple external functions at once, there could be an optional block inside.
extern {
fn func_in_another_lang_zero(int) -> void;
fn func_in_another_lang_one(float) -> int;
fn func_in_another_lang_two(int) -> float;
}
Standard Library
Standard Library Modules:
IO
warning
The STD-IO module is not stable and/or partially implemented.
using std.io;
writeln
Parameters:
1 = int
Returns:
int
null (void)
C Impl:
int std__io__writeln____int_int(int a);
void std__io__writeln____int_null(int a);
FS
warning
The STD-FS module is not stable and/or partially implemented.
using std.fs;
read
Parameters:
1 = string | path of file
Returns:
string | contents of file
write
Parameters: 1 = string | path of file 2 = string | new contents
Returns: bool | success status
ENV
warning
The STD-ENV module is not stable and/or partially implemented.
using std.env;
get
Parameters:
1 = string | environment variable name
Returns:
string | contents of environment variable
args
Parameters:
Returns:
string[] | command line parameters on launch
Math
Random
The KSL Standard
A standard for KSL features and language implementation. In the future all new features and language components will need to be submitted for review and added to the standard before being included in the compiler.
The using
Keyword
04 / 09 / 25
Objective
The objective of the using keyword is to enable multi-file projects to be compiled without manually defining all included files from the command line, it also serves as a way to disclose which existing object files should be linked after codegen.
Dictionary
using
- The using keyword
Examples
The using keyword can change it's meaning depending on the syntax that follows. Following a using keyword with a string will tell the compiler to include another source file at the path provided, relative paths are preferred, example:
/* file: main.k */
using "./api/weather.k";
This syntax will include the source of ./api/weather.k
in the compiler. It's
worth noting that each source file is compiled individually using a shared symbol
table. The resulting object files for each individual source file are then linked
together to produce the final executable.
The second way to use the using keyword is with identifiers. This syntax will tell the compiler to link an object file following the path of the identifier. For example:
/* file: main.k */
using std.io;
This example will tell the compiler to link std/io.o
with the resulting object
files at the end of compilation. By default the compiler will search it's own
working directory for object files to link, however if it does not find any then
it will switch to the project working directory. Periods in the syntax will denote
folders, for example:
/* file: main.k */
using os.api.platform;
This example will attempt to link os/api/platform.o
with your KSL code.
Conditionals
Proposal unfinished.
The while
Keyword
Proposal unfinished.
The for
Keyword
Proposal unfinished.
Templates
04 / 09 / 25
Objective
The objective of templates is to achieve an effect similar to polymorphism. For example, defining baseline logic (the template) without static types, then during compile time a clone of the template with static types is generated for each unique call.
Dictionary
template
- A keyword denoting the definition of a template function.
$[a-zA-Z]
- Syntax denoting the use of an unknown type, single letters only.
Examples
In the example below you can see a template add function, it takes two parameters
of unknown types. The parameter names are x
and y
with types $A
and $B
,
respectively. Templates must return/derive a single type.
template add($A x, $B y) -> int {
return int'(x + y);
}
If this template was called here is an example generated function:
fn add(float x, int y) -> int {
return int'(x + y);
}
add(10.0, 5);
Notice how in this example the template call (add(10.0, 5)
) uses a float and an
integer? This will dictate how to template generates the function. A template can
also be used for multiple function calls with multiple dynamic types, ex:
fn add(float x, int y) -> int {
return int'(x + y);
}
fn add(float x, float y) -> int {
return int'(x + y);
}
Templates can also reference the dynamic types multiple times in it's function body as long as the result/derivitive of the function remains constant, ex:
template multiply($A x) -> int {
$A amount = 2;
$A result = x * amount;
return int'result;
}
The fn
, ->
, & return
Keywords
04 / 09 / 25
Objective
The objectives of the fn, ->, and return keywords are to add rich function support to KSL.
Dictionary
fn
- Keyword used to denote the start of a function.
->
- Keyword used to denote the return type/derivative of a function.
return
- Keyword used to return a value from a function.
Examples
The fn keyword followed by a single identifier and parenthesis defines a basic
function. Functions also need a defined return type, which can be set using the
derive keyword (->
.) Once you're done in your function body you can include
a return
keyword to return a value. It's expected that the returned value
matches the type defined after derive
. Technically speaking, the return keyword
is optional, KSL will insert one for you automatically if it doesn't see one.
fn main(int a) -> float {
return 10.0;
}
In the example above we can see a function defined, this function has the name
main
, it takes one integer parameter called a
, and it returns a float. This
function has the return
statement defined in it's function body.
Functions can have multiple parameters separated by a comma. They cannot have multiple return types though.
fn main(int a, int b) -> int {
return a + b;
}
Casting
Proposal unfinished.
Compiler Internals
This series of documentation pages is entierly dedicated to explaining how the compiler works internally. If you're not actively working on the KSL compiler or building a KSL module then you probably don't want to worry about anything here.
- Compiling the Compiler
- Compiling KSL-STDLIB
- Name Mangling Convention
- What is fractstrike
- Expression Semantics
Compiling the Compiler
On Windows
Requirements:
- KSL Source Code
- Rust 1.87^: Download Rust
- LLVM 18.1.x: Download LLVM
- The
libxml2
requirement for LLVM seems to be missing on Windows, luckily you can download it withvcpkg install libxml2:x64-windows
. Once it's downloaded, rename the file fromlibxml2
tolibxml2s
and put it in yourllvm/lib
folder.- If you need
vcpkg
click here.
- If you need
You'll want to make sure Rust is installed fully. I've found that
installing it globally just takes care of a lot of potential issues.
Make sure the LLVM/bin
folder is added to your path as well.
On top of that, you'll want to make a new system variable:
Variable | Value |
---|---|
LLVM_SYS_180_PREFIX | <your_llvm_root_directory> |
Once you have Rust and LLVM set up you shouldn't really need anything
else! Just navigate to the ksl source code root directory and run
cargo build
or cargo build --release
.
It sounds pretty simple but some of the config stuff can take a while to figure out, I'd say this process could take anywhere between 30 minutes and few hours, depending on your technical skills. Don't get discouraged if it takes a while.
On Linux
note
Documentation help wanted! The Linux build instructions are not finalized and may depend on distro, any contributions would be great!
Requirements:
On MacOS
note
Documentation help wanted! The MacOS build instructions are not finalized. Any contributions toward this section would be super helpful!
Compiling KSL-STDLIB
yeah good luck lol
Name Mangling Convention
The KSL name mangling convention is designed to prevent any kind of type confusion. That includes the return type.
This also means that you can provide function overloading in KSL modules and external object files. The main reason for the return type being included is so that there can be functions that serve the same purpose but the KSL return type predictor doesn't get too broken.
So, for example, at the time of writing both of these
functions exist in the std.io
module:
// std.io.writeln(int _) -> int
int std__io__writeln____int_int(int num) {
printf("%d", num);
return 0;
}
// std.io.writeln(int _) -> null
void std__io__writeln____int_null(int num) {
printf("%d", num);
return;
}
Functionally they're the exact same, but one returns an integer and one returns void ("null" in KSL at the time of writing.)
note
void
is likely going to be added in the near future.
The convention for name mangling in KSL is as follows:
- Periods in identifiers are translated to two underscores.
- Space between the function name and types are four underscores.
- The rest of the name will have all parameter types and the return type all separated by one underscore.
So:
std.io.writeln(int num) -> null
// Becomes
std__io__writeln____i64__null
std.io.writeln(f32 x) -> int
// Becomes
std__io__writeln____f32__i64
You'll also see that the type keyword int
turns into i64
.
This is because, as explained in variables,
int
and float
are just aliases for i64
and f64
respectively.
Reasoning for this choice are explained there.
fractstrike
fractstrike is ksl's runtime garbage collector [in development].
Expression Semantics
This page will go over how semantic analysis handles expressions. It will cover the technical details behind return type prediction in inline calls and type promotion.
Currently, KSL will travel the expression in the AST and determine the smallest possible type for all literals and the types defined for each variable. It will then cross reference these anywhere there is an operation between them. It will pick the larger type of the two and "promote" the entire expression to that type. This way we can calculate expressions for differing types without losing data from downcasts.
In the future, we're likely to change this system so that the entire expression recieves a type hint ahead of time. It will attempt to convert all literals (and potentially variables) to this type. If the expression ends up with a larger type than the hint it will simply throw an error.
Additionally, if there is a type that doesn't match the rest that cannot be implicitly changed it will throw a warning or error to the programmer explaining that in order to "do this operation" they must cast to an acceptable type.
Error Codes
A list of error codes in the KSL compiler, where the originate, why they were triggered, etc.
EL0000 = Error [Lexer]: Unexpected End-of-File Immediately After Comment Opening
EL0001 = Error [Lexer]: Unexpected End-of-File While Lexing Comment
EL0002 = Error [Lexer]: Unexpected End-of-File Immediately After Comment Opening
EL0003 = Error [Lexer]: Unexpected End-of-File In Closing of Multi-Line Comment
EL0004 = Error [Lexer]: Unexpected End-of-File While Lexing Comment
EL0005 = Error [Lexer]: Unexpected End-of-File Immediately After Identifier Opening
EL0006 = Error [Lexer]: Unexpected End-of-File While Lexing Identifier
EL0007 = Error [Lexer]: Unexpected End-of-File Immediately After Number Opening
EL0008 = Error [Lexer]: Unexpected End-of-File While Lexing Number
EL0009 = Error [Lexer]: Unexpected End-of-File Immediately After String Opening
EL0010 = Error [Lexer]: Unexpected End-of-File While Lexing String
EP0000 = Error [Parser]: Expected <token> but got <token>
EP0001 = Error [Parser [Iden]]: Expected identifier, found <token>
EP0002 = Error [Parser [Qden]]: Expected identifier, found <token>
EP0003 = Error [Parser]: Unexpected end-of-file while parsing block
EP0004 = Error [Parser]: Function <name> parameter <param name> must be a valid type, currently <invalid type name>
EP0005 = Error [Parser]: Function <name> must derive valid type, currently <invalid type name>
EP0006 = Error [Parser]: Invalid statement used to initialize for loop
EP0007 = Error [Parser]: Invalid statement used in for loop
EP0008 = Error [Parser]: Namespace Declaration without a Name
EP0009 = Error [Parser]: Expected identifier or string after using but got <token>
EP0010 = Error [Parser]: Unexpected Token in Expression, Found <token>
EP0011 = Error [Parser]: Expected Literal in Expression, Found <token>
ES0000 = Error [Semantics]: Expected variable <name> to be <type> but it's <type>, maybe cast?
ES0001 = Error [Semantics]: Reference to undefined variable <name>
ES0002 = Error [Semantics]: Invalid Binary Operation Types, Cannot use <type> With <type>, Consider Casting
ES0003 = Error [Semantics]: Inline Function Call with Type Hint Doesn't Match Function Return Type (Hint: <type>, Returns <type>)
ES0004 = Error [Semantics]: Unexpected Number of Parameters to Function <name>, Expected <value> but got <value>
ES0005 = Error [Semantics]: Type mismatch passed to function <name> in arg slot <value>, expected <type> but got <type>
ES0006 = Error [Semantics]: Different types passed to forward declared function <name> in arg slot <value>, previously used <type> but <type> was passed, verify this is correct
ES0007 = Error [Semantics]: Empty array literal, verify this is correct
ES0008 = Error [Semantics]: Array literal initialized with type <type> was passed a <type>
ES0009 = Error [Semantics]: Missing type hint for array literal declaration
ES0010 = Error [Semantics]: Invalid array index type, must be in the integer family, got <type>
ES0011 = Error [Semantics]: Expected <literal> to be Parsed as an Integer
ES0012 = Error [Semantics]: Expected <literal> to be Parsed as a Float
ES0013 = Error [Semantics]: Cannot use <type> in the context of <type< (attempted literal upcast)
ES0014 = Error [Semantics]: Cannot use <type> in the context of <type> (attempted type conversion)
ES0015 = Error [Semantics]: Cannot implicitly convert <type> to <type> (would lose fractional data)
ES0016 = Error [Semantics]: Cannot implicity convert <type> to <type>
ES0017 = Error [Semantics]: Type mismatch in variable declaration <name>, expected <type> but got <type>
ES0018 = Error [Semantics]: Type mismatch in variable reassignment <name>, expected <type> but got <type>
ES0019 = Error [Semantics]: Attempted to reassign variable that does not exist <name>
ES0020 = Error [Semantics]: Type mismatch in function return, expected <type> but got <type>
ES0021 = Error [Semantics]: Type mismatch in function return, expected <type> but got none
ES0022 = Error [Semantics]: Condition in if statement must be of type Boolean, got <type>
ES0023 = Error [Semantics]: Condition in while statement must be of type Boolean, got <type>
ES0024 = Error [Semantics]: Condition in for statement must be of type Boolean, got <type>
ET0000 = Error [Symbols]: Scope <scope_name> not found in path
ET0001 = Error [Symbols]: Symbol <name> already exists in current scope
ET0002 = Error [Symbols]: Symbol <name> exists and is not a scope
ET0003 = Error [Symbols]: Symbol <name> already exists in specified scope
ET0004 = Error [Symbols]: Invalid scope in path <scope_name>
ET0005 = Error [Symbols]: Cannot enter scope <scope_name> because it's occupied by a non-scope-containing symbol
EN0000 = Error [Linker]: KSL requires <os_linker> for linking, install it or add it to path
EN0001 = Error [Linker]: Failed to run linker: <linker_error>
EN0002 = Error [Linker]: Linker failed with status code <linker_status><linker_error>