use std::fmt;
use std::sync::Arc;
use crate::ast::{ReflectedValue, Value};
#[derive(Debug, Clone, PartialEq)]
pub enum Bound {
Pct(f64),
Frac(f64),
Ord(u64),
Star,
Fill,
StarSplit(u64),
StarShaped(Vec<f64>),
Gap(Box<Bound>),
}
impl Bound {
pub fn resolve_against(&self, base_start: u64, base_end: u64) -> Option<u64> {
let extent = base_end.saturating_sub(base_start);
match self {
Bound::Pct(p) => Some(base_start + ((p / 100.0) * extent as f64).round() as u64),
Bound::Frac(f) => Some(base_start + (f * extent as f64).round() as u64),
Bound::Ord(o) => Some(base_start.saturating_add(*o).min(base_end)),
Bound::Star | Bound::Fill | Bound::StarSplit(_) | Bound::StarShaped(_)
| Bound::Gap(_) => None,
}
}
pub fn is_tail(&self) -> bool {
matches!(
self,
Bound::Star | Bound::Fill | Bound::StarSplit(_) | Bound::StarShaped(_)
)
}
pub fn is_sized(&self) -> bool {
matches!(self, Bound::Pct(_) | Bound::Frac(_) | Bound::Ord(_))
}
}
impl fmt::Display for Bound {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Bound::Pct(p) => write!(f, "{p}%"),
Bound::Frac(v) => write!(f, "{v}"),
Bound::Ord(o) => write!(f, "{o}"),
Bound::Star => write!(f, "*"),
Bound::Fill => write!(f, "..."),
Bound::StarSplit(n) => write!(f, "*/{n}"),
Bound::StarShaped(w) => {
let ws: Vec<String> = w.iter().map(|x| format!("{x:.3}")).collect();
write!(f, "*/shaped:{}", ws.join(","))
}
Bound::Gap(inner) => write!(f, "~{inner}"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum PartitionOrder {
#[default]
Unchanged,
SmallestFirst,
LargestFirst,
Random,
}
impl fmt::Display for PartitionOrder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
PartitionOrder::Unchanged => "unchanged",
PartitionOrder::SmallestFirst => "smallest_first",
PartitionOrder::LargestFirst => "largest_first",
PartitionOrder::Random => "random",
};
write!(f, "{s}")
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Chunking {
SingleRange {
start: Bound,
end: Bound,
},
DeltaList {
deltas: Vec<Bound>,
},
}
#[derive(Debug, Clone, PartialEq)]
pub struct PartitionSpec {
pub chunking: Chunking,
pub window: Option<(Bound, Bound)>,
pub order: PartitionOrder,
}
impl PartitionSpec {
pub fn single_range(start: Bound, end: Bound) -> Self {
Self {
chunking: Chunking::SingleRange { start, end },
window: None,
order: PartitionOrder::Unchanged,
}
}
pub fn delta_list(deltas: Vec<Bound>) -> Self {
Self {
chunking: Chunking::DeltaList { deltas },
window: None,
order: PartitionOrder::Unchanged,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Partition {
pub idx: u64,
pub count: u64,
pub start_ord: u64,
pub end_ord: u64,
pub start_pct: f64,
pub end_pct: f64,
pub base_extent: u64,
}
impl Partition {
#[inline]
pub fn cardinality(&self) -> u64 {
self.end_ord - self.start_ord
}
}
pub fn parse(input: &str) -> Result<PartitionSpec, String> {
let mut tokens: Vec<&str> = input.split_whitespace().collect();
if tokens.is_empty() {
return Err(format!("empty spec: `{input}`"));
}
let mut order = PartitionOrder::Unchanged;
if tokens.len() >= 2 {
let last = *tokens.last().unwrap();
if !last.is_empty()
&& last.chars().all(|c| c.is_ascii_alphabetic() || c == '_')
&& last != "in"
{
order = match last {
"unchanged" => PartitionOrder::Unchanged,
"smallest_first" => PartitionOrder::SmallestFirst,
"largest_first" => PartitionOrder::LargestFirst,
"random" => PartitionOrder::Random,
"ascending" => {
return Err(
"`ascending`: partition order sorts key on partition SIZE, \
not ordinal position (position order is always the \
generation order — that's `unchanged`). Spell it \
`smallest_first`"
.into(),
);
}
"descending" => {
return Err(
"`descending`: partition order sorts key on partition SIZE, \
not ordinal position (position order is always the \
generation order — that's `unchanged`). Spell it \
`largest_first`"
.into(),
);
}
other => {
return Err(format!(
"unknown order `{other}` — supported: unchanged, \
smallest_first, largest_first, random"
));
}
};
tokens.pop();
}
}
let in_positions: Vec<usize> = tokens
.iter()
.enumerate()
.filter_map(|(i, t)| (*t == "in").then_some(i))
.collect();
let (chunk_tokens, window_tokens): (&[&str], Option<&[&str]>) = match in_positions.as_slice() {
[] => (&tokens[..], None),
[i] => {
if *i == 0 {
return Err(format!("`in` without a chunking spec before it: `{input}`"));
}
if *i == tokens.len() - 1 {
return Err(format!("`in` without a window range after it: `{input}`"));
}
(&tokens[..*i], Some(&tokens[*i + 1..]))
}
_ => {
return Err(format!(
"at most one `in <window>` clause is allowed: `{input}`"
));
}
};
let window = match window_tokens {
None => None,
Some(wt) => Some(parse_window(&clean_part(wt), input)?),
};
let chunking = parse_chunking(&clean_part(chunk_tokens), input)?;
Ok(PartitionSpec { chunking, window, order })
}
fn clean_part(tokens: &[&str]) -> String {
tokens
.concat()
.chars()
.filter(|c| !matches!(c, '[' | ']' | '(' | ')'))
.collect()
}
fn parse_window(cleaned: &str, input: &str) -> Result<(Bound, Bound), String> {
let Some((lhs, rhs)) = split_range(cleaned) else {
return Err(format!(
"the window after `in` must be a `start..end` range; got `{cleaned}` in `{input}`"
));
};
let start = parse_bound(lhs)?;
let end = parse_bound(rhs)?;
if !start.is_sized() || !end.is_sized() {
return Err(format!(
"window endpoints must be sized values (percentage, fraction, or \
ordinal); got `{cleaned}` in `{input}`"
));
}
Ok((start, end))
}
fn parse_chunking(cleaned: &str, input: &str) -> Result<Chunking, String> {
if cleaned.is_empty() {
return Err(format!("empty spec: `{input}`"));
}
if let Some((name, args)) = split_recipe(cleaned) {
let deltas = normalise_to_pct(&expand_recipe_weights(name, args)?)?;
return Ok(Chunking::DeltaList { deltas });
}
if cleaned == "..." {
return Err(
"the fill token `...` repeats the preceding delta until the extent \
is used up; it needs at least one delta before it (e.g. `1%,...`)"
.into(),
);
}
if cleaned.starts_with("*/") || cleaned.contains(",*/") {
return parse_delta_list(cleaned, input);
}
if cleaned.contains(',') {
return parse_delta_list(cleaned, input);
}
if let Some((lhs, rhs)) = split_range(cleaned) {
let start = parse_bound(lhs)?;
let end = parse_bound(rhs)?;
if !start.is_sized() || !end.is_sized() {
return Err(format!(
"`*`, `...`, `~`, and `*/N` are only valid inside a comma-separated \
delta list, not a `..` range; got `{input}`"
));
}
return Ok(Chunking::SingleRange { start, end });
}
parse_delta_list(cleaned, input)
}
fn parse_delta_list(cleaned: &str, input: &str) -> Result<Chunking, String> {
let (head, star_tail) = if let Some(rest) = cleaned.strip_prefix("*/") {
("", Some(rest))
} else if let Some(pos) = cleaned.find(",*/") {
(&cleaned[..pos], Some(&cleaned[pos + 3..]))
} else {
(cleaned, None)
};
let mut deltas: Vec<Bound> = Vec::new();
if !head.is_empty() {
for entry in head.split(',') {
if entry.is_empty() {
return Err(format!("empty entry in delta list: `{input}`"));
}
deltas.extend(parse_delta_entry(entry)?);
}
} else if star_tail.is_none() {
return Err(format!("empty spec: `{input}`"));
}
if let Some(tail) = star_tail {
deltas.push(parse_star_tail(tail)?);
}
let tail_count = deltas.iter().filter(|b| b.is_tail()).count();
if tail_count > 1 {
return Err(format!(
"at most one remainder token (`*`, `...`, `*/N`, or `*/recipe`) is \
allowed in a delta list; got {tail_count} in `{input}`"
));
}
if let Some(pos) = deltas
.iter()
.position(|b| matches!(b, Bound::Fill | Bound::StarSplit(_) | Bound::StarShaped(_)))
{
if pos != deltas.len() - 1 {
return Err(format!(
"`{}` consumes the rest of the extent and must be the last entry \
in the delta list; got `{input}`",
deltas[pos]
));
}
if matches!(deltas[pos], Bound::Fill) {
if pos == 0 {
return Err(
"the fill token `...` repeats the preceding delta until the extent \
is used up; it needs at least one delta before it (e.g. `1%,...`)"
.into(),
);
}
if matches!(deltas[pos - 1], Bound::Gap(_)) {
return Err(format!(
"`...` after a gap would emit nothing — the fill token repeats \
the immediately preceding delta. Put a sized delta before `...`, \
in `{input}`"
));
}
}
}
if !deltas.iter().any(|b| b.is_sized() || b.is_tail()) {
return Err(format!(
"spec emits no partitions — every entry is a gap: `{input}`"
));
}
Ok(Chunking::DeltaList { deltas })
}
fn parse_delta_entry(raw: &str) -> Result<Vec<Bound>, String> {
if let Some(rest) = raw.strip_prefix('~') {
if let Some((_, rep)) = rest.split_once('x')
&& !rep.is_empty() && rep.chars().all(|c| c.is_ascii_digit()) {
return Err(format!(
"`~{rest}`: repetition does not apply to gaps — size the gap \
directly (adjacent gaps are one gap)"
));
}
let inner = parse_bound(rest)?;
if !inner.is_sized() {
return Err(format!(
"`~{rest}`: a gap requires a sized value (percentage, fraction, or \
ordinal). To ignore the trailing remainder, just end the list \
without a tail token — under-summing lists drop the gap"
));
}
return Ok(vec![Bound::Gap(Box::new(inner))]);
}
if let Some((lhs, rhs)) = raw.split_once('x')
&& !lhs.is_empty() && !rhs.is_empty() && rhs.chars().all(|c| c.is_ascii_digit()) {
let n: u64 = rhs
.parse()
.map_err(|_| format!("invalid repetition count in `{raw}`"))?;
if n == 0 {
return Err(format!(
"`{raw}`: the repetition count must be >= 1"
));
}
let b = parse_bound(lhs)?;
if !b.is_sized() {
return Err(format!(
"`{raw}`: repetition applies to sized deltas (percentage, \
fraction, or ordinal) only"
));
}
return Ok(vec![b; n as usize]);
}
Ok(vec![parse_bound(raw)?])
}
fn parse_star_tail(divisor: &str) -> Result<Bound, String> {
if !divisor.contains(':') && divisor.contains(',') {
let count = divisor.split(',').next().unwrap_or(divisor);
return Err(format!(
"`*/{count}` consumes the rest of the extent and must be the last \
entry in the delta list; got trailing entries after it"
));
}
if let Some((name, args)) = split_recipe(divisor) {
if name == "linear" {
return Err(format!(
"`*/linear:{args}`: spell an equal-count remainder split as \
`*/{args}` — `*/N` is the canonical form"
));
}
let weights = normalise_weights(&expand_recipe_weights(name, args)?)?;
return Ok(Bound::StarShaped(weights));
}
if divisor.contains('%') || divisor.contains('.') {
return Err(format!(
"`*/{divisor}`: the divisor after `*/` is a chunk count and must be a \
bare integer (e.g. `*/10` = remainder in 10 equal chunks). For \
fixed-size chunks repeated until the extent is used up, spell the \
size as a delta followed by the fill token: `{divisor},...`"
));
}
let n: u64 = divisor.parse().map_err(|_| {
format!("invalid remainder split `*/{divisor}`: expected `*/N` with integer N >= 1, or `*/recipe:args`")
})?;
if n == 0 {
return Err("`*/0`: the remainder split count must be >= 1".into());
}
Ok(Bound::StarSplit(n))
}
fn split_recipe(s: &str) -> Option<(&str, &str)> {
let colon = s.find(':')?;
let name = &s[..colon];
if name.is_empty() {
return None;
}
if !name.chars().all(|c| c.is_ascii_alphabetic() || c == '_') {
return None;
}
Some((name, &s[colon + 1..]))
}
fn split_range(s: &str) -> Option<(&str, &str)> {
s.find("..").map(|idx| (&s[..idx], &s[idx + 2..]))
}
fn parse_bound(raw: &str) -> Result<Bound, String> {
let s = raw.trim();
if s.is_empty() {
return Err("empty bound".into());
}
if s == "..." {
return Ok(Bound::Fill);
}
if s == "*" || s == "*%" {
return Ok(Bound::Star);
}
if let Some(num) = s.strip_suffix('%') {
let value: f64 = num
.trim()
.parse()
.map_err(|_| format!("invalid percentage `{raw}`: expected a number before `%`"))?;
if !(0.0..=100.0).contains(&value) {
return Err(format!(
"percentage `{raw}` out of range — must be in [0%, 100%]"
));
}
return Ok(Bound::Pct(value));
}
if s.contains('.') {
let value: f64 = s
.parse()
.map_err(|_| format!("invalid decimal `{raw}`"))?;
if !(0.0..=1.0).contains(&value) {
return Err(format!(
"decimal `{raw}` is ambiguous — fractions must be in [0.0, 1.0]; \
did you mean `{}%` (percentage), `0.0{}` (fraction), or `{}` (literal ordinal)?",
value, raw.replace('.', ""), raw.replace('.', ""),
));
}
return Ok(Bound::Frac(value));
}
let value: u64 = s
.parse()
.map_err(|_| format!("invalid number `{raw}`: expected an integer ordinal, decimal fraction (0.x), or `N%` percentage"))?;
Ok(Bound::Ord(value))
}
fn expand_recipe_weights(name: &str, args: &str) -> Result<Vec<f64>, String> {
let parts: Vec<&str> = args.split(',').map(|s| s.trim()).collect();
let weights = match name {
"linear" => recipe_linear(&parts)?,
"ratios" => recipe_ratios(&parts)?,
"mul" => recipe_mul(&parts)?,
"bin" => recipe_bin(&parts)?,
"fib" => recipe_fib(&parts)?,
"ln" => recipe_ln(&parts)?,
"geom" => recipe_geom(&parts)?,
"zipf" => recipe_zipf(&parts)?,
"pareto" => recipe_pareto(&parts)?,
"front_heavy" => recipe_front_heavy(&parts)?,
"back_heavy" => recipe_back_heavy(&parts)?,
_ => {
return Err(format!(
"unknown recipe `{name}` — supported: linear, ratios, mul, bin, fib, ln, \
geom, zipf, pareto, front_heavy, back_heavy"
));
}
};
Ok(weights)
}
fn parse_u64_arg(arg: &str, ctx: &str) -> Result<u64, String> {
arg.parse()
.map_err(|_| format!("invalid integer arg `{arg}` for {ctx}"))
}
fn parse_f64_arg(arg: &str, ctx: &str) -> Result<f64, String> {
arg.parse()
.map_err(|_| format!("invalid number arg `{arg}` for {ctx}"))
}
fn recipe_linear(args: &[&str]) -> Result<Vec<f64>, String> {
if args.len() != 1 {
return Err(format!(
"linear:N expects exactly 1 argument (the partition count); got {}",
args.len()
));
}
let n = parse_u64_arg(args[0], "linear")?;
if n == 0 {
return Err("linear:N requires N >= 1".into());
}
Ok(vec![1.0; n as usize])
}
fn recipe_ratios(args: &[&str]) -> Result<Vec<f64>, String> {
if args.is_empty() {
return Err("ratios:a,b,c,... requires at least one weight".into());
}
args.iter()
.map(|a| parse_f64_arg(a, "ratios"))
.collect()
}
fn recipe_mul(args: &[&str]) -> Result<Vec<f64>, String> {
let (start, ratio) = match args.len() {
1 => (1.0, parse_f64_arg(args[0], "mul")?),
2 => (parse_f64_arg(args[0], "mul")?, parse_f64_arg(args[1], "mul")?),
n => return Err(format!("mul:R or mul:S,R expects 1 or 2 arguments; got {n}")),
};
if start <= 0.0 {
return Err(format!("mul:S,R requires S > 0; got {start}"));
}
if ratio <= 0.0 {
return Err(format!("mul:R requires R > 0; got {ratio}"));
}
const HARD_CAP: usize = 64;
let mut weights = Vec::with_capacity(HARD_CAP);
let mut current = start;
for _ in 0..HARD_CAP {
if !current.is_finite() || current <= 0.0 {
break;
}
weights.push(current);
if ratio < 1.0 && current < start * 0.001 {
break;
}
current *= ratio;
if ratio >= 1.0 && current >= start * 1000.0 {
if current.is_finite() {
weights.push(current);
}
break;
}
}
if weights.is_empty() {
return Err(format!("mul:{start},{ratio} produced no terms — pick a larger start"));
}
Ok(weights)
}
fn recipe_bin(args: &[&str]) -> Result<Vec<f64>, String> {
if args.len() != 1 {
return Err(format!(
"bin:N expects exactly 1 argument (the term count); got {}",
args.len()
));
}
let n = parse_u64_arg(args[0], "bin")?;
if n == 0 {
return Err("bin:N requires N >= 1".into());
}
let degree = n - 1;
let mut coeffs = vec![1.0f64; n as usize];
for k in 1..=degree {
coeffs[k as usize] = coeffs[(k - 1) as usize] * ((degree - k + 1) as f64) / (k as f64);
}
Ok(coeffs)
}
fn recipe_fib(args: &[&str]) -> Result<Vec<f64>, String> {
if args.len() != 1 {
return Err(format!(
"fib:N expects exactly 1 argument (the term count); got {}",
args.len()
));
}
let n = parse_u64_arg(args[0], "fib")?;
if n == 0 {
return Err("fib:N requires N >= 1".into());
}
let mut weights = Vec::with_capacity(n as usize);
let (mut a, mut b) = (1u64, 2u64);
for _ in 0..n {
weights.push(a as f64);
let next = a.saturating_add(b);
a = b;
b = next;
}
Ok(weights)
}
fn recipe_ln(args: &[&str]) -> Result<Vec<f64>, String> {
if args.len() != 1 {
return Err(format!(
"ln:N expects exactly 1 argument (the term count); got {}",
args.len()
));
}
let n = parse_u64_arg(args[0], "ln")?;
if n == 0 {
return Err("ln:N requires N >= 1".into());
}
Ok((1..=n).map(|i| (1.0 + i as f64).ln()).collect())
}
fn recipe_geom(args: &[&str]) -> Result<Vec<f64>, String> {
if args.len() != 2 {
return Err(format!(
"geom:N,R expects exactly 2 arguments; got {}",
args.len()
));
}
let n = parse_u64_arg(args[0], "geom")?;
let r = parse_f64_arg(args[1], "geom")?;
if n == 0 {
return Err("geom:N,R requires N >= 1".into());
}
if r <= 0.0 {
return Err(format!("geom:N,R requires R > 0; got {r}"));
}
let mut weights = Vec::with_capacity(n as usize);
let mut current = 1.0;
for _ in 0..n {
weights.push(current);
current *= r;
}
Ok(weights)
}
fn recipe_zipf(args: &[&str]) -> Result<Vec<f64>, String> {
if args.len() != 2 {
return Err(format!(
"zipf:s,N expects exactly 2 arguments; got {}",
args.len()
));
}
let s = parse_f64_arg(args[0], "zipf")?;
let n = parse_u64_arg(args[1], "zipf")?;
if s <= 0.0 {
return Err(format!("zipf:s,N requires s > 0; got {s}"));
}
if n == 0 {
return Err("zipf:s,N requires N >= 1".into());
}
Ok((1..=n).map(|i| 1.0 / (i as f64).powf(s)).collect())
}
fn recipe_pareto(args: &[&str]) -> Result<Vec<f64>, String> {
if args.len() != 2 {
return Err(format!(
"pareto:alpha,N expects exactly 2 arguments; got {}",
args.len()
));
}
let alpha = parse_f64_arg(args[0], "pareto")?;
let n = parse_u64_arg(args[1], "pareto")?;
if alpha <= 0.0 {
return Err(format!("pareto:alpha,N requires alpha > 0; got {alpha}"));
}
if n == 0 {
return Err("pareto:alpha,N requires N >= 1".into());
}
Ok((1..=n).map(|i| (1.0 / i as f64).powf(alpha)).collect())
}
fn recipe_front_heavy(args: &[&str]) -> Result<Vec<f64>, String> {
if args.len() != 1 {
return Err(format!(
"front_heavy:N expects exactly 1 argument; got {}",
args.len()
));
}
let n = parse_u64_arg(args[0], "front_heavy")?;
if n == 0 {
return Err("front_heavy:N requires N >= 1".into());
}
Ok((1..=n).rev().map(|i| i as f64).collect())
}
fn recipe_back_heavy(args: &[&str]) -> Result<Vec<f64>, String> {
if args.len() != 1 {
return Err(format!(
"back_heavy:N expects exactly 1 argument; got {}",
args.len()
));
}
let n = parse_u64_arg(args[0], "back_heavy")?;
if n == 0 {
return Err("back_heavy:N requires N >= 1".into());
}
Ok((1..=n).map(|i| i as f64).collect())
}
fn normalise_weights(weights: &[f64]) -> Result<Vec<f64>, String> {
if weights.iter().any(|w| !w.is_finite() || *w < 0.0) {
return Err("recipe produced non-finite or negative weights".into());
}
let sum: f64 = weights.iter().sum();
if sum <= 0.0 {
return Err("recipe produced zero total weight".into());
}
Ok(weights.iter().map(|w| w / sum * 100.0).collect())
}
fn normalise_to_pct(weights: &[f64]) -> Result<Vec<Bound>, String> {
Ok(normalise_weights(weights)?.into_iter().map(Bound::Pct).collect())
}
pub fn resolve(
spec: &PartitionSpec,
base_start: u64,
base_end: u64,
) -> Result<Vec<Partition>, String> {
if base_end < base_start {
return Err(format!(
"resolve: base_end ({base_end}) < base_start ({base_start})"
));
}
let base_extent = base_end - base_start;
let (dom_start, dom_end) = match &spec.window {
None => (base_start, base_end),
Some((ws, we)) => {
let s = ws
.resolve_against(base_start, base_end)
.expect("window bounds are sized (checked at parse time)");
let e = we
.resolve_against(base_start, base_end)
.expect("window bounds are sized (checked at parse time)");
if e < s {
return Err(format!(
"window `in {ws}..{we}` is empty or reversed against \
base=[{base_start}..{base_end}): start={s}, end={e}"
));
}
(s, e)
}
};
let dom_extent = dom_end - dom_start;
let frame = Frame { base_start, base_extent };
let mut partitions = match &spec.chunking {
Chunking::SingleRange { start, end } => {
let start_ord = start
.resolve_against(dom_start, dom_end)
.expect("tail tokens not allowed in SingleRange (checked at parse time)");
let end_ord = end
.resolve_against(dom_start, dom_end)
.expect("tail tokens not allowed in SingleRange (checked at parse time)");
if end_ord < start_ord {
return Err(format!(
"resolved range is empty or reversed: start={start_ord}, end={end_ord} \
(spec start={start}, end={end}, base=[{dom_start}..{dom_end}))"
));
}
if start_ord == end_ord {
return Err(format!(
"range `{start}..{end}` resolves to zero ordinals \
([{start_ord}..{end_ord}) against base=[{dom_start}..{dom_end})) — \
the slice rounds to nothing at this extent; widen the range \
or use a larger extent"
));
}
vec![frame.partition(0, start_ord, end_ord)]
}
Chunking::DeltaList { deltas } => {
resolve_delta_list(deltas, dom_start, dom_end, dom_extent, frame)?
}
};
let count = partitions.len() as u64;
for p in &mut partitions {
p.count = count;
}
apply_order(&mut partitions, spec);
Ok(partitions)
}
#[derive(Clone, Copy)]
struct Frame {
base_start: u64,
base_extent: u64,
}
impl Frame {
fn partition(&self, idx: u64, start_ord: u64, end_ord: u64) -> Partition {
Partition {
count: 0,
idx,
start_ord,
end_ord,
start_pct: pct_of(start_ord, self.base_start, self.base_extent),
end_pct: pct_of(end_ord, self.base_start, self.base_extent),
base_extent: self.base_extent,
}
}
}
fn apply_order(partitions: &mut [Partition], spec: &PartitionSpec) {
match spec.order {
PartitionOrder::Unchanged => {}
PartitionOrder::SmallestFirst => {
partitions.sort_by_key(|p| p.cardinality());
}
PartitionOrder::LargestFirst => {
partitions.sort_by_key(|p| std::cmp::Reverse(p.cardinality()));
}
PartitionOrder::Random => {
let mut state = xxhash_rust::xxh3::xxh3_64(format!("{spec:?}").as_bytes());
for i in (1..partitions.len()).rev() {
let j = (splitmix64(&mut state) % (i as u64 + 1)) as usize;
partitions.swap(i, j);
}
}
}
}
fn splitmix64(state: &mut u64) -> u64 {
*state = state.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = *state;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
fn resolve_delta_list(
deltas: &[Bound],
dom_start: u64,
dom_end: u64,
extent: u64,
frame: Frame,
) -> Result<Vec<Partition>, String> {
let non_tail_exact: f64 = deltas
.iter()
.filter(|b| !b.is_tail())
.map(|b| delta_exact_ordinals(b, extent))
.sum();
let tolerance = 1e-6 * (extent as f64).max(1.0);
if non_tail_exact > extent as f64 + tolerance {
return Err(format!(
"delta list sums to {} ordinals, exceeding the cursor's extent {extent}; \
trim the list or use a `*` remainder to absorb the overflow",
non_tail_exact.round() as u64
));
}
let mut partitions: Vec<Partition> = Vec::with_capacity(deltas.len());
let mut cursor = dom_start;
let mut exact_pos = 0.0f64;
let push = |partitions: &mut Vec<Partition>, start: u64, end: u64| {
let idx = partitions.len() as u64;
partitions.push(frame.partition(idx, start, end));
};
let boundary = |exact_pos: f64| -> u64 {
(dom_start + exact_pos.round() as u64).min(dom_end)
};
for (i, delta) in deltas.iter().enumerate() {
match delta {
Bound::Star => {
exact_pos += extent as f64 - non_tail_exact;
let next = boundary(exact_pos);
push(&mut partitions, cursor, next);
cursor = next;
}
Bound::Fill => {
let chunk = delta_exact_ordinals(&deltas[i - 1], extent);
if chunk < 1.0 {
return Err(format!(
"fill token `...` would repeat a delta of less than one \
ordinal (`{}` resolves to {chunk:.3} ordinals against \
extent {extent})",
deltas[i - 1]
));
}
while cursor < dom_end {
exact_pos += chunk;
let next = boundary(exact_pos);
push(&mut partitions, cursor, next);
cursor = next;
}
}
Bound::StarSplit(n) => {
let remainder = dom_end - cursor;
if remainder == 0 {
return Err(format!(
"`*/{n}` has no remainder to divide — the preceding deltas \
already cover the extent {extent}"
));
}
if *n > remainder {
return Err(format!(
"`*/{n}` cannot divide a remainder of {remainder} ordinals \
into {n} non-empty partitions"
));
}
for (s, e) in split_evenly(cursor, dom_end, *n) {
push(&mut partitions, s, e);
}
cursor = dom_end;
}
Bound::StarShaped(weights) => {
let remainder = dom_end - cursor;
if remainder == 0 {
return Err(format!(
"`*/<recipe>` has no remainder to divide — the preceding \
deltas already cover the extent {extent}"
));
}
let start = cursor;
let mut cum = 0.0f64;
for w in weights {
cum += w;
let next = (start + ((cum / 100.0) * remainder as f64).round() as u64)
.min(dom_end);
if next == cursor {
return Err(format!(
"`*/<recipe>` produces an empty partition — weight \
{w:.3}% of a {remainder}-ordinal remainder rounds to \
zero ordinals; use fewer/coarser weights or a larger \
remainder"
));
}
push(&mut partitions, cursor, next);
cursor = next;
}
exact_pos += remainder as f64;
}
Bound::Gap(inner) => {
exact_pos += delta_exact_ordinals(inner, extent);
cursor = boundary(exact_pos);
}
other => {
exact_pos += delta_exact_ordinals(other, extent);
let next = boundary(exact_pos);
push(&mut partitions, cursor, next);
cursor = next;
}
}
}
debug_assert!(cursor <= dom_end);
Ok(partitions)
}
fn delta_exact_ordinals(b: &Bound, extent: u64) -> f64 {
match b {
Bound::Pct(p) => (p / 100.0) * extent as f64,
Bound::Frac(f) => f * extent as f64,
Bound::Ord(o) => *o as f64,
Bound::Gap(inner) => delta_exact_ordinals(inner, extent),
Bound::Star | Bound::Fill | Bound::StarSplit(_) | Bound::StarShaped(_) => {
unreachable!("tail tokens handled separately")
}
}
}
pub fn subdivide_partition(p: &Partition, n: u64) -> Result<Vec<Partition>, String> {
let card = p.cardinality();
if n == 0 {
return Err("subdivide(p, 0): the sub-partition count must be >= 1".into());
}
if n > card {
return Err(format!(
"subdivide(p, {n}): cannot divide partition #{} of {card} ordinals \
into {n} non-empty sub-partitions",
p.idx
));
}
let pct_at = |ord: u64| -> f64 {
p.start_pct
+ (ord - p.start_ord) as f64 / card as f64 * (p.end_pct - p.start_pct)
};
Ok(split_evenly(p.start_ord, p.end_ord, n)
.into_iter()
.enumerate()
.map(|(i, (start_ord, end_ord))| Partition {
idx: i as u64,
count: n,
start_ord,
end_ord,
start_pct: pct_at(start_ord),
end_pct: pct_at(end_ord),
base_extent: p.base_extent,
})
.collect())
}
pub fn split_evenly(start_ord: u64, end_ord: u64, n: u64) -> Vec<(u64, u64)> {
debug_assert!(n >= 1, "split_evenly requires n >= 1");
debug_assert!(end_ord >= start_ord);
let span = (end_ord - start_ord) as u128;
let n_wide = n as u128;
let boundary = |i: u64| -> u64 {
start_ord + ((i as u128 * span + n_wide / 2) / n_wide) as u64
};
(0..n).map(|i| (boundary(i), boundary(i + 1))).collect()
}
#[inline]
fn pct_of(ordinal: u64, base_start: u64, extent: u64) -> f64 {
if extent == 0 {
0.0
} else {
(ordinal - base_start) as f64 * 100.0 / extent as f64
}
}
impl ReflectedValue for Partition {
fn type_name(&self) -> &str { "Partition" }
fn display(&self) -> String {
format!(
"Partition({}/{} [{}..{}) [{:.2}%..{:.2}%))",
self.idx, self.count,
self.start_ord, self.end_ord, self.start_pct, self.end_pct,
)
}
fn to_json_value(&self) -> serde_json::Value {
serde_json::json!({
"idx": self.idx,
"count": self.count,
"start_ord": self.start_ord,
"end_ord": self.end_ord,
"start_pct": self.start_pct,
"end_pct": self.end_pct,
"base_extent": self.base_extent,
"cardinality": self.cardinality(),
})
}
fn as_any(&self) -> &dyn std::any::Any { self }
fn clone_reflected(&self) -> Box<dyn ReflectedValue> {
Box::new(*self)
}
}
impl ReflectedValue for PartitionSpec {
fn type_name(&self) -> &str { "PartitionSpec" }
fn display(&self) -> String {
let chunking = match &self.chunking {
Chunking::SingleRange { start, end } => format!("{start}..{end}"),
Chunking::DeltaList { deltas } => {
let parts: Vec<String> = deltas.iter().map(|b| b.to_string()).collect();
parts.join(",")
}
};
let window = match &self.window {
Some((s, e)) => format!(" in {s}..{e}"),
None => String::new(),
};
let order = match self.order {
PartitionOrder::Unchanged => String::new(),
o => format!(" {o}"),
};
format!("PartitionSpec({chunking}{window}{order})")
}
fn to_json_value(&self) -> serde_json::Value {
serde_json::Value::String(self.display())
}
fn as_any(&self) -> &dyn std::any::Any { self }
fn clone_reflected(&self) -> Box<dyn ReflectedValue> {
Box::new(self.clone())
}
}
#[derive(Debug, Clone)]
pub struct PartitionList(pub Arc<Vec<Partition>>);
impl PartitionList {
pub fn new(partitions: Vec<Partition>) -> Self {
Self(Arc::new(partitions))
}
pub fn len(&self) -> usize { self.0.len() }
pub fn is_empty(&self) -> bool { self.0.is_empty() }
pub fn as_slice(&self) -> &[Partition] { &self.0 }
}
impl ReflectedValue for PartitionList {
fn type_name(&self) -> &str { "PartitionList" }
fn display(&self) -> String {
let parts: Vec<String> = self.0.iter().map(|p| {
format!("[{}..{})", p.start_ord, p.end_ord)
}).collect();
format!("PartitionList[{}]={}", self.0.len(), parts.join(","))
}
fn to_json_value(&self) -> serde_json::Value {
serde_json::Value::Array(self.0.iter().map(|p| p.to_json_value()).collect())
}
fn as_any(&self) -> &dyn std::any::Any { self }
fn clone_reflected(&self) -> Box<dyn ReflectedValue> {
Box::new(self.clone())
}
}
impl Value {
pub fn from_partition(p: Partition) -> Self {
Value::Ext(Box::new(p))
}
pub fn from_partition_spec(s: PartitionSpec) -> Self {
Value::Ext(Box::new(s))
}
pub fn from_partition_list(parts: Vec<Partition>) -> Self {
Value::Ext(Box::new(PartitionList::new(parts)))
}
pub fn as_partition(&self) -> Option<&Partition> {
match self {
Value::Ext(b) => b.as_any().downcast_ref::<Partition>(),
_ => None,
}
}
pub fn as_partition_spec(&self) -> Option<&PartitionSpec> {
match self {
Value::Ext(b) => b.as_any().downcast_ref::<PartitionSpec>(),
_ => None,
}
}
pub fn as_partition_list(&self) -> Option<&PartitionList> {
match self {
Value::Ext(b) => b.as_any().downcast_ref::<PartitionList>(),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_bound_percentage() {
assert_eq!(parse_bound("53%").unwrap(), Bound::Pct(53.0));
assert_eq!(parse_bound("0%").unwrap(), Bound::Pct(0.0));
assert_eq!(parse_bound("100%").unwrap(), Bound::Pct(100.0));
assert_eq!(parse_bound("0.5%").unwrap(), Bound::Pct(0.5));
}
#[test]
fn parse_bound_percentage_out_of_range_rejected() {
assert!(parse_bound("101%").is_err());
assert!(parse_bound("-1%").is_err());
}
#[test]
fn parse_bound_fraction() {
assert_eq!(parse_bound("0.5").unwrap(), Bound::Frac(0.5));
assert_eq!(parse_bound("0.0").unwrap(), Bound::Frac(0.0));
assert_eq!(parse_bound("1.0").unwrap(), Bound::Frac(1.0));
assert_eq!(parse_bound("0.123").unwrap(), Bound::Frac(0.123));
}
#[test]
fn parse_bound_fraction_out_of_range_rejected() {
let err = parse_bound("1.5").unwrap_err();
assert!(err.contains("ambiguous"), "diagnostic should explain: {err}");
}
#[test]
fn parse_bound_literal_ordinal() {
assert_eq!(parse_bound("0").unwrap(), Bound::Ord(0));
assert_eq!(parse_bound("100").unwrap(), Bound::Ord(100));
assert_eq!(parse_bound("999999").unwrap(), Bound::Ord(999_999));
}
#[test]
fn parse_bound_star_token() {
assert_eq!(parse_bound("*").unwrap(), Bound::Star);
assert_eq!(parse_bound("*%").unwrap(), Bound::Star);
}
#[test]
fn parse_form1_simple_pct() {
let spec = parse("0..53%").unwrap();
assert_eq!(
spec,
PartitionSpec::single_range(Bound::Ord(0), Bound::Pct(53.0))
);
}
#[test]
fn parse_form1_brackets_tolerated() {
let canonical = PartitionSpec::single_range(Bound::Ord(0), Bound::Pct(53.0));
assert_eq!(parse("[0..53%]").unwrap(), canonical);
assert_eq!(parse("[0..53%)").unwrap(), canonical);
assert_eq!(parse("(0..53%]").unwrap(), canonical);
}
#[test]
fn parse_form1_fraction_form() {
let spec = parse("0..0.53").unwrap();
assert_eq!(
spec,
PartitionSpec::single_range(Bound::Ord(0), Bound::Frac(0.53))
);
}
#[test]
fn parse_form1_literal_ordinals() {
let spec = parse("100..1000").unwrap();
assert_eq!(
spec,
PartitionSpec::single_range(Bound::Ord(100), Bound::Ord(1000))
);
}
#[test]
fn parse_form1_mixed_literal_and_pct() {
let spec = parse("100..50%").unwrap();
assert_eq!(
spec,
PartitionSpec::single_range(Bound::Ord(100), Bound::Pct(50.0))
);
}
#[test]
fn parse_form1_mixed_frac_and_literal() {
let spec = parse("0.10..10000").unwrap();
assert_eq!(
spec,
PartitionSpec::single_range(Bound::Frac(0.10), Bound::Ord(10000))
);
}
#[test]
fn parse_form1_rejects_star() {
assert!(parse("0..*").is_err());
assert!(parse("*..50%").is_err());
}
#[test]
fn parse_form2_with_star() {
let spec = parse("2%,10%,*%").unwrap();
assert_eq!(
spec,
PartitionSpec::delta_list(vec![Bound::Pct(2.0), Bound::Pct(10.0), Bound::Star])
);
}
#[test]
fn parse_form2_fraction_equivalent() {
let spec = parse("0.02,0.10,*").unwrap();
assert_eq!(
spec,
PartitionSpec::delta_list(vec![Bound::Frac(0.02), Bound::Frac(0.10), Bound::Star])
);
}
#[test]
fn parse_form2_literal_deltas() {
let spec = parse("1000,5000,*").unwrap();
assert_eq!(
spec,
PartitionSpec::delta_list(vec![Bound::Ord(1000), Bound::Ord(5000), Bound::Star])
);
}
#[test]
fn parse_form2_mixed_entries() {
let spec = parse("1000,10%,*").unwrap();
assert_eq!(
spec,
PartitionSpec::delta_list(vec![Bound::Ord(1000), Bound::Pct(10.0), Bound::Star])
);
}
#[test]
fn parse_form2_short_list_no_star() {
let spec = parse("20%,30%").unwrap();
assert_eq!(
spec,
PartitionSpec::delta_list(vec![Bound::Pct(20.0), Bound::Pct(30.0)])
);
}
#[test]
fn parse_form2_rejects_multiple_stars() {
let err = parse("*,*").unwrap_err();
assert!(err.contains("at most one"), "diagnostic: {err}");
}
#[test]
fn parse_form2_fill_token() {
let spec = parse("90%,1%,...").unwrap();
assert_eq!(
spec,
PartitionSpec::delta_list(vec![Bound::Pct(90.0), Bound::Pct(1.0), Bound::Fill])
);
}
#[test]
fn parse_form2_star_split_token() {
let spec = parse("90%,*/10").unwrap();
assert_eq!(
spec,
PartitionSpec::delta_list(vec![Bound::Pct(90.0), Bound::StarSplit(10)])
);
}
#[test]
fn parse_star_split_alone_is_whole_extent_split() {
let spec = parse("*/16").unwrap();
assert_eq!(
spec,
PartitionSpec::delta_list(vec![Bound::StarSplit(16)])
);
}
#[test]
fn parse_fill_alone_rejected_with_hint() {
let err = parse("...").unwrap_err();
assert!(err.contains("preceding delta") || err.contains("before it"), "diagnostic: {err}");
}
#[test]
fn parse_fill_first_in_list_rejected() {
let err = parse("...,10%").unwrap_err();
assert!(err.contains("before it") || err.contains("last entry"), "diagnostic: {err}");
}
#[test]
fn parse_fill_not_last_rejected() {
let err = parse("1%,...,10%").unwrap_err();
assert!(err.contains("last entry"), "diagnostic: {err}");
}
#[test]
fn parse_star_split_not_last_rejected() {
let err = parse("*/4,10%").unwrap_err();
assert!(err.contains("last entry"), "diagnostic: {err}");
}
#[test]
fn parse_rejects_mixed_tail_tokens() {
let err = parse("1%,*,...").unwrap_err();
assert!(err.contains("at most one"), "diagnostic: {err}");
let err = parse("1%,*,*/4").unwrap_err();
assert!(err.contains("at most one"), "diagnostic: {err}");
}
#[test]
fn parse_star_split_pct_divisor_rejected_with_teaching_hint() {
let err = parse("90%,*/1%").unwrap_err();
assert!(err.contains("chunk count"), "diagnostic: {err}");
assert!(err.contains("1%,..."), "diagnostic should teach the fill form: {err}");
let err = parse("90%,*/0.01").unwrap_err();
assert!(err.contains("chunk count"), "diagnostic: {err}");
}
#[test]
fn parse_star_split_zero_rejected() {
let err = parse("90%,*/0").unwrap_err();
assert!(err.contains(">= 1"), "diagnostic: {err}");
}
#[test]
fn parse_form1_rejects_tail_tokens() {
assert!(parse("0..*/4").is_err());
assert!(parse("0....").is_err());
}
fn deltas_only(spec: PartitionSpec) -> Vec<Bound> {
match spec.chunking {
Chunking::DeltaList { deltas } => deltas,
other => panic!("expected DeltaList, got {other:?}"),
}
}
fn pcts_of(spec: PartitionSpec) -> Vec<f64> {
deltas_only(spec)
.into_iter()
.map(|b| match b {
Bound::Pct(p) => p,
other => panic!("expected Pct, got {other:?}"),
})
.collect()
}
#[test]
fn recipe_linear_uniform_split() {
let pcts = pcts_of(parse("linear:4").unwrap());
assert_eq!(pcts.len(), 4);
for p in &pcts {
assert!((p - 25.0).abs() < 1e-9, "expected 25%, got {p}");
}
}
#[test]
fn recipe_ratios_normalises_weights() {
let pcts = pcts_of(parse("ratios:1,1,2").unwrap());
assert_eq!(pcts.len(), 3);
assert!((pcts[0] - 25.0).abs() < 1e-9);
assert!((pcts[1] - 25.0).abs() < 1e-9);
assert!((pcts[2] - 50.0).abs() < 1e-9);
}
#[test]
fn recipe_bin_5_is_five_terms_of_binomial_expansion() {
let pcts = pcts_of(parse("bin:5").unwrap());
assert_eq!(pcts.len(), 5);
let expected = [1.0 / 16.0, 4.0 / 16.0, 6.0 / 16.0, 4.0 / 16.0, 1.0 / 16.0];
for (i, e) in expected.iter().enumerate() {
assert!((pcts[i] - e * 100.0).abs() < 1e-9, "term {i}: {} vs {}", pcts[i], e * 100.0);
}
}
#[test]
fn recipe_fib_7_uses_distinct_fibonacci() {
let pcts = pcts_of(parse("fib:7").unwrap());
assert_eq!(pcts.len(), 7);
let expected_weights = [1.0, 2.0, 3.0, 5.0, 8.0, 13.0, 21.0];
let sum: f64 = expected_weights.iter().sum();
for (i, w) in expected_weights.iter().enumerate() {
assert!((pcts[i] - w / sum * 100.0).abs() < 1e-9);
}
}
#[test]
fn recipe_ln_5_log_spaced() {
let pcts = pcts_of(parse("ln:5").unwrap());
assert_eq!(pcts.len(), 5);
for i in 1..pcts.len() {
assert!(pcts[i] > pcts[i - 1], "ln:N should be monotonic");
}
let total: f64 = pcts.iter().sum();
assert!((total - 100.0).abs() < 1e-9, "total: {total}");
}
#[test]
fn recipe_mul_decay_tail_off() {
let pcts = pcts_of(parse("mul:0.5").unwrap());
assert!(!pcts.is_empty());
let total: f64 = pcts.iter().sum();
assert!((total - 100.0).abs() < 1e-9, "total: {total}");
assert!(pcts[0] > pcts[1]);
}
#[test]
fn recipe_mul_growth_caps_at_3_orders_of_magnitude() {
let pcts = pcts_of(parse("mul:2").unwrap());
assert!(!pcts.is_empty());
assert!(pcts.len() < 64, "should terminate well before hard cap");
let total: f64 = pcts.iter().sum();
assert!((total - 100.0).abs() < 1e-9, "total: {total}");
}
#[test]
fn recipe_mul_with_start_and_ratio() {
let pcts = pcts_of(parse("mul:5,0.5").unwrap());
let total: f64 = pcts.iter().sum();
assert!((total - 100.0).abs() < 1e-9, "total: {total}");
}
#[test]
fn recipe_geom_fixed_term_count() {
let pcts = pcts_of(parse("geom:5,2").unwrap());
assert_eq!(pcts.len(), 5);
let expected_total: f64 = 31.0;
let expected = [1.0, 2.0, 4.0, 8.0, 16.0];
for (i, e) in expected.iter().enumerate() {
assert!((pcts[i] - e / expected_total * 100.0).abs() < 1e-9);
}
}
#[test]
fn recipe_front_heavy_declining() {
let pcts = pcts_of(parse("front_heavy:4").unwrap());
assert_eq!(pcts.len(), 4);
for i in 1..pcts.len() {
assert!(pcts[i] < pcts[i - 1], "front_heavy should be monotonic-declining");
}
}
#[test]
fn recipe_back_heavy_growing() {
let pcts = pcts_of(parse("back_heavy:4").unwrap());
assert_eq!(pcts.len(), 4);
for i in 1..pcts.len() {
assert!(pcts[i] > pcts[i - 1], "back_heavy should be monotonic-growing");
}
}
#[test]
fn recipe_unknown_name_rejected() {
let err = parse("blorp:3").unwrap_err();
assert!(err.contains("unknown recipe"), "diagnostic: {err}");
assert!(err.contains("linear"), "should list supported recipes: {err}");
}
#[test]
fn resolve_form1_percentage_against_extent() {
let spec = parse("0..50%").unwrap();
let parts = resolve(&spec, 0, 1000).unwrap();
assert_eq!(parts.len(), 1);
assert_eq!(parts[0].start_ord, 0);
assert_eq!(parts[0].end_ord, 500);
assert_eq!(parts[0].cardinality(), 500);
}
#[test]
fn resolve_form1_literal_ordinals() {
let spec = parse("100..1000").unwrap();
let parts = resolve(&spec, 0, 10000).unwrap();
assert_eq!(parts[0].start_ord, 100);
assert_eq!(parts[0].end_ord, 1000);
assert_eq!(parts[0].cardinality(), 900);
}
#[test]
fn resolve_form1_mixed_literal_and_pct() {
let spec = parse("100..50%").unwrap();
let parts = resolve(&spec, 0, 1000).unwrap();
assert_eq!(parts[0].start_ord, 100);
assert_eq!(parts[0].end_ord, 500);
}
#[test]
fn resolve_form2_three_partition_pct_list() {
let spec = parse("2%,10%,*%").unwrap();
let parts = resolve(&spec, 0, 1000).unwrap();
assert_eq!(parts.len(), 3);
assert_eq!(parts[0].start_ord, 0);
assert_eq!(parts[0].end_ord, 20);
assert_eq!(parts[1].start_ord, 20);
assert_eq!(parts[1].end_ord, 120);
assert_eq!(parts[2].start_ord, 120);
assert_eq!(parts[2].end_ord, 1000);
assert_eq!(parts[2].cardinality(), 880);
}
#[test]
fn resolve_form2_literal_deltas() {
let spec = parse("1000,5000,*").unwrap();
let parts = resolve(&spec, 0, 10000).unwrap();
assert_eq!(parts.len(), 3);
assert_eq!(parts[0].start_ord, 0);
assert_eq!(parts[0].end_ord, 1000);
assert_eq!(parts[1].start_ord, 1000);
assert_eq!(parts[1].end_ord, 6000);
assert_eq!(parts[2].start_ord, 6000);
assert_eq!(parts[2].end_ord, 10000);
}
#[test]
fn resolve_form2_mixed_literal_and_pct_with_star() {
let spec = parse("1000,10%,*").unwrap();
let parts = resolve(&spec, 0, 10000).unwrap();
assert_eq!(parts.len(), 3);
assert_eq!(parts[0].cardinality(), 1000);
assert_eq!(parts[1].cardinality(), 1000); assert_eq!(parts[2].cardinality(), 8000); }
#[test]
fn resolve_form2_short_list_drops_trailing_gap() {
let spec = parse("20%,30%").unwrap();
let parts = resolve(&spec, 0, 1000).unwrap();
assert_eq!(parts.len(), 2);
assert_eq!(parts[0].end_ord, 200);
assert_eq!(parts[1].end_ord, 500); }
#[test]
fn resolve_rejects_over_extent_sum() {
let spec = parse("60%,60%").unwrap();
let err = resolve(&spec, 0, 1000).unwrap_err();
assert!(err.contains("exceeding"), "diagnostic: {err}");
}
#[test]
fn resolve_recipe_against_extent() {
let spec = parse("linear:4").unwrap();
let parts = resolve(&spec, 0, 1000).unwrap();
assert_eq!(parts.len(), 4);
for p in &parts {
assert_eq!(p.cardinality(), 250);
}
}
#[test]
fn resolve_partition_indices_assigned() {
let spec = parse("linear:5").unwrap();
let parts = resolve(&spec, 0, 1000).unwrap();
for (i, p) in parts.iter().enumerate() {
assert_eq!(p.idx, i as u64);
}
}
#[test]
fn resolve_partition_pcts_populated() {
let spec = parse("linear:4").unwrap();
let parts = resolve(&spec, 0, 1000).unwrap();
assert!((parts[0].start_pct - 0.0).abs() < 1e-9);
assert!((parts[0].end_pct - 25.0).abs() < 1e-9);
assert!((parts[3].end_pct - 100.0).abs() < 1e-9);
}
#[test]
fn resolve_fill_and_star_split_coincide_at_90_10() {
let explicit = resolve(
&parse("90%,1%,1%,1%,1%,1%,1%,1%,1%,1%,1%").unwrap(), 0, 1000).unwrap();
let filled = resolve(&parse("90%,1%,...").unwrap(), 0, 1000).unwrap();
let split = resolve(&parse("90%,*/10").unwrap(), 0, 1000).unwrap();
assert_eq!(explicit.len(), 11);
assert_eq!(filled, explicit);
assert_eq!(split, explicit);
assert_eq!(filled[0].cardinality(), 900);
for p in &filled[1..] {
assert_eq!(p.cardinality(), 10);
}
assert_eq!(filled[10].end_ord, 1000);
}
#[test]
fn resolve_fill_truncates_final_chunk() {
let parts = resolve(&parse("3,2,...").unwrap(), 0, 10).unwrap();
let bounds: Vec<(u64, u64)> =
parts.iter().map(|p| (p.start_ord, p.end_ord)).collect();
assert_eq!(bounds, vec![(0, 3), (3, 5), (5, 7), (7, 9), (9, 10)]);
}
#[test]
fn resolve_fill_with_nothing_left_adds_no_chunks() {
let parts = resolve(&parse("90%,10%,...").unwrap(), 0, 1000).unwrap();
assert_eq!(parts.len(), 2);
assert_eq!(parts[1].end_ord, 1000);
}
#[test]
fn resolve_fill_subordinal_chunk_rejected() {
let err = resolve(&parse("50%,0.01%,...").unwrap(), 0, 100).unwrap_err();
assert!(err.contains("less than one ordinal"), "diagnostic: {err}");
}
#[test]
fn resolve_pct_boundaries_round_at_cumulative_position() {
let parts = resolve(&parse("linear:3").unwrap(), 0, 1000).unwrap();
let bounds: Vec<(u64, u64)> =
parts.iter().map(|p| (p.start_ord, p.end_ord)).collect();
assert_eq!(bounds, vec![(0, 333), (333, 667), (667, 1000)]);
}
#[test]
fn resolve_star_split_distributes_rounding_slack() {
let parts = resolve(&parse("*/3").unwrap(), 0, 100).unwrap();
assert_eq!(parts.len(), 3);
assert_eq!(parts[0].start_ord, 0);
assert_eq!(parts[2].end_ord, 100);
for w in parts.windows(2) {
assert_eq!(w[0].end_ord, w[1].start_ord, "contiguous");
}
let sizes: Vec<u64> = parts.iter().map(|p| p.cardinality()).collect();
assert!(sizes.iter().all(|s| *s == 33 || *s == 34), "sizes: {sizes:?}");
assert_eq!(sizes.iter().sum::<u64>(), 100);
}
#[test]
fn resolve_star_split_alone_equals_linear_recipe() {
let split = resolve(&parse("*/16").unwrap(), 0, 1600).unwrap();
let linear = resolve(&parse("linear:16").unwrap(), 0, 1600).unwrap();
assert_eq!(split, linear);
}
#[test]
fn resolve_star_split_no_remainder_rejected() {
let err = resolve(&parse("100%,*/4").unwrap(), 0, 1000).unwrap_err();
assert!(err.contains("no remainder"), "diagnostic: {err}");
}
#[test]
fn resolve_star_split_finer_than_remainder_rejected() {
let err = resolve(&parse("90%,*/200").unwrap(), 0, 1000).unwrap_err();
assert!(err.contains("non-empty"), "diagnostic: {err}");
}
#[test]
fn resolve_tail_indices_continue_from_head() {
let parts = resolve(&parse("50%,*/5").unwrap(), 0, 1000).unwrap();
assert_eq!(parts.len(), 6);
for (i, p) in parts.iter().enumerate() {
assert_eq!(p.idx, i as u64);
}
}
#[test]
fn split_evenly_boundaries_monotone_and_exact() {
for (start, end, n) in [(0u64, 100u64, 7u64), (5, 5, 1), (0, 3, 3), (1000, 10007, 13)] {
let chunks = split_evenly(start, end, n);
assert_eq!(chunks.len(), n as usize);
assert_eq!(chunks[0].0, start);
assert_eq!(chunks[n as usize - 1].1, end);
for w in chunks.windows(2) {
assert_eq!(w[0].1, w[1].0);
}
let total: u64 = chunks.iter().map(|(s, e)| e - s).sum();
assert_eq!(total, end - start);
}
}
#[test]
fn parse_tolerates_whitespace_in_lists() {
let spec = parse(" 2% , 10% , *% ").unwrap();
assert_eq!(
spec,
PartitionSpec::delta_list(vec![Bound::Pct(2.0), Bound::Pct(10.0), Bound::Star])
);
}
#[test]
fn partition_roundtrips_through_value_ext() {
let p = Partition {
idx: 2,
count: 4,
start_ord: 100,
end_ord: 500,
start_pct: 10.0,
end_pct: 50.0,
base_extent: 1000,
};
let v = Value::from_partition(p);
let recovered = v.as_partition().expect("downcast");
assert_eq!(recovered.idx, 2);
assert_eq!(recovered.start_ord, 100);
assert_eq!(recovered.end_ord, 500);
assert_eq!(recovered.cardinality(), 400);
}
#[test]
fn partition_spec_roundtrips_through_value_ext() {
let spec = parse("fib:5").unwrap();
let v = Value::from_partition_spec(spec);
let recovered = v.as_partition_spec().expect("downcast");
match &recovered.chunking {
Chunking::DeltaList { deltas } => assert_eq!(deltas.len(), 5),
other => panic!("expected DeltaList, got {other:?}"),
}
}
#[test]
fn partition_list_roundtrips_through_value_ext() {
let spec = parse("linear:4").unwrap();
let parts = resolve(&spec, 0, 1000).unwrap();
let v = Value::from_partition_list(parts);
let recovered = v.as_partition_list().expect("downcast");
assert_eq!(recovered.len(), 4);
assert_eq!(recovered.as_slice()[0].start_ord, 0);
assert_eq!(recovered.as_slice()[3].end_ord, 1000);
}
#[test]
fn non_partition_value_downcast_returns_none() {
let v = Value::U64(42);
assert!(v.as_partition().is_none());
assert!(v.as_partition_spec().is_none());
assert!(v.as_partition_list().is_none());
}
#[test]
fn parse_tolerates_whitespace_in_range() {
let spec = parse(" 0 .. 53 % ").unwrap();
assert_eq!(
spec,
PartitionSpec::single_range(Bound::Ord(0), Bound::Pct(53.0))
);
}
#[test]
fn parse_window_clause() {
let spec = parse("linear:4 in 25%..75%").unwrap();
assert_eq!(spec.window, Some((Bound::Pct(25.0), Bound::Pct(75.0))));
assert_eq!(spec.order, PartitionOrder::Unchanged);
match &spec.chunking {
Chunking::DeltaList { deltas } => assert_eq!(deltas.len(), 4),
other => panic!("expected DeltaList, got {other:?}"),
}
}
#[test]
fn parse_window_requires_range() {
let err = parse("linear:4 in 50%").unwrap_err();
assert!(err.contains("start..end"), "diagnostic: {err}");
}
#[test]
fn parse_window_requires_sized_bounds() {
let err = parse("linear:4 in 0..*").unwrap_err();
assert!(err.contains("sized"), "diagnostic: {err}");
}
#[test]
fn parse_window_clause_position_errors() {
assert!(parse("in 0..50%").unwrap_err().contains("chunking spec"));
assert!(parse("linear:4 in").unwrap_err().contains("window range"));
assert!(parse("linear:2 in 0..50% in 0..10%").unwrap_err().contains("at most one"));
}
#[test]
fn resolve_windowed_chunking_is_window_relative() {
let parts = resolve(&parse("linear:4 in 20%..100%").unwrap(), 0, 1000).unwrap();
let bounds: Vec<(u64, u64)> =
parts.iter().map(|p| (p.start_ord, p.end_ord)).collect();
assert_eq!(bounds, vec![(200, 400), (400, 600), (600, 800), (800, 1000)]);
}
#[test]
fn resolve_windowed_form1_composes() {
let parts = resolve(&parse("0..50% in 50%..100%").unwrap(), 0, 1000).unwrap();
assert_eq!(parts.len(), 1);
assert_eq!((parts[0].start_ord, parts[0].end_ord), (500, 750));
}
#[test]
fn resolve_windowed_tail_tokens() {
let parts = resolve(&parse("90%,*/10 in 0..50%").unwrap(), 0, 1000).unwrap();
assert_eq!(parts.len(), 11);
assert_eq!((parts[0].start_ord, parts[0].end_ord), (0, 450));
assert_eq!(parts[10].end_ord, 500);
assert_eq!(parts[1].cardinality(), 5);
}
#[test]
fn parse_finite_repetition_expands() {
let spec = parse("1%x3").unwrap();
assert_eq!(
spec,
PartitionSpec::delta_list(vec![Bound::Pct(1.0); 3])
);
}
#[test]
fn parse_repetition_zero_rejected() {
let err = parse("1%x0").unwrap_err();
assert!(err.contains(">= 1"), "diagnostic: {err}");
}
#[test]
fn parse_repetition_on_tail_rejected() {
assert!(parse("*x3").is_err());
assert!(parse("...x3").is_err());
}
#[test]
fn resolve_repetition_equals_fill_and_split_at_90_10() {
let explicit = resolve(&parse("90%,1%,...").unwrap(), 0, 1000).unwrap();
let repeated = resolve(&parse("90%,1%x10").unwrap(), 0, 1000).unwrap();
assert_eq!(repeated, explicit);
}
#[test]
fn parse_gap_entry() {
let spec = parse("10%,~80%,10%").unwrap();
assert_eq!(
spec,
PartitionSpec::delta_list(vec![
Bound::Pct(10.0),
Bound::Gap(Box::new(Bound::Pct(80.0))),
Bound::Pct(10.0),
])
);
}
#[test]
fn parse_gap_requires_sized_bound() {
let err = parse("10%,~*").unwrap_err();
assert!(err.contains("sized"), "diagnostic: {err}");
}
#[test]
fn parse_gap_repetition_rejected() {
let err = parse("10%,~10%x3").unwrap_err();
assert!(err.contains("size the gap"), "diagnostic: {err}");
}
#[test]
fn parse_all_gaps_rejected() {
let err = parse("~10%,~20%").unwrap_err();
assert!(err.contains("emits no partitions"), "diagnostic: {err}");
}
#[test]
fn parse_fill_after_gap_rejected() {
let err = parse("5%,~5%,...").unwrap_err();
assert!(err.contains("emit nothing"), "diagnostic: {err}");
}
#[test]
fn resolve_gap_consumes_without_emitting() {
let parts = resolve(&parse("10%,~80%,10%").unwrap(), 0, 1000).unwrap();
let bounds: Vec<(u64, u64, u64)> =
parts.iter().map(|p| (p.idx, p.start_ord, p.end_ord)).collect();
assert_eq!(bounds, vec![(0, 0, 100), (1, 900, 1000)]);
}
#[test]
fn resolve_gap_counts_toward_star_remainder() {
let parts = resolve(&parse("10%,~40%,*").unwrap(), 0, 1000).unwrap();
let bounds: Vec<(u64, u64)> =
parts.iter().map(|p| (p.start_ord, p.end_ord)).collect();
assert_eq!(bounds, vec![(0, 100), (500, 1000)]);
}
#[test]
fn parse_star_shaped_recipe() {
let spec = parse("50%,*/ratios:1,3").unwrap();
match &spec.chunking {
Chunking::DeltaList { deltas } => {
assert_eq!(deltas.len(), 2);
match &deltas[1] {
Bound::StarShaped(w) => {
assert_eq!(w.len(), 2);
assert!((w[0] - 25.0).abs() < 1e-9);
assert!((w[1] - 75.0).abs() < 1e-9);
}
other => panic!("expected StarShaped, got {other:?}"),
}
}
other => panic!("expected DeltaList, got {other:?}"),
}
}
#[test]
fn parse_star_linear_rejected_with_canonical_hint() {
let err = parse("90%,*/linear:4").unwrap_err();
assert!(err.contains("*/4"), "diagnostic should point at `*/N`: {err}");
}
#[test]
fn resolve_star_shaped_divides_remainder_by_weights() {
let parts = resolve(&parse("50%,*/ratios:1,3").unwrap(), 0, 1000).unwrap();
let bounds: Vec<(u64, u64)> =
parts.iter().map(|p| (p.start_ord, p.end_ord)).collect();
assert_eq!(bounds, vec![(0, 500), (500, 625), (625, 1000)]);
}
#[test]
fn resolve_star_shaped_alone_covers_extent() {
let parts = resolve(&parse("*/fib:3").unwrap(), 0, 600).unwrap();
let bounds: Vec<(u64, u64)> =
parts.iter().map(|p| (p.start_ord, p.end_ord)).collect();
assert_eq!(bounds, vec![(0, 100), (100, 300), (300, 600)]);
}
#[test]
fn resolve_star_shaped_empty_chunk_rejected() {
let err = resolve(&parse("90%,*/ratios:1,1000").unwrap(), 0, 100).unwrap_err();
assert!(err.contains("empty partition"), "diagnostic: {err}");
}
#[test]
fn parse_order_suffix() {
assert_eq!(parse("fib:5 largest_first").unwrap().order, PartitionOrder::LargestFirst);
assert_eq!(parse("fib:5 smallest_first").unwrap().order, PartitionOrder::SmallestFirst);
assert_eq!(parse("fib:5 random").unwrap().order, PartitionOrder::Random);
assert_eq!(parse("fib:5 unchanged").unwrap().order, PartitionOrder::Unchanged);
assert_eq!(parse("fib:5").unwrap().order, PartitionOrder::Unchanged);
}
#[test]
fn parse_unknown_order_rejected() {
let err = parse("fib:5 descend").unwrap_err();
assert!(err.contains("unknown order"), "diagnostic: {err}");
assert!(err.contains("largest_first"), "diagnostic should list options: {err}");
}
#[test]
fn parse_bare_direction_words_rejected_with_axis_hint() {
let err = parse("fib:5 ascending").unwrap_err();
assert!(err.contains("smallest_first"), "diagnostic: {err}");
assert!(err.contains("SIZE"), "diagnostic should name the axis: {err}");
let err = parse("fib:5 descending").unwrap_err();
assert!(err.contains("largest_first"), "diagnostic: {err}");
}
#[test]
fn resolve_largest_first_sorts_by_cardinality_keeping_idx() {
let parts = resolve(&parse("fib:5 largest_first").unwrap(), 0, 1000).unwrap();
for w in parts.windows(2) {
assert!(w[0].cardinality() >= w[1].cardinality(), "largest first");
}
assert_eq!(parts[0].idx, 4);
assert_eq!(parts[4].idx, 0);
}
#[test]
fn resolve_smallest_first_is_stable_for_equal_sizes() {
let parts = resolve(&parse("linear:3 smallest_first").unwrap(), 0, 999).unwrap();
let idxs: Vec<u64> = parts.iter().map(|p| p.idx).collect();
assert_eq!(idxs, vec![0, 1, 2]);
}
#[test]
fn resolve_random_is_deterministic_permutation() {
let a = resolve(&parse("linear:8 random").unwrap(), 0, 800).unwrap();
let b = resolve(&parse("linear:8 random").unwrap(), 0, 800).unwrap();
assert_eq!(a, b, "same spec must shuffle identically");
let mut by_idx = a.clone();
by_idx.sort_by_key(|p| p.idx);
let unchanged = resolve(&parse("linear:8").unwrap(), 0, 800).unwrap();
assert_eq!(by_idx, unchanged, "shuffle is a permutation of the same partitions");
assert_ne!(a, unchanged, "8 elements should not shuffle to identity here");
}
#[test]
fn display_round_trips_window_and_order() {
let spec = parse("linear:2 in 0..50% largest_first").unwrap();
let shown = ReflectedValue::display(&spec);
assert!(shown.contains("in 0..50%"), "display: {shown}");
assert!(shown.contains("largest_first"), "display: {shown}");
}
#[test]
fn windowed_partitions_label_against_full_base_frame() {
let parts = resolve(&parse("linear:4 in 20%..100%").unwrap(), 0, 1000).unwrap();
let p = &parts[0];
assert_eq!((p.start_ord, p.end_ord), (200, 400));
assert!((p.start_pct - 20.0).abs() < 1e-9, "start_pct: {}", p.start_pct);
assert!((p.end_pct - 40.0).abs() < 1e-9, "end_pct: {}", p.end_pct);
assert_eq!(p.base_extent, 1000, "base_extent is the full base, not the window");
}
#[test]
fn form1_zero_width_slice_rejected() {
let err = resolve(&parse("0..1%").unwrap(), 0, 10).unwrap_err();
assert!(err.contains("zero ordinals"), "diagnostic: {err}");
}
#[test]
fn delta_list_subordinal_recipe_tails_tolerated() {
let parts = resolve(&parse("mul:0.5").unwrap(), 0, 100).unwrap();
assert_eq!(parts.len(), 11, "term count is weight-driven, not extent-driven");
assert_eq!(parts.last().unwrap().end_ord, 100);
}
}