Class

Full OOP with fields and methods. Every user-defined class implicitly extends Object, inheriting getType(), toString(), and isInstanceOf().


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.

Inheritance

A class can extend another class with extends:

Class:Create(Dog extends Animal) {
    function speak() {
        Konsol:Print(name + " barks");
    }
}

The subclass inherits all fields and methods of the parent. Overriding a method replaces the parent's implementation for that subclass.

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.


Examples

Classes

// 07_classes.ks — Class:Create (fields + methods)

Konsol:Print("=== Classes ===");

// ── Counter class ─────────────────────────────────────────────────────────────
Class:Create(Counter) {
    Var:Number value;

    function reset() {
        value = 0;
    }

    function increment() {
        value = value + 1;
    }

    function decrement() {
        value = value - 1;
    }

    function add(Number n) {
        value = value + n;
    }

    function get() : Number {
        return value;
    }
}

Class:Counter c;
c.increment();
c.increment();
c.increment();
Konsol:Print("after 3 increments: ${c.value}");
c.decrement();
Konsol:Print("after decrement:    ${c.value}");
c.add(10);
Konsol:Print("after add(10):      ${c.value}");
c.reset();
Konsol:Print("after reset:        ${c.value}");

// ── Animal class ──────────────────────────────────────────────────────────────
Class:Create(Animal) {
    Var:String name;
    Var:String species;
    Var:Number age;

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

    function birthday() {
        age = age + 1;
    }

    function describe() {
        Konsol:Print("${name} is a ${species} aged ${age}");
    }
}

Class:Animal dog;
dog.name    = "Rex";
dog.species = "dog";
dog.age     = 2;
dog.describe();
dog.birthday();
dog.describe();
dog.setName("Buddy");
dog.describe();

// Two independent instances
Class:Animal cat;
cat.name    = "Whiskers";
cat.species = "cat";
cat.age     = 5;
cat.describe();

// ── Stack class ───────────────────────────────────────────────────────────────
/* Simulated stack using a fixed array field + a top index.
   MINKS classes don't support embedded arrays, so we use
   numbered fields as slots (max 4 items for this demo). */
Class:Create(Stack4) {
    Var:Number s0;
    Var:Number s1;
    Var:Number s2;
    Var:Number s3;
    Var:Number top;

    function push(Number v) {
        if (top == 0) { s0 = v; }
        if (top == 1) { s1 = v; }
        if (top == 2) { s2 = v; }
        if (top == 3) { s3 = v; }
        top = top + 1;
    }

    function peek() : Number {
        if (top == 1) { return s0; }
        if (top == 2) { return s1; }
        if (top == 3) { return s2; }
        if (top == 4) { return s3; }
        return 0;
    }

    function size() : Number {
        return top;
    }
}

Konsol:Print("--- Stack4 ---");
Class:Stack4 stk;
stk.push(10);
stk.push(20);
stk.push(30);
Konsol:Print("size = ${stk.size()}");
Konsol:Print("peek = ${stk.peek()}");

Basic inheritance

// 14_inherit_basic.ks — Single-level inheritance: child gets parent fields + methods

Konsol:Print("=== Basic Inheritance ===");

// ── Base class ────────────────────────────────────────────────────────────────
Class:Create(Shape) {
    Var:String color;
    Var:String label;

    function setColor(String c) {
        color = c;
    }

    function describe() {
        Konsol:Print("${label} | color: ${color}");
    }
}

// ── Circle extends Shape ──────────────────────────────────────────────────────
// Inherits: color, label, setColor(), describe()
// Adds:     radius, area()
Class:Create(Circle extends Shape) {
    Var:Number radius;

    function setRadius(Number r) {
        radius = r;
    }

    function area() : Number {
        // pi * r^2  (using ^ for power)
        return 3.14159 * radius ^ 2;
    }

    function describe() {
        // Override: include radius in output
        Konsol:Print("Circle | color: ${color} | radius: ${radius}");
    }
}

