Add default planes to std (#5433)

* Type ascription

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Support negation of planes

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Add default planes to std

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Don't double wrap docs files in const_

Signed-off-by: Nick Cameron <nrc@ncameron.org>

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
This commit is contained in:
Nick Cameron
2025-02-27 15:58:58 +13:00
committed by GitHub
parent 12edb6375d
commit 89bc93e4cd
50 changed files with 2135 additions and 433 deletions

View File

@ -29,7 +29,7 @@ angledLine(data: AngledLineData, sketch: Sketch, tag?: TagDeclarator) -> Sketch
### Examples
```js
exampleSketch = startSketchOn('XZ')
exampleSketch = startSketchOn(XZ)
|> startProfileAt([0, 0], %)
|> yLineTo(15, %)
|> angledLine({ angle = 30, length = 15 }, %)

View File

@ -29,7 +29,7 @@ angledLineOfXLength(data: AngledLineData, sketch: Sketch, tag?: TagDeclarator) -
### Examples
```js
sketch001 = startSketchOn('XZ')
sketch001 = startSketchOn(XZ)
|> startProfileAt([0, 0], %)
|> angledLineOfXLength({ angle = 45, length = 10 }, %, $edge1)
|> angledLineOfXLength({ angle = -15, length = 20 }, %, $edge2)

View File

@ -29,7 +29,7 @@ angledLineOfYLength(data: AngledLineData, sketch: Sketch, tag?: TagDeclarator) -
### Examples
```js
exampleSketch = startSketchOn('XZ')
exampleSketch = startSketchOn(XZ)
|> startProfileAt([0, 0], %)
|> line(end = [10, 0])
|> angledLineOfYLength({ angle = 45, length = 10 }, %)

View File

@ -29,7 +29,7 @@ angledLineThatIntersects(data: AngledLineThatIntersectsData, sketch: Sketch, tag
### Examples
```js
exampleSketch = startSketchOn('XZ')
exampleSketch = startSketchOn(XZ)
|> startProfileAt([0, 0], %)
|> line(endAbsolute = [5, 10])
|> line(endAbsolute = [-10, 10], tag = $lineToIntersect)

View File

@ -29,7 +29,7 @@ angledLineToX(data: AngledLineToData, sketch: Sketch, tag?: TagDeclarator) -> Sk
### Examples
```js
exampleSketch = startSketchOn('XZ')
exampleSketch = startSketchOn(XZ)
|> startProfileAt([0, 0], %)
|> angledLineToX({ angle = 30, to = 10 }, %)
|> line(end = [0, 10])

View File

@ -29,7 +29,7 @@ angledLineToY(data: AngledLineToData, sketch: Sketch, tag?: TagDeclarator) -> Sk
### Examples
```js
exampleSketch = startSketchOn('XZ')
exampleSketch = startSketchOn(XZ)
|> startProfileAt([0, 0], %)
|> angledLineToY({ angle = 60, to = 20 }, %)
|> line(end = [-20, 0])

View File

@ -31,7 +31,7 @@ arc(data: ArcData, sketch: Sketch, tag?: TagDeclarator) -> Sketch
### Examples
```js
exampleSketch = startSketchOn('XZ')
exampleSketch = startSketchOn(XZ)
|> startProfileAt([0, 0], %)
|> line(end = [10, 0])
|> arc({

View File

@ -29,7 +29,7 @@ arcTo(data: ArcToData, sketch: Sketch, tag?: TagDeclarator) -> Sketch
### Examples
```js
exampleSketch = startSketchOn('XZ')
exampleSketch = startSketchOn(XZ)
|> startProfileAt([0, 0], %)
|> arcTo({ end = [10, 0], interior = [5, 5] }, %)
|> close()

View File

@ -29,7 +29,7 @@ bezierCurve(data: BezierData, sketch: Sketch, tag?: TagDeclarator) -> Sketch
### Examples
```js
exampleSketch = startSketchOn('XZ')
exampleSketch = startSketchOn(XZ)
|> startProfileAt([0, 0], %)
|> line(end = [0, 10])
|> bezierCurve({

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,15 +0,0 @@
---
title: "QUARTER_TURN"
excerpt: ""
layout: manual
---
```js
QUARTER_TURN: number(deg) = 90deg
```

File diff suppressed because one or more lines are too long

View File

@ -1,15 +0,0 @@
---
title: "THREE_QUARTER_TURN"
excerpt: ""
layout: manual
---
```js
THREE_QUARTER_TURN: number(deg) = 270deg
```

View File

@ -1,5 +1,5 @@
---
title: "ZERO"
title: "std::XY"
excerpt: ""
layout: manual
---
@ -9,7 +9,7 @@ layout: manual
```js
ZERO: number = 0
std::XY
```

View File

@ -1,5 +1,5 @@
---
title: "HALF_TURN"
title: "std::XZ"
excerpt: ""
layout: manual
---
@ -9,7 +9,7 @@ layout: manual
```js
HALF_TURN: number(deg) = 180deg
std::XZ
```

15
docs/kcl/const_std-YZ.md Normal file
View File

@ -0,0 +1,15 @@
---
title: "std::YZ"
excerpt: ""
layout: manual
---
```js
std::YZ
```

View File

@ -28,7 +28,7 @@ hole(holeSketch: SketchSet, sketch: Sketch) -> Sketch
### Examples
```js
exampleSketch = startSketchOn('XY')
exampleSketch = startSketchOn(XY)
|> startProfileAt([0, 0], %)
|> line(end = [0, 5])
|> line(end = [5, 0])
@ -44,7 +44,7 @@ example = extrude(exampleSketch, length = 1)
```js
fn squareHoleSketch() {
squareSketch = startSketchOn('-XZ')
squareSketch = startSketchOn(-XZ)
|> startProfileAt([-1, -1], %)
|> line(end = [2, 0])
|> line(end = [0, 2])
@ -53,7 +53,7 @@ fn squareHoleSketch() {
return squareSketch
}
exampleSketch = startSketchOn('-XZ')
exampleSketch = startSketchOn(-XZ)
|> circle({ center = [0, 0], radius = 3 }, %)
|> hole(squareHoleSketch(), %)
example = extrude(exampleSketch, length = 1)

View File

@ -19,6 +19,9 @@ layout: manual
* [`HALF_TURN`](kcl/const_std-HALF_TURN)
* [`QUARTER_TURN`](kcl/const_std-QUARTER_TURN)
* [`THREE_QUARTER_TURN`](kcl/const_std-THREE_QUARTER_TURN)
* [`XY`](kcl/const_std-XY)
* [`XZ`](kcl/const_std-XZ)
* [`YZ`](kcl/const_std-YZ)
* [`ZERO`](kcl/const_std-ZERO)
* [`abs`](kcl/abs)
* [`acos`](kcl/acos)

View File

@ -30,7 +30,7 @@ line(sketch: Sketch, endAbsolute?: [number], end?: [number], tag?: TagDeclarator
### Examples
```js
triangle = startSketchOn("XZ")
triangle = startSketchOn(XZ)
|> startProfileAt([0, 0], %)
// The 'end' argument means it ends at exactly [10, 0].
// This is an absolute measurement, it is NOT relative to
@ -41,7 +41,7 @@ triangle = startSketchOn("XZ")
|> close()
|> extrude(length = 5)
box = startSketchOn("XZ")
box = startSketchOn(XZ)
|> startProfileAt([10, 10], %)
// The 'to' argument means move the pen this much.
// So, [10, 0] is a relative distance away from the current point.

View File

@ -27,7 +27,7 @@ profileStart(sketch: Sketch) -> [number]
### Examples
```js
sketch001 = startSketchOn('XY')
sketch001 = startSketchOn(XY)
|> startProfileAt([5, 2], %)
|> angledLine({ angle = 120, length = 50 }, %, $seg01)
|> angledLine({

View File

@ -27,7 +27,7 @@ profileStartX(sketch: Sketch) -> number
### Examples
```js
sketch001 = startSketchOn('XY')
sketch001 = startSketchOn(XY)
|> startProfileAt([5, 2], %)
|> angledLine([-26.6, 50], %)
|> angledLine([90, 50], %)

View File

@ -27,7 +27,7 @@ profileStartY(sketch: Sketch) -> number
### Examples
```js
sketch001 = startSketchOn('XY')
sketch001 = startSketchOn(XY)
|> startProfileAt([5, 2], %)
|> angledLine({ angle = -60, length = 14 }, %)
|> angledLineToY({ angle = 30, to = profileStartY(%) }, %)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -29,7 +29,7 @@ tangentialArc(data: TangentialArcData, sketch: Sketch, tag?: TagDeclarator) -> S
### Examples
```js
exampleSketch = startSketchOn('XZ')
exampleSketch = startSketchOn(XZ)
|> startProfileAt([0, 0], %)
|> angledLine({ angle = 60, length = 10 }, %)
|> tangentialArc({ radius = 10, offset = -120 }, %)

View File

@ -29,7 +29,7 @@ tangentialArcTo(to: [number], sketch: Sketch, tag?: TagDeclarator) -> Sketch
### Examples
```js
exampleSketch = startSketchOn('XZ')
exampleSketch = startSketchOn(XZ)
|> startProfileAt([0, 0], %)
|> angledLine({ angle = 60, length = 10 }, %)
|> tangentialArcTo([15, 15], %)

View File

@ -29,7 +29,7 @@ tangentialArcToRelative(delta: [number], sketch: Sketch, tag?: TagDeclarator) ->
### Examples
```js
exampleSketch = startSketchOn('XZ')
exampleSketch = startSketchOn(XZ)
|> startProfileAt([0, 0], %)
|> angledLine({ angle = 45, length = 10 }, %)
|> tangentialArcToRelative([0, -10], %)

View File

@ -34,6 +34,18 @@ A custom plane.
----
A custom plane which has not been sent to the engine. It must be sent before it is used.
**enum:** `Uninit`
----

View File

@ -29,7 +29,7 @@ xLine(length: number, sketch: Sketch, tag?: TagDeclarator) -> Sketch
### Examples
```js
exampleSketch = startSketchOn('XZ')
exampleSketch = startSketchOn(XZ)
|> startProfileAt([0, 0], %)
|> xLine(15, %)
|> angledLine({ angle = 80, length = 15 }, %)

View File

@ -29,7 +29,7 @@ xLineTo(to: number, sketch: Sketch, tag?: TagDeclarator) -> Sketch
### Examples
```js
exampleSketch = startSketchOn('XZ')
exampleSketch = startSketchOn(XZ)
|> startProfileAt([0, 0], %)
|> xLineTo(15, %)
|> angledLine({ angle = 80, length = 15 }, %)

View File

@ -29,7 +29,7 @@ yLine(length: number, sketch: Sketch, tag?: TagDeclarator) -> Sketch
### Examples
```js
exampleSketch = startSketchOn('XZ')
exampleSketch = startSketchOn(XZ)
|> startProfileAt([0, 0], %)
|> yLine(15, %)
|> angledLine({ angle = 30, length = 15 }, %)

View File

@ -29,7 +29,7 @@ yLineTo(to: number, sketch: Sketch, tag?: TagDeclarator) -> Sketch
### Examples
```js
exampleSketch = startSketchOn("XZ")
exampleSketch = startSketchOn(XZ)
|> startProfileAt([0, 0], %)
|> angledLine({ angle = 50, length = 45 }, %)
|> yLineTo(0, %)

View File

@ -8,21 +8,22 @@ use crate::{
execution::{
annotations,
cad_op::{OpArg, OpKclValue, Operation},
kcl_value::{FunctionSource, NumericType},
kcl_value::{FunctionSource, NumericType, PrimitiveType, RuntimeType},
memory,
state::ModuleState,
BodyType, EnvironmentRef, ExecState, ExecutorContext, KclValue, Metadata, TagEngineInfo, TagIdentifier,
BodyType, EnvironmentRef, ExecState, ExecutorContext, KclValue, Metadata, Plane, PlaneType, Point3d,
TagEngineInfo, TagIdentifier,
},
modules::{ModuleId, ModulePath, ModuleRepr},
parsing::ast::types::{
Annotation, ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem,
CallExpression, CallExpressionKw, Expr, FunctionExpression, IfExpression, ImportPath, ImportSelector,
ItemVisibility, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Node, NodeRef,
ObjectExpression, PipeExpression, Program, TagDeclarator, UnaryExpression, UnaryOperator,
ObjectExpression, PipeExpression, Program, TagDeclarator, Type, UnaryExpression, UnaryOperator,
},
source_range::SourceRange,
std::{
args::{Arg, KwArgs},
args::{Arg, FromKclValue, KwArgs},
FunctionKind,
},
CompilationError,
@ -586,11 +587,89 @@ impl ExecutorContext {
// TODO this lets us use the label as a variable name, but not as a tag in most cases
result
}
Expr::AscribedExpression(expr) => {
let result = self
.execute_expr(&expr.expr, exec_state, metadata, &[], statement_kind)
.await?;
coerce(result, &expr.ty, exec_state).map_err(|value| {
KclError::Semantic(KclErrorDetails {
message: format!(
"could not coerce {} value to type {}",
value.human_friendly_type(),
expr.ty
),
source_ranges: vec![expr.into()],
})
})?
}
};
Ok(item)
}
}
fn coerce(value: KclValue, ty: &Node<Type>, exec_state: &mut ExecState) -> Result<KclValue, KclValue> {
let ty = RuntimeType::from_parsed(ty.inner.clone(), &exec_state.mod_local.settings).ok_or_else(|| value.clone())?;
if value.has_type(&ty) {
return Ok(value);
}
// TODO coerce numeric types
if let KclValue::Object { value, meta } = value {
return match ty {
RuntimeType::Primitive(PrimitiveType::Plane) => {
let origin = value
.get("origin")
.and_then(Point3d::from_kcl_val)
.ok_or_else(|| KclValue::Object {
value: value.clone(),
meta: meta.clone(),
})?;
let x_axis = value
.get("xAxis")
.and_then(Point3d::from_kcl_val)
.ok_or_else(|| KclValue::Object {
value: value.clone(),
meta: meta.clone(),
})?;
let y_axis = value
.get("yAxis")
.and_then(Point3d::from_kcl_val)
.ok_or_else(|| KclValue::Object {
value: value.clone(),
meta: meta.clone(),
})?;
let z_axis = value
.get("zAxis")
.and_then(Point3d::from_kcl_val)
.ok_or_else(|| KclValue::Object {
value: value.clone(),
meta: meta.clone(),
})?;
let id = exec_state.global.id_generator.next_uuid();
let plane = Plane {
id,
artifact_id: id.into(),
origin,
x_axis,
y_axis,
z_axis,
value: PlaneType::Uninit,
// TODO use length unit from origin
units: exec_state.length_unit(),
meta,
};
Ok(KclValue::Plane { value: Box::new(plane) })
}
_ => Err(KclValue::Object { value, meta }),
};
}
Err(value)
}
impl BinaryPart {
#[async_recursion]
pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
@ -882,9 +961,19 @@ impl Node<UnaryExpression> {
ty: ty.clone(),
})
}
KclValue::Plane { value } => {
let mut plane = value.clone();
plane.z_axis.x *= -1.0;
plane.z_axis.y *= -1.0;
plane.z_axis.z *= -1.0;
plane.value = PlaneType::Uninit;
plane.id = exec_state.next_uuid();
Ok(KclValue::Plane { value: plane })
}
_ => Err(KclError::Semantic(KclErrorDetails {
message: format!(
"You can only negate numbers, but this is a {}",
"You can only negate numbers or planes, but this is a {}",
value.human_friendly_type()
),
source_ranges: vec![self.into()],
@ -1950,6 +2039,65 @@ mod test {
}
}
#[tokio::test(flavor = "multi_thread")]
async fn ascription() {
let program = r#"
a = 42: number
b = a: number
p = {
origin = { x = 0, y = 0, z = 0 },
xAxis = { x = 1, y = 0, z = 0 },
yAxis = { x = 0, y = 1, z = 0 },
zAxis = { x = 0, y = 0, z = 1 }
}: Plane
"#;
let result = parse_execute(program).await.unwrap();
let mem = result.3.memory();
assert!(matches!(
mem.get_from("p", result.1, SourceRange::default()).unwrap(),
KclValue::Plane { .. }
));
let program = r#"
a = 42: string
"#;
let result = parse_execute(program).await;
assert!(result
.unwrap_err()
.to_string()
.contains("could not coerce number value to type string"));
let program = r#"
a = 42: Plane
"#;
let result = parse_execute(program).await;
assert!(result
.unwrap_err()
.to_string()
.contains("could not coerce number value to type Plane"));
}
#[tokio::test(flavor = "multi_thread")]
async fn neg_plane() {
let program = r#"
p = {
origin = { x = 0, y = 0, z = 0 },
xAxis = { x = 1, y = 0, z = 0 },
yAxis = { x = 0, y = 1, z = 0 },
zAxis = { x = 0, y = 0, z = 1 }
}: Plane
p2 = -p
"#;
let result = parse_execute(program).await.unwrap();
let mem = result.3.memory();
match mem.get_from("p2", result.1, SourceRange::default()).unwrap() {
KclValue::Plane { value } => assert_eq!(value.z_axis.z, -1.0),
_ => unreachable!(),
}
}
#[tokio::test(flavor = "multi_thread")]
async fn multiple_returns() {
let program = r#"fn foo() {

View File

@ -13,6 +13,7 @@ use crate::{
errors::KclError,
execution::{ExecState, Metadata, TagEngineInfo, TagIdentifier, UnitLen},
parsing::ast::types::{Node, NodeRef, TagDeclarator, TagNode},
std::sketch::PlaneData,
};
type Point2D = kcmc::shared::Point2d<f64>;
@ -267,10 +268,82 @@ pub struct Plane {
}
impl Plane {
pub(crate) fn from_plane_data(value: crate::std::sketch::PlaneData, exec_state: &mut ExecState) -> Self {
pub(crate) fn into_plane_data(self) -> PlaneData {
if self.origin == Point3d::new(0.0, 0.0, 0.0) {
match self {
Self {
origin: Point3d { x: 0.0, y: 0.0, z: 0.0 },
x_axis: Point3d { x: 1.0, y: 0.0, z: 0.0 },
y_axis: Point3d { x: 0.0, y: 1.0, z: 0.0 },
z_axis: Point3d { x: 0.0, y: 0.0, z: 1.0 },
..
} => return PlaneData::XY,
Self {
origin: Point3d { x: 0.0, y: 0.0, z: 0.0 },
x_axis: Point3d { x: 1.0, y: 0.0, z: 0.0 },
y_axis: Point3d { x: 0.0, y: 1.0, z: 0.0 },
z_axis:
Point3d {
x: 0.0,
y: 0.0,
z: -1.0,
},
..
} => return PlaneData::NegXY,
Self {
origin: Point3d { x: 0.0, y: 0.0, z: 0.0 },
x_axis: Point3d { x: 1.0, y: 0.0, z: 0.0 },
y_axis: Point3d { x: 0.0, y: 0.0, z: 1.0 },
z_axis:
Point3d {
x: 0.0,
y: -1.0,
z: 0.0,
},
..
} => return PlaneData::XZ,
Self {
origin: Point3d { x: 0.0, y: 0.0, z: 0.0 },
x_axis: Point3d { x: 1.0, y: 0.0, z: 0.0 },
y_axis: Point3d { x: 0.0, y: 0.0, z: 1.0 },
z_axis: Point3d { x: 0.0, y: 1.0, z: 0.0 },
..
} => return PlaneData::NegXZ,
Self {
origin: Point3d { x: 0.0, y: 0.0, z: 0.0 },
x_axis: Point3d { x: 0.0, y: 1.0, z: 0.0 },
y_axis: Point3d { x: 0.0, y: 0.0, z: 1.0 },
z_axis: Point3d { x: 1.0, y: 0.0, z: 0.0 },
..
} => return PlaneData::YZ,
Self {
origin: Point3d { x: 0.0, y: 0.0, z: 0.0 },
x_axis: Point3d { x: 0.0, y: 1.0, z: 0.0 },
y_axis: Point3d { x: 0.0, y: 0.0, z: 1.0 },
z_axis:
Point3d {
x: -1.0,
y: 0.0,
z: 0.0,
},
..
} => return PlaneData::NegYZ,
_ => {}
}
}
PlaneData::Plane {
origin: self.origin,
x_axis: self.x_axis,
y_axis: self.y_axis,
z_axis: self.z_axis,
}
}
pub(crate) fn from_plane_data(value: PlaneData, exec_state: &mut ExecState) -> Self {
let id = exec_state.global.id_generator.next_uuid();
match value {
crate::std::sketch::PlaneData::XY => Plane {
PlaneData::XY => Plane {
id,
artifact_id: id.into(),
origin: Point3d::new(0.0, 0.0, 0.0),
@ -281,7 +354,7 @@ impl Plane {
units: exec_state.length_unit(),
meta: vec![],
},
crate::std::sketch::PlaneData::NegXY => Plane {
PlaneData::NegXY => Plane {
id,
artifact_id: id.into(),
origin: Point3d::new(0.0, 0.0, 0.0),
@ -292,7 +365,7 @@ impl Plane {
units: exec_state.length_unit(),
meta: vec![],
},
crate::std::sketch::PlaneData::XZ => Plane {
PlaneData::XZ => Plane {
id,
artifact_id: id.into(),
origin: Point3d::new(0.0, 0.0, 0.0),
@ -303,7 +376,7 @@ impl Plane {
units: exec_state.length_unit(),
meta: vec![],
},
crate::std::sketch::PlaneData::NegXZ => Plane {
PlaneData::NegXZ => Plane {
id,
artifact_id: id.into(),
origin: Point3d::new(0.0, 0.0, 0.0),
@ -314,7 +387,7 @@ impl Plane {
units: exec_state.length_unit(),
meta: vec![],
},
crate::std::sketch::PlaneData::YZ => Plane {
PlaneData::YZ => Plane {
id,
artifact_id: id.into(),
origin: Point3d::new(0.0, 0.0, 0.0),
@ -325,7 +398,7 @@ impl Plane {
units: exec_state.length_unit(),
meta: vec![],
},
crate::std::sketch::PlaneData::NegYZ => Plane {
PlaneData::NegYZ => Plane {
id,
artifact_id: id.into(),
origin: Point3d::new(0.0, 0.0, 0.0),
@ -336,7 +409,7 @@ impl Plane {
units: exec_state.length_unit(),
meta: vec![],
},
crate::std::sketch::PlaneData::Plane {
PlaneData::Plane {
origin,
x_axis,
y_axis,
@ -344,10 +417,10 @@ impl Plane {
} => Plane {
id,
artifact_id: id.into(),
origin: *origin,
x_axis: *x_axis,
y_axis: *y_axis,
z_axis: *z_axis,
origin,
x_axis,
y_axis,
z_axis,
value: PlaneType::Custom,
units: exec_state.length_unit(),
meta: vec![],
@ -356,9 +429,8 @@ impl Plane {
}
/// The standard planes are XY, YZ and XZ (in both positive and negative)
/// Custom planes are any other plane that the user might specify.
pub fn is_custom(&self) -> bool {
matches!(self.value, PlaneType::Custom)
pub fn is_standard(&self) -> bool {
!matches!(self.value, PlaneType::Custom | PlaneType::Uninit)
}
}
@ -389,7 +461,6 @@ pub struct Face {
/// Type for a plane.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, FromStr, Display)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
#[display(style = "camelCase")]
pub enum PlaneType {
#[serde(rename = "XY", alias = "xy")]
@ -402,9 +473,11 @@ pub enum PlaneType {
#[display("YZ")]
YZ,
/// A custom plane.
#[serde(rename = "Custom")]
#[display("Custom")]
Custom,
/// A custom plane which has not been sent to the engine. It must be sent before it is used.
#[display("Uninit")]
Uninit,
}
/// A sketch is a collection of paths.

View File

@ -13,7 +13,8 @@ use crate::{
},
parsing::{
ast::types::{
DefaultParamVal, FunctionExpression, KclNone, Literal, LiteralValue, Node, TagDeclarator, TagNode,
DefaultParamVal, FunctionExpression, KclNone, Literal, LiteralValue, Node,
PrimitiveType as AstPrimitiveType, TagDeclarator, TagNode, Type,
},
token::NumericSuffix,
},
@ -562,6 +563,51 @@ impl KclValue {
Ok(*b)
}
/// True if `self` has a type which is a subtype of `ty` without coercion.
pub fn has_type(&self, ty: &RuntimeType) -> bool {
let Some(self_ty) = self.ty() else {
return false;
};
self_ty.subtype(ty)
}
fn ty(&self) -> Option<RuntimeType> {
match self {
KclValue::Bool { .. } => Some(RuntimeType::Primitive(PrimitiveType::Boolean)),
KclValue::Number { ty, .. } => Some(RuntimeType::Primitive(PrimitiveType::Number(ty.clone()))),
KclValue::String { .. } => Some(RuntimeType::Primitive(PrimitiveType::String)),
KclValue::Object { value, .. } => {
let properties = value
.iter()
.map(|(k, v)| v.ty().map(|t| (k.clone(), t)))
.collect::<Option<Vec<_>>>()?;
Some(RuntimeType::Object(properties))
}
KclValue::Plane { .. } => Some(RuntimeType::Primitive(PrimitiveType::Plane)),
KclValue::Sketch { .. } => Some(RuntimeType::Primitive(PrimitiveType::Sketch)),
KclValue::Sketches { .. } => Some(RuntimeType::Array(PrimitiveType::Sketch)),
KclValue::Solid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Solid)),
KclValue::Solids { .. } => Some(RuntimeType::Array(PrimitiveType::Solid)),
KclValue::Array { value, .. } => Some(RuntimeType::Tuple(
value
.iter()
.map(|v| v.ty().and_then(RuntimeType::primitive))
.collect::<Option<Vec<_>>>()?,
)),
KclValue::Face { .. } => None,
KclValue::Helix { .. } => None,
KclValue::ImportedGeometry(..) => None,
KclValue::Function { .. } => None,
KclValue::Module { .. } => None,
KclValue::TagIdentifier(_) => None,
KclValue::TagDeclarator(_) => None,
KclValue::KclNone { .. } => None,
KclValue::Uuid { .. } => None,
KclValue::Tombstone { .. } => None,
}
}
/// If this memory item is a function, call it with the given arguments, return its val as Ok.
/// If it's not a function, return Err.
pub async fn call_fn(
@ -647,6 +693,79 @@ impl KclValue {
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum RuntimeType {
Primitive(PrimitiveType),
Array(PrimitiveType),
Tuple(Vec<PrimitiveType>),
Object(Vec<(String, RuntimeType)>),
}
impl RuntimeType {
pub fn from_parsed(value: Type, settings: &super::MetaSettings) -> Option<Self> {
match value {
Type::Primitive(pt) => Some(RuntimeType::Primitive(PrimitiveType::from_parsed(pt, settings)?)),
Type::Array(pt) => Some(RuntimeType::Array(PrimitiveType::from_parsed(pt, settings)?)),
Type::Object { properties } => Some(RuntimeType::Object(
properties
.into_iter()
.map(|p| {
p.type_.and_then(|t| {
RuntimeType::from_parsed(t.inner, settings).map(|ty| (p.identifier.inner.name, ty))
})
})
.collect::<Option<Vec<_>>>()?,
)),
}
}
// Subtype with no coercion, including refining numeric types.
fn subtype(&self, sup: &RuntimeType) -> bool {
use RuntimeType::*;
match (self, sup) {
// TODO arrays could be covariant
(Primitive(t1), Primitive(t2)) | (Array(t1), Array(t2)) => t1 == t2,
(Tuple(t1), Tuple(t2)) => t1 == t2,
(Tuple(t1), Array(t2)) => t1.iter().all(|t| t == t2),
// TODO record subtyping - subtype can be larger, fields can be covariant.
(Object(t1), Object(t2)) => t1 == t2,
_ => false,
}
}
fn primitive(self) -> Option<PrimitiveType> {
match self {
RuntimeType::Primitive(t) => Some(t),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum PrimitiveType {
Number(NumericType),
String,
Boolean,
Sketch,
Solid,
Plane,
}
impl PrimitiveType {
fn from_parsed(value: AstPrimitiveType, settings: &super::MetaSettings) -> Option<Self> {
match value {
AstPrimitiveType::String => Some(PrimitiveType::String),
AstPrimitiveType::Boolean => Some(PrimitiveType::Boolean),
AstPrimitiveType::Number(suffix) => Some(PrimitiveType::Number(NumericType::from_parsed(suffix, settings))),
AstPrimitiveType::Sketch => Some(PrimitiveType::Sketch),
AstPrimitiveType::Solid => Some(PrimitiveType::Solid),
AstPrimitiveType::Plane => Some(PrimitiveType::Plane),
_ => None,
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]

View File

@ -948,7 +948,7 @@ yo = 5 + 6
abc = 3
identifierGuy = 5
part001 = startSketchOn('XY')
part001 = startSketchOn(XY)
|> startProfileAt([-1.2, 4.83], %)
|> line(end = [2.8, 0])
|> angledLine([100 + 100, 3.01], %)
@ -965,7 +965,7 @@ yo2 = hmm([identifierGuy + 5])"#;
#[tokio::test(flavor = "multi_thread")]
async fn test_execute_with_pipe_substitutions_unary() {
let ast = r#"const myVar = 3
const part001 = startSketchOn('XY')
const part001 = startSketchOn(XY)
|> startProfileAt([0, 0], %)
|> line(end = [3, 4], tag = $seg01)
|> line(end = [
@ -980,7 +980,7 @@ const part001 = startSketchOn('XY')
#[tokio::test(flavor = "multi_thread")]
async fn test_execute_with_pipe_substitutions() {
let ast = r#"const myVar = 3
const part001 = startSketchOn('XY')
const part001 = startSketchOn(XY)
|> startProfileAt([0, 0], %)
|> line(end = [3, 4], tag = $seg01)
|> line(end = [
@ -1003,7 +1003,7 @@ const halfArmAngle = armAngle / 2
const arrExpShouldNotBeIncluded = [1, 2, 3]
const objExpShouldNotBeIncluded = { a: 1, b: 2, c: 3 }
const part001 = startSketchOn('XY')
const part001 = startSketchOn(XY)
|> startProfileAt([0, 0], %)
|> yLineTo(1, %)
|> xLine(3.84, %) // selection-range-7ish-before-this
@ -1024,7 +1024,7 @@ fn thing = () => {
return -8
}
const firstExtrude = startSketchOn('XY')
const firstExtrude = startSketchOn(XY)
|> startProfileAt([0,0], %)
|> line(end = [0, l])
|> line(end = [w, 0])
@ -1045,7 +1045,7 @@ fn thing = (x) => {
return -x
}
const firstExtrude = startSketchOn('XY')
const firstExtrude = startSketchOn(XY)
|> startProfileAt([0,0], %)
|> line(end = [0, l])
|> line(end = [w, 0])
@ -1066,7 +1066,7 @@ fn thing = (x) => {
return [0, -x]
}
const firstExtrude = startSketchOn('XY')
const firstExtrude = startSketchOn(XY)
|> startProfileAt([0,0], %)
|> line(end = [0, l])
|> line(end = [w, 0])
@ -1091,7 +1091,7 @@ fn thing = (x) => {
return other_thing(x)
}
const firstExtrude = startSketchOn('XY')
const firstExtrude = startSketchOn(XY)
|> startProfileAt([0,0], %)
|> line(end = [0, l])
|> line(end = [w, 0])
@ -1105,7 +1105,7 @@ const firstExtrude = startSketchOn('XY')
#[tokio::test(flavor = "multi_thread")]
async fn test_execute_with_function_sketch() {
let ast = r#"fn box = (h, l, w) => {
const myBox = startSketchOn('XY')
const myBox = startSketchOn(XY)
|> startProfileAt([0,0], %)
|> line(end = [0, l])
|> line(end = [w, 0])
@ -1124,7 +1124,7 @@ const fnBox = box(3, 6, 10)"#;
#[tokio::test(flavor = "multi_thread")]
async fn test_get_member_of_object_with_function_period() {
let ast = r#"fn box = (obj) => {
let myBox = startSketchOn('XY')
let myBox = startSketchOn(XY)
|> startProfileAt(obj.start, %)
|> line(end = [0, obj.l])
|> line(end = [obj.w, 0])
@ -1143,7 +1143,7 @@ const thisBox = box({start: [0,0], l: 6, w: 10, h: 3})
#[tokio::test(flavor = "multi_thread")]
async fn test_get_member_of_object_with_function_brace() {
let ast = r#"fn box = (obj) => {
let myBox = startSketchOn('XY')
let myBox = startSketchOn(XY)
|> startProfileAt(obj["start"], %)
|> line(end = [0, obj["l"]])
|> line(end = [obj["w"], 0])
@ -1162,7 +1162,7 @@ const thisBox = box({start: [0,0], l: 6, w: 10, h: 3})
#[tokio::test(flavor = "multi_thread")]
async fn test_get_member_of_object_with_function_mix_period_brace() {
let ast = r#"fn box = (obj) => {
let myBox = startSketchOn('XY')
let myBox = startSketchOn(XY)
|> startProfileAt(obj["start"], %)
|> line(end = [0, obj["l"]])
|> line(end = [obj["w"], 0])
@ -1184,7 +1184,7 @@ const thisBox = box({start: [0,0], l: 6, w: 10, h: 3})
let ast = r#"
fn test2 = () => {
return {
thing: startSketchOn('XY')
thing: startSketchOn(XY)
|> startProfileAt([0, 0], %)
|> line(end = [0, 1])
|> line(end = [1, 0])
@ -1205,7 +1205,7 @@ x2.thing
#[ignore] // ignore til we get loops
async fn test_execute_with_function_sketch_loop_objects() {
let ast = r#"fn box = (obj) => {
let myBox = startSketchOn('XY')
let myBox = startSketchOn(XY)
|> startProfileAt(obj.start, %)
|> line(end = [0, obj.l])
|> line(end = [obj.w, 0])
@ -1227,7 +1227,7 @@ for var in [{start: [0,0], l: 6, w: 10, h: 3}, {start: [-10,-10], l: 3, w: 5, h:
#[ignore] // ignore til we get loops
async fn test_execute_with_function_sketch_loop_array() {
let ast = r#"fn box = (h, l, w, start) => {
const myBox = startSketchOn('XY')
const myBox = startSketchOn(XY)
|> startProfileAt([0,0], %)
|> line(end = [0, l])
|> line(end = [w, 0])
@ -1249,7 +1249,7 @@ for var in [[3, 6, 10, [0,0]], [1.5, 3, 5, [-10,-10]]] {
#[tokio::test(flavor = "multi_thread")]
async fn test_get_member_of_array_with_function() {
let ast = r#"fn box = (arr) => {
let myBox =startSketchOn('XY')
let myBox =startSketchOn(XY)
|> startProfileAt(arr[0], %)
|> line(end = [0, arr[1]])
|> line(end = [arr[2], 0])
@ -1335,7 +1335,7 @@ fn transform = (replicaId) => {
}
fn layer = () => {
return startSketchOn("XY")
return startSketchOn(XY)
|> circle({ center: [0, 0], radius: 1 }, %, $tag1)
|> extrude(length = 10)
}
@ -1448,7 +1448,7 @@ const leg1 = 5 // inches
const leg2 = 8 // inches
fn thickness = () => { return 0.56 }
const bracket = startSketchOn('XY')
const bracket = startSketchOn(XY)
|> startProfileAt([0,0], %)
|> line(end = [0, leg1])
|> line(end = [leg2, 0])
@ -1645,7 +1645,7 @@ const leg2 = 8 // inches
const thickness_squared = distance * p * FOS * 6 / sigmaAllow
const thickness = 0.56 // inches. App does not support square root function yet
const bracket = startSketchOn('XY')
const bracket = startSketchOn(XY)
|> startProfileAt([0,0], %)
|> line(end = [0, leg1])
|> line(end = [leg2, 0])
@ -1679,7 +1679,7 @@ const leg1 = 5 // inches
const leg2 = 8 // inches
const thickness_squared = (distance * p * FOS * 6 / (sigmaAllow - width))
const thickness = 0.32 // inches. App does not support square root function yet
const bracket = startSketchOn('XY')
const bracket = startSketchOn(XY)
|> startProfileAt([0,0], %)
|> line(end = [0, leg1])
|> line(end = [leg2, 0])
@ -1703,7 +1703,7 @@ const leg1 = 5 // inches
const leg2 = 8 // inches
const thickness_squared = distance * p * FOS * 6 / (sigmaAllow - width)
const thickness = 0.32 // inches. App does not support square root function yet
const bracket = startSketchOn('XY')
const bracket = startSketchOn(XY)
|> startProfileAt([0,0], %)
|> line(end = [0, leg1])
|> line(end = [leg2, 0])
@ -1738,7 +1738,7 @@ let w = f() + f()
#[tokio::test(flavor = "multi_thread")]
async fn kcl_test_ids_stable_between_executions() {
let code = r#"sketch001 = startSketchOn('XZ')
let code = r#"sketch001 = startSketchOn(XZ)
|> startProfileAt([61.74, 206.13], %)
|> xLine(305.11, %, $seg01)
|> yLine(-291.85, %)
@ -1763,7 +1763,7 @@ let w = f() + f()
// Get the id_generator from the first execution.
let id_generator = cache::read_old_ast().await.unwrap().exec_state.global.id_generator;
let code = r#"sketch001 = startSketchOn('XZ')
let code = r#"sketch001 = startSketchOn(XZ)
|> startProfileAt([62.74, 206.13], %)
|> xLine(305.11, %, $seg01)
|> yLine(-291.85, %)

View File

@ -1,12 +1,12 @@
use sha2::{Digest as DigestTrait, Sha256};
use super::types::{DefaultParamVal, ItemVisibility, LabelledExpression, LiteralValue, VariableKind};
use crate::parsing::ast::types::{
Annotation, ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryPart, BodyItem, CallExpression,
CallExpressionKw, ElseIf, Expr, ExpressionStatement, FnArgType, FunctionExpression, Identifier, IfExpression,
ImportItem, ImportSelector, ImportStatement, KclNone, Literal, LiteralIdentifier, MemberExpression, MemberObject,
ObjectExpression, ObjectProperty, Parameter, PipeExpression, PipeSubstitution, Program, ReturnStatement,
TagDeclarator, UnaryExpression, VariableDeclaration, VariableDeclarator,
Annotation, ArrayExpression, ArrayRangeExpression, Ascription, BinaryExpression, BinaryPart, BodyItem,
CallExpression, CallExpressionKw, DefaultParamVal, ElseIf, Expr, ExpressionStatement, FunctionExpression,
Identifier, IfExpression, ImportItem, ImportSelector, ImportStatement, ItemVisibility, KclNone, LabelledExpression,
Literal, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, ObjectExpression, ObjectProperty,
Parameter, PipeExpression, PipeSubstitution, Program, ReturnStatement, TagDeclarator, Type, UnaryExpression,
VariableDeclaration, VariableDeclarator, VariableKind,
};
/// Position-independent digest of the AST node.
@ -142,6 +142,7 @@ impl Expr {
Expr::UnaryExpression(ue) => ue.compute_digest(),
Expr::IfExpression(e) => e.compute_digest(),
Expr::LabelledExpression(e) => e.compute_digest(),
Expr::AscribedExpression(e) => e.compute_digest(),
Expr::None(_) => {
let mut hasher = Sha256::new();
hasher.update(b"Value::None");
@ -183,20 +184,20 @@ impl LiteralIdentifier {
}
}
}
impl FnArgType {
impl Type {
pub fn compute_digest(&mut self) -> Digest {
let mut hasher = Sha256::new();
match self {
FnArgType::Primitive(prim) => {
Type::Primitive(prim) => {
hasher.update(b"FnArgType::Primitive");
hasher.update(prim.digestable_id())
}
FnArgType::Array(prim) => {
Type::Array(prim) => {
hasher.update(b"FnArgType::Array");
hasher.update(prim.digestable_id())
}
FnArgType::Object { properties } => {
Type::Object { properties } => {
hasher.update(b"FnArgType::Object");
hasher.update(properties.len().to_ne_bytes());
for prop in properties.iter_mut() {
@ -409,6 +410,13 @@ impl LabelledExpression {
});
}
impl Ascription {
compute_digest!(|slf, hasher| {
hasher.update(slf.expr.compute_digest());
hasher.update(slf.ty.compute_digest());
});
}
impl PipeExpression {
compute_digest!(|slf, hasher| {
hasher.update(slf.body.len().to_ne_bytes());

View File

@ -37,6 +37,7 @@ impl Expr {
Expr::UnaryExpression(unary_expression) => unary_expression.module_id,
Expr::IfExpression(expr) => expr.module_id,
Expr::LabelledExpression(expr) => expr.expr.module_id(),
Expr::AscribedExpression(expr) => expr.expr.module_id(),
Expr::None(none) => none.module_id,
}
}

View File

@ -675,6 +675,7 @@ pub enum Expr {
UnaryExpression(BoxNode<UnaryExpression>),
IfExpression(BoxNode<IfExpression>),
LabelledExpression(BoxNode<LabelledExpression>),
AscribedExpression(BoxNode<Ascription>),
None(Node<KclNone>),
}
@ -718,6 +719,7 @@ impl Expr {
Expr::PipeSubstitution(_pipe_substitution) => None,
Expr::IfExpression(_) => None,
Expr::LabelledExpression(expr) => expr.expr.get_non_code_meta(),
Expr::AscribedExpression(expr) => expr.expr.get_non_code_meta(),
Expr::None(_none) => None,
}
}
@ -745,6 +747,7 @@ impl Expr {
Expr::IfExpression(_) => {}
Expr::PipeSubstitution(_) => {}
Expr::LabelledExpression(expr) => expr.expr.replace_value(source_range, new_value),
Expr::AscribedExpression(expr) => expr.expr.replace_value(source_range, new_value),
Expr::None(_) => {}
}
}
@ -767,6 +770,7 @@ impl Expr {
Expr::UnaryExpression(unary_expression) => unary_expression.start,
Expr::IfExpression(expr) => expr.start,
Expr::LabelledExpression(expr) => expr.start,
Expr::AscribedExpression(expr) => expr.start,
Expr::None(none) => none.start,
}
}
@ -789,6 +793,7 @@ impl Expr {
Expr::UnaryExpression(unary_expression) => unary_expression.end,
Expr::IfExpression(expr) => expr.end,
Expr::LabelledExpression(expr) => expr.end,
Expr::AscribedExpression(expr) => expr.end,
Expr::None(none) => none.end,
}
}
@ -817,6 +822,8 @@ impl Expr {
Expr::TagDeclarator(_) => None,
// TODO LSP hover info for tag
Expr::LabelledExpression(expr) => expr.expr.get_hover_value_for_position(pos, code),
// TODO LSP hover info for type
Expr::AscribedExpression(expr) => expr.expr.get_hover_value_for_position(pos, code),
// TODO: LSP hover information for symbols. https://github.com/KittyCAD/modeling-app/issues/1127
Expr::PipeSubstitution(_) => None,
}
@ -847,6 +854,7 @@ impl Expr {
Expr::UnaryExpression(ref mut unary_expression) => unary_expression.rename_identifiers(old_name, new_name),
Expr::IfExpression(ref mut expr) => expr.rename_identifiers(old_name, new_name),
Expr::LabelledExpression(expr) => expr.expr.rename_identifiers(old_name, new_name),
Expr::AscribedExpression(expr) => expr.expr.rename_identifiers(old_name, new_name),
Expr::None(_) => {}
}
}
@ -873,6 +881,7 @@ impl Expr {
Expr::UnaryExpression(unary_expression) => unary_expression.get_constraint_level(),
Expr::IfExpression(expr) => expr.get_constraint_level(),
Expr::LabelledExpression(expr) => expr.expr.get_constraint_level(),
Expr::AscribedExpression(expr) => expr.expr.get_constraint_level(),
Expr::None(none) => none.get_constraint_level(),
}
}
@ -882,6 +891,7 @@ impl Expr {
Expr::CallExpression(call_expression) => call_expression.has_substitution_arg(),
Expr::CallExpressionKw(call_expression) => call_expression.has_substitution_arg(),
Expr::LabelledExpression(expr) => expr.expr.has_substitution_arg(),
Expr::AscribedExpression(expr) => expr.expr.has_substitution_arg(),
_ => false,
}
}
@ -910,6 +920,7 @@ impl Expr {
Expr::UnaryExpression(_) => "expression",
Expr::IfExpression(_) => "if expression",
Expr::LabelledExpression(_) => "labelled expression",
Expr::AscribedExpression(_) => "type-ascribed expression",
Expr::None(_) => "none",
}
}
@ -994,6 +1005,27 @@ impl LabelledExpression {
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]
pub struct Ascription {
pub expr: Expr,
pub ty: Node<Type>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub digest: Option<Digest>,
}
impl Ascription {
pub(crate) fn new(expr: Expr, ty: Node<Type>) -> Node<Ascription> {
let start = expr.start();
let end = ty.end;
let module_id = expr.module_id();
Node::new(Ascription { expr, ty, digest: None }, start, end, module_id)
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]
@ -2984,9 +3016,10 @@ impl PipeExpression {
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, JsonSchema)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]
pub enum FnArgPrimitive {
pub enum PrimitiveType {
/// A string type.
String,
/// A number type.
@ -3002,69 +3035,100 @@ pub enum FnArgPrimitive {
SketchSurface,
/// An solid type.
Solid,
/// A plane.
Plane,
}
impl FnArgPrimitive {
impl PrimitiveType {
pub fn digestable_id(&self) -> &[u8] {
match self {
FnArgPrimitive::String => b"string",
FnArgPrimitive::Number(suffix) => suffix.digestable_id(),
FnArgPrimitive::Boolean => b"bool",
FnArgPrimitive::Tag => b"tag",
FnArgPrimitive::Sketch => b"Sketch",
FnArgPrimitive::SketchSurface => b"SketchSurface",
FnArgPrimitive::Solid => b"Solid",
PrimitiveType::String => b"string",
PrimitiveType::Number(suffix) => suffix.digestable_id(),
PrimitiveType::Boolean => b"bool",
PrimitiveType::Tag => b"tag",
PrimitiveType::Sketch => b"Sketch",
PrimitiveType::SketchSurface => b"SketchSurface",
PrimitiveType::Solid => b"Solid",
PrimitiveType::Plane => b"Plane",
}
}
pub fn from_str(s: &str, suffix: Option<NumericSuffix>) -> Option<Self> {
match (s, suffix) {
("string", None) => Some(FnArgPrimitive::String),
("bool", None) => Some(FnArgPrimitive::Boolean),
("tag", None) => Some(FnArgPrimitive::Tag),
("Sketch", None) => Some(FnArgPrimitive::Sketch),
("SketchSurface", None) => Some(FnArgPrimitive::SketchSurface),
("Solid", None) => Some(FnArgPrimitive::Solid),
("number", None) => Some(FnArgPrimitive::Number(NumericSuffix::None)),
("number", Some(s)) => Some(FnArgPrimitive::Number(s)),
("string", None) => Some(PrimitiveType::String),
("bool", None) => Some(PrimitiveType::Boolean),
("tag", None) => Some(PrimitiveType::Tag),
("Sketch", None) => Some(PrimitiveType::Sketch),
("SketchSurface", None) => Some(PrimitiveType::SketchSurface),
("Solid", None) => Some(PrimitiveType::Solid),
("Plane", None) => Some(PrimitiveType::Plane),
("number", None) => Some(PrimitiveType::Number(NumericSuffix::None)),
("number", Some(s)) => Some(PrimitiveType::Number(s)),
_ => None,
}
}
}
impl fmt::Display for FnArgPrimitive {
impl fmt::Display for PrimitiveType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FnArgPrimitive::Number(suffix) => {
PrimitiveType::Number(suffix) => {
write!(f, "number")?;
if *suffix != NumericSuffix::None {
write!(f, "({suffix})")?;
}
Ok(())
}
FnArgPrimitive::String => write!(f, "string"),
FnArgPrimitive::Boolean => write!(f, "bool"),
FnArgPrimitive::Tag => write!(f, "tag"),
FnArgPrimitive::Sketch => write!(f, "Sketch"),
FnArgPrimitive::SketchSurface => write!(f, "SketchSurface"),
FnArgPrimitive::Solid => write!(f, "Solid"),
PrimitiveType::String => write!(f, "string"),
PrimitiveType::Boolean => write!(f, "bool"),
PrimitiveType::Tag => write!(f, "tag"),
PrimitiveType::Sketch => write!(f, "Sketch"),
PrimitiveType::SketchSurface => write!(f, "SketchSurface"),
PrimitiveType::Solid => write!(f, "Solid"),
PrimitiveType::Plane => write!(f, "Plane"),
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]
pub enum FnArgType {
pub enum Type {
/// A primitive type.
Primitive(FnArgPrimitive),
Primitive(PrimitiveType),
// An array of a primitive type.
Array(FnArgPrimitive),
Array(PrimitiveType),
// An object type.
Object {
properties: Vec<Parameter>,
},
}
impl fmt::Display for Type {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Type::Primitive(primitive_type) => primitive_type.fmt(f),
Type::Array(primitive_type) => write!(f, "{primitive_type}[]"),
Type::Object { properties } => {
write!(f, "{{")?;
let mut first = true;
for p in properties {
if first {
first = false;
} else {
write!(f, ",")?;
}
write!(f, "{}: ", p.identifier.name)?;
if let Some(ty) = &p.type_ {
write!(f, " {}", ty.inner)?;
}
}
write!(f, " }}")
}
}
}
}
/// Default value for a parameter of a KCL function.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
@ -3092,7 +3156,7 @@ pub struct Parameter {
/// The type of the parameter.
/// This is optional if the user defines a type.
#[serde(skip)]
pub type_: Option<FnArgType>,
pub type_: Option<Node<Type>>,
/// Is the parameter optional?
/// If so, what is its default value?
/// If this is None, then the parameter is required.
@ -3142,7 +3206,7 @@ pub struct FunctionExpression {
pub params: Vec<Parameter>,
pub body: Node<Program>,
#[serde(skip)]
pub return_type: Option<FnArgType>,
pub return_type: Option<Node<Type>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
@ -3608,11 +3672,17 @@ const cylinder = startSketchOn('-XZ')
let params = &func_expr.params;
assert_eq!(params.len(), 3);
assert_eq!(
params[0].type_,
Some(FnArgType::Primitive(FnArgPrimitive::Number(NumericSuffix::Mm)))
params[0].type_.as_ref().unwrap().inner,
Type::Primitive(PrimitiveType::Number(NumericSuffix::Mm))
);
assert_eq!(
params[1].type_.as_ref().unwrap().inner,
Type::Primitive(PrimitiveType::String)
);
assert_eq!(
params[2].type_.as_ref().unwrap().inner,
Type::Primitive(PrimitiveType::String)
);
assert_eq!(params[1].type_, Some(FnArgType::Primitive(FnArgPrimitive::String)));
assert_eq!(params[2].type_, Some(FnArgType::Primitive(FnArgPrimitive::String)));
}
#[tokio::test(flavor = "multi_thread")]
@ -3633,11 +3703,17 @@ const cylinder = startSketchOn('-XZ')
let params = &func_expr.params;
assert_eq!(params.len(), 3);
assert_eq!(
params[0].type_,
Some(FnArgType::Array(FnArgPrimitive::Number(NumericSuffix::None)))
params[0].type_.as_ref().unwrap().inner,
Type::Array(PrimitiveType::Number(NumericSuffix::None))
);
assert_eq!(
params[1].type_.as_ref().unwrap().inner,
Type::Array(PrimitiveType::String)
);
assert_eq!(
params[2].type_.as_ref().unwrap().inner,
Type::Primitive(PrimitiveType::String)
);
assert_eq!(params[1].type_, Some(FnArgType::Array(FnArgPrimitive::String)));
assert_eq!(params[2].type_, Some(FnArgType::Primitive(FnArgPrimitive::String)));
}
#[tokio::test(flavor = "multi_thread")]
@ -3659,12 +3735,12 @@ const cylinder = startSketchOn('-XZ')
let params = &func_expr.params;
assert_eq!(params.len(), 3);
assert_eq!(
params[0].type_,
Some(FnArgType::Array(FnArgPrimitive::Number(NumericSuffix::None)))
params[0].type_.as_ref().unwrap().inner,
Type::Array(PrimitiveType::Number(NumericSuffix::None))
);
assert_eq!(
params[1].type_,
Some(FnArgType::Object {
params[1].type_.as_ref().unwrap().inner,
Type::Object {
properties: vec![
Parameter {
identifier: Node::new(
@ -3676,7 +3752,12 @@ const cylinder = startSketchOn('-XZ')
40,
module_id,
),
type_: Some(FnArgType::Primitive(FnArgPrimitive::Number(NumericSuffix::None))),
type_: Some(Node::new(
Type::Primitive(PrimitiveType::Number(NumericSuffix::None)),
42,
48,
module_id
)),
default_value: None,
labeled: true,
digest: None,
@ -3691,7 +3772,7 @@ const cylinder = startSketchOn('-XZ')
56,
module_id,
),
type_: Some(FnArgType::Array(FnArgPrimitive::String)),
type_: Some(Node::new(Type::Array(PrimitiveType::String), 58, 64, module_id)),
default_value: None,
labeled: true,
digest: None
@ -3706,15 +3787,18 @@ const cylinder = startSketchOn('-XZ')
72,
module_id,
),
type_: Some(FnArgType::Primitive(FnArgPrimitive::String)),
type_: Some(Node::new(Type::Primitive(PrimitiveType::String), 75, 81, module_id)),
labeled: true,
default_value: Some(DefaultParamVal::none()),
digest: None
}
]
})
}
);
assert_eq!(
params[2].type_.as_ref().unwrap().inner,
Type::Primitive(PrimitiveType::String)
);
assert_eq!(params[2].type_, Some(FnArgType::Primitive(FnArgPrimitive::String)));
}
#[tokio::test(flavor = "multi_thread")]
@ -3736,8 +3820,8 @@ const cylinder = startSketchOn('-XZ')
let params = &func_expr.params;
assert_eq!(params.len(), 0);
assert_eq!(
func_expr.return_type,
Some(FnArgType::Object {
func_expr.return_type.as_ref().unwrap().inner,
Type::Object {
properties: vec![
Parameter {
identifier: Node::new(
@ -3749,7 +3833,12 @@ const cylinder = startSketchOn('-XZ')
18,
module_id,
),
type_: Some(FnArgType::Primitive(FnArgPrimitive::Number(NumericSuffix::None))),
type_: Some(Node::new(
Type::Primitive(PrimitiveType::Number(NumericSuffix::None)),
20,
26,
module_id
)),
default_value: None,
labeled: true,
digest: None
@ -3764,7 +3853,7 @@ const cylinder = startSketchOn('-XZ')
34,
module_id,
),
type_: Some(FnArgType::Array(FnArgPrimitive::String)),
type_: Some(Node::new(Type::Array(PrimitiveType::String), 36, 42, module_id)),
default_value: None,
labeled: true,
digest: None
@ -3779,13 +3868,13 @@ const cylinder = startSketchOn('-XZ')
50,
module_id,
),
type_: Some(FnArgType::Primitive(FnArgPrimitive::String)),
type_: Some(Node::new(Type::Primitive(PrimitiveType::String), 53, 59, module_id)),
labeled: true,
default_value: Some(DefaultParamVal::none()),
digest: None
}
]
})
}
);
}

View File

@ -13,7 +13,7 @@ use winnow::{
};
use super::{
ast::types::{ImportPath, LabelledExpression},
ast::types::{Ascription, ImportPath, LabelledExpression},
token::NumericSuffix,
};
use crate::{
@ -23,11 +23,11 @@ use crate::{
ast::types::{
Annotation, ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem,
BoxNode, CallExpression, CallExpressionKw, CommentStyle, DefaultParamVal, ElseIf, Expr,
ExpressionStatement, FnArgPrimitive, FnArgType, FunctionExpression, Identifier, IfExpression, ImportItem,
ImportSelector, ImportStatement, ItemVisibility, LabeledArg, Literal, LiteralIdentifier, LiteralValue,
MemberExpression, MemberObject, Node, NodeList, NonCodeMeta, NonCodeNode, NonCodeValue, ObjectExpression,
ObjectProperty, Parameter, PipeExpression, PipeSubstitution, Program, ReturnStatement, Shebang,
TagDeclarator, UnaryExpression, UnaryOperator, VariableDeclaration, VariableDeclarator, VariableKind,
ExpressionStatement, FunctionExpression, Identifier, IfExpression, ImportItem, ImportSelector,
ImportStatement, ItemVisibility, LabeledArg, Literal, LiteralIdentifier, LiteralValue, MemberExpression,
MemberObject, Node, NodeList, NonCodeMeta, NonCodeNode, NonCodeValue, ObjectExpression, ObjectProperty,
Parameter, PipeExpression, PipeSubstitution, PrimitiveType, Program, ReturnStatement, Shebang,
TagDeclarator, Type, UnaryExpression, UnaryOperator, VariableDeclaration, VariableDeclarator, VariableKind,
},
math::BinaryExpressionToken,
token::{Token, TokenSlice, TokenType},
@ -580,7 +580,8 @@ fn operand(i: &mut TokenSlice) -> PResult<BinaryPart> {
| Expr::ArrayExpression(_)
| Expr::ArrayRangeExpression(_)
| Expr::ObjectExpression(_)
| Expr::LabelledExpression(..) => return Err(CompilationError::fatal(source_range, TODO_783)),
| Expr::LabelledExpression(..)
| Expr::AscribedExpression(..) => return Err(CompilationError::fatal(source_range, TODO_783)),
Expr::None(_) => {
return Err(CompilationError::fatal(
source_range,
@ -842,7 +843,7 @@ fn object_property(i: &mut TokenSlice) -> PResult<Node<ObjectProperty>> {
))
.parse_next(i)?;
ignore_whitespace(i);
let expr = expression_but_not_ascription
let expr = expression
.context(expected(
"the value which you're setting the property to, e.g. in 'height: 4', the value is 4",
))
@ -1120,7 +1121,7 @@ fn function_expr(i: &mut TokenSlice) -> PResult<Expr> {
// return x
// }
fn function_decl(i: &mut TokenSlice) -> PResult<(Node<FunctionExpression>, bool)> {
fn return_type(i: &mut TokenSlice) -> PResult<FnArgType> {
fn return_type(i: &mut TokenSlice) -> PResult<Node<Type>> {
colon(i)?;
ignore_whitespace(i);
argument_type(i)
@ -1832,24 +1833,6 @@ fn return_stmt(i: &mut TokenSlice) -> PResult<Node<ReturnStatement>> {
/// Parse a KCL expression.
fn expression(i: &mut TokenSlice) -> PResult<Expr> {
let expr = expression_but_not_ascription.parse_next(i)?;
let ty = opt((colon, opt(whitespace), argument_type)).parse_next(i)?;
// TODO this is probably not giving ascription the right precedence, but I have no idea how Winnow is handling that.
// Since we're not creating AST nodes for ascription, I don't think it matters right now.
if let Some((colon, _, _)) = ty {
ParseContext::err(CompilationError::err(
// Sadly there is no SourceRange for the type itself
colon.into(),
"Type ascription is experimental and currently does nothing.",
));
}
Ok(expr)
}
// TODO once we remove the old record instantiation syntax, we can accept types ascription anywhere.
fn expression_but_not_ascription(i: &mut TokenSlice) -> PResult<Expr> {
alt((
pipe_expression.map(Box::new).map(Expr::PipeExpression),
expression_but_not_pipe,
@ -1859,7 +1842,7 @@ fn expression_but_not_ascription(i: &mut TokenSlice) -> PResult<Expr> {
}
fn expression_but_not_pipe(i: &mut TokenSlice) -> PResult<Expr> {
let expr = alt((
let mut expr = alt((
binary_expression.map(Box::new).map(Expr::BinaryExpression),
unary_expression.map(Box::new).map(Expr::UnaryExpression),
expr_allowed_in_pipe_expr,
@ -1867,6 +1850,12 @@ fn expression_but_not_pipe(i: &mut TokenSlice) -> PResult<Expr> {
.context(expected("a KCL value"))
.parse_next(i)?;
let ty = opt((colon, opt(whitespace), argument_type)).parse_next(i)?;
if let Some((_, _, ty)) = ty {
ParseContext::warn(CompilationError::err((&ty).into(), "Type ascription is experimental."));
expr = Expr::AscribedExpression(Box::new(Ascription::new(expr, ty)))
}
let label = opt(label).parse_next(i)?;
match label {
Some(label) => Ok(Expr::LabelledExpression(Box::new(LabelledExpression::new(expr, label)))),
@ -2521,11 +2510,18 @@ fn labeled_argument(i: &mut TokenSlice) -> PResult<LabeledArg> {
/// - a primitive type, e.g. 'number' or 'string' or 'bool'
/// - an array type, e.g. 'number[]' or 'string[]' or 'bool[]'
/// - an object type, e.g. '{x: number, y: number}' or '{name: string, age: number}'
fn argument_type(i: &mut TokenSlice) -> PResult<FnArgType> {
fn argument_type(i: &mut TokenSlice) -> PResult<Node<Type>> {
let type_ = alt((
// Object types
// TODO it is buggy to treat object fields like parameters since the parameters parser assumes a terminating `)`.
(open_brace, parameters, close_brace).map(|(_, params, _)| Ok(FnArgType::Object { properties: params })),
(open_brace, parameters, close_brace).map(|(open, params, close)| {
Ok(Node::new(
Type::Object { properties: params },
open.start,
close.end,
open.module_id,
))
}),
// Array types
(
one_of(TokenType::Type),
@ -2534,8 +2530,8 @@ fn argument_type(i: &mut TokenSlice) -> PResult<FnArgType> {
close_bracket,
)
.map(|(token, uom, _, _)| {
FnArgPrimitive::from_str(&token.value, uom)
.map(FnArgType::Array)
PrimitiveType::from_str(&token.value, uom)
.map(|t| Node::new(Type::Array(t), token.start, token.end, token.module_id))
.ok_or_else(|| {
CompilationError::fatal(token.as_source_range(), format!("Invalid type: {}", token.value))
})
@ -2552,8 +2548,8 @@ fn argument_type(i: &mut TokenSlice) -> PResult<FnArgType> {
"Unit of Measure types are experimental and currently do nothing.",
));
}
FnArgPrimitive::from_str(&token.value, suffix)
.map(FnArgType::Primitive)
PrimitiveType::from_str(&token.value, suffix)
.map(|t| Node::new(Type::Primitive(t), token.start, token.end, token.module_id))
.ok_or_else(|| {
CompilationError::fatal(token.as_source_range(), format!("Invalid type: {}", token.value))
})
@ -2571,7 +2567,7 @@ fn uom_for_type(i: &mut TokenSlice) -> PResult<NumericSuffix> {
struct ParamDescription {
labeled: bool,
arg_name: Token,
type_: std::option::Option<FnArgType>,
type_: std::option::Option<Node<Type>>,
default_value: Option<DefaultParamVal>,
}
@ -4537,11 +4533,11 @@ let myBox = box([0,0], -3, -16, -10)
fn test_parse_tag_starting_with_reserved_type() {
let some_program_string = r#"
startSketchOn('XY')
|> line(%, $sketch)
|> line(%, $Sketch)
"#;
assert_err(
some_program_string,
"Cannot assign a tag to a reserved keyword: sketch",
"Cannot assign a tag to a reserved keyword: Sketch",
[41, 47],
);
}

View File

@ -54,9 +54,10 @@ lazy_static! {
set.insert("string", TokenType::Type);
set.insert("number", TokenType::Type);
set.insert("bool", TokenType::Type);
set.insert("sketch", TokenType::Type);
set.insert("sketch_surface", TokenType::Type);
set.insert("solid", TokenType::Type);
set.insert("Sketch", TokenType::Type);
set.insert("SketchSurface", TokenType::Type);
set.insert("Solid", TokenType::Type);
set.insert("Plane", TokenType::Type);
set
};

View File

@ -1118,10 +1118,10 @@ impl<'a> FromKclValue<'a> for super::sketch::PlaneData {
// Case 0: actual plane
if let KclValue::Plane { value } = arg {
return Some(Self::Plane {
origin: Box::new(value.origin),
x_axis: Box::new(value.x_axis),
y_axis: Box::new(value.y_axis),
z_axis: Box::new(value.z_axis),
origin: value.origin,
x_axis: value.x_axis,
y_axis: value.y_axis,
z_axis: value.z_axis,
});
}
// Case 1: predefined plane
@ -1139,10 +1139,10 @@ impl<'a> FromKclValue<'a> for super::sketch::PlaneData {
// Case 2: custom plane
let obj = arg.as_object()?;
let_field_of!(obj, plane, &KclObjectFields);
let origin = plane.get("origin").and_then(FromKclValue::from_kcl_val).map(Box::new)?;
let x_axis = plane.get("xAxis").and_then(FromKclValue::from_kcl_val).map(Box::new)?;
let y_axis = plane.get("yAxis").and_then(FromKclValue::from_kcl_val).map(Box::new)?;
let z_axis = plane.get("zAxis").and_then(FromKclValue::from_kcl_val).map(Box::new)?;
let origin = plane.get("origin").and_then(FromKclValue::from_kcl_val)?;
let x_axis = plane.get("xAxis").and_then(FromKclValue::from_kcl_val)?;
let y_axis = plane.get("yAxis").and_then(FromKclValue::from_kcl_val)?;
let z_axis = plane.get("zAxis").and_then(FromKclValue::from_kcl_val)?;
Some(Self::Plane {
origin,
x_axis,

View File

@ -110,7 +110,7 @@ pub async fn line(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
/// Extend the current sketch with a new straight line.
///
/// ```no_run
/// triangle = startSketchOn("XZ")
/// triangle = startSketchOn(XZ)
/// |> startProfileAt([0, 0], %)
/// // The 'end' argument means it ends at exactly [10, 0].
/// // This is an absolute measurement, it is NOT relative to
@ -121,7 +121,7 @@ pub async fn line(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
/// |> close()
/// |> extrude(length = 5)
///
/// box = startSketchOn("XZ")
/// box = startSketchOn(XZ)
/// |> startProfileAt([10, 10], %)
/// // The 'to' argument means move the pen this much.
/// // So, [10, 0] is a relative distance away from the current point.
@ -275,7 +275,7 @@ pub async fn x_line_to(exec_state: &mut ExecState, args: Args) -> Result<KclValu
/// then xLineTo(4) draws a line from (1, 1) to (4, 1)
///
/// ```no_run
/// exampleSketch = startSketchOn('XZ')
/// exampleSketch = startSketchOn(XZ)
/// |> startProfileAt([0, 0], %)
/// |> xLineTo(15, %)
/// |> angledLine({
@ -330,7 +330,7 @@ pub async fn y_line_to(exec_state: &mut ExecState, args: Args) -> Result<KclValu
/// then yLineTo(4) draws a line from (1, 1) to (1, 4)
///
/// ```no_run
/// exampleSketch = startSketchOn("XZ")
/// exampleSketch = startSketchOn(XZ)
/// |> startProfileAt([0, 0], %)
/// |> angledLine({
/// angle = 50,
@ -376,7 +376,7 @@ pub async fn x_line(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
/// from the current position along the 'x' axis.
///
/// ```no_run
/// exampleSketch = startSketchOn('XZ')
/// exampleSketch = startSketchOn(XZ)
/// |> startProfileAt([0, 0], %)
/// |> xLine(15, %)
/// |> angledLine({
@ -426,7 +426,7 @@ pub async fn y_line(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
/// from the current position along the 'y' axis.
///
/// ```no_run
/// exampleSketch = startSketchOn('XZ')
/// exampleSketch = startSketchOn(XZ)
/// |> startProfileAt([0, 0], %)
/// |> yLine(15, %)
/// |> angledLine({
@ -487,7 +487,7 @@ pub async fn angled_line(exec_state: &mut ExecState, args: Args) -> Result<KclVa
/// measure of some angle and distance.
///
/// ```no_run
/// exampleSketch = startSketchOn('XZ')
/// exampleSketch = startSketchOn(XZ)
/// |> startProfileAt([0, 0], %)
/// |> yLineTo(15, %)
/// |> angledLine({
@ -575,7 +575,7 @@ pub async fn angled_line_of_x_length(exec_state: &mut ExecState, args: Args) ->
/// along some angle (in degrees) for some relative length in the 'x' dimension.
///
/// ```no_run
/// sketch001 = startSketchOn('XZ')
/// sketch001 = startSketchOn(XZ)
/// |> startProfileAt([0, 0], %)
/// |> angledLineOfXLength({ angle = 45, length = 10 }, %, $edge1)
/// |> angledLineOfXLength({ angle = -15, length = 20 }, %, $edge2)
@ -646,7 +646,7 @@ pub async fn angled_line_to_x(exec_state: &mut ExecState, args: Args) -> Result<
/// in the 'x' dimension.
///
/// ```no_run
/// exampleSketch = startSketchOn('XZ')
/// exampleSketch = startSketchOn(XZ)
/// |> startProfileAt([0, 0], %)
/// |> angledLineToX({ angle = 30, to = 10 }, %)
/// |> line(end = [0, 10])
@ -710,7 +710,7 @@ pub async fn angled_line_of_y_length(exec_state: &mut ExecState, args: Args) ->
/// along some angle (in degrees) for some relative length in the 'y' dimension.
///
/// ```no_run
/// exampleSketch = startSketchOn('XZ')
/// exampleSketch = startSketchOn(XZ)
/// |> startProfileAt([0, 0], %)
/// |> line(end = [10, 0])
/// |> angledLineOfYLength({ angle = 45, length = 10 }, %)
@ -772,7 +772,7 @@ pub async fn angled_line_to_y(exec_state: &mut ExecState, args: Args) -> Result<
/// in the 'y' dimension.
///
/// ```no_run
/// exampleSketch = startSketchOn('XZ')
/// exampleSketch = startSketchOn(XZ)
/// |> startProfileAt([0, 0], %)
/// |> angledLineToY({ angle = 60, to = 20 }, %)
/// |> line(end = [-20, 0])
@ -850,7 +850,7 @@ pub async fn angled_line_that_intersects(exec_state: &mut ExecState, args: Args)
/// segment.
///
/// ```no_run
/// exampleSketch = startSketchOn('XZ')
/// exampleSketch = startSketchOn(XZ)
/// |> startProfileAt([0, 0], %)
/// |> line(endAbsolute = [5, 10])
/// |> line(endAbsolute = [-10, 10], tag = $lineToIntersect)
@ -952,6 +952,7 @@ async fn inner_start_sketch_at(data: [f64; 2], exec_state: &mut ExecState, args:
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase", untagged)]
#[allow(clippy::large_enum_variant)]
pub enum SketchData {
PlaneOrientation(PlaneData),
Plane(Box<Plane>),
@ -962,6 +963,7 @@ pub enum SketchData {
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
#[allow(clippy::large_enum_variant)]
pub enum PlaneData {
/// The XY plane.
#[serde(rename = "XY", alias = "xy")]
@ -984,16 +986,16 @@ pub enum PlaneData {
/// A defined plane.
Plane {
/// Origin of the plane.
origin: Box<Point3d>,
origin: Point3d,
/// What should the planes X axis be?
#[serde(rename = "xAxis")]
x_axis: Box<Point3d>,
x_axis: Point3d,
/// What should the planes Y axis be?
#[serde(rename = "yAxis")]
y_axis: Box<Point3d>,
y_axis: Point3d,
/// The z-axis (normal).
#[serde(rename = "zAxis")]
z_axis: Box<Point3d>,
z_axis: Point3d,
},
}
@ -1028,7 +1030,7 @@ pub async fn start_sketch_on(exec_state: &mut ExecState, args: Args) -> Result<K
///
///
/// ```no_run
/// exampleSketch = startSketchOn("XY")
/// exampleSketch = startSketchOn(XY)
/// |> startProfileAt([0, 0], %)
/// |> line(end = [10, 0])
/// |> line(end = [0, 10])
@ -1057,7 +1059,7 @@ pub async fn start_sketch_on(exec_state: &mut ExecState, args: Args) -> Result<K
/// ```
///
/// ```no_run
/// exampleSketch = startSketchOn("XY")
/// exampleSketch = startSketchOn(XY)
/// |> startProfileAt([0, 0], %)
/// |> line(end = [10, 0])
/// |> line(end = [0, 10], tag = $sketchingFace)
@ -1086,7 +1088,7 @@ pub async fn start_sketch_on(exec_state: &mut ExecState, args: Args) -> Result<K
/// ```
///
/// ```no_run
/// exampleSketch = startSketchOn('XY')
/// exampleSketch = startSketchOn(XY)
/// |> startProfileAt([4, 12], %)
/// |> line(end = [2, 0])
/// |> line(end = [0, -6])
@ -1142,15 +1144,20 @@ async fn inner_start_sketch_on(
Ok(SketchSurface::Plane(plane))
}
SketchData::Plane(plane) => {
// Create artifact used only by the UI, not the engine.
let id = exec_state.next_uuid();
exec_state.add_artifact(Artifact::StartSketchOnPlane {
id: ArtifactId::from(id),
plane_id: plane.id,
source_range: args.source_range,
});
if plane.value == crate::exec::PlaneType::Uninit {
let plane = make_sketch_plane_from_orientation(plane.into_plane_data(), exec_state, args).await?;
Ok(SketchSurface::Plane(plane))
} else {
// Create artifact used only by the UI, not the engine.
let id = exec_state.next_uuid();
exec_state.add_artifact(Artifact::StartSketchOnPlane {
id: ArtifactId::from(id),
plane_id: plane.id,
source_range: args.source_range,
});
Ok(SketchSurface::Plane(plane))
Ok(SketchSurface::Plane(plane))
}
}
SketchData::Solid(solid) => {
let Some(tag) = tag else {
@ -1238,10 +1245,10 @@ async fn make_sketch_plane_from_orientation(
plane.id,
ModelingCmd::from(mcmd::MakePlane {
clobber,
origin: (*origin).into(),
origin: origin.into(),
size,
x_axis: (*x_axis).into(),
y_axis: (*y_axis).into(),
x_axis: x_axis.into(),
y_axis: y_axis.into(),
hide,
}),
)
@ -1266,7 +1273,7 @@ pub async fn start_profile_at(exec_state: &mut ExecState, args: Args) -> Result<
/// Start a new profile at a given point.
///
/// ```no_run
/// exampleSketch = startSketchOn('XZ')
/// exampleSketch = startSketchOn(XZ)
/// |> startProfileAt([0, 0], %)
/// |> line(end = [10, 0])
/// |> line(end = [0, 10])
@ -1277,7 +1284,7 @@ pub async fn start_profile_at(exec_state: &mut ExecState, args: Args) -> Result<
/// ```
///
/// ```no_run
/// exampleSketch = startSketchOn('-XZ')
/// exampleSketch = startSketchOn(-XZ)
/// |> startProfileAt([10, 10], %)
/// |> line(end = [10, 0])
/// |> line(end = [0, 10])
@ -1288,7 +1295,7 @@ pub async fn start_profile_at(exec_state: &mut ExecState, args: Args) -> Result<
/// ```
///
/// ```no_run
/// exampleSketch = startSketchOn('-XZ')
/// exampleSketch = startSketchOn(-XZ)
/// |> startProfileAt([-10, 23], %)
/// |> line(end = [10, 0])
/// |> line(end = [0, 10])
@ -1314,7 +1321,7 @@ pub(crate) async fn inner_start_profile_at(
args.flush_batch_for_solid_set(exec_state, face.solid.clone().into())
.await?;
}
SketchSurface::Plane(plane) if plane.is_custom() => {
SketchSurface::Plane(plane) if !plane.is_standard() => {
// Hide whatever plane we are sketching on.
// This is especially helpful for offset planes, which would be visible otherwise.
args.batch_end_cmd(
@ -1413,7 +1420,7 @@ pub async fn profile_start_x(_exec_state: &mut ExecState, args: Args) -> Result<
/// value.
///
/// ```no_run
/// sketch001 = startSketchOn('XY')
/// sketch001 = startSketchOn(XY)
/// |> startProfileAt([5, 2], %)
/// |> angledLine([-26.6, 50], %)
/// |> angledLine([90, 50], %)
@ -1438,7 +1445,7 @@ pub async fn profile_start_y(_exec_state: &mut ExecState, args: Args) -> Result<
/// value.
///
/// ```no_run
/// sketch001 = startSketchOn('XY')
/// sketch001 = startSketchOn(XY)
/// |> startProfileAt([5, 2], %)
/// |> angledLine({ angle = -60, length = 14 }, %)
/// |> angledLineToY({ angle = 30, to = profileStartY(%) }, %)
@ -1462,7 +1469,7 @@ pub async fn profile_start(_exec_state: &mut ExecState, args: Args) -> Result<Kc
/// value.
///
/// ```no_run
/// sketch001 = startSketchOn('XY')
/// sketch001 = startSketchOn(XY)
/// |> startProfileAt([5, 2], %)
/// |> angledLine({ angle = 120, length = 50 }, %, $seg01)
/// |> angledLine({ angle = segAng(seg01) + 120, length = 50 }, %)
@ -1491,7 +1498,7 @@ pub async fn close(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
/// origin, ensuring the resulting 2-dimensional sketch is not open-ended.
///
/// ```no_run
/// startSketchOn('XZ')
/// startSketchOn(XZ)
/// |> startProfileAt([0, 0], %)
/// |> line(end = [10, 10])
/// |> line(end = [10, 0])
@ -1500,7 +1507,7 @@ pub async fn close(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
/// ```
///
/// ```no_run
/// exampleSketch = startSketchOn('-XZ')
/// exampleSketch = startSketchOn(-XZ)
/// |> startProfileAt([0, 0], %)
/// |> line(end = [10, 0])
/// |> line(end = [0, 10])
@ -1625,7 +1632,7 @@ pub async fn arc(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kcl
/// for to construct your shape, you're likely looking for tangentialArc.
///
/// ```no_run
/// exampleSketch = startSketchOn('XZ')
/// exampleSketch = startSketchOn(XZ)
/// |> startProfileAt([0, 0], %)
/// |> line(end = [10, 0])
/// |> arc({
@ -1733,7 +1740,7 @@ pub async fn arc_to(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
/// the start and end.
///
/// ```no_run
/// exampleSketch = startSketchOn('XZ')
/// exampleSketch = startSketchOn(XZ)
/// |> startProfileAt([0, 0], %)
/// |> arcTo({
/// end = [10,0],
@ -1870,7 +1877,7 @@ pub async fn tangential_arc(exec_state: &mut ExecState, args: Args) -> Result<Kc
/// degrees along the imaginary circle.
///
/// ```no_run
/// exampleSketch = startSketchOn('XZ')
/// exampleSketch = startSketchOn(XZ)
/// |> startProfileAt([0, 0], %)
/// |> angledLine({
/// angle = 60,
@ -2004,7 +2011,7 @@ pub async fn tangential_arc_to_relative(exec_state: &mut ExecState, args: Args)
/// coordinates.
///
/// ```no_run
/// exampleSketch = startSketchOn('XZ')
/// exampleSketch = startSketchOn(XZ)
/// |> startProfileAt([0, 0], %)
/// |> angledLine({
/// angle = 60,
@ -2071,7 +2078,7 @@ async fn inner_tangential_arc_to(
/// distance away.
///
/// ```no_run
/// exampleSketch = startSketchOn('XZ')
/// exampleSketch = startSketchOn(XZ)
/// |> startProfileAt([0, 0], %)
/// |> angledLine({
/// angle = 45,
@ -2178,7 +2185,7 @@ pub async fn bezier_curve(exec_state: &mut ExecState, args: Args) -> Result<KclV
/// shape.
///
/// ```no_run
/// exampleSketch = startSketchOn('XZ')
/// exampleSketch = startSketchOn(XZ)
/// |> startProfileAt([0, 0], %)
/// |> line(end = [0, 10])
/// |> bezierCurve({
@ -2259,7 +2266,7 @@ pub async fn hole(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
/// Use a 2-dimensional sketch to cut a hole in another 2-dimensional sketch.
///
/// ```no_run
/// exampleSketch = startSketchOn('XY')
/// exampleSketch = startSketchOn(XY)
/// |> startProfileAt([0, 0], %)
/// |> line(end = [0, 5])
/// |> line(end = [5, 0])
@ -2273,7 +2280,7 @@ pub async fn hole(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
///
/// ```no_run
/// fn squareHoleSketch() {
/// squareSketch = startSketchOn('-XZ')
/// squareSketch = startSketchOn(-XZ)
/// |> startProfileAt([-1, -1], %)
/// |> line(end = [2, 0])
/// |> line(end = [0, 2])
@ -2282,7 +2289,7 @@ pub async fn hole(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
/// return squareSketch
/// }
///
/// exampleSketch = startSketchOn('-XZ')
/// exampleSketch = startSketchOn(-XZ)
/// |> circle({ center = [0, 0], radius = 3 }, %)
/// |> hole(squareHoleSketch(), %)
/// example = extrude(exampleSketch, length = 1)

View File

@ -3,11 +3,10 @@ use std::fmt::Write;
use crate::parsing::{
ast::types::{
Annotation, ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem,
CallExpression, CallExpressionKw, CommentStyle, DefaultParamVal, Expr, FnArgType, FormatOptions,
FunctionExpression, IfExpression, ImportSelector, ImportStatement, ItemVisibility, LabeledArg, Literal,
LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Node, NonCodeNode, NonCodeValue,
ObjectExpression, Parameter, PipeExpression, Program, TagDeclarator, UnaryExpression, VariableDeclaration,
VariableKind,
CallExpression, CallExpressionKw, CommentStyle, DefaultParamVal, Expr, FormatOptions, FunctionExpression,
IfExpression, ImportSelector, ImportStatement, ItemVisibility, LabeledArg, Literal, LiteralIdentifier,
LiteralValue, MemberExpression, MemberObject, Node, NonCodeNode, NonCodeValue, ObjectExpression, Parameter,
PipeExpression, Program, TagDeclarator, Type, UnaryExpression, VariableDeclaration, VariableKind,
},
token::NumericSuffix,
PIPE_OPERATOR,
@ -281,6 +280,12 @@ impl Expr {
result += &e.label.name;
result
}
Expr::AscribedExpression(e) => {
let mut result = e.expr.recast(options, indentation_level, ctxt);
result += ": ";
result += &e.ty.recast(options, indentation_level);
result
}
Expr::None(_) => {
unimplemented!("there is no literal None, see https://github.com/KittyCAD/modeling-app/issues/1115")
}
@ -511,23 +516,10 @@ impl ArrayExpression {
/// An expression is syntactically trivial: i.e., a literal, identifier, or similar.
fn expr_is_trivial(expr: &Expr) -> bool {
match expr {
Expr::Literal(_) | Expr::Identifier(_) | Expr::TagDeclarator(_) | Expr::PipeSubstitution(_) | Expr::None(_) => {
true
}
Expr::BinaryExpression(_)
| Expr::FunctionExpression(_)
| Expr::CallExpression(_)
| Expr::CallExpressionKw(_)
| Expr::PipeExpression(_)
| Expr::ArrayExpression(_)
| Expr::ArrayRangeExpression(_)
| Expr::ObjectExpression(_)
| Expr::MemberExpression(_)
| Expr::UnaryExpression(_)
| Expr::IfExpression(_)
| Expr::LabelledExpression(_) => false,
}
matches!(
expr,
Expr::Literal(_) | Expr::Identifier(_) | Expr::TagDeclarator(_) | Expr::PipeSubstitution(_) | Expr::None(_)
)
}
impl ArrayRangeExpression {
@ -801,12 +793,12 @@ impl Parameter {
}
}
impl FnArgType {
impl Type {
pub fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String {
match self {
FnArgType::Primitive(t) => t.to_string(),
FnArgType::Array(t) => format!("{t}[]"),
FnArgType::Object { properties } => {
Type::Primitive(t) => t.to_string(),
Type::Array(t) => format!("{t}[]"),
Type::Object { properties } => {
let mut result = "{".to_owned();
for p in properties {
result += " ";
@ -2227,8 +2219,8 @@ firstExtrude = startSketchOn('XY')
);
}
#[tokio::test(flavor = "multi_thread")]
async fn test_recast_math_start_negative() {
#[test]
fn test_recast_math_start_negative() {
let some_program_string = r#"myVar = -5 + 6"#;
let program = crate::parsing::top_level_parse(some_program_string).unwrap();
@ -2236,8 +2228,8 @@ firstExtrude = startSketchOn('XY')
assert_eq!(recasted.trim(), some_program_string);
}
#[tokio::test(flavor = "multi_thread")]
async fn test_recast_math_negate_parens() {
#[test]
fn test_recast_math_negate_parens() {
let some_program_string = r#"wallMountL = 3.82
thickness = 0.5
@ -2253,12 +2245,12 @@ startSketchOn('XY')
assert_eq!(recasted.trim(), some_program_string);
}
#[tokio::test(flavor = "multi_thread")]
async fn test_recast_math_nested_parens() {
#[test]
fn test_recast_math_nested_parens() {
let some_program_string = r#"distance = 5
p = 3
FOS = 2
sigmaAllow = 8
p = 3: Plane
FOS = { a = 3, b = 42 }: Sketch
sigmaAllow = 8: number(mm)
width = 20
thickness = sqrt(distance * p * FOS * 6 / (sigmaAllow * width))"#;
let program = crate::parsing::top_level_parse(some_program_string).unwrap();
@ -2267,8 +2259,8 @@ thickness = sqrt(distance * p * FOS * 6 / (sigmaAllow * width))"#;
assert_eq!(recasted.trim(), some_program_string);
}
#[tokio::test(flavor = "multi_thread")]
async fn no_vardec_keyword() {
#[test]
fn no_vardec_keyword() {
let some_program_string = r#"distance = 5"#;
let program = crate::parsing::top_level_parse(some_program_string).unwrap();
@ -2358,7 +2350,7 @@ sketch002 = startSketchOn({
#[test]
fn unparse_fn_unnamed() {
let input = r#"squares_out = reduce(arr, 0, fn(i, squares) {
let input = r#"squares_out = reduce(arr, 0: number, fn(i, squares) {
return 1
})
"#;

View File

@ -33,6 +33,7 @@ pub enum Node<'a> {
IfExpression(NodeRef<'a, types::IfExpression>),
ElseIf(&'a types::ElseIf),
LabelledExpression(NodeRef<'a, types::LabelledExpression>),
Ascription(NodeRef<'a, types::Ascription>),
Parameter(&'a types::Parameter),
@ -74,6 +75,7 @@ impl Node<'_> {
Node::ElseIf(n) => n.digest,
Node::KclNone(n) => n.digest,
Node::LabelledExpression(n) => n.digest,
Node::Ascription(n) => n.digest,
}
}
@ -116,6 +118,7 @@ impl Node<'_> {
Node::ElseIf(n) => *n as *const _ as *const (),
Node::KclNone(n) => *n as *const _ as *const (),
Node::LabelledExpression(n) => *n as *const _ as *const (),
Node::Ascription(n) => *n as *const _ as *const (),
}
}
}
@ -156,6 +159,7 @@ impl TryFrom<&Node<'_>> for SourceRange {
Node::ObjectProperty(n) => SourceRange::from(*n),
Node::IfExpression(n) => SourceRange::from(*n),
Node::LabelledExpression(n) => SourceRange::from(*n),
Node::Ascription(n) => SourceRange::from(*n),
// This is broken too
Node::ElseIf(n) => SourceRange::new(n.cond.start(), n.cond.end(), n.cond.module_id()),
@ -197,6 +201,7 @@ impl<'tree> From<&'tree types::Expr> for Node<'tree> {
types::Expr::UnaryExpression(ue) => ue.as_ref().into(),
types::Expr::IfExpression(e) => e.as_ref().into(),
types::Expr::LabelledExpression(e) => e.as_ref().into(),
types::Expr::AscribedExpression(e) => e.as_ref().into(),
types::Expr::None(n) => n.into(),
}
}
@ -280,6 +285,7 @@ impl_from_ref!(Node, Parameter);
impl_from!(Node, IfExpression);
impl_from!(Node, ElseIf);
impl_from!(Node, LabelledExpression);
impl_from!(Node, Ascription);
impl_from!(Node, KclNone);
#[cfg(test)]

View File

@ -130,6 +130,9 @@ impl<'tree> Visitable<'tree> for Node<'tree> {
Node::LabelledExpression(e) => {
vec![(&e.expr).into(), (&e.label).into()]
}
Node::Ascription(e) => {
vec![(&e.expr).into()]
}
Node::PipeSubstitution(_)
| Node::TagDeclarator(_)
| Node::Identifier(_)

View File

@ -8,3 +8,24 @@ export ZERO = 0
export QUARTER_TURN = 90deg
export HALF_TURN = 180deg
export THREE_QUARTER_TURN = 270deg
export XY = {
origin = { x = 0, y = 0, z = 0 },
xAxis = { x = 1, y = 0, z = 0 },
yAxis = { x = 0, y = 1, z = 0 },
zAxis = { x = 0, y = 0, z = 1 },
}: Plane
export XZ = {
origin = { x = 0, y = 0, z = 0 },
xAxis = { x = 1, y = 0, z = 0 },
yAxis = { x = 0, y = 0, z = 1 },
zAxis = { x = 0, y = -1, z = 0 },
}: Plane
export YZ = {
origin = { x = 0, y = 0, z = 0 },
xAxis = { x = 0, y = 1, z = 0 },
yAxis = { x = 0, y = 0, z = 1 },
zAxis = { x = 1, y = 0, z = 0 },
}: Plane