KonsolScript

KonsolScript is modern lightweight embeddable scripting language optimized for tooling, games, automation, and beginner accessibility designed for someone who values clarity, portability, and extensibility.


Table of Contents

  1. Language basics
  2. Operators
  3. Control flow — if/else, while, for, foreach, break, continue, switch/case
  4. Functions
  5. Arrays
  6. Structs (UDTs)
  7. Classes
  8. Error handling
  9. Built-in modules — Konsol, Math, String, File, Time, List, Map, Json, Path, OS, Regex, Date, Hash, CSV
  10. Adding a module
  11. Plugin system
  12. Available plugins — curl, sqlite, mysql, zip
  13. Embedding API

Language basics

Types

Three primitive types, all stored in a Value variant:

Type Declaration keyword Default
Number Var:Number 0
String Var:String ""
Boolean Var:Boolean false

Variable declaration

Var:Number x;
Var:Number y = 42;
Var:String name = "hello";
Var:Boolean flag = true;
Var:Number a = 1, b = 2, c = 3;

Variables can be assigned without re-declaring:

x = x + 1;
name = "world";

Literals

42            // integer
3.14          // float
0xFF          // hex (stored as Number)
"hello\n"     // string — supports \n \t \" \\
"Hi ${name}!" // interpolated string — ${expr} is evaluated at runtime
true false    // booleans

String interpolation

Embed any expression inside ${ } within a double-quoted string:

Var:String name = "world";
Konsol:Print("Hello ${name}!");         // Hello world!

Var:Number a = 3;
Var:Number b = 4;
Konsol:Print("${a} + ${b} = ${a + b}"); // 3 + 4 = 7

Var:String msg = "Result: ${a * b}";
Konsol:Print(msg);                      // Result: 12

Guru-level: full expressions inside ${}

${} accepts any expression the runtime can evaluate — not just variable names. Arithmetic, ternary, function calls, and even nested interpolation all work. — ${} is a full expression slot, not merely a variable substitution.

Konsol:Print("area     = ${width * height}");
Konsol:Print("midpoint = ${(a + b) / 2}");
Konsol:Print("sign     = ${x > 0 ? "pos" : x < 0 ? "neg" : "zero"}");
Konsol:Print("doubled  = ${timesTwo(n)}");

Multi-line strings (triple-quote)

Use """...""" for strings that span multiple lines or contain double-quote characters:

Var:String msg = """Hello,
World!
This is line three.""";
Konsol:Print(msg);

// Double-quotes inside are fine
Var:String code = """Konsol:Print("hi");""";

// ${} interpolation works inside triple-quoted strings too
Var:String name = "Alice";
Var:String greeting = """Dear ${name},
  Welcome!""";
Konsol:Print(greeting);

When the expression inside ${} gets complex, factor it into a variable first:

// discouraged — legal but harder to debug
Konsol:Print("${x > 0 ? "pos" : x < 0 ? "neg" : "zero"}");

// preferred
Var:String sign = x > 0 ? "pos" : x < 0 ? "neg" : "zero";
Konsol:Print("sign: ${sign}");

Comments

// single-line comment

/* multi-line
   comment */

Preprocessor

#define

Plain word-boundary text substitution before tokenisation:

#define MAX 100
#define PI 3.14159

Var:Number limit = MAX;

#include

Two forms:

Syntax Behaviour
#include "name.ks" Always loads a KonsolScript file
#include "name" Plugin-first: searches for name.dll / .so / .dylib, then libname.dll / .so / .dylib, falls back to name.ks

KonsolScript file includes

#include "lib/utils.ks"
#include "helpers.ks"

Plugin includes

#include "kse_sample"     // loads kse_sample.dll / .so from plugin search paths

Plugin search order for #include "name":

  1. <directory of including file>/plugins/name.dll
  2. <main script root>/plugins/name.dll
  3. Dirs added via engine.addPluginPath() or the --plugin-path CLI flag
  4. MINKS_PLUGIN_PATH env var (colon-separated on Linux/macOS; semicolon on Windows)
  5. ~/.minks/plugins/ (Linux/macOS) — %APPDATA%\minks\plugins\ (Windows)
  6. ./plugins/

If the library is not found, minks falls back to searching for name.ks. If that also fails, a runtime error is raised. Each plugin name is include-guarded — including the same name twice is a no-op.

// lib/math_utils.ks
function clamp(Number val, Number lo, Number hi):Number {
    if (val < lo) { return lo; }
    if (val > hi) { return hi; }
    return val;
}

// main.ks
#include "lib/math_utils.ks"

Var:Number x = clamp(200, 0, 100);
Konsol:Print(x);   // 100

Function parameters may only be written with a type prefix:

function add2(Number a, Number b):Number  { return a + b; }   // typed

Entry point

If the script defines a main function, it is called automatically. Global variable declarations are still executed first to initialise globals.

Var:Number count = 0;   // initialised before main() runs

function main() {
    Konsol:Print(count);
}

If there is no main, all top-level statements execute in order.


Operators

Arithmetic

Op Meaning
+ Add / concat
- Subtract
* Multiply
/ Divide
% Modulo

Use Math:Power(base, exp, out) for exponentiation — ^ is now bitwise XOR.

+ on a string and any value concatenates both as strings.

Comparison

Op Meaning
== Equal
!= Not Equal
> Greater than
>= Greater than or equal
< Less than
<= Less than or equal to

Logical

Op Meaning
&& logical AND
` ` logical OR
! logical NOT

Bitwise

Op Meaning
& bitwise AND
^ bitwise XOR
`\ ` bitwise OR
~ bitwise NOT (complement)
<< left shift
>> right shift

Operands are truncated to 64-bit integer before the operation; the result is stored as Number.

Var:Number flags = 1 | 4;      // 5
Var:Number masked = flags & 3; // 1
Var:Number flipped = 12 ^ 10;  // 6  (1100 ^ 1010 = 0110)
Var:Number toggled = 5 ^ 5;    // 0  (x ^ x = 0)
Var:Number shifted = 1 << 8;   // 256
Var:Boolean isOdd = (n & 1) == 1;

Assignment

Op Meaning
= assign
-= subtract then assign
+= add then assign
*= multiply then assign
/= divide then assign
%= modulus then assign
++ increment (add 1)
-- decrement (subtract 1)

Ternary

condition ? valueIfTrue : valueIfFalse
Var:Number x = 10;
Var:String label = x > 0 ? "positive" : "non-positive";
Konsol:Print(label);        // positive

// works inside ${} interpolation too
Konsol:Print("sign: ${x > 0 ? "pos" : "neg"}");

Ternary has the lowest precedence after ||. Nesting is allowed — use parentheses for clarity.

Precedence (high to low)

Unary (`-`, `!`, `~`) → `* / %` → `+ -` → `<< >>` → comparison → `&` → `^` → `|` → `&&` → `||` → ternary `?:`