// ── Rectangle extends Shape ───────────────────────────────────────────────────
// Inherits: color, label, setColor(), describe()
// Adds:     width, height, area()
Class:Create(Rectangle extends Shape) {
    Var:Number width;
    Var:Number height;

    function setSize(Number w, Number h) {
        width  = w;
        height = h;
    }

    function area() : Number {
        return width * height;
    }

    function describe() {
        Konsol:Print("Rect   | color: ${color} | size: ${width} x ${height}");
    }
}

// ── Usage ─────────────────────────────────────────────────────────────────────
Class:Circle c;
c.setColor("red");
c.label = "my circle";
c.setRadius(5);
c.describe();

Var:Number _ret = 0;
Math:Round(c.area(), _ret);
Konsol:Print("  area  = ${_ret}");

Class:Rectangle r;
r.setColor("blue");
r.label = "my rect";
r.setSize(4, 6);
r.describe();
Konsol:Print("  area  = ${r.area()}");

// Inherited describe() on a plain Shape
Class:Shape s;
s.color = "green";
s.label = "plain shape";
s.describe();

// Two circles are independent
Class:Circle c2;
c2.setColor("yellow");
c2.setRadius(3);
c2.describe();
Konsol:Print("  area  = ${c2.area()}");

Method override

// 15_inherit_override.ks — Method override: child replaces a parent method

Konsol:Print("=== Method Override ===");

// ── Base ──────────────────────────────────────────────────────────────────────
Class:Create(Animal) {
    Var:String name;
    Var:Number legs;

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

    function speak() {
        Konsol:Print("${name} makes a generic sound");
    }

    function info() {
        Konsol:Print("${name} has ${legs} legs");
    }
}

// ── Dog: overrides speak(), inherits info() ────────────────────────────────────
Class:Create(Dog extends Animal) {
    Var:String breed;

    function speak() {
        Konsol:Print("${name} says: Woof!");
    }

    function fetch() {
        Konsol:Print("${name} fetches the ball");
    }
}

// ── Cat: overrides speak(), inherits info() ────────────────────────────────────
Class:Create(Cat extends Animal) {
    Var:Boolean indoor;

    function speak() {
        Konsol:Print("${name} says: Meow!");
    }

    function purr() {
        Konsol:Print("${name} purrs...");
    }
}

// ── Bird: overrides speak() and info() ───────────────────────────────────────
Class:Create(Bird extends Animal) {
    Var:Number wingspan;

    function speak() {
        Konsol:Print("${name} says: Tweet!");
    }

    function info() {
        // Override info to include wingspan
        Konsol:Print("${name} has ${legs} legs and wingspan ${wingspan}");
    }
}

// ── Demo ──────────────────────────────────────────────────────────────────────
Class:Animal base;
base.name = "GenericAnimal";
base.legs = 4;
base.speak();
base.info();

Konsol:Print("---");

Class:Dog d;
d.name  = "Rex";
d.legs  = 4;
d.breed = "Labrador";
d.speak();      // overridden — "Woof!"
d.info();       // inherited from Animal
d.fetch();      // Dog-only

Konsol:Print("---");

Class:Cat c;
c.name   = "Whiskers";
c.legs   = 4;
c.indoor = true;
c.speak();      // overridden — "Meow!"
c.info();       // inherited
c.purr();       // Cat-only

Konsol:Print("---");

Class:Bird b;
b.name     = "Tweety";
b.legs     = 2;
b.wingspan = 30;
b.speak();      // overridden — "Tweet!"
b.info();       // overridden — shows wingspan too

Inheritance chain

// 16_inherit_chain.ks — Multi-level inheritance chain

Konsol:Print("=== Inheritance Chain ===");

// Level 1 ─────────────────────────────────────────────────────────────────────
Class:Create(LivingThing) {
    Var:Boolean alive;
    Var:Number  energy;

    function init() {
        alive  = true;
        energy = 100;
    }

    function breathe() {
        Konsol:Print("breathes");
    }

    function status() {
        Konsol:Print("alive: ${alive} | energy: ${energy}");
    }
}

