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.
- 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
Add Finx to your Cargo.toml:
[dependencies] finx = "0.1.0"
use finx::Finx; fn main() { // Create a new language engine let mut engine = Finx::new(); // Run a simple expression let result = engine.eval("2 + 3 * 4").unwrap(); println!("Result: {}", result); // Result: 14 // Define variables and functions engine.execute(r#" let name = "World"; fn greet(person) { return "Hello, " + person + "!"; } "#).unwrap(); // Use defined variables and functions let greeting = engine.eval("greet(name)").unwrap(); println!("{}", greeting); // Hello, World! }
Register Rust functions to be called from scripts:
use finx::{Finx, Value}; let mut engine = Finx::new(); // Register a simple math function engine.register_function("multiply", |args| { if let [Value::Number(a), Value::Number(b)] = args { Value::Number(a * b) } else { panic!("multiply expects two numbers"); } }, 2); // Use the native function in a script let result = engine.eval("multiply(6, 7)").unwrap(); assert_eq!(result.as_num(), Some(42.0));
For easier native function registration:
use finx::{Finx, register_function}; let mut engine = Finx::new(); register_function!(engine, "add", 2, |a: f64, b: f64| -> f64 { a + b }); register_function!(engine, "format_name", 2, |first: &str, last: &str| -> String { format!("{}, {}", last, first) }); let result = engine.eval(r#"format_name("John", "Doe")"#).unwrap(); assert_eq!(result.as_str(), Some("Doe, John"));
Finx supports advanced native functions using closures, enabling state capture and more dynamic behavior:
use finx::{Finx, Value}; use std::rc::Rc; let mut engine = Finx::new(); // Simple closure with captured state let prefix = "LOG: ".to_string(); engine.register_function("log", Rc::new(move |args| { if let [Value::Str(msg)] = args { println!("{}{}", prefix, msg); } Value::Null })); engine.execute(r#"log("Hello from script!");"#).unwrap(); // Output: LOG: Hello from script!
For shared state between multiple closures, use Rc<RefCell<T>>:
use finx::{Finx, Value}; use std::rc::Rc; use std::cell::RefCell; let mut engine = Finx::new(); // Shared counter state let counter = Rc::new(RefCell::new(0_i32)); // Increment function let counter_clone = counter.clone(); engine.register_function("increment", Rc::new(move |_args| { let mut count = counter_clone.borrow_mut(); *count += 1; Value::Number(*count as f64) })); // Get current count let counter_clone = counter.clone(); engine.register_function("get_count", Rc::new(move |_args| { let count = counter_clone.borrow(); Value::Number(*count as f64) })); // Reset counter let counter_clone = counter.clone(); engine.register_function("reset", Rc::new(move |_args| { let mut count = counter_clone.borrow_mut(); *count = 0; Value::Null })); engine.execute(r#" print(increment()); // 1 print(increment()); // 2 print(get_count()); // 2 reset(); print(get_count()); // 0 "#).unwrap();
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::Finx; use std::rc::Rc; let mut engine = Finx::new(); // For closures engine.register_closure("add", |args| { // Implementation finx::Value::Null });
Finx supports a familiar, C-like syntax:
let x = 42; let name = "Alice"; let is_valid = true; let empty = null; x = x + 1; // Reassignment
fn add(a, b) { return a + b; } fn factorial(n) { if n <= 1 { return 1; } return n * factorial(n - 1); }
fn make_counter() { let count = 0; fn increment() { count = count + 1; return count; } return increment; } let counter = make_counter(); print(counter()); // 1 print(counter()); // 2
// Conditionals if x > 0 { print("Positive"); } else if x < 0 { print("Negative"); } else { print("Zero"); } // Loops let i = 0; while i < 5 { print(i); i = i + 1; } for i in 0..10 { print(i); }
When using Finx::new(), you get access to common functions:
print(abs(-42)); // 42 print(sqrt(16)); // 4 print(max(10, 20)); // 20 print(min(10, 20)); // 10 print(pow(2, 3)); // 8 print(len("hello")); // 5 print(is_num(42)); // true print(is_str("test")); // true
Finx provides comprehensive error handling:
use finx::{Finx, FinxError}; let mut engine = Finx::new(); match engine.eval("unknown_variable") { Ok(result) => println!("Result: {}", result), Err(FinxError::RuntimeError(msg)) => println!("Runtime error: {}", msg), Err(FinxError::ParseError(err)) => println!("Parse error: {}", err), Err(err) => println!("Other error: {}", err), }
let mut engine = Finx::new(); // Execute a script file engine.execute_file("example_scripts/example.fx")?; // Evaluate an expression from a file let result = engine.eval_file("example_scripts/example.fx")?;
let mut engine = Finx::new(); engine.execute(r#" print("Hello"); print("World"); "#)?; // Get all print output let output = engine.get_output(); assert_eq!(output, &["Hello", "World"]); // Clear output for next execution engine.clear_output();
let mut engine = Finx::new(); // Set recursion limits engine.set_max_recursion_depth(500);
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
use finx::{Finx, Value}; let mut engine = Finx::new(); let result = engine.eval("42").unwrap(); match result { Value::Number(n) => println!("Got number: {}", n), Value::Str(s) => println!("Got string: {}", s), Value::Bool(b) => println!("Got boolean: {}", b), Value::Null => println!("Got null"), _ => println!("Got other value"), } // Or use convenience methods if let Some(num) = result.as_num() { println!("Number value: {}", num); }