Whole module imports (#4767)

Signed-off-by: Nick Cameron <nrc@ncameron.org>
This commit is contained in:
Nick Cameron
2024-12-17 09:38:32 +13:00
committed by GitHub
parent fa22c14723
commit 8f9dc06228
63 changed files with 1283 additions and 358 deletions

View File

@ -9,7 +9,7 @@ Create a 3D surface or solid by interpolating between two or more sketches.
The sketches need to closed and on the same plane. The sketches need to closed and on the same plane.
```js ```js
loft(sketches: [Sketch], v_degree: NonZeroU32, bez_approximate_rational: bool, base_curve_index?: u32, tolerance?: number) -> Solid loft(sketches: [Sketch], v_degree: NonZeroU32, bez_approximate_rational: bool, base_curve_index?: integer, tolerance?: number) -> Solid
``` ```
@ -20,7 +20,7 @@ loft(sketches: [Sketch], v_degree: NonZeroU32, bez_approximate_rational: bool, b
| `sketches` | [`[Sketch]`](/docs/kcl/types/Sketch) | Which sketches to loft. Must include at least 2 sketches. | Yes | | `sketches` | [`[Sketch]`](/docs/kcl/types/Sketch) | Which sketches to loft. Must include at least 2 sketches. | Yes |
| `v_degree` | `NonZeroU32` | Degree of the interpolation. Must be greater than zero. For example, use 2 for quadratic, or 3 for cubic interpolation in the V direction. This defaults to 2, if not specified. | Yes | | `v_degree` | `NonZeroU32` | Degree of the interpolation. Must be greater than zero. For example, use 2 for quadratic, or 3 for cubic interpolation in the V direction. This defaults to 2, if not specified. | Yes |
| `bez_approximate_rational` | `bool` | Attempt to approximate rational curves (such as arcs) using a bezier. This will remove banding around interpolations between arcs and non-arcs. It may produce errors in other scenarios Over time, this field won't be necessary. | Yes | | `bez_approximate_rational` | `bool` | Attempt to approximate rational curves (such as arcs) using a bezier. This will remove banding around interpolations between arcs and non-arcs. It may produce errors in other scenarios Over time, this field won't be necessary. | Yes |
| `base_curve_index` | `u32` | This can be set to override the automatically determined topological base curve, which is usually the first section encountered. | No | | `base_curve_index` | `integer` | This can be set to override the automatically determined topological base curve, which is usually the first section encountered. | No |
| `tolerance` | `number` | Tolerance for the loft operation. | No | | `tolerance` | `number` | Tolerance for the loft operation. | No |
### Returns ### Returns

View File

@ -35,7 +35,7 @@ The transform function returns a transform object. All properties of the object
- `rotation.origin` (either "local" i.e. rotate around its own center, "global" i.e. rotate around the scene's center, or a 3D point, defaults to "local") - `rotation.origin` (either "local" i.e. rotate around its own center, "global" i.e. rotate around the scene's center, or a 3D point, defaults to "local")
```js ```js
patternTransform(total_instances: u32, transform_function: FunctionParam, solid_set: SolidSet) -> [Solid] patternTransform(total_instances: integer, transform_function: FunctionParam, solid_set: SolidSet) -> [Solid]
``` ```
@ -43,7 +43,7 @@ patternTransform(total_instances: u32, transform_function: FunctionParam, solid_
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `total_instances` | `u32` | | Yes | | `total_instances` | `integer` | | Yes |
| `transform_function` | `FunctionParam` | | Yes | | `transform_function` | `FunctionParam` | | Yes |
| `solid_set` | [`SolidSet`](/docs/kcl/types/SolidSet) | A solid or a group of solids. | Yes | | `solid_set` | [`SolidSet`](/docs/kcl/types/SolidSet) | A solid or a group of solids. | Yes |

View File

@ -9,7 +9,7 @@ Just like patternTransform, but works on 2D sketches not 3D solids.
```js ```js
patternTransform2d(total_instances: u32, transform_function: FunctionParam, solid_set: SketchSet) -> [Sketch] patternTransform2d(total_instances: integer, transform_function: FunctionParam, solid_set: SketchSet) -> [Sketch]
``` ```
@ -17,7 +17,7 @@ patternTransform2d(total_instances: u32, transform_function: FunctionParam, soli
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `total_instances` | `u32` | | Yes | | `total_instances` | `integer` | | Yes |
| `transform_function` | `FunctionParam` | | Yes | | `transform_function` | `FunctionParam` | | Yes |
| `solid_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | A sketch or a group of sketches. | Yes | | `solid_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | A sketch or a group of sketches. | Yes |

View File

@ -97135,7 +97135,7 @@
}, },
{ {
"name": "base_curve_index", "name": "base_curve_index",
"type": "u32", "type": "integer",
"schema": { "schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
"title": "Nullable_uint32", "title": "Nullable_uint32",
@ -101932,6 +101932,31 @@
} }
} }
}, },
{
"type": "object",
"required": [
"__meta",
"type",
"value"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Module"
]
},
"value": {
"$ref": "#/components/schemas/ModuleId"
},
"__meta": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Metadata"
}
}
}
},
{ {
"type": "object", "type": "object",
"required": [ "required": [
@ -103313,6 +103338,12 @@
} }
} }
}, },
"ModuleId": {
"description": "Identifier of a source file. Uses a u32 to keep the size small.",
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"KclNone": { "KclNone": {
"description": "KCL value for an optional parameter which was not given an argument. (remember, parameters are in the function declaration, arguments are in the function call/application).", "description": "KCL value for an optional parameter which was not given an argument. (remember, parameters are in the function declaration, arguments are in the function call/application).",
"type": "object", "type": "object",
@ -103931,6 +103962,31 @@
} }
} }
}, },
{
"type": "object",
"required": [
"__meta",
"type",
"value"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Module"
]
},
"value": {
"$ref": "#/components/schemas/ModuleId"
},
"__meta": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Metadata"
}
}
}
},
{ {
"type": "object", "type": "object",
"required": [ "required": [
@ -105312,6 +105368,12 @@
} }
} }
}, },
"ModuleId": {
"description": "Identifier of a source file. Uses a u32 to keep the size small.",
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"KclNone": { "KclNone": {
"description": "KCL value for an optional parameter which was not given an argument. (remember, parameters are in the function declaration, arguments are in the function call/application).", "description": "KCL value for an optional parameter which was not given an argument. (remember, parameters are in the function declaration, arguments are in the function call/application).",
"type": "object", "type": "object",
@ -105934,6 +105996,31 @@
} }
} }
}, },
{
"type": "object",
"required": [
"__meta",
"type",
"value"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Module"
]
},
"value": {
"$ref": "#/components/schemas/ModuleId"
},
"__meta": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Metadata"
}
}
}
},
{ {
"type": "object", "type": "object",
"required": [ "required": [
@ -107315,6 +107402,12 @@
} }
} }
}, },
"ModuleId": {
"description": "Identifier of a source file. Uses a u32 to keep the size small.",
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"KclNone": { "KclNone": {
"description": "KCL value for an optional parameter which was not given an argument. (remember, parameters are in the function declaration, arguments are in the function call/application).", "description": "KCL value for an optional parameter which was not given an argument. (remember, parameters are in the function declaration, arguments are in the function call/application).",
"type": "object", "type": "object",
@ -122705,7 +122798,7 @@
"args": [ "args": [
{ {
"name": "total_instances", "name": "total_instances",
"type": "u32", "type": "integer",
"schema": { "schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
"title": "uint32", "title": "uint32",
@ -125482,7 +125575,7 @@
"args": [ "args": [
{ {
"name": "total_instances", "name": "total_instances",
"type": "u32", "type": "integer",
"schema": { "schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
"title": "uint32", "title": "uint32",
@ -137258,6 +137351,31 @@
} }
} }
}, },
{
"type": "object",
"required": [
"__meta",
"type",
"value"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Module"
]
},
"value": {
"$ref": "#/components/schemas/ModuleId"
},
"__meta": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Metadata"
}
}
}
},
{ {
"type": "object", "type": "object",
"required": [ "required": [
@ -138639,6 +138757,12 @@
} }
} }
}, },
"ModuleId": {
"description": "Identifier of a source file. Uses a u32 to keep the size small.",
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"KclNone": { "KclNone": {
"description": "KCL value for an optional parameter which was not given an argument. (remember, parameters are in the function declaration, arguments are in the function call/application).", "description": "KCL value for an optional parameter which was not given an argument. (remember, parameters are in the function declaration, arguments are in the function call/application).",
"type": "object", "type": "object",
@ -139254,6 +139378,31 @@
} }
} }
}, },
{
"type": "object",
"required": [
"__meta",
"type",
"value"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Module"
]
},
"value": {
"$ref": "#/components/schemas/ModuleId"
},
"__meta": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Metadata"
}
}
}
},
{ {
"type": "object", "type": "object",
"required": [ "required": [
@ -139869,6 +140018,31 @@
} }
} }
}, },
{
"type": "object",
"required": [
"__meta",
"type",
"value"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Module"
]
},
"value": {
"$ref": "#/components/schemas/ModuleId"
},
"__meta": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Metadata"
}
}
}
},
{ {
"type": "object", "type": "object",
"required": [ "required": [
@ -141250,6 +141424,12 @@
} }
} }
}, },
"ModuleId": {
"description": "Identifier of a source file. Uses a u32 to keep the size small.",
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"KclNone": { "KclNone": {
"description": "KCL value for an optional parameter which was not given an argument. (remember, parameters are in the function declaration, arguments are in the function call/application).", "description": "KCL value for an optional parameter which was not given an argument. (remember, parameters are in the function declaration, arguments are in the function call/application).",
"type": "object", "type": "object",
@ -141866,6 +142046,31 @@
} }
} }
}, },
{
"type": "object",
"required": [
"__meta",
"type",
"value"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Module"
]
},
"value": {
"$ref": "#/components/schemas/ModuleId"
},
"__meta": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Metadata"
}
}
}
},
{ {
"type": "object", "type": "object",
"required": [ "required": [
@ -142508,6 +142713,31 @@
} }
} }
}, },
{
"type": "object",
"required": [
"__meta",
"type",
"value"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Module"
]
},
"value": {
"$ref": "#/components/schemas/ModuleId"
},
"__meta": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Metadata"
}
}
}
},
{ {
"type": "object", "type": "object",
"required": [ "required": [
@ -143862,6 +144092,12 @@
} }
} }
}, },
"ModuleId": {
"description": "Identifier of a source file. Uses a u32 to keep the size small.",
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"KclNone": { "KclNone": {
"description": "KCL value for an optional parameter which was not given an argument. (remember, parameters are in the function declaration, arguments are in the function call/application).", "description": "KCL value for an optional parameter which was not given an argument. (remember, parameters are in the function declaration, arguments are in the function call/application).",
"type": "object", "type": "object",
@ -144496,6 +144732,31 @@
} }
} }
}, },
{
"type": "object",
"required": [
"__meta",
"type",
"value"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Module"
]
},
"value": {
"$ref": "#/components/schemas/ModuleId"
},
"__meta": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Metadata"
}
}
}
},
{ {
"type": "object", "type": "object",
"required": [ "required": [
@ -145877,6 +146138,12 @@
} }
} }
}, },
"ModuleId": {
"description": "Identifier of a source file. Uses a u32 to keep the size small.",
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"KclNone": { "KclNone": {
"description": "KCL value for an optional parameter which was not given an argument. (remember, parameters are in the function declaration, arguments are in the function call/application).", "description": "KCL value for an optional parameter which was not given an argument. (remember, parameters are in the function declaration, arguments are in the function call/application).",
"type": "object", "type": "object",
@ -146492,6 +146759,31 @@
} }
} }
}, },
{
"type": "object",
"required": [
"__meta",
"type",
"value"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Module"
]
},
"value": {
"$ref": "#/components/schemas/ModuleId"
},
"__meta": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Metadata"
}
}
}
},
{ {
"type": "object", "type": "object",
"required": [ "required": [
@ -147107,6 +147399,31 @@
} }
} }
}, },
{
"type": "object",
"required": [
"__meta",
"type",
"value"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Module"
]
},
"value": {
"$ref": "#/components/schemas/ModuleId"
},
"__meta": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Metadata"
}
}
}
},
{ {
"type": "object", "type": "object",
"required": [ "required": [
@ -148488,6 +148805,12 @@
} }
} }
}, },
"ModuleId": {
"description": "Identifier of a source file. Uses a u32 to keep the size small.",
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"KclNone": { "KclNone": {
"description": "KCL value for an optional parameter which was not given an argument. (remember, parameters are in the function declaration, arguments are in the function call/application).", "description": "KCL value for an optional parameter which was not given an argument. (remember, parameters are in the function declaration, arguments are in the function call/application).",
"type": "object", "type": "object",
@ -149106,6 +149429,31 @@
} }
} }
}, },
{
"type": "object",
"required": [
"__meta",
"type",
"value"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Module"
]
},
"value": {
"$ref": "#/components/schemas/ModuleId"
},
"__meta": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Metadata"
}
}
}
},
{ {
"type": "object", "type": "object",
"required": [ "required": [
@ -150487,6 +150835,12 @@
} }
} }
}, },
"ModuleId": {
"description": "Identifier of a source file. Uses a u32 to keep the size small.",
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"KclNone": { "KclNone": {
"description": "KCL value for an optional parameter which was not given an argument. (remember, parameters are in the function declaration, arguments are in the function call/application).", "description": "KCL value for an optional parameter which was not given an argument. (remember, parameters are in the function declaration, arguments are in the function call/application).",
"type": "object", "type": "object",
@ -151103,6 +151457,31 @@
} }
} }
}, },
{
"type": "object",
"required": [
"__meta",
"type",
"value"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Module"
]
},
"value": {
"$ref": "#/components/schemas/ModuleId"
},
"__meta": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Metadata"
}
}
}
},
{ {
"type": "object", "type": "object",
"required": [ "required": [
@ -151745,6 +152124,31 @@
} }
} }
}, },
{
"type": "object",
"required": [
"__meta",
"type",
"value"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Module"
]
},
"value": {
"$ref": "#/components/schemas/ModuleId"
},
"__meta": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Metadata"
}
}
}
},
{ {
"type": "object", "type": "object",
"required": [ "required": [
@ -153099,6 +153503,12 @@
} }
} }
}, },
"ModuleId": {
"description": "Identifier of a source file. Uses a u32 to keep the size small.",
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"KclNone": { "KclNone": {
"description": "KCL value for an optional parameter which was not given an argument. (remember, parameters are in the function declaration, arguments are in the function call/application).", "description": "KCL value for an optional parameter which was not given an argument. (remember, parameters are in the function declaration, arguments are in the function call/application).",
"type": "object", "type": "object",

View File

@ -13,13 +13,18 @@ Data to draw an angled line.
An angle and length with explicitly named parameters An angle and length with explicitly named parameters
[`PolarCoordsData`](/docs/kcl/types/PolarCoordsData) **Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `angle` |`number`| The angle of the line (in degrees). | No |
| `length` |`number`| The length of the line. | No |
---- ----

View File

@ -329,6 +329,23 @@ Data for an imported geometry.
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Module`| | No |
| `value` |[`ModuleId`](/docs/kcl/types/ModuleId)| Any KCL value. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
----
**Type:** `object`
## Properties ## Properties
| Property | Type | Description | Required | | Property | Type | Description | Required |

View File

@ -0,0 +1,16 @@
---
title: "ModuleId"
excerpt: "Identifier of a source file. Uses a u32 to keep the size small."
layout: manual
---
Identifier of a source file. Uses a u32 to keep the size small.
**Type:** `integer` (`uint32`)

View File

@ -397,6 +397,7 @@ function moreNodePathFromSourceRange(
} }
return path return path
} }
return path
} }
console.error('not implemented: ' + node.type) console.error('not implemented: ' + node.type)

View File

@ -259,7 +259,7 @@ export function emptyExecState(): ExecState {
function execStateFromRaw(raw: RawExecState): ExecState { function execStateFromRaw(raw: RawExecState): ExecState {
return { return {
memory: ProgramMemory.fromRaw(raw.memory), memory: ProgramMemory.fromRaw(raw.modLocal.memory),
} }
} }

View File

@ -779,6 +779,8 @@ fn rust_type_to_openapi_type(t: &str) -> String {
if t == "f64" { if t == "f64" {
return "number".to_string(); return "number".to_string();
} else if t == "u32" {
return "integer".to_string();
} else if t == "str" { } else if t == "str" {
return "string".to_string(); return "string".to_string();
} else { } else {
@ -813,7 +815,7 @@ fn generate_code_block_test(fn_name: &str, code_block: &str, index: usize) -> pr
context_type: crate::execution::ContextType::Mock, context_type: crate::execution::ContextType::Mock,
}; };
ctx.run(program.into(), &mut crate::ExecState::default()).await.unwrap(); ctx.run(program.into(), &mut crate::ExecState::new()).await.unwrap();
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]

View File

@ -14,7 +14,7 @@ mod test_examples_someFn {
settings: Default::default(), settings: Default::default(),
context_type: crate::execution::ContextType::Mock, context_type: crate::execution::ContextType::Mock,
}; };
ctx.run(program.into(), &mut crate::ExecState::default()) ctx.run(program.into(), &mut crate::ExecState::new())
.await .await
.unwrap(); .unwrap();
} }

View File

@ -14,7 +14,7 @@ mod test_examples_someFn {
settings: Default::default(), settings: Default::default(),
context_type: crate::execution::ContextType::Mock, context_type: crate::execution::ContextType::Mock,
}; };
ctx.run(program.into(), &mut crate::ExecState::default()) ctx.run(program.into(), &mut crate::ExecState::new())
.await .await
.unwrap(); .unwrap();
} }

View File

@ -15,7 +15,7 @@ mod test_examples_show {
settings: Default::default(), settings: Default::default(),
context_type: crate::execution::ContextType::Mock, context_type: crate::execution::ContextType::Mock,
}; };
ctx.run(program.into(), &mut crate::ExecState::default()) ctx.run(program.into(), &mut crate::ExecState::new())
.await .await
.unwrap(); .unwrap();
} }
@ -52,7 +52,7 @@ mod test_examples_show {
settings: Default::default(), settings: Default::default(),
context_type: crate::execution::ContextType::Mock, context_type: crate::execution::ContextType::Mock,
}; };
ctx.run(program.into(), &mut crate::ExecState::default()) ctx.run(program.into(), &mut crate::ExecState::new())
.await .await
.unwrap(); .unwrap();
} }

View File

@ -15,7 +15,7 @@ mod test_examples_show {
settings: Default::default(), settings: Default::default(),
context_type: crate::execution::ContextType::Mock, context_type: crate::execution::ContextType::Mock,
}; };
ctx.run(program.into(), &mut crate::ExecState::default()) ctx.run(program.into(), &mut crate::ExecState::new())
.await .await
.unwrap(); .unwrap();
} }

View File

@ -16,7 +16,7 @@ mod test_examples_my_func {
settings: Default::default(), settings: Default::default(),
context_type: crate::execution::ContextType::Mock, context_type: crate::execution::ContextType::Mock,
}; };
ctx.run(program.into(), &mut crate::ExecState::default()) ctx.run(program.into(), &mut crate::ExecState::new())
.await .await
.unwrap(); .unwrap();
} }
@ -53,7 +53,7 @@ mod test_examples_my_func {
settings: Default::default(), settings: Default::default(),
context_type: crate::execution::ContextType::Mock, context_type: crate::execution::ContextType::Mock,
}; };
ctx.run(program.into(), &mut crate::ExecState::default()) ctx.run(program.into(), &mut crate::ExecState::new())
.await .await
.unwrap(); .unwrap();
} }

View File

@ -16,7 +16,7 @@ mod test_examples_line_to {
settings: Default::default(), settings: Default::default(),
context_type: crate::execution::ContextType::Mock, context_type: crate::execution::ContextType::Mock,
}; };
ctx.run(program.into(), &mut crate::ExecState::default()) ctx.run(program.into(), &mut crate::ExecState::new())
.await .await
.unwrap(); .unwrap();
} }
@ -53,7 +53,7 @@ mod test_examples_line_to {
settings: Default::default(), settings: Default::default(),
context_type: crate::execution::ContextType::Mock, context_type: crate::execution::ContextType::Mock,
}; };
ctx.run(program.into(), &mut crate::ExecState::default()) ctx.run(program.into(), &mut crate::ExecState::new())
.await .await
.unwrap(); .unwrap();
} }

View File

@ -15,7 +15,7 @@ mod test_examples_min {
settings: Default::default(), settings: Default::default(),
context_type: crate::execution::ContextType::Mock, context_type: crate::execution::ContextType::Mock,
}; };
ctx.run(program.into(), &mut crate::ExecState::default()) ctx.run(program.into(), &mut crate::ExecState::new())
.await .await
.unwrap(); .unwrap();
} }
@ -52,7 +52,7 @@ mod test_examples_min {
settings: Default::default(), settings: Default::default(),
context_type: crate::execution::ContextType::Mock, context_type: crate::execution::ContextType::Mock,
}; };
ctx.run(program.into(), &mut crate::ExecState::default()) ctx.run(program.into(), &mut crate::ExecState::new())
.await .await
.unwrap(); .unwrap();
} }

View File

@ -15,7 +15,7 @@ mod test_examples_show {
settings: Default::default(), settings: Default::default(),
context_type: crate::execution::ContextType::Mock, context_type: crate::execution::ContextType::Mock,
}; };
ctx.run(program.into(), &mut crate::ExecState::default()) ctx.run(program.into(), &mut crate::ExecState::new())
.await .await
.unwrap(); .unwrap();
} }

View File

@ -15,7 +15,7 @@ mod test_examples_import {
settings: Default::default(), settings: Default::default(),
context_type: crate::execution::ContextType::Mock, context_type: crate::execution::ContextType::Mock,
}; };
ctx.run(program.into(), &mut crate::ExecState::default()) ctx.run(program.into(), &mut crate::ExecState::new())
.await .await
.unwrap(); .unwrap();
} }

View File

@ -15,7 +15,7 @@ mod test_examples_import {
settings: Default::default(), settings: Default::default(),
context_type: crate::execution::ContextType::Mock, context_type: crate::execution::ContextType::Mock,
}; };
ctx.run(program.into(), &mut crate::ExecState::default()) ctx.run(program.into(), &mut crate::ExecState::new())
.await .await
.unwrap(); .unwrap();
} }

View File

@ -15,7 +15,7 @@ mod test_examples_import {
settings: Default::default(), settings: Default::default(),
context_type: crate::execution::ContextType::Mock, context_type: crate::execution::ContextType::Mock,
}; };
ctx.run(program.into(), &mut crate::ExecState::default()) ctx.run(program.into(), &mut crate::ExecState::new())
.await .await
.unwrap(); .unwrap();
} }

View File

@ -15,7 +15,7 @@ mod test_examples_show {
settings: Default::default(), settings: Default::default(),
context_type: crate::execution::ContextType::Mock, context_type: crate::execution::ContextType::Mock,
}; };
ctx.run(program.into(), &mut crate::ExecState::default()) ctx.run(program.into(), &mut crate::ExecState::new())
.await .await
.unwrap(); .unwrap();
} }

View File

@ -14,7 +14,7 @@ mod test_examples_some_function {
settings: Default::default(), settings: Default::default(),
context_type: crate::execution::ContextType::Mock, context_type: crate::execution::ContextType::Mock,
}; };
ctx.run(program.into(), &mut crate::ExecState::default()) ctx.run(program.into(), &mut crate::ExecState::new())
.await .await
.unwrap(); .unwrap();
} }

View File

@ -164,7 +164,7 @@ async fn snapshot_endpoint(body: Bytes, state: ExecutorContext) -> Response<Body
}; };
eprintln!("Executing {test_name}"); eprintln!("Executing {test_name}");
let mut exec_state = ExecState::default(); let mut exec_state = ExecState::new();
// This is a shitty source range, I don't know what else to use for it though. // This is a shitty source range, I don't know what else to use for it though.
// There's no actual KCL associated with this reset_scene call. // There's no actual KCL associated with this reset_scene call.
if let Err(e) = state if let Err(e) = state

View File

@ -16,7 +16,7 @@ pub async fn kcl_to_engine_core(code: &str) -> Result<String> {
let ctx = ExecutorContext::new_forwarded_mock(Arc::new(Box::new( let ctx = ExecutorContext::new_forwarded_mock(Arc::new(Box::new(
crate::conn_mock_core::EngineConnection::new(ref_result).await?, crate::conn_mock_core::EngineConnection::new(ref_result).await?,
))); )));
ctx.run(program.into(), &mut ExecState::default()).await?; ctx.run(program.into(), &mut ExecState::new()).await?;
let result = result.lock().expect("mutex lock").clone(); let result = result.lock().expect("mutex lock").clone();
Ok(result) Ok(result)

View File

@ -604,24 +604,6 @@ fn clean_function_name(name: &str) -> String {
fn_name fn_name
} }
/// Check if a schema is the same as another schema, but don't check the description.
fn is_same_schema(sa: &schemars::schema::Schema, sb: &schemars::schema::Schema) -> bool {
let schemars::schema::Schema::Object(a) = sa else {
return sa == sb;
};
let schemars::schema::Schema::Object(b) = sb else {
return sa == sb;
};
let mut a = a.clone();
a.metadata = None;
let mut b = b.clone();
b.metadata = None;
a == b
}
/// Recursively create references for types we already know about. /// Recursively create references for types we already know about.
fn recurse_and_create_references( fn recurse_and_create_references(
name: &str, name: &str,
@ -655,24 +637,6 @@ fn recurse_and_create_references(
return Ok(schemars::schema::Schema::Object(obj)); return Ok(schemars::schema::Schema::Object(obj));
} }
// Check if this is the type we already know about.
for (n, s) in types {
if is_same_schema(schema, s) && name != n && !n.starts_with("[") {
// Return a reference to the type.
let sref = schemars::schema::Schema::new_ref(n.to_string());
// Add the existing metadata to the reference.
let schemars::schema::Schema::Object(ro) = sref else {
return Err(anyhow::anyhow!(
"Failed to get object schema, should have not been a primitive"
));
};
let mut ro = ro.clone();
ro.metadata = o.metadata.clone();
return Ok(schemars::schema::Schema::Object(ro));
}
}
let mut obj = o.clone(); let mut obj = o.clone();
// If we have an object iterate over the properties and recursively create references. // If we have an object iterate over the properties and recursively create references.

View File

@ -29,7 +29,7 @@ impl BinaryPart {
match self { match self {
BinaryPart::Literal(literal) => Ok(literal.into()), BinaryPart::Literal(literal) => Ok(literal.into()),
BinaryPart::Identifier(identifier) => { BinaryPart::Identifier(identifier) => {
let value = exec_state.memory.get(&identifier.name, identifier.into())?; let value = exec_state.memory().get(&identifier.name, identifier.into())?;
Ok(value.clone()) Ok(value.clone())
} }
BinaryPart::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, ctx).await, BinaryPart::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, ctx).await,
@ -47,7 +47,7 @@ impl Node<MemberExpression> {
let array = match &self.object { let array = match &self.object {
MemberObject::MemberExpression(member_expr) => member_expr.get_result(exec_state)?, MemberObject::MemberExpression(member_expr) => member_expr.get_result(exec_state)?,
MemberObject::Identifier(identifier) => { MemberObject::Identifier(identifier) => {
let value = exec_state.memory.get(&identifier.name, identifier.into())?; let value = exec_state.memory().get(&identifier.name, identifier.into())?;
value.clone() value.clone()
} }
}; };
@ -75,7 +75,7 @@ impl Node<MemberExpression> {
// TODO: Don't use recursion here, use a loop. // TODO: Don't use recursion here, use a loop.
MemberObject::MemberExpression(member_expr) => member_expr.get_result(exec_state)?, MemberObject::MemberExpression(member_expr) => member_expr.get_result(exec_state)?,
MemberObject::Identifier(identifier) => { MemberObject::Identifier(identifier) => {
let value = exec_state.memory.get(&identifier.name, identifier.into())?; let value = exec_state.memory().get(&identifier.name, identifier.into())?;
value.clone() value.clone()
} }
}; };
@ -310,11 +310,11 @@ pub(crate) async fn execute_pipe_body(
// Now that we've evaluated the first child expression in the pipeline, following child expressions // Now that we've evaluated the first child expression in the pipeline, following child expressions
// should use the previous child expression for %. // should use the previous child expression for %.
// This means there's no more need for the previous pipe_value from the parent AST node above this one. // This means there's no more need for the previous pipe_value from the parent AST node above this one.
let previous_pipe_value = std::mem::replace(&mut exec_state.pipe_value, Some(output)); let previous_pipe_value = std::mem::replace(&mut exec_state.mod_local.pipe_value, Some(output));
// Evaluate remaining elements. // Evaluate remaining elements.
let result = inner_execute_pipe_body(exec_state, body, ctx).await; let result = inner_execute_pipe_body(exec_state, body, ctx).await;
// Restore the previous pipe value. // Restore the previous pipe value.
exec_state.pipe_value = previous_pipe_value; exec_state.mod_local.pipe_value = previous_pipe_value;
result result
} }
@ -340,10 +340,10 @@ async fn inner_execute_pipe_body(
let output = ctx let output = ctx
.execute_expr(expression, exec_state, &metadata, StatementKind::Expression) .execute_expr(expression, exec_state, &metadata, StatementKind::Expression)
.await?; .await?;
exec_state.pipe_value = Some(output); exec_state.mod_local.pipe_value = Some(output);
} }
// Safe to unwrap here, because pipe_value always has something pushed in when the `match first` executes. // Safe to unwrap here, because pipe_value always has something pushed in when the `match first` executes.
let final_output = exec_state.pipe_value.take().unwrap(); let final_output = exec_state.mod_local.pipe_value.take().unwrap();
Ok(final_output) Ok(final_output)
} }
@ -417,7 +417,7 @@ impl Node<CallExpressionKw> {
// before running, and we will likely want to use the // before running, and we will likely want to use the
// return value. The call takes ownership of the args, // return value. The call takes ownership of the args,
// so we need to build the op before the call. // so we need to build the op before the call.
exec_state.operations.push(op); exec_state.mod_local.operations.push(op);
} }
result result
}; };
@ -431,8 +431,8 @@ impl Node<CallExpressionKw> {
let source_range = SourceRange::from(self); let source_range = SourceRange::from(self);
// Clone the function so that we can use a mutable reference to // Clone the function so that we can use a mutable reference to
// exec_state. // exec_state.
let func = exec_state.memory.get(fn_name, source_range)?.clone(); let func = exec_state.memory().get(fn_name, source_range)?.clone();
let fn_dynamic_state = exec_state.dynamic_state.merge(&exec_state.memory); let fn_dynamic_state = exec_state.mod_local.dynamic_state.merge(exec_state.memory());
// Track call operation. // Track call operation.
let op_labeled_args = args let op_labeled_args = args
@ -441,16 +441,20 @@ impl Node<CallExpressionKw> {
.iter() .iter()
.map(|(k, v)| (k.clone(), OpArg::new(v.source_range))) .map(|(k, v)| (k.clone(), OpArg::new(v.source_range)))
.collect(); .collect();
exec_state.operations.push(Operation::UserDefinedFunctionCall { exec_state
name: Some(fn_name.clone()), .mod_local
function_source_range: func.function_def_source_range().unwrap_or_default(), .operations
unlabeled_arg: args.kw_args.unlabeled.as_ref().map(|arg| OpArg::new(arg.source_range)), .push(Operation::UserDefinedFunctionCall {
labeled_args: op_labeled_args, name: Some(fn_name.clone()),
source_range: callsite, function_source_range: func.function_def_source_range().unwrap_or_default(),
}); unlabeled_arg: args.kw_args.unlabeled.as_ref().map(|arg| OpArg::new(arg.source_range)),
labeled_args: op_labeled_args,
source_range: callsite,
});
let return_value = { let return_value = {
let previous_dynamic_state = std::mem::replace(&mut exec_state.dynamic_state, fn_dynamic_state); let previous_dynamic_state =
std::mem::replace(&mut exec_state.mod_local.dynamic_state, fn_dynamic_state);
let result = func let result = func
.call_fn_kw(args, exec_state, ctx.clone(), callsite) .call_fn_kw(args, exec_state, ctx.clone(), callsite)
.await .await
@ -459,7 +463,7 @@ impl Node<CallExpressionKw> {
// TODO currently ignored by the frontend // TODO currently ignored by the frontend
e.add_source_ranges(vec![source_range]) e.add_source_ranges(vec![source_range])
}); });
exec_state.dynamic_state = previous_dynamic_state; exec_state.mod_local.dynamic_state = previous_dynamic_state;
result? result?
}; };
@ -476,7 +480,10 @@ impl Node<CallExpressionKw> {
})?; })?;
// Track return operation. // Track return operation.
exec_state.operations.push(Operation::UserDefinedFunctionReturn); exec_state
.mod_local
.operations
.push(Operation::UserDefinedFunctionReturn);
Ok(result) Ok(result)
} }
@ -537,7 +544,7 @@ impl Node<CallExpression> {
// before running, and we will likely want to use the // before running, and we will likely want to use the
// return value. The call takes ownership of the args, // return value. The call takes ownership of the args,
// so we need to build the op before the call. // so we need to build the op before the call.
exec_state.operations.push(op); exec_state.mod_local.operations.push(op);
} }
result result
}; };
@ -551,27 +558,31 @@ impl Node<CallExpression> {
let source_range = SourceRange::from(self); let source_range = SourceRange::from(self);
// Clone the function so that we can use a mutable reference to // Clone the function so that we can use a mutable reference to
// exec_state. // exec_state.
let func = exec_state.memory.get(fn_name, source_range)?.clone(); let func = exec_state.memory().get(fn_name, source_range)?.clone();
let fn_dynamic_state = exec_state.dynamic_state.merge(&exec_state.memory); let fn_dynamic_state = exec_state.mod_local.dynamic_state.merge(exec_state.memory());
// Track call operation. // Track call operation.
exec_state.operations.push(Operation::UserDefinedFunctionCall { exec_state
name: Some(fn_name.clone()), .mod_local
function_source_range: func.function_def_source_range().unwrap_or_default(), .operations
unlabeled_arg: None, .push(Operation::UserDefinedFunctionCall {
// TODO: Add the arguments for legacy positional parameters. name: Some(fn_name.clone()),
labeled_args: Default::default(), function_source_range: func.function_def_source_range().unwrap_or_default(),
source_range: callsite, unlabeled_arg: None,
}); // TODO: Add the arguments for legacy positional parameters.
labeled_args: Default::default(),
source_range: callsite,
});
let return_value = { let return_value = {
let previous_dynamic_state = std::mem::replace(&mut exec_state.dynamic_state, fn_dynamic_state); let previous_dynamic_state =
std::mem::replace(&mut exec_state.mod_local.dynamic_state, fn_dynamic_state);
let result = func.call_fn(fn_args, exec_state, ctx.clone()).await.map_err(|e| { let result = func.call_fn(fn_args, exec_state, ctx.clone()).await.map_err(|e| {
// Add the call expression to the source ranges. // Add the call expression to the source ranges.
// TODO currently ignored by the frontend // TODO currently ignored by the frontend
e.add_source_ranges(vec![source_range]) e.add_source_ranges(vec![source_range])
}); });
exec_state.dynamic_state = previous_dynamic_state; exec_state.mod_local.dynamic_state = previous_dynamic_state;
result? result?
}; };
@ -588,7 +599,10 @@ impl Node<CallExpression> {
})?; })?;
// Track return operation. // Track return operation.
exec_state.operations.push(Operation::UserDefinedFunctionReturn); exec_state
.mod_local
.operations
.push(Operation::UserDefinedFunctionReturn);
Ok(result) Ok(result)
} }
@ -604,7 +618,7 @@ fn update_memory_for_tags_of_geometry(result: &mut KclValue, exec_state: &mut Ex
match result { match result {
KclValue::Sketch { value: ref mut sketch } => { KclValue::Sketch { value: ref mut sketch } => {
for (_, tag) in sketch.tags.iter() { for (_, tag) in sketch.tags.iter() {
exec_state.memory.update_tag(&tag.value, tag.clone())?; exec_state.mut_memory().update_tag(&tag.value, tag.clone())?;
} }
} }
KclValue::Solid(ref mut solid) => { KclValue::Solid(ref mut solid) => {
@ -642,7 +656,7 @@ fn update_memory_for_tags_of_geometry(result: &mut KclValue, exec_state: &mut Ex
info.sketch = solid.id; info.sketch = solid.id;
t.info = Some(info); t.info = Some(info);
exec_state.memory.update_tag(&tag.name, t.clone())?; exec_state.mut_memory().update_tag(&tag.name, t.clone())?;
// update the sketch tags. // update the sketch tags.
solid.sketch.tags.insert(tag.name.clone(), t); solid.sketch.tags.insert(tag.name.clone(), t);
@ -650,11 +664,8 @@ fn update_memory_for_tags_of_geometry(result: &mut KclValue, exec_state: &mut Ex
} }
// Find the stale sketch in memory and update it. // Find the stale sketch in memory and update it.
if let Some(current_env) = exec_state let cur_env_index = exec_state.memory().current_env.index();
.memory if let Some(current_env) = exec_state.mut_memory().environments.get_mut(cur_env_index) {
.environments
.get_mut(exec_state.memory.current_env.index())
{
current_env.update_sketch_tags(&solid.sketch); current_env.update_sketch_tags(&solid.sketch);
} }
} }
@ -673,7 +684,9 @@ impl Node<TagDeclarator> {
}], }],
})); }));
exec_state.memory.add(&self.name, memory_item.clone(), self.into())?; exec_state
.mut_memory()
.add(&self.name, memory_item.clone(), self.into())?;
Ok(self.into()) Ok(self.into())
} }
@ -868,7 +881,7 @@ impl Property {
Ok(Property::String(name.to_string())) Ok(Property::String(name.to_string()))
} else { } else {
// Actually evaluate memory to compute the property. // Actually evaluate memory to compute the property.
let prop = exec_state.memory.get(name, property_src)?; let prop = exec_state.memory().get(name, property_src)?;
jvalue_to_prop(prop, property_sr, name) jvalue_to_prop(prop, property_sr, name)
} }
} }

View File

@ -10,7 +10,7 @@ use crate::{
execution::{Face, ImportedGeometry, MemoryFunction, Metadata, Plane, SketchSet, Solid, SolidSet, TagIdentifier}, execution::{Face, ImportedGeometry, MemoryFunction, Metadata, Plane, SketchSet, Solid, SolidSet, TagIdentifier},
parsing::ast::types::{FunctionExpression, KclNone, LiteralValue, TagDeclarator, TagNode}, parsing::ast::types::{FunctionExpression, KclNone, LiteralValue, TagDeclarator, TagNode},
std::{args::Arg, FnAsArg}, std::{args::Arg, FnAsArg},
ExecState, ExecutorContext, KclError, SourceRange, ExecState, ExecutorContext, KclError, ModuleId, SourceRange,
}; };
pub type KclObjectFields = HashMap<String, KclValue>; pub type KclObjectFields = HashMap<String, KclValue>;
@ -84,6 +84,11 @@ pub enum KclValue {
#[serde(rename = "__meta")] #[serde(rename = "__meta")]
meta: Vec<Metadata>, meta: Vec<Metadata>,
}, },
Module {
value: ModuleId,
#[serde(rename = "__meta")]
meta: Vec<Metadata>,
},
KclNone { KclNone {
value: KclNone, value: KclNone,
#[serde(rename = "__meta")] #[serde(rename = "__meta")]
@ -143,6 +148,7 @@ impl From<KclValue> for Vec<SourceRange> {
KclValue::String { meta, .. } => to_vec_sr(&meta), KclValue::String { meta, .. } => to_vec_sr(&meta),
KclValue::Array { meta, .. } => to_vec_sr(&meta), KclValue::Array { meta, .. } => to_vec_sr(&meta),
KclValue::Object { meta, .. } => to_vec_sr(&meta), KclValue::Object { meta, .. } => to_vec_sr(&meta),
KclValue::Module { meta, .. } => to_vec_sr(&meta),
KclValue::Uuid { meta, .. } => to_vec_sr(&meta), KclValue::Uuid { meta, .. } => to_vec_sr(&meta),
KclValue::KclNone { meta, .. } => to_vec_sr(&meta), KclValue::KclNone { meta, .. } => to_vec_sr(&meta),
} }
@ -173,6 +179,7 @@ impl From<&KclValue> for Vec<SourceRange> {
KclValue::Uuid { meta, .. } => to_vec_sr(meta), KclValue::Uuid { meta, .. } => to_vec_sr(meta),
KclValue::Array { meta, .. } => to_vec_sr(meta), KclValue::Array { meta, .. } => to_vec_sr(meta),
KclValue::Object { meta, .. } => to_vec_sr(meta), KclValue::Object { meta, .. } => to_vec_sr(meta),
KclValue::Module { meta, .. } => to_vec_sr(meta),
KclValue::KclNone { meta, .. } => to_vec_sr(meta), KclValue::KclNone { meta, .. } => to_vec_sr(meta),
} }
} }
@ -198,6 +205,7 @@ impl KclValue {
KclValue::Solids { value } => value.iter().flat_map(|sketch| &sketch.meta).copied().collect(), KclValue::Solids { value } => value.iter().flat_map(|sketch| &sketch.meta).copied().collect(),
KclValue::ImportedGeometry(x) => x.meta.clone(), KclValue::ImportedGeometry(x) => x.meta.clone(),
KclValue::Function { meta, .. } => meta.clone(), KclValue::Function { meta, .. } => meta.clone(),
KclValue::Module { meta, .. } => meta.clone(),
KclValue::KclNone { meta, .. } => meta.clone(), KclValue::KclNone { meta, .. } => meta.clone(),
} }
} }
@ -263,6 +271,7 @@ impl KclValue {
KclValue::String { .. } => "string (text)", KclValue::String { .. } => "string (text)",
KclValue::Array { .. } => "array (list)", KclValue::Array { .. } => "array (list)",
KclValue::Object { .. } => "object", KclValue::Object { .. } => "object",
KclValue::Module { .. } => "module",
KclValue::KclNone { .. } => "None", KclValue::KclNone { .. } => "None",
} }
} }

View File

@ -22,6 +22,7 @@ type Point3D = kcmc::shared::Point3d<f64>;
pub use function_param::FunctionParam; pub use function_param::FunctionParam;
pub use kcl_value::{KclObjectFields, KclValue}; pub use kcl_value::{KclObjectFields, KclValue};
use uuid::Uuid;
pub(crate) mod cache; pub(crate) mod cache;
mod cad_op; mod cad_op;
@ -35,7 +36,8 @@ use crate::{
execution::cache::{CacheInformation, CacheResult}, execution::cache::{CacheInformation, CacheResult},
fs::{FileManager, FileSystem}, fs::{FileManager, FileSystem},
parsing::ast::types::{ parsing::ast::types::{
BodyItem, Expr, FunctionExpression, ImportSelector, ItemVisibility, Node, NodeRef, TagDeclarator, TagNode, BodyItem, Expr, FunctionExpression, ImportSelector, ItemVisibility, Node, NodeRef, Program as AstProgram,
TagDeclarator, TagNode,
}, },
settings::types::UnitLength, settings::types::UnitLength,
source_range::{ModuleId, SourceRange}, source_range::{ModuleId, SourceRange},
@ -47,14 +49,32 @@ use crate::{
pub use cad_op::Operation; pub use cad_op::Operation;
/// State for executing a program. /// State for executing a program.
#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)] #[ts(export)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct ExecState { pub struct ExecState {
/// Program variable bindings. pub global: GlobalState,
pub memory: ProgramMemory, pub mod_local: ModuleState,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct GlobalState {
/// The stable artifact ID generator. /// The stable artifact ID generator.
pub id_generator: IdGenerator, pub id_generator: IdGenerator,
/// Map from source file absolute path to module ID.
pub path_to_source_id: IndexMap<std::path::PathBuf, ModuleId>,
/// Map from module ID to module info.
pub module_infos: IndexMap<ModuleId, ModuleInfo>,
}
#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct ModuleState {
/// Program variable bindings.
pub memory: ProgramMemory,
/// Dynamic state that follows dynamic flow of the program. /// Dynamic state that follows dynamic flow of the program.
pub dynamic_state: DynamicState, pub dynamic_state: DynamicState,
/// The current value of the pipe operator returned from the previous /// The current value of the pipe operator returned from the previous
@ -65,29 +85,104 @@ pub struct ExecState {
/// The stack of import statements for detecting circular module imports. /// The stack of import statements for detecting circular module imports.
/// If this is empty, we're not currently executing an import statement. /// If this is empty, we're not currently executing an import statement.
pub import_stack: Vec<std::path::PathBuf>, pub import_stack: Vec<std::path::PathBuf>,
/// Map from source file absolute path to module ID.
pub path_to_source_id: IndexMap<std::path::PathBuf, ModuleId>,
/// Map from module ID to module info.
pub module_infos: IndexMap<ModuleId, ModuleInfo>,
/// Operations that have been performed in execution order, for display in /// Operations that have been performed in execution order, for display in
/// the Feature Tree. /// the Feature Tree.
pub operations: Vec<Operation>, pub operations: Vec<Operation>,
} }
impl Default for ExecState {
fn default() -> Self {
Self::new()
}
}
impl ExecState { impl ExecState {
fn add_module(&mut self, path: std::path::PathBuf) -> ModuleId { pub fn new() -> Self {
ExecState {
global: GlobalState::new(),
mod_local: ModuleState::default(),
}
}
fn reset(&mut self) {
let mut id_generator = self.global.id_generator.clone();
// We do not pop the ids, since we want to keep the same id generator.
// This is for the front end to keep track of the ids.
id_generator.next_id = 0;
let mut global = GlobalState::new();
global.id_generator = id_generator;
*self = ExecState {
global,
mod_local: ModuleState::default(),
};
}
pub fn memory(&self) -> &ProgramMemory {
&self.mod_local.memory
}
pub fn mut_memory(&mut self) -> &mut ProgramMemory {
&mut self.mod_local.memory
}
pub fn next_uuid(&mut self) -> Uuid {
self.global.id_generator.next_uuid()
}
async fn add_module(
&mut self,
path: std::path::PathBuf,
ctxt: &ExecutorContext,
source_range: SourceRange,
) -> Result<ModuleId, KclError> {
// Need to avoid borrowing self in the closure. // Need to avoid borrowing self in the closure.
let new_module_id = ModuleId::from_usize(self.path_to_source_id.len()); let new_module_id = ModuleId::from_usize(self.global.path_to_source_id.len());
let mut is_new = false; let mut is_new = false;
let id = *self.path_to_source_id.entry(path.clone()).or_insert_with(|| { let id = *self.global.path_to_source_id.entry(path.clone()).or_insert_with(|| {
is_new = true; is_new = true;
new_module_id new_module_id
}); });
if is_new { if is_new {
let module_info = ModuleInfo { id, path }; let source = ctxt.fs.read_to_string(&path, source_range).await?;
self.module_infos.insert(id, module_info); // TODO handle parsing errors properly
let parsed = crate::parsing::parse_str(&source, id).parse_errs_as_err()?;
let module_info = ModuleInfo {
id,
path,
parsed: Some(parsed),
};
self.global.module_infos.insert(id, module_info);
} }
id
Ok(id)
}
}
impl GlobalState {
fn new() -> Self {
let mut global = GlobalState {
id_generator: Default::default(),
path_to_source_id: Default::default(),
module_infos: Default::default(),
};
// TODO(#4434): Use the top-level file's path.
let root_path = PathBuf::new();
let root_id = ModuleId::default();
global.module_infos.insert(
root_id,
ModuleInfo {
id: root_id,
path: root_path.clone(),
parsed: None,
},
);
global.path_to_source_id.insert(root_path, root_id);
global
} }
} }
@ -159,6 +254,13 @@ impl ProgramMemory {
})) }))
} }
/// Returns all bindings in the current scope.
#[allow(dead_code)]
fn get_all_cur_scope(&self) -> IndexMap<String, KclValue> {
let env = &self.environments[self.current_env.index()];
env.bindings.clone()
}
/// Find all solids in the memory that are on a specific sketch id. /// Find all solids in the memory that are on a specific sketch id.
/// This does not look inside closures. But as long as we do not allow /// This does not look inside closures. But as long as we do not allow
/// mutation of variables in KCL, closure memory should be a subset of this. /// mutation of variables in KCL, closure memory should be a subset of this.
@ -274,18 +376,14 @@ pub struct DynamicState {
} }
impl DynamicState { impl DynamicState {
pub fn new() -> Self {
Self::default()
}
#[must_use] #[must_use]
pub fn merge(&self, memory: &ProgramMemory) -> Self { fn merge(&self, memory: &ProgramMemory) -> Self {
let mut merged = self.clone(); let mut merged = self.clone();
merged.append(memory); merged.append(memory);
merged merged
} }
pub fn append(&mut self, memory: &ProgramMemory) { fn append(&mut self, memory: &ProgramMemory) {
for env in &memory.environments { for env in &memory.environments {
for item in env.bindings.values() { for item in env.bindings.values() {
if let KclValue::Solid(eg) = item { if let KclValue::Solid(eg) = item {
@ -295,7 +393,7 @@ impl DynamicState {
} }
} }
pub fn edge_cut_ids_on_sketch(&self, sketch_id: uuid::Uuid) -> Vec<uuid::Uuid> { pub(crate) fn edge_cut_ids_on_sketch(&self, sketch_id: uuid::Uuid) -> Vec<uuid::Uuid> {
self.solid_ids self.solid_ids
.iter() .iter()
.flat_map(|eg| { .flat_map(|eg| {
@ -553,7 +651,7 @@ pub struct Plane {
impl Plane { impl Plane {
pub(crate) fn from_plane_data(value: crate::std::sketch::PlaneData, exec_state: &mut ExecState) -> Self { pub(crate) fn from_plane_data(value: crate::std::sketch::PlaneData, exec_state: &mut ExecState) -> Self {
let id = exec_state.id_generator.next_uuid(); let id = exec_state.global.id_generator.next_uuid();
match value { match value {
crate::std::sketch::PlaneData::XY => Plane { crate::std::sketch::PlaneData::XY => Plane {
id, id,
@ -1001,13 +1099,14 @@ pub enum BodyType {
/// Info about a module. Right now, this is pretty minimal. We hope to cache /// Info about a module. Right now, this is pretty minimal. We hope to cache
/// modules here in the future. /// modules here in the future.
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize, ts_rs::TS, JsonSchema)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS, JsonSchema)]
#[ts(export)] #[ts(export)]
pub struct ModuleInfo { pub struct ModuleInfo {
/// The ID of the module. /// The ID of the module.
id: ModuleId, id: ModuleId,
/// Absolute path of the module's source file. /// Absolute path of the module's source file.
path: std::path::PathBuf, path: std::path::PathBuf,
parsed: Option<Node<AstProgram>>,
} }
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, JsonSchema)] #[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, JsonSchema)]
@ -1796,7 +1895,7 @@ impl ExecutorContext {
source_range: crate::execution::SourceRange, source_range: crate::execution::SourceRange,
) -> Result<(), KclError> { ) -> Result<(), KclError> {
self.engine self.engine
.clear_scene(&mut exec_state.id_generator, source_range) .clear_scene(&mut exec_state.global.id_generator, source_range)
.await?; .await?;
// We do not create the planes here as the post hook in wasm will do that // We do not create the planes here as the post hook in wasm will do that
@ -1897,23 +1996,13 @@ impl ExecutorContext {
if cache_result.clear_scene && !self.is_mock() { if cache_result.clear_scene && !self.is_mock() {
// Pop the execution state, since we are starting fresh. // Pop the execution state, since we are starting fresh.
let mut id_generator = exec_state.id_generator.clone(); exec_state.reset();
// We do not pop the ids, since we want to keep the same id generator.
// This is for the front end to keep track of the ids.
id_generator.next_id = 0;
*exec_state = ExecState {
id_generator,
..Default::default()
};
// We don't do this in mock mode since there is no engine connection // We don't do this in mock mode since there is no engine connection
// anyways and from the TS side we override memory and don't want to clear it. // anyways and from the TS side we override memory and don't want to clear it.
self.reset_scene(exec_state, Default::default()).await?; self.reset_scene(exec_state, Default::default()).await?;
} }
// TODO: Use the top-level file's path.
exec_state.add_module(std::path::PathBuf::from(""));
// Re-apply the settings, in case the cache was busted. // Re-apply the settings, in case the cache was busted.
self.engine.reapply_settings(&self.settings, Default::default()).await?; self.engine.reapply_settings(&self.settings, Default::default()).await?;
@ -1937,11 +2026,13 @@ impl ExecutorContext {
match statement { match statement {
BodyItem::ImportStatement(import_stmt) => { BodyItem::ImportStatement(import_stmt) => {
let source_range = SourceRange::from(import_stmt); let source_range = SourceRange::from(import_stmt);
let (module_memory, module_exports) = let module_id = self.open_module(&import_stmt.path, exec_state, source_range).await?;
self.open_module(&import_stmt.path, exec_state, source_range).await?;
match &import_stmt.selector { match &import_stmt.selector {
ImportSelector::List { items } => { ImportSelector::List { items } => {
let (_, module_memory, module_exports) = self
.exec_module(module_id, exec_state, ExecutionKind::Isolated, source_range)
.await?;
for import_item in items { for import_item in items {
// Extract the item from the module. // Extract the item from the module.
let item = let item =
@ -1965,18 +2056,24 @@ impl ExecutorContext {
} }
// Add the item to the current module. // Add the item to the current module.
exec_state.memory.add( exec_state.mut_memory().add(
import_item.identifier(), import_item.identifier(),
item.clone(), item.clone(),
SourceRange::from(&import_item.name), SourceRange::from(&import_item.name),
)?; )?;
if let ItemVisibility::Export = import_stmt.visibility { if let ItemVisibility::Export = import_stmt.visibility {
exec_state.module_exports.push(import_item.identifier().to_owned()); exec_state
.mod_local
.module_exports
.push(import_item.identifier().to_owned());
} }
} }
} }
ImportSelector::Glob(_) => { ImportSelector::Glob(_) => {
let (_, module_memory, module_exports) = self
.exec_module(module_id, exec_state, ExecutionKind::Isolated, source_range)
.await?;
for name in module_exports.iter() { for name in module_exports.iter() {
let item = module_memory.get(name, source_range).map_err(|_err| { let item = module_memory.get(name, source_range).map_err(|_err| {
KclError::Internal(KclErrorDetails { KclError::Internal(KclErrorDetails {
@ -1984,18 +2081,20 @@ impl ExecutorContext {
source_ranges: vec![source_range], source_ranges: vec![source_range],
}) })
})?; })?;
exec_state.memory.add(name, item.clone(), source_range)?; exec_state.mut_memory().add(name, item.clone(), source_range)?;
if let ItemVisibility::Export = import_stmt.visibility { if let ItemVisibility::Export = import_stmt.visibility {
exec_state.module_exports.push(name.clone()); exec_state.mod_local.module_exports.push(name.clone());
} }
} }
} }
ImportSelector::None(_) => { ImportSelector::None { .. } => {
return Err(KclError::Semantic(KclErrorDetails { let name = import_stmt.module_name().unwrap();
message: "Importing whole module is not yet implemented, sorry.".to_owned(), let item = KclValue::Module {
source_ranges: vec![source_range], value: module_id,
})); meta: vec![source_range.into()],
};
exec_state.mut_memory().add(&name, item, source_range)?;
} }
} }
last_expr = None; last_expr = None;
@ -2025,11 +2124,11 @@ impl ExecutorContext {
StatementKind::Declaration { name: &var_name }, StatementKind::Declaration { name: &var_name },
) )
.await?; .await?;
exec_state.memory.add(&var_name, memory_item, source_range)?; exec_state.mut_memory().add(&var_name, memory_item, source_range)?;
// Track exports. // Track exports.
if let ItemVisibility::Export = variable_declaration.visibility { if let ItemVisibility::Export = variable_declaration.visibility {
exec_state.module_exports.push(var_name); exec_state.mod_local.module_exports.push(var_name);
} }
last_expr = None; last_expr = None;
} }
@ -2043,7 +2142,7 @@ impl ExecutorContext {
StatementKind::Expression, StatementKind::Expression,
) )
.await?; .await?;
exec_state.memory.return_ = Some(value); exec_state.mut_memory().return_ = Some(value);
last_expr = None; last_expr = None;
} }
} }
@ -2069,18 +2168,19 @@ impl ExecutorContext {
path: &str, path: &str,
exec_state: &mut ExecState, exec_state: &mut ExecState,
source_range: SourceRange, source_range: SourceRange,
) -> Result<(ProgramMemory, Vec<String>), KclError> { ) -> Result<ModuleId, KclError> {
let resolved_path = if let Some(project_dir) = &self.settings.project_directory { let resolved_path = if let Some(project_dir) = &self.settings.project_directory {
project_dir.join(path) project_dir.join(path)
} else { } else {
std::path::PathBuf::from(&path) std::path::PathBuf::from(&path)
}; };
if exec_state.import_stack.contains(&resolved_path) { if exec_state.mod_local.import_stack.contains(&resolved_path) {
return Err(KclError::ImportCycle(KclErrorDetails { return Err(KclError::ImportCycle(KclErrorDetails {
message: format!( message: format!(
"circular import of modules is not allowed: {} -> {}", "circular import of modules is not allowed: {} -> {}",
exec_state exec_state
.mod_local
.import_stack .import_stack
.iter() .iter()
.map(|p| p.as_path().to_string_lossy()) .map(|p| p.as_path().to_string_lossy())
@ -2091,31 +2191,44 @@ impl ExecutorContext {
source_ranges: vec![source_range], source_ranges: vec![source_range],
})); }));
} }
let module_id = exec_state.add_module(resolved_path.clone()); exec_state.add_module(resolved_path.clone(), self, source_range).await
let source = self.fs.read_to_string(&resolved_path, source_range).await?; }
// TODO handle parsing errors properly
let program = crate::parsing::parse_str(&source, module_id).parse_errs_as_err()?;
exec_state.import_stack.push(resolved_path.clone()); async fn exec_module(
let original_execution = self.engine.replace_execution_kind(ExecutionKind::Isolated); &self,
let original_memory = std::mem::take(&mut exec_state.memory); module_id: ModuleId,
let original_exports = std::mem::take(&mut exec_state.module_exports); exec_state: &mut ExecState,
exec_kind: ExecutionKind,
source_range: SourceRange,
) -> Result<(Option<KclValue>, ProgramMemory, Vec<String>), KclError> {
// TODO It sucks that we have to clone the whole module AST here
let info = exec_state.global.module_infos[&module_id].clone();
let mut local_state = ModuleState {
import_stack: exec_state.mod_local.import_stack.clone(),
..Default::default()
};
local_state.import_stack.push(info.path.clone());
std::mem::swap(&mut exec_state.mod_local, &mut local_state);
let original_execution = self.engine.replace_execution_kind(exec_kind);
// The unwrap here is safe since we only elide the AST for the top module.
let result = self let result = self
.inner_execute(&program, exec_state, crate::execution::BodyType::Root) .inner_execute(&info.parsed.unwrap(), exec_state, crate::execution::BodyType::Root)
.await; .await;
let module_exports = std::mem::replace(&mut exec_state.module_exports, original_exports);
let module_memory = std::mem::replace(&mut exec_state.memory, original_memory);
self.engine.replace_execution_kind(original_execution);
exec_state.import_stack.pop();
result.map_err(|err| { std::mem::swap(&mut exec_state.mod_local, &mut local_state);
self.engine.replace_execution_kind(original_execution);
let result = result.map_err(|err| {
if let KclError::ImportCycle(_) = err { if let KclError::ImportCycle(_) = err {
// It was an import cycle. Keep the original message. // It was an import cycle. Keep the original message.
err.override_source_ranges(vec![source_range]) err.override_source_ranges(vec![source_range])
} else { } else {
KclError::Semantic(KclErrorDetails { KclError::Semantic(KclErrorDetails {
message: format!( message: format!(
"Error loading imported file. Open it to view more details. {path}: {}", "Error loading imported file. Open it to view more details. {}: {}",
info.path.display(),
err.message() err.message()
), ),
source_ranges: vec![source_range], source_ranges: vec![source_range],
@ -2123,7 +2236,7 @@ impl ExecutorContext {
} }
})?; })?;
Ok((module_memory, module_exports)) Ok((result, local_state.memory, local_state.module_exports))
} }
#[async_recursion] #[async_recursion]
@ -2139,8 +2252,23 @@ impl ExecutorContext {
Expr::Literal(literal) => KclValue::from(literal), Expr::Literal(literal) => KclValue::from(literal),
Expr::TagDeclarator(tag) => tag.execute(exec_state).await?, Expr::TagDeclarator(tag) => tag.execute(exec_state).await?,
Expr::Identifier(identifier) => { Expr::Identifier(identifier) => {
let value = exec_state.memory.get(&identifier.name, identifier.into())?; let value = exec_state.memory().get(&identifier.name, identifier.into())?.clone();
value.clone() if let KclValue::Module { value: module_id, meta } = value {
let (result, _, _) = self
.exec_module(module_id, exec_state, ExecutionKind::Normal, metadata.source_range)
.await?;
result.ok_or_else(|| {
KclError::Semantic(KclErrorDetails {
message: format!(
"Evaluating module `{}` as part of an assembly did not produce a result",
identifier.name
),
source_ranges: vec![metadata.source_range, meta[0].source_range],
})
})?
} else {
value
}
} }
Expr::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, self).await?, Expr::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, self).await?,
Expr::FunctionExpression(function_expression) => { Expr::FunctionExpression(function_expression) => {
@ -2151,7 +2279,7 @@ impl ExecutorContext {
expression: function_expression.clone(), expression: function_expression.clone(),
meta: vec![metadata.to_owned()], meta: vec![metadata.to_owned()],
func: None, func: None,
memory: Box::new(exec_state.memory.clone()), memory: Box::new(exec_state.memory().clone()),
} }
} }
Expr::CallExpression(call_expression) => call_expression.execute(exec_state, self).await?, Expr::CallExpression(call_expression) => call_expression.execute(exec_state, self).await?,
@ -2168,7 +2296,7 @@ impl ExecutorContext {
source_ranges: vec![pipe_substitution.into()], source_ranges: vec![pipe_substitution.into()],
})); }));
} }
StatementKind::Expression => match exec_state.pipe_value.clone() { StatementKind::Expression => match exec_state.mod_local.pipe_value.clone() {
Some(x) => x, Some(x) => x,
None => { None => {
return Err(KclError::Semantic(KclErrorDetails { return Err(KclError::Semantic(KclErrorDetails {
@ -2188,7 +2316,9 @@ impl ExecutorContext {
let result = self let result = self
.execute_expr(&expr.expr, exec_state, metadata, statement_kind) .execute_expr(&expr.expr, exec_state, metadata, statement_kind)
.await?; .await?;
exec_state.memory.add(&expr.label.name, result.clone(), init.into())?; exec_state
.mut_memory()
.add(&expr.label.name, result.clone(), init.into())?;
// TODO this lets us use the label as a variable name, but not as a tag in most cases // TODO this lets us use the label as a variable name, but not as a tag in most cases
result result
} }
@ -2373,12 +2503,12 @@ pub(crate) async fn call_user_defined_function(
// Execute the function body using the memory we just created. // Execute the function body using the memory we just created.
let (result, fn_memory) = { let (result, fn_memory) = {
let previous_memory = std::mem::replace(&mut exec_state.memory, fn_memory); let previous_memory = std::mem::replace(&mut exec_state.mod_local.memory, fn_memory);
let result = ctx let result = ctx
.inner_execute(&function_expression.body, exec_state, BodyType::Block) .inner_execute(&function_expression.body, exec_state, BodyType::Block)
.await; .await;
// Restore the previous memory. // Restore the previous memory.
let fn_memory = std::mem::replace(&mut exec_state.memory, previous_memory); let fn_memory = std::mem::replace(&mut exec_state.mod_local.memory, previous_memory);
(result, fn_memory) (result, fn_memory)
}; };
@ -2403,12 +2533,12 @@ pub(crate) async fn call_user_defined_function_kw(
// Execute the function body using the memory we just created. // Execute the function body using the memory we just created.
let (result, fn_memory) = { let (result, fn_memory) = {
let previous_memory = std::mem::replace(&mut exec_state.memory, fn_memory); let previous_memory = std::mem::replace(&mut exec_state.mod_local.memory, fn_memory);
let result = ctx let result = ctx
.inner_execute(&function_expression.body, exec_state, BodyType::Block) .inner_execute(&function_expression.body, exec_state, BodyType::Block)
.await; .await;
// Restore the previous memory. // Restore the previous memory.
let fn_memory = std::mem::replace(&mut exec_state.memory, previous_memory); let fn_memory = std::mem::replace(&mut exec_state.mod_local.memory, previous_memory);
(result, fn_memory) (result, fn_memory)
}; };
@ -2433,7 +2563,7 @@ mod tests {
OldAstState, OldAstState,
}; };
pub async fn parse_execute(code: &str) -> Result<(Program, ExecutorContext, ExecState)> { async fn parse_execute(code: &str) -> Result<(Program, ExecutorContext, ExecState)> {
let program = Program::parse_no_errs(code)?; let program = Program::parse_no_errs(code)?;
let ctx = ExecutorContext { let ctx = ExecutorContext {
@ -2450,6 +2580,7 @@ mod tests {
} }
/// Convenience function to get a JSON value from memory and unwrap. /// Convenience function to get a JSON value from memory and unwrap.
#[track_caller]
fn mem_get_json(memory: &ProgramMemory, name: &str) -> KclValue { fn mem_get_json(memory: &ProgramMemory, name: &str) -> KclValue {
memory.get(name, SourceRange::default()).unwrap().to_owned() memory.get(name, SourceRange::default()).unwrap().to_owned()
} }
@ -2880,21 +3011,21 @@ let shape = layer() |> patternTransform(10, transform, %)
async fn test_math_execute_with_functions() { async fn test_math_execute_with_functions() {
let ast = r#"const myVar = 2 + min(100, -1 + legLen(5, 3))"#; let ast = r#"const myVar = 2 + min(100, -1 + legLen(5, 3))"#;
let (_, _, exec_state) = parse_execute(ast).await.unwrap(); let (_, _, exec_state) = parse_execute(ast).await.unwrap();
assert_eq!(5.0, mem_get_json(&exec_state.memory, "myVar").as_f64().unwrap()); assert_eq!(5.0, mem_get_json(exec_state.memory(), "myVar").as_f64().unwrap());
} }
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_math_execute() { async fn test_math_execute() {
let ast = r#"const myVar = 1 + 2 * (3 - 4) / -5 + 6"#; let ast = r#"const myVar = 1 + 2 * (3 - 4) / -5 + 6"#;
let (_, _, exec_state) = parse_execute(ast).await.unwrap(); let (_, _, exec_state) = parse_execute(ast).await.unwrap();
assert_eq!(7.4, mem_get_json(&exec_state.memory, "myVar").as_f64().unwrap()); assert_eq!(7.4, mem_get_json(exec_state.memory(), "myVar").as_f64().unwrap());
} }
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_math_execute_start_negative() { async fn test_math_execute_start_negative() {
let ast = r#"const myVar = -5 + 6"#; let ast = r#"const myVar = -5 + 6"#;
let (_, _, exec_state) = parse_execute(ast).await.unwrap(); let (_, _, exec_state) = parse_execute(ast).await.unwrap();
assert_eq!(1.0, mem_get_json(&exec_state.memory, "myVar").as_f64().unwrap()); assert_eq!(1.0, mem_get_json(exec_state.memory(), "myVar").as_f64().unwrap());
} }
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
@ -2903,7 +3034,7 @@ let shape = layer() |> patternTransform(10, transform, %)
let (_, _, exec_state) = parse_execute(ast).await.unwrap(); let (_, _, exec_state) = parse_execute(ast).await.unwrap();
assert_eq!( assert_eq!(
std::f64::consts::TAU, std::f64::consts::TAU,
mem_get_json(&exec_state.memory, "myVar").as_f64().unwrap() mem_get_json(exec_state.memory(), "myVar").as_f64().unwrap()
); );
} }
@ -2911,7 +3042,7 @@ let shape = layer() |> patternTransform(10, transform, %)
async fn test_math_define_decimal_without_leading_zero() { async fn test_math_define_decimal_without_leading_zero() {
let ast = r#"let thing = .4 + 7"#; let ast = r#"let thing = .4 + 7"#;
let (_, _, exec_state) = parse_execute(ast).await.unwrap(); let (_, _, exec_state) = parse_execute(ast).await.unwrap();
assert_eq!(7.4, mem_get_json(&exec_state.memory, "thing").as_f64().unwrap()); assert_eq!(7.4, mem_get_json(exec_state.memory(), "thing").as_f64().unwrap());
} }
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
@ -2951,10 +3082,10 @@ fn check = (x) => {
check(false) check(false)
"#; "#;
let (_, _, exec_state) = parse_execute(ast).await.unwrap(); let (_, _, exec_state) = parse_execute(ast).await.unwrap();
assert_eq!(false, mem_get_json(&exec_state.memory, "notTrue").as_bool().unwrap()); assert_eq!(false, mem_get_json(exec_state.memory(), "notTrue").as_bool().unwrap());
assert_eq!(true, mem_get_json(&exec_state.memory, "notFalse").as_bool().unwrap()); assert_eq!(true, mem_get_json(exec_state.memory(), "notFalse").as_bool().unwrap());
assert_eq!(true, mem_get_json(&exec_state.memory, "c").as_bool().unwrap()); assert_eq!(true, mem_get_json(exec_state.memory(), "c").as_bool().unwrap());
assert_eq!(false, mem_get_json(&exec_state.memory, "d").as_bool().unwrap()); assert_eq!(false, mem_get_json(exec_state.memory(), "d").as_bool().unwrap());
} }
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
@ -3635,4 +3766,65 @@ shell({ faces = ['end'], thickness = 0.25 }, firstSketch)"#;
assert_eq!(result, None); assert_eq!(result, None);
} }
#[tokio::test(flavor = "multi_thread")]
async fn kcl_test_ids_stable_between_executions() {
let code = r#"sketch001 = startSketchOn('XZ')
|> startProfileAt([61.74, 206.13], %)
|> xLine(305.11, %, $seg01)
|> yLine(-291.85, %)
|> xLine(-segLen(seg01), %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
|> extrude(40.14, %)
|> shell({
faces: [seg01],
thickness: 3.14,
}, %)
"#;
let ctx = crate::test_server::new_context(UnitLength::Mm, true, None)
.await
.unwrap();
let old_program = crate::Program::parse_no_errs(code).unwrap();
// Execute the program.
let mut exec_state = Default::default();
let cache_info = crate::CacheInformation {
old: None,
new_ast: old_program.ast.clone(),
};
ctx.run(cache_info, &mut exec_state).await.unwrap();
// Get the id_generator from the first execution.
let id_generator = exec_state.global.id_generator.clone();
let code = r#"sketch001 = startSketchOn('XZ')
|> startProfileAt([62.74, 206.13], %)
|> xLine(305.11, %, $seg01)
|> yLine(-291.85, %)
|> xLine(-segLen(seg01), %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
|> extrude(40.14, %)
|> shell({
faces: [seg01],
thickness: 3.14,
}, %)
"#;
// Execute a slightly different program again.
let program: Program = crate::Program::parse_no_errs(code).unwrap();
let cache_info = crate::CacheInformation {
old: Some(crate::OldAstState {
ast: old_program.ast.clone(),
exec_state: exec_state.clone(),
settings: ctx.settings.clone(),
}),
new_ast: program.ast.clone(),
};
// Execute the program.
ctx.run(cache_info, &mut exec_state).await.unwrap();
assert_eq!(id_generator, exec_state.global.id_generator);
}
} }

View File

@ -726,11 +726,11 @@ impl Backend {
drop(last_successful_ast_state); drop(last_successful_ast_state);
self.memory_map self.memory_map
.insert(params.uri.to_string(), exec_state.memory.clone()); .insert(params.uri.to_string(), exec_state.memory().clone());
// Send the notification to the client that the memory was updated. // Send the notification to the client that the memory was updated.
self.client self.client
.send_notification::<custom_notifications::MemoryUpdated>(exec_state.memory) .send_notification::<custom_notifications::MemoryUpdated>(exec_state.mod_local.memory)
.await; .await;
Ok(()) Ok(())

View File

@ -2344,6 +2344,7 @@ async fn kcl_test_kcl_lsp_diagnostics_on_execution_error() {
.await; .await;
// Get the diagnostics. // Get the diagnostics.
// TODO warnings being stomped by execution errors?
assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 1); assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 1);
// Update the text. // Update the text.

View File

@ -59,8 +59,8 @@ impl ImportStatement {
} }
} }
ImportSelector::Glob(_) => hasher.update(b"ImportSelector::Glob"), ImportSelector::Glob(_) => hasher.update(b"ImportSelector::Glob"),
ImportSelector::None(None) => hasher.update(b"ImportSelector::None"), ImportSelector::None { alias: None } => hasher.update(b"ImportSelector::None"),
ImportSelector::None(Some(alias)) => { ImportSelector::None { alias: Some(alias) } => {
hasher.update(b"ImportSelector::None"); hasher.update(b"ImportSelector::None");
hasher.update(alias.compute_digest()); hasher.update(alias.compute_digest());
} }

View File

@ -1225,7 +1225,7 @@ pub enum ImportSelector {
Glob(Node<()>), Glob(Node<()>),
/// Import the module itself (the param is an optional alias). /// Import the module itself (the param is an optional alias).
/// E.g., `import "foo.kcl" as bar` /// E.g., `import "foo.kcl" as bar`
None(Option<Node<Identifier>>), None { alias: Option<Node<Identifier>> },
} }
impl ImportSelector { impl ImportSelector {
@ -1244,8 +1244,8 @@ impl ImportSelector {
None None
} }
ImportSelector::Glob(_) => None, ImportSelector::Glob(_) => None,
ImportSelector::None(None) => None, ImportSelector::None { alias: None } => None,
ImportSelector::None(Some(alias)) => { ImportSelector::None { alias: Some(alias) } => {
let alias_source_range = SourceRange::from(&*alias); let alias_source_range = SourceRange::from(&*alias);
if !alias_source_range.contains(pos) { if !alias_source_range.contains(pos) {
return None; return None;
@ -1264,8 +1264,8 @@ impl ImportSelector {
} }
} }
ImportSelector::Glob(_) => {} ImportSelector::Glob(_) => {}
ImportSelector::None(None) => {} ImportSelector::None { alias: None } => {}
ImportSelector::None(Some(alias)) => alias.rename(old_name, new_name), ImportSelector::None { alias: Some(alias) } => alias.rename(old_name, new_name),
} }
} }
} }
@ -1296,7 +1296,7 @@ impl Node<ImportStatement> {
false false
} }
ImportSelector::Glob(_) => false, ImportSelector::Glob(_) => false,
ImportSelector::None(_) => name == self.module_name().unwrap(), ImportSelector::None { .. } => name == self.module_name().unwrap(),
} }
} }
@ -1304,7 +1304,7 @@ impl Node<ImportStatement> {
/// Validated during parsing and guaranteed to return `Some` if the statement imports /// Validated during parsing and guaranteed to return `Some` if the statement imports
/// the module itself (i.e., self.selector is ImportSelector::None). /// the module itself (i.e., self.selector is ImportSelector::None).
pub fn module_name(&self) -> Option<String> { pub fn module_name(&self) -> Option<String> {
if let ImportSelector::None(Some(alias)) = &self.selector { if let ImportSelector::None { alias: Some(alias) } = &self.selector {
return Some(alias.name.clone()); return Some(alias.name.clone());
} }

View File

@ -1447,7 +1447,7 @@ fn import_stmt(i: &mut TokenSlice) -> PResult<BoxNode<ImportStatement>> {
require_whitespace(i)?; require_whitespace(i)?;
let (mut selector, path) = alt(( let (mut selector, path) = alt((
string_literal.map(|s| (ImportSelector::None(None), Some(s))), string_literal.map(|s| (ImportSelector::None { alias: None }, Some(s))),
glob.map(|t| { glob.map(|t| {
let s = t.as_source_range(); let s = t.as_source_range();
( (
@ -1510,7 +1510,7 @@ fn import_stmt(i: &mut TokenSlice) -> PResult<BoxNode<ImportStatement>> {
)); ));
} }
if let ImportSelector::None(ref mut a) = selector { if let ImportSelector::None { alias: ref mut a } = selector {
if let Some(alias) = opt(preceded( if let Some(alias) = opt(preceded(
(whitespace, import_as_keyword, whitespace), (whitespace, import_as_keyword, whitespace),
identifier.context(expected("an identifier to alias the import")), identifier.context(expected("an identifier to alias the import")),

View File

@ -365,6 +365,7 @@ mod tests {
use super::*; use super::*;
use crate::parsing::token::TokenSlice; use crate::parsing::token::TokenSlice;
fn assert_parse_err<'i, P, O, E>(mut p: P, s: &'i str) fn assert_parse_err<'i, P, O, E>(mut p: P, s: &'i str)
where where
O: std::fmt::Debug, O: std::fmt::Debug,

View File

@ -127,7 +127,7 @@ async fn execute(test_name: &str, render_to_png: bool) {
}); });
assert_snapshot(test_name, "Operations executed", || { assert_snapshot(test_name, "Operations executed", || {
insta::assert_json_snapshot!("ops", e.exec_state.operations); insta::assert_json_snapshot!("ops", e.exec_state.mod_local.operations);
}); });
} }
e => { e => {
@ -711,6 +711,27 @@ mod import_glob {
super::execute(TEST_NAME, false).await super::execute(TEST_NAME, false).await
} }
} }
mod import_whole {
const TEST_NAME: &str = "import_whole";
/// Test parsing KCL.
#[test]
fn parse() {
super::parse(TEST_NAME)
}
/// Test that parsing and unparsing KCL produces the original KCL input.
#[test]
fn unparse() {
super::unparse(TEST_NAME)
}
/// Test that KCL is executed correctly.
#[tokio::test(flavor = "multi_thread")]
async fn kcl_test_execute() {
super::execute(TEST_NAME, false).await
}
}
mod import_side_effect { mod import_side_effect {
const TEST_NAME: &str = "import_side_effect"; const TEST_NAME: &str = "import_side_effect";

View File

@ -185,7 +185,7 @@ impl Args {
exec_state: &'e mut ExecState, exec_state: &'e mut ExecState,
tag: &'a TagIdentifier, tag: &'a TagIdentifier,
) -> Result<&'e crate::execution::TagEngineInfo, KclError> { ) -> Result<&'e crate::execution::TagEngineInfo, KclError> {
if let KclValue::TagIdentifier(t) = exec_state.memory.get(&tag.value, self.source_range)? { if let KclValue::TagIdentifier(t) = exec_state.memory().get(&tag.value, self.source_range)? {
Ok(t.info.as_ref().ok_or_else(|| { Ok(t.info.as_ref().ok_or_else(|| {
KclError::Type(KclErrorDetails { KclError::Type(KclErrorDetails {
message: format!("Tag `{}` does not have engine info", tag.value), message: format!("Tag `{}` does not have engine info", tag.value),
@ -251,12 +251,12 @@ impl Args {
// Find all the solids on the same shared sketch. // Find all the solids on the same shared sketch.
ids.extend( ids.extend(
exec_state exec_state
.memory .memory()
.find_solids_on_sketch(solid.sketch.id) .find_solids_on_sketch(solid.sketch.id)
.iter() .iter()
.flat_map(|eg| eg.get_all_edge_cut_ids()), .flat_map(|eg| eg.get_all_edge_cut_ids()),
); );
ids.extend(exec_state.dynamic_state.edge_cut_ids_on_sketch(sketch_id)); ids.extend(exec_state.mod_local.dynamic_state.edge_cut_ids_on_sketch(sketch_id));
traversed_sketches.push(sketch_id); traversed_sketches.push(sketch_id);
} }

View File

@ -134,7 +134,7 @@ async fn inner_chamfer(
EdgeReference::Tag(edge_tag) => args.get_tag_engine_info(exec_state, &edge_tag)?.id, EdgeReference::Tag(edge_tag) => args.get_tag_engine_info(exec_state, &edge_tag)?.id,
}; };
let id = exec_state.id_generator.next_uuid(); let id = exec_state.global.id_generator.next_uuid();
args.batch_end_cmd( args.batch_end_cmd(
id, id,
ModelingCmd::from(mcmd::Solid3dFilletEdge { ModelingCmd::from(mcmd::Solid3dFilletEdge {

View File

@ -84,7 +84,7 @@ async fn inner_extrude(
exec_state: &mut ExecState, exec_state: &mut ExecState,
args: Args, args: Args,
) -> Result<SolidSet, KclError> { ) -> Result<SolidSet, KclError> {
let id = exec_state.id_generator.next_uuid(); let id = exec_state.next_uuid();
// Extrude the element(s). // Extrude the element(s).
let sketches: Vec<Sketch> = sketch_set.into(); let sketches: Vec<Sketch> = sketch_set.into();
@ -93,7 +93,7 @@ async fn inner_extrude(
// Before we extrude, we need to enable the sketch mode. // Before we extrude, we need to enable the sketch mode.
// We do this here in case extrude is called out of order. // We do this here in case extrude is called out of order.
args.batch_modeling_cmd( args.batch_modeling_cmd(
exec_state.id_generator.next_uuid(), exec_state.next_uuid(),
ModelingCmd::from(mcmd::EnableSketchMode { ModelingCmd::from(mcmd::EnableSketchMode {
animated: false, animated: false,
ortho: false, ortho: false,
@ -121,7 +121,7 @@ async fn inner_extrude(
// Disable the sketch mode. // Disable the sketch mode.
args.batch_modeling_cmd( args.batch_modeling_cmd(
exec_state.id_generator.next_uuid(), exec_state.next_uuid(),
ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable {}), ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable {}),
) )
.await?; .await?;
@ -140,7 +140,7 @@ pub(crate) async fn do_post_extrude(
// Bring the object to the front of the scene. // Bring the object to the front of the scene.
// See: https://github.com/KittyCAD/modeling-app/issues/806 // See: https://github.com/KittyCAD/modeling-app/issues/806
args.batch_modeling_cmd( args.batch_modeling_cmd(
exec_state.id_generator.next_uuid(), exec_state.next_uuid(),
ModelingCmd::from(mcmd::ObjectBringToFront { object_id: sketch.id }), ModelingCmd::from(mcmd::ObjectBringToFront { object_id: sketch.id }),
) )
.await?; .await?;
@ -163,7 +163,7 @@ pub(crate) async fn do_post_extrude(
let solid3d_info = args let solid3d_info = args
.send_modeling_cmd( .send_modeling_cmd(
exec_state.id_generator.next_uuid(), exec_state.next_uuid(),
ModelingCmd::from(mcmd::Solid3dGetExtrusionFaceInfo { ModelingCmd::from(mcmd::Solid3dGetExtrusionFaceInfo {
edge_id: any_edge_id, edge_id: any_edge_id,
object_id: sketch.id, object_id: sketch.id,
@ -196,7 +196,7 @@ pub(crate) async fn do_post_extrude(
// Instead, the Typescript codebases (which handles WebSocket sends when compiled via Wasm) // Instead, the Typescript codebases (which handles WebSocket sends when compiled via Wasm)
// uses this to build the artifact graph, which the UI needs. // uses this to build the artifact graph, which the UI needs.
args.batch_modeling_cmd( args.batch_modeling_cmd(
exec_state.id_generator.next_uuid(), exec_state.next_uuid(),
ModelingCmd::from(mcmd::Solid3dGetOppositeEdge { ModelingCmd::from(mcmd::Solid3dGetOppositeEdge {
edge_id: curve_id, edge_id: curve_id,
object_id: sketch.id, object_id: sketch.id,
@ -206,7 +206,7 @@ pub(crate) async fn do_post_extrude(
.await?; .await?;
args.batch_modeling_cmd( args.batch_modeling_cmd(
exec_state.id_generator.next_uuid(), exec_state.next_uuid(),
ModelingCmd::from(mcmd::Solid3dGetNextAdjacentEdge { ModelingCmd::from(mcmd::Solid3dGetNextAdjacentEdge {
edge_id: curve_id, edge_id: curve_id,
object_id: sketch.id, object_id: sketch.id,
@ -259,7 +259,7 @@ pub(crate) async fn do_post_extrude(
let extrude_surface = ExtrudeSurface::ExtrudePlane(crate::execution::ExtrudePlane { let extrude_surface = ExtrudeSurface::ExtrudePlane(crate::execution::ExtrudePlane {
// pushing this values with a fake face_id to make extrudes mock-execute safe // pushing this values with a fake face_id to make extrudes mock-execute safe
face_id: exec_state.id_generator.next_uuid(), face_id: exec_state.next_uuid(),
tag: path.get_base().tag.clone(), tag: path.get_base().tag.clone(),
geo_meta: GeoMeta { geo_meta: GeoMeta {
id: path.get_base().geo_meta.id, id: path.get_base().geo_meta.id,
@ -305,8 +305,8 @@ fn analyze_faces(exec_state: &mut ExecState, args: &Args, face_infos: Vec<Extrus
}; };
if args.ctx.is_mock() { if args.ctx.is_mock() {
// Create fake IDs for start and end caps, to make extrudes mock-execute safe // Create fake IDs for start and end caps, to make extrudes mock-execute safe
faces.start_cap_id = Some(exec_state.id_generator.next_uuid()); faces.start_cap_id = Some(exec_state.next_uuid());
faces.end_cap_id = Some(exec_state.id_generator.next_uuid()); faces.end_cap_id = Some(exec_state.next_uuid());
} }
for face_info in face_infos { for face_info in face_infos {
match face_info.cap { match face_info.cap {

View File

@ -143,7 +143,7 @@ async fn inner_fillet(
for edge_tag in data.tags { for edge_tag in data.tags {
let edge_id = edge_tag.get_engine_id(exec_state, &args)?; let edge_id = edge_tag.get_engine_id(exec_state, &args)?;
let id = exec_state.id_generator.next_uuid(); let id = exec_state.next_uuid();
args.batch_end_cmd( args.batch_end_cmd(
id, id,
ModelingCmd::from(mcmd::Solid3dFilletEdge { ModelingCmd::from(mcmd::Solid3dFilletEdge {
@ -225,11 +225,11 @@ pub async fn get_opposite_edge(exec_state: &mut ExecState, args: Args) -> Result
}] }]
async fn inner_get_opposite_edge(tag: TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<Uuid, KclError> { async fn inner_get_opposite_edge(tag: TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<Uuid, KclError> {
if args.ctx.is_mock() { if args.ctx.is_mock() {
return Ok(exec_state.id_generator.next_uuid()); return Ok(exec_state.next_uuid());
} }
let face_id = args.get_adjacent_face_to_tag(exec_state, &tag, false).await?; let face_id = args.get_adjacent_face_to_tag(exec_state, &tag, false).await?;
let id = exec_state.id_generator.next_uuid(); let id = exec_state.next_uuid();
let tagged_path = args.get_tag_engine_info(exec_state, &tag)?; let tagged_path = args.get_tag_engine_info(exec_state, &tag)?;
let resp = args let resp = args
@ -302,11 +302,11 @@ async fn inner_get_next_adjacent_edge(
args: Args, args: Args,
) -> Result<Uuid, KclError> { ) -> Result<Uuid, KclError> {
if args.ctx.is_mock() { if args.ctx.is_mock() {
return Ok(exec_state.id_generator.next_uuid()); return Ok(exec_state.next_uuid());
} }
let face_id = args.get_adjacent_face_to_tag(exec_state, &tag, false).await?; let face_id = args.get_adjacent_face_to_tag(exec_state, &tag, false).await?;
let id = exec_state.id_generator.next_uuid(); let id = exec_state.next_uuid();
let tagged_path = args.get_tag_engine_info(exec_state, &tag)?; let tagged_path = args.get_tag_engine_info(exec_state, &tag)?;
let resp = args let resp = args
@ -387,11 +387,11 @@ async fn inner_get_previous_adjacent_edge(
args: Args, args: Args,
) -> Result<Uuid, KclError> { ) -> Result<Uuid, KclError> {
if args.ctx.is_mock() { if args.ctx.is_mock() {
return Ok(exec_state.id_generator.next_uuid()); return Ok(exec_state.next_uuid());
} }
let face_id = args.get_adjacent_face_to_tag(exec_state, &tag, false).await?; let face_id = args.get_adjacent_face_to_tag(exec_state, &tag, false).await?;
let id = exec_state.id_generator.next_uuid(); let id = exec_state.next_uuid();
let tagged_path = args.get_tag_engine_info(exec_state, &tag)?; let tagged_path = args.get_tag_engine_info(exec_state, &tag)?;
let resp = args let resp = args

View File

@ -61,7 +61,7 @@ async fn inner_helix(
exec_state: &mut ExecState, exec_state: &mut ExecState,
args: Args, args: Args,
) -> Result<Box<Solid>, KclError> { ) -> Result<Box<Solid>, KclError> {
let id = exec_state.id_generator.next_uuid(); let id = exec_state.next_uuid();
args.batch_modeling_cmd( args.batch_modeling_cmd(
id, id,
ModelingCmd::from(mcmd::EntityMakeHelix { ModelingCmd::from(mcmd::EntityMakeHelix {

View File

@ -300,13 +300,13 @@ async fn inner_import(
if args.ctx.is_mock() { if args.ctx.is_mock() {
return Ok(ImportedGeometry { return Ok(ImportedGeometry {
id: exec_state.id_generator.next_uuid(), id: exec_state.next_uuid(),
value: import_files.iter().map(|f| f.path.to_string()).collect(), value: import_files.iter().map(|f| f.path.to_string()).collect(),
meta: vec![args.source_range.into()], meta: vec![args.source_range.into()],
}); });
} }
let id = exec_state.id_generator.next_uuid(); let id = exec_state.next_uuid();
let resp = args let resp = args
.send_modeling_cmd( .send_modeling_cmd(
id, id,

View File

@ -142,7 +142,7 @@ async fn inner_loft(
})); }));
} }
let id = exec_state.id_generator.next_uuid(); let id = exec_state.next_uuid();
args.batch_modeling_cmd( args.batch_modeling_cmd(
id, id,
ModelingCmd::from(mcmd::Loft { ModelingCmd::from(mcmd::Loft {

View File

@ -121,7 +121,7 @@ async fn inner_mirror_2d(
let (axis, origin) = axis.axis_and_origin()?; let (axis, origin) = axis.axis_and_origin()?;
args.batch_modeling_cmd( args.batch_modeling_cmd(
exec_state.id_generator.next_uuid(), exec_state.next_uuid(),
ModelingCmd::from(mcmd::EntityMirror { ModelingCmd::from(mcmd::EntityMirror {
ids: starting_sketches.iter().map(|sketch| sketch.id).collect(), ids: starting_sketches.iter().map(|sketch| sketch.id).collect(),
axis, axis,
@ -134,7 +134,7 @@ async fn inner_mirror_2d(
let edge_id = edge.get_engine_id(exec_state, &args)?; let edge_id = edge.get_engine_id(exec_state, &args)?;
args.batch_modeling_cmd( args.batch_modeling_cmd(
exec_state.id_generator.next_uuid(), exec_state.next_uuid(),
ModelingCmd::from(mcmd::EntityMirrorAcrossEdge { ModelingCmd::from(mcmd::EntityMirrorAcrossEdge {
ids: starting_sketches.iter().map(|sketch| sketch.id).collect(), ids: starting_sketches.iter().map(|sketch| sketch.id).collect(),
edge_id, edge_id,

View File

@ -379,7 +379,7 @@ async fn send_pattern_transform<T: GeometryTrait>(
exec_state: &mut ExecState, exec_state: &mut ExecState,
args: &Args, args: &Args,
) -> Result<Vec<T>, KclError> { ) -> Result<Vec<T>, KclError> {
let id = exec_state.id_generator.next_uuid(); let id = exec_state.next_uuid();
let resp = args let resp = args
.send_modeling_cmd( .send_modeling_cmd(
@ -1032,7 +1032,7 @@ async fn pattern_circular(
exec_state: &mut ExecState, exec_state: &mut ExecState,
args: Args, args: Args,
) -> Result<Geometries, KclError> { ) -> Result<Geometries, KclError> {
let id = exec_state.id_generator.next_uuid(); let id = exec_state.next_uuid();
let num_repetitions = match data.repetitions() { let num_repetitions = match data.repetitions() {
RepetitionsNeeded::More(n) => n, RepetitionsNeeded::More(n) => n,
RepetitionsNeeded::None => { RepetitionsNeeded::None => {

View File

@ -209,7 +209,7 @@ async fn make_offset_plane_in_engine(plane: &Plane, exec_state: &mut ExecState,
// Set the color. // Set the color.
args.batch_modeling_cmd( args.batch_modeling_cmd(
exec_state.id_generator.next_uuid(), exec_state.next_uuid(),
ModelingCmd::from(mcmd::PlaneSetColor { ModelingCmd::from(mcmd::PlaneSetColor {
color, color,
plane_id: plane.id, plane_id: plane.id,

View File

@ -264,7 +264,7 @@ async fn inner_revolve(
let angle = Angle::from_degrees(data.angle.unwrap_or(360.0)); let angle = Angle::from_degrees(data.angle.unwrap_or(360.0));
let id = exec_state.id_generator.next_uuid(); let id = exec_state.next_uuid();
match data.axis { match data.axis {
AxisOrEdgeReference::Axis(axis) => { AxisOrEdgeReference::Axis(axis) => {
let (axis, origin) = axis.axis_and_origin()?; let (axis, origin) = axis.axis_and_origin()?;

View File

@ -100,7 +100,7 @@ async fn inner_circle(
let angle_start = Angle::zero(); let angle_start = Angle::zero();
let angle_end = Angle::turn(); let angle_end = Angle::turn();
let id = exec_state.id_generator.next_uuid(); let id = exec_state.next_uuid();
args.batch_modeling_cmd( args.batch_modeling_cmd(
id, id,
@ -269,7 +269,7 @@ async fn inner_polygon(
// Draw all the lines with unique IDs and modified tags // Draw all the lines with unique IDs and modified tags
for vertex in vertices.iter().skip(1) { for vertex in vertices.iter().skip(1) {
let from = sketch.current_pen_position()?; let from = sketch.current_pen_position()?;
let id = exec_state.id_generator.next_uuid(); let id = exec_state.next_uuid();
args.batch_modeling_cmd( args.batch_modeling_cmd(
id, id,
@ -304,7 +304,7 @@ async fn inner_polygon(
// Close the polygon by connecting back to the first vertex with a new ID // Close the polygon by connecting back to the first vertex with a new ID
let from = sketch.current_pen_position()?; let from = sketch.current_pen_position()?;
let close_id = exec_state.id_generator.next_uuid(); let close_id = exec_state.next_uuid();
args.batch_modeling_cmd( args.batch_modeling_cmd(
close_id, close_id,
@ -337,7 +337,7 @@ async fn inner_polygon(
sketch.paths.push(current_path); sketch.paths.push(current_path);
args.batch_modeling_cmd( args.batch_modeling_cmd(
exec_state.id_generator.next_uuid(), exec_state.next_uuid(),
ModelingCmd::from(mcmd::ClosePath { path_id: sketch.id }), ModelingCmd::from(mcmd::ClosePath { path_id: sketch.id }),
) )
.await?; .await?;

View File

@ -230,7 +230,7 @@ async fn inner_shell(
} }
args.batch_modeling_cmd( args.batch_modeling_cmd(
exec_state.id_generator.next_uuid(), exec_state.next_uuid(),
ModelingCmd::from(mcmd::Solid3dShellFace { ModelingCmd::from(mcmd::Solid3dShellFace {
hollow: false, hollow: false,
face_ids, face_ids,
@ -316,7 +316,7 @@ async fn inner_hollow(
args.flush_batch_for_solid_set(exec_state, solid.clone().into()).await?; args.flush_batch_for_solid_set(exec_state, solid.clone().into()).await?;
args.batch_modeling_cmd( args.batch_modeling_cmd(
exec_state.id_generator.next_uuid(), exec_state.next_uuid(),
ModelingCmd::from(mcmd::Solid3dShellFace { ModelingCmd::from(mcmd::Solid3dShellFace {
hollow: true, hollow: true,
face_ids: Vec::new(), // This is empty because we want to hollow the entire object. face_ids: Vec::new(), // This is empty because we want to hollow the entire object.

View File

@ -124,7 +124,7 @@ async fn inner_line_to(
args: Args, args: Args,
) -> Result<Sketch, KclError> { ) -> Result<Sketch, KclError> {
let from = sketch.current_pen_position()?; let from = sketch.current_pen_position()?;
let id = exec_state.id_generator.next_uuid(); let id = exec_state.next_uuid();
args.batch_modeling_cmd( args.batch_modeling_cmd(
id, id,
@ -299,7 +299,7 @@ async fn inner_line(
let from = sketch.current_pen_position()?; let from = sketch.current_pen_position()?;
let to = [from.x + delta[0], from.y + delta[1]]; let to = [from.x + delta[0], from.y + delta[1]];
let id = exec_state.id_generator.next_uuid(); let id = exec_state.next_uuid();
args.batch_modeling_cmd( args.batch_modeling_cmd(
id, id,
@ -488,7 +488,7 @@ async fn inner_angled_line(
let to: [f64; 2] = [from.x + delta[0], from.y + delta[1]]; let to: [f64; 2] = [from.x + delta[0], from.y + delta[1]];
let id = exec_state.id_generator.next_uuid(); let id = exec_state.next_uuid();
args.batch_modeling_cmd( args.batch_modeling_cmd(
id, id,
@ -1230,7 +1230,7 @@ pub(crate) async fn inner_start_profile_at(
// Hide whatever plane we are sketching on. // Hide whatever plane we are sketching on.
// This is especially helpful for offset planes, which would be visible otherwise. // This is especially helpful for offset planes, which would be visible otherwise.
args.batch_end_cmd( args.batch_end_cmd(
exec_state.id_generator.next_uuid(), exec_state.next_uuid(),
ModelingCmd::from(mcmd::ObjectVisible { ModelingCmd::from(mcmd::ObjectVisible {
object_id: plane.id, object_id: plane.id,
hidden: true, hidden: true,
@ -1243,7 +1243,7 @@ pub(crate) async fn inner_start_profile_at(
// Enter sketch mode on the surface. // Enter sketch mode on the surface.
// We call this here so you can reuse the sketch surface for multiple sketches. // We call this here so you can reuse the sketch surface for multiple sketches.
let id = exec_state.id_generator.next_uuid(); let id = exec_state.next_uuid();
args.batch_modeling_cmd( args.batch_modeling_cmd(
id, id,
ModelingCmd::from(mcmd::EnableSketchMode { ModelingCmd::from(mcmd::EnableSketchMode {
@ -1261,8 +1261,8 @@ pub(crate) async fn inner_start_profile_at(
) )
.await?; .await?;
let id = exec_state.id_generator.next_uuid(); let id = exec_state.next_uuid();
let path_id = exec_state.id_generator.next_uuid(); let path_id = exec_state.next_uuid();
args.batch_modeling_cmd(path_id, ModelingCmd::from(mcmd::StartPath {})) args.batch_modeling_cmd(path_id, ModelingCmd::from(mcmd::StartPath {}))
.await?; .await?;
@ -1427,7 +1427,7 @@ pub(crate) async fn inner_close(
let from = sketch.current_pen_position()?; let from = sketch.current_pen_position()?;
let to: Point2d = sketch.start.from.into(); let to: Point2d = sketch.start.from.into();
let id = exec_state.id_generator.next_uuid(); let id = exec_state.next_uuid();
args.batch_modeling_cmd(id, ModelingCmd::from(mcmd::ClosePath { path_id: sketch.id })) args.batch_modeling_cmd(id, ModelingCmd::from(mcmd::ClosePath { path_id: sketch.id }))
.await?; .await?;
@ -1436,7 +1436,7 @@ pub(crate) async fn inner_close(
if let SketchSurface::Plane(_) = sketch.on { if let SketchSurface::Plane(_) = sketch.on {
// We were on a plane, disable the sketch mode. // We were on a plane, disable the sketch mode.
args.batch_modeling_cmd( args.batch_modeling_cmd(
exec_state.id_generator.next_uuid(), exec_state.next_uuid(),
ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable {}), ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable {}),
) )
.await?; .await?;
@ -1573,7 +1573,7 @@ pub(crate) async fn inner_arc(
} }
let ccw = angle_start < angle_end; let ccw = angle_start < angle_end;
let id = exec_state.id_generator.next_uuid(); let id = exec_state.next_uuid();
args.batch_modeling_cmd( args.batch_modeling_cmd(
id, id,
@ -1652,7 +1652,7 @@ pub(crate) async fn inner_arc_to(
args: Args, args: Args,
) -> Result<Sketch, KclError> { ) -> Result<Sketch, KclError> {
let from: Point2d = sketch.current_pen_position()?; let from: Point2d = sketch.current_pen_position()?;
let id = exec_state.id_generator.next_uuid(); let id = exec_state.next_uuid();
// The start point is taken from the path you are extending. // The start point is taken from the path you are extending.
args.batch_modeling_cmd( args.batch_modeling_cmd(
@ -1798,7 +1798,7 @@ async fn inner_tangential_arc(
let tangent_info = sketch.get_tangential_info_from_paths(); //this function desperately needs some documentation let tangent_info = sketch.get_tangential_info_from_paths(); //this function desperately needs some documentation
let tan_previous_point = tangent_info.tan_previous_point(from.into()); let tan_previous_point = tangent_info.tan_previous_point(from.into());
let id = exec_state.id_generator.next_uuid(); let id = exec_state.next_uuid();
let (center, to, ccw) = match data { let (center, to, ccw) = match data {
TangentialArcData::RadiusAndOffset { radius, offset } => { TangentialArcData::RadiusAndOffset { radius, offset } => {
@ -1935,7 +1935,7 @@ async fn inner_tangential_arc_to(
}); });
let delta = [to_x - from.x, to_y - from.y]; let delta = [to_x - from.x, to_y - from.y];
let id = exec_state.id_generator.next_uuid(); let id = exec_state.next_uuid();
args.batch_modeling_cmd(id, tan_arc_to(&sketch, &delta)).await?; args.batch_modeling_cmd(id, tan_arc_to(&sketch, &delta)).await?;
let current_path = Path::TangentialArcTo { let current_path = Path::TangentialArcTo {
@ -2018,7 +2018,7 @@ async fn inner_tangential_arc_to_relative(
})); }));
} }
let id = exec_state.id_generator.next_uuid(); let id = exec_state.next_uuid();
args.batch_modeling_cmd(id, tan_arc_to(&sketch, &delta)).await?; args.batch_modeling_cmd(id, tan_arc_to(&sketch, &delta)).await?;
let current_path = Path::TangentialArcTo { let current_path = Path::TangentialArcTo {
@ -2138,7 +2138,7 @@ async fn inner_bezier_curve(
let delta = data.to; let delta = data.to;
let to = [from.x + data.to[0], from.y + data.to[1]]; let to = [from.x + data.to[0], from.y + data.to[1]];
let id = exec_state.id_generator.next_uuid(); let id = exec_state.next_uuid();
args.batch_modeling_cmd( args.batch_modeling_cmd(
id, id,
@ -2230,7 +2230,7 @@ async fn inner_hole(
let hole_sketches: Vec<Sketch> = hole_sketch.into(); let hole_sketches: Vec<Sketch> = hole_sketch.into();
for hole_sketch in hole_sketches { for hole_sketch in hole_sketches {
args.batch_modeling_cmd( args.batch_modeling_cmd(
exec_state.id_generator.next_uuid(), exec_state.next_uuid(),
ModelingCmd::from(mcmd::Solid2dAddHole { ModelingCmd::from(mcmd::Solid2dAddHole {
object_id: sketch.id, object_id: sketch.id,
hole_id: hole_sketch.id, hole_id: hole_sketch.id,
@ -2241,7 +2241,7 @@ async fn inner_hole(
// suggestion (mike) // suggestion (mike)
// we also hide the source hole since its essentially "consumed" by this operation // we also hide the source hole since its essentially "consumed" by this operation
args.batch_modeling_cmd( args.batch_modeling_cmd(
exec_state.id_generator.next_uuid(), exec_state.next_uuid(),
ModelingCmd::from(mcmd::ObjectVisible { ModelingCmd::from(mcmd::ObjectVisible {
object_id: hole_sketch.id, object_id: hole_sketch.id,
hidden: true, hidden: true,

View File

@ -87,7 +87,7 @@ async fn inner_sweep(
exec_state: &mut ExecState, exec_state: &mut ExecState,
args: Args, args: Args,
) -> Result<Box<Solid>, KclError> { ) -> Result<Box<Solid>, KclError> {
let id = exec_state.id_generator.next_uuid(); let id = exec_state.next_uuid();
args.batch_modeling_cmd( args.batch_modeling_cmd(
id, id,
ModelingCmd::from(mcmd::Sweep { ModelingCmd::from(mcmd::Sweep {

View File

@ -41,7 +41,7 @@ pub async fn execute_and_snapshot_ast(
let ctx = new_context(units, true, project_directory).await?; let ctx = new_context(units, true, project_directory).await?;
do_execute_and_snapshot(&ctx, ast) do_execute_and_snapshot(&ctx, ast)
.await .await
.map(|(state, snap)| (state.memory, state.operations, snap)) .map(|(state, snap)| (state.mod_local.memory, state.mod_local.operations, snap))
} }
pub async fn execute_and_snapshot_no_auth( pub async fn execute_and_snapshot_no_auth(

View File

@ -146,11 +146,11 @@ impl ImportStatement {
string.push_str(" from "); string.push_str(" from ");
} }
ImportSelector::Glob(_) => string.push_str("* from "), ImportSelector::Glob(_) => string.push_str("* from "),
ImportSelector::None(_) => {} ImportSelector::None { .. } => {}
} }
string.push_str(&format!("\"{}\"", self.path)); string.push_str(&format!("\"{}\"", self.path));
if let ImportSelector::None(Some(alias)) = &self.selector { if let ImportSelector::None { alias: Some(alias) } = &self.selector {
string.push_str(" as "); string.push_str(" as ");
string.push_str(&alias.name); string.push_str(&alias.name);
} }

View File

@ -5,8 +5,9 @@ description: Error from executing import_side_effect.kcl
KCL Semantic error KCL Semantic error
× semantic: Error loading imported file. Open it to view more details. × semantic: Error loading imported file. Open it to view more details.
│ export_side_effect.kcl: Cannot send modeling commands while importing. tests/import_side_effect/export_side_effect.kcl: Cannot send modeling
│ Wrap your code in a function if you want to import the file. commands while importing. Wrap your code in a function if you want to
│ import the file.
╭──── ╭────
1 │ import foo from "export_side_effect.kcl" 1 │ import foo from "export_side_effect.kcl"
· ──────────────────────────────────────── · ────────────────────────────────────────

View File

@ -1,27 +1,5 @@
--- ---
source: kcl/src/simulation_tests.rs source: kcl/src/simulation_tests.rs
description: Operations executed import_side_effect.kcl description: Operations executed import_side_effect.kcl
snapshot_kind: text
--- ---
[ []
{
"isError": true,
"labeledArgs": {
"data": {
"sourceRange": [
95,
99,
1
]
}
},
"name": "startSketchOn",
"sourceRange": [
81,
100,
1
],
"type": "StdLibCall",
"unlabeledArg": null
}
]

View File

@ -0,0 +1,150 @@
---
source: kcl/src/simulation_tests.rs
description: Result of parsing import_whole.kcl
---
{
"Ok": {
"body": [
{
"end": 32,
"path": "exported_mod.kcl",
"selector": {
"type": "None",
"alias": {
"end": 32,
"name": "foo",
"start": 29,
"type": "Identifier"
}
},
"start": 0,
"type": "ImportStatement",
"type": "ImportStatement"
},
{
"declaration": {
"end": 96,
"id": {
"end": 37,
"name": "bar",
"start": 34,
"type": "Identifier"
},
"init": {
"body": [
{
"end": 43,
"name": "foo",
"start": 40,
"type": "Identifier",
"type": "Identifier"
},
{
"arguments": [
{
"end": 92,
"properties": [
{
"end": 72,
"key": {
"end": 62,
"name": "faces",
"start": 57,
"type": "Identifier"
},
"start": 57,
"type": "ObjectProperty",
"value": {
"elements": [
{
"end": 71,
"raw": "'end'",
"start": 66,
"type": "Literal",
"type": "Literal",
"value": "end"
}
],
"end": 72,
"start": 65,
"type": "ArrayExpression",
"type": "ArrayExpression"
}
},
{
"end": 90,
"key": {
"end": 83,
"name": "thickness",
"start": 74,
"type": "Identifier"
},
"start": 74,
"type": "ObjectProperty",
"value": {
"end": 90,
"raw": "0.25",
"start": 86,
"type": "Literal",
"type": "Literal",
"value": 0.25
}
}
],
"start": 55,
"type": "ObjectExpression",
"type": "ObjectExpression"
},
{
"end": 95,
"start": 94,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
}
],
"callee": {
"end": 54,
"name": "shell",
"start": 49,
"type": "Identifier"
},
"end": 96,
"start": 49,
"type": "CallExpression",
"type": "CallExpression"
}
],
"end": 96,
"start": 40,
"type": "PipeExpression",
"type": "PipeExpression"
},
"start": 34,
"type": "VariableDeclarator"
},
"end": 96,
"kind": "const",
"start": 34,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
}
],
"end": 97,
"nonCodeMeta": {
"nonCodeNodes": {
"0": [
{
"end": 34,
"start": 32,
"type": "NonCodeNode",
"value": {
"type": "newLine"
}
}
]
},
"startNodes": []
},
"start": 0
}
}

View File

@ -0,0 +1,3 @@
startSketchOn('XY')
|> circle({ center = [5, 5], radius = 10 }, %)
|> extrude(10, %)

View File

@ -0,0 +1,4 @@
import "exported_mod.kcl" as foo
bar = foo
|> shell({ faces = ['end'], thickness = 0.25 }, %)

View File

@ -0,0 +1,32 @@
---
source: kcl/src/simulation_tests.rs
description: Operations executed import_whole.kcl
---
[
{
"labeledArgs": {
"data": {
"sourceRange": [
55,
92,
0
]
},
"solid_set": {
"sourceRange": [
94,
95,
0
]
}
},
"name": "shell",
"sourceRange": [
49,
96,
0
],
"type": "StdLibCall",
"unlabeledArg": null
}
]

View File

@ -0,0 +1,164 @@
---
source: kcl/src/simulation_tests.rs
description: Program memory after executing import_whole.kcl
---
{
"environments": [
{
"bindings": {
"HALF_TURN": {
"type": "Number",
"value": 180.0,
"__meta": []
},
"QUARTER_TURN": {
"type": "Number",
"value": 90.0,
"__meta": []
},
"THREE_QUARTER_TURN": {
"type": "Number",
"value": 270.0,
"__meta": []
},
"ZERO": {
"type": "Number",
"value": 0.0,
"__meta": []
},
"bar": {
"type": "Solid",
"type": "Solid",
"id": "[uuid]",
"value": [
{
"faceId": "[uuid]",
"id": "[uuid]",
"sourceRange": [
25,
68,
1
],
"tag": null,
"type": "extrudeArc"
}
],
"sketch": {
"type": "Sketch",
"id": "[uuid]",
"paths": [
{
"__geoMeta": {
"id": "[uuid]",
"sourceRange": [
25,
68,
1
]
},
"ccw": true,
"center": [
5.0,
5.0
],
"from": [
15.0,
5.0
],
"radius": 10.0,
"tag": null,
"to": [
15.0,
5.0
],
"type": "Circle"
}
],
"on": {
"type": "plane",
"id": "[uuid]",
"value": "XY",
"origin": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"xAxis": {
"x": 1.0,
"y": 0.0,
"z": 0.0
},
"yAxis": {
"x": 0.0,
"y": 1.0,
"z": 0.0
},
"zAxis": {
"x": 0.0,
"y": 0.0,
"z": 1.0
},
"__meta": []
},
"start": {
"from": [
15.0,
5.0
],
"to": [
15.0,
5.0
],
"tag": null,
"__geoMeta": {
"id": "[uuid]",
"sourceRange": [
25,
68,
1
]
}
},
"__meta": [
{
"sourceRange": [
25,
68,
1
]
}
]
},
"height": 10.0,
"startCapId": "[uuid]",
"endCapId": "[uuid]",
"__meta": [
{
"sourceRange": [
25,
68,
1
]
}
]
},
"foo": {
"type": "Module",
"value": 1,
"__meta": [
{
"sourceRange": [
0,
32,
0
]
}
]
}
},
"parent": null
}
],
"currentEnv": 0,
"return": null
}

View File

@ -86,7 +86,7 @@ pub async fn execute(
// Populate from the old exec state if it exists. // Populate from the old exec state if it exists.
if let Some(program_memory_override) = program_memory_override { if let Some(program_memory_override) = program_memory_override {
exec_state.memory = program_memory_override; exec_state.mod_local.memory = program_memory_override;
} else { } else {
// If we are in mock mode, we don't want to use any cache. // If we are in mock mode, we don't want to use any cache.
if let Some(old) = read_old_ast_memory().await { if let Some(old) = read_old_ast_memory().await {
@ -110,7 +110,7 @@ pub async fn execute(
} }
// Add additional outputs to the error. // Add additional outputs to the error.
let error = KclErrorWithOutputs::new(err, exec_state.operations.clone()); let error = KclErrorWithOutputs::new(err, exec_state.mod_local.operations.clone());
// Throw the error. // Throw the error.
return Err(serde_json::to_string(&error).map_err(|serde_err| serde_err.to_string())?); return Err(serde_json::to_string(&error).map_err(|serde_err| serde_err.to_string())?);

View File

@ -1,7 +1,7 @@
mod cache; mod cache;
use kcl_lib::{ use kcl_lib::{
test_server::{execute_and_snapshot, execute_and_snapshot_no_auth, new_context}, test_server::{execute_and_snapshot, execute_and_snapshot_no_auth},
UnitLength, UnitLength,
}; };
@ -2001,62 +2001,3 @@ async fn kcl_test_error_no_auth_websocket() {
.to_string() .to_string()
.contains("Please send the following object over this websocket")); .contains("Please send the following object over this websocket"));
} }
#[tokio::test(flavor = "multi_thread")]
async fn kcl_test_ids_stable_between_executions() {
let code = r#"sketch001 = startSketchOn('XZ')
|> startProfileAt([61.74, 206.13], %)
|> xLine(305.11, %, $seg01)
|> yLine(-291.85, %)
|> xLine(-segLen(seg01), %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
|> extrude(40.14, %)
|> shell({
faces: [seg01],
thickness: 3.14,
}, %)
"#;
let ctx = new_context(UnitLength::Mm, true, None).await.unwrap();
let old_program = kcl_lib::Program::parse_no_errs(code).unwrap();
// Execute the program.
let mut exec_state = Default::default();
let cache_info = kcl_lib::CacheInformation {
old: None,
new_ast: old_program.ast.clone(),
};
ctx.run(cache_info, &mut exec_state).await.unwrap();
// Get the id_generator from the first execution.
let id_generator = exec_state.id_generator.clone();
let code = r#"sketch001 = startSketchOn('XZ')
|> startProfileAt([62.74, 206.13], %)
|> xLine(305.11, %, $seg01)
|> yLine(-291.85, %)
|> xLine(-segLen(seg01), %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
|> extrude(40.14, %)
|> shell({
faces: [seg01],
thickness: 3.14,
}, %)
"#;
// Execute a slightly different program again.
let program = kcl_lib::Program::parse_no_errs(code).unwrap();
let cache_info = kcl_lib::CacheInformation {
old: Some(kcl_lib::OldAstState {
ast: old_program.ast.clone(),
exec_state: exec_state.clone(),
settings: ctx.settings.clone(),
}),
new_ast: program.ast.clone(),
};
// Execute the program.
ctx.run(cache_info, &mut exec_state).await.unwrap();
assert_eq!(id_generator, exec_state.id_generator);
}

View File

@ -15,8 +15,8 @@ async fn setup(code: &str, name: &str) -> Result<(ExecutorContext, Program, Modu
// We need to get the sketch ID. // We need to get the sketch ID.
// Get the sketch ID from memory. // Get the sketch ID from memory.
let KclValue::Sketch { value: sketch } = exec_state.memory.get(name, SourceRange::default()).unwrap() else { let KclValue::Sketch { value: sketch } = exec_state.memory().get(name, SourceRange::default()).unwrap() else {
anyhow::bail!("part001 not found in memory: {:?}", exec_state.memory); anyhow::bail!("part001 not found in memory: {:?}", exec_state.memory());
}; };
let sketch_id = sketch.id; let sketch_id = sketch.id;