// Level 2: Animal extends LivingThing ─────────────────────────────────────────
// Inherits: alive, energy, init(), breathe(), status()
// Adds:     name, move()
Class:Create(Animal extends LivingThing) {
    Var:String name;

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

    function move() {
        energy = energy - 10;
        Konsol:Print("${name} moves (energy now ${energy} )");
    }

    function status() {
        // Override to include name
        Konsol:Print("${name} | alive: ${alive} | energy: ${energy}");
    }
}

// Level 3: Dog extends Animal ─────────────────────────────────────────────────
// Inherits: alive, energy, init(), breathe(), setName(), move(), status()
// Adds:     breed, fetch()
Class:Create(Dog extends Animal) {
    Var:String breed;

    function setBreed(String b) {
        breed = b;
    }

    function fetch() {
        energy = energy - 20;
        Konsol:Print("${name} the ${breed} fetches! (energy now ${energy} )");
    }

    function status() {
        // Override again at this level
        Konsol:Print("${name} ( ${breed} ) | alive: ${alive} | energy: ${energy}");
    }
}

// Level 4: GuideDog extends Dog ───────────────────────────────────────────────
// Inherits everything from Dog (and transitively Animal, LivingThing)
// Adds:     owner, guide()
Class:Create(GuideDog extends Dog) {
    Var:String owner;

    function setOwner(String o) {
        owner = o;
    }

    function guide() {
        energy = energy - 15;
        Konsol:Print("${name} guides ${owner} (energy now ${energy} )");
    }
}

// ── Demo ──────────────────────────────────────────────────────────────────────
Konsol:Print("--- LivingThing ---");
Class:LivingThing lt;
lt.init();
lt.breathe();
lt.status();

Konsol:Print("--- Animal ---");
Class:Animal a;
a.init();
a.setName("GenericAnimal");
a.breathe();    // inherited from LivingThing
a.move();       // Animal
a.status();     // overridden at Animal level

Konsol:Print("--- Dog ---");
Class:Dog dog;
dog.init();
dog.setName("Buddy");
dog.setBreed("Labrador");
dog.breathe();  // inherited from LivingThing (2 levels up)
dog.move();     // inherited from Animal
dog.fetch();    // Dog-only
dog.status();   // overridden at Dog level

Konsol:Print("--- GuideDog ---");
Class:GuideDog gd;
gd.init();
gd.setName("Lassie");
gd.setBreed("Collie");
gd.setOwner("John");
gd.breathe();   // inherited from LivingThing (3 levels up)
gd.move();      // inherited from Animal (2 levels up)
gd.fetch();     // inherited from Dog
gd.guide();     // GuideDog-only
gd.status();    // Dog's override (GuideDog didn't re-override it)

Inheriting built-ins

// 17_inherit_builtins.ks — Extending built-in modules via composition
//
// Built-in "classes" (Konsol, Math, String, File, Time) are C++ handler
// functions, not ClassDef objects, so you cannot write:
//
//   Class:Create(MyMath extends Math) { ... }   // NOT supported
//
// Instead, wrap them: create a user class whose methods delegate to the
// built-in calls internally. This is composition, and it's just as powerful.

Konsol:Print("=== Built-in Extension via Composition ===");

// ── Extended Math ─────────────────────────────────────────────────────────────
// Wraps Math: and adds Clamp, Lerp, Hypot, IsPrime
Class:Create(MathEx) {

    function clamp(Number v, Number lo, Number hi) : Number {
        if (v < lo) { return lo; }
        if (v > hi) { return hi; }
        return v;
    }

    function lerp(Number a, Number b, Number t) : Number {
        return a + (b - a) * t;
    }

    function hypot(Number a, Number b) : Number {
        Var:Number _ret = 0;
        Math:Sqrt(a * a + b * b, _ret);
        return _ret;
    }

    function isPrime(Number n) : Boolean {
        if (n < 2) { return false; }
        Var:Number i = 2;
        while (i * i <= n) {
            Var:Number rem = n % i;
            if (rem == 0) { return false; }
            i++;
        }
        return true;
    }

    function sign(Number n) : Number {
        if (n > 0) { return 1; }
        if (n < 0) { return -1; }
        return 0;
    }
}