Control flow

if / else if / else

if (x > 10) {
    Konsol:Print("big");
} else if (x > 5) {
    Konsol:Print("medium");
} else {
    Konsol:Print("small");
}

while

Var:Number i = 0;
while (i < 5) {
    Konsol:Print(i);
    i++;
}

for

for (Number i = 0; i < 10; i++) {
    Konsol:Print(i);
}

The init clause can use Number declaration or a plain assignment. The increment supports ++, --, and = expr.

break

while (true) {
    if (done == true) { break; }
}

continue

Skips the rest of the current iteration and jumps to the next. In a for loop the increment still runs.

for (Number i = 0; i < 10; i++) {
    if (i % 2 == 0) { continue; }
    Konsol:Print(i);   // prints odd numbers only
}

switch / case / default

Evaluates an expression and executes the matching case block. Works with numbers and strings. Supports fall-through (omit break) and a default branch.

switch (expr) {
    case value1:
        // statements
        break;
    case value2:
        // statements
        break;
    default:
        // statements
}

break exits the switch. continue inside a switch propagates to the enclosing loop. Nested switches are supported.

Var:Number x = 2;
switch (x) {
    case 1:
        Konsol:Print("one");
        break;
    case 2:
        Konsol:Print("two");
        break;
    default:
        Konsol:Print("other");
}

Var:String day = "Wed";
switch (day) {
    case "Mon": Konsol:Print("Monday"); break;
    case "Wed": Konsol:Print("Wednesday"); break;
    default:    Konsol:Print("another day");
}

Fall-through example (no break on case 1):

switch (n) {
    case 1:
        Konsol:Print("one or two");
    case 2:
        Konsol:Print("two");
        break;
}

foreach

Iterate over a List, Array, Map (yields keys, sorted), or String (yields characters) without a manual index counter.

foreach (varName in collectionName) {
    // body — varName holds the current element / key
}

break and continue work the same as in for / while.

List:String fruits;
List:Push("apple", fruits);
List:Push("banana", fruits);
List:Push("cherry", fruits);

foreach (String item in fruits) {
    Konsol:Print(item);         // apple  banana  cherry
}

// Map — keys yielded in sorted order
Map:New cfg;
Map:Set("host", "localhost", cfg);
Map:Set("port", "8080", cfg);

Var:String val;
foreach (key in cfg) {
    Map:Get(cfg, key, val);
    Konsol:Print(key + " = " + val);
}

// String — one character per iteration
Var:String word = "hi";
foreach (ch in word) {
    Konsol:Print(ch);           // h  i
}

Functions

function greet(String name) {
    Konsol:Print("Hello, " + name);
}

function add(Number a, Number b) : Number {
    return a + b;
}

greet("world");
Var:Number result = add(3, 4);

Parameter types can be written as Number x or Var:Number x — both work.

Return type annotation (: Number, : String, : Boolean) is required for non-void functions.

Functions can be called in expressions:

Var:Number n = add(1, 2) * 10;

Recursion is supported. Each call gets its own local scope; locals are erased after the call.


Arrays

Fixed-size typed arrays. Size is set at declaration and does not change. Use List for dynamic resizable collections.

Declaration

Array:New name[size]:Type;

size can be a literal or a variable expression. Type is Number, String, or Boolean.

Array:New scores[5]:Number;
Array:New names[3]:String;
Array:New flags[4]:Boolean;

Var:Number n = 10;
Array:New buf[n]:Number;    // size from variable

Elements are zero-initialised: 0, "", or false.

Access

scores[0] = 95;
scores[1] = 87;
Konsol:Print(scores[0]);    // 95

Index reads out of bounds return 0 / "" / false. Index writes out of bounds are silently ignored.

Iterating

for (Number i = 0; i < 5; i++) {
    Konsol:Print(scores[i]);
}

foreach (s in scores) {
    Konsol:Print(s);
}

Note on performance

Array and List currently share the same internal Value storage, so there is no runtime performance difference between them. Array is kept as a distinct type to leave room for a typed contiguous-storage optimisation in a future release.


Structs (UDTs)

Simple data-only record types. No methods.

Var:Create(Point, Number x, Number y);

Var:Point p;
p.x = 10;
p.y = 20;
Konsol:Print(p.x + p.y);

Var:Create is processed at symbol-build time. Multiple fields separated by commas.


Classes

Full OOP with fields and methods.

Definition

Class:Create(Animal) {
    Var:String name;
    Var:Number age;

    function setName(String n) {
        name = n;
    }

    function speak() {
        Konsol:Print(name + " says hello");
    }

    function getAge() : Number {
        return age;
    }
}

Fields are declared with Var:Type fieldName; inside the body. Methods are declared with function. Inside a method, fields are accessible directly by name.

Instantiation and use

Class:Animal dog;       // single instance via Class:TypeName
Var:Animal cat;         // equivalent shorthand: Var:TypeName
dog.name = "Rex";
dog.age = 3;
dog.setName("Buddy");
dog.speak();

Var:Number a = dog.age;

Field access for reading uses instance.field. For writing, assign directly: instance.field = value.

Method calls use instance.method(args). Modified fields are written back to the instance after each method call.

Collections of class instances

List:Animal  pack;   // list of Animal instances
Array:New herd[10]:Animal;   // array of Animal instances

Multiple instances

Each Class:TypeName varName (or Var:TypeName varName) creates an independent instance with its own copy of the fields.


Error handling

try {
    throw "something went wrong";
} catch (String e) {
    Konsol:Print("Caught: " + e);
} finally {
    Konsol:Print("always runs");
}

Error output format

Runtime errors and uncaught exceptions print to stderr with the line number, the offending source line, and a caret (^) pointing to the exact token:

KonsolScript Error (line 5): undefined variable 'total'
  5 | Konsol:Print(total);
    |              ^^^^^

Uncaught throw exceptions show the same context, plus a full stack trace when the exception propagated through one or more function calls:

Uncaught exception (line 12): something went wrong
  12 | throw "something went wrong";

Stack trace:
  at inner (called from line 8)
  at outer (called from line 3)
  at main

The trace lists frames innermost-first. Each frame names the function and the line in the caller where the call was made.

Calling a method on a class that was never registered — whether a plugin that wasn't loaded or a .ks file that wasn't included — raises a clear error rather than silently doing nothing:

Runtime error at line 3: unknown class 'Sample' — missing #include?

Built-in modules

All stdlib calls use the Class:Method(args) syntax. Methods that return a value follow the ByRef convention: the caller pre-declares a receiver variable and passes it as the last argument.

Var:Number h;
Time:GetHour(h);
Konsol:Print(h);    // current hour

All stdlib modules follow the ByRef convention — there are no _ret exceptions.

Konsol

