ClassBuilder

Inherits from Actor

ClassBuilder — Fluent builder for creating and registering Beamtalk classes.

ClassBuilder is an Actor subclass — each builder instance is a short-lived gen_server process that accumulates configuration via cascaded messages. The register method is a terminal operation: it registers the class with the runtime, stops the builder process, and returns the new class object.

Codegen emits Core Erlang that calls ClassBuilder directly — the Beamtalk snippet below is conceptual:

Object classBuilder
  name: #Counter;
  fields: #{ #count => 0 };
  methods: #{ #increment => [...], #value => [...] };
  register

References

  • ADR 0038: docs/ADR/0038-subclass-classbuilder-protocol.md
  • Pattern: stdlib/src/Actor.bt (ClassBuilder is Actor subclass)

Instance Methods

name: aSymbol source

Set the class name (a Symbol).

Examples

builder name: #Counter
superclass: aClass source

Set the superclass reference.

Examples

builder superclass: Object
fields: aDict source

Set all fields at once (used by codegen).

Examples

builder fields: #{ #count => 0 }
methods: aDict source

Set all methods at once (used by codegen).

Examples

builder methods: #{ #increment => fun }
methodSource: aDict source

Set per-method source text as a selector => source string map.

Populated automatically by the compiler from block-literal method bodies (BT-2246) so builder-defined classes are visible to SystemNavigation source-text queries, just like file-defined ones. Methods built from computed/non-literal funs have no source literal and are simply omitted (known-present but unindexable).

Examples

builder methodSource: #{ #increment => "increment => self.count := self.count + 1" }
classMethods: aDict source

Set all class-side methods at once (ADR 0084, BT-2267).

Each value is a class-method block whose body the compiler lowers with the class-method calling convention (fun(ClassSelf, ClassVars, ...)), so class-variable mutations thread correctly and super/self resolve. The runtime installs them as runtime class-method funs (BT-2266).

Examples

builder classMethods: #{ #bump => [:self | self.total := self.total + 1] }
classMethodSource: aDict source

Set per-class-method source text as a selector => source string map.

The class-side counterpart of methodSource:. Populated automatically by the compiler from classMethods: block-literal bodies (BT-2270) so builder-defined class methods are visible to SystemNavigation source-text queries on the class side (BT-2195), just like file-defined ones. Class methods built from computed/non-literal funs have no source literal and are simply omitted (known-present but unindexable).

Examples

builder classMethodSource: #{ #bump => "bump => self.total := self.total + 1" }
classVars: aDict source

Set class-variable initial values (ADR 0084, BT-2267).

The class-side counterpart of fields:. Named classVars: rather than classState: because classState: is a reserved class-variable declaration keyword (it cannot be a method selector), mirroring how the instance-side setter is fields:, not the reserved field:/state:.

Examples

builder classVars: #{ #total => 0 }
methodSignatures: aDict source

Set instance-method display signatures as a selector => signature map (ADR 0084, BT-2268).

These are the human-readable signatures shown by :help (e.g. "increment -> Integer"). The file-defined path derives them from the method AST; the builder path supplies them explicitly so programmatically built classes reach :help parity.

Examples

builder methodSignatures: #{ #increment => "increment -> Integer" }
classMethodSignatures: aDict source

Set class-side method display signatures as a selector => signature map (ADR 0084, BT-2268).

The class-side counterpart of methodSignatures:, shown by :help for class methods.

Examples

builder classMethodSignatures: #{ #default => "default -> Counter" }
methodReturnTypes: aDict source

Set instance-method return-type metadata as a selector => type map (ADR 0084, BT-2268).

Mirrors the return-type channel the compiler emits for file-defined classes so reflective type queries return the same data for builder-defined ones.

Examples

builder methodReturnTypes: #{ #increment => #Integer }
classMethodReturnTypes: aDict source

Set class-side method return-type metadata as a selector => type map (ADR 0084, BT-2268).

The class-side counterpart of methodReturnTypes:.

Examples

builder classMethodReturnTypes: #{ #default => #Counter }
methodDocs: aDict source

Set per-method doc comments as a selector => doc string map (ADR 0084, BT-2268).

These are the doc comments reflected by documentation queries and shown in :help, matching the doc channel the compiler emits for file-defined classes.

Examples

builder methodDocs: #{ #increment => "Add one to the count." }
classMethodDocs: aDict source

Set per-class-method doc comments as a selector => doc string map (ADR 0084, BT-2268).

The class-side counterpart of methodDocs:.

Examples

