Tutorial: Your First PCD Program
This tutorial takes you from zero to a working PCD program in 15 minutes. By the end, you will have:
- Installed the
brikc compiler
- Written and compiled a PCD program
- Understood monomers, EVA composition, and Φ_c certification
- Built a real policy circuit
Step 1: Install brikc
curl -fsSL https://brik64.dev/install | sh
Verify the installation:
brikc --version
# brikc v4.0.0-beta.2 (fixpoint: 7229cfcd...)
brikc check --self
# ✓ Self-compilation fixpoint verified
brikc check --self verifies that the compiler binary matches the known fixpoint hash. If this passes, you have an authentic, unmodified BRIK-64 compiler.
Step 2: Hello World
Create a file called hello.pcd:
PC hello {
let msg = "Hello, Digital Circuitality!\n";
let len = MC_43.LEN(msg);
MC_33.WRITE(1, msg, len);
OUTPUT 0;
}
What’s happening here:
PC hello { ... } — defines a circuit named “hello”
MC_43.LEN(msg) — calls monomer 43 (String family: LEN) to get the string length
MC_33.WRITE(1, msg, len) — calls monomer 33 (I/O family: WRITE) to write to stdout (fd=1)
OUTPUT 0 — the circuit’s terminal value (exit code 0)
Run it:
brikc run hello.pcd
# Hello, Digital Circuitality!
Check its certification:
brikc check hello.pcd
# ✓ Circuit closed: Φ_c = 1.000
# ✓ All monomers verified
# ✓ Ω = 1
Step 3: Understanding Monomers
Monomers are the 64 Core atomic operations (plus 64 Extended) of BRIK-64. Think of them as logic gates — each one does exactly one thing, and each one is formally verified.
Every monomer is called with MC_XX.NAME(args):
PC monomer_demo {
// Arithmetic: MC_00.ADD8 adds two numbers (saturating at 255)
let sum = MC_00.ADD8(100, 50); // 150
// String: MC_40.CONCAT joins strings
let greeting = MC_40.CONCAT("sum = ", "150");
// I/O: MC_58.WRITE prints to stdout
let _ = MC_58.WRITE(greeting + "\n");
OUTPUT 0;
}
Key rule: Monomers always return Value::I64, except MC_03.DIV8 which returns a tuple:
let (quotient, remainder) = MC_03.DIV8(17, 5);
// quotient = 3, remainder = 2
Forgetting to destructure MC_03.DIV8 is the #1 beginner mistake. Always use let (q, r) = MC_03.DIV8(a, b).
Step 4: Variables, Functions, and Loops
Variables
All variables are immutable. “Updating” a variable creates a new binding:
let x = 10;
let x = x + 1; // new SSA slot, x is now 11
Functions
fn double(n) {
return n * 2;
}
fn greet(name) {
return "Hello, " + name + "!";
}
Loops
Always prefer loop(N) over while:
// Good: bounded, deterministic
let sum = 0;
loop(100) as i {
let sum = sum + i;
}
// With index variable
loop(10) as i {
let _ = MC_58.WRITE("iteration " + from_int(i) + "\n");
}
while loops have a known SSA bug in the current planner. Always use loop(N) with an if guard instead:// Instead of: while (pos < max) { ... }
// Use:
loop(max) as _i {
if (pos < max) {
// your logic here
let pos = pos + 1;
}
}
Step 5: Using the Standard Library
PCD has a standard library written entirely in PCD:
import "stdlib/math.pcd";
import "stdlib/string.pcd";
import "stdlib/array.pcd";
import "stdlib/fmt.pcd";
PC stdlib_demo {
// Math
let x = abs(-42); // 42
let m = max(10, 20); // 20
let s = sqrt(144); // 12
// String
let parts = split("a,b,c", ","); // ["a", "b", "c"]
let upper = upper("hello"); // "HELLO"
let found = contains("hello world", "world"); // true
// Array
let nums = [3, 1, 4, 1, 5];
let sorted = sort(nums); // [1, 1, 3, 4, 5]
let doubled = map(nums, fn(x) { x * 2 }); // [6, 2, 8, 2, 10]
// Formatting
let msg = format("Sum={}, Max={}", [sum, m]);
OUTPUT msg;
}
Step 6: Conditionals and Pattern Matching
if / else
fn classify(n) {
if (n > 0) {
return "positive";
} else if (n < 0) {
return "negative";
} else {
return "zero";
}
}
match
fn day_name(d) {
return match d {
1 => "Monday",
2 => "Tuesday",
3 => "Wednesday",
4 => "Thursday",
5 => "Friday",
6 => "Saturday",
7 => "Sunday",
_ => "invalid"
};
}
Step 7: Structs
struct Point {
x: i64,
y: i64
}
fn distance_squared(a, b) {
let dx = a.x - b.x;
let dy = a.y - b.y;
return dx * dx + dy * dy;
}
PC struct_demo {
let p1 = Point { x: 0, y: 0 };
let p2 = Point { x: 3, y: 4 };
let d2 = distance_squared(p1, p2);
// d2 = 25
OUTPUT d2;
}
Step 8: Error Handling
PC safe_read {
try {
let data = MC_56.READ("input.txt");
let _ = MC_58.WRITE("Read " + from_int(len(data)) + " bytes\n");
OUTPUT 0;
} catch (err) {
let _ = MC_58.WRITE("Error: " + err + "\n");
OUTPUT 1;
}
}
try/catch is a statement, not an expression. You cannot write let x = try { ... }. Assign inside the block instead.
Step 9: Build a Real Policy Circuit
This is where PCD shines. A policy circuit intercepts an AI action and returns ALLOW or BLOCK:
import "stdlib/string.pcd";
import "stdlib/json.pcd";
import "stdlib/array.pcd";
PC file_write_policy {
// Read the proposed action from stdin
let action_json = MC_56.READ("/dev/stdin");
let action = parse(action_json);
let path = get(action, "path");
let agent = get(action, "agent_id");
// Rule 1: Only /tmp/ writes allowed
if (!starts_with(path, "/tmp/")) {
OUTPUT "BLOCK: writes only allowed to /tmp/";
}
// Rule 2: Only known agents
let known = ["claude-code", "codex-cli"];
if (!contains(known, agent)) {
OUTPUT "BLOCK: unknown agent " + agent;
}
// Rule 3: No dotfiles
if (contains(path, "/.")) {
OUTPUT "BLOCK: dotfile writes forbidden";
}
OUTPUT "ALLOW";
}
Compile and test:
# Compile to native binary
brikc build file_write_policy.pcd -o policy
# Test with a valid action
echo '{"path":"/tmp/output.txt","agent_id":"claude-code"}' | ./policy
# ALLOW
# Test with a blocked action
echo '{"path":"/etc/passwd","agent_id":"claude-code"}' | ./policy
# BLOCK: writes only allowed to /tmp/
# Test with unknown agent
echo '{"path":"/tmp/test.txt","agent_id":"rogue-agent"}' | ./policy
# BLOCK: unknown agent rogue-agent
Why Φ_c = 1 matters here: The compiler formally verifies that every possible input to this circuit produces either “ALLOW” or “BLOCK: …”. There is no input that causes undefined behavior, a crash, or an ambiguous result.
Step 10: Compile to Multiple Targets
The same PCD compiles to 5 backends:
# Native x86-64 ELF (standalone, no dependencies)
brikc build policy.pcd -o policy
# Rust module
brikc emit --rust policy.pcd -o policy.rs
# JavaScript ES module
brikc emit --js policy.pcd -o policy.mjs
# Python module
brikc emit --python policy.pcd -o policy.py
# WebAssembly
brikc build --target wasm32 policy.pcd -o policy.wasm
All five outputs are semantically equivalent — same input always produces same output, regardless of target.
What to Learn Next