unilang

Crates.iounilang
lib.rsunilang
version0.46.0
created_at2025-05-11 00:58:51.602444+00
updated_at2025-12-19 21:16:22.35594+00
descriptionDefine your command-line utility interface once and get consistent interaction across multiple modalities β€” CLI, GUI, TUI, AUI, Web APIs, and moreβ€”essentially for free.
homepagehttps://github.com/Wandalen/wTools/tree/master/module/core/unilang/readme.md
repositoryhttps://github.com/Wandalen/wTools/tree/master/module/core/unilang
max_upload_size
id1668977
size3,180,658
Wandalen (Wandalen)

documentation

https://docs.rs/unilang

README

Module :: unilang

experimental rust-status docs.rs Open in Gitpod discord

Zero-overhead command framework with compile-time command registration

Table of Contents

Getting Started

Understanding Performance

Core Concepts

Advanced Topics

Reference


Value Proposition

unilang processes command definitions at compile-time, generating optimized static command registries that provide O(1) command lookups with zero runtime overhead and zero additional dependencies for downstream crates. This approach delivers:

  • 50x faster command resolution compared to runtime HashMap lookups (~80ns vs ~4,000ns)
  • Compile-time validation of all command definitions and arguments
  • Smaller binary size through static analysis and dead code elimination
  • SIMD acceleration for parsing with 4-25x performance improvements
  • Zero memory allocations for command lookup operations

Architecture Overview

Build-Time Processing (Happens Once)

Developer writes YAML
         |
         v
   unilang.commands.yaml
   - name: ".greet"
     description: "..."
     arguments: [...]
         |
         v
   build.rs (automatic)
   - Discovers YAML files
   - Validates definitions
   - Generates static command map
         |
         v
   OUT_DIR/static_commands.rs
   static STATIC_COMMANDS: [...]
   // Static commands with O(1) lookups
         |
         v
   Binary compiled
   (zero-overhead ready)

Runtime Execution (Every Command)

User: ".greet name::Alice"
         |
         v
     Parser
   (SIMD accelerated)
         |
         v
  Static Registry Lookup
   O(1) static map access
   ~80ns (no hashing!)
         |
         v
   Semantic Analyzer
   - Validate types
   - Apply defaults
   - Check constraints
         |
         v
   Command Routine
   Execute business logic
         |
         v
   Output Result

Key Benefits:

  • ⚑ Build-time: Validation catches errors before deployment
  • πŸš€ Runtime: O(1) lookups with zero allocations (~80ns)
  • πŸ“¦ Binary: Smaller size via static analysis
  • πŸ”’ Type-safe: Impossible to register invalid commands

Getting Started (60 Seconds)

  1. Add dependency:

    [dependencies]
    unilang = "0.35"
    
  2. Create unilang.commands.yaml in project root:

    - name: ".greet"
      description: "Greeting command"
      arguments:
        - name: "name"
          kind: "String"
          attributes:
            optional: true
            default: "World"
    
  3. Run example:

    cargo run --example static_01_basic_compile_time
    

What just happened? Commands auto-discovered from YAML, compiled into zero-overhead static registry, ready for O(1) lookups.

Next: See detailed guide for production setup.

Troubleshooting

Common issues and solutions for new users:

❌ "Command not found" error

Symptom: Error: Command '.greet' not found in registry

Cause: Command name missing dot prefix

Solution:

# ❌ Wrong
- name: "greet"

# βœ… Correct
- name: ".greet"

All command names MUST start with . (dot prefix)

❌ "Type mismatch" error during build

Symptom: Type hint warnings during cargo build

Cause: YAML default values quoted when they shouldn't be

Solution:

# ❌ Wrong - quoted values
arguments:
  - name: "dry"
    kind: "Boolean"
    attributes:
      default: 'false'     # String, not Boolean!

# βœ… Correct - unquoted primitives
arguments:
  - name: "dry"
    kind: "Boolean"
    attributes:
      default: false       # Boolean type

