fiddler-script 0.1.3

A minimal C-style scripting language interpreter
Documentation

FiddlerScript

A minimal C-style scripting language with a Rust-based interpreter.

Features

  • Variables: let x = 10; for integers, let name = "hello"; for strings, and bytes support
  • Control Flow: if-else statements and for loops with C-style syntax
  • Functions: User-defined functions with fn name(params) { ... } syntax
  • Built-ins: print(), len(), str(), int(), getenv(), parse_json(), bytes_to_string(), bytes()
  • Comments: Single-line comments with //
  • Environment Variables: All OS environment variables available as script variables
  • External Integration: Set/get variables from Rust code, including bytes data

Usage

Add to your Cargo.toml:

[dependencies]
fiddler-script = { path = "../fiddler-script" }

Basic Example

use fiddler_script::Interpreter;

fn main() {
    let source = r#"
        let x = 10;
        let y = 20;
        print(x + y);
    "#;

    let mut interpreter = Interpreter::new();
    interpreter.run(source).expect("Failed to run script");
}

Injecting Variables

You can inject variables into the interpreter before running a script:

use fiddler_script::{Interpreter, Value};

fn main() {
    let mut interpreter = Interpreter::new();

    // Set variables of different types
    interpreter.set_variable_int("count", 42);
    interpreter.set_variable_string("name", "Alice");
    interpreter.set_variable_bytes("data", b"raw bytes".to_vec());
    interpreter.set_variable_value("flag", Value::Boolean(true));

    interpreter.run(r#"
        print("Count:", count);
        print("Name:", name);
        print("Data length:", len(data));
    "#).unwrap();
}

Retrieving Variables

After running a script, you can retrieve variable values:

use fiddler_script::Interpreter;

fn main() {
    let mut interpreter = Interpreter::new();
    interpreter.run("let result = 10 * 5;").unwrap();

    // Get as Value type
    if let Some(value) = interpreter.get_value("result") {
        println!("Result: {}", value);
    }

    // Get as bytes
    if let Some(bytes) = interpreter.get_bytes("result") {
        println!("As bytes: {:?}", bytes);
    }

    // Check if variable exists
    if interpreter.has_variable("result") {
        println!("Variable exists!");
    }
}

Custom Built-in Functions

use fiddler_script::{Interpreter, Value, RuntimeError};
use std::collections::HashMap;

fn main() {
    let mut builtins: HashMap<String, fn(Vec<Value>) -> Result<Value, RuntimeError>> = HashMap::new();

    builtins.insert("double".to_string(), |args| {
        if let Some(Value::Integer(n)) = args.first() {
            Ok(Value::Integer(n * 2))
        } else {
            Err(RuntimeError::InvalidArgument("Expected integer".to_string()))
        }
    });

    let mut interpreter = Interpreter::with_builtins(builtins);
    interpreter.run("print(double(21));").unwrap(); // Prints: 42
}

Language Syntax

Variables

let x = 10;
let name = "hello";
let flag = true;

Arithmetic

let sum = 1 + 2;
let diff = 5 - 3;
let product = 4 * 5;
let quotient = 10 / 2;
let remainder = 7 % 3;

Comparison and Logical Operators

let a = 5 > 3;      // true
let b = 5 == 5;     // true
let c = 5 != 3;     // true
let d = true && false;  // false
let e = true || false;  // true
let f = !true;      // false

Control Flow

// If-else
if (x > 0) {
    print("positive");
} else if (x < 0) {
    print("negative");
} else {
    print("zero");
}

// For loop
for (let i = 0; i < 10; i = i + 1) {
    print(i);
}

Functions

fn add(a, b) {
    return a + b;
}

let result = add(5, 3);
print(result);  // 8

Recursion

fn factorial(n) {
    if (n <= 1) {
        return 1;
    }
    return n * factorial(n - 1);
}

print(factorial(5));  // 120

Built-in Functions

Function Description
print(args...) Print values to stdout
len(value) Get the length of a string or bytes
str(value) Convert a value to a string
int(value) Convert a value to an integer
getenv(name) Get an environment variable by name (returns null if not found)
drop(value) Returns null (useful for clearing variables)
parse_json(bytes) Parse JSON from bytes or string
jmespath(data, expr) Query JSON data using JMESPath expressions
bytes_to_string(bytes) Convert bytes to a UTF-8 string
bytes(value) Convert any value to bytes

Environment Variables

By default, the interpreter loads all OS environment variables as script variables:

// Access environment variables directly
print(HOME);       // Prints home directory
print(PATH);       // Prints PATH

// Or use getenv() for dynamic access
let key = "HOME";
print(getenv(key));

Use Interpreter::new_without_env() to create an interpreter without loading environment variables.

Working with Bytes

Bytes are useful for handling binary data and JSON:

// Parse JSON data
let json = parse_json(json_data);  // json_data injected from Rust

// Convert bytes to string
let text = bytes_to_string(raw_bytes);

// Convert to bytes
let b = bytes("hello");  // String to bytes
let n = bytes(42);       // Integer to bytes (as string representation)