Range refactor (#12405)

# Description
Currently, `Range` is a struct with a `from`, `to`, and `incr` field,
which are all type `Value`. This PR changes `Range` to be an enum over
`IntRange` and `FloatRange` for better type safety / stronger compile
time guarantees.

Fixes: #11778 Fixes: #11777 Fixes: #11776 Fixes: #11775 Fixes: #11774
Fixes: #11773 Fixes: #11769.

# User-Facing Changes
Hopefully none, besides bug fixes.

Although, the `serde` representation might have changed.
This commit is contained in:
Ian Manske
2024-04-06 14:04:56 +00:00
committed by GitHub
parent 75fedcc8dd
commit 7a7d43344e
27 changed files with 1126 additions and 798 deletions

View File

@ -1,7 +1,7 @@
use nu_engine::command_prelude::*;
use nu_protocol::Range;
use nu_protocol::{FloatRange, Range};
use rand::prelude::{thread_rng, Rng};
use std::cmp::Ordering;
use std::ops::Bound;
#[derive(Clone)]
pub struct SubCommand;
@ -68,43 +68,39 @@ fn float(
stack: &mut Stack,
call: &Call,
) -> Result<PipelineData, ShellError> {
let mut range_span = call.head;
let span = call.head;
let range: Option<Spanned<Range>> = call.opt(engine_state, stack, 0)?;
let (min, max) = if let Some(spanned_range) = range {
let r = spanned_range.item;
range_span = spanned_range.span;
let mut thread_rng = thread_rng();
if r.is_end_inclusive() {
(r.from.coerce_float()?, r.to.coerce_float()?)
} else if r.to.coerce_float()? >= 1.0 {
(r.from.coerce_float()?, r.to.coerce_float()? - 1.0)
} else {
(0.0, 0.0)
match range {
Some(range) => {
let range_span = range.span;
let range = FloatRange::from(range.item);
if range.step() < 0.0 {
return Err(ShellError::InvalidRange {
left_flank: range.start().to_string(),
right_flank: match range.end() {
Bound::Included(end) | Bound::Excluded(end) => end.to_string(),
Bound::Unbounded => "".into(),
},
span: range_span,
});
}
let value = match range.end() {
Bound::Included(end) => thread_rng.gen_range(range.start()..=end),
Bound::Excluded(end) => thread_rng.gen_range(range.start()..end),
Bound::Unbounded => thread_rng.gen_range(range.start()..f64::INFINITY),
};
Ok(PipelineData::Value(Value::float(value, span), None))
}
} else {
(0.0, 1.0)
};
match min.partial_cmp(&max) {
Some(Ordering::Greater) => Err(ShellError::InvalidRange {
left_flank: min.to_string(),
right_flank: max.to_string(),
span: range_span,
}),
Some(Ordering::Equal) => Ok(PipelineData::Value(
Value::float(min, Span::new(64, 64)),
None => Ok(PipelineData::Value(
Value::float(thread_rng.gen_range(0.0..1.0), span),
None,
)),
_ => {
let mut thread_rng = thread_rng();
let result: f64 = thread_rng.gen_range(min..max);
Ok(PipelineData::Value(
Value::float(result, Span::new(64, 64)),
None,
))
}
}
}

View File

@ -1,7 +1,7 @@
use nu_engine::command_prelude::*;
use nu_protocol::Range;
use rand::prelude::{thread_rng, Rng};
use std::cmp::Ordering;
use std::ops::Bound;
#[derive(Clone)]
pub struct SubCommand;
@ -71,34 +71,44 @@ fn integer(
let span = call.head;
let range: Option<Spanned<Range>> = call.opt(engine_state, stack, 0)?;
let mut range_span = call.head;
let (min, max) = if let Some(spanned_range) = range {
let r = spanned_range.item;
range_span = spanned_range.span;
if r.is_end_inclusive() {
(r.from.as_int()?, r.to.as_int()?)
} else if r.to.as_int()? > 0 {
(r.from.as_int()?, r.to.as_int()? - 1)
} else {
(0, 0)
}
} else {
(0, i64::MAX)
};
let mut thread_rng = thread_rng();
match min.partial_cmp(&max) {
Some(Ordering::Greater) => Err(ShellError::InvalidRange {
left_flank: min.to_string(),
right_flank: max.to_string(),
span: range_span,
}),
Some(Ordering::Equal) => Ok(PipelineData::Value(Value::int(min, span), None)),
_ => {
let mut thread_rng = thread_rng();
let result: i64 = thread_rng.gen_range(min..=max);
match range {
Some(range) => {
let range_span = range.span;
match range.item {
Range::IntRange(range) => {
if range.step() < 0 {
return Err(ShellError::InvalidRange {
left_flank: range.start().to_string(),
right_flank: match range.end() {
Bound::Included(end) | Bound::Excluded(end) => end.to_string(),
Bound::Unbounded => "".into(),
},
span: range_span,
});
}
Ok(PipelineData::Value(Value::int(result, span), None))
let value = match range.end() {
Bound::Included(end) => thread_rng.gen_range(range.start()..=end),
Bound::Excluded(end) => thread_rng.gen_range(range.start()..end),
Bound::Unbounded => thread_rng.gen_range(range.start()..=i64::MAX),
};
Ok(PipelineData::Value(Value::int(value, span), None))
}
Range::FloatRange(_) => Err(ShellError::UnsupportedInput {
msg: "float range".into(),
input: "value originates from here".into(),
msg_span: call.head,
input_span: range.span,
}),
}
}
None => Ok(PipelineData::Value(
Value::int(thread_rng.gen_range(0..=i64::MAX), span),
None,
)),
}
}