manvbanana.com ← PCB

PCB Language Specification

PCB Language Specification

Language Name: PCB (Pocket/Cup/Box)
Version: 0.0.1-SNAPSHOT
Runtime: JVM / Java 21
Interpreter Entry Point: Box.Box.Box / Box.GameSpaceInterpreter.PCBServer


Table of Contents

  1. Philosophy & Design Intent
  2. Program Structure
  3. Bidirectionality — The Central Concept
  4. Lexical Rules
  5. 4.1 Comments
  6. 4.2 Whitespace
  7. 4.3 Identifiers
  8. 4.4 Brackets
  9. 4.5 Dot Notation
  10. Literals & Primitive Types
  11. 5.1 Integer
  12. 5.2 Double / Float
  13. 5.3 Binary
  14. 5.4 String
  15. 5.5 Character
  16. 5.6 Boolean
  17. 5.7 Null
  18. Container Types
  19. 6.0 Container Ontology
  20. 6.1 box / xob — Pure Data Container
  21. 6.2 cup / puc — Code Execution Container
  22. 6.3 pkt / tkp — Ecosystem Containers
  23. 6.4 knt / tnk — Orientation Operators
  24. Variable Declaration & Assignment
  25. 7.1 Forward Declaration
  26. 7.2 Reverse Declaration
  27. 7.3 Assignment
  28. 7.4 Enforce (Strict Typing)
  29. Operators
  30. 8.1 Arithmetic Operators
  31. 8.2 Increment & Decrement
  32. 8.3 Comparison Operators
  33. 8.4 Logical Operators
  34. 8.5 Assignment Operators
  35. 8.6 Stream Operators
  36. Control Flow
  37. 9.1 Dot-Chain Conditionals
  38. 9.2 Forward Conditional Block
  39. 9.3 Reverse Conditional Block
  40. 9.4 Labeled Knot Blocks
  41. 9.5 Nested Labeled Knot Blocks
  42. Functions
  43. Container Operations
  44. I/O Operations
  45. Mathematical Operations
  46. HATTAG — Dynamic Type System
  47. Function Links and the Contract System
  48. FlatLand Game Integration Commands
  49. Type Keywords
  50. PCBServer API Reference
  51. Full Program Examples
  52. Operator Precedence
  53. Error Reporting
  54. Complete Keyword Reference

1. Philosophy & Design Intent

PCB is an interpreted, dynamically-typed scripting language designed to run inside (and interact with) a 2D game engine. Its core design principles are:

Bidirectionality. A PCB program runs in both the forward and backward directions simultaneously — not as a mode you choose, but as two concurrent strands operating on shared data. Direction is a global interpreter state that knots can flip at runtime. Lexical reversal (keywords spelled backward, dot-chain arguments mirrored) is a surface projection of this underlying directional traversal, not the definition of it.

Container hierarchy. PCB has six container types arranged in two mirrored hierarchies. The forward hierarchy is box → cup → pkt; the backward mirror is xob → puc → tkp. Each type in the backward hierarchy is the directional dual of its forward counterpart. tkp spans both hierarchies because it is the convergence point — the container that has run both directions over shared data and produces the final result. Knots (knt/tnk) stand apart: they act as container, orientation marker, and operator simultaneously.

Dot-chain as execution graph. The . operator is not punctuation — it is a directional composition operator that encodes both data flow and execution order. print.("hello") and ("hello").tnirp are not syntactic variants of the same thing; they are the same node in an execution graph traversed from opposite ends.

Game integration. PCB includes first-class commands for creating, moving, and destroying entities in the FlatLand game world. Scripts can be attached to game objects and run in response to game events.

Converging formalization. PCB is exploratory in origin but converging toward a formal model. The type hierarchy, lifetime system, and bidirectional invariants are intentional constraints. Where behavior and intent diverge, the intent is documented explicitly.


2. Program Structure

A PCB program is a plain text file. There is no required entry point (no main). Both execution strands — forward and backward — traverse the same statement list concurrently, operating on shared state.

// This is a comment
box x = 10
print.(x)

A PCB program is passed to the interpreter as a string. The runtime pipeline is:

Source Text
  → Scanner   (tokenize)
  → Grouper   (match brackets, second pass)
  → Parser    (build AST)
  → Resolver  (resolve variable scopes)
  → Interpreter (execute)

3. Bidirectionality — The Central Concept

A PCB program runs forward and backward simultaneously. This is not a mode selection — both directional strands operate on the same shared environment concurrently. The forward strand traverses the statement list from index 0 upward; the backward strand traverses from the last statement downward. Because they share state, operations from one strand are visible to the other; contention is first-come-first-serve.

Direction is global interpreter state. The forward flag belongs to the interpreter instance. Knots (knt/tnk) flip this flag at runtime via the oscillation model: at each knot block boundary the direction alternates, so both forward and backward statements fire on alternating passes through the body. The direction state at knot exit persists into all subsequent execution — this is the mechanism by which a program transitions between forward and backward execution mid-run.

Lexical reversal is a projection. Every keyword has an exact reverse counterpart spelled backward. This is the surface expression of directional traversal, not its definition. Forward and backward programs are structural duals under traversal inversion — same execution graph, opposite traversal direction.

Every keyword has an exact reverse counterpart — the same word spelled backward:

Forward Backward Meaning
print tnirp Output
fun nuf Function declaration
return nruter Return value
box xob Container declaration (box=transparent, xob=opaque)
cup puc Cup declaration (cup=forward execution, puc=execution inversion)
pkt tkp Pocket declaration
knt tnk Knot declaration
true eurt Boolean true
false eslaf Boolean false
null llun Null value
save evas Write to file
read daer Read from file
into otni Into (used with read)
rename emaner Rename file
move evom Move file
to ot To (used with rename/move)
and dna Logical AND
or ro Logical OR
not ton Logical NOT
open nepo Open
contains sniatnoc Membership test
add dda Add to container
remove evomer Remove from container
clear raelc Clear container
size ezis Size of container
empty ytpme Empty check
push hsup Push to stack
setat tates Set at index
getat tateg Get at index
sub bus Substring/subtraction
alive evila Pocket liveness query
sin nis Sine
cos soc Cosine
tan nat Tangent
sinh hnis Hyperbolic sine
cosh hsoc Hyperbolic cosine
tanh hnat Hyperbolic tangent
log gol Logarithm
ln nl Natural log
exp pxe Exponential
yroot toory Nth root
abs sba Absolute value
sqrt trqs Square root
floor roolf Floor
ceil liec Ceiling
round dnuor Round to nearest integer
sign ngis Signum
asin nisa Arcsine
acos soca Arccosine
atan nata Arctangent
asinh hnisa Inverse hyperbolic sine
acosh hsoca Inverse hyperbolic cosine
atanh hnata Inverse hyperbolic tangent
fresnelc clenserf Fresnel cosine integral
min nim Minimum of two scalars
max xam Maximum of two scalars
band dnab Bitwise AND
bor rob Bitwise OR
bxor roxb Bitwise XOR
bnot tonb Bitwise NOT
bleft tfelb Left shift
bright thgirb Right shift
norm mron Vector magnitude
unit tinu Unit vector
vdot todv Dot product / matrix multiply
cross ssorc Cross product
vadd ddav Vector add
vsub busv Vector subtract
vscale elacsv Scalar multiply vector
trans snart Matrix transpose
vdet tedv Matrix determinant
vinv vniv Matrix inverse
trace ecart Matrix trace
assert tressa Assertion (tressa is no-op)
consume Read file into container (forward only)

Directional Semantics (Working Model)

Direction in PCB is not text order — it is evaluation flow over a structure. A forward and backward program are duals under traversal inversion: the same execution graph, the same nodes, but traversed from opposite ends.

More precisely:

Lexical reversal (spelled-backward keywords, mirrored dot-chain argument order) is the surface projection of this model — a notational consequence of traversal inversion, not its cause. Two programs that are lexical mirrors of each other are the same execution graph; which form you write depends only on which traversal direction you intend to be "reading" the code.

Syntax Direction

The dot-chain direction reflects the execution direction:

// Forward: keyword.( argument )
print.("Hello")
sin.(89)
save.("/path").(value)

// Backward: ( argument ).keyword
("Hello").tnirp
(89).nis
(value).("/path").evas

This symmetry means a forward program and its mirror image (reversed text, reversed keywords) execute the same logic in the opposite order.

Direction Flag

The interpreter is initialized with an initial direction. This sets which strand begins first; knots may flip direction thereafter.

box.runJson(source, true);   // initialize forward
box.runJson(source, false);  // initialize backward

Via PCBServer:

{ "source": "...", "direction": "fwd" }
{ "source": "...", "direction": "bwd" }

The Interpreter class extends Thread — the two-strand concurrent model is the intended architecture. The current PCBServer implementation initializes a single direction, with the full concurrent two-strand execution model still converging.


4. Lexical Rules

4.1 Comments

Single-line comments. Two forms are accepted:

// This is a comment — double forward slash
; This is also a comment — semicolon

Both forms cause the scanner to skip the rest of the line. There are no multi-line comment delimiters.

4.2 Whitespace

Spaces, tabs, and newlines are tokenized (not silently discarded) as SPACE, TAB, NEWLINE, SPACERETURN tokens. The Grouper and Parser treat these as insignificant for structural purposes but their presence may affect certain token adjacency rules.

4.3 Identifiers

An identifier starts with a letter or underscore (a-z, A-Z, _) and may contain letters, digits, and underscores.

apple
myVariable
_hidden
x1234x

The scanner first checks whether a word matches a reserved keyword. If it does not, it is emitted as IDENTIFIER.

4.4 Brackets

PCB uses three bracket types. Each has distinct semantic meaning depending on context:

Open Close Token Names Primary Use
( ) OPENPAREN / CLOSEDPAREN Grouping, scope delimiters, knot block markers
{ } OPENBRACE / CLOSEDBRACE Block bodies, container bodies
[ ] OPENSQUARE / CLOSEDSQUARE Array/list literals, file references

