Add int() function to KCL stdlib (#3116)

This commit is contained in:
Jonathan Tran
2024-07-25 21:18:52 -04:00
committed by GitHub
parent 029799215b
commit 4dd8a25fdd
8 changed files with 186 additions and 0 deletions

View File

@ -38,6 +38,7 @@ layout: manual
* [`helix`](kcl/helix)
* [`hole`](kcl/hole)
* [`import`](kcl/import)
* [`int`](kcl/int)
* [`lastSegX`](kcl/lastSegX)
* [`lastSegY`](kcl/lastSegY)
* [`legAngX`](kcl/legAngX)

43
docs/kcl/int.md Normal file

File diff suppressed because one or more lines are too long

View File

@ -85702,6 +85702,39 @@
"const model = import(\"tests/inputs/cube.step\")"
]
},
{
"name": "int",
"summary": "Converts a number to an integer.",
"description": "Callers should use floor(), ceil(), or other rounding function first if they care about how numbers with fractional parts are converted. If the number has a fractional part, it's truncated, moving the number towards zero.\nIf the number is NaN or has a magnitude, either positive or negative, that is too large to fit into the internal integer representation, the result is a runtime error.",
"tags": [
"convert"
],
"args": [
{
"name": "num",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
}
],
"returnValue": {
"name": "",
"type": "i64",
"schema": {
"type": "integer",
"format": "int64"
},
"required": true
},
"unpublished": false,
"deprecated": false,
"examples": [
"const sketch001 = startSketchOn('XZ')\n |> circle([0, 0], 2, %)\nconst extrude001 = extrude(5, sketch001)\n\nconst pattern01 = patternTransform(int(ceil(5 / 2)), (id) => {\n return { translate: [4 * id, 0, 0] }\n}, extrude001)"
]
},
{
"name": "lastSegX",
"summary": "Returns the last segment of x.",

View File

@ -343,6 +343,7 @@ pub fn get_type_string_from_schema(schema: &schemars::schema::Schema) -> Result<
return Ok((Primitive::Uuid.to_string(), false));
} else if format == "double"
|| format == "uint"
|| format == "int32"
|| format == "int64"
|| format == "uint8"
|| format == "uint32"

View File

@ -125,6 +125,10 @@ impl Args {
}))
}
pub(crate) fn make_user_val_from_i64(&self, n: i64) -> Result<MemoryItem, KclError> {
self.make_user_val_from_json(serde_json::Value::Number(serde_json::Number::from(n)))
}
pub fn make_user_val_from_f64(&self, f: f64) -> Result<MemoryItem, KclError> {
self.make_user_val_from_json(serde_json::Value::Number(serde_json::Number::from_f64(f).ok_or_else(
|| {

View File

@ -0,0 +1,102 @@
//! Conversions between types.
use derive_docs::stdlib;
use schemars::JsonSchema;
use crate::{
errors::{KclError, KclErrorDetails},
executor::{MemoryItem, SourceRange},
std::Args,
};
#[derive(Debug, PartialEq, Eq)]
enum ConversionError {
Nan,
TooLarge,
}
impl ConversionError {
pub fn into_kcl_error(self, source_range: SourceRange) -> KclError {
match self {
ConversionError::Nan => KclError::Semantic(KclErrorDetails {
message: "NaN cannot be converted to an integer".to_owned(),
source_ranges: vec![source_range],
}),
ConversionError::TooLarge => KclError::Semantic(KclErrorDetails {
message: "Number is too large to convert to integer".to_owned(),
source_ranges: vec![source_range],
}),
}
}
}
/// Converts a number to integer.
pub async fn int(args: Args) -> Result<MemoryItem, KclError> {
let num = args.get_number()?;
let converted = inner_int(num).map_err(|err| err.into_kcl_error(args.source_range))?;
args.make_user_val_from_i64(converted)
}
/// Converts a number to an integer.
///
/// Callers should use floor(), ceil(), or other rounding function first if they
/// care about how numbers with fractional parts are converted. If the number
/// has a fractional part, it's truncated, moving the number towards zero.
///
/// If the number is NaN or has a magnitude, either positive or negative, that
/// is too large to fit into the internal integer representation, the result is
/// a runtime error.
///
/// ```no_run
/// const sketch001 = startSketchOn('XZ')
/// |> circle([0, 0], 2, %)
/// const extrude001 = extrude(5, sketch001)
///
/// const pattern01 = patternTransform(int(ceil(5 / 2)), (id) => {
/// return { translate: [4 * id, 0, 0] }
/// }, extrude001)
/// ```
#[stdlib {
name = "int",
tags = ["convert"],
}]
fn inner_int(num: f64) -> Result<i64, ConversionError> {
if num.is_nan() {
return Err(ConversionError::Nan);
}
if num > 2_f64.powi(53) || num < -(2_f64.powi(53)) {
// 2^53 is the largest magnitude integer that can be represented in f64
// and accurately converted.
return Err(ConversionError::TooLarge);
}
Ok(num as i64)
}
#[cfg(test)]
mod tests {
use core::f64;
use pretty_assertions::assert_eq;
use super::*;
#[test]
fn test_inner_int() {
assert_eq!(inner_int(0.0), Ok(0));
assert_eq!(inner_int(-0.0), Ok(0));
assert_eq!(inner_int(3.0), Ok(3));
assert_eq!(inner_int(2.5), Ok(2));
assert_eq!(inner_int(-2.5), Ok(-2));
assert_eq!(inner_int(f64::NAN), Err(ConversionError::Nan));
assert_eq!(inner_int(f64::INFINITY), Err(ConversionError::TooLarge));
assert_eq!(inner_int(f64::NEG_INFINITY), Err(ConversionError::TooLarge));
assert_eq!(inner_int(2_f64.powi(53)), Ok(2_i64.pow(53)));
assert_eq!(inner_int(-(2_f64.powi(53))), Ok(-(2_i64.pow(53))));
// Note: 2_f64.powi(53) + 1.0 can't be represented.
assert_eq!(inner_int(2_f64.powi(53) + 2.0), Err(ConversionError::TooLarge));
assert_eq!(inner_int(-(2_f64.powi(53)) - 2.0), Err(ConversionError::TooLarge));
assert_eq!(inner_int(-(2_f64.powi(64))), Err(ConversionError::TooLarge));
}
}

View File

@ -2,6 +2,7 @@
pub mod args;
pub mod chamfer;
pub mod convert;
pub mod extrude;
pub mod fillet;
pub mod helix;
@ -45,6 +46,7 @@ lazy_static! {
Box::new(LegLen),
Box::new(LegAngX),
Box::new(LegAngY),
Box::new(crate::std::convert::Int),
Box::new(crate::std::extrude::Extrude),
Box::new(crate::std::segment::SegEndX),
Box::new(crate::std::segment::SegEndY),

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB