More numeric type propagations (#6221)
Last few numeric type propagations Signed-off-by: Nick Cameron <nrc@ncameron.org>
This commit is contained in:
@ -78,7 +78,7 @@ assertEqual(sum, 6, 0.00001, "1 + 2 + 3 summed is 6")
|
|||||||
```js
|
```js
|
||||||
// Declare a function that sketches a decagon.
|
// Declare a function that sketches a decagon.
|
||||||
fn decagon(radius) {
|
fn decagon(radius) {
|
||||||
// Each side of the decagon is turned this many degrees from the previous angle.
|
// Each side of the decagon is turned this many radians from the previous angle.
|
||||||
stepAngle = 1 / 10 * TAU
|
stepAngle = 1 / 10 * TAU
|
||||||
|
|
||||||
// Start the decagon sketch at this point.
|
// Start the decagon sketch at this point.
|
||||||
|
File diff suppressed because one or more lines are too long
@ -235702,7 +235702,7 @@
|
|||||||
"examples": [
|
"examples": [
|
||||||
"// This function adds two numbers.\nfn add(a, b) {\n return a + b\n}\n\n// This function adds an array of numbers.\n// It uses the `reduce` function, to call the `add` function on every\n// element of the `arr` parameter. The starting value is 0.\nfn sum(arr) {\n return reduce(arr, 0, add)\n}\n\n/* The above is basically like this pseudo-code:\nfn sum(arr):\n sumSoFar = 0\n for i in arr:\n sumSoFar = add(sumSoFar, i)\n return sumSoFar */\n\n// We use `assertEqual` to check that our `sum` function gives the\n// expected result. It's good to check your work!\nassertEqual(sum([1, 2, 3]), 6, 0.00001, \"1 + 2 + 3 summed is 6\")",
|
"// This function adds two numbers.\nfn add(a, b) {\n return a + b\n}\n\n// This function adds an array of numbers.\n// It uses the `reduce` function, to call the `add` function on every\n// element of the `arr` parameter. The starting value is 0.\nfn sum(arr) {\n return reduce(arr, 0, add)\n}\n\n/* The above is basically like this pseudo-code:\nfn sum(arr):\n sumSoFar = 0\n for i in arr:\n sumSoFar = add(sumSoFar, i)\n return sumSoFar */\n\n// We use `assertEqual` to check that our `sum` function gives the\n// expected result. It's good to check your work!\nassertEqual(sum([1, 2, 3]), 6, 0.00001, \"1 + 2 + 3 summed is 6\")",
|
||||||
"// This example works just like the previous example above, but it uses\n// an anonymous `add` function as its parameter, instead of declaring a\n// named function outside.\narr = [1, 2, 3]\nsum = reduce(arr, 0, fn(i, result_so_far) {\n return i + result_so_far\n})\n\n// We use `assertEqual` to check that our `sum` function gives the\n// expected result. It's good to check your work!\nassertEqual(sum, 6, 0.00001, \"1 + 2 + 3 summed is 6\")",
|
"// This example works just like the previous example above, but it uses\n// an anonymous `add` function as its parameter, instead of declaring a\n// named function outside.\narr = [1, 2, 3]\nsum = reduce(arr, 0, fn(i, result_so_far) {\n return i + result_so_far\n})\n\n// We use `assertEqual` to check that our `sum` function gives the\n// expected result. It's good to check your work!\nassertEqual(sum, 6, 0.00001, \"1 + 2 + 3 summed is 6\")",
|
||||||
"// Declare a function that sketches a decagon.\nfn decagon(radius) {\n // Each side of the decagon is turned this many degrees from the previous angle.\n stepAngle = 1 / 10 * TAU\n\n // Start the decagon sketch at this point.\n startOfDecagonSketch = startSketchOn(XY)\n |> startProfileAt([cos(0) * radius, sin(0) * radius], %)\n\n // Use a `reduce` to draw the remaining decagon sides.\n // For each number in the array 1..10, run the given function,\n // which takes a partially-sketched decagon and adds one more edge to it.\n fullDecagon = reduce([1..10], startOfDecagonSketch, fn(i, partialDecagon) {\n // Draw one edge of the decagon.\n x = cos(stepAngle * i) * radius\n y = sin(stepAngle * i) * radius\n return line(partialDecagon, end = [x, y])\n })\n\n return fullDecagon\n}\n\n/* The `decagon` above is basically like this pseudo-code:\nfn decagon(radius):\n stepAngle = (1/10) * TAU\n plane = startSketchOn('XY')\n startOfDecagonSketch = startProfileAt([(cos(0)*radius), (sin(0) * radius)], plane)\n\n // Here's the reduce part.\n partialDecagon = startOfDecagonSketch\n for i in [1..10]:\n x = cos(stepAngle * i) * radius\n y = sin(stepAngle * i) * radius\n partialDecagon = line(partialDecagon, end = [x, y])\n fullDecagon = partialDecagon // it's now full\n return fullDecagon */\n\n// Use the `decagon` function declared above, to sketch a decagon with radius 5.\ndecagon(5.0)\n |> close()"
|
"// Declare a function that sketches a decagon.\nfn decagon(radius) {\n // Each side of the decagon is turned this many radians from the previous angle.\n stepAngle = 1 / 10 * TAU\n\n // Start the decagon sketch at this point.\n startOfDecagonSketch = startSketchOn(XY)\n |> startProfileAt([cos(0) * radius, sin(0) * radius], %)\n\n // Use a `reduce` to draw the remaining decagon sides.\n // For each number in the array 1..10, run the given function,\n // which takes a partially-sketched decagon and adds one more edge to it.\n fullDecagon = reduce([1..10], startOfDecagonSketch, fn(i, partialDecagon) {\n // Draw one edge of the decagon.\n x = cos(stepAngle * i) * radius\n y = sin(stepAngle * i) * radius\n return line(partialDecagon, end = [x, y])\n })\n\n return fullDecagon\n}\n\n/* The `decagon` above is basically like this pseudo-code:\nfn decagon(radius):\n stepAngle = (1/10) * TAU\n plane = startSketchOn('XY')\n startOfDecagonSketch = startProfileAt([(cos(0)*radius), (sin(0) * radius)], plane)\n\n // Here's the reduce part.\n partialDecagon = startOfDecagonSketch\n for i in [1..10]:\n x = cos(stepAngle * i) * radius\n y = sin(stepAngle * i) * radius\n partialDecagon = line(partialDecagon, end = [x, y])\n fullDecagon = partialDecagon // it's now full\n return fullDecagon */\n\n// Use the `decagon` function declared above, to sketch a decagon with radius 5.\ndecagon(5.0)\n |> close()"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -360,15 +360,6 @@ impl KclValue {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Put the number into a KCL value.
|
|
||||||
pub const fn from_number(f: f64, meta: Vec<Metadata>) -> Self {
|
|
||||||
Self::Number {
|
|
||||||
value: f,
|
|
||||||
meta,
|
|
||||||
ty: NumericType::Unknown,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn from_number_with_type(f: f64, ty: NumericType, meta: Vec<Metadata>) -> Self {
|
pub const fn from_number_with_type(f: f64, ty: NumericType, meta: Vec<Metadata>) -> Self {
|
||||||
Self::Number { value: f, meta, ty }
|
Self::Number { value: f, meta, ty }
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
pub(super) static ref CHECK_NUMERIC_TYPES: bool = {
|
pub(crate) static ref CHECK_NUMERIC_TYPES: bool = {
|
||||||
let env_var = std::env::var("ZOO_NUM_TYS");
|
let env_var = std::env::var("ZOO_NUM_TYS");
|
||||||
let Ok(env_var) = env_var else {
|
let Ok(env_var) = env_var else {
|
||||||
return false;
|
return false;
|
||||||
@ -416,6 +416,80 @@ impl NumericType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn combine_eq_array(input: &[TyF64]) -> (Vec<f64>, NumericType) {
|
||||||
|
use NumericType::*;
|
||||||
|
|
||||||
|
let mut result = input.iter().map(|t| t.n).collect();
|
||||||
|
|
||||||
|
let mut ty = Any;
|
||||||
|
// Invariant mismatch is true => ty is Known
|
||||||
|
let mut mismatch = false;
|
||||||
|
for i in input {
|
||||||
|
if i.ty == Any || ty == i.ty {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
match (&ty, &i.ty) {
|
||||||
|
(Any, t) => {
|
||||||
|
ty = t.clone();
|
||||||
|
}
|
||||||
|
(_, Unknown) | (Default { .. }, Default { .. }) => return (result, Unknown),
|
||||||
|
|
||||||
|
// Known types and compatible, but needs adjustment.
|
||||||
|
(Known(UnitType::Length(_)), Known(UnitType::Length(_)))
|
||||||
|
| (Known(UnitType::Angle(_)), Known(UnitType::Angle(_))) => {
|
||||||
|
mismatch = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Known but incompatible.
|
||||||
|
(Known(_), Known(_)) => return (result, Unknown),
|
||||||
|
|
||||||
|
// Known and unknown, no adjustment for counting numbers.
|
||||||
|
(Known(UnitType::Count), Default { .. }) | (Default { .. }, Known(UnitType::Count)) => {
|
||||||
|
ty = Known(UnitType::Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
(Known(UnitType::Length(l1)), Default { len: l2, .. }) => {
|
||||||
|
mismatch |= l1 != l2;
|
||||||
|
}
|
||||||
|
(Known(UnitType::Angle(a1)), Default { angle: a2, .. }) => {
|
||||||
|
mismatch |= a1 != a2;
|
||||||
|
}
|
||||||
|
|
||||||
|
(Default { len: l1, .. }, Known(UnitType::Length(l2))) => {
|
||||||
|
mismatch |= l1 != l2;
|
||||||
|
ty = Known(UnitType::Length(*l2));
|
||||||
|
}
|
||||||
|
(Default { angle: a1, .. }, Known(UnitType::Angle(a2))) => {
|
||||||
|
mismatch |= a1 != a2;
|
||||||
|
ty = Known(UnitType::Angle(*a2));
|
||||||
|
}
|
||||||
|
|
||||||
|
(Unknown, _) | (_, Any) => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !mismatch {
|
||||||
|
return (result, ty);
|
||||||
|
}
|
||||||
|
|
||||||
|
result = result
|
||||||
|
.into_iter()
|
||||||
|
.zip(input)
|
||||||
|
.map(|(n, i)| match (&ty, &i.ty) {
|
||||||
|
(Known(UnitType::Length(l1)), Known(UnitType::Length(l2)) | Default { len: l2, .. }) => {
|
||||||
|
l2.adjust_to(n, *l1)
|
||||||
|
}
|
||||||
|
(Known(UnitType::Angle(a1)), Known(UnitType::Angle(a2)) | Default { angle: a2, .. }) => {
|
||||||
|
a2.adjust_to(n, *a1)
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
(result, ty)
|
||||||
|
}
|
||||||
|
|
||||||
/// Combine two types for multiplication-like operations.
|
/// Combine two types for multiplication-like operations.
|
||||||
pub fn combine_mul(a: TyF64, b: TyF64) -> (f64, f64, NumericType) {
|
pub fn combine_mul(a: TyF64, b: TyF64) -> (f64, f64, NumericType) {
|
||||||
use NumericType::*;
|
use NumericType::*;
|
||||||
@ -1847,11 +1921,16 @@ n = 10inch / 2mm
|
|||||||
o = 3mm / 3
|
o = 3mm / 3
|
||||||
p = 3_ / 4
|
p = 3_ / 4
|
||||||
q = 4inch / 2_
|
q = 4inch / 2_
|
||||||
|
|
||||||
|
r = min(0, 3, 42)
|
||||||
|
s = min(0, 3mm, -42)
|
||||||
|
t = min(100, 3in, 142mm)
|
||||||
|
u = min(3rad, 4in)
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
let result = parse_execute(program).await.unwrap();
|
let result = parse_execute(program).await.unwrap();
|
||||||
if *CHECK_NUMERIC_TYPES {
|
if *CHECK_NUMERIC_TYPES {
|
||||||
assert_eq!(result.exec_state.errors().len(), 2);
|
assert_eq!(result.exec_state.errors().len(), 3);
|
||||||
} else {
|
} else {
|
||||||
assert!(result.exec_state.errors().is_empty());
|
assert!(result.exec_state.errors().is_empty());
|
||||||
}
|
}
|
||||||
@ -1875,5 +1954,10 @@ q = 4inch / 2_
|
|||||||
assert_value_and_type("o", &result, 1.0, NumericType::mm());
|
assert_value_and_type("o", &result, 1.0, NumericType::mm());
|
||||||
assert_value_and_type("p", &result, 1.0, NumericType::count());
|
assert_value_and_type("p", &result, 1.0, NumericType::count());
|
||||||
assert_value_and_type("q", &result, 2.0, NumericType::Known(UnitType::Length(UnitLen::Inches)));
|
assert_value_and_type("q", &result, 2.0, NumericType::Known(UnitType::Length(UnitLen::Inches)));
|
||||||
|
|
||||||
|
assert_value_and_type("r", &result, 0.0, NumericType::default());
|
||||||
|
assert_value_and_type("s", &result, -42.0, NumericType::mm());
|
||||||
|
assert_value_and_type("t", &result, 3.0, NumericType::Known(UnitType::Length(UnitLen::Inches)));
|
||||||
|
assert_value_and_type("u", &result, 3.0, NumericType::Unknown);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -523,15 +523,6 @@ impl Args {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn make_user_val_from_f64(&self, f: f64) -> KclValue {
|
|
||||||
KclValue::from_number(
|
|
||||||
f,
|
|
||||||
vec![Metadata {
|
|
||||||
source_range: self.source_range,
|
|
||||||
}],
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn make_user_val_from_f64_with_type(&self, f: TyF64) -> KclValue {
|
pub(super) fn make_user_val_from_f64_with_type(&self, f: TyF64) -> KclValue {
|
||||||
KclValue::from_number_with_type(
|
KclValue::from_number_with_type(
|
||||||
f.n,
|
f.n,
|
||||||
|
@ -133,7 +133,7 @@ pub async fn reduce(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
|||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// // Declare a function that sketches a decagon.
|
/// // Declare a function that sketches a decagon.
|
||||||
/// fn decagon(radius) {
|
/// fn decagon(radius) {
|
||||||
/// // Each side of the decagon is turned this many degrees from the previous angle.
|
/// // Each side of the decagon is turned this many radians from the previous angle.
|
||||||
/// stepAngle = (1/10) * TAU
|
/// stepAngle = (1/10) * TAU
|
||||||
///
|
///
|
||||||
/// // Start the decagon sketch at this point.
|
/// // Start the decagon sketch at this point.
|
||||||
|
@ -6,18 +6,31 @@ use kcl_derive_docs::stdlib;
|
|||||||
use super::args::FromArgs;
|
use super::args::FromArgs;
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
execution::{types::NumericType, ExecState, KclValue},
|
execution::{
|
||||||
|
types::{self, NumericType},
|
||||||
|
ExecState, KclValue,
|
||||||
|
},
|
||||||
std::args::{Args, TyF64},
|
std::args::{Args, TyF64},
|
||||||
|
CompilationError,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Compute the remainder after dividing `num` by `div`.
|
/// Compute the remainder after dividing `num` by `div`.
|
||||||
/// If `num` is negative, the result will be too.
|
/// If `num` is negative, the result will be too.
|
||||||
pub async fn rem(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
pub async fn rem(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||||
let n = args.get_unlabeled_kw_arg("number to divide")?;
|
let n: TyF64 = args.get_unlabeled_kw_arg("number to divide")?;
|
||||||
let d = args.get_kw_arg("divisor")?;
|
let d: TyF64 = args.get_kw_arg("divisor")?;
|
||||||
|
|
||||||
|
let (n, d, ty) = NumericType::combine_div(n, d);
|
||||||
|
if *types::CHECK_NUMERIC_TYPES && ty == NumericType::Unknown {
|
||||||
|
// TODO suggest how to fix this
|
||||||
|
exec_state.warn(CompilationError::err(
|
||||||
|
args.source_range,
|
||||||
|
"Remainder of numbers which have unknown or incompatible units.",
|
||||||
|
));
|
||||||
|
}
|
||||||
let remainder = inner_rem(n, d);
|
let remainder = inner_rem(n, d);
|
||||||
|
|
||||||
Ok(args.make_user_val_from_f64(remainder))
|
Ok(args.make_user_val_from_f64_with_type(TyF64::new(remainder, ty)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the remainder after dividing `num` by `div`.
|
/// Compute the remainder after dividing `num` by `div`.
|
||||||
@ -243,11 +256,19 @@ fn inner_ceil(num: f64) -> Result<f64, KclError> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the minimum of the given arguments.
|
/// Compute the minimum of the given arguments.
|
||||||
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 = args.get_number_array()?;
|
let nums = args.get_number_array_with_types()?;
|
||||||
|
let (nums, ty) = NumericType::combine_eq_array(&nums);
|
||||||
|
if *types::CHECK_NUMERIC_TYPES && ty == NumericType::Unknown {
|
||||||
|
// TODO suggest how to fix this
|
||||||
|
exec_state.warn(CompilationError::err(
|
||||||
|
args.source_range,
|
||||||
|
"Calling `min` on numbers which have unknown or incompatible units.",
|
||||||
|
));
|
||||||
|
}
|
||||||
let result = inner_min(nums);
|
let result = inner_min(nums);
|
||||||
|
|
||||||
Ok(args.make_user_val_from_f64(result))
|
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, ty)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the minimum of the given arguments.
|
/// Compute the minimum of the given arguments.
|
||||||
@ -280,11 +301,19 @@ fn inner_min(args: Vec<f64>) -> f64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the maximum of the given arguments.
|
/// Compute the maximum of the given arguments.
|
||||||
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 = args.get_number_array()?;
|
let nums = args.get_number_array_with_types()?;
|
||||||
|
let (nums, ty) = NumericType::combine_eq_array(&nums);
|
||||||
|
if *types::CHECK_NUMERIC_TYPES && ty == NumericType::Unknown {
|
||||||
|
// TODO suggest how to fix this
|
||||||
|
exec_state.warn(CompilationError::err(
|
||||||
|
args.source_range,
|
||||||
|
"Calling `max` on numbers which have unknown or incompatible units.",
|
||||||
|
));
|
||||||
|
}
|
||||||
let result = inner_max(nums);
|
let result = inner_max(nums);
|
||||||
|
|
||||||
Ok(args.make_user_val_from_f64(result))
|
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, ty)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the maximum of the given arguments.
|
/// Compute the maximum of the given arguments.
|
||||||
|
@ -69,7 +69,7 @@ export fn cos(@num: number(rad)): number(_) {}
|
|||||||
/// |> startProfileAt([0, 0], %)
|
/// |> startProfileAt([0, 0], %)
|
||||||
/// |> angledLine({
|
/// |> angledLine({
|
||||||
/// angle = 50,
|
/// angle = 50,
|
||||||
/// length = 15 / sin(toDegrees(135)),
|
/// length = 15 / sin(toRadians(135)),
|
||||||
/// }, %)
|
/// }, %)
|
||||||
/// |> yLine(endAbsolute = 0)
|
/// |> yLine(endAbsolute = 0)
|
||||||
/// |> close()
|
/// |> close()
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 58 KiB |
Reference in New Issue
Block a user