Brackets are structural carriers, not operators. They delimit structure — they do not carry execution semantics of their own. The Grouper pairs each open bracket with its close bracket and assigns a unique identifier to the pair; this pairing is what makes containers and scopes addressable, not any intrinsic meaning of (, {, or [ themselves. Execution meaning belongs to the keywords and operators that appear inside or alongside bracket structures, never to the brackets themselves.

Brackets can be mixed for structural nesting (see Knots, section 6.4) and for bidirectional constructs where forward and reverse forms use mirrored bracket arrangements.

4.5 Dot Notation

The . (dot) is the directional composition operator in PCB. It encodes both data flow and execution order — it is not syntactic sugar but the primary means of constructing execution graphs.

keyword.(argument)
object.operation.(argument)
(argument).keyword
(argument).object.operation

In forward form, the operator precedes its argument. In reverse form, the argument precedes the operator. Both forms express the same execution graph node traversed from opposite ends — not two different operations, but one operation with two directional projections.

Execution graph construction. Each dot-chain expression constructs a node in the execution graph. The . encodes two things simultaneously: data flow (what value passes between nodes) and execution order (which node evaluates first). In forward traversal, the leftmost node evaluates first and its result flows right. In backward traversal, the rightmost node evaluates first and its result flows left. The graph structure is the same; only the traversal direction differs.

Multi-step chains build a linear sub-graph:

save.("/path/f.txt").(value)
// node 1: evaluate value → node 2: evaluate "/path/f.txt" → node 3: save

In reverse:

(value).("/path/f.txt").evas
// same three nodes, opposite traversal order
print.("hello")        // forward: print the string "hello"
("hello").tnirp        // reverse: the string "hello" fed into reverse-print

sin.(89)               // forward: sine of 89
(89).nis               // reverse: 89 into reverse-sine

save.("/path/f.txt").(value)     // forward: save
(value).("/path/f.txt").evas     // reverse: save

5. Literals & Primitive Types

5.1 Integer

Whole numbers, no decimal point. Token type: INTNUM.

0
42
10001
-5

5.2 Double / Float

Numbers with a decimal point. Token type: DOUBLENUM.

3.14
6.33
0.5
-1.0

5.3 Binary

Binary numbers are written with a b prefix and optional b suffix. Token type: BINNUM.

b0110         // binary 6 (prefix form)
0110b         // binary 6 (suffix form)
b011          // binary 3

Binary values can be used in arithmetic and as arguments to mathematical functions:

print.(b011!)    // factorial of binary 3 = 6

5.4 String

Strings are enclosed in double quotes. Newlines within a string are permitted. Token type: STRING.

"hello world"
"running forward"
""
"contains spaces and \n newlines"

Strings support + concatenation:

print.("hmm : " + hmm)

5.5 Character

Single characters enclosed in single quotes. Token type: CHAR.

'a'
'Z'
'\n'

5.6 Boolean

Two values. Both forward and reverse forms exist.

Value Forward Reverse
True true eurt
False false eslaf
true
false
eurt
eslaf

5.7 Null

Four null tokens exist — two forward forms and two reverse forms:

Forward Reverse Notes
null llun Primary null
NULL LLUN Uppercase alias
nill llin Secondary null variant
NILL LLIN Uppercase secondary variant
box x = null
box y = nill

5.8 Primitive Boxing

Everything in PCB is a box. Primitives are not stored raw — at every storage boundary the runtime wraps them in a single-item BoxInstance. Computation may produce raw Java values, but they are boxed before being placed into:

Auto-unboxing happens transparently in binary operators (parseBinData): a BoxInstance on either operand side is unwrapped before arithmetic or comparison. The result of the operator is raw; it is re-boxed only at the next storage boundary.

Key rules:

Input to Boxer.box() Output
Double, Integer, String, Boolean BoxInstance([value])
null BoxInstance([null])distinct from empty BoxInstance
Any Instance (container) Passed through unchanged — containers are never re-wrapped
Non-boxable Java object null; error sent to NON/LIMBO sink

Boxer.unbox(v) extracts the value from a single-item BoxInstance; returns null for an empty one; throws RuntimeError for a multi-item one; is a no-op for raw primitives.


6. Container Types

Containers are the fundamental building blocks of PCB. A variable is always a container — primitives are stored inside containers. The six container types form two mirrored hierarchies.

6.0 Container Ontology

PCB containers are organized as a bidirectional type hierarchy. The notation := means "is a", n means "and a", means "acts as":

Forward hierarchy:
  box  :=  box
  cup  :=  cup  n  box
  pkt  :=  pkt  n  cup  n  box

Backward (reverse) hierarchy:
  xob  :=  xob  n  box
  puc  :=  puc  n  xob  n  box
  tkp  :=  tkp  n  puc  n  xob  n  pkt  n  cup  n  box

Orientation operators (not in IS-A hierarchy):
  knt  ⊃  container  n  orientation  n  operator
  tnk  ⊃  container  n  orientation  n  operator

Each forward type has a backward mirror: boxxob, cuppuc, pkttkp. tkp spans both hierarchies because it is the convergence point — the result of forward and backward execution meeting. knt and tnk are not containers in the IS-A sense; they act as container, orientation marker, and operator simultaneously.

Java interface hierarchy (runtime type markers):

IBox
 ├── ICup  extends IBox
 │    └── IPkt  extends ICup
 │         ├── ITkp  extends IPkt
 │         ├── IKnt  extends IPkt
 │         └── ITnk  extends IPkt
 └── IXob  extends IBox
      └── IPuc  extends IXob, ICup

BoxInstance    implements IBox
CupInstance    implements ICup
PocketInstance implements IPkt
KnotInstance   implements IKnt
TonkInstance   implements ITnk
TkpInstance    implements ITkp
XobInstance    implements IXob   (also extends BoxInstance)
PucInstance    implements IPuc   (also extends CupInstance)

Every PCB container is instanceof IBox. This is the runtime enforcement of the "everything is a box" invariant.

Intended semantics (working model):

Container Role
box Pure data container — fully transparent: all inspection and mutation ops permitted (getat, setat, sub, contains, remove, push, pop, add, size, empty, clear)
xob Opaque data container — push/pop/add/size/empty/clear permitted; getat/setat/sub/contains/remove throw RuntimeError("xob: 'op' not permitted — xob is opaque")
cup Code execution container — normal forward execution; body traversed forward
puc Execution-inversion cup — same container interface as cup; when the body executes, the interpreter's invertedMode flag is toggled so every instruction runs in its inverse form (PLUS↔MINUS, TIMES↔FORWARDSLASH, ><, AND↔OR, push/add→pop, pop→no-op, setat→add, etc.). Double-nesting (puc inside puc body) restores normal execution
pkt / tkp Holds code structures without executing them; both have independent tick-based Flow processing; bidirectional seam crossing: tkppkt at heat death or natural expiry; pkttkp on natural expiry; user-kill destroys without crossing
knt / tnk Conditional control flow — knot runner, can flip global execution direction

6.1 box / xob — Pure Data Container

box is the transparent data container. All operations are permitted.

xob is the opaque data container — structural inspection is blocked. Only interface operations that treat the container as an abstract queue are allowed. This enforces an interface-only discipline: you can move things in and out but cannot look inside or modify specific positions.

Declaration (box — transparent):

box name = value
box name = [element1, element2, element3]
box name = (element1 element2 element3)

Reverse declaration (xob — opaque):

value = name xob

Permitted operations:

Operation box xob
push
pop
add
size
empty
clear
getat ✗ RuntimeError
setat ✗ RuntimeError
sub ✗ RuntimeError
contains ✗ RuntimeError
remove ✗ RuntimeError

Examples (from source):

box b = 0
box apple = 0
box x = 0
box y = 0

10 = i xob
9 = j xob

A box can be initialized with a nested structure:

c{ b{ 5 p{ 5 }p 6 4 3 2}b f{4}f}c

6.2 cup / puc — Code Execution Container

cup is the forward code execution container. Its body runs normally.

puc is the execution-inversion cup. The container interface (push/pop/add/remove/getat/setat/sub/clear/empty/size) is identical to cup — the difference is purely in execution. When execute() is called (e.g. when a flow bootstraps the puc inside a pkt/tkp), the interpreter's invertedMode flag is toggled before the body runs and restored after. This inverts every operation inside the body:

Body instruction Normal (cup) Inverted (puc)
a + b addition subtraction
a - b subtraction addition
a * b multiplication division
a / b division multiplication
a > b greater-than less-than
a < b less-than greater-than
a >= b
a <= b
and / dna AND OR
or / ro OR AND
?x (logical NOT) !truthy(x) truthy(x) (drops negation)
push c v push v to front pop from c
add c v append v to back pop from c
pop c remove from front no-op
remove c i remove at i pop from c
getat c i get at i pop from c
setat c i v set at i add v to c

Double inversion: A puc inside another puc's body toggles invertedMode twice, restoring normal execution — puc(puc(body)) = body.

Directionality collapse on divide: / (FORWARDSLASH, div(left, right)) and \ (BACKSLASH, div(right, left)) both invert to multiplication. Because multiplication is commutative, a * b and b * a produce the same value, so no behavioral difference results — but the forward/backward distinction between the two divide forms is not preserved after inversion. This is an accepted consequence of having no directional multiply tokens to invert into.

Declaration (cup — forward execution):

cup name = (value)
cup name = [value1, value2]

Reverse declaration (puc — execution inversion):

(value) = name puc

Enforced cup:

enforce cup buzz = (666)

Examples (from source):

enforce cup buzz = (666)
print.("buzz " + buzz)
buzz = (777)
print.("buzz " + buzz)

6.3 pkt / tkp — Ecosystem Containers

pkt (forward) and tkp (reverse) are the ecosystem container types. Both are self-contained execution ecosystems — they hold elements, process flows independently on their own threads, and manage their own lifecycle. They share the same underlying element model but have distinct lifecycles.

pkt — Ecosystem Container (Forward)

pkt is the forward ecosystem container. Elements are pushed and popped in LIFO order and it supports an independent Flow tick engine.

Declaration (seam form — { and ( interleave; } closes before )):

{ pkt name = ( values } body code )

// empty pocket, no initial values:
{ pkt name = ( } )

Enforced pkt:

{ enforce pkt boff = ( "hello" } )

Stack operations (seam form):

{ pkt mike = ( }
    push.mike.10
    push.mike.20
    pop.mike
)

tkp — Ecosystem Container (Reverse)

tkp is the reverse form of pkt. It is not simply a backward stack — it has a distinct lifecycle: it runs a tick loop over its contents independently of the main program. When it reaches heat death (all work done, all flows exhausted) or when its natural lifetime expires, it undergoes a seam crossing and becomes a pkt. If a user explicitly kills it (= null), it is destroyed without transformation.

Declaration:

// Seam structural form — mirror of pkt; parens and braces overlap in reverse order:
( body code { values ) = name tkp }

Independence: Flow processing is completely independent of program execution. When a tkp is declared, the main program continues immediately — the tkp tick loop runs on its own thread concurrently. The environment is updated asynchronously when seam crossing occurs.

Tick loop: On each tick, each active Flow scans the body in its current direction. Flow's job is to bootstrap executable items and respond to flow signals. What it does depends on what it finds:

Item found What Flow does
Flow object in body Scavenged — chain absorbed, item removed
String(".") single period Flips active flow's direction (FORWARD↔BACKWARD), period consumed
Bare bracket string "(" "{" ")" "}" Synthesizes period → new independent Flow promoted to flows list (open=fwd, close=bwd), consumed
PocketOpen/CupOpen/PocketClosed/CupClosed expr in body Same as bare bracket — synthesize period, new independent Flow, label discarded, consumed
String with embedded connectors "(." ".)" etc. Chain absorbed, string added as cargo
cup / puc Bootstrapped — re-executes originalBody, cup consumed, token spent
knt / tnk BootstrappedKnotRunner.runWithRouting() runs knot and routes output via interp.environment; token spent
Nested pkt / tkp Ignored — Flow does not enter them

Bootstrapping vs routing are separate concerns. Flow starts execution (bootstrap). Where the output goes is routing — handled entirely by KnotRunner using interp.environment to look up the target container. Flow does not participate in routing.

Heat death is reached when all chain tokens are spent and no actionable items remain (no dormant Flow connectors or objects, no unexecuted cups or knots, no bracket signals).

Cup/puc bootstrap detail: When Flow bootstraps a cup or puc, the cup re-evaluates its original pre-construction statements (stored as originalBody). This re-execution runs in the current environment context. The cup is consumed (removed from the body) after execution.

knt/tnk bootstrap and routing detail: When Flow bootstraps a knt or tnk, KnotRunner.runWithRouting(forKnot) is called. It resolves the route target via resolveRouteTarget(), runs the knot, then injects results into the named target container looked up in interp.environment. Routing is the knot's own responsibility — it does not feed back into Flow processing. (UNSTABLE — routing semantics not fully validated)

Period as direction inverter: A standalone . in the body flips the active flow's direction. All subsequent scanning by that flow proceeds in the opposite direction. Syntax: ( . "hello") or ( . , "hello") — the existing DOT token is reused; primative() returns Expr.Literal(".") for a standalone dot; evaluateBody() reduces it to String(".") before the tick loop sees it.

Period as remote direction signal via knt/tnk: A . inside a knt or tnk body produces "." as a routed result. When KnotRunner.runWithRouting() injects that result into a pkt or tkp target, the tick loop in the target flips its active flow's direction. This means a knot can remotely flip the flow direction of another container — the period is both a local direction inverter and a routable direction signal. In box, cup, or puc targets, "." lands inertly in the body and does nothing.

Seam crossing (tkp → pkt): At heat death, or when the tkp's natural lifetime expires (TRAVERSAL exhausted, DEPENDENT dependency died, CONDITIONAL expression became false), the tkp becomes a pkt, inheriting all contents and any remaining lifetime budget. The environment entry for the variable is updated in place. If the resulting pkt contains flows, it begins its own independent tick loop. User-kill (= null) destroys without transformation.

pkt tick loop and seam crossing (pkt → tkp): pkt has the same tick engine as tkp. At pkt heat death the pkt simply stops ticking — no seam crossing. When a pkt's natural lifetime expires (TRAVERSAL, DEPENDENT, or CONDITIONAL), it undergoes a seam crossing and becomes a tkp, inheriting body and remaining lifetime budget. User-kill (= null) destroys without transformation. INDEFINITE pockets never seam-cross naturally.

Bidirectional seam crossing summary:

Event pkt result tkp result
Natural lifetime expiry → seam-crosses to tkp → seam-crosses to pkt
Heat death stops ticking (stays pkt) → seam-crosses to pkt
User-kill (= null) destroyed, no transformation destroyed, no transformation
INDEFINITE never expires naturally never expires naturally

6.3.1 Pocket Lifetime

Both pkt and tkp support a lifetime annotation written immediately after the closing ) of the pocket literal. When the lifetime expires naturally, the pocket seam-crosses to the opposite type. User-kill (= null) destroys without transformation.

Syntax Kind Meaning
(...).* INDEFINITE Lives forever (default)
(...).N TRAVERSAL Lives for exactly N container operations
(...).^(name) DEPENDENT Lives as long as the named pocket is alive
(...).^{expr} CONDITIONAL Lives as long as expr evaluates to true

Examples:

// Indefinite — lives forever (same as no annotation)
{ pkt a = ( 1 2 3 }.* )

// Traversal — seam-crosses to tkp after 5 container operations
{ pkt counter = ( 1 2 3 }.5 )

// Dependent — seam-crosses to tkp when pocket b dies
{ pkt a = ( 1 2 3 }.^(b) )

// Conditional — seam-crosses to tkp when n reaches 0
{ pkt data = ( 1 2 3 }.^{n > 0} )

Lifetime transfer: When seam crossing occurs in either direction, the remaining lifetime budget carries over. For TRAVERSAL lifetimes, the remaining count (not the original) is transferred. INDEFINITE lifetimes carry through unchanged.

Proactive enforcement: DEPENDENT and CONDITIONAL lifetimes are checked after every statement — a pocket seam-crosses within one statement of its condition becoming false or its dependency dying. TRAVERSAL decrements only on explicit container operations and seam-crosses when the count reaches zero.

6.3.2 Explicit Death

Assigning null to a pocket variable kills it:

myPocket = null

This is the only explicit death mechanism — there is no destroy keyword.

Cascading death to nested pockets: When a pocket is destroyed (via = null), the runtime walks its body and calls beginDeath() on any PocketInstance items found directly inside the body (one level deep). Grandchildren — pockets nested inside those nested pockets — are not affected by this walk. They will eventually be affected only if their own dependency or condition lifetime triggers separately.

6.3.3 Death State Transition

This section covers user-kill only (= null). Natural lifetime expiry seam-crosses instead of dying — see §6.3 bidirectional table.

A user-killed pocket does not instantly cease to exist. It passes through two stages:

Stage State
1 (stripping) Flows stripped, tick stopped, body readable but inert. All mutations blocked. isAlive() returns false.
2 (dead) Body cleared. Fully static. All operations return null/false/0.

Stage 1 begins on null assignment. Stage 2 is advanced by probeLifetimes() after the next statement, or immediately on the next mutation attempt.

Cascading death: There is no orphaning. If pocket B has lifetime .^(A) and A is user-killed (enters stage 1), B detects !A.isAlive() and seam-crosses immediately. Death propagates through the dependency tree — dependent pockets seam-cross rather than dying when their dependency is killed by the user.

6.3.4 Querying Liveness

// Forward: x.alive.()   — returns true if x is alive
box isLive = x.alive.()

// Backward: ().evila.x
box isLive = ().evila.x

Returns true for all non-pocket container types (they cannot be destroyed).

6.4 knt / tnk — Orientation Operators

knt and tnk are not containers in the IS-A sense. They act as container + orientation marker + operator simultaneously. They are the conditional control-flow mechanism of PCB.

A knot body is executed by a KnotRunner that traverses statements direction-aware (forward or backward based on current forward state). KnotRunner uses an oscillation model: at the false endpoint of a forward condition, if the condition is still true, the runner flips direction to backward and traverses the body in reverse — this is the pass on which backward statements (tnirp, rav, xob, etc.) fire. When the backward pass reaches the condition's inner bracket (indexTrue), if the condition is still true, the runner flips back to forward and re-enters the body. Oscillation continues until the condition fails at either endpoint. The execution direction at block exit persists as global interpreter state. Variable declarations (box/xob) are pre-initialized before setup regions run via initializeAllDeclarations(), which forces forward=true for box and forward=false for xob so each initializer evaluates in the correct direction.

knt is the forward form and fires only when forward == true; tnk is the backward mirror and fires only when forward == false. runTonk() delegates to runKnot() — since direction is already backward when tnk executes, traversal is correct.

ControlGraph model (design target): In the formal ControlGraph architecture, a knt/tnk body is a subgraph. Each cup/pocket bracket inside the body is a ControlNode. Condition regions are cross-family boundary regions — their TRUE edge crosses the family boundary, their FALSE edge returns via ownership. Direction is a post-edge effect: the edge is selected first, then direction may flip. Conditions trigger only via immediate adjacency crossing; ownership jumps and unwind do not trigger conditions. Reachability (which nodes can ever be reached) is a static graph property, computed once. The DefaultTraversalGraph (the actual runtime graph) is a filtered subset of the ReachabilityGraph — it can only remove edges, never add them.

Inside a pkt or tkp, knt/tnk act as conditional data routers. When a flow triggers a knt or tnk in a pocket/tkp body, the knot runs, produces results, and injects those results into the container named by the true or false branch label (resolved from the environment).

Knot / Tonk Routing Semantics

knt routes within the active family and preserves container directionality. It allows self-routing and forward promotion into richer container forms.

knt routes

box -> box
box -> cup

cup -> cup
cup -> pkt

pkt -> pkt

xob -> xob
xob -> puc

puc -> puc
puc -> pkt

tkp -> tkp

tnk routes across forward/reverse counterparts and supports packet collapse/extraction from pkt into box.

tnk routes

box <-> xob

cup <-> puc

pkt <-> tkp
pkt -> box

Interpretation

UNSTABLE: Pocket transport routing semantics are not fully validated. The interaction between condition evaluation, knot execution, and data injection into target containers is subject to change. Runtime routing is currently only implemented for pkt and tkp source containers.

Declaration:

knt name = label{inner(}label)inner
knt joy = v{d(}v)d

Reverse form:

tnk name = ...

Examples (from source):

hey{ hi( }yeh )ih
// hey and yeh are labels; brackets form the knot structure

knt joy = v{d(}v)d
// joy holds the knot structure v{d(}v)d

Cups and pockets inside a knot body serve as condition branch markers — the interleaving of { and ( brackets defines the true/false branch structure that KnotRunner uses to navigate conditionally.


7. Variable Declaration & Assignment

7.1 Forward Declaration

box name = value
cup name = value
{ pkt name = ( values } body )
knt name = structure

The type keyword (box, cup, pkt, knt) precedes the identifier.

Multiple declarations may appear on one line:

box x = 0 box y = 0 box z = 0

7.2 Reverse Declaration

In reverse form, value and type keyword swap positions:

value = name xob     // reverse box
value = name puc     // reverse cup

Examples (from source):

10 = i xob
9 = j xob
8 = k xob

0 = kiwi xob
0 = b xob
6 = d xob

7.3 Assignment

Forward assignment:

name = value

Reverse assignment:

value = name

Chained assignment (both forward and reverse in one expression):

hmm = (pear = sin.("34").tnirp = apple) = mmh

This assigns mmhapplesin.("34")pearhmm in a chain. The evaluation flows from the innermost expression outward.

Cross-assignment (pocket initialization):

mike = (6 7 8 9) = ike

Assigns the structure (6 7 8 9) to both mike and ike.

7.4 Enforce (Strict Typing)

The enforce keyword makes a container strictly typed — it will reject assignments of the wrong type.

enforce cup buzz = (666)
{ enforce pkt boff = ( "hello" } )

Without enforce, containers accept any value assignment. With enforce, the declared container type is locked in and type mismatches produce an error.


8. Operators

8.1 Arithmetic Operators

Symbol Operation Example
+ Addition 5 + 6
- Subtraction 7 - 3
* Multiplication x * y
/ Division (forward) x / y
\ Division (backward) x \ y
% Modulo x % y
^ Power/Exponentiation x ^ 2

8.2 Increment & Decrement

PCB uses ++ and -- as prefix or postfix operators. Multiple consecutive ++ or -- tokens compound:

x++         // increment x by 1 (postfix)
++x         // increment x by 1 (prefix)
x--         // decrement x by 1 (postfix)
--x         // decrement x by 1 (prefix)

Multiple increments are written as consecutive ++ pairs:

x++++        // increment x by 2
++++++apple  // increment apple by 3 (prefix form)
apple++++++++  // increment apple by 4 (postfix form)

Decrement chains:

--i--        // decrement i (both prefix and postfix appear in bidirectional blocks)

From source:

++++++apple        // +3 to apple (forward block)
apple++++++++      // +4 to apple (reverse block reads this as +4)
++++++++pear       // +4 to pear
pear++++++++       // +4 to pear

8.3 Comparison Operators

Symbol Meaning Reverse
== Equal =!
!= Not equal
> Greater than =<
< Less than =>
>= Greater or equal =>=
<= Less or equal =<=

Examples:

b < 4
x < 3
y < 4
z < 5
apple < 100
pear < 50
kiwi < 100
d > 2
d == 3
i > 4
j > 2
k > 3
3 < 4 and 5 < 4
3 > 4 or 5 < 4

8.4 Logical Operators

Forward Reverse Meaning
and dna Logical AND
or ro Logical OR
not ton Logical NOT

Examples (from source):

print.(3 > 4 and 5 < 4)    // false
print.(3 < 4 and 5 < 4)    // true
print.(3 < 4 and 5 > 4)    // false (5 is not > 4)
print.(3 > 4 or 5 < 4)     // false
print.(3 < 4 or 5 < 4)     // true

true or false
false and true
book or tree
time and crunch

8.5 Assignment Operators

Symbol Meaning
= Assign
+= Add and assign
-= Subtract and assign
*= Multiply and assign
/= Divide and assign (forward)
\= Divide and assign (backward)
%= Modulo and assign
^= Power and assign
== Equality test
!= Not-equal test
=! Reverse not-equal
=> Reverse less-than (equal to <)
=< Reverse greater-than (equal to >)

Examples:

"hello" += 7
book -= nice

8.6 Stream Operators

PCB has two stream operators for file I/O:

Symbol Name Direction Meaning
>>> EXPELL Forward Write/expel a value to a file
<<< CONSUME Forward Read/consume a file into a variable
// Expel: write c.1 to file
c.1 >>> ["/home/wes/workspace/BoxInterpreter/resources3/DUCKCOP.txt"]

// Consume: read file into c.1
c.1 <<< ["/home/wes/workspace/BoxInterpreter/resources3/DUCKCOP2.txt"]

The file path is enclosed in [] brackets when used with stream operators.


9. Control Flow

Design model: PCB control flow is formally defined as traversal over a static ControlGraph — a directed graph of ControlNode objects (brackets and labels) connected by explicit typed edges: adjacency, ownership, condition (true/false), entry (descend into nested interval), and unwind (exit to enclosing boundary). The graph is built once from structure and never modified at runtime. Runtime execution selects edges; direction is a post-edge effect, not an input to edge selection. Conditions trigger only via immediate adjacency crossing — not via ownership jumps or unwind. This is the target architecture; the current runtime is KnotRunner with the oscillation model (see Technical Documentation §6.7 and §6.16).

9.1 Dot-Chain Conditionals

PCB's conditional execution uses dot-chain bracket syntax — no conditional keywords exist. Conditions are pocket expressions (...) and bodies are cup expressions {...}, chained with ..

Forward conditional (If) — fires when forward == true:

(condA).{body A}.(condB).{body B}.{else}

Minimal form (single branch, no else):

(x > 0).{print.(x)}

Full form:

(x > 0).{print.("positive")}.(x < 0).{print.("negative")}.{print.("zero")}

Reverse conditional (Fi) — fires when forward == false:

The structural mirror — the entire chain is written reversed:

{else}.{body B}.(condB).{body A}.(condA)

Same logical evaluation order as the forward form; the source text is the forward form spelled backward.

Bidirectional conditional (Ifi) — fires in both directions:

(condA).{body}.(condB)

Extended ifi chain:

(condA).{body A}.(condB).{body B}.(condC)

Each body has a separate forward-guard (the pocket to its left) and the chain's final pocket closes the backward-guard sequence.


PCB has no loop keywords. Repeating execution is expressed through the bracket/knot structure below. The ( { condition ) body } bracket interleaving is a conditional knot block structure in which the KnotRunner oscillates through the body while the condition holds, firing forward and backward statements on alternating passes.

9.2 Forward Conditional Block

The forward conditional block evaluates a condition and oscillates through the body while it holds.

Syntax:

( { condition )
    // body
}

( opens the block structure. { opens the condition header. ) closes the ( at the seam — brackets overlap here. Body follows. } closes the {. Initialization can sit between ( and {.

Simple example (from source — script.txt):

(box b = 0 {b < 10) print.("\n hello world") b++}

Expanded form:

( box b = 0 { b < 4 )
    print.("forward ")
    ("backward ").tnirp
    b++
}

9.3 Reverse Conditional Block

The reverse conditional block mirrors the forward block — brackets and condition placement are swapped.

Syntax:

{
    // body
( condition } 0 = variable xob )

The condition appears at the closing side in (condition}. The variable reset follows. The entire reverse block is wrapped in an outer (...).

Example (from source — TEST/TESTWORKING):

("running backward ").tnirp
{
    ("backward ").tnirp
    print.("forward ")
    b++
( b < 4 } 0 = b xob )
print.("running forward ")

In reverse mode, this is entered from the closing condition header upward.

9.4 Labeled Knot Blocks

Labels allow multiple knot blocks to share a scope and reference each other for nested entry/exit. A label is any identifier prefixed before a bracket.

Forward labeled knot block:

label{ condition )label
    // body
( exitCondition }label

The opening label goes before {, the same label follows the ). The closing }label ends the block.

Example (from source — TEST/TESTWORKING):

x( box f = 0
    a{ f < 4 )x
        ++c
        print.("forward")
        ("drawkcab").tnirp
        f++
    z( d == 3 }a c < 5 }b
0 = c xob 6 = d xob )z

Here: - x( opens labeled scope x - a{ opens labeled block a - )x closes labeled scope at the opening side matching label x - }a closes labeled block a - )z closes labeled scope z

9.5 Nested Labeled Knot Blocks

Multiple blocks are nested via multiple labels. Each label independently tracks its scope.

Example (from source — TEST/TESTWORKING):

("running backward").tnirp
x(
    box apple = 0
    y(
        box pear = 0
        a{ apple < 100 )x
        b{ pear < 50 )y

            ++++++apple
            print.("Hello")
            apple++++++++
            ("World").tnirp
            pear++++++++
            print.("how")
            ++++++++pear
            ("are").tnirp
            ++++++++kiwi
            print.("you")
            kiwi++++++++
            ("today").tnirp

        z( kiwi < 100 }a pear < 100 }b
0 = kiwi xob )z
print.("running forward")

Label map for this example:

x(         — opens scope x
y(         — opens scope y
a{         — opens block a
b{         — opens block b
)x         — closes condition side of scope x
)y         — closes condition side of scope y
}a pear    — closes block a with condition
}b         — closes block b
)z         — closes scope z

Second nested example (from source — TEST/T):

("running backward").tnirp
(box x = 0 box y = 0 box z = 0
    a{ x < 3  b{ y < 4  c{ z < 5 )
        ++x++
        ("marauder").tnirp
        ++y++
        ("rock").tnirp
        ++z++
        ("mauve").tnirp
        --i--
        print.(sinh.(25).nat).tnirp
        --j--
        ("hicory").tnirp
        --k--
    ( i > 4 }a  j > 2 }c  k > 3 }b
10 = i xob  9 = j xob  8 = k xob )
print.("running forward")

This creates three nested blocks a, b, c opened and closed on the same lines.


10. Functions

Functions are declared with fun and nuf as opening and closing brackets, with all parts dot-chained. There is no separate forward and backward function form — a single declaration names both a forward entry point and a backward entry point, sharing one cup body.

10.1 Declaration Syntax

Full bidirectional function:

fun.forwardName.[type param, type param].{shared body}.[param type, param type].backwardName.nuf

Forward-only (no backward name, no nuf):

fun.forwardName.[type param, ...].{body}

Backward-only (no fun, starts with body):

{body}.[param type, ...].backwardName.nuf

No-parameter variants:

fun.greet.[].{print.("hello")}
fun.greet.[].{print.("hello")}.[].teerG.nuf

10.2 Parameter Lists

Forward and backward parameter lists use opposite ordering — a direct expression of PCB's lexical reversal rule:

Valid parameter types: box, pkt, cup, knt, xob, tekcop, puc, tonk

Example:

fun.add.[box a, box b].{return.(a + b)}.[b box, a box].dda.nuf

10.3 Shared Cup Body

Both the forward name and the backward name call the same cup {...}. When the forward function is called (forward == true), return.(expr) fires. When the backward function is called (forward == false), (expr).nruter fires. Forward and backward statements in the same body fire on the appropriate pass.

fun.greet.[box name].{
    print.("Hello, " + name)
    (name + " ,olleH").tnirp
    return.(name)
    (name).nruter
}.[name box].teerG.nuf

10.4 Calling Functions

Forward call — fires when forward == true:

forwardName(arg1, arg2)

Backward call — fires when forward == false:

(arg1, arg2).backwardName

Direction is enforced at runtime: calling a forward function while forward == false (or vice versa) throws a direction-mismatch error.

10.5 Return

Forward return — fires when forward == true:

return.(expression)

Backward return — fires when forward == false:

(expression).nruter

A function with no return statement returns null. Both return and nruter can appear in the same cup body — each fires only in its respective direction.


11. Container Operations

11.1 Forward Operations

All container operations use dot-chain syntax.

Operation Syntax Description
Add add.container.value Add value to box/cup
Remove remove.container Remove last element
Clear clear.container Remove all elements
Size size.container Number of elements
Empty empty.container True if no elements
Push push.container.value Push to pkt (stack)
Pop pop.container Pop from pkt
Set at index setat.container.index.value Set value at position
Get at index getat.container.index Get value at position
Sub sub.container.start.end Subrange
Alive x.alive.() True if pkt/tkp is alive (not destroyed)

11.2 Reverse Operations

Forward Reverse
add dda
remove evomer
clear raelc
size ezis
empty ytpme
push hsup
pop (no reverse listed)
setat tates
getat tateg
sub bus
alive evila

11.3 Deep Indexing

Nested container elements are accessed via dot-separated integer indices:

c.0.3.0       // container c, index 0, sub-index 3, sub-sub-index 0
c.1           // container c, index 1
2.0.c         // reverse form: c at index 0 at index 2

Reading into a deep index:

read.("/path/file.txt").into.(2.0.c)
print.(2.0.c)
read.("/path/file.txt").into.(1.c)
print.(c.1)

Writing to a deep index:

c.0.3 = 10001
print.("c.0.3.0 " + c.0.3.0)

Reverse deep set:

{99} = 0.0.0.buzz

Example (from source — resources/test4.txt):

c{ b{ 5 p{ 5 }p 6 4 3 2}b f{4}f}c
print.("c.0.3.0 " + c.0.3.0)     // prints original value
c.0.3 = 10001
print.("c.0.3.0 " + c.0.3.0)     // prints 10001
print.("c: " + c)

11.4 Contains / Membership Test

Tests whether a container holds a value.

Forward:

contains container value
() contains open "a"
[] contains 6

Reverse:

(value).sniatnoc.container
((print.("lala")).sniatnoc.j).tnirp
(((5).nis).sniatnoc.j).tnirp
((false).sniatnoc.j).tnirp
(("hello").sniatnoc.j).tnirp

From source (TEST/TEST):

().boi = k xob
boi{sin.(5).nis  print.("lala") "hello"}iob

().poi = x xob
poi[sin.(5).nis  false "hello"]iop

Different bracket types ({ }, ( ), [ ]) after the label create different container variant forms.

11.5 consume (<<<) — Read File into Container

The consume operator reads a file's lines directly into a container body. Each non-blank line is parsed as a PCB value (null, true, false, a double, or a string) and appended to the container.

Syntax:

consume.(container, "filepath")

The consume operator accepts any container type as its first argument: box, cup, pkt, knt, tnk, or tkp.

Stream operator form (equivalent):

container <<< ["filepath"]

Examples:

box data = []
consume.(data, "/path/to/values.txt")
// Each non-blank line of values.txt is now appended to data

// Stream operator form:
data <<< ["/path/to/values.txt"]

// Works with pkt too:
{ pkt stack = ( }  )
consume.(stack, "/path/to/items.txt")

Parsing rules for each line: - Blank lines are skipped entirely. - null, NULL, nill, NILL → null value. - true, eurt → boolean true; false, eslaf → boolean false. - A line that parses as a number → double. - Anything else → string.

There is no reverse (emrosnoc) form — consume is a forward-only operation.


12. I/O Operations

12.1 Print

The most fundamental output operation. Both forward and reverse forms exist.

Forward:

print.("hello world")
print.(variable)
print.(sin.(89))
print.("value: " + value)
print.(5)

Reverse:

("hello world").tnirp
(variable).tnirp
(sin.(89)).tnirp      // NOTE: the .tnirp is the reverse print
("value").tnirp
["69"].tnirp

Bidirectional output (from source — TEST/TESTWORKING):

("running backward ").tnirp   // reverse print — executes in backward pass
print.("running forward ")    // forward print — executes in forward pass

A program typically opens with ("...").tnirp and closes with print.("...") — the reverse print announces the backward execution, the forward print announces the forward execution.

12.2 Save

Write a value or container to a file.

Forward:

save.("/path/to/file.txt").(value)
save.("/path/to/file.txt").(container.index)
save.("/path/to/folder").()    // create a folder (empty save)

Reverse:

(value).("/path/to/file.txt").evas
(container.index).("/path/to/file.txt").evas
().("/path/to/folder").evas

Stream operator form:

save.("path for fun").["lfkjaslkafsjdhfl"]

Examples (from source):

save.("/home/wes/workspace/.../resources3/newDIR/FLIRT.txt").(2.0.c)
save.("/home/wes/workspace/.../resources3/newDIR/GHOSTS.txt").(c)
save.("/home/wes/workspace/.../resources3/newFOLDER").()

(1.0.c).("/home/wes/workspace/.../resources3/DUCKCOP.txt").evas
(c.0).("/home/wes/workspace/.../resources3/DUCKCOP2.txt").evas
().("/home/wes/workspace/.../resources3/ITSAFOLDER").evas

12.3 Read

Read a file's contents into a container or variable.

Forward:

read.("/path/to/file.txt").into.(variable)
read.("/path/to/file.txt").into.(container.index)

Reverse:

variable.otni.("/path/to/file.txt").daer
(pear{}raep).otni.("/path/to/file.txt").daer

Examples (from source):

read.("/home/wes/workspace/.../resources3/move.txt").into.(2.0.c)
print.(2.0.c)
read.("/home/wes/workspace/.../resources3/move.txt").into.(1.c)
print.(c.1)

// Reverse form:
(pear{}raep).otni.("/home/wes/workspace/.../resources3/move.txt").daer
print.(pear)

// Alternate reverse form:
variable.otni.("pathandfilename2").daer

12.4 Rename

Rename a file or directory.

Forward:

rename.("/old/name.txt").to.("/new/name.txt")

Reverse:

("/new/name.txt").ot.("/old/name.txt").emaner

Examples (from source):

rename.("/home/wes/.../resources3/move.txt").to.("/home/wes/.../resources3/moving.txt")
("/home/wes/.../resources3/movingTWOELECTRICBOOGALOO.txt").ot.("/home/wes/.../resources3/moving.txt").emaner

("PATH1").ot.("pathandfilename1").emaner
rename.("bjcity").to.("wendy")

12.5 Move

Move a file to a new location.

Forward:

move.("/old/path/file.txt").to.("/new/path/file.txt")

Reverse:

("/new/path/file.txt").ot.("/old/path/file.txt").evom

Examples (from source):

move.("/home/wes/.../resources/move.txt").to.("/home/wes/.../resources2/move.txt")
("/home/wes/.../resources3/move.txt").ot.("/home/wes/.../resources2/move.txt").evom

("PATH").ot.("pathandfilename").evom
move.("hibityJibity").to.("billy")

12.6 assert / tressa — Assertion

assert checks a condition and throws a RuntimeError if it is falsy. tressa is the backward keyword and is always a no-op.

Forward:

assert.(condition)

If condition is false or null, the runtime throws:

RuntimeError: assertion failed: <value>

Truthy non-boolean values (non-null numbers, non-empty strings, container instances) pass the assertion without error. There is no puc inversion defined for assert.

Backward:

(condition).tressa.

tressa is a no-op in all cases — it never throws, regardless of the condition value. It exists solely as the backward lexical mirror.

Examples:

assert.(x > 0)              // throws if x <= 0
assert.(true)               // always passes
assert.(null)               // throws: assertion failed: null
assert.(myBox)              // passes — container is truthy

// Backward form — always silent:
(x > 0).tressa.

13. Mathematical Operations

All math operations follow dot-chain syntax and have bidirectional forms.

13.1 Trigonometry

Forward Reverse Function
sin.(x) (x).nis Sine
cos.(x) (x).soc Cosine
tan.(x) (x).nat Tangent
sinh.(x) (x).hnis Hyperbolic sine
cosh.(x) (x).hsoc Hyperbolic cosine
tanh.(x) (x).hnat Hyperbolic tangent

Examples (from source — resources/test4.txt):

print.(sin.(89))
print.(cos.(89))
print.(tan.(89))
print.(sinh.(89))
print.(cosh.(89))
print.(tanh.(89))
print.((98).nis)
print.((98).soc)
print.((98).nat)
print.((98).hnis)
print.((98).hsoc)
print.((98).hnat)

Trig functions can be chained:

print.(sinh.(25).nat).tnirp
// forward: sinh(25), then nat (reverse-tan) chains with tnirp

13.2 Logarithm

Forward (two-argument: base, value):

log.(base, value)
log.(2, 1024)     // log base 2 of 1024 = 10

Reverse:

(value, base).gol
(4201, 2).gol     // reverse: log base 2 of 4201

Examples (from source):

print.(log.(2, 1024))
print.((4201, 2).gol)

Natural log:

ln.(x)        // forward natural log
(x).nl        // reverse natural log

13.3 Exponential & Roots

Exponential:

exp.(x)       // e^x
(x).pxe       // reverse

Nth root:

yroot.(n, x)       // nth root of x
(x, n).toory       // reverse

13.4 Factorial

Factorial uses the ! symbol. Both prefix (!n) and postfix (n!) forms are valid.

Postfix (forward):

6!
6.33!
b011!

Prefix (reverse):

!6
!6.33
!b011

Examples (from source — resources/test4.txt):

print.(6!)          // 720
print.(6.33!)       // factorial of 6.33
print.(b011!)       // factorial of binary 011 (= 3! = 6)
print.(!6)          // reverse form: 720
print.(!6.33)
print.(!b011)

13.5 Rounding and Sign

All of these are Mono (single-argument) postfix operations. All have bidirectional forms. puc inversion is noted per function.

Forward Reverse Function puc inversion
abs.(x) (x).sba. Absolute value self-inverse
sqrt.(x) (x).trqs. Square root self-inverse
floor.(x) (x).roolf. Floor (round down) ceil
ceil.(x) (x).liec. Ceiling (round up) floor
round.(x) (x).dnuor. Round to nearest integer self-inverse
sign.(x) (x).ngis. Signum: -1, 0, or 1 self-inverse

Examples:

print.(abs.(-7))       // 7
print.(sqrt.(16.0))    // 4.0
print.(floor.(3.7))    // 3.0
print.(ceil.(3.2))     // 4.0
print.(round.(3.5))    // 4.0
print.(sign.(-99))     // -1.0

// Reverse forms:
print.((-7).sba.)      // 7
print.((3.7).roolf.)   // 3.0

13.6 Inverse Trigonometry

All are Mono postfix, all bidirectional, all puc self-inverse.

Forward Reverse Function
asin.(x) (x).nisa. Arcsine
acos.(x) (x).soca. Arccosine
atan.(x) (x).nata. Arctangent
asinh.(x) (x).hnisa. Inverse hyperbolic sine
acosh.(x) (x).hsoca. Inverse hyperbolic cosine
atanh.(x) (x).hnata. Inverse hyperbolic tangent

Examples:

print.(asin.(1.0))     // π/2 ≈ 1.5708
print.(acos.(0.0))     // π/2 ≈ 1.5708
print.(atan.(1.0))     // π/4 ≈ 0.7854
print.(atanh.(0.5))    // ≈ 0.5493

// Reverse forms:
print.((1.0).nisa.)
print.((1.0).soca.)

13.7 Fresnel Integrals

Mono postfix. Computes the Fresnel integrals as defined by the standard physics convention.

Forward Reverse Function
fresnelc.(x) (x).clenserf. Fresnel cosine integral C(x) = ∫₀ˣ cos(πt²/2) dt

No puc inversion is defined for Fresnel integrals.

Example:

print.(fresnelc.(1.0))    // ≈ 0.7799
print.((1.0).clenserf.)   // reverse form

13.8 Scalar min / max

Binary two-argument operations. Argument order reverses in the backward form.

Forward Reverse Function puc inversion
min.(a, b) (b, a).nim. Smaller of two values max
max.(a, b) (b, a).xam. Larger of two values min

Examples:

print.(min.(3, 7))       // 3
print.(max.(3, 7))       // 7
print.((7, 3).nim.)      // 3  (reversed argument order)
print.((7, 3).xam.)      // 7

13.9 Bitwise Operations

All operate on integer values. Non-integer doubles are truncated to int before the operation. Results are returned as doubles.

Mono (single-argument):

Forward Reverse Function puc inversion
bnot.(x) (x).tonb. Bitwise NOT (~x) self-inverse

Binary two-argument:

Forward Reverse Function puc inversion
band.(a, b) (b, a).dnab. Bitwise AND bor
bor.(a, b) (b, a).rob. Bitwise OR band
bxor.(a, b) (b, a).roxb. Bitwise XOR self-inverse
bleft.(a, b) (b, a).tfelb. Left shift (a << b) bright
bright.(a, b) (b, a).thgirb. Right shift (a >> b) bleft

Examples:

print.(band.(12, 10))    // 8  (1100 & 1010)
print.(bor.(12, 10))     // 14 (1100 | 1010)
print.(bxor.(12, 10))    // 6  (1100 ^ 1010)
print.(bnot.(0))         // -1
print.(bleft.(1, 4))     // 16 (1 << 4)
print.(bright.(16, 2))   // 4  (16 >> 2)

// Reverse forms:
print.((10, 12).dnab.)   // 8
print.((10, 12).rob.)    // 14

13.10 Vector Operations

Vectors are box containers whose body items are numeric values. Operations require BoxInstance operands.

Mono postfix (single container):

Forward Reverse Function puc inversion
norm.(v) (v).mron. L2 magnitude (Euclidean length) unit
unit.(v) (v).tinu. Normalize to unit vector norm

Binary two-argument:

Forward Reverse Function puc inversion
vdot.(a, b) (b, a).todv. Dot product of two 1D vectors → scalar cross (1D only)
cross.(a, b) (b, a).ssorc. Cross product of two 3-vectors → vector vdot
vadd.(a, b) (b, a).ddav. Element-wise addition vsub
vsub.(a, b) (b, a).busv. Element-wise subtraction vadd
vscale.(v, k) (v, k).elacsv. Scalar multiply: each element × k → scale by 1/k

Type model: A vector is a box whose body contains numeric elements. There is no separate vector type — a box [1.0, 2.0, 3.0] is a valid 3-vector. Vector operations require that all body elements are numeric.

Examples:

box v1 = [3.0, 4.0]
box v2 = [1.0, 0.0]

print.(norm.(v1))         // 5.0
print.(unit.(v1))         // [0.6, 0.8]

print.(vdot.(v1, v2))     // 3.0
print.(vadd.(v1, v2))     // [4.0, 4.0]
print.(vsub.(v1, v2))     // [2.0, 4.0]
print.(vscale.(v1, 2.0))  // [6.0, 8.0]

box a = [1.0, 0.0, 0.0]
box b = [0.0, 1.0, 0.0]
print.(cross.(a, b))      // [0.0, 0.0, 1.0]

13.11 Matrix Operations

Matrices are box containers whose body elements are themselves box containers (rows). Each row-box holds the column values for that row. No new types are introduced — the standard box type serves both vectors and matrices.

Mono postfix (single container):

Forward Reverse Function puc inversion
trans.(m) (m).snart. Matrix transpose self-inverse
vdet.(m) (m).tedv. Matrix determinant → scalar trace
vinv.(m) (m).vniv. Matrix inverse (Gauss-Jordan) self-inverse
trace.(m) (m).ecart. Trace (sum of main diagonal) → scalar vdet

Binary two-argument (vdot — overloaded for 2D):

When both operands are 2D matrices (box of boxes), vdot.(a, b) performs matrix multiplication and returns a matrix. When operands are 1D vectors, it returns a scalar dot product (see §13.10).

Type model: A 2×2 matrix is written as a box of two row-boxes:

box row0 = [1.0, 2.0]
box row1 = [3.0, 4.0]
box m = [row0, row1]

Examples:

box r0 = [1.0, 2.0]
box r1 = [3.0, 4.0]
box m = [r0, r1]

print.(trans.(m))      // [[1.0, 3.0], [2.0, 4.0]]
print.(vdet.(m))       // -2.0  (1*4 - 2*3)
print.(trace.(m))      // 5.0   (1 + 4)
print.(vinv.(m))       // [[-2.0, 1.0], [1.5, -0.5]]

// Matrix multiply:
print.(vdot.(m, m))    // m squared

// Reverse forms:
print.((m).snart.)
print.((m).ecart.)

14. HATTAG — Dynamic Type System

#HATTAG and #GATTAH are special markers that define dynamic token types — user-defined type labels that the scanner recognizes at scan time.

Syntax:

#HATTAG#typename1#typename2#typename3#GATTAH

Everything between #HATTAG and #GATTAH is a sequence of #-separated type names. Each name becomes a registered TTDynamic token type for the duration of the program.

Case insensitivity: #HATTAG is recognized in any capitalisation combination:

#HATTAG
#hattag
#Hattag
#HATTag
// ... (all 16+ capitalisation variants are registered)

#GATTAH is recognized as #GATTAH or #gattah (two variants).

Example (from source — resources/test.txt):

#HATTAG#bobs#GATTAH
a1234a( x1234x{ c1234c{ d1234d{ )a4321a }x4321x }c4321c }d4321d
4+5

After the #HATTAG block, the identifiers bobs becomes a recognized dynamic token type. The labels a1234a, x1234x, etc., are used as bracket labels.

Use case: HATTAG allows PCB programs to define their own type vocabulary at the lexical level, before parsing. A program can declare game-world entity types, schema types, or protocol types as first-class lexical tokens.


The contract system is PCB's mechanism for defining structural interfaces, type shapes, and runtime conformance guarantees. It ties together function link stubs, link interfaces, templates, user-defined types, and an execution context stack.

A Function Link declares forward and backward stubs — signature-only declarations without a shared cup body. They are the building block of link interfaces.

fun.forwardName.[type param, ...].[param type, ...].backwardName.nuf

Both names are registered in the environment as BoxFunction stubs. Calling either stub throws a RuntimeError — they are declarations, not executable code.

Examples:

fun.draw.[].ward.nuf
fun.paint.[box x].[x box].tniap.nuf
fun.shoot.[box target, box speed].[speed box, target box].toahs.nuf
fun.run.[].nur.nuf

A Link Interface is a cup decorated with ! that declares a named structural contract. Its body contains only function link stubs — no executable code.

!InterfaceName{
  fun.method1.[type param].[param type].1dohtem.nuf
  fun.method2.[].2dohtem.nuf
}InterfaceName!

The forward label (!InterfaceName) and closing label (InterfaceName!) are fused with the cup delimiters by the Grouper. The closing label is the character-by-character reverse of the forward name.

Example — Drawable interface:

!Drawable{
  fun.draw.[].ward.nuf
  fun.resize.[box scale].[elacs box].eziser.nuf
}elbawarD!

Example — Shape interface:

!Shape{
  fun.area.[].aera.nuf
  fun.resize.[box factor].[rotcaf box].eziser.nuf
}epahS!

The registered BoxClass has isLink = true. All methods are link signatures — stubs with no body. Calling any method throws a RuntimeError. The interface name is stored in templateLinkNames for conformance checking.

15.3 Templates

A Template is a cup decorated with # that provides a concrete implementation. Templates can declare conformance to one or more link interfaces using &LinkName chains.

Plain template:

#Circle{
  fun.area.{
    return.(3.14159 * radius * radius)
    (3.14159 * radius * radius).nruter
  }.aera.nuf
}elcriC#

Template conforming to a link interface:

#Circle&Drawable{
  fun.draw.{
    print.("drawing circle")
    ("drawing circle").tnirp
  }.ward.nuf
  fun.resize.[box scale].{
    box r = radius * scale
    elacs * suider = r xob
  }.[elacs box].eziser.nuf
}elbawarD&elcriC#

The &DrawableName in the opening and DrawableName& in the closing (before the reversed template name) declare conformance. Multiple links: #Circle&Drawable&Serializable{ ... }elbazilaireS&elbawarD&elcriC#.

With a base template: #Circle>Shape{ ... }epahS<elcriC#.

15.4 User-Defined Types

PCB supports user-defined structural types that classify values by their string shape. A type declaration specifies a name, optional link associations, an optional template binding, and a slot pattern.

Full syntax:

:TypeName(&LinkName)*(#TemplateName)?[slotPattern](TemplateName#)?(LinkName&)*TypeNameRev:

Minimal form (no links, no template):

:Foo[@sg $_ gs@]ooF:

With a link:

:MyType&Arithmetic[@sg $_ gs@]citemhtirA&epyTyM:

With a template:

:MyType#MyTmpl[@sg $_ gs@]lpmTyM#epyTyM:

Full form — decimal number type:

:NumberFormatLong&Arithmetic#PreciseNumbers[
  @sg $_^X["","-"] gs@,
  @in $$_^["0","1","2","3","4","5","6","7","8","9"] ni@,
  @dp $"." pd@,
  @fr $$_^["0","1","2","3","4","5","6","7","8","9"] rf@
]srebmuNesicerP#citemhtirA&gnoLtamroFrebmuN:

This declares NumberFormatLong with four slot groups: sign (sg), integer digits (in), decimal point (dp), and fraction digits (fr).

The closing mirror is the exact reverse of the opening: reversed type name at the end, reversed template name (if present) followed by #, then reversed link names in reverse order each followed by &, then the closing :.

Slot Pattern Primitives

PatternMeaning
@cat $_ catRev@ONE — single anchor value
@cat $$_^["v1","v2",...] catRev@MANY — repeated values constrained to list
@cat $"literal" catRev@Literal match
@cat $_^X["v1","v2",...] catRev@Exclusive OR — exactly one value from list

Multiple slots are separated by commas. Each slot is wrapped in @category ... categoryRev@ delimiters.

Anchor rule: Each type must contain exactly one $_ anchor slot. Adjacent MANY slots without an intervening literal anchor throw a RuntimeError at declaration time.

Mirror requirement: The closing mirror type name must be the exact character-by-character reverse of the forward type name. :Foo[...]ooF: is valid; :Foo[...]Wrong: fails.

15.5 Type Inference

The ? inference operator matches a value's string representation against all registered user types and wraps it in a structured BoxInstance.

Forward:

?box c = 3.14

Backward:

3.14 = c xob?

The runtime serializes the value to a string, then iterates userTypeRegistry calling tryMatchType on each entry. The first match determines the slot grouping. If no type matches, a single-item BoxInstance containing the raw string is returned.

Example — inferring a decimal number:

:NumberFormatLong&Arithmetic#PreciseNumbers[
  @sg $_^X["","-"] gs@,
  @in $$_^["0","1","2","3","4","5","6","7","8","9"] ni@,
  @dp $"." pd@,
  @fr $$_^["0","1","2","3","4","5","6","7","8","9"] rf@
]srebmuNesicerP#citemhtirA&gnoLtamroFrebmuN:

?box price = 3.14

After inference, price is a BoxInstance with four slot groups:

SlotValue
sg"" (empty sign)
in"3" (integer part)
dp"." (decimal point)
fr"14" (fractional part)

Backward inference reverses the string before matching: 3.14 = c xob? matches against "41.3", so in receives "41" and fr receives "3".

No-match fallback: With no registered types, ?box x = 3.14 produces a single-item BoxInstance with body ["3.14"].

15.6 Template Instantiation

The @ operator instantiates a template and binds the result to a name.

Forward:

Circle @ myCircle

Backward:

myCircle @ elcriC

In the forward form, Circle is looked up in the template registry and called with no arguments. The resulting Instance is bound to myCircle (and its reversed name) in the current environment. If the template has link interface requirements (templateLinkNames non-empty), the contractContextStack is pushed before instantiation and popped after.

15.7 Conformance and C3 Rollback

When a template declares &LinkName conformance requirements, the runtime executes a conformance check at template declaration time:

  1. For each name in templateLinkNames, the runtime looks up the corresponding BoxClass.
  2. For each method stub in that BoxClass, it verifies the template body contains a concrete implementation (a non-stub BoxFunction).
  3. If any method is missing, a conformanceError is thrown naming the missing method.

Conformance passes:

!Drawable{
  fun.draw.[].ward.nuf
}elbawarD!

#Circle&Drawable{
  fun.draw.{
    print.("drawing circle")
    ("drawing circle").tnirp
  }.ward.nuf
}elbawarD&elcriC#
// → conformance passes: draw and ward both implemented

