Zy Language Reference
Read status and limitations and the migration guide. Agent methods return placeholders, security helpers only print messages, and
~uses keyword substring matching. Checked annotations, records, enums, and try/catch are specified in types and errors. APIs are in the standard library.
Version: 0.3.0-dev (experimental development build)
File extension: .zy
Paradigm: Interpreted, gradually typed, imperative
Local code reuse uses import "./policy.zy" as policy, export let, and export fn at file scope. See local modules for the full syntax, scope, resolution, and resource limits. import, export, and as are reserved keywords. CLI tooling documents formatting and dependency checks.
Table of Contents
- Overview
- Lexical Rules
- Types
- Variables
- Operators
- Control Flow
- Functions
- Lists
- Maps
- Networking Primitives
- AI Agents
- Built-in Functions
- String Methods
- List Methods
- Map Methods
- Subnet Methods
- Error System
- Scoping Rules and Closures
- Truthiness Rules
- Grammar Summary
1. Overview
Zy is a gradually typed interpreted language for scripts, readable network policies, and validation. IP addresses and subnets are built-in values. Records and annotations enforce contracts; unannotated values remain dynamic. Local modules let scripts share code. Agent values are also supported, but their AI methods are simulations.
Its syntax enforces mandatory braces { } for all blocks, which eliminates whitespace-sensitive parsing and enables precise, human-centric error messages modeled after Rust and Elm.
2. Lexical Rules
Comments
Line comments begin with // and continue to the end of the line. There are no block comments.
// This is a comment
let x = 10 // inline comment
Statement Termination
Statements are terminated by newlines. Semicolons are accepted but not required.
let a = 1
let b = 2 // preferred — no semicolons needed
let c = 3; // also valid
Identifiers
Identifiers must start with a letter or underscore, followed by any number of letters, digits, or underscores. They are case-sensitive.
user_name ✓
_private ✓
Value2 ✓
2invalid ✗ (starts with digit)
Keywords
The following words are reserved and cannot be used as identifiers:
let fn return if else while for in not and or
true false null agent break continue
import export as try catch throw record enum
String Literals
Strings are enclosed in double quotes. Supported escape sequences:
| Sequence | Meaning |
|---|---|
\n | Newline |
\t | Tab |
\" | Literal quote |
\\ | Literal backslash |
let msg = "Hello, world!"
let path = "C:\\Users\\admin"
let line = "first\nsecond"
3. Types
Zy is gradually typed. Every value carries its type at runtime; optional annotations add enforced contracts. See types and errors for records, enums, nullable values, and error handling.
| Type | Description | Example |
|---|---|---|
Int | 64-bit signed integer | 42, -7, 0 |
Float | 64-bit floating point | 3.14, -0.5 |
String | UTF-8 text | "hello" |
Bool | Boolean | true, false |
Null | Absence of a value | null |
List | Ordered, heterogeneous collection | [1, "a", true] |
Map | Key-value dictionary | {name: "Alice", age: 30} |
IP | IPv4/IPv6 address (native type) | 192.168.1.1 |
Subnet | IPv4/IPv6 CIDR block (native type) | 192.168.1.0/24 |
Agent | AI agent instance | see §11 |
Function | User-defined function (supports closures) | see §7 |
The null Literal
null is a first-class value and keyword. It represents the deliberate absence of a value. Functions that do not return a value implicitly return null.
let empty = null
print(type(null)) // Null
if not empty {
print("no value") // executes — null is falsy
}
Type Inspection
print(type(42)) // Int
print(type("hello")) // String
print(type(192.168.1.1)) // IP
print(type(10.0.0.0/8)) // Subnet
print(type(null)) // Null
print(type({})) // Map
Type Conversion
let n = int("42") // String → Integer: 42
let f = float(7) // Integer → Float: 7.0
let s = str(3.14) // any → String: "3.14"
4. Variables
Declaration
All variables must be declared with let before use.
let city = "Manila"
let port = 8080
let active = true
let data = null // explicitly null
Optional Type Annotation
A type annotation after : enforces the declared contract on initialization and reassignment. Function parameters and -> Type results can also be annotated. Type? accepts null. See the full contract.
let zone: Subnet = 192.168.1.0/24
let count: Int = 0
Re-assignment
After declaration, a variable is re-assigned without let:
let score = 0
score = score + 10 // re-assignment
Compound Assignment
Zy supports the common shorthand operators that combine an arithmetic operation with assignment:
| Operator | Equivalent To | Example |
|---|---|---|
+= | x = x + value | score += 5 |
-= | x = x - value | health -= 10 |
*= | x = x * value | total *= 2 |
/= | x = x / value | ratio /= 100 |
let score = 10
score += 5 // 15
score *= 2 // 30
score -= 3 // 27
score /= 9 // 3.0
print(score) // 3.0
Assignment Errors
Attempting to assign to a name that was never declared with let is a Name Error:
undeclared = 5
// ❌ Name Error: Cannot assign to undefined variable 'undeclared'.
// 💡 Hint: Declare the variable first with: let undeclared = ...
5. Operators
Arithmetic
| Operator | Operation | Example | Result |
|---|---|---|---|
+ | Addition | 3 + 2 | 5 |
- | Subtraction | 10 - 4 | 6 |
* | Multiplication | 3 * 4 | 12 |
/ | Division | 7 / 2 | 3.5 |
% | Modulo | 10 % 3 | 1 |
** | Exponentiation | 2 ** 10 | 1024.0 |
Integer +, -, *, and % preserve exact signed 64-bit results and report overflow. Float operands promote these operations to Float. / always returns a finite Float; div(a, b) returns an integer quotient truncated toward zero. Comparisons preserve integer precision, including mixed integer/float comparisons.
Out-of-range literals, conversions, and nonfinite math results raise located errors. type(42) returns "Int". See the numeric decision.
** (power) always returns Float, even for integer operands.
Division and modulo by zero raise a Runtime Error.
Power Operator Associativity
** is right-associative: 2 ** 3 ** 2 is evaluated as 2 ** (3 ** 2) = 2 ** 9 = 512.0.
print(4 ** 2) // 16.0
print(2 ** 3 ** 2) // 512.0 (right-associative)
String Concatenation
+ concatenates when either operand is a String. The other operand is automatically converted to its string representation.
let msg = "Port: " + 8080 // "Port: 8080"
let info = str(99) + "% load" // "99% load"
Comparison
| Operator | Meaning |
|---|---|
== | Equal |
!= | Not equal |
< | Less than |
<= | Less than or equal |
> | Greater than |
>= | Greater than or equal |
Numeric comparisons accept both Integer and Float. Equality (==, !=) works across all types including IP, String, and Bool.
Ordering comparisons (<, <=, >, >=) also work on String values (lexicographic order).
Logical
| Operator | Meaning | Short-circuits |
|---|---|---|
and | Logical AND | Yes — stops at first false |
or | Logical OR | Yes — stops at first true |
not | Logical NOT | N/A |
if active and load < 90 {
print("All good")
}
if error or timeout {
print("Something went wrong")
}
if not connected {
print("Offline")
}
Membership — in / not in
Tests whether a value is contained in a list, a key exists in a map, a substring exists in a string, or an IP address is within a subnet.
// List membership
if "admin" in roles {
print("Access granted")
}
// Map key membership
let config = {host: "localhost", port: 8080}
if "host" in config {
print("host is set")
}
// Substring check
if "error" in log_line {
print("Error found in log")
}
// IP-in-subnet (native)
if client_ip in 192.168.1.0/24 {
print("Local client")
}
// Negated membership
if client_ip not in trusted_range {
quarantine(client_ip)
}
Semantic Match — ~
The ~ operator performs a case-insensitive keyword substring check between two strings. It counts how many whitespace-separated words from the right string occur in the left string. It returns true when the count reaches max(1, floor(word_count / 2)). It does not evaluate intent or meaning.
let payload = "user tried to access account with leaked credentials"
if payload ~ "password security threat" {
// triggers when enough right-side keywords occur as substrings
print("Threat detected")
}
Both sides must be String. A Type Error is raised otherwise.
Embedding-based similarity is not implemented. Any future AI similarity API is a separate design decision; see the roadmap.
Operator Precedence
From highest to lowest:
| Level | Operators |
|---|---|
| 1 | - (unary negation), not (unary) |
| 2 | ** (right-associative) |
| 3 | * / % |
| 4 | + - |
| 5 | == != < <= > >= ~ in not in |
| 6 | not |
| 7 | and |
| 8 | or |
Use parentheses to override precedence:
let result = (a + b) * c
6. Control Flow
if / else if / else
Parentheses around the condition are not required. Braces { } are mandatory.
if temperature > 80 {
print("Overheating")
} else {
print("Normal temperature")
}
else if chains are fully supported and may be nested to any depth:
let score = 85
if score >= 90 {
print("A")
} else if score >= 80 {
print("B")
} else if score >= 70 {
print("C")
} else {
print("F")
}
while
Repeats the body as long as the condition is truthy.
let retries = 0
while retries < 5 {
print("Attempting connection...")
retries += 1
}
for / in
Iterates over every element of a List. The loop variable is scoped to the loop body.
let nodes = ["web-01", "web-02", "db-01"]
for node in nodes {
print("Checking node: " + node)
}
Combined with range() for numeric iteration:
for i in range(5) {
print("Step " + str(i)) // 0 1 2 3 4
}
for i in range(1, 6) {
print(str(i)) // 1 2 3 4 5
}
break
Immediately exits the nearest enclosing while or for loop.
let i = 0
while i < 100 {
i += 1
if i > 10 {
break // stops the loop when i reaches 11
}
}
print(i) // 11
Using break outside a loop raises a Runtime Error.
continue
Skips the rest of the current loop iteration and jumps to the next one.
for i in range(10) {
if i % 2 == 0 {
continue // skip even numbers
}
print(i) // prints 1, 3, 5, 7, 9
}
Using continue outside a loop raises a Runtime Error.
7. Functions
Declaration
fn function_name(param1, param2) {
// body
return value
}
- Parameters are positional; no default values.
returnexits the function and optionally produces a value.- A function without
returnreturnsnull. - Functions are values — they can be stored in variables and passed as arguments.
fn add(a, b) {
return a + b
}
let result = add(10, 20)
print(result) // 30
Recursion
fn factorial(n) {
if n <= 1 {
return 1
}
return n * factorial(n - 1)
}
print(factorial(6)) // 720
Functions as Values
fn double(x) {
return x * 2
}
let transform = double
print(transform(7)) // 14
Closures and Nested Functions
Functions in Zy use lexical scoping — they capture the environment at the point of their definition. This makes closures possible:
fn make_counter(start) {
let count = start
fn increment() {
count += 1
return count
}
return increment
}
let counter = make_counter(0)
print(counter()) // 1
print(counter()) // 2
print(counter()) // 3
Each call to make_counter() creates an independent closure with its own count variable. The inner function increment captures count from its enclosing scope and can both read and update it.
Higher-Order Functions
fn apply(f, value) {
return f(value)
}
fn square(x) {
return x ** 2
}
print(apply(square, 5)) // 25.0
Argument Count
Calling a function with the wrong number of arguments raises a Runtime Error with a clear message:
fn greet(name) { return "Hello " + name }
greet()
// ❌ Runtime Error: Function 'greet' expects 1 argument(s), but got 0.
// 💡 Hint: Pass exactly 1 argument(s) to 'greet'.
8. Lists
Lists are ordered, zero-indexed, and can hold values of mixed types.
Literal Syntax
let numbers = [1, 2, 3, 4, 5]
let mixed = [42, "hello", true, 192.168.1.1]
let empty = []
Indexing
let first = numbers[0] // 1
let last = numbers[-1] // 5 (negative indexing from end)
Out-of-bounds access raises a Runtime Error:
numbers[99]
// ❌ Runtime Error: List index 99 is out of range (length 5).
// 💡 Hint: Valid indices are 0 to 4.
Index Assignment
let fruits = ["apple", "banana", "cherry"]
fruits[1] = "blueberry"
print(fruits) // [apple, blueberry, cherry]
Iterating
for item in numbers {
print(item)
}
List Methods
See §14 List Methods for the full method reference.
9. Maps
Maps are ordered key-value dictionaries. Keys are always stored as strings; values can be any type. Insertion order is preserved.
Literal Syntax
let user = {
name: "Alice",
age: 30,
active: true
}
Bare identifiers (name:) are treated as string keys. String literals can also be used:
let config = {
"host": "localhost",
"port": 8080
}
An empty map is {}.
Reading Values
Bracket notation (dynamic key):
let name = user["name"] // "Alice"
Dot notation (static key — must be a valid identifier):
let age = user.age // 30
If the key does not exist, both notations return null.
Writing Values
user["city"] = "Nairobi" // bracket notation
user.score = 100 // dot notation
Key Membership
The in operator checks whether a key exists in a map:
if "name" in user {
print("name is set")
}
if "email" not in user {
print("no email on file")
}
Map Methods
See §15 Map Methods for the full method reference.
Maps and len()
let m = {a: 1, b: 2, c: 3}
print(len(m)) // 3
10. Networking Primitives
Zy treats IPv4 addresses and CIDR subnets as distinct native types, not strings. This prevents accidental misuse and enables type-safe network operations.
IP Literals
Any dotted-quad address is automatically parsed as an IP type:
let gateway = 192.168.1.1
let dns_server = 8.8.8.8
let loopback = 127.0.0.1
An invalid address causes a Type Error at runtime.
Subnet Literals
Any CIDR notation is automatically parsed as a Subnet type:
let lan = 192.168.0.0/16
let dmz = 10.10.10.0/24
let host_only = 172.16.0.1/32
IP Membership
The in / not in operators work natively between IP and Subnet:
let zone = 10.0.0.0/8
let host = 10.5.3.2
if host in zone {
print("Host is inside the zone")
}
if 172.16.0.1 not in zone {
print("External address detected")
}
Subnet Properties
| Property | Type | Description |
|---|---|---|
subnet.network | String | Network address as a string |
subnet.mask | Integer | Prefix length (e.g. 24) |
let net = 192.168.1.0/24
print(net.network) // 192.168.1.0
print(net.mask) // 24
Subnet Methods
See §16 Subnet Methods.
11. AI Agents
Agents are first-class values in Zy. They store configuration and expose simulated query and vision methods. The current runtime does not contact model providers or process images.
Declaration
agent AgentName {
model: "model-identifier",
temperature: 0.2,
instructions: "System prompt for the agent."
}
All properties are optional. Recognized properties:
| Property | Type | Description |
|---|---|---|
model | String | AI model identifier |
temperature | Float | Sampling temperature (0.0 – 1.0) |
instructions | String | System-level instruction prompt |
mode | String | Operational mode (e.g. "computer-vision") |
agent SecurityMonitor {
model: "gemini-2.0-pro",
temperature: 0.1,
instructions: "You are a security analyst. Be terse and precise."
}
agent VisionCore {
model: "vision-pro",
mode: "computer-vision"
}
Agent Methods
.ask(prompt)
Currently prints a query message and returns a simulated response String. No provider request is made.
let verdict = SecurityMonitor.ask("Is this payload malicious: " + payload)
print(verdict)
.detect_objects(frame)
Currently prints a simulation message and returns ["unknown_object"]. The input is not processed by a vision model.
let found = VisionCore.detect_objects(frame)
if found.contains("unauthorized_person") {
trigger_alert(location: "Server Room B")
}
Accessing Agent Properties
Properties declared in the agent block are readable as normal member access:
print(SecurityMonitor.model) // gemini-2.0-pro
print(SecurityMonitor.temperature) // 0.1
The Semantic Match Operator with Agents
A common pattern combines the ~ operator with an agent for layered threat detection:
let payload = "GET /admin?token=leaked_token HTTP/1.1"
if payload ~ "authentication bypass credential theft" {
let analysis = SecurityMonitor.ask("Classify threat level: " + payload)
print(analysis)
}
12. Built-in Functions
These functions are available globally in every .zy program.
print(value, ...)
Prints one or more values to standard output, separated by spaces, followed by a newline.
print("Hello") // Hello
print("Load:", 72, "%") // Load: 72 %
print(192.168.1.1) // 192.168.1.1
len(value)
Returns the length of a String (character count), List (element count), or Map (key count).
len("hello") // 5
len([1, 2, 3]) // 3
len({a: 1, b: 2}) // 2
type(value)
Returns the type name of a value as a String.
type(42) // "Int"
type(3.14) // "Float"
type("hi") // "String"
type(true) // "Bool"
type(null) // "Null"
type([1, 2]) // "List"
type({}) // "Map"
type(10.0.0.0/8) // "Subnet"
type(192.168.1.1) // "IP"
str(value)
Converts any value to its String representation.
str(100) // "100"
str(3.14) // "3.14"
str(true) // "true"
str(null) // "null"
str(192.168.1.1) // "192.168.1.1"
int(value)
Converts a Float or numeric String to an Integer.
int(3.9) // 3 (truncates)
int("42") // 42
float(value)
Converts an Integer or numeric String to a Float.
float(7) // 7.0
float("3.14") // 3.14
range(stop) / range(start, stop) / range(start, stop, step)
Generates a List of integers with an exclusive stop. Results are limited to 65,536 entries; step/bound arithmetic cannot wrap. Zero steps or oversized results raise an error.
range(5) // [0, 1, 2, 3, 4]
range(2, 6) // [2, 3, 4, 5]
range(0, 10, 2) // [0, 2, 4, 6, 8]
range(5, 0, -1) // [5, 4, 3, 2, 1]
abs(n)
Returns the absolute value of a number.
abs(-7) // 7
abs(3.5) // 3.5
max(a, b, ...) / min(a, b, ...)
Returns the largest or smallest value among the arguments.
max(3, 7, 2) // 7
min(3, 7, 2) // 2
sqrt(n)
Returns the square root of n as a Float.
sqrt(16) // 4.0
sqrt(2) // 1.4142135...
pow(base, exp)
Returns base ** exp as a Float.
pow(2, 10) // 1024.0
pow(3, 3) // 27.0
floor(n) / ceil(n) / round(n)
Standard rounding functions. floor and ceil return an Integer; round returns an Integer.
floor(3.9) // 3
ceil(3.1) // 4
round(3.5) // 4
round(3.4) // 3
input(prompt?)
Reads a line from standard input and returns it as a String. An optional prompt string is printed before reading.
let name = input("Enter your name: ")
print("Hello, " + name)
assert(condition, message?)
Asserts that condition is truthy. If it is not, a Runtime Error is raised with the optional message.
assert(1 + 1 == 2)
assert(len("hello") == 5, "String length is wrong")
// Failure:
assert(false, "This always fails")
// ❌ Runtime Error: Assertion failed: This always fails
format(template, args...)
Returns a formatted string by replacing each {} placeholder with the corresponding argument (converted via str()).
let msg = format("Hello, {}! You are {} years old.", "Alice", 30)
print(msg) // Hello, Alice! You are 30 years old.
let log = format("[{}] ERROR: {}", "2026-01-01", "Disk full")
print(log) // [2026-01-01] ERROR: Disk full
Placeholders are filled left-to-right. Extra arguments are ignored; unmatched {} remain as-is.
quarantine(ip)
Security primitive. Prints a quarantine notice for the given IP. In a production runtime, this hooks into firewall or network isolation APIs.
quarantine(suspicious_ip)
// 🔒 Quarantined: 10.0.0.99
trigger_alert(location:)
Security primitive. Fires an alert for the given location string.
trigger_alert(location: "Server Room B")
// 🚨 ALERT triggered at: Server Room B
13. String Methods
Methods are called with dot notation followed by parentheses: value.method(args).
length is a property (no parentheses); all others are methods (require ()).
| Method / Property | Returns | Description |
|---|---|---|
s.length | Integer | Number of characters (Unicode-aware) |
s.upper() | String | Uppercase copy |
s.lower() | String | Lowercase copy |
s.trim() | String | Removes leading and trailing whitespace |
s.contains(sub) | Bool | Whether the string contains the substring |
s.split(sep) | List | Splits on an explicit String separator |
s.starts_with(prefix) | Bool | Whether the string starts with the given prefix |
s.ends_with(suffix) | Bool | Whether the string ends with the given suffix |
s.replace(old, new) | String | Replaces all occurrences of old with new |
s.index_of(sub) | Integer | Index of first occurrence, or -1 if not found |
let name = " Zy Language "
print(name.trim()) // "Zy Language"
print(name.trim().upper()) // "Zy LANGUAGE"
print(name.trim().lower()) // "Zy language"
print(name.trim().contains("Lang")) // true
print(name.trim().split(" ")) // [Zy, Language]
print(name.trim().starts_with("Zy")) // true
print(name.trim().ends_with("e")) // true
print(name.trim().replace("Zy", "Go")) // "Go Language"
print(name.trim().index_of("L")) // 4
print(name.length) // 16 (includes spaces)
Method calls can be chained:
let clean = raw_input.trim().lower().replace("-", "_")
14. List Methods
Method arity is checked. Sorting accepts all numbers or all strings and returns a shallow copy. Slicing uses integer bounds, clamps out-of-range values, and returns an empty list for reversed bounds. Collection aliases compare equal by identity; separate collections do not use structural equality.
length is a property (no parentheses); all others are methods.
| Method / Property | Returns | Description |
|---|---|---|
list.length | Integer | Number of elements |
list.contains(val) | Bool | Whether the list contains an equal value |
list.append(val) | List | Appends val in-place; returns the same list |
list.pop() | any | Removes and returns the last element |
list.pop(i) | any | Removes and returns the element at index i |
list.slice(start, end) | List | Returns a new sub-list from start (inclusive) to end (exclusive) |
list.reverse() | List | Returns a new reversed copy (does not mutate) |
list.sort() | List | Returns a new sorted copy (does not mutate) |
list.join(sep) | String | Joins elements into a string using sep as separator |
list.index_of(val) | Integer | Index of first occurrence, or -1 if not found |
let nums = [5, 3, 8, 1, 9, 2]
print(nums.sort()) // [1, 2, 3, 5, 8, 9]
print(nums.reverse()) // [2, 9, 1, 8, 3, 5]
print(nums.slice(1, 4)) // [9, 1, 8] (of reversed list)
print(nums.length) // 6
let words = ["hello", "world", "foo"]
words.append("bar")
let last = words.pop() // "bar" — removes last element
print(words.join(", ")) // hello, world, foo
print(words.index_of("world")) // 1
Negative indices in pop() count from the end:
let items = [10, 20, 30, 40]
print(items.pop(-1)) // 40
print(items) // [10, 20, 30]
15. Map Methods
length is a property; all others are methods.
| Method / Property | Returns | Description |
|---|---|---|
map.length | Integer | Number of key-value pairs |
map.keys() | List | List of all keys (in insertion order) |
map.values() | List | List of all values (in insertion order) |
map.has(key) | Bool | Whether the given key exists |
map.remove(key) | null | Removes the key-value pair; no-op if key is missing |
let config = {host: "localhost", port: 8080, debug: true}
print(config.length) // 3
print(config.keys()) // [host, port, debug]
print(config.values()) // [localhost, 8080, true]
print(config.has("port")) // true
print(config.has("timeout")) // false
config.remove("debug")
print(config) // {host: localhost, port: 8080}
16. Subnet Methods
Splits require parent.mask < prefix <= 32 and produce at most 65,536 results. /0 boundaries terminate correctly. net.allocate() supports variable-size planning; see the standard library.
| Method / Property | Returns | Description |
|---|---|---|
subnet.network | String | Network address string (e.g. "192.168.1.0") |
subnet.mask | Integer | Prefix length (e.g. 24) |
subnet.split(prefix) | List | Splits into all equal /prefix sub-networks |
subnet.contains(ip) | Bool | Whether the given IP is inside the subnet |
let zone = 10.0.0.0/8
print(zone.network) // 10.0.0.0
print(zone.mask) // 8
// Split into /10 subnets
let quarters = zone.split(10)
print(quarters[0]) // 10.0.0.0/10
print(quarters[1]) // 10.64.0.0/10
// Membership via method
print(zone.contains(10.5.1.2)) // true
split() Type Safety
The prefix argument must be an Integer. Passing a String raises a Type Error with a hint:
zone.split("10")
// ❌ Type Error: subnet.split() requires an Integer CIDR prefix (e.g. split(28)).
// 💡 Hint: Remove quotes if you used a string: split(28) not split("28").
17. Error System
Zy produces structured, human-centric error messages. Every error includes:
- The error kind (Syntax, Type, Runtime, Name)
- A plain-language description of what went wrong
- A file reference with line and column numbers
- A source excerpt with a
^caret pointing at the exact position - A
💡 Hintsuggesting how to fix it
Error Format
❌ <Kind> Error: <message>
--> <file>:<line>:<column>
|
N | <source line>
| ^
| <message repeated>
💡 Hint: <actionable fix>
Error Kinds
| Kind | When it occurs |
|---|---|
Syntax Error | Unrecognized character, missing brace, malformed token |
Type Error | Wrong type for operation (e.g. string CIDR, non-numeric math) |
Runtime Error | Division by zero, out-of-bounds index, wrong argument count |
Name Error | Reading or assigning an undeclared variable |
Examples
Missing opening brace:
if load > 90
print("High load")
// ❌ Syntax Error: Expected '{' to start a block, but found 'print'.
// --> monitor.zy:2:5
// |
// 2 | print("High load")
// | ^
// | Add a '{' here to open the block.
String used as subnet mask:
let zone = 192.168.0.0/16
let sub = zone.split("20")
// ❌ Type Error: subnet.split() requires an Integer CIDR prefix (e.g. split(28)).
// --> config.zy:2:21
// |
// 2 | let sub = zone.split("20")
// | ^
// 💡 Hint: Remove quotes if you used a string: split(28) not split("28").
Undefined variable:
print(user_count)
// ❌ Name Error: 'user_count' is not defined.
// --> app.zy:1:7
// |
// 1 | print(user_count)
// | ^
// 💡 Hint: Declare it first: let user_count = ...
Division by zero:
let ratio = total / active
// ❌ Runtime Error: Division by zero.
// --> calc.zy:1:17
// 💡 Hint: Ensure the divisor is not zero before dividing.
18. Scoping Rules and Closures
- Every
{ }block creates a new inner scope. - Inner scopes can read variables from outer scopes.
- Inner scopes can re-assign variables declared in outer scopes.
- Variables declared with
letinside a block are not visible outside it.
let x = 10
fn show() {
let y = 20
print(x) // ✓ reads outer x
print(y) // ✓ reads local y
}
show()
print(x) // ✓ still 10
// print(y) // ✗ Name Error — y is not in scope here
Closure Semantics
Functions use lexical scoping: they capture the environment at the point of their definition, not at the point of their call.
fn make_adder(n) {
fn adder(x) {
return x + n // captures n from make_adder's scope
}
return adder
}
let add5 = make_adder(5)
let add10 = make_adder(10)
print(add5(3)) // 8
print(add10(3)) // 13
Each closure has independent state:
fn make_counter(start) {
let count = start
fn next() {
count += 1
return count
}
return next
}
let a = make_counter(0)
let b = make_counter(100)
print(a()) // 1
print(a()) // 2
print(b()) // 101 — independent from a
19. Truthiness Rules
Every value has an inherent boolean interpretation used in if, while, and logical operators.
| Value | Truthy? |
|---|---|
null | false |
false | false |
0 | false |
0.0 | false |
"" (empty string) | false |
[] (empty list) | false |
{} (empty map) | false |
| Any other value | true |
let items = []
if items {
print("Has items")
} else {
print("Empty list") // this branch executes
}
let config = {}
if not config {
print("Config is empty") // executes
}
20. Grammar Summary
program → statement* EOF
statement → let_stmt
| assign_stmt
| aug_assign_stmt
| index_assign_stmt
| fn_decl
| type_decl
| try_stmt
| throw_stmt
| import_stmt
| export_stmt
| agent_decl
| return_stmt
| break_stmt
| continue_stmt
| if_stmt
| while_stmt
| for_stmt
| expr_stmt
let_stmt → "let" IDENT (":" type_ref)? "=" expression
assign_stmt → IDENT "=" expression
aug_assign_stmt → IDENT ("+=" | "-=" | "*=" | "/=") expression
index_assign_stmt → postfix "[" expression "]" "=" expression
fn_decl → "fn" IDENT "(" params? ")" ("->" type_ref)? block
type_ref → IDENT ("." IDENT)? "?"?
type_decl → "record" IDENT "{" (IDENT ":" type_ref ","?)* "}"
| "enum" IDENT "{" (IDENT ","?)+ "}"
try_stmt → "try" block "catch" IDENT block
throw_stmt → "throw" expression
import_stmt → "import" STRING "as" IDENT
export_stmt → "export" (let_stmt | fn_decl | type_decl)
agent_decl → "agent" IDENT "{" (IDENT ":" expression ","?)* "}"
return_stmt → "return" expression?
break_stmt → "break"
continue_stmt → "continue"
if_stmt → "if" expression block ("else" ("if" expression block)* ("else" block)?)?
while_stmt → "while" expression block
for_stmt → "for" IDENT "in" expression block
expr_stmt → expression
block → "{" statement* "}"
params → IDENT (":" type_ref)? ("," IDENT (":" type_ref)?)*
expression → or_expr
or_expr → and_expr ("or" and_expr)*
and_expr → not_expr ("and" not_expr)*
not_expr → "not" not_expr | comparison
comparison → add_sub (("==" | "!=" | "<" | "<=" | ">" | ">="
| "~" | "in" | "not" "in") add_sub)*
add_sub → mul_div (("+" | "-") mul_div)*
mul_div → power (("*" | "/" | "%") power)*
power → unary ("**" power)? // right-associative
unary → "-" unary | postfix
postfix → primary (("." IDENT ("(" args? ")")?
| "[" expression "]"
| "(" args? ")"))*
primary → INT | FLOAT | STRING | BOOL | NULL | IP | SUBNET
| IDENT | "(" expression ")" | list_literal | map_literal
list_literal → "[" (expression ("," expression)*)? "]"
map_literal → "{" (map_pair ("," map_pair)*)? "}"
map_pair → (IDENT | STRING) ":" expression
args → (IDENT ":")? expression ("," (IDENT ":")? expression)*