Nyl Language Reference
Nyl is a modern, statically-typed (with type inference), safe, and efficient programming language designed for versatility and ease of use. It features a unique syntax that combines the best of C-style languages with modern functional and scripting capabilities.
Execution and CLI
Running a Script
To run a .nyl file:
nyl main.nyl
REPL (Read-Eval-Print Loop)
To start the interactive shell:
nyl
Distributable Self-Hosted Package
Nyl can bundle a script with the host runtime executable:
nyl -build main.nyl
Native Compilation Experimental
To generate ultra-small, high-performance native machine binaries using native compilation with cross-compiler toolchains (zig cc, clang, or gcc):
# Native build for current host
nyl compile main.nyl -o build/
# Cross-compile for Linux x86_64 static binary
nyl compile main.nyl -target x86_64-linux-musl -o distrib/
Variables and Declarations
Nyl uses automatic type inference by default. You do not need keywords like var or let to declare variables, though they are supported for compatibility.
Implicit Declaration (Standard)
name = "Nyl" // string
version = 1.0 // number
isActive = true // bool
Explicit Typing
Variables can be explicitly typed, though this is primarily used for specific internal requirements and is not recommended for general user code.
int age = 25
number price = 19.99
string name = "Nyl"
bool isActive = true
Strings and Interpolation
Interpolation
Default behavior uses {} to inject variables into strings.
name = "World"
println("Hello {name}") // Hello World
println('Raw text') // Using single quotes
Disabling Interpolation (Raw Strings)
Use a trailing # inside or outside the string to escape the interpolation engine.
println("Not {interpolated}"#)
Verbatim and Multiline
Verbatim strings ignore escape characters (like \), and triple-quotes allow multiline blocks.
path = @"C:\Users\Nyl"
text = """
Multiline
String
"""
️ Maps (Dictionaries)
Maps store key-value pairs and support both dynamic inference and explicit typing.
Literals and Access
person = { "name": "Alice", "age": 29, } // Trailing comma supported
println(person["name"])
println(person.name) // Property-style shortcut
Explicit Typing
map<string, int> scores = { "player1": 100, "player2": 150 }
Basic Syntax
Comments
- Single-line:
// comment - Documentation:
/// doc comment - Multi-line:
/ block comment /
Semicolons and Commas
- Semicolons: Optional and mainly serve to separate multiple statements on a single line.
- Commas: Optional between fields in type definitions. Trailing commas are supported in lists (arrays, maps, parameters, etc.).
Data Types
int,long: Integers.number: Floating point.string: Unicode text.bool:trueorfalse.byte: 8-bit unsigned integer (0-255).datetime: Dates and times.nil: Absence of value.any: Universal type.ptr: Native pointer (FFI).
Numeric Literal Suffixes
By default, Nyl infers decimals as number (double underlying precision) and whole numbers as int or long. You can force exact boxed native primitives by using the following C#-style literal suffixes:
forF: Strict 32-bit floating point (float). Example:3.14FmorM: Strict 128-bit precise decimal (decimal). Example:10.5mnorN: Strict 64-bit floating point (number). Example:99nlorL: Strict 64-bit integer (long). Example:100L
Bytes and Bitwise Operations
Nyl provides robust support for byte-level manipulation.
Byte Literals
b1 = 42 // Decimal
b2 = 0x2A // Hex
b3 = 0b101010 // Binary
b4 = 'A' // Character literal
Bitwise Operators
&(AND),|(OR),^(XOR),~(NOT)<<(Left Shift),>>(Right Shift)- Assignments:
&=,|=,^=,<<=,>>=
Type Conversion
Using .parse()
n = int.parse("123")
d = number.parse("3.14")
b = byte.parse("255")
x = parse(int, val)
Using Literal Suffixes
For numeric types, you can skip .parse() entirely and use suffixes directly on the literal:
price = 19.99M // decimal
speed = 3.14F // float
count = 100L // long
value = 42n // number (double)
Import System
Basic and Alias
import "math.nyl"
import "utils.nyl" as utils
Fallback and Modules
import "local.nyl" fallback "default.nyl"
import os.windows as win
Control Flow
If / Else
if x > 10 { ... } else { ... }
res = condition ? "Yes" : "No"
Loops For (Unified)
for i < 10 { ... } // While-style
for { ... } // Infinite
for item in list { ... } // Foreach
for i, val in list { ... } // Foreach with index
for i in 1..10 { ... } // Range (inclusive)
Match
match x {
1 => println("One")
2, 3 => println("Two/Three")
_ => println("Other")
}
Ranges
Ranges represent an inclusive interval between two values. They support numbers, characters, dates, and enums.
Syntax and Types
r1 = 1..5 // Numbers: 1, 2, 3, 4, 5
r2 = "a".."c" // Chars: "a", "b", "c"
r3 = start..end // Dates: day-by-day
r4 = Red..Blue // Enums: variants in defined order
Properties and Indexing
r = 1..10
print(r.count()) // 10
print(r[2]) // 3
️ Unified Type System (Structs & Unions)
Nyl features a powerful type system with inheritance and union types.
Structs/Classes
type User {
string name = "Anonymous", // Optional comma
int age, // Trailing comma
fn isAdult() => self.age >= 18
}
u = User(name = "Bob", age = 20)
Inheritance and Generics
type Box<T> { T value }
type Admin : User { ... }
Union Types and Enums
Unions allow a variable to hold one of several types or specific variants (Enums).
type Result = string | int
type Direction = North | South | East | West
fn move(Direction d) {
match d {
case North => println("Up")
case _ => println("Other")
}
}
Immutable Updates (with)
Create a copy with modified fields.
p2 = p1 with { name = "New Name", age = 21 }
️ Error Handling
Nyl uses the try/or construct for safe expression evaluation.
result = try (10 / 0) or {
println("Error: {err.message}")
return 0
}
Within the or block, the err object is automatically available with err.type and err.message.
Arrays (arrayList)
list = [1, 2, 3]
list.push(4)
println(list.len())
️ Functions & Defers
Functions
Functions can be defined in multiple ways, from concise arrow functions to detailed block bodies with explicit types.
Expression Body (Arrow Function)
fn add(a, b) => a + b
Block Body
The last expression in a block body is automatically returned.
fn calculate(x, y) {
result = x * y
result + 10 // Returns this value
}
Explicit Typing
You can specify parameter types and return types.
fn multiply(int a, int b) int {
return a * b
}
Multiple Return Values (Tuples)
Nyl supports returning multiple values, which can be unpacked during assignment.
fn getPlayerInfo() string, int {
return "Alice", 25
}
name, age = getPlayerInfo()
Anonymous Functions (Expressions)
Functions are first-class citizens and can be assigned to variables or passed as arguments.
// Assignment
adder = fn(x) => x + 5
println(adder(10)) // 15
// As argument
apply(fn(x) => x * 2, 7)
Overloading and Union Types
Functions can be overloaded by type or accept multiple types using unions.
fn describe(int v) => "Integer: {v}"
fn describe(string v) => "String: {v}"
fn process(int | string input) {
if input is int { /* ... */ }
}
Defers (Cleanup)
defer blocks run when the function exits (LIFO order).
fn work() {
file = open("data.txt")
defer close(file)
// ...
}
️ Decorators
Decorators provide metadata and behavior modification for functions and parameters.
decorator log(msg) { ... }
[log("Use newFunc instead")]
fn oldFunc() { ... }
Native Decorators
Nyl includes several built-in decorators for common tasks:
[auth]: Used to mark functions that require authentication (e.g., inNylWeb).[obsolete("message")]: Marks a function as obsolete and emits a compile-time warning.
- Example: [obsolete("Use betterFunc")]
[test]: Marks a function as a unit test or theory.
- Simple test: [test]
- Theory with data: [test(1, 2)]
⏳ Async e Await
Nyl supports task-oriented asynchronous programming via async and await. This enables functions to run in the background in a non-blocking manner within the VM.
Running in Background
You append the async keyword right before calling the function.
fn fetch_data() {
os.sleep(100)
return "Data"
}
task = async fetch_data()
// Do other work while loading...
result = await task
Canceling Tasks
A running NylTask (returned by an async call) can be interrupted via its cancellation token.
task = async fetch_data()
task.cancel()
println(task.isCanceled()) // true
println(task.isCompleted()) // true
Note: It is recommended to handle potential cancellation errors (Task was canceled.) using thetry/orexpression block.
task.all and task.any
You can aggregate and await arrays of tasks dynamically through the task global native methods:
tasks = [async req1(), async req2()]
// Awaits for all of them to complete:
results = await task.all(tasks)
println(results[0])
// Returns the value of whichever finishes first:
first_result = await task.any(tasks)
Web Server and Networking (NylWeb)
Nyl includes a built-in, lightweight networking stack using Microsoft Kestrel. It supports standard HTTP routing, static file serving, and high-performance WebSockets.
Starting a Server
There are two ways to manage a server: a global "singleton" loop or a dedicated instance.
1. Global Loop (Quick Start)
nylweb.runLoop(8080)
get("/ping", fn() => "pong")
2. Instance-based Server (Recommended)
Using an instance allows for better state management and parallel servers.
server = nylweb.createServer(8080)
server.getRouter("/api/v1", fn() => { "version": 1.0 })
server.runLoop() // Starts the listener
HTTP Routing
You can define routes using standard HTTP verbs (get, post, put, delete, patch).
server.post("/user", fn(body) {
user = json.parse(body)
return { "id": user.id, "status": "created" }
})
// Path parameters (starts with :)
server.get("/items/:id", fn(id) {
return "Fetching item {id}"
})
WebSockets (Real-time)
NylWeb provides a simple, event-driven WebSocket API for both servers and clients.
Server-side Push
server = nylweb.createServer(8080)
server.onConnect(fn(clientId) {
println("Client {clientId} connected!")
server.sendMessage(clientId, "Welcome!")
})
server.onMessage(fn(msg) {
// Broadcast message to all clients
for client in server.getClients() {
server.sendMessage(client, msg)
}
})
server.runLoop()
Client-side (NylClient)
Connect to any WebSocket server (Nyl or third-party).
client = nylweb.createClient("ws://localhost:8080", true) // true = auto-reconnect
client.onConnect(fn(info) {
println("Connected to server!")
client.sendMessage("Hello Server!")
})
client.onMessageClient(fn(data) {
println("Response: {data}")
})
client.connect()
Protected Routes (Authentication)
Mark any route with the [auth] decorator to enforce a valid session. You can manage users in the Key-Value (KV) store and customize the login route.
import nylweb as web
// 1. Create the server
server = web.createServer(8080)
// 2. Add users to the KV store (run once or on bootstrap)
// Plaintext password (for dev/tests):
auth.addUser(server, "admin", "secret123", "Admin")
// or use addOrUpdateUser to ensure user is always there:
auth.addOrUpdateUser(server, "john", "SK-abc123def456...", "User", true)
// 3. Protected route using default login page ("auth/loginservice")
[auth]
server.get("/panel", fn() => "Welcome to Panel")
// 4. Protected route using a custom login route
[auth("my/login")]
server.get("/dashboard", fn() => "Private Dashboard")
// 5. Build your custom login handler
server.post("my/login", fn(credentials) {
// credentials.user and credentials.pass are already decoded
// auth.approve returns a tuple: (isSuccess, jsonResponse)
success, jsonRes = auth.approve(server, credentials, "/dashboard")
return jsonRes
})
// Tip: You can also use auth.updateUser, auth.addOrUpdateUser, and auth.removeUser
// auth.removeUser(server, "john")
server.runLoop()
Static File Serving
To enable serving HTML, CSS, and JS files from a directory:
// Port, Host, WebRoot Path
server = nylweb.createServer(8080, "localhost", "./wwwroot")
server.runLoop()
Files in ./wwwroot will be automatically served at the root / path.
Foreign Function Interface (FFI)
kernel = link "kernel32.dll" { fn GetTickCount() int }
Assert
assert condition, "Success message"
Meta-Programming
Dynamic Execution (eval)
Evaluate Nyl code strings asynchronously at runtime. The @eval builtin returns a NylTask which must be awaited using the await keyword. Setting the second argument to false allows the script to share/modify the current scope.
t = @eval("os.sleep(100); return 'Hello from Eval'") // Isolated
res = await t
println(res)
Existence Checks
if fnExists("myFunction") ? myFunction()
️ Built-in Functions Reference
Core & Meta
print(),println(),input(),pause().clock(),getTime(): Timestamps.fnExists(name): Check if function exists.@eval(script, isolated): Asynchronous dynamic execution (returnsNylTask).formatToStr(obj, delimiter, camelCase, camelDelimiter, useMarkup): Formats any object to a string. Can also be used as an extension method:obj.formatToStr(...).
OS & File (os module)
os.name(),os.arch(),os.getEnvVar(),os.getUptime().os.watchProcesses(names, onStart, onStop, intervalMs?): Monitors a list of process names and invokes lifecycle callbacks automatically without blocking VM execution. Returns a handle.os.stopWatchProcesses(handle): Stops a process monitor gracefully. You can also callhandle.stop().os.fileExists(),os.fileReadText(),os.fileWriteText(),os.fileDelete().os.findFilesInFolder(path, filter).ptr.nil(),ptr.isNil(),ptr.add().
JSON Working Examples
1. Serializing to JSON
jsonStr = json.stringify({ "id": 1, "active": true })
2. Parsing into a Typed Object
type Config { string host, int port }
cfg = json.parse(jsonStr, Config)
3. Parsing as Dynamic Map
map = json.parse(jsonStr)
println(map.id)
️ Roadmap
- Native structures for FFI.
- Enhanced package management.