builder classMethodDocs: #{ #default => "Build a fresh counter." }
classDoc: aString source

Set the class-level doc comment (ADR 0084, BT-2268).

Reflected by documentation queries on the class, matching the classDoc channel the compiler emits for file-defined classes.

Examples

builder classDoc: "A simple mutable counter."
meta: aDict source

Set the class meta map (ADR 0084, BT-2268).

The meta map carries static class metadata (flags, fields, method info) that the runtime reads during registration. Left unset, the runtime falls back to reading the backing module's __beamtalk_meta/0.

Examples

builder meta: #{ #is_abstract => false }
isConstructible: aBoolean source

Set whether instances of the class can be created via new / new: (ADR 0084, BT-2268).

File-defined classes infer this from the new => self error: pattern; the builder path sets it explicitly. Leaving it unset (nil) lets the runtime compute constructibility lazily, as it does for primitive classes.

Examples

builder isConstructible: false
addField: aName default: aValue source

Add a single field with a default value.

Examples

builder addField: #name default: 'Rex'
addMethod: aSelector body: aBlock source

Add a single instance method from a block body (the "browser" use case).

The incremental counterpart of methods:. It writes the same methodSpecs map the bulk setter populates, so a class can be assembled one method at a time before register. In a classBuilder … register cascade the compiler lowers the block argument exactly as it lowers a methods: map value — the method is installed as a compiler-generated runtime fun, not a naive closure (BT-873). A computed (non-literal) fun is stored verbatim and dispatches with the fun(Self, Args…) convention.

Examples

builder addMethod: #answer body: [:self | 42]
addClassMethod: aSelector body: aBlock source

Add a single class-side method from a block body (ADR 0084, BT-2267).

The incremental counterpart of classMethods:. It writes the same classMethods map, so a class can gain class methods one at a time before register. In a classBuilder … register cascade the compiler lowers the block with the class-method calling convention (fun(ClassSelf, ClassVars, …)) so class-variable mutations thread and super/self resolve, just like a classMethods: map value.

Examples

builder addClassMethod: #bump body: [:self | self.total := self.total + 1]
addClassState: aName default: aValue source

Add a single class variable with a default value (ADR 0084, BT-2267).

The incremental counterpart of classVars:. It writes the same classState map. Named addClassState:default: rather than addClassVar:default: for symmetry with the classState: declaration keyword used on file-defined classes.

Examples

builder addClassState: #total default: 0
removeMethod: aSelector source

Remove a single instance method before register (live-editing parity).

Drops the selector from the methodSpecs map. A no-op when the selector was never added.

Examples

builder removeMethod: #answer
removeClassMethod: aSelector source

Remove a single class-side method before register (live-editing parity).

Drops the selector from the classMethods map. A no-op when the selector was never added.

Examples

builder removeClassMethod: #bump
modifier: aSymbol source

