AI Agent Reference
This is a condensed reference for AI coding agents. For human-readable documentation with detailed explanations and examples, see the Guide.
SIMPLOO AI Agent Reference
Lua OOP library with two equivalent syntaxes: block and builder.
IMPORTANT: Before writing code, examine existing codebase files to determine which syntax is used. Match the existing style. Do not mix syntaxes within a project.
Class Definition
-- Block syntax (auto-registers)
class "Name" { member = value; method = function(self) end; }
-- Builder syntax (manual register)
local c = class("Name")
c.member = value
function c:method() end
c:register() -- required
Inheritance
class "Child" extends "Parent" {}
class "Multi" extends "A, B, C" {} -- multiple inheritance
-- Builder
class("Child", {extends = "Parent"})
Interfaces
interface "IName" {
requiredMethod = function(self, arg) end; -- empty body = signature only
default { optionalMethod = function(self) return "default" end; };
}
interface "IChild" extends "IParent" {} -- interface inheritance
class "Impl" implements "IFace1, IFace2" {
requiredMethod = function(self, arg) ... end;
}
-- Combined
class "X" extends "Parent" implements "IFace" {}
-- Builder
local i = interface("IName")
i.requiredMethod = function(self, arg) end
i.default.optionalMethod = function(self) return "default" end
i:register()
local c = class("Impl", {implements = "IFace1, IFace2"})
function c:requiredMethod(arg) ... end
c:register()
Interface restrictions:
- Only public methods allowed (no private/protected/static)
- No variables (only functions)
- instance_of(InterfaceName) works for checking implementation
- Interface reference (self.IName) only exists when interface has default methods
- Calling default from override: self.InterfaceName:method()
- Interfaces cannot be instantiated: IName.new() errors
Modifiers
Apply via nesting. Order irrelevant.
class "Ex" {
public { x = 1; }; -- accessible everywhere (default if omitted)
private { y = 2; }; -- declaring class only
protected { z = 3; }; -- declaring class + subclasses
static { count = 0; }; -- shared across instances
const { MAX = 100; }; -- immutable after init
transient { cache = {}; }; -- excluded from serialization
meta { __tostring = function(self) return "str" end; }; -- metamethod
-- Combine modifiers by nesting
private { static { secret = "x"; }; };
public { static { const { VERSION = "1.0"; }; }; };
}
-- Builder syntax
c.private.static.secret = "x"
c.public.static.const.VERSION = "1.0"
Constructors/Finalizers
class "Ex" {
__construct = function(self, arg1, arg2) end; -- called on new()
__finalize = function(self) end; -- called on GC
__register = function(self) end; -- called once at class registration (self = class)
}
-- Builder
local c = class("Ex")
function c:__construct(arg1, arg2) end
function c:__finalize() end
function c:__register() end
c:register()
-- Usage: instantiation (same for both syntaxes, all equivalent)
local obj = Ex.new(a, b)
local obj = Ex:new(a, b)
local obj = Ex(a, b)
Parent Access
class "Child" extends "Parent" {
__construct = function(self, x)
self.Parent(x) -- call parent constructor (dev mode warns if forgotten)
-- OR: self.Parent:__construct(x)
end;
method = function(self)
return self.Parent:method() -- call parent method
end;
}
Constructor rules:
- Parent constructor NOT auto-called if child defines own constructor
- Parent constructor called once max (subsequent calls are no-op)
- Dev mode warns if parent constructor has __construct but child doesn't call it
- No warning if parent has no constructor or child has no constructor
Deep inheritance with varargs:
class "Level3" extends "Level2" {
__construct = function(self, myArg, ...)
self.Level2(...) -- pass remaining args to parent
self.myValue = myArg
end;
}
Namespaces
namespace "game.entities"
class "Player" {} -- creates game.entities.Player
using "other.ns.Class" -- import single class
using "other.ns.*" -- import all from namespace
using "other.ns.X" as "Y" -- import with alias
namespace "" -- return to global namespace
namespace() -- get current namespace (no args)
-- Builder: namespace in options
class("Config", {namespace = "myapp"})
Same namespace across files: re-declaring namespace "x" auto-imports existing classes in x.
Classes can reference themselves by short name within methods.
Null Values
class "Ex" { optionalRef = null; } -- declares member with nil default
-- Use nil for comparisons: if self.optionalRef == nil then
-- Never compare against null at runtime
Instance Methods
Usage (same for both syntaxes):
obj:get_name() -- "ClassName" or "ns.ClassName"
obj:get_class() -- class reference
obj:instance_of(Other) -- true if obj is/extends/implements Other (also accepts "ClassName" string)
obj:get_parents() -- {ParentName = parentInstance, ...}
obj:get_member("name") -- {value, owner, modifiers} or nil
obj:get_members() -- {name = {value, owner, modifiers}, ...}
obj:bind(fn) -- wrap callback to preserve private/protected access
obj:serialize() -- {ClassName={member=value, Parent={...}}}
obj:clone() -- deep copy (faster than serialize/deserialize, includes transient)
Serialization
Usage (same for both syntaxes):
local data = simploo.serialize(instance) -- or instance:serialize()
local obj = simploo.deserialize(data) -- or ClassName:deserialize(data)
Serializes: public/private/protected non-static non-function non-transient members.
Output: {ClassName = {members..., ParentName = {parent members...}}}. Consistent structure at all levels.
Metamethods
Mark with meta modifier:
class "Ex" {
meta {
__tostring = function(self) return "str" end;
__call = function(self, ...) end; -- instance(args) after construction
__add = function(self, other) end;
__sub = function(self, other) end;
__mul = function(self, other) end;
__div = function(self, other) end;
__mod = function(self, other) end;
__pow = function(self, other) end;
__unm = function(self) end;
__eq = function(self, other) end;
__lt = function(self, other) end;
__le = function(self, other) end;
__concat = function(self, other) end;
__index = function(self, key) end;
__newindex = function(self, key, val) end;
};
}
-- Builder
local c = class("Ex")
c.meta.__tostring = function(self) return "str" end
c.meta.__add = function(self, other) end
c:register()
Note: First call Class(args) invokes __construct. Subsequent instance(args) invokes __call.
Metamethods are inherited from parent classes.
Static Members
class "Counter" {
static { count = 0; };
__construct = function(self) self.count = self.count + 1 end;
}
-- Builder
local c = class("Counter")
c.static.count = 0
function c:__construct() self.count = self.count + 1 end
c:register()
-- Usage (same for both syntaxes)
Counter.count -- access on class
instance.count -- same value, shared
Static members: - Not copied per instance (memory efficient for large data) - Accessible via both class and instance - Changes propagate to all instances and class
Polymorphism
Child overrides called even from parent code:
class "Base" {
template = function(self) return self:hook() end; -- calls child's hook
hook = function(self) return "base" end;
}
class "Derived" extends "Base" {
hook = function(self) return "derived" end;
}
Derived():template() -- "derived"
Polymorphism in constructors: child overrides are called during parent constructor, but child members have their declared default values (child constructor hasn't run yet).
Access Control Notes
- Production mode: no access checks, maximum performance
- Development mode: private/protected enforced via scope tracking
- Private members: class-scoped (parent's private separate from child's private with same name)
- Cross-instance: same class CAN access other instance's private (class-based, like Java/C++)
bind(fn): preserves scope for callbacks passed to other classes- Coroutine-safe: scope tracked per-thread
Ambiguous Members
Multiple parents with same member name:
class "Both" extends "Left, Right" {}
obj.value -- ERROR: ambiguous
obj.Left.value -- OK
obj.Right.value -- OK
-- Or override in child to resolve
Parents with same short name from different namespaces:
class "Child" extends "ns1.Foo, ns2.Foo" {}
obj.Foo -- nil (ambiguous)
obj["ns1.Foo"]:method() -- OK via bracket notation
obj["ns2.Foo"]:method() -- OK
-- Or use 'using ... as' for aliases
Shadowing
Child public variables shadow parent's:
class "Parent" { value = "parent"; }
class "Child" extends "Parent" { value = "child"; }
Child.new().value -- "child"
Child.new().Parent.value -- "parent"
Configuration
Set before loading simploo:
simploo = {config = {}}
simploo.config["production"] = true -- disable access checks (faster)
simploo.config["classHotswap"] = true -- update existing instances on redefine
simploo.config["exposeSyntax"] = true -- globals: class, extends, etc.
simploo.config["baseInstanceTable"] = _G -- where classes stored
simploo.config["baseSyntaxTable"] = _G -- where syntax exposed
simploo.config["customModifiers"] = {} -- e.g., {"observable"}
simploo.config["strictInterfaces"] = false -- check arg names/count (Lua 5.2+ only)
dofile("simploo.lua")
-- Runtime syntax toggle
simploo.syntax.init() -- expose syntax globals
simploo.syntax.destroy() -- remove syntax globals
-- Manual hotswap init (alternative to config)
simploo.hotswap:init()
Custom syntax/instance tables:
local myLib = {}
simploo.config["baseSyntaxTable"] = myLib
simploo.config["baseInstanceTable"] = myLib
-- Extract to locals for chainable syntax:
local class, extends = myLib.class, myLib.extends
class "Player" extends "Entity" {}
Hooks
simploo.hook:add("hookName", function(...) return modified end)
simploo.hook:remove("hookName", callbackFn) -- or omit fn to remove all
simploo.hook:fire("hookName", ...)
-- Available hooks:
-- beforeRegister(data) -> data -- modify class/interface definition
-- afterRegister(data, baseInstance)
-- afterNew(instance) -> instance
-- onNamespace(name) -> name
-- onUsing(path) -> path
Hook data structure for beforeRegister:
{
type = "class", -- or "interface"
name = "ClassName",
parents = {"Parent1", "Parent2"},
implements = {"IFace1"},
members = {
memberName = {
value = <value>,
modifiers = {public = true, static = false, ...}
}
}
}
Multiple hooks: run in registration order, each receives previous hook's return value.
Hotswap
When class redefined with hotswap enabled: - New members added with default values - Removed members become nil - Methods replaced with new implementations - Existing non-function values preserved (not reset to new defaults) - Works with inheritance
Common Patterns
-- Factory
class "Factory" {
static { create = function(self, type) return self.types[type]() end; };
}
-- Singleton
class "Singleton" {
private { static { instance = null; }; };
static { get = function(self)
if not self.instance then self.instance = self() end
return self.instance
end; };
}
-- Template method
class "Base" {
algorithm = function(self) self:step1(); self:step2() end;
step1 = function(self) end; -- override in child
step2 = function(self) end;
}