The CLS language is split into two independent Rust crates —Documentation Index
Fetch the complete documentation index at: https://mintlify.com/elfrask/cls/llms.txt
Use this file to discover all available pages before exploring further.
cls-core (the compiler frontend: lexer, parser, AST, type checker) and cls-runtime (the tree-walking interpreter, value system, module resolver, and scheduler). Neither crate depends on filesystem access, a CLI, or any node-specific functionality. This means you can embed them directly into any Rust application and use CLS as a sandboxed scripting language, an expression evaluator, a configuration format, or a plugin system — with full control over which modules and globals are available to scripts.
Cargo.toml
Add both crates as path dependencies (once CLS is available on crates.io the same block will use registry versions):Minimal Working Example
The following Rust program tokenizes a CLS source string, parses it into an AST module, creates an interpreter configured with core stdlib and desktop globals, executes the module, and then callsmain:
Parse
Parser::new(tokens).parse() produces a Module (the root AST node) or a ClsError on a syntax error.Create the interpreter
Interpreter::new(intrinsics, resolver) initialises the environment, registers all global intrinsics, and builds the primitive method dispatch tables.Execute the module
interp.execute(&module) runs all top-level statements: function declarations are registered, variable initialisers are evaluated, class and structure definitions are stored.Intrinsics API
Intrinsics is the struct that supplies the node-provided global environment — the functions and values that scripts can use without importing any module.
| Method | Description |
|---|---|
Intrinsics::empty() | No globals — not even print or args |
Intrinsics::desktop_defaults(args: Vec<String>) | Provides print, input, and args |
intr.add(name: &str, value: Value) | Inject any additional global by name |
ModuleResolver API
ModuleResolver is the pluggable system that controls how import "name" statements are resolved. See Module Resolvers for the full guide; the key methods are summarised here.
| Method | Description |
|---|---|
ModuleResolver::new() | Creates an empty resolver (no modules available) |
.with_core_stdlib() | Registers math, json, async as built-in internals |
.add_internal(name: &str, value: Value) | Inject a named module backed by a Value::Record of native functions |
.set_external(closure) | Provide a closure hook for resolving user modules from files or other sources |
Type Checking Before Execution
To run the CLS type checker on an AST module before executing it, useTypeChecker from cls-core:
Vec<Diagnostic>. It does not execute any code. You can choose to treat warnings as errors, or simply log them and proceed to execution.
Error Reporting
Whenexecute or call_main returns an Err, use build_error_report and format_error to produce human-readable output with source context and a call stack trace:
ErrorFormat variants control the output style:
| Variant | Output |
|---|---|
ErrorFormat::Console | Colour-coded terminal output with source line highlight |
ErrorFormat::Plain | Plain text, suitable for log files |
ErrorFormat::Html | HTML fragment for embedding in web UIs |
ErrorFormat::Json | Machine-readable JSON object |
Attaching Source Context
If your source came from a file, pass the path to the interpreter before executing so that error reports can show the file name:Notes
Interpreter is not Send. It holds Arc<Mutex<Environment>> closures internally and relies on a single-threaded cooperative scheduler for async coroutines. Always create and run an interpreter on the same thread.The desktop node modules (
fs, http, Lib) are not part of cls-runtime. If your embedded application needs them, you must build the corresponding Value::Record of native functions and register them with resolver.add_internal("fs", fs_module) before passing the resolver to the interpreter.