Conformance fails:

!Drawable{
  fun.draw.[].ward.nuf
}elbawarD!

#BadCircle&Drawable{
  // no draw/ward implementation
}elbawarD&elcriCdaB#
// → conformanceError: missing method "draw"

C3 Rollback: On conformance failure, any link stubs registered during the failed template declaration are removed from the environment. No partial state is committed.

15.8 The Contract Context

The interpreter maintains a contractContextStack — a stack of booleans pushed and popped around contract template execution. inContractContext() returns true whenever the stack is non-empty.

Behavior:

// fresh interpreter    → inContractContext() = false
// push(true)           → inContractContext() = true
// push(true) again     → inContractContext() = true  (depth 2)
// pop()                → inContractContext() = true  (depth 1)
// pop()                → inContractContext() = false

Contract confidence — the contractConfidence field tracks a floating-point score during contract evaluation. It is -1.0 outside a contract context and in [0.0, 1.0] inside one.

Contract manifestgenerateContractManifest() returns a JSON object listing bindings discovered during contract execution:

{
  "lazy": ["manifold", "grammar"],
  "eager": ["config", "threshold"]
}

Export preamblegenerateExportPreamble() returns one read <path> into <name> directive per seed global:

read data/manifold.json into manifold
read grammar.json into grammar