Call Effect
Konsol:Print(expr) Print one expression, then newline. Use + or ${} to combine values.
Konsol:Message(expr) Alias for Print
Konsol:Log(expr) Print with [LOG] prefix
Konsol:Input(varName) Read a line from stdin into varName
Konsol:Input("prompt", varName) Print prompt then read
Konsol:Exit() Exit with code 0
Konsol:Exit(code) Exit with given code
Konsol:Chr(n, outVar) / Konsol:Char(n, outVar) ASCII char for code noutVar
Konsol:Asc(str, outVar) / Konsol:Ascii(str, outVar) ASCII code of first char of stroutVar
Konsol:IsNumeric(expr, outVar) true if value is a valid number → outVar
Konsol:Delay(ms) Sleep for ms milliseconds
Konsol:Run(cmd, outVar) Run shell command, capture stdout → outVar

Konsol:Input stores a Number if the input looks like one, otherwise a String.

Passing more than one argument to Print, Message, or Log is a syntax error.

Math

All methods are ByRef — pre-declare a receiver variable and pass it as the last argument.

Call Result
Math:Abs(n, out) / Math:Absolute(n, out) Absolute value
Math:Round(n, out) Round to nearest integer
Math:Floor(n, out) Floor
Math:Ceil(n, out) / Math:Ceiling(n, out) Ceiling
Math:Sqrt(n, out) / Math:SquareRoot(n, out) Square root
Math:Sin(n, out) / Math:Sine(n, out) Sine (radians)
Math:Cos(n, out) / Math:Cosine(n, out) Cosine (radians)
Math:Tan(n, out) / Math:Tangent(n, out) Tangent (radians)
Math:Log(n, out) Natural logarithm
Math:Exp(n, out) e raised to the power n
Math:Power(base, exp, out) / Math:Pow base raised to the power exp
Math:Min(list, out) Minimum value in a List:Number
Math:Max(list, out) Maximum value in a List:Number
Math:Clamp(n, min, max, out) Clamp n to the range [min, max]
Math:Lerp(a, b, t, out) Linear interpolation: a + t * (b - a)
Math:Random(out) Random float in [0, 1)
Math:Random(max, out) Random float in [0, max)
Math:Seed(n) Seed the RNG with integer n; call before Math:Random for reproducible sequences

String

All methods are ByRef — pre-declare a receiver variable and pass it as the last argument.

Call Result
String:Len(s, out) / String:Length(s, out) Length of s
String:Left(s, n, out) First n characters
String:Right(s, n, out) Last n characters
String:Mid(s, start, len, out) / String:Substring(s, start, len, out) Substring; start is 1-based
String:Replace(s, from, to, out) Replace all occurrences of from with to
String:Trim(s, out) Strip leading/trailing whitespace
String:Upper(s, out) Uppercase
String:Lower(s, out) Lowercase
String:Reverse(s, out) Reverse characters
String:Compare(a, b, out) -1 / 0 / 1 lexicographic
String:Contains(s, sub, out) true if sub found in s
String:Split(s, delim, out) Split into pre-declared List:String out
String:Join(delim, s1, s2, ..., out) Join strings with delimiter
List:String part;
String:Split("a,b,c", ",", part);
Var:Number sz;
List:Size(part, sz);       // 3
Var:String p0; Var:String p1; Var:String p2;
List:Get(part, 0, p0);     // a
List:Get(part, 1, p1);     // b
List:Get(part, 2, p2);     // c

File

File handles are integer IDs you choose (e.g. 1, 2).

Call Effect
File:Open(handle, path, mode) Open file; mode is "r", "w", or "a"
File:Close(handle) Close one file
File:CloseAll() Close all open files
File:Write(handle, expr) Write string to file
File:Read(handle, outVar) Read next whitespace-delimited token → outVar
File:ReadLine(handle, outVar) Read next line → outVar
File:ReadAll(handle, outVar) Read entire remaining file content → outVar
File:EOF(handle, outVar) / File:EndOfFile(handle, outVar) true if at end of file → outVar
File:Exists(path, outVar) true if file exists → outVar
File:Delete(path, outVar) Delete file; true on success → outVar
File:Open(1, "data.txt", "w");
File:Write(1, "hello\n");
File:Close(1);

Var:Boolean exists;
File:Exists("data.txt", exists);
Konsol:Print(exists);   // true

// Read entire file at once
Var:String content;
File:Open(2, "data.txt", "r");
File:ReadAll(2, content);
File:Close(2);
Konsol:Print(content);  // hello

// Read line by line
Var:Boolean eof;
Var:String line;
File:Open(3, "data.txt", "r");
File:EOF(3, eof);
while (eof == false) {
    File:ReadLine(3, line);
    Konsol:Print(line);
    File:EOF(3, eof);
}
File:Close(3);

Time

Call Effect
Time:GetHour(outVar) Current hour (0–23) → outVar
Time:GetMinute(outVar) Current minute → outVar
Time:GetSecond(outVar) Current second → outVar
Time:GetDay(outVar) Day of month → outVar
Time:GetMonth(outVar) Month (1–12) → outVar
Time:GetYear(outVar) Full year (e.g. 2025) → outVar
Time:GetTimer(outVar) Seconds since process epoch (double, for timing) → outVar
Var:Number h;
Var:Number m;
Var:Number s;
Time:GetHour(h);
Time:GetMinute(m);
Time:GetSecond(s);
Konsol:Print("${h}:${m}:${s}");

Var:Number t0;
Var:Number t1;
Time:GetTimer(t0);
// ... work ...
Time:GetTimer(t1);
Konsol:Print("elapsed: ${t1 - t0}s");

List

Dynamic resizable array with a declared element type.

Call Effect
List:Number name Create an empty list of numbers
List:String name Create an empty list of strings
List:Boolean name Create an empty list of booleans
List:ClassName name Create a list of class instances
List:Push(val, name) Append a value
List:Pop(name, outVar) Remove and return last element → outVar
List:Get(name, index, outVar) Read element at 0-based index → outVar
List:Set(name, index, val) Write element at 0-based index
List:Size(name, outVar) Number of elements → outVar
List:Remove(name, index) Remove element at index (shifts later elements)
List:Contains(name, val, outVar) true if value found → outVar
List:Clear(name) Remove all elements
List:Sort(name) Sort in-place (numbers numerically, strings lexicographically)
List:Sort(name, cmpFn) Sort in-place with comparator: cmpFn(a, b) : Boolean
List:Filter(src, predFn, dst) Fill dst with elements where predFn(x) : Boolean is true
List:Map(src, xfmFn, dst) Fill dst with xfmFn(x) : Type applied to each element
List:Reduce(src, redFn, init, outVar) Fold: redFn(acc, x) : Type → final accumulator → outVar

dst and outVar must be pre-declared. Filter/Map clear dst before filling it. Callback functions must use typed parameters and declare a return type (e.g. function fn(Number x) : Boolean).

