Variables
NSL has three ways to declare variables:
x = 42 # implicit declaration
let y = "hello" # explicit with let
const PI = 3.14159 # immutable constant
Compound assignment works on variables, dict members, and list indices:
x += 10
config.count *= 2
items[0] -= 1
Data Types
| Type | Example | Notes |
|------|---------|-------|
| int | 42, 0xFF, 0b1010 | 64-bit, supports hex/binary/octal |
| float | 3.14, 1.5e10 | 64-bit IEEE 754 |
| string | "hello", 'world' | UTF-8, single or double quotes |
| bool | true, false | |
| null | null | |
| list | [1, 2, 3] | ordered, mutable |
| dict | {name: "a", x: 1} | ordered map, string keys |
| set | {|1, 2, 3|} | unordered unique values |
| regex | /\d+/gi | full regex support |
| time | 14:30 | HH:MM literal |
| char | c'A' | single character |
String interpolation with f-strings:
let name = "NSL"
let version = 2
print(f"{name} version {version}") # "NSL version 2"
Multiline strings with triple quotes:
let text = """
This is a
multiline string
"""
Raw strings skip escape processing: r"no\escapes\here"
Procedures
Procedures are defined with :: and use indentation for the body:
:: add(a, b) ->
return a + b
:: greet(name) ->
print(f"Hello, {name}!")
# Call them
let sum = add(3, 4)
greet("world")
Procedures are hoisted -- you can call them before their definition in the file.
Lambdas
Lambdas are anonymous functions created with fn:
# Single expression
let double = fn(x) => x * 2
# Multi-statement block
let process = fn(x) =>
let result = x * 2
return result + 1
# With type annotation
let square = fn(x) -> int => x * x
Lambdas are first-class values -- pass them to functions, store in variables, return from other functions.
Control Flow
If / Else
if temperature > 30
print("Hot!")
else if temperature > 20
print("Nice")
else
print("Cold")
Ternary expressions
let label = "hot" if temp > 30 else "cold"
Note: the syntax is value_if_true if condition else value_if_false.
While and Do-While
while count > 0
count -= 1
do
let inp = input("Continue? ")
while inp != "no"
For Each
NSL uses for each (not for):
# Iterate a list
for each item in [1, 2, 3, 4, 5]
print(item)
# With filter
for each item in items where item > 3
print(item)
# Iterate dict keys
for each key in config
print(f"{key} = {config[key]}")
# Destructure key-value pairs
for each [key, val] in entries(config)
print(f"{key}: {val}")
# Destructure nested lists
for each [x, y] in [[1,2], [3,4], [5,6]]
print(f"({x}, {y})")
Break and Continue
for each n in range(100)
if n == 50
break
if n % 2 == 0
continue
print(n)
Error Handling
try
let result = risky_operation()
print(result)
catch error
print(f"Error: {error}")
finally
cleanup()
Throw your own errors:
throw "something went wrong"
Modules
Import code from other files with use:
use "utils.nsl"
use "core/math_helpers" # .nsl extension auto-appended
Module search order: absolute path, relative to current file, relative to CWD, NSL_PATH env var.
Defer
Run cleanup code when leaving a scope:
let handle = open_file("data.txt")
defer close(handle)
# ... use handle, it will be closed automatically
Assertions
assert(x > 0, "x must be positive")
require x > 0 else "x must be positive" # throws on failure