15.9 Seed Globals

The Environment class supports tagging top-level variables as seed globals — bindings associated with a specific source file path.

APIDescription
tagAsSeedGlobal(name, filePath)Tag name as originating from filePath
isSeedGlobal(name)true if name is tagged in this or any enclosing scope
getSeedGlobalPath(name)Returns the stored file path, or null if not tagged
getSeedGlobalNames()Set of all seed-tagged names in this environment

isSeedGlobal walks the enclosure chain — a seed tagged in a parent scope is visible from all child scopes. Sibling scopes cannot see each other's seeds.

// manifold tagged: tagAsSeedGlobal("manifold", "data/manifold.json")
// getSeedGlobalPath("manifold") → "data/manifold.json"
// isSeedGlobal("manifold")      → true

// Child environment can also see it:
// child.isSeedGlobal("manifold")   → true  (walks to parent)

// Sibling environment cannot:
// sibling.isSeedGlobal("manifold") → false

Lazy Get preservation: Inside a contract context, Expr.Get chains whose root is a seed global are preserved as lazy unevaluated nodes. Outside a contract context, the same chains evaluate normally.

15.10 Lazy Template Resolution

User-defined type entries carry optional forward and backward template references: templateName, mirrorTemplateName, and resolvedTemplate (the BoxClass instance, populated lazily).