List:Number scores;
List:Push(10, scores);
List:Push(20, scores);
Var:Number sz;
List:Size(scores, sz);
Konsol:Print(sz);         // 2
Var:Number val;
List:Get(scores, 0, val);
Konsol:Print(val);        // 10

Map

String-keyed dictionary.

Call Effect
Map:New name Create an empty map
Map:Set(key, val, name) Insert or overwrite a key
Map:Get(name, key, outVar) Read value → outVar (0 if key absent)
Map:Has(name, key, outVar) true if key exists → outVar
Map:Remove(name, key) Delete a key
Map:Size(name, outVar) Number of entries → outVar
Map:Keys(name, listName) Populate a pre-declared List:String with all keys (order unspecified)
Map:Clear(name) Remove all entries
Map:SetList(key, listName, name) Store a copy of listName under key
Map:GetList(name, key, listName) Retrieve list stored at key into listName
Map:SetMap(key, innerName, name) Store a copy of map innerName under key
Map:GetMap(name, key, innerName) Retrieve inner map at key into innerName
Map:New scores;
Map:Set("Alice", 95, scores);
Map:Set("Bob", 82, scores);

Var:Number val;
Map:Get(scores, "Alice", val);
Konsol:Print(val);        // 95

Var:Boolean found;
Map:Has(scores, "Carol", found);
Konsol:Print(found);      // false

Var:Number sz;
Map:Size(scores, sz);
Konsol:Print(sz);         // 2

List:String keys;
Map:Keys(scores, keys);
List:Size(keys, sz);
Konsol:Print(sz);         // 2

Json

Parse, inspect, build, and serialize JSON. Documents are named and held in memory until freed. Paths use dot notation; numeric parts index arrays. Use "" for the root node.

Call Effect
Json:NewObject name Create an empty {} document (no parens)
Json:NewArray name Create an empty [] document (no parens)
Json:Parse(name, str) Parse JSON string into named doc
Json:Free(name) Release document
Json:Stringify(name, path, outVar) Serialize doc or subtree → outVar
Json:Get(name, path, outVar) Read value → outVar (typed: Number/String/Boolean; objects/arrays → JSON string)
Json:Has(name, path, outVar) true if path exists → outVar
Json:Type(name, path, outVar) "object" "array" "string" "number" "bool" "null"outVar
Json:Length(name, path, outVar) Element count for array or object → outVar
Json:Keys(name, path, listName) Object keys → pre-declared List:String
Json:Set(name, path, value) Write scalar at path (creates keys as needed)
Json:Push(name, path, value) Append scalar to array at path ("" = root)

Path examples: "name", "user.age", "items.0", "data.scores.2". Use "" for root.

Var:Number status;
Var:String name;
Var:String typeStr;
Var:Number len;
Var:String out;

// Parse and read
Json:Parse(resp, "{\"status\":200,\"user\":{\"name\":\"Alice\",\"scores\":[95,82]}}");
Json:Get(resp, "status", status);
Konsol:Print(status);                // 200
Json:Get(resp, "user.name", name);
Konsol:Print(name);                  // Alice
Json:Type(resp, "user.scores", typeStr);
Konsol:Print(typeStr);               // array
Json:Length(resp, "user.scores", len);
Konsol:Print(len);                   // 2

// Build and serialize
Json:NewObject cfg;
Json:Set(cfg, "host", "localhost");
Json:Set(cfg, "port", 8080);
Json:Stringify(cfg, "", out);
Konsol:Print(out);                   // {"host":"localhost","port":8080}

// Build array
Json:NewArray tags;
Json:Push(tags, "", "fast");
Json:Push(tags, "", "safe");
Json:Stringify(tags, "", out);
Konsol:Print(out);                   // ["fast","safe"]

Json:Free(resp);
Json:Free(cfg);
Json:Free(tags);

Path

Pure string-based path manipulation plus filesystem queries.

Call Effect
Path:Join(a, b, outVar) Joined path with native separator → outVar
Path:DirName(p, outVar) / Path:DirectoryName(p, outVar) Parent directory ("." if none) → outVar
Path:BaseName(p, outVar) / Path:FileName(p, outVar) Filename including extension → outVar
Path:Stem(p, outVar) Filename without extension → outVar
Path:Extension(p, outVar) Extension with dot, e.g. ".ks"outVar
Path:Exists(p, outVar) true if path exists → outVar
Path:IsFile(p, outVar) true if regular file → outVar
Path:IsDir(p, outVar) / Path:IsDirectory(p, outVar) true if directory → outVar
Path:Absolute(p, outVar) Absolute path string → outVar
Var:String result;
Path:Join("src", "main.ks", result);
Konsol:Print(result);           // src/main.ks  (or src\main.ks on Windows)

Path:Extension("readme.md", result);
Konsol:Print(result);           // .md

Var:Boolean exists;
Path:Exists("script.ks", exists);
Konsol:Print(exists);           // true

OS

Operating-system services: working directory, directory listing, environment variables, and process control.

Call Effect
OS:Args(listName) Populate a pre-declared List:String with the positional script arguments
OS:Cwd(outVar) / OS:CurrentDirectory(outVar) Current working directory → outVar
OS:ChDir(p) / OS:ChangeDirectory(p) Change working directory
OS:ListDir(p, listName) / OS:ListDirectory(p, listName) Populate a pre-declared List:String with filenames (sorted)
OS:MkDir(p, outVar) / OS:MakeDirectory(p, outVar) Create directory; true if newly created → outVar
OS:MkDirs(p, outVar) / OS:MakeDirectories(p, outVar) Create directory tree; true if any dir was created → outVar
OS:Remove(p, outVar) Remove file or empty dir; true if removed → outVar
OS:Rename(old, new) Rename/move a file or directory —
OS:GetEnv(name, outVar) / OS:GetEnvironment(name, outVar) Environment variable value ("" if not set) → outVar
OS:SetEnv(name, value) / OS:SetEnvironment(name, value) Set environment variable
OS:System(cmd, outVar) Run shell command; exit code → outVar
OS:Exit(code) Terminate the process —
// Script arguments: minks script.ks hello world
List:String args;
OS:Args(args);
Var:Number count;
List:Size(args, count);
Konsol:Print(count);            // 2
Var:String first;
List:Get(args, 0, first);
Konsol:Print(first);            // hello

Var:String cwd;
OS:Cwd(cwd);
Konsol:Print(cwd);              // /home/user/project  (or C:\… on Windows)

Var:String greeting;
OS:SetEnv("GREETING", "hi");
OS:GetEnv("GREETING", greeting);
Konsol:Print(greeting);         // hi

Var:Boolean ok;
OS:MkDir("output", ok);

List:String entries;
OS:ListDir(".", entries);
Var:Number sz;
List:Size(entries, sz);
Konsol:Print(sz);               // number of entries in current directory

Regex

Pattern matching and capture groups using ECMAScript regex syntax (wraps std::regex). Patterns are compiled and cached on first use; a bad pattern returns false / "" / empty list rather than throwing.