Rule: Boolean, Integer, Float values must NOT be quoted in YAML

❌ Build fails with "could not find static_commands.rs"

Symptom: error: couldn't read .../static_commands.rs: No such file or directory

Cause: Wrong feature flag or missing build-time approach

Solution:

# Ensure you have a build-time approach enabled
[dependencies]
unilang = "0.35"  # Default includes approach_yaml_multi_build

Check that you have at least one .yaml file with commands, or enable a different build-time approach feature.

❌ Performance not improved after migration

Symptom: Lookups still slow after switching to build-time approach

Cause: Using runtime registration in production CLI (10-50x performance penalty)

Solution:

// ❌ Wrong for production CLIs - 10-50x slower than static registration
// βœ… Appropriate for REPL/plugins/prototyping where flexibility > performance
let registry = CommandRegistry::new();

// βœ… Correct - using static registry
let registry = StaticCommandRegistry::from_commands(&STATIC_COMMANDS);

Verify you're using StaticCommandRegistry, not CommandRegistry::new().

❌ "Unknown parameter" error

Symptom: Error: Unknown parameter 'nam'

Cause: Typo in parameter name

Solution: Check error message for "Did you mean...?" suggestion:

Error: Unknown parameter 'nam'. Did you mean 'name'?
Use '.greet ??' for help

Parameter names are validated strictly - use ?? operator to see valid parameters.

πŸ” Still having issues?

  1. Check examples: Run cargo run --example static_01_basic_compile_time
  2. Enable verbose logging: Set RUST_LOG=debug
  3. Verify feature flags: Run cargo tree -f "{p} {f}"
  4. Review usage.md for detailed syntax rules

Usage Guide

For comprehensive usage patterns, best practices, and quick reference: See usage.md for concise guide covering:

  • Command naming conventions and structure
  • Argument types and validation
  • Parameter syntax and quoting rules
  • Help system (3 access methods)
  • Error handling patterns
  • REPL implementation
  • CLI aggregation
  • Testing and performance
  • Security considerations

Quick Start: Build-Time Registration (Recommended)

Step 1: Define Commands

Create unilang.commands.yaml:

- name: ".greet"           # RECOMMENDED: Show full command name
  namespace: ""            # Leave empty for root-level commands
  description: "High-performance greeting command"
  arguments:
    - name: "name"
      kind: "String"
      attributes:
        optional: true
        default: "World"

YAML Command Naming:

  • Recommended: name: ".greet" with namespace: "" - shows exact command users will type
  • Alternative: name: "greet" with namespace: ".session" - requires understanding concatenation
  • Always include the dot prefix in either name or namespace

Step 2: Configure Cargo.toml

The default configuration already enables build-time YAML processing:

[dependencies]
# Multi-YAML build-time approach is enabled by default
unilang = "0.35"

For single-file YAML approach, use:

[dependencies]
unilang = { version = "0.35", default-features = false, features = [
  "enabled",
  "approach_yaml_single_build"  # Single YAML file at compile-time
]}

Step 3: Build Script (Do You Need build.rs?)

Decision Tree:

Using default multi-YAML approach?
  β”œβ”€ YES β†’ βœ… No build.rs needed (auto-discovery)
  └─ NO  β†’ Using single-file YAML?
             β”œβ”€ YES β†’ βœ… Add minimal build.rs below
             └─ NO  β†’ βœ… Custom approach - implement discovery logic

For Single-File YAML Only (approach_yaml_single_build):

fn main()
{
  // Rebuild if YAML file changes
  println!( "cargo:rerun-if-changed=unilang.commands.yaml" );
  // Static registry generation happens automatically
}

Default Multi-YAML: No build.rs needed - automatic discovery of all .yaml files!

Step 4: Zero-Cost Execution

use unilang::prelude::*;

// Include compile-time generated commands (created automatically by build system)
include!( concat!( env!( "OUT_DIR" ), "/static_commands.rs" ) );

