Skip to main content

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

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

OperatorDescriptionExample
+Additiona + b
-Subtractiona - b
*Multiplicationa * b
/Integer divisiona / b
%Moduloa % b

Comparison

OperatorDescription
==Equal
!=Not equal
<Less than
>Greater than
<=Less than or equal
>=Greater than or equal

Logical

OperatorDescription
&&Logical AND
||Logical OR
!Logical NOT

String Concatenation

let greeting = "Hello, " + name + "!";

String Literals and Escapes

PCD strings are UTF-8. Supported escape sequences:
EscapeMeaning
\nNewline
\tTab
\\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

LimitValueNotes
MAX_DEPTH256Maximum nesting depth for AST nodes
Binary literalsNot supportedUse decimal/hex only
while loopsUse with cautionWhileLoop SSA bug in current planner
RecursionDepth ≤ 256Use iterative loop(N) for deep recursion
Max call depth4096 (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