Call Effect
Regex:Test(pattern, str, outVar) true if pattern matches anywhere in stroutVar
Regex:Match(pattern, str, outVar) / Regex:IsMatch true if pattern matches the entire stroutVar
Regex:Find(pattern, str, outVar) First match text, or ""outVar
Regex:Replace(pattern, str, repl, outVar) All matches replaced with reploutVar
Regex:Groups(pattern, str, listName) Populate pre-declared List:String; index 0 = full match, 1+ = capture groups
Var:Boolean found;
Regex:Test("[0-9]+", "abc 42 def", found);
Konsol:Print(found);        // true

Var:String match;
Regex:Find("[a-z]+", "Hello World", match);
Konsol:Print(match);        // ello

Var:String result;
Regex:Replace("\\s+", "hello   world", " ", result);
Konsol:Print(result);       // hello world

List:String groups;
Regex:Groups("(\\w+)@(\\w+)", "user@host", groups);
// groups[0] = "user@host", groups[1] = "user", groups[2] = "host"
Var:String g0; Var:String g1; Var:String g2;
List:Get(groups, 0, g0);
List:Get(groups, 1, g1);
List:Get(groups, 2, g2);
Konsol:Print("${g0} → ${g1} at ${g2}");

Date

Unix-timestamp-based date arithmetic and formatting. All timestamps are Number (seconds since the Unix epoch, as returned by Date:Now).

Supported strftime format specifiers: %Y year · %m month · %d day · %H hour · %M minute · %S second.

Call Effect
Date:Now(outVar) Current Unix timestamp → outVar
Date:Format(ts, fmt, outVar) Format timestamp using strftime-style format → outVar
Date:Parse(str, fmt, outVar) Parse date string to timestamp → outVar (-1 on failure)
Date:Year(ts, outVar) Full year (e.g. 2025) → outVar
Date:Month(ts, outVar) Month 1–12 → outVar
Date:Day(ts, outVar) Day of month 1–31 → outVar
Date:Hour(ts, outVar) Hour 0–23 → outVar
Date:Minute(ts, outVar) Minute 0–59 → outVar
Date:Second(ts, outVar) Second 0–59 → outVar
Date:Add(ts, days, outVar) / Date:AddDays Timestamp + days × 86400outVar
Date:Diff(ts1, ts2, outVar) / Date:DaysBetween (ts2 − ts1) in whole days → outVar
Var:Number ts;
Date:Now(ts);

Var:String formatted;
Date:Format(ts, "%Y-%m-%d", formatted);
Konsol:Print(formatted);        // e.g. 2025-08-01

Var:Number year; Var:Number month; Var:Number day;
Date:Year(ts, year);
Date:Month(ts, month);
Date:Day(ts, day);
Konsol:Print("${year}-${month}-${day}");

// Add 7 days
Var:Number nextWeek;
Date:Add(ts, 7, nextWeek);
Date:Format(nextWeek, "%Y-%m-%d", formatted);
Konsol:Print(formatted);

// Days between two timestamps
Var:Number past;
Date:Parse("2025-01-01", "%Y-%m-%d", past);
Var:Number diff;
Date:Diff(past, ts, diff);
Konsol:Print("${diff} days since Jan 1");

Hash

Hashing and Base64 encoding. All methods are ByRef. Implementations are self-contained — no external library required.

Call Effect
Hash:MD5(str, outVar) MD5 digest → 32-char lowercase hex → outVar
Hash:SHA256(str, outVar) SHA-256 digest → 64-char lowercase hex → outVar
Hash:Base64Encode(str, outVar) Base64-encode string → outVar
Hash:Base64Decode(str, outVar) Base64-decode string → outVar
Var:String h;
Hash:MD5("hello", h);
Konsol:Print(h);        // 5d41402abc4b2a76b9719d911017c592

Hash:SHA256("hello", h);
Konsol:Print(h);        // 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824

Var:String enc;
Hash:Base64Encode("hello world", enc);
Konsol:Print(enc);      // aGVsbG8gd29ybGQ=

Var:String dec;
Hash:Base64Decode(enc, dec);
Konsol:Print(dec);      // hello world

CSV

Parse, build, and serialize RFC 4180 CSV data. Documents are named and held in memory until freed. Rows and columns are 0-based.

Call Effect
CSV:Parse(name, str) Parse CSV string into named document (comma delimiter)
CSV:Parse(name, str, delim) Parse with custom single-character delimiter
CSV:Get(name, row, col, outVar) Read cell → outVar ("" if out of range)
CSV:Set(name, row, col, val) Write cell; extends document as needed
CSV:Rows(name, outVar) / CSV:RowCount Row count → outVar
CSV:Cols(name, row, outVar) / CSV:ColumnCount Column count for given row → outVar
CSV:Stringify(name, outVar) / CSV:ToString Serialize to CSV string → outVar
CSV:Stringify(name, delim, outVar) Serialize with custom delimiter
CSV:Free(name) / CSV:Release Release document
Var:String src = "name,age\nAlice,30\nBob,25";
CSV:Parse(data, src);

Var:Number rows;
CSV:Rows(data, rows);
Konsol:Print(rows);     // 3

Var:String cell;
CSV:Get(data, 1, 0, cell);
Konsol:Print(cell);     // Alice

// Build from scratch
CSV:Set(out, 0, 0, "product");
CSV:Set(out, 0, 1, "price");
CSV:Set(out, 1, 0, "apple");
CSV:Set(out, 1, 1, "1.20");

Var:String csv;
CSV:Stringify(out, csv);
Konsol:Print(csv);

CSV:Free(data);
CSV:Free(out);

Adding a module

Modules are registered as class handlers — the same mechanism the built-in classes use. You do not need to modify the parser.

1. Create kse_mymodule.cpp

#include "kse.hpp"

static size_t mymodule_handler(Engine& e, size_t i) {
    ++i; // skip "MyModule"
    if (e.tokenText(i) != ":") return e.skipOptSemi(i);
    ++i; // skip ":"

    std::string method = e.tokenText(i);
    ++i; // skip method name
    if (e.tokenText(i) == "(") ++i; // skip (

    if (method == "Hello") {
        // read input argument, then outVar (ByRef convention)
        Value v = e.evalExpr(i);
        if (e.tokenText(i) == ",") ++i; // skip comma
        std::string outVar = e.tokenText(i); ++i; // read receiver name
        if (e.tokenText(i) == ")") ++i; // skip )

        std::string msg = "Hello from MyModule: " + toString(v);
        e.setVar(outVar, Value(msg));
        return e.skipOptSemi(i);
    }

    return e.skipOptSemi(i);
}

void register_mymodule(Engine& e) {
    e.registerClass("MyModule", mymodule_handler);
}

2. Register it in kse.cpp

Declare the registrar at the top:

void register_mymodule(Engine&);

Then call it in the Engine constructor:

