-
Notifications
You must be signed in to change notification settings - Fork 566
XGo Class Framework
"Don't define a language for specific domain. Abstract domain knowledge for it." — XGo Design Philosophy
A class framework is XGo's mechanism for abstracting domain knowledge into reusable application skeletons. It is as fundamental to XGo as interface is to Go.
Rather than defining a dedicated DSL for every domain, XGo promotes Specific Domain Friendliness (SDF): write the scaffolding once in plain Go, then let application developers fill in lightweight XGo source files — called classfiles — that contain only their domain-specific logic. The XGo compiler handles all the wiring automatically.
A class framework consists of two roles:
- A project class — the singleton root of the application. There is exactly one per project.
- One or more work class kinds — the units of work the application is composed of. A project typically has many instances of these.
A classfile is an XGo source file that implicitly defines a class. For example, Rect.gox:
var (
Width, Height int
)
func Area() int {
return Width * Height
}This is equivalent to the following Go code:
type Rect struct {
Width, Height int
}
func (this *Rect) Area() int {
return this.Width * this.Height
}The value of this form is ergonomics: developers — especially beginners — can define new types using the sequential-programming syntax they already know. Variables are variables; functions are functions. No type declarations, no receivers.
Every class framework ships a gox.mod file that tells the XGo compiler how to interpret classfiles:
project <filePattern> <BaseType> <importPath> [extraImports...]
class [flags] <filePattern> <BaseType> [ProtocolType]
- The
projectdirective registers the project classfile pattern and its base type. - The
classdirective (repeatable) registers work classfile patterns. When a framework has multiple work class kinds, eachclassdirective carries aProtocolTypeargument — an interface the compiler uses to route instances to the correct parameter of the Template Recv Method.
Flags:
| Flag | Effect |
|---|---|
-embed |
Auto-declare each work class instance as a named field on the project class struct |
-prefix=<P> |
Prepend P to the generated type name of every work class in this kind |
Given the gox.mod declarations and the user's classfiles, the XGo compiler automatically produces:
Each project classfile becomes a struct embedding its declared BaseType. Each work classfile becomes a struct embedding its own BaseType plus a pointer back to the project class instance. If -embed is set, the project struct also gets one named field per work class instance.
Top-level code in the project classfile compiles into MainEntry(). Top-level code in each work classfile compiles into Main().
If the base type itself implements the entry function, the compiler automatically inserts a call to it before the user code runs. The ProtocolType interface serves a separate purpose: it defines the expected Main signature so the compiler can generate the correct method prototype.
The compiler synthesizes a Main method on the project class that:
- Instantiates all work class objects.
- Initialises the
-embedfields (if applicable). - Calls the framework's Template Recv Method, passing the project instance and all work class instances.
func main() {
new(ProjectClass).Main()
}The Template Recv Method is the hook a framework author writes to receive control after the compiler has assembled everything. Its name follows the convention XGot_<BaseType>_Main (historically also Gopt_<BaseType>_Main).
Its signature encodes the framework's structural contract:
// Single work class kind — variadic
func XGot_Game_Main(app Gamer, sprites ...Sprite)
// Multiple work class kinds — one typed slice per kind
func Gopt_MCPApp_Main(app iAppProto, resources []ResourceProto, tools []ToolProto, prompts []PromptProto)The compiler reads this signature to know how many kinds of work classes exist, and how to group instances when calling the method. The framework implementation of this function handles the rest: setting up the runtime, loading resources, calling each work class's Main, and so on.
github.com/goplus/spx
spx is the earliest class framework in the XGo ecosystem, purpose-built for STEM education.
xgo 1.7
project main.spx Game github.com/goplus/spx/v2 math
class -embed *.spx SpriteImplThis declares:
-
main.spxis the project classfile; its class automatically embedsspx.Game. Top-level code inmain.spxbecomes the body ofMainEntry. - All other
*.spxfiles are work classfiles; each embedsspx.SpriteImpl. Top-level code becomes the body ofMain. The-embedflag causes each work class instance to be declared as a named field on the project class.
A "Aircraft War" game might have:
main.spx // game loop and setup
Bullet.spx // bullet behaviour
Enemy.spx // enemy behaviour
MyAircraft.spx // player behaviour
// From main.spx — project class
type Game struct {
spx.Game // embedded base type
Bullet *Bullet // auto-generated by -embed
Enemy *Enemy
MyAircraft *MyAircraft
... // user defined members in main.spx
}
func (this *Game) MainEntry() {
... // user code from main.spx
}
// Synthesized by the XGo compiler:
func (this *Game) Main() {
bullet := new(Bullet)
enemy := new(Enemy)
myAircraft := new(MyAircraft)
// initialise -embed fields
this.Bullet = bullet
this.Enemy = enemy
this.MyAircraft = myAircraft
// call the Template Recv Method
spx.XGot_Game_Main(this, bullet, enemy, myAircraft)
}
// From Bullet.spx — work class
type Bullet struct {
spx.SpriteImpl // embedded base type
*Game // pointer back to project instance
... // user defined members in Bullet.spx
}
func (this *Bullet) Main() {
... // user code from Bullet.spx
}
// Enemy and MyAircraft follow the same pattern.
// Global entry point
func main() {
new(Game).Main()
}func XGot_Game_Main(game Gamer, sprites ...Sprite)Gamer is satisfied by *Game; Sprite is satisfied by every work class. The engine loads each sprite's resources and calls its Main to run its behaviour loop.
github.com/goplus/cobra
xgo 1.7
project *_app.gox App github.com/goplus/cobra/xcmd
class -prefix=Cmd_ *_cmd.gox CommandThis declares:
-
*_app.goxis the project classfile pattern; the class embedsxcmd.App. -
*_cmd.goxfiles are work classfiles; each embedsxcmd.Command. The-prefix=Cmd_flag prependsCmd_to each generated type name. For example,run_cmd.goxproduces typeCmd_runrather thanrun, avoiding collisions with common identifiers.
myapp_app.gox // application entry
run_cmd.gox // "run" subcommand
build_cmd.gox // "build" subcommand
The xcmd.Command base type implements iCommandProto:
type iCommandProto interface {
...
Main(fname string)
}By defining this interface, the framework tells the compiler that every work class Main must accept a string argument. The compiler generates accordingly, and also inserts a call to the base class Main first, since xcmd.Command itself implements Main:
type Cmd_run struct {
xcmd.Command
*xcmd.App
}
func (this *Cmd_run) Main(_xgo_arg0 string) {
this.Command.Main(_xgo_arg0) // base class call, auto-inserted by compiler
... // user code from run_cmd.gox
}The same mechanism works for the project class: if the framework defines a custom MainEntry signature via a protocol interface, the compiler synthesises the base class call there too.
func XGot_App_Main(app iAppProto, cmds ...iCommandProto)iAppProto is satisfied by the project class; iCommandProto is satisfied by every Cmd_* work class. The framework implementation registers each command with the underlying cobra.Command infrastructure and launches the CLI.
github.com/goplus/yap
YAP uses filenames to define routes: get.yap handles GET /; get_p_#id.yap handles GET /p/:id. A minimal web server is a single file:
// get.yap
html `<html><body>Hello, YAP!</body></html>`xgo 1.7
project main.yap AppV2 github.com/goplus/yap
class *.yap HandleriHandlerProto declares:
type iHandlerProto interface {
Main(ctx *Context)
}Every handler's Main is generated with a *Context parameter, giving each handler direct access to the HTTP request and response. As with cobra, the compiler inserts a call to the base class Main automatically if yap.Handler implements it.
func XGot_AppV2_Main(app AppType, handlers ...iHandlerProto)github.com/goplus/mcp
The MCP framework is an example of a framework with multiple distinct work class kinds.
xgo 1.7
project *_mcp.gox MCPApp github.com/goplus/mcp/server
class *_res.gox ResourceApp ResourceProto
class *_tool.gox ToolApp ToolProto
class *_prompt.gox PromptApp PromptProto| File suffix | Base type | Protocol |
|---|---|---|
*_res.gox |
server.ResourceApp |
ResourceProto |
*_tool.gox |
server.ToolApp |
ToolProto |
*_prompt.gox |
server.PromptApp |
PromptProto |
MyServer_mcp.gox // MCP server entry
Files_res.gox // resource: file listing
Search_tool.gox // tool: search
Summarize_tool.gox // tool: summarize
Greet_prompt.gox // prompt: greeting template
// From myserver_mcp.gox — project class
type MyServer struct {
server.MCPApp
}
func (this *MyServer) MainEntry() {
... // user code from myserver_mcp.gox
}
// Synthesized by the XGo compiler:
func (this *MyServer) Main() {
filesRes := new(Files)
searchTool := new(Search)
summarizeTool := new(Summarize)
greetPrompt := new(Greet)
server.Gopt_MCPApp_Main(this,
[]server.ResourceProto{filesRes},
[]server.ToolProto{searchTool, summarizeTool},
[]server.PromptProto{greetPrompt},
)
}
// From search_tool.gox — work class
type Search struct {
server.ToolApp
*MyServer
}
func (this *Search) Main() {
... // user code from search_tool.gox
}
// Resources and prompts follow the same pattern with their respective base types.func Gopt_MCPApp_Main(
app iAppProto,
resources []ResourceProto,
tools []ToolProto,
prompts []PromptProto,
)This is the key structural difference from the previous frameworks. Because the MCP protocol distinguishes resources, tools, and prompts at runtime, the framework needs to receive them as separate typed slices rather than a single variadic. The compiler groups each work class instance by its declared ProtocolType and passes them into the corresponding slice argument. The framework implementation then registers each group with the MCP server accordingly.
XGo ships a built-in class framework for unit testing, with file suffix _test.gox. Given a function:
func foo(v int) int {
return v * 2
}A foo_test.gox file can test it directly — no TestXXX function boilerplate required:
if v := foo(50); v != 100 {
t.error "foo(50) ret: ${v}"
}
t.run "foo -10", t => {
if foo(-10) != -20 {
t.fatal "foo(-10) != -20"
}
}| Concern | Mechanism |
|---|---|
| Group all work class instances together | Single variadic parameter in Template Recv Method |
| Distinguish work class kinds at runtime | Multiple class directives + ProtocolType + typed slice parameters |
| Access work instances from the project class |
-embed flag |
| Avoid type name collisions |
-prefix=<P> flag |
Custom Main / MainEntry parameter signature |
ProtocolType interface declares the desired signature |
| Auto-call base class entry functions | Compiler inserts the call unconditionally when the base type implements the entry function |
| Framework | Domain | File Pattern |
|---|---|---|
| spx | 2D game engine (STEM) | *.spx |
| mcp | MCP server (AI) |
*_mcp.gox, *_tool.gox, etc. |
| mcptest | MCP testing | *_mtest.gox |
| yap | HTTP web framework | *.yap |
| yaptest | HTTP test framework | *_ytest.gox |
| ydb | Database framework | *_ydb.gox |
| cobra | CLI framework |
*_app.gox, *_cmd.gox
|
| gsh | Shell scripting | *.gsh |
| (built-in) | Unit testing | *_test.gox |