fn main() -> Result< (), unilang::Error >
{
  // StaticCommandRegistry requires approach_yaml_single_build,
  // approach_yaml_multi_build, or other build-time approach feature
  let registry = StaticCommandRegistry::from_commands( &STATIC_COMMANDS );
  let pipeline = Pipeline::new( registry );

  // O(1) lookup - no hashing overhead
  let result = pipeline.process_command_simple( ".greet name::Alice" );

  println!( "Output: {}", result.outputs[ 0 ].content );
  Ok( () )
}

Performance Comparison

Approach Lookup Time Memory Overhead Binary Size
Build-Time (Static) ~80ns Zero Smaller
Runtime (HashMap) ~4,000ns Hash tables + allocations Larger

Benchmark Results:

  • Static lookups: ~80-100ns (static map + zero allocations)
  • Runtime lookups: ~4,000-5,000ns (HashMap + semantic analysis)
  • Performance gain: 50x faster command resolution

Benchmark Methodology

Hardware: AMD Ryzen 9 5950X, 64GB RAM, NVMe SSD Framework: benchkit - specialized benchmarking framework for unilang Test Scenario: Lookup 100 commands from registry of 1,000 commands Iterations: 10,000 runs per approach, median reported Tool: cargo run --example static_03_performance_comparison

What's measured:

  • Static: Static map lookup + CommandDefinition retrieval
  • Runtime: HashMap lookup + CommandDefinition retrieval
  • Excludes parsing (measured separately with SIMD benchmarks)

Run benchmarks yourself:

cargo run --release --example static_03_performance_comparison

See /examples/static_03_performance_comparison.rs for full benchmark implementation.

Comparison to Alternatives

How unilang differs from popular Rust CLI frameworks:

Feature unilang clap structopt argh
Command Lookup O(1) static Runtime HashMap Runtime Runtime
Definition Style YAML/JSON/Rust DSL Rust builder API Derive macros Derive macros
Modality Support CLI, REPL, Web API CLI only CLI only CLI only
Multi-file Organization Auto-discovery Manual Manual Manual
Runtime Registration Hybrid (10-50x slower†) No No No
Build-time Validation Yes No Yes (compile) Yes (compile)
REPL Support Built-in Manual Manual Manual
Help Generation Auto + 3 operators Auto Auto Auto
Performance ~80ns lookup ~200-500ns ~200-500ns ~100-300ns
CLI Aggregation Built-in Manual Manual Manual
Learning Curve Medium Low Low Very Low

† Runtime registration appropriate for REPL applications, plugin systems, and prototyping. Not recommended for production CLIs due to performance penalty.

When to Choose unilang

Choose unilang if you need:

  • βœ… REPL or interactive shell interfaces
  • βœ… Multi-modality support (CLI + Web API + TUI)
  • βœ… Runtime plugin/command discovery (hybrid mode)
  • βœ… Maximum performance with static dispatch
  • βœ… CLI aggregation across multiple crates
  • βœ… Declarative YAML/JSON command definitions

Choose alternatives if:

  • ❌ You only need basic CLI arg parsing (use argh - simplest)
  • ❌ You prefer pure-Rust derive macros (use structopt/clap)
  • ❌ You need mature ecosystem with extensive plugins (clap has most)
  • ❌ You want minimal learning curve (argh is fastest to learn)

Philosophy Differences

unilang:

  • "Define once, use everywhere" - single command definition for all modalities
  • Optimized for command-rich applications (100s of commands)
  • Build-time optimization and validation

clap/structopt/argh:

  • "CLI-first" - specialized for argument parsing
  • Optimized for applications with 1-20 commands
  • Runtime flexibility, simpler mental model

User Guide: Integration Decisions

Decision 1: How Should I Define Commands?

⚠️ IMPORTANT: Opinionated Defaults

unilang ONLY enables Approach #2 by default. To use any other approach, you must explicitly enable its feature flag in Cargo.toml.

