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:

bash
nyl main.nyl

REPL (Read-Eval-Print Loop)

To start the interactive shell:

bash
nyl

Distributable Self-Hosted Package

Nyl can bundle a script with the host runtime executable:

bash
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):

bash
# 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)

nyl
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.

nyl
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.

nyl
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.

nyl
println("Not {interpolated}"#)

Verbatim and Multiline

Verbatim strings ignore escape characters (like \), and triple-quotes allow multiline blocks.

nyl
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

nyl
person = { "name": "Alice", "age": 29, } // Trailing comma supported
println(person["name"])
println(person.name) // Property-style shortcut

Explicit Typing

nyl
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: true or false.
  • 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:

  • f or F: Strict 32-bit floating point (float). Example: 3.14F
  • m or M: Strict 128-bit precise decimal (decimal). Example: 10.5m
  • n or N: Strict 64-bit floating point (number). Example: 99n
  • l or L: Strict 64-bit integer (long). Example: 100L

Bytes and Bitwise Operations

Nyl provides robust support for byte-level manipulation.

Byte Literals

nyl
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()

nyl
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:

nyl
price = 19.99M   // decimal
speed = 3.14F    // float
count = 100L     // long
value = 42n      // number (double)

Import System

Basic and Alias

nyl
import "math.nyl"
import "utils.nyl" as utils

Fallback and Modules

nyl
import "local.nyl" fallback "default.nyl"
import os.windows as win

Control Flow

If / Else

nyl
if x > 10 { ... } else { ... }
res = condition ? "Yes" : "No"

Loops For (Unified)

nyl
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

nyl
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

nyl
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

nyl
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

nyl
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

nyl
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).

nyl
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.

nyl
p2 = p1 with { name = "New Name", age = 21 }

️ Error Handling

Nyl uses the try/or construct for safe expression evaluation.

nyl
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)

nyl
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)

nyl
fn add(a, b) => a + b

Block Body

The last expression in a block body is automatically returned.

nyl
fn calculate(x, y) {
    result = x * y
    result + 10 // Returns this value
}

Explicit Typing

You can specify parameter types and return types.

nyl
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.

nyl
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.

nyl
// 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.

nyl
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).

nyl
fn work() {
    file = open("data.txt")
    defer close(file)
    // ...
}

️ Decorators

Decorators provide metadata and behavior modification for functions and parameters.

nyl
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., in NylWeb).
  • [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.

nyl
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.

nyl
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 the try/or expression block.

task.all and task.any

You can aggregate and await arrays of tasks dynamically through the task global native methods:

nyl
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)

nyl
nylweb.runLoop(8080)
get("/ping", fn() => "pong")

Using an instance allows for better state management and parallel servers.

nyl
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).

nyl
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

nyl
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).

nyl
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.

nyl
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:

nyl
// 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)

nyl
kernel = link "kernel32.dll" { fn GetTickCount() int }

Assert

nyl
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.

nyl
t = @eval("os.sleep(100); return 'Hello from Eval'") // Isolated
res = await t
println(res)

Existence Checks

nyl
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 (returns NylTask).
  • 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 call handle.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

nyl
jsonStr = json.stringify({ "id": 1, "active": true })

2. Parsing into a Typed Object

nyl
type Config { string host, int port }
cfg = json.parse(jsonStr, Config)

3. Parsing as Dynamic Map

nyl
map = json.parse(jsonStr)
println(map.id)

️ Roadmap

  • Native structures for FFI.
  • Enhanced package management.