resolvedTemplate is null at declaration time if the named template has not yet been registered. When any BoxClass is registered, resolveTemplateInRegistry(name) runs and links it to all waiting type entries whose templateName matches.

Type declared before template:

:NumberFormatLong&Arithmetic#PreciseNumbers[...]...:
// → resolvedTemplate = null (PreciseNumbers not yet declared)

#PreciseNumbers{ ... }srebmuNesicerP#
// → resolveTemplateInRegistry("PreciseNumbers") fires
// → resolvedTemplate = BoxClass("PreciseNumbers")

Template declared before type:

#PreciseNumbers{ ... }srebmuNesicerP#

:NumberFormatLong&Arithmetic#PreciseNumbers[...]...:
// → PreciseNumbers already in globals → resolvedTemplate set immediately

No template declared:

:Bare[@sg $_ gs@]eraB:
// → resolvedTemplate stays null → inference falls back to string operations

Two types, one template — only matching entry resolves:

:TypeA#PreciseNumbers[...]...:
:TypeB#OtherTemplate[...]...:

#PreciseNumbers{ ... }srebmuNesicerP#
// → TypeA.resolvedTemplate set
// → TypeB.resolvedTemplate stays null (OtherTemplate not declared)

16. FlatLand Game Integration Commands

PCB includes three built-in commands for interacting with the FlatLand game engine. These are first-class keywords in the scanner.