Engine::Engine() {
    scopedVars_[""];
    register_math(*this);
    // ... existing registrations ...
    register_mymodule(*this);   // add this line
}

3. Add to Makefile

SRCS = kse.cpp kse_konsol.cpp kse_math.cpp kse_string.cpp \
       kse_file.cpp kse_time.cpp kse_class.cpp kse_try.cpp \
       kse_mymodule.cpp

4. Use it in a script

Var:String result;
MyModule:Hello("world", result);
Konsol:Print(result);

Engine API available to handlers

e.tokenText(i)          // text of token at i
e.tokenCmd(i)           // command/type id of token at i
e.tokenLine(i)          // source line number
e.tokenCount()          // total token count

e.evalExpr(i)           // evaluate expression starting at i; advances i past it
e.setVar("name", v)     // set a variable (use the caller's outVar name for return values)
e.getVar("name")        // get a variable's Value
e.hasVar("name")        // check existence

e.skipOptSemi(i)        // call as final return; skips ; if present
e.skipPast("}", i)      // scan forward to given token, return its index

// Value helpers (free functions from kse_types.hpp)
toString(v)             // Value → std::string
toDouble(v)             // Value → double
toBool(v)               // Value → bool
Value("text")           // make a string Value
Value(3.14)             // make a number Value
Value(true)             // make a bool Value

Multiple methods, multiple arguments

if (method == "Add") {
    Value a = e.evalExpr(i);
    if (e.tokenText(i) == ",") ++i;
    Value b = e.evalExpr(i);
    if (e.tokenText(i) == ",") ++i;
    std::string outVar = e.tokenText(i); ++i; // receiver
    if (e.tokenText(i) == ")") ++i;
    e.setVar(outVar, Value(toDouble(a) + toDouble(b)));
    return e.skipOptSemi(i);
}

The pattern: eval each input argument (leaving i at the following delimiter), skip commas between arguments, read the last token as the receiver variable name, skip ), write the result with e.setVar(outVar, ...).

Exposing the module as a script-side class

If you want script code to instantiate your module type like a class (field storage, method dispatch), register it via Class:Create at runtime from a .ks file, or directly manipulate e.classDefs() and e.classInsts() from C++ — the same maps the built-in Class: keyword uses.


Plugin system

Plugins are shared libraries (.dll on Windows, .so on Linux/macOS) that extend KonsolScript with new module-style classes callable with the same MyPlugin:Method(args) syntax as built-in modules. No recompile of minks is needed.

Loading plugins from a script

The recommended way — load plugins directly in the script that uses them:

#include "kse_mysql"     // finds kse_mysql.dll/.so in the plugin search path
#include "kse_audio"

See the #include section for the full search-path order.

Managing installed plugins

minks install <name>          install plugin from current directory
minks remove  <name>          delete it from the user plugin dir
minks list                    list all installed plugins

minks install searches the current working directory for <name>.dll / .so / .dylib, then lib<name>.dll / .so / .dylib. Run it from inside the directory where you built the plugin.

Installed plugins live in ~/.minks/plugins/ (Linux/macOS) or %APPDATA%\minks\plugins\ (Windows) and are available to all scripts without any flags.

cd sample_plugin
minks install kse_sample
minks list
# kse_sample

Adding a search directory (CLI)

minks --plugin-path ./my_libs script.ks

--plugin-path can be repeated. Added dirs are checked before the defaults.

Low-level explicit load (legacy / debug)

minks --plugin ./myplugin.dll script.ks
minks --plugin lib1.dll --plugin lib2.dll script.ks
minks -p ./kse_mysql.so -p ./kse_audio.so game.ks

--plugin (or -p) can be repeated. Libraries are loaded in the order given before the script runs. Prefer #include "name" in scripts over this flag for portability.

Writing a plugin — the PluginClass builder

Include minks_plugin.h (ships with minks). The PluginClass RAII fluent builder handles all token-stream parsing internally; you only write lambdas for each method.

#include "minks_plugin.h"
#include <iostream>

extern "C" void minks_register(Engine& e) {
    PluginClass(e, "MyLib")

        // .byRefMethod — last script arg is the outVar; SDK writes result there.
        // args[] inside the lambda contains only the INPUT arguments.
        .byRefMethod("Greet", [](auto args) -> Value {
            return Value(std::string("Hello, ") + toString(args[0]) + "!");
        })

        // .voidMethod — side-effects only; no receiver variable needed
        .voidMethod("Log", [](auto args) {
            std::cout << toString(args[0]) << "\n";
        })

        .byRefMethod("Add", [](auto args) -> Value {
            return Value(toDouble(args[0]) + toDouble(args[1]));
        });

    // PluginClass destructor fires here, registering "MyLib" with the engine.
}

From KonsolScript:

Var:String greeting;
MyLib:Greet("world", greeting);
Konsol:Print(greeting);         // Hello, world!

MyLib:Log("debug info");        // void — no receiver needed

Var:Number sum;
MyLib:Add(3, 7, sum);
Konsol:Print(sum);              // 10

Builder API

Builder call Output convention Description
.byRefMethod("Name", fn) Last script arg is the outVar fn receives input args; its return Value is written to the caller's pre-declared variable
.voidMethod("Name", fn) None fn returns nothing; no variable is written

Plugins have no _ret path. Every non-void plugin method must use .byRefMethod — there is no legacy method() builder.

Argument extraction

Inside your lambda, args is std::vector<Value>. Use these free helpers (from kse_types.hpp):

Helper Type extracted
toDouble(args[N]) Number (double)
toString(args[N]) String
toBool(args[N]) Boolean

Return values: Value(double), Value(std::string), Value(bool).

Build commands

# Windows
g++ -std=c++17 -shared -I<minks_dir> -o myplugin.dll myplugin.cpp

# Linux / macOS
g++ -std=c++17 -shared -fPIC -I<minks_dir> -o myplugin.so myplugin.cpp

Link additional libraries after myplugin.cpp:

g++ -std=c++17 -shared -fPIC -I../minks -o kse_mysql.so kse_mysql.cpp -lmariadb

<minks_dir> is the directory containing minks_plugin.h, kse.hpp, and kse_types.hpp.

Working example — sample plugin

sample_plugin/kse_sample.cpp exposes a Sample class with six byRefMethod entries and one voidMethod. Build, install, and use:

# from sample_plugin/
make
minks install kse_sample   # searches cwd for kse_sample.dll/.so

# In your .ks script:
#include "kse_sample"

Or for a quick test without installing (explicit load by path):

minks --plugin sample_plugin\kse_sample.dll tests\20_plugin.ks   # Windows
minks --plugin ./sample_plugin/kse_sample.so tests/20_plugin.ks  # Linux

Expected output (abridged):

