Functions & Control Flow
Functions & Control Flow
Section titled “Functions & Control Flow”Function Definitions
Section titled “Function Definitions”Functions are the primary unit of logic in NSL. They are always declared at the top level (no nested function definitions).
Basic Syntax
Section titled “Basic Syntax”ReturnType functionName(Type param1, Type param2) { // body return value;}Example
Section titled “Example”int add(int a, int b) { return a + b;}
string greet(string name) { return `Hello, ${name}!`;}Default Arguments
Section titled “Default Arguments”Parameters can have default values. Optional parameters must appear after all required parameters.
void log(string message, string level = "INFO", boolean timestamp = true) { // level defaults to "INFO", timestamp defaults to true}
// Valid calls:log("Server started"); // level="INFO", timestamp=truelog("Error occurred", "ERROR"); // timestamp=truelog("Debug info", "DEBUG", false); // All specifiedHow Defaults Work (Under the Hood)
Section titled “How Defaults Work (Under the Hood)”The VM has no concept of default parameters. The compiler handles them entirely:
- The compiler sees
log("Server started") - It looks up the function definition
- It finds 2 missing arguments
- It injects their default values into the bytecode
The generated bytecode is identical to what would be produced for log("Server started", "INFO", true). Zero runtime overhead.
Varargs
Section titled “Varargs”A function can accept a variable number of arguments using the varargs syntax. At most one varargs parameter is allowed, and it must be the last parameter.
int sum(int ...values[]) { int total = 0; for (int i = 0; i < values.length(); i++) { total = total + values[i]; } return total;}
// Calls:int a = sum(1, 2, 3); // values = [1, 2, 3]int b = sum(10, 20, 30, 40); // values = [10, 20, 30, 40]The main Entry Point
Section titled “The main Entry Point”Every .nox program must have a main function. This is the entry point called by the runtime.
- Parameters define the program’s input schema. The runtime deserializes incoming arguments (from JSON) into the specified types.
- Return type is implicitly
string. The runtime automatically converts the final returned value (evenint,double, orjson) into a string. - The
mainkeyword replaces the return type in the signature.
main(string url, double minThreshold = 10.5) { // url is required, minThreshold defaults to 10.5 return `Processed ${url} with threshold ${minThreshold}`;}Parameter to Input Schema Mapping
Section titled “Parameter to Input Schema Mapping”The main function’s signature directly maps to the program’s input schema:
| NSL Parameter | Schema |
|---|---|
string url | Required string argument |
double minThreshold = 10.5 | Optional double, defaults to 10.5 |
int[] ids | Required array of integers |
User user | Required struct mapping (JSON object) |
CLI and Host Execution
Section titled “CLI and Host Execution”When running via the nox CLI, required parameters must be provided. Optional parameters that are omitted will use their default values.
- Flags (
-a,--arg): Pass arguments usingname=valueformat. For structs or arrays, provide valid JSON strings:Terminal window nox run script.nox -a url="https://api.example.com" -a minThreshold=5.0nox run auth.nox -a user='{"name": "Alice", "age": 30}' - Interactive Prompts: If a required argument is omitted, the CLI will interactively prompt for it. For
structtypes, the CLI recursively prompts for each field individually, making complex input ergonomic.
Unified Function Call Syntax (UFCS)
Section titled “Unified Function Call Syntax (UFCS)”UFCS allows a function to be called as if it were a method of its first argument. This provides an ergonomic, object-oriented feel without classes.
The Rule
Section titled “The Rule”variable.method(args...) ←→ method(variable, args...)These two calls are semantically identical.
Example
Section titled “Example”type Point { int x; int y; }
double calculateDistance(Point p) { return Math.sqrt(p.x * p.x + p.y * p.y);}
main() { Point origin = { x: 3, y: 4 };
// Standard function call double dist1 = calculateDistance(origin);
// UFCS identical semantics double dist2 = origin.calculateDistance();
// Both return 5.0}Resolution Precedence
Section titled “Resolution Precedence”When the compiler encounters variable.method(args...), it resolves in this order:
- Host Method: Is
variablea built-in type (likestring) that has a native Kotlin-backed method calledmethod? - Struct Field: Is
methoda field on the struct type ofvariable? - Global Function (UFCS): Is there a global function named
methodwhose first parameter type matchesvariable’s type? - Error: If none match, a
SemanticErroris thrown at compile time.
Real-World Value
Section titled “Real-World Value”UFCS enables a “method chaining” style without requiring class hierarchies:
type User { string name; int age; }
boolean isAdult(User u) { return u.age >= 18; }string displayName(User u) { return u.name.upper(); }
main() { User alice = { name: "alice", age: 25 };
if (alice.isAdult()) { yield `Adult: ${alice.displayName()}`; }}Control Flow Statements
Section titled “Control Flow Statements”if / else
Section titled “if / else”if (condition) { // true branch} else if (otherCondition) { // else-if branch} else { // false branch}while Loop
Section titled “while Loop”while (condition) { // body}for Loop
Section titled “for Loop”for (int i = 0; i < 10; i++) { // body}foreach Loop
Section titled “foreach Loop”Iterates over arrays:
string[] names = ["Alice", "Bob", "Charlie"];
foreach (string name in names) { yield `Processing ${name}`;}Under the hood: The compiler desugars
foreachinto a standardwhileloop with an index counter,.length()check, and array index access.
break and continue
Section titled “break and continue”for (int i = 0; i < 100; i++) { if (i % 2 == 0) { continue; // Skip even numbers } if (i > 50) { break; // Stop at 50 } yield `Odd: ${i}`;}Under the hood:
breakcompiles to aJMPto the loop’s exit instruction.continuecompiles to aJMPto the loop’s condition re-evaluation.
Compound Assignment & Increment/Decrement
Section titled “Compound Assignment & Increment/Decrement”NSL supports shorthand operators for common mutations:
i++; // Equivalent to i = i + 1i--; // Equivalent to i = i - 1i += 5; // Equivalent to i = i + 5i -= 3; // Equivalent to i = i - 3i *= 2; // Equivalent to i = i * 2i /= 4; // Equivalent to i = i / 4i %= 10; // Equivalent to i = i % 10These work for both int and double values.
Under the hood:
i++andi--compile to dedicatedIINC/IDECinstructions.i += Ncompiles toIINCN. All are single-instruction operations with no temporary registers.
Error Handling: try / catch
Section titled “Error Handling: try / catch”try { json data = Http.getJson(url); process(data);} catch (NetworkError e) { yield `Network error: ${e}`;} catch (TypeError e) { yield `Type error: ${e}`;} catch (err) { // Catch-all for any unhandled error type yield `Unexpected error: ${err}`;}Semantics
Section titled “Semantics”- The
catchvariable (e.g.,e,err) is astringcontaining the error message - Multiple
catchblocks can target different error types - A catch-all (no type specified) catches anything not matched above
try-catchblocks can be nested- See Error Handling for the zero-cost table-driven implementation
Throwing Errors
Section titled “Throwing Errors”if (value < 0) { throw "Value must be non-negative";}Streaming: yield and return
Section titled “Streaming: yield and return”These keywords send data back to the Host application, but with critically different effects.
yield value;
Section titled “yield value;”Sends an intermediate result to the Host. The Sandbox continues executing.
main(string url) { yield "Starting download..."; // Progress update json data = Http.getJson(url); yield `Downloaded ${data.size()} items`; // More progress
for (int i = 0; i < data.size(); i++) { process(data[i]); if (i % 100 == 0) { yield `Processed ${i} items...`; // Periodic updates } }
return "All done!";}Key behavior:
yieldsends output to the Host, which decides what to do with it (print to stdout, stream over a WebSocket, buffer in a list, etc.)- Execution continues immediately after each
yield yieldcan be called from any function, not justmain
return value; in main
Section titled “return value; in main”Sends the final result and terminates the Sandbox.
main(string name) { return `Hello, ${name}!`; // Sandbox terminates after this // Any code here is unreachable}return value; in Other Functions
Section titled “return value; in Other Functions”Returns the value to the caller and exits the current function (standard semantics):
int max(int a, int b) { if (a > b) { return a; } return b;}Lifecycle Summary
Section titled “Lifecycle Summary”| Keyword | Where | Effect |
|---|---|---|
yield value | Any function | Sends interim output; execution continues |
return value | main | Sends final output; Sandbox terminates |
return value | Other functions | Returns to caller; function exits |