Skip to content

Proposal: Flat Mode for XGo Class Framework #2802

Description

@xushiwei

Summary

Add a -flat flag to the project directive in gox.mod. In flat mode, there are no true work classfiles — instead, all matching files are fragments of the project class. Their top-level code is merged into a single _xgo_WorkMain method on the project class rather than into individual Main methods on separate work class types.


Motivation

The current class framework model requires work classfiles to be structurally independent: each becomes its own Go struct with its own Main method. This works well for naturally distinct units of work (sprites, CLI commands, HTTP handlers, MCP tools), but it is not a good fit for projects that want to split a single large classfile into multiple files for organisational reasons without introducing any structural subdivision.

Flat mode addresses this gap. It allows the framework author to offer a "project-only" experience where the application developer can spread their logic across multiple files, all of which are treated as parts of one unified project class.


Design

gox.mod Syntax

The -flat flag is added to the project directive:

project -flat <filePattern> <BaseType> <importPath> [extraImports...]

When -flat is present:

  • The first file matching <filePattern> is the primary project classfile (same as without -flat). Top-level code in it is compiled into MainEntry() as usual.
  • All remaining files matching <filePattern> are project fragments. They are not treated as separate work classfiles. Instead:
    • They share the same generated struct type as the project class (they are aliases of it, not separate types).
    • Their top-level code is merged into a single method named _xgo_WorkMain on the project class.
  • No class directive is needed or expected when -flat is used.

Invariant: -flat and class directives are mutually exclusive within the same framework. A gox.mod that declares project -flat must not also declare any class directives.


Compiler Behaviour

Struct generation

Only one struct is generated for the entire project — the project class. All fragment files contribute member declarations to this single struct. There is no second struct type.

_xgo_WorkMain method

If one or more fragment files exist, the compiler synthesises:

func (this *ProjectClass) _xgo_WorkMain() {
    // top-level code from fragment file 1
    // top-level code from fragment file 2
    // ... in file order
}

The order of fragments follows the same deterministic ordering the compiler uses for work classfiles today (typically lexicographic by filename).

If no fragment files exist (i.e. only the primary project classfile is present), _xgo_WorkMain is not generated, and nil is passed to the Template Recv Method instead.

Synthesised Main method

The compiler synthesises Main on the project class as follows:

With fragment files:

func (this *ProjectClass) Main() {
    XGot_<BaseType>_Main(this, this._xgo_WorkMain)
}

Without fragment files:

func (this *ProjectClass) Main() {
    XGot_<BaseType>_Main(this, nil)
}

Global entry point

Unchanged from the standard case:

func main() {
    new(ProjectClass).Main()
}

Template Recv Method

The framework author writes:

func XGot_<BaseType>_Main(app <ProjectProto>, workMain func())
  • app — the project class instance, typed as the protocol interface <ProjectProto> that the project class satisfies.
  • workMain — a bound method value this._xgo_WorkMain, or nil when no fragment files are present.

The framework implementation decides when (and whether) to call workMain. A typical implementation calls it once after setup:

func XGot_MyBase_Main(app iMyProto, workMain func()) {
    app.Init()
    if workMain != nil {
        workMain()
    }
    app.Run()
}

Example: A Hypothetical Single-Page Script Framework

To illustrate flat mode concretely, consider a framework scriptfw where the application is logically one script but can be split across files. The framework uses the dedicated suffix *.script (alternatively, *_script.gox would also be valid).

gox.mod

xgo 1.7

project -flat *.script ScriptApp github.com/example/scriptfw

Example project

main.script     // primary project classfile
helpers.script  // fragment: helper logic
tasks.script    // fragment: task definitions

Generated Go code

// Unified project struct — all files contribute to this one type

type MyProject struct {
    scriptfw.ScriptApp
    // member declarations from main.script, helpers.script, tasks.script
}

// From main.script — primary classfile
func (this *MyProject) MainEntry() {
    // top-level code from main.script
}

// Synthesised from helpers.script + tasks.script fragments
func (this *MyProject) _xgo_WorkMain() {
    // top-level code from helpers.script (in file order)
    // top-level code from tasks.script
}

// Synthesised by the compiler
func (this *MyProject) Main() {
    scriptfw.XGot_ScriptApp_Main(this, this._xgo_WorkMain)
}

func main() {
    new(MyProject).Main()
}

Template Recv Method

func XGot_ScriptApp_Main(app iScriptProto, workMain func())

Comparison with Standard Mode

Aspect Standard mode Flat mode (-flat)
Work classfiles Each becomes its own struct type No separate struct; all files share the project class
Top-level code in non-primary files Compiled into each work class's Main() Merged into _xgo_WorkMain() on the project class
Template Recv Method second parameter sprites ...Sprite / []ToolProto / etc. workMain func()
class directives required Yes, for each work class kind Not allowed
-embed flag Applicable Not applicable (no work classes to embed)
No non-primary files Template Recv Method receives zero work instances Template Recv Method receives nil for workMain

gox.mod Grammar (updated)

project [-flat] <filePattern> <BaseType> <importPath> [extraImports...]
class   [flags] <filePattern> <BaseType> [ProtocolType]

The -flat flag is only valid on the project line. Specifying it together with any class directive in the same gox.mod is a compile-time error.

When -flat is used, <filePattern> must be a dedicated framework suffix — either a custom extension (*.spx, *.yap, *.script, …) or the *_<class>.gox form (*_script.gox, …). The bare *.gox pattern is rejected at parse time.


Summary of Changes

Component Change
gox.mod parser Recognise -flat flag on project directive; reject *.gox as pattern when -flat is set
Classfile classifier In flat mode, treat all <filePattern> matches as project-class fragments rather than work classfiles
Code generator In flat mode, emit _xgo_WorkMain (if fragments exist) and adjust synthesised Main accordingly
Validation Error if -flat and class directives coexist in one gox.mod
Documentation Update class framework reference with flat mode section

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions