Language Name: PCB (Pocket/Cup/Box)
Version: 0.0.1-SNAPSHOT
Runtime: JVM / Java 21
Interpreter Entry Point: Box.Box.Box / Box.GameSpaceInterpreter.PCBServer
<<<) — Read File into ContainerPCB 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.
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)
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) |
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:
knt/tnk) is a direction switch node in the graph. KnotRunner uses an oscillation model: when a condition remains true at the forward endpoint, direction flips to backward and the body traverses in reverse; when the backward pass reaches the inner bracket and the condition still holds, direction flips back to forward. Both directions fire on alternating passes. The direction at block exit persists as global state.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.
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.
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.
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.
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.
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.
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.
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
Whole numbers, no decimal point. Token type: INTNUM.
0
42
10001
-5
Numbers with a decimal point. Token type: DOUBLENUM.
3.14
6.33
0.5
-1.0
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
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)
Single characters enclosed in single quotes. Token type: CHAR.
'a'
'Z'
'\n'
Two values. Both forward and reverse forms exist.
| Value | Forward | Reverse |
|---|---|---|
| True | true |
eurt |
| False | false |
eslaf |
true
false
eurt
eslaf
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
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:
evaluateBody())var / rav)= / =)KnotRunner.injectIntoContainer())tick())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.
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.
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: box↔xob, cup↔puc, pkt↔tkp. 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: tkp → pkt at heat death or natural expiry; pkt → tkp on natural expiry; user-kill destroys without crossing |
knt / tnk |
Conditional control flow — knot runner, can flip global execution direction |
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
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)
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 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 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 |
Bootstrapped — KnotRunner.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 |
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.
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.
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.
// 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).
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).
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
knt = same-side routing / promotiontnk = cross-side routing / reversalpkt -> box = explicit reduction routetkp remains self-stable under kntpkt <-> tkp is the primary pocket seam under tnkUNSTABLE: 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
pktandtkpsource 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.
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
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
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 mmh → apple → sin.("34") → pear → hmm 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.
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.
| 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 |
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
| 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
| 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
| 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
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.
Design model: PCB control flow is formally defined as traversal over a static ControlGraph — a directed graph of
ControlNodeobjects (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 isKnotRunnerwith the oscillation model (see Technical Documentation §6.7 and §6.16).
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}
(condA) is the condition; {body A} executes if it holds..(condB).{body B} after each pair..{else} cup with no preceding condition pocket.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)
condA guards the body.condB guards the body.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.
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++
}
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.
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
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.
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.
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
Forward and backward parameter lists use opposite ordering — a direct expression of PCB's lexical reversal rule:
[type name, type name, ...] — type comes before name[name type, name type, ...] — name comes before typeValid 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
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
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.
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.
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) |
| 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 |
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)
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.
<<<) — Read File into ContainerThe 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.
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.
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
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
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")
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")
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.
All math operations follow dot-chain syntax and have bidirectional forms.
| 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
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
Exponential:
exp.(x) // e^x
(x).pxe // reverse
Nth root:
yroot.(n, x) // nth root of x
(x, n).toory // reverse
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)
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
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.)
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
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
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
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]
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.)
#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.
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#.
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 :.
| Pattern | Meaning |
|---|---|
@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.
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:
| Slot | Value |
|---|---|
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"].
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.
When a template declares &LinkName conformance requirements, the runtime executes a conformance check at template declaration time:
templateLinkNames, the runtime looks up the corresponding BoxClass.BoxClass, it verifies the template body contains a concrete implementation (a non-stub BoxFunction).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.
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 manifest — generateContractManifest() returns a JSON object listing bindings discovered during contract execution:
{
"lazy": ["manifold", "grammar"],
"eager": ["config", "threshold"]
}
Export preamble — generateExportPreamble() returns one read <path> into <name> directive per seed global:
read data/manifold.json into manifold
read grammar.json into grammar
The Environment class supports tagging top-level variables as seed globals — bindings associated with a specific source file path.
| API | Description |
|---|---|
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.
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)
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.
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.
The PCBServer exposes PCB execution over HTTP on port 7070 by default.
PCBServer.start(); // port 7070
PCBServer.start(8080); // custom port
From the command line:
java Box.GameSpaceInterpreter.PCBServer 7070
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"
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
}
]
}
Health check and seed status.
Response:
{
"ok": true,
"runtime": "PCBServer",
"seedsLoaded": 2,
"manifold": true,
"grammar": false,
"traversal": true,
"user": false
}
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++
}
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.
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)
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)
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)
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.
( box count = 0 { count < 5 )
print.(count)
count++
}
Output:
0
1
2
3
4
( 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++
}
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
{ 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 }
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++
}
// 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
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.
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().
| Keyword | Reverse | Type |
|---|---|---|
box |
xob |
General container |
cup |
puc |
Cup container |
pkt / pocket |
tkp / tekcop |
Ecosystem container |
knt |
tnk |
Knot container |
| 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) |
| Keyword | Reverse | Use |
|---|---|---|
fun |
nuf |
Function declaration |
return |
nruter |
Return value |
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 |
| 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) |
| 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) |
| 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 |
| Keyword | Reverse | Meaning |
|---|---|---|
and |
dna |
Logical AND |
or |
ro |
Logical OR |
not |
ton |
Logical NOT |
| Keyword | Reverse | Meaning |
|---|---|---|
DOUBLE |
— | Double type |
INT |
— | Integer type |
BIN |
— | Binary type |
type |
epyt |
Type expression |
| Keyword | Token | Action |
|---|---|---|
cre |
FLCREATE |
Create entity |
mov |
FLMOVE |
Move entity |
des |
FLDESTROY |
Destroy entity |
| Keyword | Reverse | Meaning |
|---|---|---|
#HATTAG |
#GATTAH |
Dynamic type block open/close |
| 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