use anyhow::Result;
use crate::engine::audio::interpreter::driver::AudioInterpreter;
use crate::language::syntax::ast::{DurationValue, Statement, StatementKind, Value};
use std::collections::HashMap;
#[test]
fn test_break_in_loop_exits_early() -> Result<()> {
let mut interp = AudioInterpreter::new(44100);
let mut bank_map = std::collections::HashMap::new();
bank_map.insert(
"kick".to_string(),
Value::String("file://kick.wav".to_string()),
);
interp
.variables
.insert("bank".to_string(), Value::Map(bank_map));
let trigger = Statement::trigger(".bank.kick", DurationValue::Milliseconds(100.0), None, 1, 1);
let break_stmt = Statement::new(StatementKind::Break, Value::Null, 0, 2, 1);
let loop_stmt = Statement::new(
StatementKind::Loop {
count: Value::Number(10.0),
body: vec![trigger, break_stmt],
},
Value::Null,
0,
1,
1,
);
interp.collect_events(&[loop_stmt])?;
assert!(
interp.events.events.len() >= 1,
"expected at least one event"
);
assert!(
interp.events.events.len() < 10,
"break did not exit loop as expected"
);
Ok(())
}
#[test]
fn test_function_return_sets_return_variable() -> Result<()> {
let mut interp = AudioInterpreter::new(44100);
let ret_stmt = Statement::new(
StatementKind::Return {
value: Some(Box::new(Value::Number(42.0))),
},
Value::Null,
0,
2,
1,
);
let func_stmt = Statement::new(
StatementKind::Function {
name: "myfunc".to_string(),
parameters: vec![],
body: vec![ret_stmt],
},
Value::Null,
0,
1,
1,
);
let call_stmt = Statement::new(
StatementKind::Call {
name: "myfunc".to_string(),
args: vec![],
},
Value::Null,
0,
3,
1,
);
interp.collect_events(&[func_stmt, call_stmt])?;
if let Some(val) = interp.variables.get("__return") {
match val {
Value::Number(n) => assert_eq!(*n, 42.0),
other => panic!("unexpected return type: {:?}", other),
}
} else {
panic!("__return not set by function call");
}
Ok(())
}
#[test]
fn test_nested_break_inner_only() -> Result<()> {
let mut interp = AudioInterpreter::new(44100);
let inner_print = Statement::print("inner", 1, 1);
let inner_break = Statement::new(StatementKind::Break, Value::Null, 0, 2, 1);
let inner_loop = Statement::new(
StatementKind::Loop {
count: Value::Number(10.0),
body: vec![inner_print, inner_break],
},
Value::Null,
0,
1,
1,
);
let outer_loop = Statement::new(
StatementKind::Loop {
count: Value::Number(3.0),
body: vec![inner_loop],
},
Value::Null,
0,
1,
1,
);
interp.collect_events(&[outer_loop])?;
assert_eq!(
interp.events.logs.len(),
3,
"expected 3 inner prints, got {}",
interp.events.logs.len()
);
Ok(())
}
#[test]
fn test_pass_ms_loop_exits_on_break() -> Result<()> {
let mut interp = AudioInterpreter::new(44100);
let p = Statement::print("tick", 1, 1);
let b = Statement::new(StatementKind::Break, Value::Null, 0, 2, 1);
let pass_call = Value::Call {
name: "pass".to_string(),
args: vec![Value::Number(1.0)],
};
let loop_stmt = Statement::new(
StatementKind::Loop {
count: pass_call,
body: vec![p, b],
},
Value::Null,
0,
1,
1,
);
interp.collect_events(&[loop_stmt])?;
assert!(interp.events.logs.len() >= 1, "expected at least one log");
assert!(
interp.events.logs.len() < 10,
"pass(ms) loop did not exit on break"
);
Ok(())
}
#[test]
fn test_return_nested_function_propagation() -> Result<()> {
let mut interp = AudioInterpreter::new(44100);
let inner_ret = Statement::new(
StatementKind::Return {
value: Some(Box::new(Value::Number(7.0))),
},
Value::Null,
0,
2,
1,
);
let inner_func = Statement::new(
StatementKind::Function {
name: "inner".to_string(),
parameters: vec![],
body: vec![inner_ret],
},
Value::Null,
0,
1,
1,
);
let call_inner = Statement::new(
StatementKind::Call {
name: "inner".to_string(),
args: vec![],
},
Value::Null,
0,
3,
1,
);
let outer_ret = Statement::new(
StatementKind::Return {
value: Some(Box::new(Value::Identifier("__return".to_string()))),
},
Value::Null,
0,
4,
1,
);
let outer_func = Statement::new(
StatementKind::Function {
name: "outer".to_string(),
parameters: vec![],
body: vec![call_inner, outer_ret],
},
Value::Null,
0,
5,
1,
);
let call_outer = Statement::new(
StatementKind::Call {
name: "outer".to_string(),
args: vec![],
},
Value::Null,
0,
6,
1,
);
interp.collect_events(&[inner_func, outer_func, call_outer])?;
if let Some(v) = interp.variables.get("__return") {
if let Value::Number(n) = v {
assert_eq!(*n, 7.0);
} else {
panic!("unexpected __return type");
}
} else {
panic!("__return missing after nested function call");
}
Ok(())
}
#[test]
fn test_post_increment_indexing() -> Result<()> {
let mut interp = AudioInterpreter::new(44100);
interp.variables.insert("i".to_string(), Value::Number(0.0));
interp.variables.insert(
"arr".to_string(),
Value::Array(vec![Value::Number(10.0), Value::Number(20.0)]),
);
let print_idx = Statement::new(
StatementKind::Print,
Value::Identifier("arr[i++]".to_string()),
0,
1,
1,
);
interp.collect_events(&[print_idx])?;
assert_eq!(interp.events.logs.len(), 1);
let msg = &interp.events.logs[0].1;
assert!(
msg.contains("10"),
"expected printed value to contain 10, got {}",
msg
);
if let Some(Value::Number(n)) = interp.variables.get("i") {
assert_eq!(*n as i32, 1);
} else {
panic!("i missing or wrong type");
}
Ok(())
}
#[test]
fn test_arithmetic_indexing() -> Result<()> {
let mut interp = AudioInterpreter::new(44100);
interp.variables.insert("i".to_string(), Value::Number(0.0));
interp.variables.insert(
"arr".to_string(),
Value::Array(vec![
Value::Number(10.0),
Value::Number(20.0),
Value::Number(30.0),
]),
);
let p1 = Statement::new(
StatementKind::Print,
Value::Identifier("arr[i+1]".to_string()),
0,
1,
1,
);
let p2 = Statement::new(
StatementKind::Print,
Value::Identifier("arr[i*2]".to_string()),
0,
1,
2,
);
interp.collect_events(&[p1, p2])?;
assert!(interp.events.logs.len() >= 2);
let m1 = &interp.events.logs[0].1;
let m2 = &interp.events.logs[1].1;
assert!(m1.contains("20") || m1.contains("20.0"));
assert!(m2.contains("10") || m2.contains("10.0") || m2.contains("30") || m2.contains("30.0"));
Ok(())
}
#[test]
fn test_effect_chaining_on_trigger() -> Result<()> {
let mut interp = AudioInterpreter::new(44100);
interp.variables.insert(
"kick".to_string(),
Value::String("file://kick.wav".to_string()),
);
let mut e1 = HashMap::new();
e1.insert("type".to_string(), Value::String("reverse".to_string()));
e1.insert("enabled".to_string(), Value::Boolean(true));
let mut e2 = HashMap::new();
e2.insert("type".to_string(), Value::String("speed".to_string()));
e2.insert("factor".to_string(), Value::Number(1.5));
let effects = Value::Array(vec![Value::Map(e1.clone()), Value::Map(e2.clone())]);
let trigger = Statement::trigger(
"kick",
DurationValue::Milliseconds(100.0),
Some(effects.clone()),
1,
1,
);
interp.collect_events(&[trigger])?;
let sample_events: Vec<_> = interp
.events
.events
.iter()
.filter_map(|e| match e {
crate::engine::audio::events::AudioEvent::Sample {
uri: _,
start_time: _,
velocity: _,
effects,
source: _,
} => Some(effects.clone()),
_ => None,
})
.collect();
assert!(
!sample_events.is_empty(),
"expected at least one sample event"
);
let first_effects = &sample_events[0];
match first_effects {
Some(Value::Array(arr)) => {
assert_eq!(arr.len(), 2);
}
other => panic!("unexpected effects payload: {:?}", other),
}
Ok(())
}