=== Sample Plugin ===
plugin version: kse_sample 1.0.0
[Sample] Hello, world!
Add(3, 4)      = 7
Multiply(6, 7) = 42
(10+20)*2      = 60
Upper          = HELLO FROM PLUGIN
Repeat(ab, 5)  = ababababab
--- IsPrime ---
2 is prime
3 is prime
...

Plugin typed exceptions

Plugins can declare named exception classes that scripts catch by type — the same try / catch / finally syntax used for built-in exceptions.

A plugin registers its exception type once in minks_register:

registerPluginException(e, "MySQLException");

Then throws it from any method:

throwPluginException("MySQLException", mysql_error(conn), (double)mysql_errno(conn));

The caught instance exposes two fields:

Field Type Description
e.message String Human-readable error description
e.code Number Numeric error code (0 if unused)

From KonsolScript:

try {
    MySQL:Connect("localhost", "root", "wrongpass", "mydb", db);
} catch (MySQLException e) {
    Konsol:Print("MySQL error ${e.code}: ${e.message}");
} catch (Exception e) {
    Konsol:Print("Unexpected error: ${e.message}");
}

catch (Exception e) is the generic catch-all that matches any typed plugin exception. More specific types (MySQLException, CurlException, etc.) must be named exactly.

IDE false-positive on minks_plugin.h

Editors using clangd may flag 'kse.hpp' file not found or 'minks_plugin.h' file not found inside sample_app/ or sample_plugin/ source files. This is a false positive — clangd does not read those directories' Makefile and therefore does not pick up the -I.. flag. The make build compiles correctly. Add a compile_commands.json or a .clangd config with CompileFlags: Add: [-I..] to suppress it.


Available plugins

Plugins ship as separate builds alongside minks. Install with minks install <name> — the package manager automatically installs any required system library via apt, pacman, brew, or MSYS2 pacman depending on the platform.

All plugin methods follow the ByRef convention. Errors on open/connect throw a typed exception catchable in try / catch.


curl — HTTP client

Requires: libcurl (auto-installed)

#include "curl"

Curl:SetHeader("Authorization", "Bearer mytoken");
Curl:SetHeader("Accept", "application/json");

Var:String body;
Var:Number status;

try {
    Curl:Get("https://api.example.com/users", body);
} catch (CurlException e) {
    Konsol:Print("curl error: ${e.message}");
}

Curl:Status(status);
Konsol:Print("${status}: ${body}");

Curl:ClearHeaders();
Method Args Out Description
Curl:Get url String HTTP GET → response body
Curl:Post url, body String HTTP POST with body
Curl:Put url, body String HTTP PUT with body
Curl:Delete url String HTTP DELETE
Curl:Status Number HTTP status code of last request
Curl:SetHeader key, value void Set a persistent request header
Curl:ClearHeaders void Remove all set headers
Curl:SetTimeout seconds void Request timeout (default: 30)

sqlite — SQLite database

Requires: nothing — SQLite is compiled directly into the plugin (amalgamation)

#include "sqlite"

Var:Number db;
Var:Boolean ok;
Var:String rows;
Var:Number n;

try {
    SQLite:Open("data.db", db);
} catch (SQLiteException e) {
    Konsol:Print("open failed: ${e.message}");
}

SQLite:Exec(db, "CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)", ok);
SQLite:Exec(db, "INSERT INTO users (name) VALUES ('Alice')", ok);
SQLite:LastInsertId(db, n);
Konsol:Print("inserted id: ${n}");

SQLite:Query(db, "SELECT * FROM users", rows);
Konsol:Print(rows);   // [{"id":1,"name":"Alice"}]

SQLite:Close(db);

Query results are returned as a JSON string — parse with the Json: module.

Method Args Out Description
SQLite:Open path Number Open or create database; returns handle. Throws SQLiteException on failure
SQLite:Close handle void Close the database
SQLite:Exec handle, sql Boolean Run DDL / DML; true = success
SQLite:Query handle, sql String SELECT → JSON array of row objects
SQLite:QueryOne handle, sql String SELECT → first row as JSON object, or ""
SQLite:RowsAffected handle Number Rows changed by last Exec
SQLite:LastInsertId handle Number Row ID of last INSERT
SQLite:Error handle String Last error message, "" if none

mysql — MySQL database

Requires: libmysqlclient (auto-installed)

#include "mysql"

Var:Number db;
Var:Boolean ok;
Var:String rows;

try {
    MySQL:Connect("localhost", "root", "secret", "mydb", db);
} catch (MySQLException e) {
    Konsol:Print("connect failed (${e.code}): ${e.message}");
}

MySQL:Exec(db, "INSERT INTO users (name) VALUES ('Alice')", ok);

Var:Number lastId;
MySQL:LastInsertId(db, lastId);

MySQL:Query(db, "SELECT * FROM users ORDER BY id", rows);
Konsol:Print(rows);   // [{"id":1,"name":"Alice"}]

MySQL:Close(db);

Query results are returned as a JSON string — parse with the Json: module.

Method Args Out Description
MySQL:Connect host, user, pass, db Number Connect; returns handle. Throws MySQLException on failure
MySQL:ConnectPort host, port, user, pass, db Number Connect with explicit port
MySQL:Close handle void Close the connection
MySQL:Exec handle, sql Boolean Run DDL / DML; true = success
MySQL:Query handle, sql String SELECT → JSON array of row objects
MySQL:QueryOne handle, sql String SELECT → first row as JSON object, or ""
MySQL:RowsAffected handle Number Rows changed by last Exec
MySQL:LastInsertId handle Number Auto-increment ID of last INSERT
MySQL:Error handle String Last error message, "" if none

zip — Zip archives

Requires: libzip (auto-installed)

#include "zip"

Var:Number z;
Var:Boolean ok;
Var:String content;
Var:Number n;

try {
    Zip:Open("archive.zip", z);
} catch (ZipException e) {
    Konsol:Print("open failed: ${e.message}");
}

Zip:AddText(z, "hello.txt", "Hello from minks!", ok);
Zip:AddFile(z, "data/config.json", "config.json", ok);
Zip:AddDir(z, "logs/", ok);
Zip:Count(z, n);
Konsol:Print("entries: ${n}");
Zip:Close(z);   // writes to disk

// Read back
Zip:Open("archive.zip", z);
Zip:Read(z, "hello.txt", content);
Konsol:Print(content);          // Hello from minks!
Zip:Extract(z, "hello.txt", "extracted.txt", ok);
Zip:Close(z);
Method Args Out Description
Zip:Open path Number Open or create archive; returns handle. Throws ZipException on failure
Zip:Close handle void Write changes to disk and release handle
Zip:Discard handle void Close without saving changes
Zip:AddFile handle, entryName, filePath Boolean Add file from disk (read lazily at Close)
Zip:AddText handle, entryName, content Boolean Add a string as a zip entry
Zip:AddDir handle, dirName Boolean Add a directory entry
Zip:Count handle Number Number of entries in the archive
Zip:Name handle, index String Entry name at zero-based index
Zip:Read handle, entryName String Read entry contents as a string
Zip:Extract handle, entryName, destPath Boolean Extract entry to a file on disk
Zip:Error handle String Last error message, "" if none