10 approaches are currently implemented (see full comparison):

# Approach Feature Flag Default Lookup Speed When to Use
1 YAML file β†’ Build-time static approach_yaml_single_build ❌ ~80ns Single-file projects, compile-time validation
2 Multi-YAML files β†’ Build-time static approach_yaml_multi_build βœ… DEFAULT ~80ns Modular projects, best DX, auto-discovery
3 YAML file β†’ Runtime loading approach_yaml_runtime ❌ ~4,200ns Plugin configs, runtime loading
4 JSON file β†’ Build-time static approach_json_single_build ❌ ~80ns JSON-first projects, API generation
5 Multi-JSON files β†’ Build-time static approach_json_multi_build ❌ ~80ns Large JSON projects, modular organization
6 JSON file β†’ Runtime loading approach_json_runtime ❌ ~4,200ns Runtime config loading, dynamic commands
7 Rust DSL (builder API) (always available) βœ… ~4,200ns Core API, prototyping, type-safe definitions
8 Rust DSL (const fn + static) approach_rust_dsl_const ❌ ~80ns High-performance DSL, compile-time
18 Hybrid (static + runtime) approach_hybrid ❌ Mixed Base CLI + plugin system

Why Approach #2 is Default:

  • Best developer experience with auto-discovery of command files
  • Optimal for modular projects splitting commands across multiple files
  • Compile-time validation catches errors before runtime
  • Zero overhead with static registry generation

See full comparison: 21 approaches documented including planned features like declarative macros, proc macros, TOML, RON, Protobuf, GraphQL, OpenAPI.

Decision 2: How Do I Execute Commands?

// CLI applications: Avoids shell quoting issues
let result = pipeline.process_command_from_argv( &std::env::args().collect() );

// REPL/interactive applications: String-based parsing
let result = pipeline.process_command_simple( ".greet name::Alice" );

Decision 3: What Are the Naming Rules?

βœ… Commands should start with a dot:

.greet name::Alice           # Recommended
greet name::Alice            # Not recommended

Decision 4: What Features Should I Enable?

Choose Your Configuration:

Use Case Configuration What You Get
Production (Recommended) unilang = "0.35" Multi-YAML + SIMD + Enhanced REPL
Custom Approach unilang = { version = "0.35", default-features = false, features = ["enabled", "approach_json_single_build"] } Switch to specific approach
Minimal (Core API Only) unilang = { version = "0.35", default-features = false, features = ["enabled"] } Rust DSL only, no build-time
Full (Development/Testing) unilang = { version = "0.35", features = ["full"] } All 21 approaches available

Most users: Just use unilang = "0.35" and you're done.

Feature Architecture:

The framework uses approach-based feature flags:

  • Each CLI definition approach has its own feature (e.g., approach_yaml_multi_build)
  • Approach features automatically enable required infrastructure (static_registry, yaml_parser, etc.)
  • Only Approach #2 enabled by default for optimal performance and minimal binary size
  • See Feature Flags Documentation for complete list

Decision 5: How Does Help Work?

Three methods available:

.command ?                   # Traditional operator (bypasses validation)
.command ??                  # Modern parameter
.command.help                # Auto-generated help command

Decision 6: Error Handling

Unknown parameters are always detected with Levenshtein distance suggestions. This cannot be disabled and ensures command correctness.

Decision 7: Advanced Use Cases

  • REPL applications: Use enhanced_repl feature for history, completion, secure input
  • WASM deployment: Full framework support in browsers with SIMD acceleration
  • Interactive arguments: Prompt for missing required arguments in REPL mode

Advanced Topics

The following sections cover advanced usage patterns, migration strategies, and specialized deployments. New users should start with the Getting Started section above.


CLI Aggregation: Unifying Multiple Tools

⚠️ Feature Required: multi_file (automatically enabled by default approach_yaml_multi_build)

