KCL Evaluator vs Runner: Understanding the Functional Difference Between kcl-evaluator and kcl-runner Components
The kcl-evaluator crate provides the core language engine that executes KCL AST and produces in-memory values, while kcl-runner serves as the executable wrapper that handles CLI arguments, file I/O, and invokes the evaluator to produce formatted output.
KCL (Kusion Configuration Language) separates language semantics from execution orchestration through two distinct architectural components in the kcl-lang/kcl repository. Understanding the functional difference between kcl-evaluator and kcl-runner is essential for developers embedding KCL in Rust applications or contributing to the compiler toolchain. While both crates work together to execute KCL programs, they operate at fundamentally different layers of the stack.
What Is the kcl-evaluator Component?
The kcl-evaluator crate contains the core evaluation engine responsible for walking the KCL AST, resolving symbols, executing statements, and producing in-memory KCL values (ValueRef). Located in crates/evaluator/src/lib.rs, this component implements pure language semantics without dealing with command-line handling, file discovery, or output formatting.
The primary type is Evaluator<'ctx>, which owns the AST, evaluation frames, scopes, lazy evaluation structures, and a Context used by the runtime. Key public methods include:
new(program)– Instantiates the evaluator with a parsedast::Programrun()– Executes the full program evaluationrun_as_function()– Evaluates the program as a specific function or schema targetplan_globals_to_string()– Converts the final evaluated globals into JSON/YAML string representations
The evaluator’s workflow follows a strict pipeline: it initializes scopes via init_scope(kcl_ast::MAIN_PKG), compiles AST modules using compile_ast_modules(), and plans the final value through plan_globals_to_string(). This design makes the evaluator ideal for embedding KCL logic directly into other Rust programs or for unit-testing evaluation logic without filesystem dependencies.
What Is the kcl-runner Component?
The kcl-runner crate implements the executable front-end (the kclc/kcl binary) that orchestrates the entire execution process. Unlike the evaluator, this crate focuses on process-level concerns including environment configuration, CLI parsing, and result formatting. The crate is located at crates/runner/ and notably contains only Cargo.toml and build.rs, lacking a src/ directory because it primarily exports a binary target rather than a library API.
The runner’s responsibilities include:
- CLI parsing – Handling flags such as
--output,--schema, and--stream - File loading – Using
kcl-loaderto resolve the import graph and load source files - Context creation – Instantiating a
kcl_runtime::Contextto hold buffers, options, and result strings - Evaluator invocation – Calling
Evaluator::new(...).run()orrun_as_function()after preparing the runtime context - Output formatting – Printing or writing JSON/YAML results, handling custom manifest streams, and managing exit codes
The build.rs script in crates/runner/build.rs injects environment variables such as KCL_DEFAULT_TARGET, ensuring the binary is configured correctly for the target platform. While the runner crate itself is minimal, the actual CLI entry point typically resides in crates/cli/src/main.rs, which coordinates file loading and invokes the evaluator logic.
Key Architectural Differences
| Aspect | kcl-evaluator | kcl-runner |
|---|---|---|
| Primary Role | Language engine executing AST and semantics | Executable wrapper managing I/O and runtime |
| Public API | Rust library with Evaluator<'ctx> type |
Binary target only; no public Rust API |
| Input | In-memory ast::Program |
File paths and CLI arguments |
| Output | ValueRef and internal strings |
Formatted JSON/YAML to stdout or files |
| Key File | crates/evaluator/src/lib.rs |
crates/runner/Cargo.toml and build.rs |
| Environment | Pure computation; no side effects | Sets env vars (e.g., KCL_DEFAULT_TARGET) |
The evaluator acts as the brain that understands KCL syntax, type checking, and lazy evaluation, while the runner acts as the nervous system connecting that brain to the outside world through filesystem operations and standard streams.
Practical Usage Examples
Using the Evaluator Directly (Library Code)
Embed KCL evaluation in your Rust application by interfacing directly with the evaluator crate:
use kcl_ast::ast::Program;
use kcl_evaluator::Evaluator;
use kcl_runtime::Context;
// Assume `prog` is a parsed `Program` obtained from kcl_parser
let mut runtime = Context::new(); // runtime context
let evaluator = Evaluator::new(&prog); // create evaluator
evaluator.init_scope(kcl_ast::MAIN_PKG); // initialise top‑level scope
evaluator.compile_ast_modules(&prog.get_modules_for_pkg(kcl_ast::MAIN_PKG));
let (json, yaml) = evaluator.plan_globals_to_string(); // final output
println!("JSON:\n{}", json);
println!("YAML:\n{}", yaml);
Running KCL via the CLI (Binary)
Execute KCL files using the runner binary built from the kcl-runner crate:
# Build the binary
cargo build -p kcl-runner
# Execute a KCL file and get JSON/YAML
./target/debug/kclc example.k
# → prints JSON and YAML to stdout, or writes to files with `--output`
Running as a Function
Target specific schemas or functions within a KCL file using the runner’s function mode:
./kclc --func my_pkg.my_schema example.k
# The runner calls `Evaluator::run_as_function()` and returns the
# evaluated `ValueRef` of the target function/schema.
Source Code Structure
Understanding the repository layout clarifies the separation of concerns:
crates/evaluator/src/lib.rs– Defines theEvaluatorstruct, evaluation flow, and value planning logic according to the KCL language semantics.crates/runner/Cargo.toml– Cargo manifest specifying the binary target and dependencies for the executable front-end.crates/runner/build.rs– Build script that injects environment variables such asKCL_DEFAULT_TARGETduring compilation.crates/cli/src/main.rs– CLI entry point that parses arguments, loads files, creates theContext, and invokes the evaluator.crates/runtime/src/context.rs– Holds runtime buffers, options, and result strings used by both the evaluator and runner components.
Summary
kcl-evaluatorprovides the core language engine incrates/evaluator/src/lib.rs, exposing theEvaluator<'ctx>type for AST execution and value production.kcl-runnerserves as the minimal binary wrapper incrates/runner/that handles CLI parsing, file I/O, and environment setup before invoking the evaluator.- The evaluator operates on in-memory AST structures (
ast::Program) and returnsValueRefobjects, while the runner processes filesystem paths and formats output as JSON/YAML. - The runner crate contains only
Cargo.tomlandbuild.rs, indicating it primarily exports a binary rather than a library API. - Direct library use of
kcl-evaluatoris ideal for embedding KCL in Rust applications, whilekcl-runnerprovides the standard command-line interface.
Frequently Asked Questions
Can I use kcl-evaluator without kcl-runner in my Rust application?
Yes. The kcl-evaluator crate is designed as a library that operates independently of the runner. You can construct an Evaluator directly with a parsed ast::Program, call init_scope() and compile_ast_modules(), and retrieve results via plan_globals_to_string(). This approach is ideal for unit testing, custom build tools, or embedded scripting engines where you want to avoid CLI overhead.
Why does the kcl-runner crate lack a src directory?
The kcl-runner crate functions as a binary target rather than a library. It contains only Cargo.toml (defining the binary dependencies) and build.rs (setting compile-time environment variables like KCL_DEFAULT_TARGET). The actual CLI logic typically resides in crates/cli/src/main.rs, which imports functionality from the evaluator and runtime crates to orchestrate execution.
How does the evaluator handle output formatting versus the runner?
The evaluator produces raw string representations through plan_globals_to_string(), which returns JSON and YAML strings based on the evaluated global variables. The kcl-runner component handles the presentation layer—writing these strings to stdout or files, applying custom formatting flags like --stream, and managing error exit codes. The evaluator focuses on correctness of the values; the runner focuses on delivery of those values to the user.
Which component should I modify to add custom CLI flags?
You should modify the runner layer, specifically the CLI entry point in crates/cli/src/main.rs (which coordinates the runner functionality). This file parses command-line arguments, validates inputs, and prepares the Context before invoking the evaluator. The kcl-evaluator itself should remain agnostic to CLI concerns, maintaining its focus on language semantics and evaluation logic.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →