Adding a module

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


1. Create kse_mymodule.cpp

Place it under cls/ to match the source layout (e.g. cls/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() {
    // ... existing constructor body ...
    register_mymodule(*this);   // add this line
}

3. Add to Makefile

Open the root Makefile and append your file to LIB_SRCS, using the path relative to it:

LIB_SRCS = core/kse.cpp \
           cls/kse_var.cpp \
           ...
           cls/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: i = e.skipPast("}", i)

// 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, read the last token as the receiver variable name, skip ), write the result with e.setVar(outVar, ...).

Void methods

A void method takes input arguments but produces no output variable. Skip the outVar read and setVar call entirely:

if (method == "Log") {
    Value msg = e.evalExpr(i);
    if (e.tokenText(i) == ")") ++i;

    std::cout << "[MyModule] " << toString(msg) << "\n";
    return e.skipOptSemi(i);
}

Script side:

MyModule:Log("hello");

Throwing exceptions

Use KseException to signal errors from a handler. The message is caught as a String on the script side:

if (method == "Sqrt") {
    Value v = e.evalExpr(i);
    if (e.tokenText(i) == ",") ++i;
    std::string outVar = e.tokenText(i); ++i;
    if (e.tokenText(i) == ")") ++i;

    double n = toDouble(v);
    if (n < 0) throw KseException("MyModule:Sqrt - argument must be non-negative");
    e.setVar(outVar, Value(std::sqrt(n)));
    return e.skipOptSemi(i);
}

Script side:

try {
    Var:Number result;
    MyModule:Sqrt(-1, result);
} catch (String err) {
    Konsol:Print(err);   // MyModule:Sqrt - argument must be non-negative
}

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.

Script-side wrapper - define the class in a .ks file and load it at startup:

Class:Create(Greeter) {
    Var:String name;

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

    function greet() {
        Var:String result;
        MyModule:Hello(name, result);
        Konsol:Print(result);
    }
}

Usage:

Class:Greeter g;
g.name = "world";
g.greet();   // prints: Hello from MyModule: world

The class fields (name) live on the instance; methods call the C++ module handler via the normal MyModule:Method(args..., outVar) convention.

For a simpler plugin-based approach, see Plugin System.