Modules are registered as class handlers - the same mechanism the built-in modules use. You do not need to modify the parser.
kse_mymodule.cppPlace 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);
}
kse.cppDeclare 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
}
MakefileOpen 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
Var:String result;
MyModule:Hello("world", result);
Konsol:Print(result);
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
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, ...).
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");
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
}
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.