Finx - Embeddable Scripting Language
Finx is a fast, lightweight scripting language designed for easy embedding in Rust applications. It features a stack-based virtual machine, lexical scoping with closures, and a simple but powerful syntax.
Features
- Fast execution: Stack-based virtual machine with optimized bytecode
- Easy integration: Simple API for embedding in Rust applications
- Native functions: Register Rust functions to be called from scripts
- Memory safe: Built in Rust with safe memory management
- Rich syntax: Support for functions, closures, loops, conditionals
- Context management: Maintain state between script executions
Quick Start
Add Finx to your Cargo.toml:
[]
= "0.1.0"
Basic Usage
use Finx;
Native Functions
Register Rust functions to be called from scripts:
use ;
let mut engine = new;
// Register a simple math function
engine.register_function;
// Use the native function in a script
let result = engine.eval.unwrap;
assert_eq!;
Using the Convenience Macro
For easier native function registration:
use ;
let mut engine = new;
register_function!;
register_function!;
let result = engine.eval.unwrap;
assert_eq!;
Native Functions with Closures
Finx supports advanced native functions using closures, enabling state capture and more dynamic behavior:
Basic Closure Registration
use ;
use Rc;
let mut engine = new;
// Simple closure with captured state
let prefix = "LOG: ".to_string;
engine.register_function;
engine.execute.unwrap;
// Output: LOG: Hello from script!
Shared Mutable State
For shared state between multiple closures, use Rc<RefCell<T>>:
use ;
use Rc;
use RefCell;
let mut engine = new;
// Shared counter state
let counter = new;
// Increment function
let counter_clone = counter.clone;
engine.register_function;
// Get current count
let counter_clone = counter.clone;
engine.register_function;
// Reset counter
let counter_clone = counter.clone;
engine.register_function;
engine.execute.unwrap;
When to Use Closures vs Function Pointers
Use Closures When:
- You need to capture configuration or state
- Functions need to share mutable state
- You want to create factory functions for different behaviors
- You need access to external resources (files, network, etc.)
Use Function Pointers When:
- Simple, stateless operations
- Maximum performance is critical
- Functions are pure/mathematical
- Backward compatibility with existing code
Convenience Method:
use Finx;
use Rc;
let mut engine = new;
// For closures
engine.register_closure;
Language Syntax
Finx supports a familiar, C-like syntax:
Variables and Assignment
let x = 42;
let name = "Alice";
let is_valid = true;
let empty = null;
x = x + 1; // Reassignment
Functions
fn
fn
Closures
fn
let counter = ;
; // 1
; // 2
Control Flow
// Conditionals
if x > 0 else if x < 0 else
// Loops
let i = 0;
while i < 5
;
}
Built-in Functions
When using Finx::new(), you get access to common functions:
; // 42
; // 4
; // 20
; // 10
; // 8
; // 5
; // true
; // true
Error Handling
Finx provides comprehensive error handling:
use ;
let mut engine = new;
match engine.eval
Advanced Usage
Running Scripts from Files
let mut engine = new;
// Execute a script file
engine.execute_file?;
// Evaluate an expression from a file
let result = engine.eval_file?;
Managing Output
let mut engine = new;
engine.execute?;
// Get all print output
let output = engine.get_output;
assert_eq!;
// Clear output for next execution
engine.clear_output;
Performance Tuning
let mut engine = new;
// Set recursion limits
engine.set_max_recursion_depth;
Value Types
Finx supports the following data types:
- Numbers: 64-bit floating point (
42,3.14,-1.5) - Strings: UTF-8 strings (
"hello","world") - Booleans:
trueandfalse - Null:
nullvalue - Functions: First-class functions and closures
Working with Values
use ;
let mut engine = new;
let result = engine.eval.unwrap;
match result
// Or use convenience methods
if let Some = result.as_num