Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .DS_Store
Binary file not shown.
111 changes: 111 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,53 @@ pub fn parse_expression(expr: &str) -> Result<ExprAST> {
parser::Parser::new(expr)?.parse_stmt()
}

/// ## Usage
///
/// Parse an expression and compile it directly to bytecode without keeping the AST.
/// This is useful when you want to cache the compiled bytecode and execute it multiple times
/// with different contexts, avoiding the parsing and compilation overhead.
///
/// ``` rust
/// use expression_engine::{parse_expression_to_bytecode, execute_program, create_context, Value};
/// let input = "a + b * 2";
/// let program = parse_expression_to_bytecode(input).unwrap();
///
/// // Execute the same bytecode program multiple times with different contexts
/// let mut ctx1 = create_context!("a" => 10, "b" => 5);
/// let result1 = execute_program(&program, &mut ctx1).unwrap();
/// assert_eq!(result1, Value::from(20));
///
/// let mut ctx2 = create_context!("a" => 1, "b" => 2);
/// let result2 = execute_program(&program, &mut ctx2).unwrap();
/// assert_eq!(result2, Value::from(5));
/// ```
pub fn parse_expression_to_bytecode(expr: &str) -> Result<bytecode::Program> {
init();
let ast = parser::Parser::new(expr)?.parse_stmt()?;
Comment on lines +86 to +87
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parse_expression_to_bytecode re-implements parsing (Parser::new(...).parse_stmt()) instead of delegating to the existing parse_expression helper. This duplicates logic and can drift if parsing behavior or initialization changes. Consider calling parse_expression(expr)? here and then compiling that AST.

Suggested change
init();
let ast = parser::Parser::new(expr)?.parse_stmt()?;
let ast = parse_expression(expr)?;

Copilot uses AI. Check for mistakes.
bytecode::compile_expression(&ast)
}

/// ## Usage
///
/// Execute a pre-compiled bytecode program with a given context.
/// This function should be used together with `parse_expression_to_bytecode` when you need
/// to execute the same expression multiple times with different contexts, which is more
/// efficient than calling `execute` repeatedly.
///
/// ``` rust
/// use expression_engine::{parse_expression_to_bytecode, execute_program, create_context, Value};
/// let input = "x * 2 + y";
/// let program = parse_expression_to_bytecode(input).unwrap();
///
/// let mut ctx = create_context!("x" => 5, "y" => 3);
/// let result = execute_program(&program, &mut ctx).unwrap();
/// assert_eq!(result, Value::from(13));
/// ```
pub fn execute_program(program: &bytecode::Program, ctx: &mut Context) -> Result<value::Value> {
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The public API returns Result<value::Value>, while other public entrypoints in this module return Result<Value> (the crate alias). For a consistent and simpler public surface, prefer returning Result<Value> here as well.

Suggested change
pub fn execute_program(program: &bytecode::Program, ctx: &mut Context) -> Result<value::Value> {
pub fn execute_program(program: &bytecode::Program, ctx: &mut Context) -> Result<Value> {

Copilot uses AI. Check for mistakes.
Comment thread
ashyanSpada marked this conversation as resolved.
init();
bytecode::execute_program(program, ctx)
}

/// ## Usage
///
/// You can register some inner functions in advance via this method
Expand Down Expand Up @@ -312,4 +359,68 @@ mod tests {
_ => panic!("Expected Error::DivByZero"),
}
}

#[test]
fn test_parse_expression_to_bytecode() {
let input = "a + b * 2";
let program = crate::parse_expression_to_bytecode(input);
assert!(program.is_ok());
let program = program.unwrap();
assert!(!program.instructions.is_empty());
assert!(!program.constants.is_empty());
}

#[test]
fn test_execute_program() {
let input = "x * 2 + y";
let program = crate::parse_expression_to_bytecode(input).unwrap();
let mut ctx = create_context!("x" => 5, "y" => 3);
let result = crate::execute_program(&program, &mut ctx).unwrap();
assert_eq!(result, Value::from(13));
}

#[test]
fn test_parse_and_execute_bytecode_reuse() {
// Test that we can compile once and execute multiple times
let input = "a + b * 2";
let program = crate::parse_expression_to_bytecode(input).unwrap();

// Execute with first context
let mut ctx1 = create_context!("a" => 10, "b" => 5);
let result1 = crate::execute_program(&program, &mut ctx1).unwrap();
assert_eq!(result1, Value::from(20));

// Execute with second context
let mut ctx2 = create_context!("a" => 1, "b" => 2);
let result2 = crate::execute_program(&program, &mut ctx2).unwrap();
assert_eq!(result2, Value::from(5));

// Execute with third context
let mut ctx3 = create_context!("a" => 100, "b" => 50);
let result3 = crate::execute_program(&program, &mut ctx3).unwrap();
assert_eq!(result3, Value::from(200));
}

#[test]
fn test_execute_program_with_function() {
let input = "f(x) + y";
let program = crate::parse_expression_to_bytecode(input).unwrap();
let mut ctx = create_context!(
"x" => 10,
"y" => 5,
"f" => Arc::new(|params| {
let val = params[0].clone().integer()?;
Ok(Value::from(val * 2))
})
);
let result = crate::execute_program(&program, &mut ctx).unwrap();
assert_eq!(result, Value::from(25));
}

#[test]
fn test_parse_expression_to_bytecode_invalid() {
let input = "a + )"; // Invalid expression - unmatched closing paren
let result = crate::parse_expression_to_bytecode(input);
assert!(result.is_err());
}
}
Loading