Class:MathEx mx;
Konsol:Print("clamp(15, 0, 10) = ${mx.clamp(15, 0, 10)}");
Konsol:Print("clamp(-3, 0, 10) = ${mx.clamp(-3, 0, 10)}");
Konsol:Print("clamp( 5, 0, 10) = ${mx.clamp(5, 0, 10)}");
Konsol:Print("lerp(0, 100, 0.25) = ${mx.lerp(0, 100, 0.25)}");
Konsol:Print("hypot(3, 4) = ${mx.hypot(3, 4)}");
Konsol:Print("isPrime(7)  = ${mx.isPrime(7)}");
Konsol:Print("isPrime(9)  = ${mx.isPrime(9)}");
Konsol:Print("isPrime(13) = ${mx.isPrime(13)}");
Konsol:Print("sign(-5)    = ${mx.sign(-5)}");
Konsol:Print("sign(0)     = ${mx.sign(0)}");
Konsol:Print("sign(3)     = ${mx.sign(3)}");

// ── Extended String ───────────────────────────────────────────────────────────
// Wraps String: and adds StartsWith, EndsWith, Repeat, PadLeft
Class:Create(StringEx) {

    function startsWith(String s, String prefix) : Boolean {
        Var:Number plen = 0;
        Var:String head = "";
        Var:Number cmp = 0;
        String:Len(prefix, plen);
        String:Left(s, plen, head);
        String:Compare(head, prefix, cmp);
        return cmp == 0;
    }

    function endsWith(String s, String suffix) : Boolean {
        Var:Number slen = 0;
        Var:String tail = "";
        Var:Number cmp = 0;
        String:Len(suffix, slen);
        String:Right(s, slen, tail);
        String:Compare(tail, suffix, cmp);
        return cmp == 0;
    }

    function repeat(String s, Number n) : String {
        Var:String result = "";
        Var:Number i = 0;
        while (i < n) {
            result = result + s;
            i++;
        }
        return result;
    }

    function isBlank(String s) : Boolean {
        Var:String trimmed = "";
        Var:Number tlen = 0;
        String:Trim(s, trimmed);
        String:Len(trimmed, tlen);
        return tlen == 0;
    }
}

Class:StringEx sx;
Konsol:Print("startsWith(hello, hel) = ${sx.startsWith("hello", "hel")}");
Konsol:Print("startsWith(hello, wor) = ${sx.startsWith("hello", "wor")}");
Konsol:Print("endsWith(hello, llo)   = ${sx.endsWith("hello", "llo")}");
Konsol:Print("endsWith(hello, abc)   = ${sx.endsWith("hello", "abc")}");
Konsol:Print("repeat(ab, 4) = ${sx.repeat("ab", 4)}");
Konsol:Print("isBlank('')   = ${sx.isBlank("")}");
Konsol:Print("isBlank('  ') = ${sx.isBlank("   ")}");
Konsol:Print("isBlank(hi)   = ${sx.isBlank("hi")}");

// ── Extended Logger ───────────────────────────────────────────────────────────
// Wraps Konsol: and adds a prefix, log levels, and timestamps
Class:Create(Logger) {
    Var:String prefix;
    Var:Boolean timestamps;

    function setPrefix(String p) {
        prefix = p;
    }

    function enableTimestamps() {
        timestamps = true;
    }

    function info(String msg) {
        if (timestamps == true) {
            Time:GetHour();   Var:Number h = _ret;
            Time:GetMinute(); Var:Number m = _ret;
            Time:GetSecond(); Var:Number sec = _ret;
            Konsol:Print("[INFO] ${h} : ${m} : ${sec} | ${prefix} ${msg}");
        } else {
            Konsol:Print("[INFO] ${prefix} ${msg}");
        }
    }

    function warn(String msg) {
        Konsol:Print("[WARN] ${prefix} ${msg}");
    }

    function err(String msg) {
        Konsol:Print("[ERR ] ${prefix} ${msg}");
    }
}

Konsol:Print("--- Logger ---");
Class:Logger log;
log.setPrefix("[App]");
log.info("starting up");
log.warn("low memory");
log.err("connection failed");

log.enableTimestamps();
log.info("timestamps enabled");