Keyword Token Direction Action
cre FLCREATE Forward Create a FlatLander entity
mov FLMOVE Forward Move a FlatLander entity
des FLDESTROY Forward Destroy a FlatLander entity

There are also environment-level commands: - FLECREATE — create a FlatLand environment object - FLEDESTROY — destroy a FlatLand environment object - FLSETVALUE — set a value on an entity

Syntax (authored examples):

// Create a skeleton at position (100, 200)
cre skeleton SKELETON 100 200

// Move the skeleton to (150, 200)
mov skeleton 150 200

// Destroy the skeleton
des skeleton

// Set a field value on the skeleton
FLsetValue skeleton health 50

When a PCB script containing these commands runs inside the game engine, the Interpreter's visit methods for FLCreate, FLMove, and FLDestroy call directly into FlatLandFacebook to instantiate, reposition, or remove entities. This is the bridge between language and game world.


17. Type Keywords

PCB has type keywords for declaring the expected numeric type of a variable or expression.

Keyword Reverse Meaning
DOUBLE Double-precision float type
INT Integer type
BIN Binary number type
type epyt Type expression
box pkt cup a()a puc knt xob
x = "five"

The line box pkt cup a()a puc knt xob declares box, pkt, cup, and knt forward, then a()a labels a scope, then puc knt xob closes with reverse declarations.


