Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

RetrogradeDev/finx

Repository files navigation

Finx - Embeddable Scripting Language

Finx Logo

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:

[dependencies]
finx = "0.1.0"

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_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));

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
}));
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)
}));
// 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();

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
});

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(len("hello")); // 5
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

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")?;

Managing Output

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();

Performance Tuning

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(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);
}

About

Embeddable Scripting Language for Rust

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

AltStyle によって変換されたページ (->オリジナル) /