Generate code in SLOP (Symbolic LLM-Optimized Programming), a language designed for minimal human involvement in coding. Use when: (1) User asks for SLOP code, (2) Need strong contracts and range types, (3) Creating code with typed holes for incremental generation, (4) Generating efficient C code. SLOP uses S-expression syntax and transpiles to C.
SLOP (Symbolic LLM-Optimized Programming) is a language designed for hybrid human-machine code generation. It uses S-expression syntax and transpiles to C.
The SLOP_HOME environment variable points to the SLOP distribution. Resolution order: (1) SLOP_HOME if set, (2) package-relative paths as fallback.
| Location | Contents |
|---|---|
$SLOP_HOME/spec/LANGUAGE.md | Complete language specification |
$SLOP_HOME/spec/REFERENCE.md | Quick reference for code generation |
$SLOP_HOME/spec/VERIFICATION.md | Verification guide |
$SLOP_HOME/lib/std/ | Standard library modules |
$SLOP_HOME/lib/compiler/ | Self-hosted compiler sources |
$SLOP_HOME/bin/ |
| Native compiler binaries |
$SLOP_HOME/examples/ | Code samples and reference implementations |
Standard library modules:
| Module | Path | Description |
|---|---|---|
| io | lib/std/io/file.slop | File I/O operations |
| os | lib/std/os/env.slop | Environment variables |
| strlib | lib/std/strlib/strlib.slop | String utilities |
| math | lib/std/math/mathlib.slop | Math functions (FFI to math.h) |
| thread | lib/std/thread/thread.slop | Concurrency primitives |
| path | lib/std/path/path.slop | Path manipulation (dirname, join, basename, extension) |
SLOP is self-hosted: native compiler binaries are used by default. Use --python on most commands to fall back to the Python toolchain.
# Inspection and documentation
slop parse file.slop # Parse and inspect AST
slop parse file.slop --holes # Show holes in file
slop doc file.slop # Generate documentation from annotations
slop ref # Full language reference (for AI)
slop ref types # Type system reference
slop ref --list # List available topics
slop paths # Show resolved SLOP paths
# Verification
slop check file.slop # Type check
slop verify file.slop # Verify contracts with Z3
slop verify file.slop --mode warn # Warn instead of error on failures
slop test file.slop # Run @example and @property tests
# Building
slop transpile file.slop -o out.c # Transpile to C
slop build file.slop -o binary # Full build (requires cc)
slop build file.slop --library static # Build as static library
slop build file.slop --skip-check # Skip type checking
# Code generation and formatting
slop fill file.slop -o filled.slop # Fill holes with LLM
slop derive schema.json -o types.slop # Generate types from schema
slop format file.slop # Format SLOP source code
slop format file.slop --check # Check formatting (CI)
Humans: Specify WHAT (intent, contracts, types, examples)
Machines: Handle HOW (implementation, verification, compilation)
Type system: Catch errors early (range bounds, exhaustive matching, contract violations)
When generating or editing SLOP code, follow these structural editing rules to ensure syntactically correct output.
Balanced parentheses - ALL code must have perfectly balanced ( and ). Count obsessively.
Structural editing terminology - Describe changes using structural operations BEFORE showing code:
<slop-paren-audit> requirement - Before ANY SLOP code block, insert an audit:
<slop-paren-audit>
Line 1: ( = 3, ) = 3
Line 2: ( = 2, ) = 2
Total: ( = 5, ) = 5 ✓
</slop-paren-audit>
BEFORE/AFTER structure - When editing existing code, show markers:
BEFORE: →( target-sexp )←
AFTER: →( new-sexp )←
Atomic units - Treat these as indivisible:
@-forms: (@intent ...), (@spec ...), (@pre ...), etc.(hole ...) expressions(fn ...), (type ...), (module ...), (ffi ...) declarationsInfix {} as unit - Treat entire {infix conditions} as one balanced sexp unit:
(@pre {x > 0 and x < 100}) ; {} is atomic
Refuse risky edits - If a requested edit risks breaking structure, refuse and suggest the structural equivalent. Never produce unbalanced code.
SLOP idioms:
@-contracts together at top of function bodylet bindings vertically; Comment
;; Documentation comment
(module name (export ...) forms...)
(import module-name name*)
(type Name type-expr)
(const NAME Type const-expr)
(fn name ((param Type)...) annotations... body)
(fn name ((param Type)...) annotations... body :c-name "c_name") ; C name override
(fn ((param Type)...) body) ; Lambda (anonymous function)
(const NAME Type const-expr)
;; const-expr: literals, other constants, arithmetic/bitwise, sizeof
;; NOT allowed: function calls, variables, runtime expressions
(const MAX_CONN Int 128)
(const BUF_SIZE U64 (* 4 1024))
(const FLAGS Int (| FLAG_A FLAG_B))
C Mapping: integers → #define, others → static const.
(fn name ((in x Type) ; Read-only (default) - pass by value
(out result Type) ; Write-only - pointer to uninitialized
(mut state Type)) ; Mutable - value or pointer depending on Type
...)
;; Immutable bindings (default) - set! is NOT allowed
(let ((x 10)
(y 20))
(+ x y))
;; Sequential bindings - later bindings can reference earlier ones
(let* ((x 10)
(y (+ x 5))) ; y can use x
(+ x y)) ; → 25
;; Mutable bindings - set! IS allowed
(let ((mut counter 0))
(set! counter (+ counter 1))
counter)
;; Mutable with explicit type
(let ((mut total Int 0))
(for (i 0 10)
(set! total (+ total i)))
total)
Important: set! on a variable requires the mut keyword in its binding.
(Int) ; Any integer (int64_t)
(Int 0 ..) ; Non-negative
(Int 1 .. 100) ; Bounded range
(I8) (I16) (I32) (I64) ; Explicit width signed
(U8) (U16) (U32) (U64) ; Explicit width unsigned
(Float) ; 64-bit float (double)
(F32) ; 32-bit float
(Bool) ; true or false
(String) ; Record: { data: (Ptr U8), len: U64 }
(String 1 .. 255) ; Length-bounded string
(Bytes) ; Record: { data: (Ptr U8), len: U64, cap: U64 }
(List T 1 ..) ; Non-empty list
(Array T 10) ; Fixed-size array (stack allocated)
(Slice T) ; View into array/list
(Ptr T) ; Pointer to T
(ScopedPtr T) ; Scoped pointer (freed when scope ends)
(OptPtr T) ; Nullable pointer
(Option T) ; T or none
(Result T E) ; Success or error
(Map K V) ; Hash map
(Set T) ; Hash set
(Chan T) ; Typed channel (concurrency)
(Thread T) ; Thread handle returning T
(enum a b c) ; Enumeration
(record (x T) (y U)) ; Struct
(union (a T) (b U)) ; Tagged union
;; Collection literals - IMMUTABLE values
(list Int 1 2 3) ; Immutable list with explicit type
(list 1 2 3) ; Inferred from first element
(map String Int ("a" 1) ("b" 2)) ; Immutable map with explicit types
(map ("a" 1) ("b" 2)) ; Inferred from first pair
;; For mutable collections, use list-new/map-new with mut binding:
(let ((mut items (list-new arena Int)))
(list-push items 1) ; OK: items is mutable
items)
;; See references/types.md for complete type reference
(@generic (T)) ; Single type parameter
(@generic (T U)) ; Multiple type parameters
;; Type variables appear in @spec param/return types
(fn identity ((in x T))
(@generic (T))
(@intent "Return the value unchanged")
(@spec ((T) -> T))
(@pure)
x)
At call sites, the type checker unifies argument types to bind type variables and substitutes into the return type.
(fn name ((params...))
(@intent "Human-readable purpose") ; REQUIRED
(@spec ((ParamTypes) -> ReturnType)) ; REQUIRED
body)
Contracts enable automated testing and future SMT verification. Always specify when possible - they are essential to SLOP's verification story.
(@pre condition) ; Precondition - REQUIRED for non-trivial functions
(@post condition) ; Postcondition ($result = return value)
(@assume condition) ; Trusted axiom for verification (e.g., FFI behavior)
(@pure) ; No side effects, deterministic
(@trusted) ; Skip verification entirely (for unverifiable functions)
(@doc "Extended documentation") ; Detailed docs, emits as C comment
(@example (args) -> result) ; Executable test case - include multiple!
(@example :eq eq-fn (args) -> result) ; Test with custom equality function
(@alloc arena) ; Function allocates in arena
(@alloc static) ; Returns static/global data
(@alloc none) ; Does not allocate
Contracts support infix notation using curly braces. Prefer infix for scaffolds as it's more readable:
;; Infix notation - PREFERRED for scaffolds
(@pre {x > 0})
(@pre {x >= 0 and x <= 100})
(@post {$result == a + b})
;; Prefix notation (also valid)
(@pre (> x 0))
(@post (== $result (+ a b)))
Infix precedence (high to low): *, /, % → +, - → comparisons → and → or
Grouping and function calls:
(@pre {(a + b) * c > 0}) ; Use () for grouping
(@pre {(len arr) > 0}) ; Use prefix for function calls
(@post {$result >= (min a b)}) ; Mix infix operators with prefix calls
Examples are especially important - they serve as:
(@property (forall (x T) expr)) ; Property assertion
(@deprecated "message") ; Deprecation notice
(@generation-mode mode) ; deterministic|template|llm
(@derived-from "path") ; Source tracking
(@generated-by src :version v) ; Generation metadata
(@requires category :prompt "..." (fn-sigs...)) ; Requirements
(@loop-invariant condition) ; Loop invariant (verification escape hatch)
(@invariant condition) ; Type invariant (on type definitions)
(@callback-assume param expr) ; Property of callback args ($callback-arg for each arg)
Use @invariant on type definitions to specify relational constraints between fields.
Invariant expressions use field names directly (not prefixed):
(type Graph (record (triples (List Triple)) (size GraphSize))
(@invariant (== size (list-len triples))))
(type Counter (record (count (Int 0 ..)) (max-count (Int 1 ..)))
(@invariant (<= count max-count)))
Use @loop-invariant inside loops for verification:
(let ((mut sum 0))
(for (i 0 n)
(@loop-invariant {sum >= 0})
(set! sum (+ sum i)))
sum)
Use @callback-assume to declare properties of callback arguments in higher-order functions:
(fn for-each-item ((g Graph) (callback (Fn (Item) Unit)))
(@intent "Apply callback to each item in graph")
(@spec ((Graph (Fn (Item) Unit)) -> Unit))
(@callback-assume callback (graph-contains g $callback-arg))
...)
;; $callback-arg refers to each argument passed to the callback
The verifier desugars callback-taking function calls into for-each loops and transforms @callback-assume into (forall (t $result) ...) axioms for Z3 reasoning.
The @requires annotation declares dependencies that must be provided before code can be filled:
(@requires storage
:prompt "Which storage approach?"
:options (
("In-memory Map" map)
("Database via FFI" db))
;; Required function signatures
(state-get-pet ((state (Ptr State)) (id PetId)) -> (Option (Ptr Pet)))
(state-insert-pet ((state (Ptr State)) (pet (Ptr NewPet))) -> Pet))
;; Arena allocation (primary pattern)
(with-arena 4096
(let ((data (arena-alloc arena (sizeof Data))))
...)) ; Arena freed at end
;; Named arenas (binds custom name instead of 'arena')
(with-arena :as scratch 4096
(arena-alloc scratch 256)) ; Uses 'scratch' instead of 'arena'
;; Nested named arenas for different lifetimes
(with-arena :as output 8192
(with-arena :as scratch 4096
(let ((temp (parse scratch input)))
(build-result output temp))))
;; scratch freed first, then output
;; For allocating functions, pass arena as first param
(fn create-user ((arena Arena) (name String))
(@alloc arena)
...)
;; Mutable pointer for in-place modification
(let ((mut conn (create-connection arena)))
(set! conn state 'connected)
conn)
;; Slices (borrowed views)
(fn process ((data (Slice U8)))
(for-each (byte data) ...))
Two modes: generation (create new code) and refactoring (improve existing code).
;; Generation mode - create new implementation
(hole Type "prompt")
(hole Type "prompt"
:complexity tier-2 ; tier-1 to tier-4
:context (var1 var2 fn1) ; Whitelist of available identifiers
:required (var1 fn1) ; Identifiers that must appear in output
:examples ((in) -> out))
;; Refactoring mode - improve existing code
;; Pass existing code as the 3rd positional argument (after Type and prompt)
(hole Type "prompt"
existing-code-expression ; The code to refactor/improve
:complexity tier-3)
;; Example: Refactor for performance
(hole Int "optimize this loop"
(let ((mut sum 0))
(for (i 0 n)
(set! sum (+ sum (* i i))))
sum)
:complexity tier-3
:context (n))
Complexity Tiers:
tier-1: 1-3B models (trivial: simple arithmetic, field access)tier-2: 7-8B models (simple conditionals, result construction)tier-3: 13-34B models (loops, multiple conditions, state)tier-4: 70B+ models (algorithms, complex state machines);; In match expressions:
_ ; Wildcard
identifier ; Binding (captures value)
'symbol ; Quoted value (enum variant match)
literal ; Literal match
(array p1 p2...) ; Array pattern
(list p1 p2... | rest) ; List with rest
(record Type (f1 p1)...) ; Struct destructure
(union Tag pattern) ; Union variant
(guard pattern when expr) ; Guarded pattern
Important: Use quoted symbols 'Fizz for enum value matches. Bare identifiers are bindings.
;; Error handling (Result type)
(match (might-fail x)
((ok val) (use val))
((error e) (handle e)))
;; Simple enum matching - bare variant names
(match status
(Active (do-active))
(Inactive (do-inactive)))
;; Field access (auto -> for pointers)
(. record field) ; record.field or record->field
(set! record field value) ; Mutation
(@ array index) ; Array indexing
;; Loops
(for (i 0 10) body)
(for-each (x collection) body) ; Iterate List, Set, or Map (keys only)
(for-each ((key value) map) body) ; Iterate Map key-value pairs
(while cond body)
(break) (continue) (return expr)
;; for-each collection types:
;; - List: elements in order
;; - Set: elements (order undefined)
;; - Map with (var map): iterates keys only
;; - Map with ((k v) map): iterates key-value pairs
;; Data construction
(record-new Type (f1 v1) (f2 v2)) ; Named fields
(TypeName v1 v2 ...) ; Positional (order matches definition)
(union-new Type Tag value)
(ok val) (error 'variant) ; QUOTE the error variant!
(some val) (none)
;; Address-of and dereference
(addr expr) ; &expr
(deref ptr) ; *ptr
;; Error handling shorthand
(? fallible-expr) ; Early return on error
;; Declare C functions and constants
(ffi "header.h"
(func-name ((param Type)...) ReturnType) ; Function
(func-name ((param Type)...) ReturnType :variadic) ; Variadic function
(CONST_NAME Type) ; Constant (no params = constant)
...)
;; Declare C structs for field access
(ffi-struct "header.h" struct_name
(field1 Type1)
(field2 Type2))
;; When SLOP name differs from C name
(ffi-struct "sys/stat.h" stat_buf :c-name "stat"
(st_size I64)
(st_mode U32))
;; Type casting
(cast Type expr)
;; C inline escape hatch
(c-inline "SOME_C_CONSTANT")
;; FFI-only type: Char
;; Use Char for C char* parameters (distinct from I8/U8)
(ffi "string.h"
(strcpy ((dest (Ptr Char)) (src (Ptr Char))) (Ptr Char)))
Example:
(ffi "stdio.h"
(SEEK_SET Int) ; Constant
(SEEK_CUR Int)
(EOF Int)
(printf ((fmt (Ptr Char))) Int :variadic) ; Variadic C function
(fclose ((file (Ptr Void))) Int)) ; Function
(ffi "unistd.h"
(read ((fd Int) (buf (Ptr U8)) (n U64)) I64)
(write ((fd Int) (buf (Ptr U8)) (n U64)) I64)
(close ((fd Int)) Int))
(ffi-struct "netinet/in.h" sockaddr_in
(sin_family U16)
(sin_port U16)
(sin_addr U32))
SLOP C
──── ─
(Int 0 .. 255) → uint8_t + range check
(Ptr User) → User*
(. user name) → user->name (auto-detects pointer)
(arena-alloc ...) → slop_arena_alloc(...)
(@pre {x > 0}) → SLOP_PRE(x > 0, "x > 0")
(@post {$result > 0}) → SLOP_POST(result > 0, "result > 0")
;; Arithmetic
(+ a b) (- a b) (* a b) (/ a b) (% a b)
(min a b) (max a b) ; Minimum/maximum of two values
;; Memory
(arena-new size) (arena-alloc arena size) (arena-free arena)
(with-arena size body) ; Scoped arena, implicit 'arena' var
(with-arena :as name size body) ; Named scoped arena, binds 'name'
(sizeof Type) (addr expr) (deref ptr)
;; I/O (String, Int, Bool, or Float - type inferred)
(print val) (println val)
;; Strings (builtins)
(int-to-string arena n) ; Int -> String
(string-new arena str) (string-len s) (string-concat arena a b)
(string-eq a b) (string-slice s start end) (string-split arena s delim)
(string-push-char arena s c) ; Append U8 char to string
;; Strings (strlib - requires import)
;; (import strlib (starts-with ends-with contains trim parse-int float-to-string))
;; float-to-string: (float-to-string arena f) ; Float -> String
;; Lists
(list-new arena Type) ; Create empty mutable list
(list Type e1 e2...) ; Immutable literal
(list-push list item) ; Mutates list (requires mut binding)
(list-pop list) ; Remove and return last element -> (Option T)
(list-get list idx) (list-len list)
;; Maps
(map-new arena KeyType ValType) ; Create empty mutable map
(map KeyType ValType (k1 v1)...) ; Immutable literal
(map-put m k v) ; Mutates map (requires mut binding)
(map-get m k) (map-has m k)
(map-keys m) ; Return list of all keys
(map-remove m k) ; Remove key from mutable map
;; Sets
(set-new arena ElementType) ; Create empty mutable set
(set Type e1 e2...) ; Immutable set literal
(set-put set elem) ; Add element to set
(set-has set elem) ; Check if element exists -> Bool
(set-remove set elem) ; Remove element from set
(set-elements set) ; Get all elements as list
;; Result
(ok val) (error 'variant) (is-ok r) (unwrap r)
;; Option
(some val) (none) (is-some o) (is-none o)
;; Time
(now-ms) (sleep-ms ms)
;; Concurrency (thread library)
(chan Type arena) ; Create unbuffered channel → (Ptr (Chan T))
(chan-buffered Type arena cap) ; Create buffered channel → (Ptr (Chan T))
(chan-close ch) ; Close channel
(send ch value) ; Send value (blocks if full) → (Result Unit ChanError)
(recv ch) ; Receive value (blocks if empty) → (Result T ChanError)
(try-recv ch) ; Non-blocking receive → (Result T ChanError)
(spawn arena func) ; Spawn thread running func → (Ptr (Thread T))
(join thread) ; Wait for thread, get result
(@pre {x > 0}) not (@pre (> x 0))(error 'not-found) not (error not-found)mut for mutable bindings: (let ((mut x 0)) (set! x 1))list-new/map-new with mut binding for mutable collectionsWhen generating SLOP scaffolds (files with holes for LLM filling):
;; GOOD: Use the named type in signatures
(type PetId (Int 1 ..))
(fn get-pet ((id PetId)) ...)
;; BAD: Inline range (LLM won't connect it to PetId)
(fn get-pet ((id (Int 1 ..))) ...)
Compile-time requirement: Variant names must be globally unique across all enum and union types in a module. Using the same variant name in different types will result in a compile error.
;; COMPILE ERROR: 'bad-request' and 'not-found' used in multiple types
(type ApiError (enum bad-request not-found))
(type HttpStatus (enum ok bad-request not-found))
;; CORRECT: Unique variant names across all types
(type ApiError (enum api-bad-request api-not-found))
(type HttpStatus (enum http-ok http-bad-request http-not-found))
;; GOOD
(error 'not-found)
;; BAD - "Undefined variable: not-found"
(error not-found)
;; If function expects (Int 0 ..):
(const HANDLER_ID Int 0) ;; BAD - type mismatch
(const HANDLER_ID (Int 0 ..) 0) ;; GOOD
;; :context = whitelist of what CAN be used
;; :required = what MUST appear in output
(hole Unit "delete"
:context (state id) ; Can use these
:required (state-delete-pet)) ; Must call this
;; File: petstore.slop
(module petstore ...) ;; GOOD
;; File: petstore-api.slop
(module petstore ...) ;; BAD - resolver won't find it
SLOP provides multiple levels of verification:
@example execution, @property testingAfter generating SLOP files, run the type checker:
slop check path/to/file.slop
Expected behavior with holes:
UnfilledHoleError - expected for scaffoldspip install -e .
slop parse file.slop # Parse and inspect
slop parse file.slop --holes # Show holes in file
slop check file.slop # Type check a file
slop verify file.slop # Verify contracts with Z3 (requires z3-solver)
slop verify file.slop --mode warn # Warn instead of error
slop verify file.slop --timeout 10 # Set Z3 timeout (seconds)
The verifier checks:
@pre and @post contract consistency (path-sensitive)@invariant)@property auto-propagation as loop invariants@callback-assume for higher-order function reasoninglist-contains verifier-only predicate (usable in annotations, no runtime representation)Verifier-only predicates:
(list-contains lst elem) — membership test for @post, @property, @loop-invariant, @assume annotations only (no C codegen)See $SLOP_HOME/spec/VERIFICATION.md for the full verification guide.
# Validate a hole implementation against expected type
slop check-hole '(+ x 1)' -t Int -p '((x Int))'
# With context from a file
slop check-hole '(helper 42)' -t Int -c myfile.slop
# From stdin
echo '(ok value)' | slop check-hole -t '(Result T E)'
slop transpile file.slop -o output.c # Transpile to C
slop build file.slop -o binary # Full build (requires cc)
slop build file.slop --library static # Build as static library
slop build file.slop --library shared # Build as shared library
slop build file.slop --skip-check # Skip type checking (bootstrapping)
Build Modes:
-g -DSLOP_DEBUG - runtime contract checks enabled-O3 -DNDEBUG - contract checks removed for speed-O2 -DSLOP_SAFE - contract checks with optimizationMost commands accept --python to use the Python toolchain instead of native binaries.
slop derive schema.json -o types.slop # Generate types from schema
slop fill file.slop -o filled.slop # Fill holes with LLM
slop test file.slop # Run @example and @property tests
slop test file.slop --rebuild # Rebuild before testing
slop format file.slop # Format SLOP source code
slop format file.slop --check # Check formatting (exit 1 if not)
slop paths # Show resolved SLOP paths
slop paths -v # Verbose (show examples list)
slop doc file.slop # Generate markdown docs to stdout
slop doc file.slop -o docs.md # Write to file
slop doc file.slop -f json # Output as JSON
Extracts @intent, @spec, @pre, @post, @example annotations into readable documentation.
slop ref # Full language reference
slop ref types # Type system reference
slop ref --list # List available topics
Use slop ref for quick lookups of syntax, built-ins, and patterns.
These functions/patterns do NOT exist in SLOP - use the alternatives:
| Don't Use | Use Instead |
|---|---|
print-int n | (println n) - print/println accept Int directly |
print-float n | (println n) - print/println accept Float directly |
(println enum-value) | Use match to print different strings |
arena outside with-arena | Wrap code in (with-arena size ...) |
(block ...) | (do ...) for sequencing |
(begin ...) | (do ...) for sequencing |
(progn ...) | (do ...) for sequencing |
strlen s | (string-len s) |
malloc | (arena-alloc arena size) |
list.length | (list-len list) |
arr.length | Arrays are fixed size - use declared size |
list-append | (list-push list elem) |
list-add | (list-push list elem) |
map-set | (map-put map key val) |
hash-get | (map-get map key) |
parse-int | (import strlib (parse-int)) |
json-parse | Implement manually or FFI |
string-find | Iterate with for-each |
read-line | FFI to stdio.h |
sqrt, sin, cos | FFI to math.h |
| Definitions outside module | All (type), (fn), (const) inside (module ...) |
All definitions must be INSIDE the module form:
;; CORRECT:
(module my-module
(export public-fn)
(type MyType (Int 0 ..))
(fn public-fn (...)
...)) ; <-- closing paren wraps entire module
;; WRONG:
(module my-module
(export public-fn))
(fn public-fn ...) ; ERROR: outside module form
Quote error variants:
(error 'not-found) ; CORRECT: quoted
(error not-found) ; WRONG: undefined variable