use brink_format::Value;
use crate::error::RuntimeError;
use crate::story::Flow;
pub(crate) fn range_make(flow: &mut Flow, inclusive: bool) -> Result<(), RuntimeError> {
let end_val = flow.pop_value()?;
let start_val = flow.pop_value()?;
let (Value::Int(start), Value::Int(end)) = (&start_val, &end_val) else {
let bad = if matches!(start_val, Value::Int(_)) {
&end_val
} else {
&start_val
};
return Err(RuntimeError::StdlibWrongType {
verb: "range",
expected: "int bounds",
found: super::collection_ops::type_name(bad),
});
};
flow.value_stack.push(Value::range(*start, *end, inclusive));
Ok(())
}
pub(crate) fn range_non_empty(flow: &mut Flow) -> Result<(), RuntimeError> {
let v = flow.pop_value()?;
let Some(len) = v.range_len() else {
return Err(RuntimeError::StdlibWrongType {
verb: "non_empty",
expected: "a range",
found: super::collection_ops::type_name(&v),
});
};
let result = if len > 0 {
Value::some(v)
} else {
Value::none()
};
flow.value_stack.push(result);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::output::OutputBuffer;
use crate::story::PendingTerminal;
use alloc::vec::Vec;
fn test_flow() -> Flow {
Flow {
threads: Vec::new(),
value_stack: Vec::new(),
output: OutputBuffer::new(),
pending_choices: Vec::new(),
current_tags: Vec::new(),
in_tag: false,
skipping_choice: false,
did_safe_exit: false,
did_unsafe_yield: false,
ran_out_of_content_cause: crate::RanOutOfContentCause::default(),
line_delivered_this_turn: false,
exec_mode: crate::story::ExecMode::default(),
pure_callback: crate::story::PureCallbackState::default(),
next_block_id: 0,
pending_terminal: PendingTerminal::default(),
warnings: Vec::new(),
}
}
#[test]
fn range_make_builds_both_forms() {
for (inclusive, expected) in [
(false, Value::range(1, 4, false)),
(true, Value::range(1, 4, true)),
] {
let mut flow = test_flow();
flow.value_stack.push(Value::Int(1));
flow.value_stack.push(Value::Int(4));
range_make(&mut flow, inclusive).unwrap();
let got = flow.pop_value().unwrap();
assert_eq!(got.as_range(), expected.as_range());
}
}
#[test]
fn range_make_accepts_empty_and_backwards_bounds() {
for (a, b) in [(0, 0), (5, 2)] {
let mut flow = test_flow();
flow.value_stack.push(Value::Int(a));
flow.value_stack.push(Value::Int(b));
range_make(&mut flow, false).unwrap();
assert_eq!(flow.pop_value().unwrap().range_len(), Some(0));
}
}
#[test]
fn range_make_faults_on_non_int_bounds() {
let mut flow = test_flow();
flow.value_stack.push(Value::Int(0));
flow.value_stack.push(Value::Float(3.5));
let err = range_make(&mut flow, false).unwrap_err();
assert!(matches!(
err,
RuntimeError::StdlibWrongType {
verb: "range",
found: "float",
..
}
));
let mut flow = test_flow();
flow.value_stack.push(Value::from("a"));
flow.value_stack.push(Value::Int(3));
let err = range_make(&mut flow, true).unwrap_err();
assert!(matches!(
err,
RuntimeError::StdlibWrongType {
verb: "range",
found: "string",
..
}
));
let mut flow = test_flow();
flow.value_stack.push(Value::Bool(false));
flow.value_stack.push(Value::Int(3));
assert!(range_make(&mut flow, false).is_err());
}
#[test]
fn non_empty_mints_some_over_inhabited_ranges() {
for r in [
Value::range(1, 6, true),
Value::range(0, 1, false),
Value::range(5, 5, true), Value::range(-3, 0, false),
] {
let mut flow = test_flow();
flow.value_stack.push(r.clone());
range_non_empty(&mut flow).unwrap();
let got = flow.pop_value().unwrap();
assert_eq!(got, Value::some(r.clone()));
let Value::OptionVal(Some(inner)) = got else {
unreachable!("non_empty over inhabited must be some");
};
assert_eq!(inner.as_range(), r.as_range());
}
}
#[test]
fn non_empty_returns_none_over_empty_ranges() {
for r in [
Value::range(0, 0, false),
Value::range(5, 5, false),
Value::range(7, 2, true),
] {
let mut flow = test_flow();
flow.value_stack.push(r);
range_non_empty(&mut flow).unwrap();
assert_eq!(flow.pop_value().unwrap(), Value::none());
}
}
#[test]
fn non_empty_faults_on_non_range() {
let mut flow = test_flow();
flow.value_stack.push(Value::Int(5));
let err = range_non_empty(&mut flow).unwrap_err();
assert!(matches!(
err,
RuntimeError::StdlibWrongType {
verb: "non_empty",
found: "int",
..
}
));
}
}