18. PCBServer API Reference

The PCBServer exposes PCB execution over HTTP on port 7070 by default.

Start the Server

PCBServer.start();        // port 7070
PCBServer.start(8080);    // custom port

From the command line:

java Box.GameSpaceInterpreter.PCBServer 7070

POST /seeds

Load seed objects that will be injected as variables into every subsequent run.

Request:

{
  "manifold":  { "manifoldSeedId": "seed-001", "key": "value" },
  "grammar":   { "grammarSeedId": "gram-001" },
  "traversal": { "traversalSeedId": "trav-001" },
  "user":      { "projectId": "proj-001" }
}

Response:

{
  "ok": true,
  "manifest": {
    "manifold":  { "type": "ManifoldSeedObject", "seedId": "seed-001", "properties": ["key"] },
    "grammar":   { "type": "GrammarSeedObject", "seedId": "gram-001", "properties": [] }
  }
}

Loaded seeds are automatically injected as box variables in all subsequent /run calls:

box manifoldId = "seed-001"
box grammarId  = "gram-001"

POST /run

Execute PCB source code.

Request:

{
  "source":    "box x = 42\nprint.(x)",
  "direction": "fwd"
}

direction is "fwd" (default) or "bwd".

Response:

{
  "ok": true,
  "output": "42\n",
  "executionTime": 12,
  "direction": "fwd",
  "interfaceSeedId": "iface_abc123",
  "bindings": [
    {
      "container": "box",
      "bindName": "x",
      "userAnchor": "42",
      "seedRefs": [],
      "seedType": "manifold",
      "natural": true,
      "confidence": 0.95
    }
  ]
}

GET /status

Health check and seed status.

Response:

{
  "ok": true,
  "runtime": "PCBServer",
  "seedsLoaded": 2,
  "manifold": true,
  "grammar": false,
  "traversal": true,
  "user": false
}

19. Full Program Examples

19.1 Hello World (from source)

File: script.txt

(box b = 0 {b < 10) print.("\n hello world") b++}

A single line: declare b = 0, oscillate while b < 10, print hello world, increment b.

Expanded:

( box b = 0 { b < 10 )
    print.("\n hello world")
    b++
}

19.2 Bidirectional Hello (from source)

File: TEST/TESTWORKING

("running backward ").tnirp
( box b = 0
    { b < 4 )
        print.("forward ")
        ("backward ").tnirp
        b++
    }
print.("running forward ")

("running backward ").tnirp
{
    ("backward ").tnirp
    print.("forward ")
    b++
( b < 4 } 0 = b xob )
print.("running forward ")

Each block is both-directional: the oscillation model means the block body runs forward then backward on alternating passes, so print.("forward") fires on forward passes and ("backward").tnirp fires on backward passes — both within the same block. The second block demonstrates the same pattern in the reverse structural form. The opening ("running backward").tnirp and closing print.("running forward") mark the outer boundaries.


19.3 Nested Labeled Knot Blocks (from source)

File: TEST/TESTWORKING — full five-variable nested example

("running backward").tnirp
(box x = 0 box y = 0 box z = 0
    a{ x < 3  b{ y < 4  c{ z < 5 )
        ++x++
        ("marauder").tnirp
        ++y++
        ("rock").tnirp
        ++z++
        ("mauve").tnirp
        --i--
        print.(sinh.(25).nat).tnirp
        --j--
        ("hicory").tnirp
        --k--
    ( i > 4 }a  j > 2 }c  k > 3 }b
10 = i xob  9 = j xob  8 = k xob )
print.("running forward")

Three nested blocks labeled a, b, c. Each block oscillates independently. On the block body: - ++x++ increments x - sinh.(25).nat computes hyperbolic sine of 25, chains to reverse tangent - --i-- / --j-- / --k-- decrement i, j, k (used in closing conditions)


19.4 Trig & Math Functions (from source)

File: resources/test4.txt

print.(sin.(89))
print.(cos.(89))
print.(tan.(89))
print.(sinh.(89))
print.(cosh.(89))
print.(tanh.(89))
print.((98).nis)
print.((98).soc)
print.((98).nat)
print.((98).hnis)
print.((98).hsoc)
print.((98).hnat)

