SIGNAL Programming Language — Technical Overview
A complete technical introduction to SIGNAL: its type system, execution model, primitives, and canonical use cases.
SIGNAL Programming Language — Technical Overview
A reactive, statically-typed language for event-driven systems, data processing, and domain-driven design.
Table of Contents
- Language Philosophy
- Minimal Implementation Guide
- Lexical Structure
- Type System
- Variables and Bindings
- Operators
- Control Flow
- Functions
- Reactive Core: Signals, Rules, and Emit
- Domains (Algebraic Data Types)
- Entities (Record Types)
- State Machines
- Flows (Data Pipelines)
- Queries
- Pattern Matching
- Error Handling
- Concurrency
- Module System
- Standard Library
- SIGNAL-Lite (Embeddable Subset)
- Grammar Reference
- Complexity Analysis
1. Language Philosophy
SIGNAL is built on the Unified Council model — four foundational perspectives that shape every feature:
| Perspective | Influence | Contribution | |---|---|---| | Knuth (Algorithmic) | Complexity guarantees, precise numerics | Every operation has a stated O(·) bound | | Erdos (Graph-theoretic) | Entity relationships, state machines | Structures are graphs; transitions are edges | | Turing (Computational) | Type system, pattern matching, decidability | The type checker terminates; matching is exhaustive | | Shannon (Information-theoretic) | Signals, channels, entropy builtins | Events are discrete messages on noiseless channels |
SIGNAL is:
- Expression-oriented —
if,match,loopall return values - Reactive-first — signals and rules are primitives, not libraries
- Strongly typed with inference — types are checked statically but rarely written explicitly
- Functional-imperative hybrid — first-class functions with controlled mutation via
var
2. Minimal Implementation Guide
To build a working SIGNAL interpreter, you need five components:
2.1 Lexer (Tokenizer)
Converts source text into a stream of tokens. Minimum token set:
Keywords: let var fn return if then else match for in
while loop break continue true false null
and or not domain entity sig rule when emit
create update delete try catch
Literals: INTEGER FLOAT STRING BOOLEAN NULL
Identifiers: IDENT TYPE_IDENT (uppercase-start)
Operators: + - * / % ** ++ == != < > <= >=
= += -= *= /= |> => ->
Grouping: ( ) { } [ ]
Punctuation: , . : ;
The lexer is a single-pass O(n) scanner. Key rules:
- Identifiers starting with uppercase are
TYPE_IDENT(used for domains, entities, signals) //starts a line comment;/* ... */for block comments- Strings support interpolation:
"Hello, {name}!" - Number literals:
42,3.14,0xFF,0b1010,1000000
2.2 Parser (AST Builder)
Recursive-descent, no backtracking, O(n). The core grammar:
Program -> Declaration*
Declaration -> FnDecl | DomainDecl | EntityDecl | SigDecl
| RuleDecl | Statement
FnDecl -> 'fn' IDENT '(' Params ')' ('->' Type)? Block
DomainDecl -> 'domain' TYPE_IDENT '=' Variant ('|' Variant)*
EntityDecl -> 'entity' TYPE_IDENT Field*
SigDecl -> 'sig' TYPE_IDENT '(' Params ')'
RuleDecl -> 'rule' IDENT 'when' TYPE_IDENT '(' Params ')'
('where' Expr)? 'then' Block
Statement -> LetStmt | VarStmt | ReturnStmt | ExprStmt | Block
LetStmt -> 'let' IDENT '=' Expr
VarStmt -> 'var' IDENT '=' Expr
Expr -> Pipe
Pipe -> LogicOr ('|>' LogicOr)*
LogicOr -> LogicAnd ('or' LogicAnd)*
LogicAnd -> Equality ('and' Equality)*
Equality -> Comparison (('==' | '!=') Comparison)*
Comparison -> Addition (('<' | '>' | '<=' | '>=') Addition)*
Addition -> Multiplication (('+' | '-' | '++') Multiplication)*
Multiplication -> Power (('*' | '/' | '%') Power)*
Power -> Unary ('**' Unary)?
Unary -> ('not' | '-') Unary | Call
Call -> Primary ('(' Args ')' | '.' IDENT | '[' Expr ']')*
Primary -> Literal | IDENT | '(' Expr ')' | '[' Exprs ']'
| '{' MapEntries '}' | IfExpr | MatchExpr | Lambda
Operator precedence (lowest to highest):
| Level | Operators | Associativity | |-------|-----------|---------------| | 1 | \|> | Left | | 2 | or | Left | | 3 | and | Left | | 4 | == != | Left | | 5 | < > <= >= | Left | | 6 | + - ++ | Left | | 7 | * / % | Left | | 8 | ** | Right | | 9 | not - (unary) | Right | | 10 | . () [] | Left |
2.3 AST (Abstract Syntax Tree)
Node types your AST must represent:
// Declarations
FnDecl { name, params[], returnType?, body }
DomainDecl { name, variants[] }
EntityDecl { name, fields[], derivedFields[] }
SigDecl { name, params[] }
RuleDecl { name, signal, params[], where?, body }
// Statements
LetStmt { name, value }
VarStmt { name, value }
ReturnStmt { value? }
BreakStmt { value? }
ContinueStmt {}
ExprStmt { expr }
Block { statements[] }
// Expressions
BinaryExpr { left, op, right }
UnaryExpr { op, operand }
CallExpr { callee, args[] }
MemberExpr { object, property }
IndexExpr { object, index }
PipeExpr { left, right }
IfExpr { condition, then, else? }
MatchExpr { subject, arms[] }
ForExpr { variable, iterable, body }
WhileExpr { condition, body }
LoopExpr { body }
TryExpr { body, catchParam, catchBody }
LambdaExpr { params[], body }
EmitExpr { signal, args[] }
CreateExpr { entity, fields }
UpdateExpr { target, fields }
DeleteExpr { target }
// Literals
IntLit, FloatLit, StringLit, BoolLit, NullLit
ArrayLit { elements[] }
MapLit { entries[] }
2.4 Runtime Environment
The environment is a chain of scopes (linked hash maps):
Environment {
values: Map<String, Value>
parent: Environment? // lexical scope chain
get(name) -> looks up chain until found or error
set(name, v) -> updates existing binding in-place
define(name, v) -> creates new binding in current scope
}
Value types the runtime must support:
Value = Int(i64)
| Float(f64)
| String(string)
| Bool(bool)
| Null
| Array([]Value)
| Map(map[string]Value)
| Function(params[], body, closure)
| BuiltinFn(name, arity, func)
| DomainVariant(domain, variant, fields)
| EntityInstance(entity, fields)
2.5 Evaluator (Tree-Walk Interpreter)
The evaluator recursively walks the AST. Core dispatch:
eval(node, env) -> Value:
match node:
IntLit(n) -> Int(n)
StringLit(s) -> String(s)
BoolLit(b) -> Bool(b)
NullLit -> Null
Ident(name) -> env.get(name)
BinaryExpr(l, op, r)-> applyOp(op, eval(l, env), eval(r, env))
LetStmt(name, val) -> env.define(name, eval(val, env))
VarStmt(name, val) -> env.define(name, eval(val, env)) // mutable
CallExpr(fn, args) -> call(eval(fn, env), args.map(a => eval(a, env)))
IfExpr(c, t, e) -> if truthy(eval(c, env)) then eval(t, env) else eval(e, env)
Block(stmts) -> eval each stmt in child env; return last
FnDecl(name, p, b) -> env.define(name, Function(p, b, env))
LambdaExpr(p, b) -> Function(p, b, env) // closure
...
2.6 Reactive Runtime (Signals + Rules)
The reactive system is the heart of SIGNAL. Minimum implementation:
ReactiveRuntime {
signals: Map<String, SignalDef> // sig declarations
rules: Map<String, []RuleDef> // rules keyed by signal name
entities: Map<String, EntityDef> // entity schemas
store: Map<String, []EntityInstance> // entity instances
registerSignal(name, params)
registerRule(name, signalName, params, where?, body)
emit(signalName, args):
// O(R) dispatch — iterate rules for this signal
for rule in rules[signalName]:
childEnv = bind(rule.params, args)
if rule.where == null or truthy(eval(rule.where, childEnv)):
eval(rule.body, childEnv)
create(entityName, fields) -> instance
update(instance, fields) -> updated instance
delete(instance) -> removed
}
3. Lexical Structure
3.1 Keywords (Full Language)
SIGNAL defines 170+ keywords across several categories:
Core (30):
let var fn return if then else elif match case when
for in while loop break continue true false null
and or not is try catch finally throw import export
Reactive (12):
sig signal rule emit domain entity derive
create update delete with where
Concurrency (8):
async await spawn join select send receive atomic
Infrastructure (16):
server websocket resource component actor test suite
database queue config schedule migration extern query metric flow
State Machine (8):
state machine initial transition guard action enter exit
Modifiers (11):
pub private protected static final abstract
override virtual mutable immutable lazy
Types (22):
Integer Float Number Text String Boolean Void Any Never
DateTime Duration UUID Bytes Chan Task Stream
BigInt Rational Fixed Interval Map Set
3.2 Operators
Arithmetic: + - / % * String: ++ (concatenation) Comparison: == != <> < > <= >= Logical: and or not && || ! Assignment: = += -= *= /= %= Bitwise: & | ^ ~ << >> Functional: |> (pipe) ~> (compose) -> (arrow) => (fat arrow) Null-safe: ?? (coalesce) ?. (optional chain) ?! (error propagate) Range: .. (inclusive) ..< (exclusive) ... (spread) Channel: <- (receive/send)
Unicode operators:
<= >= != also written as ≤ ≥ ≠
element-of ∈ ∉
set operations ∩ ∪ ∅
quantifiers ∀ ∃
math ∞ λ π Σ Π μ σ
4. Type System
SIGNAL uses bidirectional type inference — the compiler can synthesize types from expressions and check expressions against expected types.
4.1 Primitive Types
let age: Int = 42
let pi: Float = 3.14159
let name: String = "Ada"
let active: Bool = true
let nothing: Null = null
4.2 Collection Types
let nums: [Int] = [1, 2, 3, 4, 5]
let config: Map<String, Any> = {"host": "localhost", "port": 8080}
let pair: (String, Int) = ("hello", 42)
4.3 Function Types
let transform: (Int) -> Int = x => x * 2
let predicate: (String) -> Bool = s => len(s) > 0
4.4 Optional and Result Types
let maybe: Int? = null // optional
let result: Result<Int, String> = Ok(42) // success or error
let option: Option<String> = Some("hello") // some or none
4.5 Advanced Numeric Types (Knuth Precision)
let big: BigInt = 99999999999999999999999
let ratio: Rational = 1/3 // exact, no floating-point loss
let money: Fixed = 19.99 // fixed-point decimal
let range: Interval = [2.99, 3.01] // interval arithmetic
4.6 Union Types
let id: Int | String = "abc-123"
4.7 Type Aliases
type UserId = Int
type Callback = (String) -> Void
type Pair<A, B> = (A, B)
5. Variables and Bindings
5.1 Immutable Bindings (let)
let x = 42
let greeting = "Hello, " ++ name
// x = 99 // ERROR — let bindings cannot be reassigned
5.2 Mutable Bindings (var)
var count = 0
count += 1 // OK — var allows mutation
count = count * 2
5.3 Constants (const)
const MAX_SIZE = 1024
const PI = 3.14159265358979
5.4 Destructuring
let [first, second, ...rest] = [1, 2, 3, 4, 5]
let {name, age} = user
let (x, y) = getPoint()
6. Operators
6.1 Pipe Operator (|>)
The pipe operator passes the left-hand value as the first argument to the right-hand function. It transforms nested calls into readable left-to-right chains:
// Without pipe:
sort(filter(map(data, x => x * 2), x => x > 10))
// With pipe:
data
|> map(x => x * 2)
|> filter(x => x > 10)
|> sort()
6.2 Function Composition (~>)
Creates a new function from two existing functions (Church style — left applies first):
let double = x => x * 2
let addOne = x => x + 1
let doubleAndAdd = double ~> addOne
doubleAndAdd(5) // 11 (double first: 10, then add one: 11)
6.3 String Concatenation (++)
let full = firstName ++ " " ++ lastName
6.4 Null Coalescing (??)
let port = config.port ?? 8080
let name = user?.name ?? "Anonymous"
6.5 Error Propagation (?!)
fn loadConfig(path: String): Result<Config, String> {
let raw = readFile(path)?! // propagates Err automatically
let parsed = jsonParse(raw)?!
Ok(parsed)
}
6.6 Range Operators
let inclusive = 1..10 // [1, 2, 3, ..., 10]
let exclusive = 1..<10 // [1, 2, 3, ..., 9]
for i in 0..<len(items) {
println(items[i])
}
7. Control Flow
7.1 If Expressions
if is an expression — it returns a value:
let status = if score >= 90 then "A"
else if score >= 80 then "B"
else "C"
// Block form:
let result = if condition {
computeA()
} else {
computeB()
}
7.2 Match Expressions
Exhaustive pattern matching:
let label = match statusCode {
200 => "OK"
404 => "Not Found"
500 => "Server Error"
code if code >= 400 => "Client Error"
_ => "Unknown"
}
Matching on domain variants:
domain Shape = Circle(r: Float) | Rect(w: Float, h: Float) | Triangle(a: Float, b: Float, c: Float)
fn area(s: Shape) -> Float {
match s {
Circle(r) => 3.14159 * r ** 2
Rect(w, h) => w * h
Triangle(a,b,c) => {
let s = (a + b + c) / 2
sqrt(s * (s-a) * (s-b) * (s-c))
}
}
}
7.3 For Loops
for item in collection {
println(item)
}
for (key, value) in map {
println(key ++ ": " ++ toString(value))
}
for i in 0..<10 {
println(i)
}
7.4 While Loops
var n = 1
while n <= 100 {
if n % 15 == 0 { println("FizzBuzz") }
else if n % 3 == 0 { println("Fizz") }
else if n % 5 == 0 { println("Buzz") }
else { println(n) }
n += 1
}
7.5 Loop (Infinite)
var attempts = 0
let result = loop {
attempts += 1
let r = tryConnect()
if isOk(r) { break unwrap(r) }
if attempts > 5 { break null }
}
8. Functions
8.1 Named Functions
fn add(a: Int, b: Int) -> Int {
a + b
}
// Expression shorthand:
fn double(x) => x * 2
fn square(x) = x * x
8.2 Lambda Expressions
let inc = x => x + 1
let multiply = (a, b) => a * b
let greet = name => "Hello, " ++ name ++ "!"
8.3 Higher-Order Functions
fn apply(f, x) => f(x)
fn compose(f, g) => x => g(f(x))
let nums = [1, 2, 3, 4, 5]
let evens = filter(x => x % 2 == 0, nums)
let doubled = map(x => x * 2, nums)
let total = reduce((acc, x) => acc + x, 0, nums)
8.4 Closures
Functions capture their lexical environment:
fn makeCounter(start: Int) {
var count = start
fn next() {
count += 1
count
}
next
}
let counter = makeCounter(0)
println(counter()) // 1
println(counter()) // 2
println(counter()) // 3
8.5 Generators
gen fibonacci() {
var a = 0
var b = 1
loop {
yield a
let temp = a
a = b
b = temp + b
}
}
let fibs = fibonacci()
for i in 0..<10 {
println(fibs())
}
8.6 Async Functions
async fn fetchData(url: String) -> Result<String, String> {
let response = await httpGet(url)
if response.status == 200 {
Ok(response.body)
} else {
Err("HTTP " ++ toString(response.status))
}
}
8.7 Annotations
@tailrec
fn factorial(n: Int, acc: Int = 1) -> Int {
if n <= 1 then acc
else factorial(n - 1, n * acc)
}
@memo
fn fib(n: Int) -> Int {
if n <= 1 then n
else fib(n - 1) + fib(n - 2)
}
@pure
fn add(a, b) => a + b
@complexity("O(n log n)")
fn mergeSort(arr) { ... }
9. Reactive Core: Signals, Rules, and Emit
This is the defining feature of SIGNAL. The reactive system models events as discrete signals on noiseless channels (Shannon's channel model).
9.1 Declaring Signals
A signal is a named event shape — it defines what can happen:
sig UserCreated(id: UUID, name: String, email: String)
sig OrderPlaced(orderId: Int, userId: UUID, total: Float)
sig PaymentReceived(orderId: Int, amount: Float)
sig TemperatureReading(sensorId: String, celsius: Float)
9.2 Defining Rules
A rule declares a reaction — when a signal fires, what happens:
rule logNewUser when UserCreated(id, name, email) then {
println("New user: " ++ name ++ " (" ++ email ++ ")")
}
rule sendWelcome when UserCreated(id, name, email)
where endsWith(email, "@company.com")
then {
emit SendEmail(email, "Welcome, " ++ name ++ "!")
}
The optional where clause filters which emissions trigger the rule.
9.3 Emitting Signals
emit UserCreated(uuid(), "Alice", "alice@company.com")
emit OrderPlaced(1001, userId, 59.99)
When emit fires:
- The runtime finds all rules registered for that signal name — O(R) where R is the rule count
- Each rule's parameters are bound to the emitted arguments
- If a
whereclause exists, it is evaluated — skip if falsy - The rule body executes in a child environment
9.4 Signal Chaining
Rules can emit further signals, creating event cascades:
sig OrderPlaced(id: Int, user: UUID, total: Float)
sig InventoryReserved(orderId: Int)
sig PaymentCharged(orderId: Int, amount: Float)
sig OrderConfirmed(orderId: Int)
rule reserveStock when OrderPlaced(id, user, total) then {
// ... reserve inventory
emit InventoryReserved(id)
}
rule chargePayment when InventoryReserved(orderId) then {
let order = getOrder(orderId)
emit PaymentCharged(orderId, order.total)
}
rule confirmOrder when PaymentCharged(orderId, amount) then {
emit OrderConfirmed(orderId)
println("Order #" ++ toString(orderId) ++ " confirmed for $" ++ toString(amount))
}
10. Domains (Algebraic Data Types)
Domains define sum types — a value is exactly one of several variants:
10.1 Simple Enumerations
domain Color = Red | Green | Blue
domain Direction = North | South | East | West
10.2 Variants with Data
domain Shape
= Circle(radius: Float)
| Rectangle(width: Float, height: Float)
| Triangle(a: Float, b: Float, c: Float)
| Point
domain Result = Ok(value: Any) | Err(message: String)
domain Option = Some(value: Any) | None
10.3 Recursive Domains
domain Expr
= Num(value: Float)
| Add(left: Expr, right: Expr)
| Mul(left: Expr, right: Expr)
| Neg(inner: Expr)
fn eval(e: Expr) -> Float {
match e {
Num(v) => v
Add(l, r) => eval(l) + eval(r)
Mul(l, r) => eval(l) * eval(r)
Neg(i) => -eval(i)
}
}
let expr = Add(Mul(Num(3), Num(4)), Neg(Num(1)))
println(eval(expr)) // 11.0
10.4 Pattern Matching with Domains
domain Animal = Dog(name: String) | Cat(name: String, indoor: Bool) | Fish(species: String)
fn describe(a: Animal) -> String {
match a {
Dog(name) => name ++ " is a good dog"
Cat(name, true) => name ++ " is an indoor cat"
Cat(name, false) => name ++ " roams freely"
Fish(species) => "A " ++ species
}
}
11. Entities (Record Types)
Entities are product types — mutable records with named fields. They model real-world objects with CRUD operations.
11.1 Declaration
entity User
id: UUID
name: String
email: String
role: String = "member" // default value
bio: String? // optional (nullable)
created_at: DateTime = now()
derive displayName = name ++ " (" ++ role ++ ")"
derive isAdmin = role == "admin"
11.2 CRUD Operations
// Create
let alice = create User {
id: uuid(),
name: "Alice",
email: "alice@example.com"
}
// Read (field access)
println(alice.name)
println(alice.displayName) // derived field, computed on access
// Update
update alice with {
role: "admin",
bio: "Engineering lead"
}
// Delete
delete alice
11.3 Derived Fields
Derived fields are computed properties — pure functions of other fields:
entity Rectangle
width: Float
height: Float
derive area = width * height
derive perimeter = 2 * (width + height)
derive isSquare = width == height
let r = create Rectangle { width: 5.0, height: 3.0 }
println(r.area) // 15.0
println(r.perimeter) // 16.0
println(r.isSquare) // false
12. State Machines
State machines model entities with discrete states and guarded transitions.
12.1 Declaration
state machine OrderStatus {
initial: pending
state pending {
enter { println("Order created, awaiting payment") }
}
state paid {
enter { emit OrderPaid(orderId) }
}
state shipped {
enter { emit OrderShipped(orderId) }
exit { println("Order has left the warehouse") }
}
state delivered
state cancelled
transition pay: pending -> paid guard paymentValid
transition ship: paid -> shipped guard inventoryAvailable
transition deliver: shipped -> delivered
transition cancel: pending -> cancelled
transition cancel: paid -> cancelled guard refundProcessed
}
12.2 Transitions
Each transition has:
- Name — a label for the transition
- Source → Target — the state change
- Guard (optional) — a boolean condition that must be true
- Action (optional) — code to execute during transition
transition approve: pending -> approved
guard { user.role == "admin" and request.valid }
action { emit RequestApproved(request.id) }
12.3 State Entry/Exit Hooks
state active {
enter { startMonitoring() }
exit { stopMonitoring(); saveSnapshot() }
}
13. Flows (Data Pipelines)
Flows are declarative, reactive data transformation pipelines:
flow ProcessOrders
from OrderPlaced.*
| filter total > 100.0
| map { orderId, total, category: categorize(total) }
| group by category
| window tumbling 1h
| select { category, count: count(), avgTotal: avg(total) }
-> Dashboard
13.1 Flow Operations
| Operation | Description | |-----------|-------------| | filter | Keep events matching predicate | | map | Transform each event | | group by | Partition by field | | window | Time-based windowing (tumbling, sliding, session) | | select | Project/aggregate fields | | sort by | Order results | | limit | Cap output count | | distinct | Remove duplicates |
13.2 Window Types
// Non-overlapping 5-minute windows:
| window tumbling 5m
// 10-minute windows sliding every 1 minute:
| window sliding 10m step 1m
// Group by inactivity gaps:
| window session 30m
14. Queries
Named, parameterized data retrievals:
query ActiveUsers(minAge: Int)
= User
where role != "banned"
and age >= @minAge
and lastLogin >= today - 30d
order by name
limit 100
query OrderStats(year: Int)
= Order
where dateYear(created_at) == @year
group by month
select { month, total: sum(amount), count: count() }
Usage:
let users = ActiveUsers(18)
let stats = OrderStats(2026)
15. Pattern Matching
15.1 Literal Patterns
match x {
0 => "zero"
1 => "one"
_ => "many"
}
15.2 Guard Clauses
match temperature {
t if t < 0 => "freezing"
t if t < 20 => "cold"
t if t < 30 => "comfortable"
_ => "hot"
}
15.3 Destructuring Patterns
match point {
(0, 0) => "origin"
(x, 0) => "on x-axis at " ++ toString(x)
(0, y) => "on y-axis at " ++ toString(y)
(x, y) => "at (" ++ toString(x) ++ ", " ++ toString(y) ++ ")"
}
15.4 Domain Variant Patterns
domain Tree = Leaf(Int) | Branch(Tree, Tree)
fn sum(t: Tree) -> Int {
match t {
Leaf(n) => n
Branch(l, r) => sum(l) + sum(r)
}
}
16. Error Handling
16.1 Try/Catch
try {
let data = readFile("config.json")
let config = jsonParse(data)
println(config.host)
} catch err {
println("Config error: " ++ err)
}
16.2 Try/Catch/Finally
let db = dbOpen("app.sqlite")
try {
dbExec(db, "INSERT INTO logs (msg) VALUES (?)", [message])
} catch err {
println("DB error: " ++ err)
} finally {
dbClose(db)
}
16.3 Result Type
fn divide(a: Float, b: Float) -> Result<Float, String> {
if b == 0 {
Err("Division by zero")
} else {
Ok(a / b)
}
}
match divide(10, 3) {
Ok(v) => println("Result: " ++ toString(v))
Err(e) => println("Error: " ++ e)
}
16.4 Error Propagation
The ?! operator unwraps Ok or early-returns Err:
fn processFile(path: String) -> Result<Data, String> {
let raw = readFileSafe(path)?!
let parsed = parseCsv(raw)?!
let validated = validateData(parsed)?!
Ok(validated)
}
17. Concurrency
17.1 Spawn and Await
let task1 = spawn { heavyComputation(dataA) }
let task2 = spawn { heavyComputation(dataB) }
let resultA = await task1
let resultB = await task2
17.2 Channels
let ch = chan(10) // buffered channel, capacity 10
spawn {
for i in 0..<100 {
ch <- i // send
}
}
spawn {
loop {
let msg = <- ch // receive
println(msg)
}
}
17.3 Select (Multiplexing)
select {
msg <- inbox => handleMessage(msg)
tick <- timer => handleTick()
timeout 5s => handleTimeout()
}
17.4 Atomic Blocks
var balance = 1000
atomic {
if balance >= amount {
balance -= amount
emit PaymentProcessed(amount)
}
}
17.5 Actor Model
actor AccountManager {
var accounts = {}
handle CreateAccount(name, initialBalance) {
accounts[name] = initialBalance
emit AccountCreated(name)
}
handle Transfer(from, to, amount) {
if accounts[from] >= amount {
accounts[from] -= amount
accounts[to] += amount
emit TransferComplete(from, to, amount)
}
}
}
18. Module System
18.1 Module Declaration
module myapp.auth.handlers
18.2 Imports
import { UserService, AuthToken } from myapp.auth
import { hash, verify } from crypto.bcrypt
import http.server as srv
18.3 Exports
export fn authenticate(token: String) -> Result<User, String> { ... }
export entity Session { ... }
// or use the pub modifier:
pub fn publicFunction() { ... }
19. Standard Library
19.1 Math (30+ functions)
abs(-5) // 5
min(3, 7) // 3
max(3, 7) // 7
floor(3.7) // 3
ceil(3.2) // 4
round(3.5) // 4
sqrt(144) // 12.0
pow(2, 10) // 1024
log(100) // 4.605...
log2(1024) // 10.0
sin(PI / 2) // 1.0
clamp(15, 0, 10) // 10
19.2 Collections (40+ functions)
len([1,2,3]) // 3
head([1,2,3]) // 1
tail([1,2,3]) // [2,3]
append([1,2], 3) // [1,2,3]
concat([1,2], [3,4]) // [1,2,3,4]
reverse([1,2,3]) // [3,2,1]
sort([3,1,2]) // [1,2,3]
unique([1,2,2,3,3]) // [1,2,3]
flatten([[1,2],[3,4]]) // [1,2,3,4]
zip([1,2,3], ["a","b","c"]) // [(1,"a"),(2,"b"),(3,"c")]
range(1, 5) // [1,2,3,4,5]
// Functional
map(x => x * 2, [1,2,3]) // [2,4,6]
filter(x => x > 2, [1,2,3,4]) // [3,4]
reduce((a,x) => a + x, 0, [1,2,3]) // 6
find(x => x > 2, [1,2,3,4]) // 3
every(x => x > 0, [1,2,3]) // true
some(x => x > 5, [1,2,3]) // false
groupBy(x => x % 2, [1,2,3,4]) // {0:[2,4], 1:[1,3]}
partition(x => x > 2, [1,2,3,4]) // [[3,4],[1,2]]
19.3 Strings (35+ functions)
len("hello") // 5
upper("hello") // "HELLO"
lower("HELLO") // "hello"
trim(" hi ") // "hi"
split("a,b,c", ",") // ["a","b","c"]
strJoin(["a","b","c"], "-") // "a-b-c"
replace("hello", "l", "r") // "herro"
startsWith("hello", "hel") // true
endsWith("hello", "llo") // true
contains("hello", "ell") // true
substring("hello", 1, 3) // "el"
repeat("ab", 3) // "ababab"
padLeft("42", 5) // " 42"
charAt("hello", 0) // "h"
19.4 Type Inspection and Conversion
typeOf(42) // "Int"
typeOf("hello") // "String"
isInt(42) // true
isString(42) // false
isNull(null) // true
isArray([1,2]) // true
isFunction(x => x) // true
toString(42) // "42"
parseInt("42") // 42
parseFloat("3.14") // 3.14
19.5 Information Theory (Shannon)
entropy([1,0,1,1,0,1,0,0]) // Shannon entropy H(X)
surprisal(0.5) // -log2(0.5) = 1.0 bit
mutual_information(x, y) // I(X;Y)
kl_divergence(p, q) // D_KL(P||Q)
cross_entropy(p, q) // H(P,Q)
joint_entropy(x, y) // H(X,Y)
conditional_entropy(x, y) // H(X|Y)
19.6 File I/O
let text = readFile("data.txt")
writeFile("output.txt", result)
appendFile("log.txt", message)
let lines = readLines("data.csv")
let files = listDir("./src")
exists("config.json") // true/false
mkdir("output")
remove("temp.txt")
19.7 HTTP
let res = httpGet("https://api.example.com/data")
println(res.status) // 200
println(res.body) // response body
let res = httpPost("https://api.example.com/users", {
name: "Alice",
email: "alice@example.com"
})
19.8 Database
let db = dbOpen("sqlite:app.db")
let users = dbQuery(db, "SELECT * FROM users WHERE active = ?", [true])
dbExec(db, "INSERT INTO logs (msg) VALUES (?)", [message])
dbClose(db)
19.9 JSON
let obj = jsonParse('{"name":"Alice","age":30}')
let str = jsonStringify({name: "Alice", age: 30})
19.10 Regex
reMatch("^[a-z]+$", "hello") // true
reCapture("(\\d+)-(\\d+)", "42-99") // ["42", "99"]
reFindAll("\\d+", "a1b2c3") // ["1","2","3"]
reReplace("\\d", "a1b2", "*") // "a*b*"
reSplit("\\s+", "hello world") // ["hello", "world"]
19.11 Cryptography
sha256("message") // hex hash
bcryptHash("password123") // bcrypt hash
bcryptVerify("password123", hash) // true/false
let key = rsaGenerateKey(2048)
let encrypted = aesEncrypt(data, key)
let decrypted = aesDecrypt(encrypted, key)
randomBytes(32) // 32 random bytes
19.12 DataFrames
let df = dfFromCsv("data.csv")
let filtered = dfFilter(df, row => row.age > 18)
let grouped = dfGroupBy(df, ["department"])
let summary = dfDescribe(df)
dfToCsv(filtered, "output.csv")
19.13 NLP / Text Processing
let tokens = tokenize("The quick brown fox")
let freq = wordFreq(text)
let top = topWords(text, 10)
let score = sentiment("I love this product!")
let lang = detectLanguage("Bonjour le monde")
19.14 Tensors and ML
let t = tensorCreate([[1,2],[3,4]])
let result = tensorMatmul(t, weights)
let activated = tensorRelu(result)
let probs = tensorSoftmax(activated)
let model = onnxLoad("model.onnx")
let output = onnxRun(model, input)
19.15 Combinators
let factorial = fix(f => n => if n <= 1 then 1 else n * f(n - 1))
factorial(10) // 3628800
let fib = memo_fix(f => n => if n <= 1 then n else f(n-1) + f(n-2))
fib(50) // 12586269025 (fast — memoized)
20. SIGNAL-Lite (Embeddable Subset)
SIGNAL-Lite is a decidable 41-keyword subset designed for embedding in host applications (e.g., PHP, JavaScript). It retains the reactive core while dropping infrastructure concerns.
20.1 Keyword Set
| Category | Keywords | |----------|----------| | Bindings | let var fn return | | Control | if then else match for in while loop break continue | | Literals | true false null | | Logic | and or not is | | Reactive | domain entity sig rule when emit | | CRUD | create update delete with where | | State | state machine initial transition guard | | Error | try catch | | Module | import export |
Plus 3 anaphoric referents: this that it
20.2 Implementation Components
A minimal SIGNAL-Lite interpreter requires:
| Component | Purpose | Size | |-----------|---------|------| | Lexer | Tokenization | ~300 lines | | Ast | Node definitions | ~200 lines | | Parser | Recursive descent | ~800 lines | | Runtime | Environment + values | ~400 lines | | ReactiveRuntime | Signals + entities + state machines | ~500 lines | | Evaluator | Tree-walk interpreter | ~1200 lines |
Total: ~3,400 lines for a complete implementation.
20.3 Host Integration
SIGNAL-Lite can register host builtins — native functions callable from SIGNAL code:
// PHP example: register a database query builtin
$engine->registerBuiltin('db_query', function($sql, $params) use ($pdo) {
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
return $stmt->fetchAll();
});
// SIGNAL-Lite code using the host builtin
let users = db_query("SELECT * FROM users WHERE active = ?", [true])
for user in users {
println(user.name)
}
21. Grammar Reference
21.1 EBNF (Core)
program = { declaration } ;
declaration = fn_decl | domain_decl | entity_decl | sig_decl
| rule_decl | state_machine | statement ;
fn_decl = "fn" IDENT "(" [ params ] ")" [ "->" type ] ( block | "=>" expr | "=" expr ) ;
domain_decl = "domain" TYPE_IDENT "=" variant { "|" variant } ;
entity_decl = "entity" TYPE_IDENT { field_decl | derive_decl } ;
sig_decl = "sig" TYPE_IDENT "(" [ params ] ")" ;
rule_decl = "rule" IDENT "when" TYPE_IDENT "(" [ params ] ")"
[ "where" expr ] "then" block ;
variant = TYPE_IDENT [ "(" [ params ] ")" ] ;
field_decl = IDENT ":" type [ "=" expr ] ;
derive_decl = "derive" IDENT "=" expr ;
params = param { "," param } ;
param = IDENT [ ":" type ] [ "=" expr ] ;
type = TYPE_IDENT [ "<" type { "," type } ">" ] [ "?" ] | type "|" type ;
block = "{" { statement } "}" ;
statement = let_stmt | var_stmt | return_stmt | break_stmt
| continue_stmt | expr_stmt ;
let_stmt = "let" IDENT [ ":" type ] "=" expr ;
var_stmt = "var" IDENT [ ":" type ] "=" expr ;
return_stmt = "return" [ expr ] ;
break_stmt = "break" [ expr ] ;
continue_stmt = "continue" ;
expr_stmt = expr ;
expr = pipe_expr ;
pipe_expr = or_expr { "|>" or_expr } ;
or_expr = and_expr { "or" and_expr } ;
and_expr = eq_expr { "and" eq_expr } ;
eq_expr = cmp_expr { ( "==" | "!=" ) cmp_expr } ;
cmp_expr = add_expr { ( "<" | ">" | "<=" | ">=" ) add_expr } ;
add_expr = mul_expr { ( "+" | "-" | "++" ) mul_expr } ;
mul_expr = pow_expr { ( "*" | "/" | "%" ) pow_expr } ;
pow_expr = unary_expr [ "**" unary_expr ] ;
unary_expr = ( "not" | "-" ) unary_expr | postfix_expr ;
postfix_expr= primary { "(" [ args ] ")" | "." IDENT | "[" expr "]" } ;
primary = INT | FLOAT | STRING | "true" | "false" | "null"
| IDENT | "(" expr ")" | "[" [ exprs ] "]" | "{" [ map_entries ] "}"
| if_expr | match_expr | for_expr | while_expr | loop_expr
| try_expr | lambda ;
if_expr = "if" expr ( "then" expr [ "else" expr ] | block [ "else" block ] ) ;
match_expr = "match" expr "{" { match_arm } "}" ;
match_arm = pattern [ "if" expr ] "=>" expr ;
for_expr = "for" IDENT "in" expr block ;
while_expr = "while" expr block ;
loop_expr = "loop" block ;
try_expr = "try" block "catch" IDENT block [ "finally" block ] ;
lambda = IDENT "=>" expr | "(" [ params ] ")" "=>" ( expr | block ) ;
22. Complexity Analysis
Performance guarantees for core operations (Knuth perspective):
| Operation | Time | Space | Notes | |-----------|------|-------|-------| | Lexing | O(n) | O(n) | n = source length | | Parsing | O(n) | O(n) | Recursive descent, no backtracking | | Type inference | O(n + c) | O(n) | c = constraint count | | Signal emit | O(R) | O(1) | R = rules for that signal | | Pattern match | O(m) | O(1) | m = match arms | | Entity create | O(1) | O(1) | Hash table insert | | Entity update | O(1) | O(1) | In-place field mutation | | Entity query | O(E) | O(E) | E = total entities (linear scan) | | State transition | O(T) | O(1) | T = transitions from current state | | Array map/filter | O(n) | O(n) | n = array length | | Array sort | O(n log n) | O(n) | Merge sort | | Map lookup | O(1) avg | O(1) | Hash map | | Closure creation | O(1) | O(k) | k = captured variables | | Channel send/recv | O(1) | O(b) | b = buffer capacity |
Appendix: Complete Example — Task Management System
module taskapp.core
// ── Types ─────────────────────────────────────────────────────
domain Priority = Low | Medium | High | Critical
entity Task
id: UUID
title: String
description: String?
priority: Priority = Medium
assignee: String?
created_at: DateTime = now()
due_date: DateTime?
derive isOverdue = due_date != null and due_date < now()
derive daysLeft = if due_date != null then dateDiff(due_date, now()) else null
// ── State Machine ─────────────────────────────────────────────
state machine TaskStatus {
initial: open
state open { enter { println("Task opened") } }
state active { enter { emit TaskStarted(taskId) } }
state review
state done { enter { emit TaskCompleted(taskId) } }
state cancelled
transition start: open -> active
transition review: active -> review
transition approve: review -> done
transition reject: review -> active
transition cancel: open -> cancelled
transition cancel: active -> cancelled
}
// ── Signals ───────────────────────────────────────────────────
sig TaskCreated(task: Task)
sig TaskAssigned(taskId: UUID, assignee: String)
sig TaskStarted(taskId: UUID)
sig TaskCompleted(taskId: UUID)
sig DeadlineApproaching(taskId: UUID, daysLeft: Int)
// ── Rules ─────────────────────────────────────────────────────
rule notifyAssignee when TaskAssigned(taskId, assignee) then {
println("Notifying " ++ assignee ++ " about task " ++ toString(taskId))
}
rule logCompletion when TaskCompleted(taskId) then {
println("Task " ++ toString(taskId) ++ " completed at " ++ toString(now()))
}
rule escalateUrgent when DeadlineApproaching(taskId, daysLeft)
where daysLeft <= 1
then {
println("URGENT: Task " ++ toString(taskId) ++ " due in " ++ toString(daysLeft) ++ " days!")
}
// ── Query ─────────────────────────────────────────────────────
query OverdueTasks()
= Task
where isOverdue == true
order by due_date
limit 50
// ── Flow ──────────────────────────────────────────────────────
flow TaskMetrics
from TaskCompleted.*
| window tumbling 1d
| select { date: today, completed: count() }
-> Dashboard
// ── Application Logic ─────────────────────────────────────────
fn createTask(title: String, priority: Priority) -> Task {
let task = create Task {
id: uuid(),
title: title,
priority: priority
}
emit TaskCreated(task)
task
}
fn assignTask(task: Task, person: String) {
update task with { assignee: person }
emit TaskAssigned(task.id, person)
}
// ── Main ──────────────────────────────────────────────────────
let bug = createTask("Fix login timeout", Critical)
assignTask(bug, "Alice")
let overdue = OverdueTasks()
for t in overdue {
println(t.title ++ " — " ++ toString(t.daysLeft) ++ " days overdue")
}
SIGNAL v1.0 — Designed by the Unified Council: Knuth, Erdos, Turing, Shannon.