Embedding API

libkonsolscript is the interpreter as a shared library. Any C++17 application can link against it to make itself scriptable — no dependency on the minks CLI.

libkonsolscript.dll   (Windows)
libkonsolscript.so    (Linux)
libkonsolscript.dylib (macOS)
libkonsolscript.a     (all platforms — static, built with `make static`)

The public API surface is two headers:

Header Contents
kse.hpp Engine class — create, configure, load, run, query
kse_types.hpp Value variant, Token, ClassDef, collection types, toString(), toDouble()

Linking

Makefile (Windows/MinGW):

CXX      = g++
CXXFLAGS = -std=c++17 -I/path/to/minks

sample_app.exe: main.cpp
    $(CXX) $(CXXFLAGS) -o $@ main.cpp -L/path/to/minks -lkonsolscript
    # Copy libkonsolscript.dll next to the exe so Windows can find it
    cmd /C copy /Y /path/to/minks/libkonsolscript.dll .

Makefile (Linux):

CXX      = g++
CXXFLAGS = -std=c++17 -I/path/to/minks

sample_app: main.cpp
    $(CXX) $(CXXFLAGS) -o $@ main.cpp \
        -L/path/to/minks -lkonsolscript -Wl,-rpath,'$$ORIGIN'
    # rpath=$ORIGIN means libkonsolscript.so is found next to the binary

macOS: replace -Wl,-rpath,'$$ORIGIN' with -Wl,-rpath,@loader_path.

Engine lifecycle

#include "kse.hpp"

Engine engine;          // ctor registers all stdlib modules
engine.setDebug(true);  // optional: trace each statement to stderr

The Engine constructor registers all built-in modules (Konsol, Math, String, File, Time, List, Map, Json, OS, Hash, Date, CSV). No extra initialisation is needed.

Running scripts

Inline / REPL-style — append a source fragment and execute immediately:

engine.eval(R"(
    Var:Number x = 6 * 7;
    Konsol:Print(x);
)");

eval() preserves engine state between calls, so variables declared in one call are visible in the next. This is the same mode the minks REPL uses.

File-style — parse a complete script, then run it:

std::ifstream f("game.ks");
std::ostringstream ss; ss << f.rdbuf();

engine.loadScript(ss.str(),
    "/path/to/scripts",   // base dir for #include resolution
    "game.ks");           // filename shown in error messages

engine.run();

Passing data between host and script

Host → script (set a global before running the script):

engine.setVar("player_name",  Value(std::string("Alice")));
engine.setVar("player_score", Value(0.0));
engine.setVar("debug_mode",   Value(true));

Script → host (read a global after run() or eval() returns):

Value score = engine.getVar("player_score");
double s = toDouble(score);

Value status = engine.getVar("status");
std::string st = toString(status);

getVar returns a zero-value (0, "", false) for names that do not exist — same semantics as KonsolScript's zero-value design.

Constructing Value

Value(42.0)                        // Number
Value(std::string("hello"))        // String  — always use std::string, not const char*
Value(true)                        // Boolean

Calling script functions from the host

Value result = engine.callFunction("add", { Value(3.0), Value(4.0) });
double n = toDouble(result);   // 7

The function must be defined in a script that was already loaded/eval'd. Missing functions throw a std::runtime_error.

Registering a host class

Host-side classes use the same handler mechanism as built-in modules. The handler receives the raw token stream and must advance past the full statement.

engine.registerClass("Game", [](Engine& e, size_t i) -> size_t {
    ++i;                                    // skip "Game"
    if (e.tokenText(i) != ":") return e.skipOptSemi(i);
    ++i;                                    // skip ":"
    std::string method = e.tokenText(i++); // read method name
    if (e.tokenText(i) == "(") ++i;        // skip (

    if (method == "GetScore") {
        std::string outVar = e.tokenText(i++);
        if (e.tokenText(i) == ")") ++i;
        e.setVar(outVar, Value(current_score));   // ByRef pattern
    }
    return e.skipOptSemi(i);
});

For simpler class registration without token parsing, use the PluginClass builder from minks_plugin.h (see Plugin system

Plugin search paths (embedding)

When a script uses #include "foobar", the engine searches its plugin path list. The host can prepend directories to this list before running any script:

engine.addPluginPath("/opt/mygame/plugins");

For iOS / embedded targets where dlopen is not available, register plugins statically before running the script:

// foobar_plugin.cpp exports:  extern "C" void minks_register(Engine&);
engine.addPluginPreload("foobar", foobar_plugin_init);
// Now #include "foobar" in a script calls foobar_plugin_init instead of dlopen

Compile with -DMINKS_NO_DYNLOAD to strip all dynamic-loading code from libkonsolscript entirely.

Worked example: sample_app

sample_app/ in the minks repository is a minimal host application that demonstrates the complete embedding workflow. It has no dependency on the minks CLI source.

sample_app/main.cpp (abridged):

#include "kse.hpp"
#include <fstream>
#include <sstream>

int main(int argc, char* argv[]) {
    Engine engine;

    // Host → script: expose app metadata as globals
    engine.setVar("app_name",    Value(std::string("SampleApp")));
    engine.setVar("app_version", Value(1.0));

    if (argc >= 2) {
        // File mode: load and run a .ks script
        std::ifstream f(argv[1]);
        std::ostringstream ss; ss << f.rdbuf();
        std::string path = argv[1];
        size_t sep = path.find_last_of("/\\");
        std::string dir = (sep != std::string::npos) ? path.substr(0, sep) : ".";
        engine.loadScript(ss.str(), dir, path);
        return engine.run() ? 0 : 1;
    }

    // Inline mode: eval fragments, read results back
    engine.eval("Var:Number result = 0; for (Number i=1;i<=10;i++){result=result+i;}");
    double sum = toDouble(engine.getVar("result"));   // 55
    return 0;
}

sample_app/demo.ks — the matching script:

Var:String banner = "Running inside: " + app_name + " v" + app_version;
Konsol:Print(banner);

Var:Number pw = 0;
Math:Power(2, 10, pw);
Konsol:Print(pw);    // 1024

Build and run:

cd sample_app
make
./sample_app           # inline demo
./sample_app demo.ks   # file mode

Platform notes

Platform Shared lib Notes
Windows libkonsolscript.dll make install places it in BINDIR alongside minks.exe; for embedding, copy it next to your host exe (no rpath on Windows)
Linux libkonsolscript.so Link with -Wl,-rpath,'$$ORIGIN' so the .so is found next to the exe
macOS libkonsolscript.dylib Link with -Wl,-rpath,@loader_path
iOS / embedded static libkonsolscript.a Build with -DMINKS_NO_DYNLOAD; use addPluginPreload for plugins

Notes and known limits