Expand description
§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.
§Basic Usage
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!
}
§Native Functions
Register Rust functions to be called from scripts:
use finx::{Finx, Value};
let mut engine = Finx::new();
// Register a simple math function
engine.register_closure("multiply", |args: &[Value]| {
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));
§Using the Convenience Macro
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"));
§Native Functions with Closures
Finx supports advanced native functions using closures, enabling state capture and more dynamic behavior:
§Basic Closure Registration
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
}), 1);
engine.execute(r#"log("Hello from script!");"#).unwrap();
// Output: LOG: Hello from script!
§Shared Mutable State
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)
}), 0);
// 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)
}), 0);
// 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
}), 0);
engine.execute(r#"
print(increment()); // 1
print(increment()); // 2
print(get_count()); // 2
reset();
print(get_count()); // 0
"#).unwrap();
§Configuration-Driven Functions
Create functions that adapt based on configuration:
use finx::{Finx, Value};
use std::rc::Rc;
let mut engine = Finx::new();
// Configuration
struct Config {
debug: bool,
log_level: String,
}
let config = Config {
debug: true,
log_level: "INFO".to_string(),
};
// Logger that respects configuration
engine.register_function("log", Rc::new(move |args| {
if let [Value::Str(level), Value::Str(msg)] = args {
if config.debug || **level != "DEBUG" {
println!("[{}] {}", level, msg);
}
}
Value::Null
}), 2);
engine.execute(r#"
log("INFO", "Application started");
log("DEBUG", "This might not show");
"#).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::Finx;
use std::rc::Rc;
let mut engine = Finx::new();
// For closures
engine.register_closure("add", |args| {
// Implementation
finx::Value::Null
}, 2);
§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 add(a, b) {
return a + b;
}
fn factorial(n) {
if n <= 1 {
return 1;
}
return n * factorial(n - 1);
}
§Closures
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
§Control Flow
// 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);
}
§Built-in Functions
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(is_num(42)); // true
print(is_str("test")); // true
§Error Handling
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),
}
§Advanced Usage
§Running Scripts from Files
use finx::Finx;
let mut engine = Finx::new();
// Execute a script file
engine.execute_file("example_scripts/example.fx").unwrap();
// Evaluate an expression from a file
let result = engine.eval_file("example_scripts/example.fx").unwrap();
§Managing Output
use finx::Finx;
let mut engine = Finx::new();
engine.execute(r#"
print("Hello");
print("World");
"#).unwrap();
// Get all print output
let output = engine.get_output();
assert_eq!(output, &["Hello", "World"]);
// Clear output for next execution
engine.clear_output();
§Performance Tuning
use finx::Finx;
let mut engine = Finx::new();
// Set recursion limits
engine.set_max_recursion_depth(500);
§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:
true
andfalse
- Null:
null
value - Functions: First-class functions and closures
§Working with Values
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(ref 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);
}
Re-exports§
pub use engine::Finx;
pub use engine::FinxError;
pub use engine::Result;
pub use vm::NativeFn;
pub use vm::Value;
Modules§
Macros§
- register_
function - Convenience macro for easily registering native functions