Skip to content

Commit ffb2dc5

Browse files
ClaudeashyanSpada
andauthored
Improve API design: Add documentation, tests, and init() call for bytecode functions
Agent-Logs-Url: https://github.com/ashyanSpada/expression_engine_rs/sessions/6c599b7f-b7f6-4f2e-a732-61b10b9c4971 Co-authored-by: ashyanSpada <22587148+ashyanSpada@users.noreply.github.com>
1 parent 28fa618 commit ffb2dc5

1 file changed

Lines changed: 101 additions & 0 deletions

File tree

src/lib.rs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,50 @@ pub fn parse_expression(expr: &str) -> Result<ExprAST> {
6262
parser::Parser::new(expr)?.parse_stmt()
6363
}
6464

65+
/// ## Usage
66+
///
67+
/// Parse an expression and compile it directly to bytecode without keeping the AST.
68+
/// This is useful when you want to cache the compiled bytecode and execute it multiple times
69+
/// with different contexts, avoiding the parsing and compilation overhead.
70+
///
71+
/// ``` rust
72+
/// use expression_engine::{parse_expression_to_bytecode, execute_program, create_context, Value};
73+
/// let input = "a + b * 2";
74+
/// let program = parse_expression_to_bytecode(input).unwrap();
75+
///
76+
/// // Execute the same bytecode program multiple times with different contexts
77+
/// let mut ctx1 = create_context!("a" => 10, "b" => 5);
78+
/// let result1 = execute_program(&program, &mut ctx1).unwrap();
79+
/// assert_eq!(result1, Value::from(20));
80+
///
81+
/// let mut ctx2 = create_context!("a" => 1, "b" => 2);
82+
/// let result2 = execute_program(&program, &mut ctx2).unwrap();
83+
/// assert_eq!(result2, Value::from(5));
84+
/// ```
6585
pub fn parse_expression_to_bytecode(expr: &str) -> Result<bytecode::Program> {
6686
init();
6787
let ast = parser::Parser::new(expr)?.parse_stmt()?;
6888
bytecode::compile_expression(&ast)
6989
}
7090

91+
/// ## Usage
92+
///
93+
/// Execute a pre-compiled bytecode program with a given context.
94+
/// This function should be used together with `parse_expression_to_bytecode` when you need
95+
/// to execute the same expression multiple times with different contexts, which is more
96+
/// efficient than calling `execute` repeatedly.
97+
///
98+
/// ``` rust
99+
/// use expression_engine::{parse_expression_to_bytecode, execute_program, create_context, Value};
100+
/// let input = "x * 2 + y";
101+
/// let program = parse_expression_to_bytecode(input).unwrap();
102+
///
103+
/// let mut ctx = create_context!("x" => 5, "y" => 3);
104+
/// let result = execute_program(&program, &mut ctx).unwrap();
105+
/// assert_eq!(result, Value::from(13));
106+
/// ```
71107
pub fn execute_program(program: &bytecode::Program, ctx: &mut Context) -> Result<value::Value> {
108+
init();
72109
bytecode::execute_program(program, ctx)
73110
}
74111

@@ -322,4 +359,68 @@ mod tests {
322359
_ => panic!("Expected Error::DivByZero"),
323360
}
324361
}
362+
363+
#[test]
364+
fn test_parse_expression_to_bytecode() {
365+
let input = "a + b * 2";
366+
let program = crate::parse_expression_to_bytecode(input);
367+
assert!(program.is_ok());
368+
let program = program.unwrap();
369+
assert!(!program.instructions.is_empty());
370+
assert!(!program.constants.is_empty());
371+
}
372+
373+
#[test]
374+
fn test_execute_program() {
375+
let input = "x * 2 + y";
376+
let program = crate::parse_expression_to_bytecode(input).unwrap();
377+
let mut ctx = create_context!("x" => 5, "y" => 3);
378+
let result = crate::execute_program(&program, &mut ctx).unwrap();
379+
assert_eq!(result, Value::from(13));
380+
}
381+
382+
#[test]
383+
fn test_parse_and_execute_bytecode_reuse() {
384+
// Test that we can compile once and execute multiple times
385+
let input = "a + b * 2";
386+
let program = crate::parse_expression_to_bytecode(input).unwrap();
387+
388+
// Execute with first context
389+
let mut ctx1 = create_context!("a" => 10, "b" => 5);
390+
let result1 = crate::execute_program(&program, &mut ctx1).unwrap();
391+
assert_eq!(result1, Value::from(20));
392+
393+
// Execute with second context
394+
let mut ctx2 = create_context!("a" => 1, "b" => 2);
395+
let result2 = crate::execute_program(&program, &mut ctx2).unwrap();
396+
assert_eq!(result2, Value::from(5));
397+
398+
// Execute with third context
399+
let mut ctx3 = create_context!("a" => 100, "b" => 50);
400+
let result3 = crate::execute_program(&program, &mut ctx3).unwrap();
401+
assert_eq!(result3, Value::from(200));
402+
}
403+
404+
#[test]
405+
fn test_execute_program_with_function() {
406+
let input = "f(x) + y";
407+
let program = crate::parse_expression_to_bytecode(input).unwrap();
408+
let mut ctx = create_context!(
409+
"x" => 10,
410+
"y" => 5,
411+
"f" => Arc::new(|params| {
412+
let val = params[0].clone().integer()?;
413+
Ok(Value::from(val * 2))
414+
})
415+
);
416+
let result = crate::execute_program(&program, &mut ctx).unwrap();
417+
assert_eq!(result, Value::from(25));
418+
}
419+
420+
#[test]
421+
fn test_parse_expression_to_bytecode_invalid() {
422+
let input = "a + )"; // Invalid expression - unmatched closing paren
423+
let result = crate::parse_expression_to_bytecode(input);
424+
assert!(result.is_err());
425+
}
325426
}

0 commit comments

Comments
 (0)