unilang excels at aggregating multiple CLI tools into a single unified command interface. This is essential for organizations that want to consolidate developer tools while maintaining namespace isolation.

Real-World Aggregation Scenario

// Requires: approach_yaml_multi_build (default) or manually enable 'multi_file' feature
use unilang::multi_yaml::CliBuilder;

// Aggregate multiple CLI tools into one unified command
let unified_cli = CliBuilder::new()
  .static_module_with_prefix( "database", "db", database_commands )
  .static_module_with_prefix( "filesystem", "fs", file_commands )
  .static_module_with_prefix( "network", "net", network_commands )
  .static_module_with_prefix( "build", "build", build_commands )
  .detect_conflicts( true )
  .build_static();

// Usage: unified-cli .db.migrate, unified-cli .fs.copy src dest

Compile-Time Aggregation Benefits

Before Aggregation:

# Separate tools requiring individual installation and learning
db-cli migrate --direction up
file-cli copy --src ./source --dest ./target --recursive
net-cli ping google.com --count 10
build-cli compile --target release

After Aggregation:

# Single unified tool with consistent interface
unified-cli .db.migrate direction::up
unified-cli .fs.copy source::./source destination::./target recursive::true
unified-cli .net.ping host::google.com count::10
unified-cli .build.compile target::release

Key Aggregation Features

Namespace Isolation

Each CLI module maintains its own command space with automatic prefix application:

Database commands become .db.migrate, .db.backup
File commands become .fs.copy, .fs.move
Network commands become .net.ping, .net.trace
No naming conflicts between modules

Conflict Detection

let registry = CliBuilder::new()
  .static_module_with_prefix( "tools", "tool", cli_a_commands )
  .static_module_with_prefix( "utils", "tool", cli_b_commands )  // Conflict!
  .detect_conflicts( true )  // Catches duplicate prefixes at build time
  .build_static();

Help System Integration

# All aggregated commands support unified help
unified-cli .db.migrate.help       # Detailed help for database migrations
unified-cli .fs.copy ??            # Interactive help during command construction
unified-cli .net.ping ?            # Traditional help operator

Advanced Aggregation Patterns

Conditional Module Loading

let registry = CliBuilder::new()
  .conditional_module( "docker", docker_commands, &[ "feature_docker" ] )
  .conditional_module( "k8s", kubernetes_commands, &[ "feature_k8s" ] )
  .build_static();

// Only includes modules when features are enabled

Multi-Source Aggregation

use std::path::PathBuf;

// Combine static commands (in-memory) and dynamic YAML loading
let registry = CliBuilder::new()
  .static_module_with_prefix( "core", ".core", core_commands )
  .dynamic_module_with_prefix( "plugins", PathBuf::from( "plugins.yaml" ), ".plugins" )
  .build_hybrid();

// Key differences:
// - static_module_with_prefix(name, prefix, Vec<CommandDefinition>)
//   β†’ Commands already in memory, fast O(1) lookup (~80-100ns)
// - dynamic_module_with_prefix(name, PathBuf, prefix)
//   β†’ Commands loaded from YAML file at runtime (~4,000ns, 50x slower)

Performance Characteristics

Approach Lookup Time Memory Overhead Conflict Detection
Build-Time O(1) static Zero Build-time
Runtime O(log n) Hash tables Runtime

Aggregation Scaling:

  • 10 modules, 100 commands each: ~80-100ns lookup regardless of module count
  • Single static map: All 1,000 commands accessible in constant time with O(1) complexity
  • Namespace resolution: Zero runtime overhead with compile-time prefixing

Complete Example

See examples/practical_cli_aggregation.rs for a comprehensive demonstration showing:

  • Individual CLI module definitions
  • Runtime and compile-time aggregation approaches
  • Namespace organization and conflict prevention
  • Unified command execution patterns
  • Performance comparison between approaches
# Run the complete aggregation demo
cargo run --example practical_cli_aggregation

