Accept n+ as array lengths (#7212)
Signed-off-by: Nick Cameron <nrc@ncameron.org>
This commit is contained in:
@ -16,15 +16,16 @@ There are some useful functions for working with arrays in the standard library,
|
|||||||
|
|
||||||
Arrays have their own types: `[T]` where `T` is the type of the elements of the array, for example, `[string]` means an array of `string`s and `[any]` means an array of any values.
|
Arrays have their own types: `[T]` where `T` is the type of the elements of the array, for example, `[string]` means an array of `string`s and `[any]` means an array of any values.
|
||||||
|
|
||||||
Array types can also include length information: `[T; n]` denotes an array of length `n` (where `n` is a number literal) and `[T; 1+]` denotes an array whose length is at least one (i.e., a non-empty array). E.g., `[string; 1+]` and `[number(mm); 3]` are valid array types.
|
Array types can also include length information: `[T; n]` denotes an array of length `n` (where `n` is a number literal) and `[T; n+]` denotes an array whose length is at least `n`. The common case for that is `[T; 1+]`, i.e., a non-empty array. E.g., `[string; 1+]` and `[number(mm); 3]` are valid array types.
|
||||||
|
|
||||||
## Ranges
|
## Ranges
|
||||||
|
|
||||||
Ranges are a succinct way to create an array of sequential numbers. The syntax is `[start .. end]` where `start` and `end` evaluate to whole numbers (integers). Ranges are inclusive of the start and end. The end must be greater than the start. Examples:
|
Ranges are a succinct way to create an array of sequential numbers. The syntax is `[start .. end]` where `start` and `end` evaluate to whole numbers (integers). Ranges are inclusive of the start and end. The end must be greater than the start. A range which is exclusive of its end is written with `<end`. Examples:
|
||||||
|
|
||||||
```kcl,norun
|
```kcl,norun
|
||||||
[0..3] // [0, 1, 2, 3]
|
[0..3] // [0, 1, 2, 3]
|
||||||
[3..10] // [3, 4, 5, 6, 7, 8, 9, 10]
|
[3..10] // [3, 4, 5, 6, 7, 8, 9, 10]
|
||||||
|
[3..<10] // [3, 4, 5, 6, 7, 8, 9]
|
||||||
x = 2
|
x = 2
|
||||||
[x..x+1] // [2, 3]
|
[x..x+1] // [2, 3]
|
||||||
```
|
```
|
||||||
|
@ -796,7 +796,7 @@ fn coerce_result_type(
|
|||||||
// can be removed.
|
// can be removed.
|
||||||
// I believe this is safe, since anywhere which requires an array should coerce the singleton
|
// I believe this is safe, since anywhere which requires an array should coerce the singleton
|
||||||
// to an array and we only do this hack for return values.
|
// to an array and we only do this hack for return values.
|
||||||
if let RuntimeType::Array(inner, ArrayLen::NonEmpty) = &ty {
|
if let RuntimeType::Array(inner, ArrayLen::Minimum(1)) = &ty {
|
||||||
ty = RuntimeType::Union(vec![(**inner).clone(), ty]);
|
ty = RuntimeType::Union(vec![(**inner).clone(), ty]);
|
||||||
}
|
}
|
||||||
let val = val.coerce(&ty, true, exec_state).map_err(|_| {
|
let val = val.coerce(&ty, true, exec_state).map_err(|_| {
|
||||||
|
@ -44,7 +44,7 @@ impl RuntimeType {
|
|||||||
pub fn sketches() -> Self {
|
pub fn sketches() -> Self {
|
||||||
RuntimeType::Array(
|
RuntimeType::Array(
|
||||||
Box::new(RuntimeType::Primitive(PrimitiveType::Sketch)),
|
Box::new(RuntimeType::Primitive(PrimitiveType::Sketch)),
|
||||||
ArrayLen::NonEmpty,
|
ArrayLen::Minimum(1),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,7 +52,7 @@ impl RuntimeType {
|
|||||||
pub fn solids() -> Self {
|
pub fn solids() -> Self {
|
||||||
RuntimeType::Array(
|
RuntimeType::Array(
|
||||||
Box::new(RuntimeType::Primitive(PrimitiveType::Solid)),
|
Box::new(RuntimeType::Primitive(PrimitiveType::Solid)),
|
||||||
ArrayLen::NonEmpty,
|
ArrayLen::Minimum(1),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -208,8 +208,13 @@ impl RuntimeType {
|
|||||||
pub fn human_friendly_type(&self) -> String {
|
pub fn human_friendly_type(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
RuntimeType::Primitive(ty) => ty.to_string(),
|
RuntimeType::Primitive(ty) => ty.to_string(),
|
||||||
RuntimeType::Array(ty, ArrayLen::None) => format!("an array of {}", ty.display_multiple()),
|
RuntimeType::Array(ty, ArrayLen::None | ArrayLen::Minimum(0)) => {
|
||||||
RuntimeType::Array(ty, ArrayLen::NonEmpty) => format!("one or more {}", ty.display_multiple()),
|
format!("an array of {}", ty.display_multiple())
|
||||||
|
}
|
||||||
|
RuntimeType::Array(ty, ArrayLen::Minimum(1)) => format!("one or more {}", ty.display_multiple()),
|
||||||
|
RuntimeType::Array(ty, ArrayLen::Minimum(n)) => {
|
||||||
|
format!("an array of {n} or more {}", ty.display_multiple())
|
||||||
|
}
|
||||||
RuntimeType::Array(ty, ArrayLen::Known(n)) => format!("an array of {n} {}", ty.display_multiple()),
|
RuntimeType::Array(ty, ArrayLen::Known(n)) => format!("an array of {n} {}", ty.display_multiple()),
|
||||||
RuntimeType::Union(tys) => tys
|
RuntimeType::Union(tys) => tys
|
||||||
.iter()
|
.iter()
|
||||||
@ -292,7 +297,7 @@ impl fmt::Display for RuntimeType {
|
|||||||
RuntimeType::Primitive(t) => t.fmt(f),
|
RuntimeType::Primitive(t) => t.fmt(f),
|
||||||
RuntimeType::Array(t, l) => match l {
|
RuntimeType::Array(t, l) => match l {
|
||||||
ArrayLen::None => write!(f, "[{t}]"),
|
ArrayLen::None => write!(f, "[{t}]"),
|
||||||
ArrayLen::NonEmpty => write!(f, "[{t}; 1+]"),
|
ArrayLen::Minimum(n) => write!(f, "[{t}; {n}+]"),
|
||||||
ArrayLen::Known(n) => write!(f, "[{t}; {n}]"),
|
ArrayLen::Known(n) => write!(f, "[{t}; {n}]"),
|
||||||
},
|
},
|
||||||
RuntimeType::Tuple(ts) => write!(
|
RuntimeType::Tuple(ts) => write!(
|
||||||
@ -321,7 +326,7 @@ impl fmt::Display for RuntimeType {
|
|||||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, ts_rs::TS, JsonSchema)]
|
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, ts_rs::TS, JsonSchema)]
|
||||||
pub enum ArrayLen {
|
pub enum ArrayLen {
|
||||||
None,
|
None,
|
||||||
NonEmpty,
|
Minimum(usize),
|
||||||
Known(usize),
|
Known(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -329,8 +334,9 @@ impl ArrayLen {
|
|||||||
pub fn subtype(self, other: ArrayLen) -> bool {
|
pub fn subtype(self, other: ArrayLen) -> bool {
|
||||||
match (self, other) {
|
match (self, other) {
|
||||||
(_, ArrayLen::None) => true,
|
(_, ArrayLen::None) => true,
|
||||||
(ArrayLen::NonEmpty, ArrayLen::NonEmpty) => true,
|
(ArrayLen::Minimum(s1), ArrayLen::Minimum(s2)) if s1 >= s2 => true,
|
||||||
(ArrayLen::Known(size), ArrayLen::NonEmpty) if size > 0 => true,
|
(ArrayLen::Known(s1), ArrayLen::Minimum(s2)) if s1 >= s2 => true,
|
||||||
|
(ArrayLen::None, ArrayLen::Minimum(0)) => true,
|
||||||
(ArrayLen::Known(s1), ArrayLen::Known(s2)) if s1 == s2 => true,
|
(ArrayLen::Known(s1), ArrayLen::Known(s2)) if s1 == s2 => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
@ -340,7 +346,7 @@ impl ArrayLen {
|
|||||||
fn satisfied(self, len: usize, allow_shrink: bool) -> Option<usize> {
|
fn satisfied(self, len: usize, allow_shrink: bool) -> Option<usize> {
|
||||||
match self {
|
match self {
|
||||||
ArrayLen::None => Some(len),
|
ArrayLen::None => Some(len),
|
||||||
ArrayLen::NonEmpty => (len > 0).then_some(len),
|
ArrayLen::Minimum(s) => (len >= s).then_some(len),
|
||||||
ArrayLen::Known(s) => (if allow_shrink { len >= s } else { len == s }).then_some(s),
|
ArrayLen::Known(s) => (if allow_shrink { len >= s } else { len == s }).then_some(s),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1557,7 +1563,7 @@ mod test {
|
|||||||
// Array subtypes
|
// Array subtypes
|
||||||
let aty = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::None);
|
let aty = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::None);
|
||||||
let aty1 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(1));
|
let aty1 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(1));
|
||||||
let aty0 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::NonEmpty);
|
let aty0 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Minimum(1));
|
||||||
|
|
||||||
match v {
|
match v {
|
||||||
KclValue::Tuple { .. } | KclValue::HomArray { .. } => {
|
KclValue::Tuple { .. } | KclValue::HomArray { .. } => {
|
||||||
@ -1640,7 +1646,7 @@ mod test {
|
|||||||
let aty = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::None);
|
let aty = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::None);
|
||||||
let aty0 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(0));
|
let aty0 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(0));
|
||||||
let aty1 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(1));
|
let aty1 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(1));
|
||||||
let aty1p = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::NonEmpty);
|
let aty1p = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Minimum(1));
|
||||||
assert_coerce_results(
|
assert_coerce_results(
|
||||||
&none,
|
&none,
|
||||||
&aty,
|
&aty,
|
||||||
@ -1854,15 +1860,25 @@ mod test {
|
|||||||
);
|
);
|
||||||
let tyh1 = RuntimeType::Array(
|
let tyh1 = RuntimeType::Array(
|
||||||
Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
|
Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
|
||||||
ArrayLen::NonEmpty,
|
ArrayLen::Minimum(1),
|
||||||
);
|
);
|
||||||
let tyh3 = RuntimeType::Array(
|
let tyh3 = RuntimeType::Array(
|
||||||
Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
|
Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
|
||||||
ArrayLen::Known(3),
|
ArrayLen::Known(3),
|
||||||
);
|
);
|
||||||
|
let tyhm3 = RuntimeType::Array(
|
||||||
|
Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
|
||||||
|
ArrayLen::Minimum(3),
|
||||||
|
);
|
||||||
|
let tyhm5 = RuntimeType::Array(
|
||||||
|
Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
|
||||||
|
ArrayLen::Minimum(5),
|
||||||
|
);
|
||||||
assert_coerce_results(&hom_arr, &tyhn, &hom_arr, &mut exec_state);
|
assert_coerce_results(&hom_arr, &tyhn, &hom_arr, &mut exec_state);
|
||||||
assert_coerce_results(&hom_arr, &tyh1, &hom_arr, &mut exec_state);
|
assert_coerce_results(&hom_arr, &tyh1, &hom_arr, &mut exec_state);
|
||||||
hom_arr.coerce(&tyh3, true, &mut exec_state).unwrap_err();
|
hom_arr.coerce(&tyh3, true, &mut exec_state).unwrap_err();
|
||||||
|
assert_coerce_results(&hom_arr, &tyhm3, &hom_arr, &mut exec_state);
|
||||||
|
hom_arr.coerce(&tyhm5, true, &mut exec_state).unwrap_err();
|
||||||
|
|
||||||
let hom_arr0 = KclValue::HomArray {
|
let hom_arr0 = KclValue::HomArray {
|
||||||
value: vec![],
|
value: vec![],
|
||||||
@ -2364,7 +2380,7 @@ d = cos(30)
|
|||||||
// Principal types
|
// Principal types
|
||||||
let tym1 = RuntimeType::Array(
|
let tym1 = RuntimeType::Array(
|
||||||
Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
|
Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
|
||||||
ArrayLen::NonEmpty,
|
ArrayLen::Minimum(1),
|
||||||
);
|
);
|
||||||
|
|
||||||
let result = KclValue::HomArray {
|
let result = KclValue::HomArray {
|
||||||
|
@ -198,7 +198,7 @@ impl Type {
|
|||||||
hasher.update(ty.compute_digest());
|
hasher.update(ty.compute_digest());
|
||||||
match len {
|
match len {
|
||||||
crate::execution::types::ArrayLen::None => {}
|
crate::execution::types::ArrayLen::None => {}
|
||||||
crate::execution::types::ArrayLen::NonEmpty => hasher.update(usize::MAX.to_ne_bytes()),
|
crate::execution::types::ArrayLen::Minimum(n) => hasher.update((-(*n as isize)).to_ne_bytes()),
|
||||||
crate::execution::types::ArrayLen::Known(n) => hasher.update(n.to_ne_bytes()),
|
crate::execution::types::ArrayLen::Known(n) => hasher.update(n.to_ne_bytes()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3336,7 +3336,7 @@ impl fmt::Display for Type {
|
|||||||
write!(f, "[{ty}")?;
|
write!(f, "[{ty}")?;
|
||||||
match len {
|
match len {
|
||||||
ArrayLen::None => {}
|
ArrayLen::None => {}
|
||||||
ArrayLen::NonEmpty => write!(f, "; 1+")?,
|
ArrayLen::Minimum(n) => write!(f, "; {n}+")?,
|
||||||
ArrayLen::Known(n) => write!(f, "; {n}")?,
|
ArrayLen::Known(n) => write!(f, "; {n}")?,
|
||||||
}
|
}
|
||||||
write!(f, "]")
|
write!(f, "]")
|
||||||
|
@ -2968,16 +2968,9 @@ fn array_type(i: &mut TokenSlice) -> ModalResult<Node<Type>> {
|
|||||||
.parse_next(i)?;
|
.parse_next(i)?;
|
||||||
close_bracket(i)?;
|
close_bracket(i)?;
|
||||||
|
|
||||||
let len = if let Some((tok, _, n, plus)) = len {
|
let len = if let Some((_, _, n, plus)) = len {
|
||||||
if plus.is_some() {
|
if plus.is_some() {
|
||||||
if n != 1 {
|
ArrayLen::Minimum(n)
|
||||||
return Err(ErrMode::Cut(ContextError::from(CompilationError::fatal(
|
|
||||||
tok.as_source_range(),
|
|
||||||
"Non-empty arrays are specified using `1+`, for a fixed-size array use just an integer",
|
|
||||||
))));
|
|
||||||
} else {
|
|
||||||
ArrayLen::NonEmpty
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
ArrayLen::Known(n)
|
ArrayLen::Known(n)
|
||||||
}
|
}
|
||||||
|
@ -106,7 +106,7 @@ pub async fn ceil(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
|
|||||||
pub async fn min(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
pub async fn min(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||||
let nums: Vec<TyF64> = args.get_unlabeled_kw_arg_typed(
|
let nums: Vec<TyF64> = args.get_unlabeled_kw_arg_typed(
|
||||||
"input",
|
"input",
|
||||||
&RuntimeType::Array(Box::new(RuntimeType::num_any()), ArrayLen::NonEmpty),
|
&RuntimeType::Array(Box::new(RuntimeType::num_any()), ArrayLen::Minimum(1)),
|
||||||
exec_state,
|
exec_state,
|
||||||
)?;
|
)?;
|
||||||
let (nums, ty) = NumericType::combine_eq_array(&nums);
|
let (nums, ty) = NumericType::combine_eq_array(&nums);
|
||||||
@ -131,7 +131,7 @@ pub async fn min(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kcl
|
|||||||
pub async fn max(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
pub async fn max(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||||
let nums: Vec<TyF64> = args.get_unlabeled_kw_arg_typed(
|
let nums: Vec<TyF64> = args.get_unlabeled_kw_arg_typed(
|
||||||
"input",
|
"input",
|
||||||
&RuntimeType::Array(Box::new(RuntimeType::num_any()), ArrayLen::NonEmpty),
|
&RuntimeType::Array(Box::new(RuntimeType::num_any()), ArrayLen::Minimum(1)),
|
||||||
exec_state,
|
exec_state,
|
||||||
)?;
|
)?;
|
||||||
let (nums, ty) = NumericType::combine_eq_array(&nums);
|
let (nums, ty) = NumericType::combine_eq_array(&nums);
|
||||||
|
@ -20,7 +20,7 @@ pub async fn shell(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
|||||||
let thickness: TyF64 = args.get_kw_arg_typed("thickness", &RuntimeType::length(), exec_state)?;
|
let thickness: TyF64 = args.get_kw_arg_typed("thickness", &RuntimeType::length(), exec_state)?;
|
||||||
let faces = args.get_kw_arg_typed(
|
let faces = args.get_kw_arg_typed(
|
||||||
"faces",
|
"faces",
|
||||||
&RuntimeType::Array(Box::new(RuntimeType::tag()), ArrayLen::NonEmpty),
|
&RuntimeType::Array(Box::new(RuntimeType::tag()), ArrayLen::Minimum(1)),
|
||||||
exec_state,
|
exec_state,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
@ -2327,7 +2327,7 @@ pub async fn subtract_2d(exec_state: &mut ExecState, args: Args) -> Result<KclVa
|
|||||||
"tool",
|
"tool",
|
||||||
&RuntimeType::Array(
|
&RuntimeType::Array(
|
||||||
Box::new(RuntimeType::Primitive(PrimitiveType::Sketch)),
|
Box::new(RuntimeType::Primitive(PrimitiveType::Sketch)),
|
||||||
ArrayLen::NonEmpty,
|
ArrayLen::Minimum(1),
|
||||||
),
|
),
|
||||||
exec_state,
|
exec_state,
|
||||||
)?;
|
)?;
|
||||||
|
@ -1427,6 +1427,7 @@ c = "dsfds": A | B | C
|
|||||||
d = [1]: [number]
|
d = [1]: [number]
|
||||||
e = foo: [number; 3]
|
e = foo: [number; 3]
|
||||||
f = [1, 2, 3]: [number; 1+]
|
f = [1, 2, 3]: [number; 1+]
|
||||||
|
f = [1, 2, 3]: [number; 3+]
|
||||||
"#;
|
"#;
|
||||||
let program = crate::parsing::top_level_parse(some_program_string).unwrap();
|
let program = crate::parsing::top_level_parse(some_program_string).unwrap();
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user