Apply a class modifier (#abstract, #sealed, #typed, #internal).

Examples

builder modifier: #sealed
native: anErlangModule source

Set the backing Erlang module for a native Actor class.

When set, the register call will link this Beamtalk class to the named Erlang module, enabling self delegate dispatch.

Examples

builder native: #beamtalk_subprocess
register source

Register the class with the runtime and return the new class object. Stops the builder process after registration — the builder is single-use and should not be retained.

Called from compiled module init, wires the BEAM module's pre-compiled methods into the class gen_server. If the class already exists (hot reload), updates the existing gen_server state via update_class/2 rather than failing.

Inherited Methods

From Actor

registerAs: name Sealed

Register this (already-spawned) actor under name.

Non-atomic with respect to spawn: another process may claim name between the actor's spawn and this call. Prefer class spawnAs: when the name is known at spawn time.

On success returns Result ok: self so calls can be chained:

(counter registerAs: #c) onSuccess: [:c | c increment]
unregister Sealed

Unregister this actor's name, if any. Idempotent.

Returns #ok even when the actor has no registered name or the name has already been released. When a real failure occurs (reserved name, type error, etc.) the error is raised — consistent with other teardown methods like stop and kill.

When an actor process exits, Erlang automatically releases its registered name; callers do not need to call unregister from terminate:.

Examples

counter unregister   // => #ok
registeredName Sealed

Return the Symbol this actor is registered under, or nil if unnamed.

Examples

counter registeredName   // => #counter (or nil)
isRegistered Sealed

Whether this actor currently has a registered name.

Examples

counter isRegistered   // => true or false
withTimeout: ms

Wrap this actor with a custom message timeout.

Returns a TimeoutProxy that forwards all messages to this actor using the given timeout (in milliseconds, or #infinity) for gen_server:call. The default OTP timeout is 5000ms.

The proxy is a separate actor process — call stop on it when done.

Examples

slowDb := db withTimeout: 30000
slowDb query: sql              // forwarded with 30s timeout
slowDb stop                    // stop the proxy when done
initialize

Optional lifecycle hook called automatically after spawn.

Override in subclasses to perform setup that goes beyond state: defaults, such as opening resources or computing derived state. Called synchronously before the spawned object is returned to the caller.

If initialize raises an error, the spawn fails with a catchable InstantiationError. Under a supervisor, the child start fails and the supervisor applies its restart strategy.

Examples

Actor subclass: Stack
  state: items = nil
  initialize => self.items := #()
terminate: _reason

Optional lifecycle hook called when the actor is shutting down.

Override in subclasses to perform cleanup such as closing resources, flushing buffers, or notifying dependents. Called synchronously during graceful shutdown (stop). The reason parameter is an Object — it may be a Symbol like #normal for graceful stop, but OTP can also pass compound terms like {shutdown, term} for non-atom shutdown reasons.

Not called when the actor is forcefully killed (kill). If terminate: raises an error, shutdown proceeds anyway. Actor state (self.field) is accessible during terminate:.

Examples

Actor subclass: Logger
  state: logFile = nil
  terminate: reason =>
    self.logFile isNil ifFalse: [self.logFile close]
delegate Sealed

Delegate message dispatch to the backing Erlang module.

This method is a sentinel — non-native Actors do not have a backing Erlang module, so calling delegate raises an Error at runtime. Native Actors (declared with native: in ClassBuilder) override this intrinsic via the compiler's codegen phase.

Examples

counter delegate   // => ERROR: delegate called on a non-native Actor
pid Sealed

Return the raw Erlang PID backing this actor.

Useful for FFI interop where an Erlang function expects a raw pid.

Examples

rawPid := counter pid
rawPid class           // => Pid
monitor Sealed

Create an Erlang monitor on this actor's process.

Returns a Reference that can be used to cancel the monitor via demonitor. The caller will receive a DOWN message if the actor exits.

Examples

ref := counter monitor
ref class              // => Reference
ref demonitor          // cancel the monitor
onExit: block Sealed

Register a callback to be invoked when this actor exits.

Monitors the actor and calls block value: reason when the actor process terminates. Returns #ok immediately. The block is called asynchronously from a lightweight watcher process.

Examples

worker onExit: [:reason |
  Logger info: "worker exited" metadata: #{"reason" => reason displayString}
]
stop Sealed

Gracefully stop this actor (gen_server:stop).

Idempotent: stopping an already-stopped actor succeeds silently. Raises an error if the actor times out during shutdown.

Examples

counter stop   // => ok
kill Sealed

Forcefully kill this actor (exit(Pid, kill)).

Unlike stop, kill cannot be trapped by the actor process.

Examples

counter kill   // => ok
isAlive Sealed

Check if this actor's process is still alive.

WARNING: isAlive check-then-act is inherently racy. The actor could die between the isAlive check and a subsequent message send. Use monitors for robust lifecycle management.

Examples

counter isAlive   // => true or false

From Object

class

Return the class of the receiver.

Examples

42 class              // => Integer
"hello" class         // => String
isNil

Test if the receiver is nil. Returns false for all objects except nil.

Examples

42 isNil              // => false
nil isNil             // => true
notNil

Test if the receiver is not nil. Returns true for all objects except nil.

Examples

42 notNil             // => true
nil notNil            // => false
ifNil: _nilBlock

If the receiver is nil, evaluate nilBlock. Otherwise return self.

Examples

42 ifNil: [0]         // => 42
nil ifNil: [0]        // => 0
ifNotNil: notNilBlock

If the receiver is not nil, evaluate notNilBlock with self.

Examples

42 ifNotNil: [:v | v + 1]   // => 43
nil ifNotNil: [:v | v + 1]  // => nil
ifNil: _nilBlock ifNotNil: notNilBlock

If nil, evaluate nilBlock; otherwise evaluate notNilBlock with self.

Examples

42 ifNil: [0] ifNotNil: [:v | v + 1]    // => 43
nil ifNil: [0] ifNotNil: [:v | v + 1]   // => 0
ifNotNil: notNilBlock ifNil: _nilBlock

If not nil, evaluate notNilBlock with self; otherwise evaluate nilBlock.

Examples

42 ifNotNil: [:v | v + 1] ifNil: [0]    // => 43
nil ifNotNil: [:v | v + 1] ifNil: [0]   // => 0
printString

Return a developer-readable string representation.

Default implementation returns "a ClassName". Subclasses such as Integer, String, and List override this to return richer output.

Examples

42 printString            // => "42"
displayString

Return a user-facing string representation for display purposes.

Default implementation delegates to printString. Subclasses such as String and Symbol override this to return a more readable form without developer annotations (e.g. no surrounding quotes or # prefix).

Examples

42 displayString             // => "42"
inspect

Inspect the receiver.

Examples

42 inspect             // => "42"
yourself Sealed

Return the receiver itself. Useful for cascading side effects.

Examples

42 yourself            // => 42
hash

Return a hash value for the receiver.

Examples

42 hash
respondsTo: selector Sealed

Test if the receiver responds to the given selector.

Examples

42 respondsTo: #abs    // => true
fieldNames Sealed

Return the names of fields.

Examples

42 fieldNames             // => #()
fieldAt: name Sealed

Return the value of the named field.

Examples

object fieldAt: #name
fieldAt: name put: value Sealed

Set the value of the named field (returns new state).

Examples

object fieldAt: #name put: "Alice"
perform: selector Sealed

Send a unary message dynamically.

Examples

42 perform: #abs       // => 42
perform: selector withArguments: args Sealed

Send a message dynamically with arguments.

Examples

3 perform: #max: withArguments: #(5)   // => 5
subclassResponsibility

Raise an error indicating this method must be overridden by a subclass.

Examples

self subclassResponsibility
notImplemented

Raise an error indicating this method has not yet been implemented.

Use this for work-in-progress stubs. Distinct from subclassResponsibility, which signals an interface contract violation.

Examples

self notImplemented
show: aValue

Send aValue to the current transcript without a trailing newline.

Nil-safe: does nothing when no transcript is set (batch compile, tests).

Examples

42 show: "value: "
showCr: aValue

Send aValue to the current transcript followed by a newline.

Nil-safe: does nothing when no transcript is set (batch compile, tests).

Examples

42 showCr: "hello world"
isKindOf: aClass

Test if the receiver is an instance of aClass or any of its subclasses.

For class-object receivers, follows Smalltalk semantics: self class is the metaclass, so the check walks the parallel metaclass hierarchy. The parallel chain is grounded at ProtoObject class superclass == Class (ADR 0036), so the metaclass tower merges into the instance-side Class → Behaviour → Object → ProtoObject chain. As a result, Integer isKindOf: Object and Integer isKindOf: Class both return true.

Examples

42 isKindOf: Integer        // => true
42 isKindOf: Object         // => true
#foo isKindOf: Symbol       // => true
#foo isKindOf: String       // => false
Integer isKindOf: Number    // => false (metaclass chain, not instance chain)
Integer isKindOf: Number class  // => true  (Number class is in the parallel chain)
Integer isKindOf: Object    // => true (grounded — Object is reachable via the metaclass tower)
Integer isKindOf: Class     // => true (Integer class inherits from Class)
error: message

Raise an error with the given message.

Examples

self error: "something went wrong"

From ProtoObject

== other

Test value equality (Erlang ==).

Examples

42 == 42           // => true
"abc" == "abc"     // => true
/= other

Test value inequality (negation of ==).

Examples

1 /= 2             // => true
42 /= 42           // => false
class

Return the class of the receiver.

Examples

42 class            // => Integer
"hello" class       // => String
doesNotUnderstand: selector args: arguments

Handle messages the receiver does not understand. Override for custom dispatch.

Examples

42 unknownMessage   // => ERROR: does_not_understand
perform: selector withArguments: arguments

Send a message dynamically with an arguments list.

Examples

42 perform: #abs withArguments: #()   // => 42
performLocally: selector withArguments: arguments

Execute a class method in the caller's process, bypassing gen_server dispatch.

The caller takes responsibility for knowing the method does not mutate class state. Useful for long-running class methods that would otherwise block the class object's gen_server.

Limitations: only resolves methods defined directly on the target class module (does not walk the superclass chain). Class variables and self are not available to the method (nil and #{} are passed).

Examples

MyClass performLocally: #run:ctx: withArguments: #(input, ctx)
perform: selector withArguments: arguments timeout: timeoutMs

Send a message dynamically with an arguments list and explicit timeout.

The timeout (in milliseconds or #infinity) applies to the gen_server:call when the receiver is an actor. For value types, timeout is ignored.

Examples

actor perform: #query withArguments: #(sql) timeout: 30000