print.(log.(2, 1024))
print.((4201, 2).gol)
print.(6!)
print.(6.33!)
print.(b011!)
print.(!6)
print.(!6.33)
print.(!b011)

print.(3 > 4 and 5 < 4)
print.(3 < 4 and 5 < 4)
print.(3 < 4 and 5 > 4)
print.(3 > 4 or 5 < 4)
print.(3 < 4 or 5 < 4)

19.5 File Operations (from source)

File: resources/test4.txt

// Nested container definition
c{ b{ 5 p{ 5 }p 6 4 3 2}b f{4}f}c

// Deep index read
print.("c.0.3.0 " + c.0.3.0)
c.0.3 = 10001
print.("c.0.3.0 " + c.0.3.0)

// Move files (both directions)
move.("/home/wes/workspace/.../resources/move.txt").to.("/home/wes/workspace/.../resources2/move.txt")
("/home/wes/workspace/.../resources3/move.txt").ot.("/home/wes/workspace/.../resources2/move.txt").evom

// Read into deep index
read.("/home/wes/workspace/.../resources3/move.txt").into.(2.0.c)
print.(2.0.c)
read.("/home/wes/workspace/.../resources3/move.txt").into.(1.c)
print.(c.1)

// Reverse read with destructuring
(pear{}raep).otni.("/home/wes/workspace/.../resources3/move.txt").daer
print.(pear)

// Rename files (both directions)
rename.("/home/wes/workspace/.../resources3/move.txt").to.("/home/wes/workspace/.../resources3/moving.txt")
("/home/wes/workspace/.../resources3/movingTWOELECTRICBOOGALOO.txt").ot.("/home/wes/workspace/.../resources3/moving.txt").emaner

// Save with deep indexing
save.("/home/wes/workspace/.../resources3/newDIR/FLIRT.txt").(2.0.c)
save.("/home/wes/workspace/.../resources3/newDIR/GHOSTS.txt").(c)
save.("/home/wes/workspace/.../resources3/newFOLDER").()

// Reverse save
(1.0.c).("/home/wes/workspace/.../resources3/DUCKCOP.txt").evas
(c.0).("/home/wes/workspace/.../resources3/DUCKCOP2.txt").evas
().("/home/wes/workspace/.../resources3/ITSAFOLDER").evas

// Stream operators
c.1 >>> ["/home/wes/workspace/.../resources3/DUCKCOP.txt"]
c.1 <<< ["/home/wes/workspace/.../resources3/DUCKCOP2.txt"]
print.(c.1)

19.6 Contains & Bracket Variants (from source)

File: TEST/TEST

((print.("lala")).sniatnoc.j).tnirp
(((5).nis).sniatnoc.j).tnirp
((false).sniatnoc.j).tnirp
(("hello").sniatnoc.j).tnirp
().coi = j xob
coi{sin.(5).nis  ko(false}ioc  print.("lala") "hello")ok

((print.("lala")).sniatnoc.k).tnirp
(((5).nis).sniatnoc.k).tnirp
((false).sniatnoc.k).tnirp
(("hello").sniatnoc.k).tnirp
().boi = k xob
boi{sin.(5).nis  print.("lala") "hello"}iob

((print.("false")).sniatnoc.h).tnirp
(((5).nis).sniatnoc.h).tnirp
((false).sniatnoc.h).tnirp
(("hello").sniatnoc.h).tnirp
().oi = h xob
oi(sin.(5).nis  print.("false") "hello")io

(((5).nis).sniatnoc.x).tnirp
((false).sniatnoc.x).tnirp
(("hello").sniatnoc.x).tnirp
().poi = x xob
poi[sin.(5).nis  false "hello"]iop

This demonstrates all three bracket types used as container bodies after a label ({ }, ( ), [ ]), and the reverse-contains operator chaining.


19.7 Counting Block (authored)

( box count = 0 { count < 5 )
    print.(count)
    count++
}

Output:

0
1
2
3
4

19.8 FizzBuzz (authored)

( box n = 1 { n <= 100 )
    (n % 15 == 0).{print.("FizzBuzz")}
    (n % 3 == 0).{print.("Fizz")}
    (n % 5 == 0).{print.("Buzz")}
    (n % 3 != 0 and n % 5 != 0).{print.(n)}
    n++
}

19.9 Fibonacci (authored)

box a = 0
box b = 1
box temp = 0
( box i = 0 { i < 10 )
    print.(a)
    temp = a + b
    a = b
    b = temp
    i++
}

Output:

0
1
1
2
3
5
8
13
21
34

19.10 Stack Operations (authored)

{ pkt stack = ( }
    push.stack.10
    push.stack.20
    push.stack.30
    print.(size.stack)         // 3
    print.(pop.stack)          // 30
    print.(pop.stack)          // 20
    print.(size.stack)         // 1
)

// Reverse stack operations (tkp seam form)
( 30.hsup.stack 20.hsup.stack 10.hsup.stack
    print.(ezis.stack)         // 3
{ ) = stack tkp }

19.11 Bidirectional Counter (authored)

This program runs one way forward, one way backward. In forward mode it counts up and prints "up". In backward mode it counts down and prints "down".

("counting down").tnirp
( box n = 10
    { n > 0 )
        ("down").tnirp
        n--
    }
0 = n xob )
print.("counting up")

( box m = 0 { m < 10 )
    print.("up")
    m++
}

19.12 FlatLand Script — Spawn & Move (authored)

// Spawn two enemies and move one
cre skeleton1 SKELETON 100 200
cre skeleton2 SKELETON 300 200

// Move skeleton1 toward skeleton2
( box step = 0 { step < 10 )
    mov skeleton1 (100 + step * 20) 200
    step++
}

// Destroy after movement
des skeleton1

20. Operator Precedence

From highest to lowest:

Precedence Operators Notes
1 (highest) ! (factorial postfix/prefix), ++, -- Unary/increment
2 ^ Exponentiation
3 *, /, \, % Multiplication, division, modulo
4 +, - Addition, subtraction
5 <, >, <=, >= Comparison
6 ==, != Equality
7 not, ton Logical NOT
8 and, dna Logical AND
9 (lowest) or, ro Logical OR

Assignment (=) is right-associative and has lower precedence than all of the above.


21. Error Reporting

Errors are reported with line and column numbers:

[column 5, line 3] Error at 'xyz': Unexpected character

Error types:

Type mismatch example:

Can not assign 42 to object of type Box.Interpreter.RunTimeTypes.str
[line: 7 column: 12]

Undefined variable:

Undefined variable 'myVar'.
[line: 4 column: 8]

The interpreter has two error flags: hadError (syntax/parse errors) and hadRuntimeError (execution errors). Both are reset between executions by Box.resetHadError().


22. Complete Keyword Reference

Container Declarations

Keyword Reverse Type
box xob General container
cup puc Cup container
pkt / pocket tkp / tekcop Ecosystem container
knt tnk Knot container

Boolean & Null

Keyword Reverse Value
true eurt Boolean true
false eslaf Boolean false
null llun Null
NULL LLUN Null (uppercase)
nill llin Null (variant)
NILL LLIN Null (uppercase variant)

Functions

Keyword Reverse Use
fun nuf Function declaration
return nruter Return value

Conditionals

Conditionals use no keywords — they are expressed entirely through dot-chain bracket syntax. See §9.5.

Form Syntax Direction
If (cond).{body}.{else} Forward only
Fi {else}.{body}.(cond) Backward only
Ifi (condF).{body}.(condB) Both directions

I/O

Keyword Reverse Action
print tnirp Output
save evas Write to file
read daer Read from file
into otni Direction marker (used with read)
rename emaner Rename file
move evom Move file
to ot Direction marker (used with rename/move)
assert tressa Assertion (tressa is no-op)

Container Operations

Keyword Reverse Action
add dda Add element
remove evomer Remove element
clear raelc Clear all
size ezis Count elements
empty ytpme Empty check
push hsup Push to stack
pop Pop from stack
setat tates Set at index
getat tateg Get at index
sub bus Subrange
contains sniatnoc Membership test
open nepo Open container
alive evila Pocket liveness query
consume Read file lines into container (forward only)

Mathematics

Keyword Reverse Function
sin nis Sine
cos soc Cosine
tan nat Tangent
sinh hnis Hyperbolic sine
cosh hsoc Hyperbolic cosine
tanh hnat Hyperbolic tangent
log gol Logarithm (base, value)
ln nl Natural logarithm
exp pxe Exponential (e^x)
yroot toory Nth root
abs sba Absolute value
sqrt trqs Square root
floor roolf Floor
ceil liec Ceiling
round dnuor Round to nearest integer
sign ngis Signum (-1/0/1)
asin nisa Arcsine
acos soca Arccosine
atan nata Arctangent
asinh hnisa Inverse hyperbolic sine
acosh hsoca Inverse hyperbolic cosine
atanh hnata Inverse hyperbolic tangent
fresnelc clenserf Fresnel cosine integral C(x)
min nim Minimum of two values
max xam Maximum of two values
band dnab Bitwise AND
bor rob Bitwise OR
bxor roxb Bitwise XOR
bnot tonb Bitwise NOT
bleft tfelb Left shift
bright thgirb Right shift
norm mron Vector L2 magnitude
unit tinu Normalize to unit vector
vdot todv Dot product / matrix multiply
cross ssorc Cross product (3-vectors)
vadd ddav Element-wise vector addition
vsub busv Element-wise vector subtraction
vscale elacsv Scalar multiply vector
trans snart Matrix transpose
vdet tedv Matrix determinant
vinv vniv Matrix inverse
trace ecart Matrix trace

Logical

Keyword Reverse Meaning
and dna Logical AND
or ro Logical OR
not ton Logical NOT

Types

Keyword Reverse Meaning
DOUBLE Double type
INT Integer type
BIN Binary type
type epyt Type expression

FlatLand Integration

Keyword Token Action
cre FLCREATE Create entity
mov FLMOVE Move entity
des FLDESTROY Destroy entity

Special

Keyword Reverse Meaning
#HATTAG #GATTAH Dynamic type block open/close

Symbols

Symbol Token Meaning
. DOT Chain separator
( ) OPENPAREN CLOSEDPAREN Paren group
{ } OPENBRACE CLOSEDBRACE Brace group
[ ] OPENSQUARE CLOSEDSQUARE Square group
>>> EXPELL Expel to file
<<< CONSUME Consume from file
! BANG Factorial / not-equal prefix
? QMARK Question mark
@ AT At symbol
_ UNDERSCORE Underscore
\| TEMPLID Template delimiter
& SINGLEAND Single ampersand
// Comment (line)
; SEMICOLON Comment (line)

PCB Language Specification — trainingGround 0.0.1-SNAPSHOT — May 2026 — pocket lifetimes, tkp seam crossing, alive/evila, assert/tressa, consume, rounding/sign ops, inverse trig, Fresnel integrals, scalar min/max, bitwise ops, vector/matrix ops, cascading pocket death