Kwargs: assert functions (#6406)
Closes https://github.com/KittyCAD/modeling-app/issues/6408
This commit is contained in:
@ -1830,18 +1830,18 @@ const bracket = startSketchOn(XY)
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_unary_operator_not_succeeds() {
|
||||
let ast = r#"
|
||||
fn returnTrue = () => { return !false }
|
||||
const t = true
|
||||
const f = false
|
||||
let notTrue = !t
|
||||
let notFalse = !f
|
||||
let c = !!true
|
||||
let d = !returnTrue()
|
||||
fn returnTrue() { return !false }
|
||||
t = true
|
||||
f = false
|
||||
notTrue = !t
|
||||
notFalse = !f
|
||||
c = !!true
|
||||
d = !returnTrue()
|
||||
|
||||
assert(!false, "expected to pass")
|
||||
assertIs(!false, error = "expected to pass")
|
||||
|
||||
fn check = (x) => {
|
||||
assert(!x, "expected argument to be false")
|
||||
assertIs(!x, error = "expected argument to be false")
|
||||
return true
|
||||
}
|
||||
check(false)
|
||||
|
@ -115,9 +115,9 @@ pub async fn reduce(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
||||
/// return sumSoFar
|
||||
/// */
|
||||
///
|
||||
/// // We use `assertEqual` to check that our `sum` function gives the
|
||||
/// // We use `assert` to check that our `sum` function gives the
|
||||
/// // expected result. It's good to check your work!
|
||||
/// assertEqual(sum([1, 2, 3]), 6, 0.00001, "1 + 2 + 3 summed is 6")
|
||||
/// assert(sum([1, 2, 3]), isEqualTo = 6, tolerance = 0.1, error = "1 + 2 + 3 summed is 6")
|
||||
/// ```
|
||||
/// ```no_run
|
||||
/// // This example works just like the previous example above, but it uses
|
||||
@ -126,9 +126,9 @@ pub async fn reduce(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
||||
/// arr = [1, 2, 3]
|
||||
/// sum = reduce(arr, 0, (i, result_so_far) => { return i + result_so_far })
|
||||
///
|
||||
/// // We use `assertEqual` to check that our `sum` function gives the
|
||||
/// // We use `assert` to check that our `sum` function gives the
|
||||
/// // expected result. It's good to check your work!
|
||||
/// assertEqual(sum, 6, 0.00001, "1 + 2 + 3 summed is 6")
|
||||
/// assert(sum, isEqualTo = 6, tolerance = 0.1, error = "1 + 2 + 3 summed is 6")
|
||||
/// ```
|
||||
/// ```no_run
|
||||
/// // Declare a function that sketches a decagon.
|
||||
@ -224,7 +224,7 @@ async fn call_reduce_closure(
|
||||
/// ```no_run
|
||||
/// arr = [1, 2, 3]
|
||||
/// new_arr = push(arr, 4)
|
||||
/// assertEqual(new_arr[3], 4, 0.00001, "4 was added to the end of the array")
|
||||
/// assert(new_arr[3], isEqualTo = 4, tolerance = 0.1, error = "4 was added to the end of the array")
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "push",
|
||||
@ -260,9 +260,9 @@ pub async fn push(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
||||
/// ```no_run
|
||||
/// arr = [1, 2, 3, 4]
|
||||
/// new_arr = pop(arr)
|
||||
/// assertEqual(new_arr[0], 1, 0.00001, "1 is the first element of the array")
|
||||
/// assertEqual(new_arr[1], 2, 0.00001, "2 is the second element of the array")
|
||||
/// assertEqual(new_arr[2], 3, 0.00001, "3 is the third element of the array")
|
||||
/// assert(new_arr[0], isEqualTo = 1, tolerance = 0.00001, error = "1 is the first element of the array")
|
||||
/// assert(new_arr[1], isEqualTo = 2, tolerance = 0.00001, error = "2 is the second element of the array")
|
||||
/// assert(new_arr[2], isEqualTo = 3, tolerance = 0.00001, error = "3 is the third element of the array")
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "pop",
|
||||
|
@ -21,135 +21,165 @@ async fn _assert(value: bool, message: &str, args: &Args) -> Result<(), KclError
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn assert_is(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let actual = args.get_unlabeled_kw_arg("actual")?;
|
||||
let error = args.get_kw_arg_opt("error")?;
|
||||
inner_assert_is(actual, error, &args).await?;
|
||||
Ok(KclValue::none())
|
||||
}
|
||||
|
||||
/// Check that the provided value is true, or raise a [KclError]
|
||||
/// with the provided description.
|
||||
pub async fn assert(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let (data, description): (bool, String) = args.get_data()?;
|
||||
inner_assert(data, &description, &args).await?;
|
||||
let actual = args.get_unlabeled_kw_arg("actual")?;
|
||||
let gt = args.get_kw_arg_opt("isGreaterThan")?;
|
||||
let lt = args.get_kw_arg_opt("isLessThan")?;
|
||||
let gte = args.get_kw_arg_opt("isGreaterThanOrEqual")?;
|
||||
let lte = args.get_kw_arg_opt("isLessThanOrEqual")?;
|
||||
let eq = args.get_kw_arg_opt("isEqualTo")?;
|
||||
let tolerance = args.get_kw_arg_opt("tolerance")?;
|
||||
let error = args.get_kw_arg_opt("error")?;
|
||||
inner_assert(actual, gt, lt, gte, lte, eq, tolerance, error, &args).await?;
|
||||
Ok(KclValue::none())
|
||||
}
|
||||
|
||||
/// Check a value at runtime, and raise an error if the argument provided
|
||||
/// is false.
|
||||
/// Asserts that a value is the boolean value true.
|
||||
/// ```no_run
|
||||
/// kclIsFun = true
|
||||
/// assertIs(kclIsFun)
|
||||
/// ```
|
||||
#[stdlib{
|
||||
name = "assertIs",
|
||||
keywords = true,
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
actual = { docs = "Value to check. If this is the boolean value true, assert passes. Otherwise it fails." },
|
||||
error = { docs = "If the value was false, the program will terminate with this error message" },
|
||||
}
|
||||
}]
|
||||
async fn inner_assert_is(actual: bool, error: Option<String>, args: &Args) -> Result<(), KclError> {
|
||||
let error_msg = match &error {
|
||||
Some(x) => x,
|
||||
None => "should have been true, but it was not",
|
||||
};
|
||||
_assert(actual, error_msg, args).await
|
||||
}
|
||||
|
||||
/// Check a value meets some expected conditions at runtime. Program terminates with an error if conditions aren't met.
|
||||
/// If you provide multiple conditions, they will all be checked and all must be met.
|
||||
///
|
||||
/// ```no_run
|
||||
/// myVar = true
|
||||
/// assert(myVar, "should always be true")
|
||||
/// n = 10
|
||||
/// assert(n, isEqualTo = 10)
|
||||
/// assert(n, isGreaterThanOrEqual = 0, isLessThan = 100, error = "number should be between 0 and 100")
|
||||
/// assert(1.0000000000012, isEqualTo = 1, tolerance = 0.0001, error = "number should be almost exactly 1")
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "assert",
|
||||
}]
|
||||
async fn inner_assert(data: bool, message: &str, args: &Args) -> Result<(), KclError> {
|
||||
_assert(data, message, args).await
|
||||
}
|
||||
|
||||
pub async fn assert_lt(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let (left, right, description): (TyF64, TyF64, String) = args.get_data()?;
|
||||
inner_assert_lt(left.n, right.n, &description, &args).await?;
|
||||
Ok(KclValue::none())
|
||||
}
|
||||
|
||||
/// Check that a numerical value is less than to another at runtime,
|
||||
/// otherwise raise an error.
|
||||
///
|
||||
/// ```no_run
|
||||
/// assertLessThan(1, 2, "1 is less than 2")
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "assertLessThan",
|
||||
}]
|
||||
async fn inner_assert_lt(left: f64, right: f64, message: &str, args: &Args) -> Result<(), KclError> {
|
||||
_assert(left < right, message, args).await
|
||||
}
|
||||
|
||||
pub async fn assert_gt(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let (left, right, description): (TyF64, TyF64, String) = args.get_data()?;
|
||||
inner_assert_gt(left.n, right.n, &description, &args).await?;
|
||||
Ok(KclValue::none())
|
||||
}
|
||||
|
||||
/// Check that a numerical value equals another at runtime,
|
||||
/// otherwise raise an error.
|
||||
///
|
||||
/// ```no_run
|
||||
/// n = 1.0285
|
||||
/// o = 1.0286
|
||||
/// assertEqual(n, o, 0.01, "n is within the given tolerance for o")
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "assertEqual",
|
||||
}]
|
||||
async fn inner_assert_equal(left: f64, right: f64, epsilon: f64, message: &str, args: &Args) -> Result<(), KclError> {
|
||||
if epsilon <= 0.0 {
|
||||
Err(KclError::Type(KclErrorDetails {
|
||||
message: "assertEqual epsilon must be greater than zero".to_owned(),
|
||||
source_ranges: vec![args.source_range],
|
||||
}))
|
||||
} else if (right - left).abs() < epsilon {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(KclError::Type(KclErrorDetails {
|
||||
message: format!("assert failed because {left} != {right}: {message}"),
|
||||
source_ranges: vec![args.source_range],
|
||||
}))
|
||||
keywords = true,
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
actual = { docs = "Value to check. It will be compared with one of the comparison arguments." },
|
||||
is_greater_than = { docs = "Comparison argument. If given, checks the `actual` value is greater than this." },
|
||||
is_less_than = { docs = "Comparison argument. If given, checks the `actual` value is less than this." },
|
||||
is_greater_than_or_equal = { docs = "Comparison argument. If given, checks the `actual` value is greater than or equal to this." },
|
||||
is_less_than_or_equal = { docs = "Comparison argument. If given, checks the `actual` value is less than or equal to this." },
|
||||
is_equal_to = { docs = "Comparison argument. If given, checks the `actual` value is less than or equal to this.", include_in_snippet = true },
|
||||
tolerance = { docs = "If `isEqualTo` is used, this is the tolerance to allow for the comparison. This tolerance is used because KCL's number system has some floating-point imprecision when used with very large decimal places." },
|
||||
error = { docs = "If the value was false, the program will terminate with this error message" },
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn assert_equal(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let (left, right, epsilon, description): (TyF64, TyF64, TyF64, String) = args.get_data()?;
|
||||
inner_assert_equal(left.n, right.n, epsilon.n, &description, &args).await?;
|
||||
Ok(KclValue::none())
|
||||
}
|
||||
|
||||
/// Check that a numerical value is greater than another at runtime,
|
||||
/// otherwise raise an error.
|
||||
///
|
||||
/// ```no_run
|
||||
/// assertGreaterThan(2, 1, "2 is greater than 1")
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "assertGreaterThan",
|
||||
}]
|
||||
async fn inner_assert_gt(left: f64, right: f64, message: &str, args: &Args) -> Result<(), KclError> {
|
||||
_assert(left > right, message, args).await
|
||||
}
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn inner_assert(
|
||||
actual: TyF64,
|
||||
is_greater_than: Option<TyF64>,
|
||||
is_less_than: Option<TyF64>,
|
||||
is_greater_than_or_equal: Option<TyF64>,
|
||||
is_less_than_or_equal: Option<TyF64>,
|
||||
is_equal_to: Option<TyF64>,
|
||||
tolerance: Option<TyF64>,
|
||||
error: Option<String>,
|
||||
args: &Args,
|
||||
) -> Result<(), KclError> {
|
||||
// Validate the args
|
||||
let no_condition_given = [
|
||||
&is_greater_than,
|
||||
&is_less_than,
|
||||
&is_greater_than_or_equal,
|
||||
&is_less_than_or_equal,
|
||||
&is_equal_to,
|
||||
]
|
||||
.iter()
|
||||
.all(|cond| cond.is_none());
|
||||
if no_condition_given {
|
||||
return Err(KclError::Type(KclErrorDetails {
|
||||
message: "You must provide at least one condition in this assert (for example, isEqualTo)".to_owned(),
|
||||
source_ranges: vec![args.source_range],
|
||||
}));
|
||||
}
|
||||
|
||||
pub async fn assert_lte(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let (left, right, description): (TyF64, TyF64, String) = args.get_data()?;
|
||||
inner_assert_lte(left.n, right.n, &description, &args).await?;
|
||||
Ok(KclValue::none())
|
||||
}
|
||||
if tolerance.is_some() && is_equal_to.is_none() {
|
||||
return Err(KclError::Type(KclErrorDetails {
|
||||
message:
|
||||
"The `tolerance` arg is only used with `isEqualTo`. Either remove `tolerance` or add an `isEqualTo` arg."
|
||||
.to_owned(),
|
||||
source_ranges: vec![args.source_range],
|
||||
}));
|
||||
}
|
||||
|
||||
/// Check that a numerical value is less than or equal to another at runtime,
|
||||
/// otherwise raise an error.
|
||||
///
|
||||
/// ```no_run
|
||||
/// assertLessThanOrEq(1, 2, "1 is less than 2")
|
||||
/// assertLessThanOrEq(1, 1, "1 is equal to 1")
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "assertLessThanOrEq",
|
||||
}]
|
||||
async fn inner_assert_lte(left: f64, right: f64, message: &str, args: &Args) -> Result<(), KclError> {
|
||||
_assert(left <= right, message, args).await
|
||||
}
|
||||
let suffix = if let Some(err_string) = error {
|
||||
format!(": {err_string}")
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
let actual = actual.n;
|
||||
|
||||
pub async fn assert_gte(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let (left, right, description): (TyF64, TyF64, String) = args.get_data()?;
|
||||
inner_assert_gte(left.n, right.n, &description, &args).await?;
|
||||
Ok(KclValue::none())
|
||||
}
|
||||
|
||||
/// Check that a numerical value is greater than or equal to another at runtime,
|
||||
/// otherwise raise an error.
|
||||
///
|
||||
/// ```no_run
|
||||
/// assertGreaterThanOrEq(2, 1, "2 is greater than 1")
|
||||
/// assertGreaterThanOrEq(1, 1, "1 is equal to 1")
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "assertGreaterThanOrEq",
|
||||
}]
|
||||
async fn inner_assert_gte(left: f64, right: f64, message: &str, args: &Args) -> Result<(), KclError> {
|
||||
_assert(left >= right, message, args).await
|
||||
// Run the checks.
|
||||
if let Some(exp) = is_greater_than {
|
||||
let exp = exp.n;
|
||||
_assert(
|
||||
actual > exp,
|
||||
&format!("Expected {actual} to be greater than {exp} but it wasn't{suffix}"),
|
||||
args,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
if let Some(exp) = is_less_than {
|
||||
let exp = exp.n;
|
||||
_assert(
|
||||
actual < exp,
|
||||
&format!("Expected {actual} to be less than {exp} but it wasn't{suffix}"),
|
||||
args,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
if let Some(exp) = is_greater_than_or_equal {
|
||||
let exp = exp.n;
|
||||
_assert(
|
||||
actual >= exp,
|
||||
&format!("Expected {actual} to be greater than or equal to {exp} but it wasn't{suffix}"),
|
||||
args,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
if let Some(exp) = is_less_than_or_equal {
|
||||
let exp = exp.n;
|
||||
_assert(
|
||||
actual <= exp,
|
||||
&format!("Expected {actual} to be less than or equal to {exp} but it wasn't{suffix}"),
|
||||
args,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
if let Some(exp) = is_equal_to {
|
||||
let exp = exp.n;
|
||||
let tolerance = tolerance.map(|e| e.n).unwrap_or(0.0000000001);
|
||||
_assert(
|
||||
(actual - exp).abs() < tolerance,
|
||||
&format!("Expected {actual} to be equal to {exp} but it wasn't{suffix}"),
|
||||
args,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ pub async fn int(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
|
||||
///
|
||||
/// ```no_run
|
||||
/// n = int(ceil(5/2))
|
||||
/// assertEqual(n, 3, 0.0001, "5/2 = 2.5, rounded up makes 3")
|
||||
/// assert(n, isEqualTo = 3, error = "5/2 = 2.5, rounded up makes 3")
|
||||
/// // Draw n cylinders.
|
||||
/// startSketchOn('XZ')
|
||||
/// |> circle(center = [0, 0], radius = 2 )
|
||||
|
@ -36,12 +36,12 @@ pub async fn rem(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kcl
|
||||
/// If `num` is negative, the result will be too.
|
||||
///
|
||||
/// ```no_run
|
||||
/// assertEqual(rem( 7, divisor = 4), 3, 0.01, "remainder is 3" )
|
||||
/// assertEqual(rem(-7, divisor = 4), -3, 0.01, "remainder is -3")
|
||||
/// assertEqual(rem( 7, divisor = -4), 3, 0.01, "remainder is 3" )
|
||||
/// assertEqual(rem( 6, divisor = 2.5), 1, 0.01, "remainder is 1" )
|
||||
/// assertEqual(rem( 6.5, divisor = 2.5), 1.5, 0.01, "remainder is 1.5" )
|
||||
/// assertEqual(rem( 6.5, divisor = 2), 0.5, 0.01, "remainder is 0.5" )
|
||||
/// assert(rem( 7, divisor = 4), isEqualTo = 3, error = "remainder is 3")
|
||||
/// assert(rem(-7, divisor = 4), isEqualTo = -3, error = "remainder is -3")
|
||||
/// assert(rem( 7, divisor = -4), isEqualTo = 3, error = "remainder is 3")
|
||||
/// assert(rem( 6, divisor = 2.5), isEqualTo = 1, error = "remainder is 1")
|
||||
/// assert(rem( 6.5, divisor = 2.5), isEqualTo = 1.5, error = "remainder is 1.5")
|
||||
/// assert(rem( 6.5, divisor = 2), isEqualTo = 0.5, error = "remainder is 0.5")
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "rem",
|
||||
|
@ -137,11 +137,7 @@ lazy_static! {
|
||||
Box::new(crate::std::units::FromCm),
|
||||
Box::new(crate::std::units::FromYd),
|
||||
Box::new(crate::std::assert::Assert),
|
||||
Box::new(crate::std::assert::AssertEqual),
|
||||
Box::new(crate::std::assert::AssertLessThan),
|
||||
Box::new(crate::std::assert::AssertGreaterThan),
|
||||
Box::new(crate::std::assert::AssertLessThanOrEq),
|
||||
Box::new(crate::std::assert::AssertGreaterThanOrEq),
|
||||
Box::new(crate::std::assert::AssertIs),
|
||||
Box::new(crate::std::transform::Scale),
|
||||
Box::new(crate::std::transform::Translate),
|
||||
Box::new(crate::std::transform::Rotate),
|
||||
|
Reference in New Issue
Block a user