PCD Syntax Reference
PCD (Printed Circuit Description) is a Turing-complete language where programs are circuit schematics. Every valid PCD program has Thermodynamic Coherence Φ_c = 1: no dead branches, no unreachable code, no undefined flows.
PCD programs are verified before execution. A program that fails coherence verification (Φ_c ≠ 1) is rejected at compile time — not at runtime.
Program Structure
Every PCD program is a named circuit block:
PC circuit_name {
// functions and logic here
OUTPUT result;
}
The OUTPUT directive marks the final value emitted by the circuit. It is mandatory.
PC add_two {
let a = 10;
let b = 32;
OUTPUT a + b;
}
Variables
Let Bindings
Variables are declared with let. All bindings are immutable by default — rebinding creates a new SSA slot:
let x = 42;
let name = "hello";
let flag = true;
Loop-Carried Variables
To mutate a variable across loop iterations, rebind it in the same scope using the same name:
let count = 0;
loop(10) as i {
let count = count + 1;
}
The PCD planner uses SSA (Static Single Assignment) form. When rebinding inside a loop(N), always use the same variable name to ensure the planner recognizes it as a loop-carried variable, not a new slot.
Type Annotations
Types are optional — the planner infers them:
let x = 0; // i64
let b = true; // bool
let s = "text"; // string
let arr = [1,2,3]; // array
Explicit type annotations are not required by the parser, but the planner enforces type consistency.
Functions
Function Declaration
fn add(a, b) {
return a + b;
}
Recursive Functions
fn factorial(n) {
if (n <= 1) {
return 1;
}
return n * factorial(n - 1);
}
Recursion depth is limited by MAX_DEPTH = 256. Deep recursive programs must be rewritten iteratively using loop(N).
Multiple Return Values via Tuple
fn divmod(a, b) {
let (q, r) = MC_03.DIV8(a, b);
return (q, r);
}
Closures
Closures are anonymous functions that capture their enclosing scope:
let multiplier = fn(x) { x * 3 };
let result = multiplier(7); // 21
Closures can be passed as arguments:
fn apply(f, x) {
return f(x);
}
let double = fn(n) { n * 2 };
let result = apply(double, 5); // 10
Conditionals
if / else
if (x > 0) {
OUTPUT "positive";
} else {
OUTPUT "non-positive";
}
if / else if chains
if (score >= 90) {
let grade = "A";
} else if (score >= 80) {
let grade = "B";
} else if (score >= 70) {
let grade = "C";
} else {
let grade = "F";
}
All branches of an if/else must be reachable and terminate coherently. The TCE verifies that Φ_c = 1 across all control paths.
Loops
loop(N) — Recommended
The loop(N) construct is the primary iteration primitive in PCD. It is deterministic, bounded, and always terminates:
loop(10) as i {
// i goes from 0 to 9
let result = i * i;
}
The as i clause is optional when the index is not needed:
let sum = 0;
loop(5) {
let sum = sum + 1;
}
Loop over array length:
let arr = [10, 20, 30, 40];
let len = MC_43.LEN(arr);
loop(len) as i {
let elem = arr[i];
}
while — Supported with Caution
while loops are syntactically supported but carry a known SSA limitation in the current planner:
let pos = 0;
let max = MC_43.LEN(text);
while (pos < max) {
let ch = MC_45.CHAR_AT(text, pos);
let pos = pos + 1;
}
WhileLoop SSA Bug: The planner captures the version of loop-carried variables before the body, not after. This can cause variables to not update correctly across iterations. Always prefer loop(N) over while in production PCD programs. Use loop(N) as i { if (cond) { body } } as a safe alternative.
Safe while-equivalent pattern:
let pos = 0;
loop(max_len) as _i {
if (pos < max) {
let ch = MC_45.CHAR_AT(text, pos);
let pos = pos + 1;
}
}
Pattern Matching
match expression
let result = match x {
0 => "zero",
1 => "one",
2 => "two",
_ => "other"
};
Match on strings:
let action = match command {
"add" => do_add(a, b),
"sub" => do_sub(a, b),
"mul" => do_mul(a, b),
_ => "unknown command"
};
Match as a statement:
match status {
200 => log("OK"),
404 => log("Not Found"),
500 => log("Server Error"),
_ => log("Unknown")
}
Structs
Definition
struct Point {
x: i64,
y: i64
}
Construction
let p = Point { x: 10, y: 20 };
Field Access
let px = p.x;
let py = p.y;
Structs in Functions
struct Rect {
width: i64,
height: i64
}
fn area(r) {
return r.width * r.height;
}
let r = Rect { width: 5, height: 8 };
let a = area(r); // 40
Try / Catch
Error handling in PCD is statement-level (not expression-level):
try {
let data = read_file("input.txt");
let parsed = parse(data);
OUTPUT parsed;
} catch (err) {
OUTPUT "error: " + err;
}
try/catch is a statement construct in PCD. You cannot use it inline as part of an expression like let x = try { ... }. Assign the result inside the try/catch body instead.
Nested try/catch:
try {
try {
let result = risky_op();
} catch (inner_err) {
let result = fallback_op();
}
OUTPUT result;
} catch (outer_err) {
OUTPUT "fatal: " + outer_err;
}
Imports
Relative Import
import "stdlib/math.pcd";
import "stdlib/string.pcd";
Import with Alias
import "brikc.pcd" as compiler;
let result = compiler.compile(source);
Selective Import
import "stdlib/math.pcd" { abs, min, max };
Circular Import Detection
The planner detects circular imports at compile time and rejects them. Circular dependencies must be broken by extracting shared code into a third module.
Monomer Calls
Monomers are the 64 atomic operations of the BRIK-64 architecture. They are called with the MC_XX.NAME(args) syntax:
Arithmetic Family (MC_00–MC_07)
let sum = MC_00.ADD8(a, b);
let diff = MC_01.SUB8(a, b);
let prod = MC_02.MUL8(a, b);
let (q, r) = MC_03.DIV8(a, b); // returns Tuple!
let rem = MC_04.MOD8(a, b);
let neg = MC_05.NEG8(a);
let abs = MC_06.ABS8(a);
let pow = MC_07.POW8(base, exp);
MC_03.DIV8 returns a Tuple (quotient, remainder), not a single value. Always destructure it: let (q, r) = MC_03.DIV8(a, b).
Logic Family (MC_08–MC_15)
let and_r = MC_08.AND8(a, b);
let or_r = MC_09.OR8(a, b);
let xor_r = MC_10.XOR8(a, b);
let not_r = MC_11.NOT8(a);
let shl = MC_12.SHL8(a, n);
let shr = MC_13.SHR8(a, n);
Memory Family (MC_16–MC_23)
let arr = MC_16.ALLOC(size);
let val = MC_17.LOAD(arr, idx);
let arr2 = MC_18.STORE(arr, idx, val);
let len = MC_19.LEN_MEM(arr);
Control Family (MC_24–MC_31)
let r = MC_24.IF(cond, then_val, else_val);
let r = MC_25.CALL(fn_ref, arg);
String Family (MC_40–MC_47)
let joined = MC_40.CONCAT(a, b);
let parts = MC_41.SPLIT(text, delimiter);
let sub = MC_42.SUBSTR(text, start, len);
let slen = MC_43.LEN(text);
let upper = MC_44.UPPER(text);
let ch = MC_45.CHAR_AT(text, idx);
let lower = MC_46.LOWER(text);
let trimmed = MC_47.TRIM(text);
Crypto Family (MC_48–MC_55)
let hash = MC_48.HASH(data);
let hmac = MC_49.HMAC(key, data);
let enc = MC_50.ENCRYPT(key, data);
let dec = MC_51.DECRYPT(key, data);
I/O Family (MC_56–MC_63)
let data = MC_56.READ(path);
let _ = MC_57.WRITE_FILE(path, data);
let _ = MC_58.WRITE(text); // stdout
let input = MC_59.READ_LINE(); // stdin
let env = MC_63.ENV("ARGV_1"); // argv[1] in native ELF
MC_63.ENV("ARGV_1") in a native ELF reads argv[1] from the process arguments, not from environment variables. In non-native backends it reads the environment variable named ARGV_1.
Operators
Arithmetic
| Operator | Description | Example |
|---|
+ | Addition | a + b |
- | Subtraction | a - b |
* | Multiplication | a * b |
/ | Integer division | a / b |
% | Modulo | a % b |
Comparison
| Operator | Description |
|---|
== | Equal |
!= | Not equal |
< | Less than |
> | Greater than |
<= | Less than or equal |
>= | Greater than or equal |
Logical
| Operator | Description |
|---|
&& | Logical AND |
|| | Logical OR |
! | Logical NOT |
String Concatenation
let greeting = "Hello, " + name + "!";
String Literals and Escapes
PCD strings are UTF-8. Supported escape sequences:
| Escape | Meaning |
|---|
\n | Newline |
\t | Tab |
\\ | Backslash |
\" | Double quote |
let msg = "Line 1\nLine 2\nLine 3";
let path = "C:\\Users\\admin";
let quoted = "He said \"hello\"";
Binary literals (0b1010) are not supported by the PCD parser. Use decimal or hexadecimal integer literals instead.
OUTPUT Directive
OUTPUT marks the circuit’s terminal value. It is equivalent to a return at the circuit level:
PC compute {
let result = 2 + 2;
OUTPUT result;
}
Multiple code paths can all lead to OUTPUT:
PC classify {
let n = MC_63.ENV("ARGV_1");
if (n > 0) {
OUTPUT "positive";
} else if (n < 0) {
OUTPUT "negative";
} else {
OUTPUT "zero";
}
}
Each OUTPUT statement terminates its control path. The TCE ensures that all control paths in a circuit eventually reach an OUTPUT or a return.
Parser Limits
| Limit | Value | Notes |
|---|
MAX_DEPTH | 256 | Maximum nesting depth for AST nodes |
| Binary literals | Not supported | Use decimal/hex only |
while loops | Use with caution | WhileLoop SSA bug in current planner |
| Recursion | Depth ≤ 256 | Use iterative loop(N) for deep recursion |
| Max call depth | 4096 (BIR) | Increased for emitter recursion |
Complete Example: Tokenizer
This example from brikc.pcd shows real PCD patterns — loop(N) with loop-carried vars, monomer calls, and early return:
fn scan_word(text, pos, max_len) {
let start = pos;
loop(max_len) as i {
if (pos >= max_len) {
return MC_42.SUBSTR(text, start, pos - start);
}
let ch = MC_45.CHAR_AT(text, pos);
if (is_alpha(ch) || is_digit(ch) || ch == "_") {
let pos = pos + 1;
} else {
return MC_42.SUBSTR(text, start, pos - start);
}
}
return MC_42.SUBSTR(text, start, pos - start);
}
Key patterns demonstrated:
loop(max_len) as i bounds the scan (no while)
let pos = pos + 1 is a loop-carried rebinding
MC_45.CHAR_AT and MC_42.SUBSTR are string monomers
- Multiple
return paths all produce coherent values