Crate finx

Crate finx 

Source
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 and false
  • 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§

compiler
engine
Finx Engine
lexer
parser
vm

Macros§

register_function
Convenience macro for easily registering native functions