This example demonstrates aggregating database, file, network, and build CLIs into a single unified tool while maintaining type safety, performance, and usability.

Command Definition Format

Basic Command Structure

- name: ".command_name"          # Required: Command identifier (must start with dot)
  namespace: ""                  # Optional: Hierarchical organization (e.g., "math", "file")
  description: "What it does"    # Required: User-facing description
  arguments:                     # Optional: Command parameters
    - name: "arg_name"
      kind: "String"             # String, Integer, Float, Boolean, Path, etc.
      attributes:
        optional: false          # Required by default
        default: "value"         # Default value if optional

Supported Argument Types

  • Basic Types: String, Integer, Float, Boolean
  • Path Types: Path, File, Directory
  • Complex Types: Url, DateTime, Pattern (regex)
  • Collections: List, Map with custom delimiters
  • Special Types: JsonString, Object, Enum

Validation Rules

arguments:
  - name: "count"
    kind: "Integer"
    validation_rules:
      - Min: 1
      - Max: 100
  - name: "email"
    kind: "String"
    validation_rules:
      - Pattern: "^[^@]+@[^@]+\\.[^@]+$"
      - MinLength: 5

Common Pitfalls (Type Defaults)

⚠️ YAML permits type coercion, but unilang enforces strict typing. The build system now emits type hints for these common mistakes:

❌ WRONG - Quoted defaults for typed arguments:

- name: "dry"
  kind: "Boolean"
  attributes:
    default: 'false'    # String literal, not boolean

- name: "verbosity"
  kind: "Integer"
  attributes:
    default: '2'        # String literal, not integer

βœ… CORRECT - Unquoted defaults matching argument type:

- name: "dry"
  kind: "Boolean"
  attributes:
    default: false      # Boolean value

- name: "verbosity"
  kind: "Integer"
  attributes:
    default: 2          # Integer value

πŸ’‘ Type Hint Detection: The build system analyzes argument definitions and emits non-blocking hints during cargo build when it detects potential type mismatches. To suppress hints for intentional cases (e.g., version strings like "1.0"), add suppress_type_hint: true to the argument's attributes.

Help Generation:

auto_help_enabled: true   # Default: true. Controls auto-generation of .command.help
                          # Set to false to prevent .command.help creation
                          # Help still available via ? and ?? operators

Command Execution Patterns

Standard Execution

let result = pipeline.process_command_simple( ".namespace.command arg::value" );
if result.success
{
  println!( "Success: {}", result.outputs[ 0 ].content );
}

Batch Processing

let commands = vec!
[
  ".file.create name::test.txt",
  ".file.write name::test.txt content::data",
  ".file.list pattern::*.txt",
];

let batch_result = pipeline.process_batch( &commands, ExecutionContext::default() );
println!( "Success rate: {:.1}%", batch_result.success_rate() * 100.0 );

Error Handling

match pipeline.process_command_simple( ".command arg::value" )
{
  result if result.success =>
  {
    // Process successful execution
    for output in result.outputs
    {
      println!( "Output: {}", output.content );
    }
  }
  result =>
  {
    if let Some( error ) = result.error
    {
      eprintln!( "Command failed: {}", error );
    }
  }
}

Help System

unilang provides comprehensive help with three access methods:

Traditional Help Operator

.command ?                    # Instant help, bypasses validation

Modern Help Parameter

.command ??                   # Clean help access
.command arg1::value ??       # Help with partial arguments

Auto-Generated Help Commands

.command.help                 # Direct help command access
.namespace.command.help       # Works with namespaced commands

Feature Configuration

Core Features

[dependencies]
unilang = "0.10"              # Default: enhanced_repl + simd + enabled

Performance Optimized

[dependencies]
unilang = { version = "0.10", features = ["simd", "enhanced_repl"] }

Minimal Footprint

[dependencies]
unilang = { version = "0.10", default-features = false, features = ["enabled"] }

