Hot++

Inheritance

A script class can derive from native classes and from other script classes.

Native base

A method replaces a native virtual method only with override. Without it the translator reports an error.

using "Actor.h";

class Player : Actor {
    override void Tick(float dt) {
        Move(Vec3(dt, 0, 0));
    }
    override void TakeDamage(float amount) { }
}

Several native bases

The first base is the main base. The other bases are interfaces. Implement each pure virtual method with override. base. calls the interface code.

using "Actor.h";
using "Damageable.h";

class Boss : Actor, IDamageable {
    int hp = 50;

    override int Absorb(int amount) {
        hp = hp - amount;
        return hp;
    }
    int BaseArmor() { return base.Armor(); }
}

These rules apply to the bases:

Const methods

Two native bases can have F() and F() const as virtual methods. A script method never has const after the parameters. The error is const is inferred; remove it.

class Foo : Widget, IConst {
    // Widget has `Size()` and IConst has `Size() const`.
    override int Size() => 2; // overrides the non-const method of Widget
}

A script class has one method for each name. The body can write the fields of this.

Parameter forms of an override

A native virtual method can take a handle type in several forms. Write the plain handle type in the override. The translator finds the native method and keeps its signature. The body gets a handle.

Native parameter The body gets
Weapon*, const Weapon* Weapon through shared_from_this(), or an intrusive reference
Weapon&, const Weapon& the same
std::shared_ptr<Weapon>, const std::shared_ptr<Weapon>&, std::shared_ptr<Weapon>&& the handle itself
class Foo : Gun {
    // Gun has `virtual void Hit(Weapon* w)`.
    override void Hit(Weapon w) { w.dmg = 1; }
}

Script parent

A script class can derive from a script class. virtual opens a method for overrides. base(...) calls the parent constructor. new hides a parent method instead of replacing it.

using "Actor.h";

class Unit : Actor {
    int hp = 100;

    Unit(int start) : base(std.string("unit")) { hp = start; }
    virtual int Power() { return 1; }
    int Attack() { return Power() * 10; }
}

class Grunt : Unit {
    int rage = 3;

    Grunt(int start, int r) : base(start + 1) { rage = r; }
    override int Power() { return rage * 2; }
    new int Attack() { return base.Attack() + 1; }
}

Abstract classes

abstract class marks a class that has abstract methods. The compiler does not allow new of an abstract class. The error is cannot create an instance of the abstract class 'C'. A host can still create the object with _create(). A child class that does not override all abstract methods stays abstract.

abstract class Shape {
    abstract int Area();
}

class Square : Shape {
    int side = 3;
    override int Area() { return side * side; }
}

A reload can make a live class abstract and back again. A live object keeps its state.

Abstract methods

An abstract method has no body and the word abstract. It is virtual, so a child class implements it with override. A call of an abstract method that has no implementation throws ScriptError with the text abstract method 'C.F' called. The call base.F() of an abstract method is an error.

abstract class Shape {
    abstract int Area();
    int Twice() { return Area() * 2; }   // calls the slot of the child
}

A .hot class must override each pure virtual method of a native base, or it must be abstract. A pure virtual method with noexcept cannot throw. The call reports the error and does not end the program. If the result type has no default value (a reference), the host gives a hot::noexcept_result<R> specialization (see Embedding rules).

Parent in another file

using "parent.hot"; makes the classes of another script visible. The script joins the generation of the child. A class name in two scripts is an error. A native stub in the main script wins over a stub with the same name in the imported script.

using "parent.hot";
using "Actor.h";

class Grunt : Unit {
    Grunt(int r) : base(r + 1) { }
}

For is and as, see Statements.

Interfaces

An interface is a type with methods and no state. A class can implement many interfaces. A .hot script declares an interface at file level.

interface IShape {
    float Area();
    std.string Name();
}

A class lists the interfaces in its base list. The parent class comes first. The interfaces come after it. A class with no parent can start with an interface.

class Square : Actor, IShape {
    float side = 2.0;
    float Area() { return side * side; }
    std.string Name() { return "square"; }
}

class Disc : IShape {
    float Area() { return 3.0; }
    std.string Name() { return "disc"; }
}

Implementation

The implementation is implicit. No override is needed. A public method implements an interface method. It must have the same name, the same parameters and the same return type. There is no covariance. A method with another signature is an error:

class 'Sq' does not implement 'IShape.Scale(int)': the method 'Scale' has another signature

A missing method is also an error: class 'Sq' does not implement 'IShape.Scale'. These rules also apply:

Use

A class handle converts to the interface without a cast. An interface value works in a field, a parameter, a return type and a collection such as std.vector<IShape>.

class Scene {
    std.vector<IShape> shapes;
    float Total() {
        float sum = 0.0;
        foreach (var s in shapes) sum += s.Area();
        return sum;
    }
    void Add() {
        IShape s = new $Disc();     // implicit conversion
        shapes.push_back(s);
        if (s is $Disc) { $Disc d = s as $Disc; }
        if (s is IShape) { }       // true when the class implements it
    }
}

is I and as I test a value for an interface. i is C and i as C test an interface value for a class. The operators == and != compare the identity of the objects. An interface value and a class handle can be compared. null is a valid interface value.

A call of a method on a null interface value throws ScriptError with the text null interface handle 'IShape'.

The compiler gives an error for these uses:

Use Error
new IShape() cannot create an instance of the interface 'IShape'
[weak] IShape s; [weak] cannot be used with the interface 'IShape': an interface handle is not a class handle. Remove '[weak]'
class C : IShape, X: an interface before the parent class the interface 'IShape' cannot be the base class of 'C': list the base class 'X' first and the interfaces after it
class C : Actor, IShape, IShape: the same interface twice 'IShape' is listed twice
struct S : IShape a struct cannot implement the interface 'IShape'
An interface in the optimized AOT mode script interfaces are not supported in the optimized AOT mode

Reload

An interface value is a handle to the object. It survives a reload. The call finds the method by name in the class of the object. So the call always uses the code of the current generation. The loader rebuilds the interface tables at each reload.

The fixtures tests/scripts/iface_v1.hot and tests/scripts/iface_params_v1.hot show these cases.

Run time data

The translator writes the table iface_rows in each class record. It lists the interface methods of the class with the slot of the implementation. The field ifaces lists the interface names. The loader builds iface_slots and iface_slot_count at each commit. The index of iface_slots is a session id of the method. A call uses hot::call_iface, and the call site caches the id. A removed class has no table.

Limits