Nari supports modular code organization through the import statement.
Import functions and variables from another file:
// math_utils.nari
func add(a, b) {
return a + b;
}
func multiply(a, b) {
return a * b;
}
global PI = 3.14159;
// main.nari
import "math_utils.nari";
print(add(5, 3)); // 8
print(multiply(4, 2)); // 8
print(PI); // 3.14159
// Import from same directory
import "helpers.nari";
// Import from subdirectory
import "utils/math.nari";
// Import from parent directory
import "../shared.nari";
import "/usr/local/lib/nari/stdlib.nari";
project/
+-- main.nari
+-- lib/
| +-- utils.nari
| +-- helpers.nari
| +-- constants.nari
+-- modules/
+-- user.nari
+-- database.nari
Create modules with encapsulated functionality:
// user_module.nari
global UserModule = {
create: func(name, email) {
return {
name: name,
email: email,
createdAt: 0 // timestamp
};
},
validate: func(user) {
if (!user.name || user.name.length() < 3) {
return false;
}
if (user.email.index_of("@") == -1) {
return false;
}
return true;
},
to_string: func(user) {
return user.name @ " <" @ user.email @ ">";
}
};
// main.nari
import "user_module.nari";
let user = UserModule.create("Alice", "alice@example.com");
if (UserModule.validate(user)) {
print(UserModule.to_string(user));
}
// config.nari
global APP_NAME = "MyApp";
global APP_VERSION = "1.0.0";
global DEBUG = true;
// main.nari
import "config.nari";
print(APP_NAME @ " v" @ APP_VERSION);
if (DEBUG) {
print("Debug mode enabled");
}
// database.nari
global connection = null;
func __init__() {
// This runs when module is imported
connection = {
host: "localhost",
port: 5432,
connected: false
};
print("Database module initialized");
}
// Call initialization
__init__();
global DB = {
connect: func() {
connection.connected = true;
print("Connected to database");
},
disconnect: func() {
connection.connected = false;
print("Disconnected");
}
};
- Imported files are executed once when first imported
- Subsequent imports of the same file are ignored (import caching)
- Circular imports are prevented
// lib.nari
print("Lib module loaded"); // Printed once
func helper() {
return 42;
}
// main.nari
import "lib.nari"; // Prints: "Lib module loaded"
import "lib.nari"; // No output (already loaded)
print(helper());
Imports are processed in order:
import "first.nari"; // Executes completely
import "second.nari"; // Then this executes
import "third.nari"; // Finally this
// All imports are complete here
The standard library is automatically imported:
// No import needed for these globals:
// - math, fs, http, net, JSON, Object, Array, String
// - platform, process, Spawn, yield, stdlib_version
// - Ok, Err, Some, None
print(math.sqrt(16)); // 4
print(stdlib_version); // "0.0.4"
There is no system module and no io module. Standard input lives on
process.stdin, and file operations live on fs.
These names are already bound, so do not reuse them. A top-level
func process(x) { ... } is silently ignored because the process global
wins, and the same declaration nested inside another function is a parse error.
Pick a different name.
See Standard Library for details.
// utils/string_utils.nari
global StringUtils = {
capitalize: func(str) {
if (str.length() == 0) return str;
let first = str.char_at(0);
let rest = str.substr(1, str.length());
return first.to_upper() @ rest;
},
reverse: func(str) {
let result = "";
for (let i = str.length() - 1; i >= 0; i--) {
result = result @ str.char_at(i);
}
return result;
},
truncate: func(str, maxLen) {
if (str.length() <= maxLen) {
return str;
}
return str.substr(0, maxLen - 3) @ "...";
}
};
// constants.nari
global HTTP_STATUS = {
OK: 200,
CREATED: 201,
BAD_REQUEST: 400,
UNAUTHORIZED: 401,
NOT_FOUND: 404,
SERVER_ERROR: 500
};
global COLORS = {
RED: "#FF0000",
GREEN: "#00FF00",
BLUE: "#0000FF"
};
global CONFIG = {
MAX_RETRIES: 3,
TIMEOUT_MS: 5000,
PAGE_SIZE: 20
};
// types.nari
type User {
id: number;
username: string;
email: string;
role: string
}
type Post {
id: number;
title: string;
content: string;
authorId: number;
createdAt: number
}
type Comment {
id: number;
postId: number;
userId: number;
text: string
}
// factories.nari
global UserFactory = {
create: func(username, email) {
return {
id: 0, // Would be set by database
username: username,
email: email,
role: "user",
createdAt: 0
};
},
createAdmin: func(username, email) {
let user = UserFactory.create(username, email);
user.role = "admin";
return user;
}
};
// logger.nari
global Logger = (func() {
let instance = null;
return {
getInstance: func() {
if (instance == null) {
instance = {
logs: [],
log: func(message) {
instance.logs.push(message);
print("[LOG] " @ message);
},
getLogs: func() {
return instance.logs;
}
};
}
return instance;
}
};
})();
Keep related functionality together:
// Good: user.nari contains all user-related functions
// Avoid: mixing user, post, and comment logic in one file
Make it clear what's exported:
// user.nari
// Private helper
func validateEmail(email) {
return email.index_of("@") != -1;
}
// Public API
global User = {
create: func(name, email) {
if (!validateEmail(email)) {
panic("Invalid email");
}
return { name: name, email: email };
}
};
Use namespaces to group related functions:
// Good
global MathUtils = {
add: func(a, b) { return a + b; },
multiply: func(a, b) { return a * b; }
};
// Avoid
global add = func(a, b) { return a + b; };
global multiply = func(a, b) { return a * b; };
// user_controller.nari
// Dependencies: user_model.nari, validation.nari
import "user_model.nari";
import "validation.nari";
// ...
Current module system limitations:
- No aliases: Can't rename imports
- No dynamic imports: Must be static at top level
- Error Handling - Using Result/Option in modules
- Standard Library - Built-in modules