Available Features

  • enabled - Core functionality (required)
  • simd - SIMD optimizations for 4-25x parsing performance
  • enhanced_repl - Advanced REPL with history, completion, secure input
  • repl - Basic REPL functionality
  • on_unknown_suggest - Fuzzy command suggestions

Examples and Learning Path

πŸš€ Start Here: Recommended Learning Path

1. Quick Start (Runtime, Educational Only)

  • 00_quick_start.rs - Get something working in 5 minutes (⚠️ runtime registration, slow)
  • 01_basic_command_registration.rs - Understand the runtime API (⚠️ 50x slower than build-time)

2. Production Approach (Build-Time, Recommended)

  • static_01_basic_compile_time.rs - READ THIS FIRST - Explains proper YAML + build.rs pattern
  • static_02_yaml_build_integration.rs - Multi-YAML file aggregation
  • static_03_performance_comparison.rs - Benchmark compile-time vs runtime (proves 50x speedup)
  • static_04_multi_module_aggregation.rs - Organize commands across modules

3. Advanced Type System

  • 02_argument_types.rs - String, Integer, Float, Boolean, Path, etc. (⚠️ requires json_parser)
  • 03_collection_types.rs - Lists, Maps with custom delimiters
  • 14_advanced_types_validation.rs - Complex validation rules (⚠️ requires json_parser)

4. Help & User Experience

  • 06_help_system.rs - Comprehensive help system
  • 18_help_conventions_demo.rs - Three help access methods (?, ??, .help)

5. REPL Applications

  • 12_repl_loop.rs - Basic REPL implementation
  • 15_interactive_repl_mode.rs - Interactive arguments + secure input (⚠️ requires enhanced_repl)
  • 17_advanced_repl_features.rs - History, completion, recovery (⚠️ requires enhanced_repl)

6. Complete Applications

  • full_cli_example.rs - Full-featured CLI with all concepts integrated
  • practical_cli_aggregation.rs - Real-world multi-tool aggregation (⚠️ requires multi_file)

⚠️ Feature Requirements Legend

  • No marker = Works with default features
  • ⚠️ json_parser = Requires JSON support feature
  • ⚠️ enhanced_repl = Requires advanced REPL features
  • ⚠️ multi_file = Requires multi-file aggregation (default includes this)

WebAssembly Support

unilang provides full WebAssembly compatibility for browser deployment:

cd examples/wasm-repl
wasm-pack build --target web --release
cd www && python3 -m http.server 8000

WASM Features:

  • Complete framework functionality in browsers
  • SIMD acceleration where supported
  • Optimized bundle size (800KB-1.2MB compressed)
  • Seamless Rust-to-JavaScript integration

Migration from Runtime to Build-Time

⚠️ PERFORMANCE NOTICE: Runtime command registration (CommandRegistry::new() and command_add_runtime()) has 10-50x slower performance than compile-time registration.

When to use runtime registration:

  • βœ… REPL applications - Commands defined interactively
  • βœ… Plugin systems - Commands loaded dynamically at runtime
  • βœ… Prototyping - Rapid development iteration

When to use compile-time registration:

  • ⚑ Production CLIs - Performance-critical applications
  • ⚑ Large command sets - 100+ commands benefit from ~80ns PHF lookups
  • ⚑ Embedded systems - Zero-overhead static dispatch

This section helps migrate performance-critical code from runtime to compile-time registration for 50x speedup.

Migrate from runtime registration (10-50x slower) to build-time registration (⚑ 50x faster) in 4 steps when performance matters.

Step 1: Extract Command Definitions to YAML

Before (Runtime, in main.rs): ⚠️ NOT RECOMMENDED FOR PRODUCTION CLIs (10-50x slower)

let mut registry = CommandRegistry::new();

let greet_cmd = CommandDefinition {
  name: ".greet".to_string(),
  namespace: String::new(),
  description: "Greeting command".to_string(),
  hint: "Say hello".to_string(),
  arguments: vec![
    ArgumentDefinition {
      name: "name".to_string(),
      kind: Kind::String,
      description: "Person's name".to_string(),
      hint: "Name".to_string(),
      attributes: ArgumentAttributes {
        optional: true,
        default: Some("World".to_string()),
        ..Default::default()
      },
      validation_rules: vec![],
      aliases: vec![],
      tags: vec![],
    }
  ],
  // ... other fields
  status: "stable".to_string(),
  version: "1.0.0".to_string(),
  aliases: vec![],
  tags: vec![],
  permissions: vec![],
  idempotent: true,
  deprecation_message: String::new(),
  http_method_hint: String::new(),
  examples: vec![],
  routine_link: None,
  auto_help_enabled: false,
};

registry.command_add_runtime(&greet_cmd, greet_routine)?;

After (Build-Time, in unilang.commands.yaml):

- name: ".greet"
  namespace: ""
  description: "Greeting command"
  hint: "Say hello"
  status: "stable"
  version: "1.0.0"
  tags: []
  aliases: []
  permissions: []
  idempotent: true
  deprecation_message: ""
  http_method_hint: ""
  auto_help_enabled: false
  examples: []
  arguments:
    - name: "name"
      kind: "String"
      description: "Person's name"
      hint: "Name"
      attributes:
        optional: true
        default: "World"
        multiple: false
        interactive: false
        sensitive: false
      validation_rules: []
      aliases: []
      tags: []
  routine_link: null

Step 2: Update Cargo.toml

Add feature flag:

[dependencies]
# Enable single-file YAML compile-time approach
unilang = { version = "0.35", features = ["approach_yaml_single_build"] }

# Or use default (multi-file auto-discovery)
unilang = "0.35"

Step 3: Configure Build Script (Single-File Only)

For approach_yaml_single_build, create build.rs:

fn main()
{
  // Rebuild if YAML file changes
  println!("cargo:rerun-if-changed=unilang.commands.yaml");

  // Static registry generation happens automatically
  // No manual code needed - the feature flag handles it
}

Note: With default approach_yaml_multi_build, no build.rs needed - auto-discovery handles everything!

Step 4: Update Code to Use Static Registry

Before (Runtime): ⚠️ 10-50x SLOWER (not recommended for production)

use unilang::prelude::*;

fn main() -> Result<(), unilang::Error> {
  let mut registry = CommandRegistry::new();

  // Manual registration (10-50x slower than static)
  registry.command_add_runtime(&greet_cmd, greet_routine)?;

  let pipeline = Pipeline::new(registry);
  let result = pipeline.process_command_simple(".greet name::Alice");
  Ok(())
}

After (Build-Time):

use unilang::prelude::*;

// Include build-time generated commands (auto-generated by build system)
include!(concat!(env!("OUT_DIR"), "/static_commands.rs"));

fn main() -> Result<(), unilang::Error> {
  // Zero-cost static registry (~80ns lookup vs ~4,000ns runtime)
  let registry = StaticCommandRegistry::from_commands(&STATIC_COMMANDS);

  let pipeline = Pipeline::new(registry);
  let result = pipeline.process_command_simple(".greet name::Alice");
  Ok(())
}

Step 5: Measure Performance Improvement

Run benchmarks:

cargo run --example static_03_performance_comparison

Expected results:

  • Runtime registration: ~4,000ns per command lookup
  • Compile-time registration: ~80-100ns per command lookup
  • Performance gain: 50x faster

Performance Optimization Guidelines

Compile-Time Best Practices

  • Use static command definitions for all known commands
  • Leverage multi-module aggregation for organization
  • Enable SIMD features for maximum parsing performance
  • Utilize conflict detection during build process

Runtime Considerations

  • Reserve runtime registration for truly dynamic scenarios
  • Minimize command modifications during execution
  • Use batch processing for multiple commands
  • Implement proper error handling and recovery
Commit count: 999

cargo fmt