Merge remote-tracking branch 'origin/main' into paultag/refgraph

This commit is contained in:
Paul R. Tagliamonte
2024-12-18 14:05:44 -05:00
113 changed files with 5492 additions and 714 deletions

File diff suppressed because one or more lines are too long

View File

@ -101,7 +101,6 @@ layout: manual
* [`sin`](kcl/sin)
* [`sqrt`](kcl/sqrt)
* [`startProfileAt`](kcl/startProfileAt)
* [`startSketchAt`](kcl/startSketchAt)
* [`startSketchOn`](kcl/startSketchOn)
* [`sweep`](kcl/sweep)
* [`tan`](kcl/tan)

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.
```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 |
| `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 |
| `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 |
### 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")
```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 |
|----------|------|-------------|----------|
| `total_instances` | `u32` | | Yes |
| `total_instances` | `integer` | | Yes |
| `transform_function` | `FunctionParam` | | Yes |
| `solid_set` | [`SolidSet`](/docs/kcl/types/SolidSet) | A solid or a group of solids. | Yes |
@ -95,7 +95,8 @@ fn cube(length, center) {
p2 = [l + x, l + y]
p3 = [l + x, -l + y]
return startSketchAt(p0)
return startSketchOn('XY')
|> startProfileAt(p0, %)
|> lineTo(p1, %)
|> lineTo(p2, %)
|> lineTo(p3, %)
@ -132,7 +133,8 @@ fn cube(length, center) {
p2 = [l + x, l + y]
p3 = [l + x, -l + y]
return startSketchAt(p0)
return startSketchOn('XY')
|> startProfileAt(p0, %)
|> lineTo(p1, %)
|> lineTo(p2, %)
|> lineTo(p3, %)
@ -195,7 +197,8 @@ fn transform(i) {
{ rotation = { angle = 45 * i } }
]
}
startSketchAt([0, 0])
startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> polygon({
radius = 10,
numSides = 4,

View File

@ -9,7 +9,7 @@ Just like patternTransform, but works on 2D sketches not 3D solids.
```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 |
|----------|------|-------------|----------|
| `total_instances` | `u32` | | Yes |
| `total_instances` | `integer` | | Yes |
| `transform_function` | `FunctionParam` | | Yes |
| `solid_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | A sketch or a group of sketches. | Yes |

View File

@ -79,10 +79,11 @@ fn decagon(radius) {
stepAngle = 1 / 10 * tau()
// Start the decagon sketch at this point.
startOfDecagonSketch = startSketchAt([cos(0) * radius, sin(0) * radius])
startOfDecagonSketch = startSketchOn('XY')
|> startProfileAt([cos(0) * radius, sin(0) * radius], %)
// Use a `reduce` to draw the remaining decagon sides.
// For each number in the array 1..10, run the given function,
// Use a `reduce` to draw the remaining decagon sides.
// For each number in the array 1..10, run the given function,
// which takes a partially-sketched decagon and adds one more edge to it.
fullDecagon = reduce([1..10], startOfDecagonSketch, fn(i, partialDecagon) {
// Draw one edge of the decagon.
@ -97,7 +98,8 @@ fn decagon(radius) {
/* The `decagon` above is basically like this pseudo-code:
fn decagon(radius):
stepAngle = (1/10) * tau()
startOfDecagonSketch = startSketchAt([(cos(0)*radius), (sin(0) * radius)])
plane = startSketchOn('XY')
startOfDecagonSketch = startProfileAt([(cos(0)*radius), (sin(0) * radius)], plane)
// Here's the reduce part.
partialDecagon = startOfDecagonSketch

View File

@ -28,7 +28,8 @@ segEnd(tag: TagIdentifier) -> [number]
```js
w = 15
cube = startSketchAt([0, 0])
cube = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line([w, 0], %, $line1)
|> line([0, w], %, $line2)
|> line([-w, 0], %, $line3)
@ -37,7 +38,8 @@ cube = startSketchAt([0, 0])
|> extrude(5, %)
fn cylinder(radius, tag) {
return startSketchAt([0, 0])
return startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> circle({
radius = radius,
center = segEnd(tag)

View File

@ -28,7 +28,8 @@ segStart(tag: TagIdentifier) -> [number]
```js
w = 15
cube = startSketchAt([0, 0])
cube = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line([w, 0], %, $line1)
|> line([0, w], %, $line2)
|> line([-w, 0], %, $line3)
@ -37,7 +38,8 @@ cube = startSketchAt([0, 0])
|> extrude(5, %)
fn cylinder(radius, tag) {
return startSketchAt([0, 0])
return startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> circle({
radius = radius,
center = segStart(tag)

View File

@ -4,6 +4,8 @@ excerpt: "Start a new 2-dimensional sketch at a given point on the 'XY' plane."
layout: manual
---
**WARNING:** This function is deprecated.
Start a new 2-dimensional sketch at a given point on the 'XY' plane.

View File

@ -97135,7 +97135,7 @@
},
{
"name": "base_curve_index",
"type": "u32",
"type": "integer",
"schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
"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",
"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": {
"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",
@ -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",
"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": {
"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",
@ -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",
"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": {
"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",
@ -122705,7 +122798,7 @@
"args": [
{
"name": "total_instances",
"type": "u32",
"type": "integer",
"schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
"title": "uint32",
@ -125467,10 +125560,10 @@
"examples": [
"// Each instance will be shifted along the X axis.\nfn transform(id) {\n return { translate = [4 * id, 0, 0] }\n}\n\n// Sketch 4 cylinders.\nsketch001 = startSketchOn('XZ')\n |> circle({ center = [0, 0], radius = 2 }, %)\n |> extrude(5, %)\n |> patternTransform(4, transform, %)",
"// Each instance will be shifted along the X axis,\n// with a gap between the original (at x = 0) and the first replica\n// (at x = 8). This is because `id` starts at 1.\nfn transform(id) {\n return { translate = [4 * (1 + id), 0, 0] }\n}\n\nsketch001 = startSketchOn('XZ')\n |> circle({ center = [0, 0], radius = 2 }, %)\n |> extrude(5, %)\n |> patternTransform(4, transform, %)",
"fn cube(length, center) {\n l = length / 2\n x = center[0]\n y = center[1]\n p0 = [-l + x, -l + y]\n p1 = [-l + x, l + y]\n p2 = [l + x, l + y]\n p3 = [l + x, -l + y]\n\n return startSketchAt(p0)\n |> lineTo(p1, %)\n |> lineTo(p2, %)\n |> lineTo(p3, %)\n |> lineTo(p0, %)\n |> close(%)\n |> extrude(length, %)\n}\n\nwidth = 20\nfn transform(i) {\n return {\n // Move down each time.\n translate = [0, 0, -i * width],\n // Make the cube longer, wider and flatter each time.\n scale = [pow(1.1, i), pow(1.1, i), pow(0.9, i)],\n // Turn by 15 degrees each time.\n rotation = { angle = 15 * i, origin = \"local\" }\n }\n}\n\nmyCubes = cube(width, [100, 0])\n |> patternTransform(25, transform, %)",
"fn cube(length, center) {\n l = length / 2\n x = center[0]\n y = center[1]\n p0 = [-l + x, -l + y]\n p1 = [-l + x, l + y]\n p2 = [l + x, l + y]\n p3 = [l + x, -l + y]\n\n return startSketchAt(p0)\n |> lineTo(p1, %)\n |> lineTo(p2, %)\n |> lineTo(p3, %)\n |> lineTo(p0, %)\n |> close(%)\n |> extrude(length, %)\n}\n\nwidth = 20\nfn transform(i) {\n return {\n translate = [0, 0, -i * width],\n rotation = {\n angle = 90 * i,\n // Rotate around the overall scene's origin.\n origin = \"global\"\n }\n }\n}\nmyCubes = cube(width, [100, 100])\n |> patternTransform(4, transform, %)",
"fn cube(length, center) {\n l = length / 2\n x = center[0]\n y = center[1]\n p0 = [-l + x, -l + y]\n p1 = [-l + x, l + y]\n p2 = [l + x, l + y]\n p3 = [l + x, -l + y]\n\n return startSketchOn('XY')\n |> startProfileAt(p0, %)\n |> lineTo(p1, %)\n |> lineTo(p2, %)\n |> lineTo(p3, %)\n |> lineTo(p0, %)\n |> close(%)\n |> extrude(length, %)\n}\n\nwidth = 20\nfn transform(i) {\n return {\n // Move down each time.\n translate = [0, 0, -i * width],\n // Make the cube longer, wider and flatter each time.\n scale = [pow(1.1, i), pow(1.1, i), pow(0.9, i)],\n // Turn by 15 degrees each time.\n rotation = { angle = 15 * i, origin = \"local\" }\n }\n}\n\nmyCubes = cube(width, [100, 0])\n |> patternTransform(25, transform, %)",
"fn cube(length, center) {\n l = length / 2\n x = center[0]\n y = center[1]\n p0 = [-l + x, -l + y]\n p1 = [-l + x, l + y]\n p2 = [l + x, l + y]\n p3 = [l + x, -l + y]\n\n return startSketchOn('XY')\n |> startProfileAt(p0, %)\n |> lineTo(p1, %)\n |> lineTo(p2, %)\n |> lineTo(p3, %)\n |> lineTo(p0, %)\n |> close(%)\n |> extrude(length, %)\n}\n\nwidth = 20\nfn transform(i) {\n return {\n translate = [0, 0, -i * width],\n rotation = {\n angle = 90 * i,\n // Rotate around the overall scene's origin.\n origin = \"global\"\n }\n }\n}\nmyCubes = cube(width, [100, 100])\n |> patternTransform(4, transform, %)",
"// Parameters\nr = 50 // base radius\nh = 10 // layer height\nt = 0.005 // taper factor [0-1)\n// Defines how to modify each layer of the vase.\n// Each replica is shifted up the Z axis, and has a smoothly-varying radius\nfn transform(replicaId) {\n scale = r * abs(1 - (t * replicaId)) * (5 + cos(replicaId / 8))\n return {\n translate = [0, 0, replicaId * 10],\n scale = [scale, scale, 0]\n }\n}\n// Each layer is just a pretty thin cylinder.\nfn layer() {\n return startSketchOn(\"XY\")\n // or some other plane idk\n |> circle({ center = [0, 0], radius = 1 }, %, $tag1)\n |> extrude(h, %)\n}\n// The vase is 100 layers tall.\n// The 100 layers are replica of each other, with a slight transformation applied to each.\nvase = layer()\n |> patternTransform(100, transform, %)",
"fn transform(i) {\n // Transform functions can return multiple transforms. They'll be applied in order.\n return [\n { translate = [30 * i, 0, 0] },\n { rotation = { angle = 45 * i } }\n ]\n}\nstartSketchAt([0, 0])\n |> polygon({\n radius = 10,\n numSides = 4,\n center = [0, 0],\n inscribed = false\n }, %)\n |> extrude(4, %)\n |> patternTransform(3, transform, %)"
"fn transform(i) {\n // Transform functions can return multiple transforms. They'll be applied in order.\n return [\n { translate = [30 * i, 0, 0] },\n { rotation = { angle = 45 * i } }\n ]\n}\nstartSketchOn('XY')\n |> startProfileAt([0, 0], %)\n |> polygon({\n radius = 10,\n numSides = 4,\n center = [0, 0],\n inscribed = false\n }, %)\n |> extrude(4, %)\n |> patternTransform(3, transform, %)"
]
},
{
@ -125482,7 +125575,7 @@
"args": [
{
"name": "total_instances",
"type": "u32",
"type": "integer",
"schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
"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",
"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": {
"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",
@ -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",
"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",
"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": {
"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",
@ -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",
"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",
"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": {
"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",
@ -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",
"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": {
"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",
@ -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",
"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",
"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": {
"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",
@ -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",
"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": {
"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",
@ -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",
"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",
"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": {
"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",
@ -153126,7 +153536,7 @@
"examples": [
"// This function adds two numbers.\nfn add(a, b) {\n return a + b\n}\n\n// This function adds an array of numbers.\n// It uses the `reduce` function, to call the `add` function on every\n// element of the `arr` parameter. The starting value is 0.\nfn sum(arr) {\n return reduce(arr, 0, add)\n}\n\n/* The above is basically like this pseudo-code:\nfn sum(arr):\n sumSoFar = 0\n for i in arr:\n sumSoFar = add(sumSoFar, i)\n return sumSoFar */\n\n\n// We use `assertEqual` to check that our `sum` function gives the\n// expected result. It's good to check your work!\nassertEqual(sum([1, 2, 3]), 6, 0.00001, \"1 + 2 + 3 summed is 6\")",
"// This example works just like the previous example above, but it uses\n// an anonymous `add` function as its parameter, instead of declaring a\n// named function outside.\narr = [1, 2, 3]\nsum = reduce(arr, 0, fn(i, result_so_far) {\n return i + result_so_far\n})\n\n// We use `assertEqual` to check that our `sum` function gives the\n// expected result. It's good to check your work!\nassertEqual(sum, 6, 0.00001, \"1 + 2 + 3 summed is 6\")",
"// Declare a function that sketches a decagon.\nfn decagon(radius) {\n // Each side of the decagon is turned this many degrees from the previous angle.\n stepAngle = 1 / 10 * tau()\n\n // Start the decagon sketch at this point.\n startOfDecagonSketch = startSketchAt([cos(0) * radius, sin(0) * radius])\n\n // Use a `reduce` to draw the remaining decagon sides.\n // For each number in the array 1..10, run the given function,\n // which takes a partially-sketched decagon and adds one more edge to it.\n fullDecagon = reduce([1..10], startOfDecagonSketch, fn(i, partialDecagon) {\n // Draw one edge of the decagon.\n x = cos(stepAngle * i) * radius\n y = sin(stepAngle * i) * radius\n return lineTo([x, y], partialDecagon)\n })\n\n return fullDecagon\n}\n\n/* The `decagon` above is basically like this pseudo-code:\nfn decagon(radius):\n stepAngle = (1/10) * tau()\n startOfDecagonSketch = startSketchAt([(cos(0)*radius), (sin(0) * radius)])\n\n // Here's the reduce part.\n partialDecagon = startOfDecagonSketch\n for i in [1..10]:\n x = cos(stepAngle * i) * radius\n y = sin(stepAngle * i) * radius\n partialDecagon = lineTo([x, y], partialDecagon)\n fullDecagon = partialDecagon // it's now full\n return fullDecagon */\n\n\n// Use the `decagon` function declared above, to sketch a decagon with radius 5.\ndecagon(5.0)\n |> close(%)"
"// Declare a function that sketches a decagon.\nfn decagon(radius) {\n // Each side of the decagon is turned this many degrees from the previous angle.\n stepAngle = 1 / 10 * tau()\n\n // Start the decagon sketch at this point.\n startOfDecagonSketch = startSketchOn('XY')\n |> startProfileAt([cos(0) * radius, sin(0) * radius], %)\n\n // Use a `reduce` to draw the remaining decagon sides.\n // For each number in the array 1..10, run the given function,\n // which takes a partially-sketched decagon and adds one more edge to it.\n fullDecagon = reduce([1..10], startOfDecagonSketch, fn(i, partialDecagon) {\n // Draw one edge of the decagon.\n x = cos(stepAngle * i) * radius\n y = sin(stepAngle * i) * radius\n return lineTo([x, y], partialDecagon)\n })\n\n return fullDecagon\n}\n\n/* The `decagon` above is basically like this pseudo-code:\nfn decagon(radius):\n stepAngle = (1/10) * tau()\n plane = startSketchOn('XY')\n startOfDecagonSketch = startProfileAt([(cos(0)*radius), (sin(0) * radius)], plane)\n\n // Here's the reduce part.\n partialDecagon = startOfDecagonSketch\n for i in [1..10]:\n x = cos(stepAngle * i) * radius\n y = sin(stepAngle * i) * radius\n partialDecagon = lineTo([x, y], partialDecagon)\n fullDecagon = partialDecagon // it's now full\n return fullDecagon */\n\n\n// Use the `decagon` function declared above, to sketch a decagon with radius 5.\ndecagon(5.0)\n |> close(%)"
]
},
{
@ -158942,7 +159352,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"w = 15\ncube = startSketchAt([0, 0])\n |> line([w, 0], %, $line1)\n |> line([0, w], %, $line2)\n |> line([-w, 0], %, $line3)\n |> line([0, -w], %, $line4)\n |> close(%)\n |> extrude(5, %)\n\nfn cylinder(radius, tag) {\n return startSketchAt([0, 0])\n |> circle({\n radius = radius,\n center = segEnd(tag)\n }, %)\n |> extrude(radius, %)\n}\n\ncylinder(1, line1)\ncylinder(2, line2)\ncylinder(3, line3)\ncylinder(4, line4)"
"w = 15\ncube = startSketchOn('XY')\n |> startProfileAt([0, 0], %)\n |> line([w, 0], %, $line1)\n |> line([0, w], %, $line2)\n |> line([-w, 0], %, $line3)\n |> line([0, -w], %, $line4)\n |> close(%)\n |> extrude(5, %)\n\nfn cylinder(radius, tag) {\n return startSketchOn('XY')\n |> startProfileAt([0, 0], %)\n |> circle({\n radius = radius,\n center = segEnd(tag)\n }, %)\n |> extrude(radius, %)\n}\n\ncylinder(1, line1)\ncylinder(2, line2)\ncylinder(3, line3)\ncylinder(4, line4)"
]
},
{
@ -162571,7 +162981,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"w = 15\ncube = startSketchAt([0, 0])\n |> line([w, 0], %, $line1)\n |> line([0, w], %, $line2)\n |> line([-w, 0], %, $line3)\n |> line([0, -w], %, $line4)\n |> close(%)\n |> extrude(5, %)\n\nfn cylinder(radius, tag) {\n return startSketchAt([0, 0])\n |> circle({\n radius = radius,\n center = segStart(tag)\n }, %)\n |> extrude(radius, %)\n}\n\ncylinder(1, line1)\ncylinder(2, line2)\ncylinder(3, line3)\ncylinder(4, line4)"
"w = 15\ncube = startSketchOn('XY')\n |> startProfileAt([0, 0], %)\n |> line([w, 0], %, $line1)\n |> line([0, w], %, $line2)\n |> line([-w, 0], %, $line3)\n |> line([0, -w], %, $line4)\n |> close(%)\n |> extrude(5, %)\n\nfn cylinder(radius, tag) {\n return startSketchOn('XY')\n |> startProfileAt([0, 0], %)\n |> circle({\n radius = radius,\n center = segStart(tag)\n }, %)\n |> extrude(radius, %)\n}\n\ncylinder(1, line1)\ncylinder(2, line2)\ncylinder(3, line3)\ncylinder(4, line4)"
]
},
{
@ -173863,7 +174273,7 @@
"labelRequired": true
},
"unpublished": false,
"deprecated": false,
"deprecated": true,
"examples": [
"exampleSketch = startSketchAt([0, 0])\n |> line([10, 0], %)\n |> line([0, 10], %)\n |> line([-10, 0], %)\n |> close(%)\n\nexample = extrude(5, exampleSketch)",
"exampleSketch = startSketchAt([10, 10])\n |> line([10, 0], %)\n |> line([0, 10], %)\n |> line([-10, 0], %)\n |> close(%)\n\nexample = extrude(5, exampleSketch)",

View File

@ -13,13 +13,18 @@ Data to draw an angled line.
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
| 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`)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

View File

@ -1,6 +1,17 @@
import { test, expect } from '@playwright/test'
import { getUtils, setup, tearDown, TEST_COLORS } from './test-utils'
import {
test as testFixture,
expect as expectFixture,
} from './fixtures/fixtureSetup'
import { join } from 'path'
import {
getUtils,
setup,
tearDown,
TEST_COLORS,
executorInputPath,
} from './test-utils'
import * as fsp from 'fs/promises'
import { XOR } from 'lib/utils'
test.beforeEach(async ({ context, page }, testInfo) => {
@ -1028,3 +1039,58 @@ part002 = startSketchOn('XZ')
await expect(page.getByTestId('segment-overlay')).toHaveCount(2)
})
})
testFixture.describe('Electron constraint tests', () => {
testFixture(
'Able to double click label to set constraint',
{ tag: '@electron' },
async ({ tronApp, homePage, scene, editor, toolbar }) => {
await tronApp.initialise({
fixtures: { homePage, scene, editor, toolbar },
folderSetupFn: async (dir) => {
const bracketDir = join(dir, 'test-sample')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
executorInputPath('angled_line.kcl'),
join(bracketDir, 'main.kcl')
)
},
})
const [clickHandler] = scene.makeMouseHelpers(600, 300)
await test.step('setup test', async () => {
await homePage.expectState({
projectCards: [
{
title: 'test-sample',
fileCount: 1,
},
],
sortBy: 'last-modified-desc',
})
await homePage.openProject('test-sample')
await scene.waitForExecutionDone()
})
await test.step('Double click to constrain', async () => {
await clickHandler()
await tronApp.page.getByRole('button', { name: 'Edit Sketch' }).click()
const child = tronApp.page
.locator('.segment-length-label-text')
.first()
.locator('xpath=..')
await child.dblclick()
const cmdBarSubmitButton = tronApp.page.getByRole('button', {
name: 'arrow right Continue',
})
await cmdBarSubmitButton.click()
await expectFixture(tronApp.page.locator('.cm-content')).toContainText(
'length001 = 15.3'
)
await expectFixture(tronApp.page.locator('.cm-content')).toContainText(
'|> angledLine([9, length001], %)'
)
await tronApp.page.getByRole('button', { name: 'Exit Sketch' }).click()
})
}
)
})

View File

@ -174,8 +174,13 @@ export const ClientSideScene = ({
const Overlays = () => {
const { context } = useModelingContext()
if (context.mouseState.type === 'isDragging') return null
// Set a large zIndex, the overlay for hover dropdown menu on line segments needs to render
// over the length labels on the line segments
return (
<div className="absolute inset-0 pointer-events-none">
<div
className="absolute inset-0 pointer-events-none"
style={{ zIndex: '99999999' }}
>
{Object.entries(context.segmentOverlays)
.filter((a) => a[1].visible)
.map(([pathToNodeString, overlay], index) => {

View File

@ -51,6 +51,7 @@ import {
defaultSourceRange,
sourceRangeFromRust,
resultIsOk,
SourceRange,
} from 'lang/wasm'
import {
engineCommandManager,
@ -102,6 +103,7 @@ import { Point3d } from 'wasm-lib/kcl/bindings/Point3d'
import { SegmentInputs } from 'lang/std/stdTypes'
import { Node } from 'wasm-lib/kcl/bindings/Node'
import { radToDeg } from 'three/src/math/MathUtils'
import { getArtifactFromRange, codeRefFromRange } from 'lang/std/artifactGraph'
type DraftSegment = 'line' | 'tangentialArcTo'
@ -584,11 +586,12 @@ export class SceneEntities {
)
let seg: Group
const _node1 = getNodeFromPath<CallExpression>(
const _node1 = getNodeFromPath<Node<CallExpression>>(
maybeModdedAst,
segPathToNode,
'CallExpression'
)
if (err(_node1)) return
const callExpName = _node1.node?.callee?.name
@ -611,6 +614,15 @@ export class SceneEntities {
from: segment.from,
to: segment.to,
}
const startRange = _node1.node.start
const endRange = _node1.node.end
const sourceRange: SourceRange = [startRange, endRange, true]
const selection: Selections = computeSelectionFromSourceRangeAndAST(
sourceRange,
maybeModdedAst
)
const result = initSegment({
prevSegment: sketch.paths[index - 1],
callExpName,
@ -623,6 +635,7 @@ export class SceneEntities {
theme: sceneInfra._theme,
isSelected,
sceneInfra,
selection,
})
if (err(result)) return
const { group: _group, updateOverlaysCallback } = result
@ -2351,3 +2364,27 @@ export function getQuaternionFromZAxis(zAxis: Vector3): Quaternion {
function massageFormats(a: Vec3Array | Point3d): Vector3 {
return isArray(a) ? new Vector3(a[0], a[1], a[2]) : new Vector3(a.x, a.y, a.z)
}
/**
* Given a SourceRange [x,y,boolean] create a Selections object which contains
* graphSelections with the artifact and codeRef.
* This can be passed to 'Set selection' to internally set the selection of the
* modelingMachine from code.
*/
function computeSelectionFromSourceRangeAndAST(
sourceRange: SourceRange,
ast: Node<Program>
): Selections {
const artifactGraph = engineCommandManager.artifactGraph
const artifact = getArtifactFromRange(sourceRange, artifactGraph) || undefined
const selection: Selections = {
graphSelections: [
{
artifact,
codeRef: codeRefFromRange(sourceRange, ast),
},
],
otherSelections: [],
}
return selection
}

View File

@ -229,7 +229,6 @@ export class SceneInfra {
const vector = new Vector3(0, 0, 0)
// Get the position of the object3D in world space
// console.log('arrowGroup', arrowGroup)
arrowGroup.getWorldPosition(vector)
// Project that position to screen space
@ -347,7 +346,6 @@ export class SceneInfra {
requestAnimationFrame(this.animate)
TWEEN.update() // This will update all tweens during the animation loop
if (!this.isFovAnimationInProgress) {
// console.log('animation frame', this.cameraControls.camera)
this.camControls.update()
this.renderer.render(this.scene, this.camControls.camera)
this.labelRenderer.render(this.scene, this.camControls.camera)
@ -434,7 +432,6 @@ export class SceneInfra {
if (!this.selected.hasBeenDragged && hasBeenDragged) {
this.selected.hasBeenDragged = true
// this is where we could fire a onDragStart event
// console.log('onDragStart', this.selected)
}
if (
hasBeenDragged &&

View File

@ -56,6 +56,8 @@ import { normaliseAngle, roundOff } from 'lib/utils'
import { SegmentOverlayPayload } from 'machines/modelingMachine'
import { SegmentInputs } from 'lang/std/stdTypes'
import { err } from 'lib/trap'
import { editorManager, sceneInfra } from 'lib/singletons'
import { Selections } from 'lib/selections'
interface CreateSegmentArgs {
input: SegmentInputs
@ -69,6 +71,7 @@ interface CreateSegmentArgs {
theme: Themes
isSelected?: boolean
sceneInfra: SceneInfra
selection?: Selections
}
interface UpdateSegmentArgs {
@ -118,6 +121,7 @@ class StraightSegment implements SegmentUtils {
isSelected = false,
sceneInfra,
prevSegment,
selection,
}) => {
if (input.type !== 'straight-segment')
return new Error('Invalid segment type')
@ -156,6 +160,7 @@ class StraightSegment implements SegmentUtils {
isSelected,
callExpName,
baseColor,
selection,
}
// All segment types get an extra segment handle,
@ -823,8 +828,37 @@ function createLengthIndicator({
lengthIndicatorText.innerText = roundOff(length).toString()
const lengthIndicatorWrapper = document.createElement('div')
// Double click workflow
lengthIndicatorWrapper.ondblclick = () => {
const selection = lengthIndicatorGroup.parent?.userData.selection
if (!selection) {
console.error('Unable to dimension segment when clicking the label.')
return
}
sceneInfra.modelingSend({
type: 'Set selection',
data: {
selectionType: 'singleCodeCursor',
selection: selection.graphSelections[0],
},
})
// Command Bar
editorManager.commandBarSend({
type: 'Find and select command',
data: {
name: 'Constrain length',
groupId: 'modeling',
argDefaultValues: {
selection,
},
},
})
}
// Style the elements
lengthIndicatorWrapper.style.position = 'absolute'
lengthIndicatorWrapper.style.pointerEvents = 'auto'
lengthIndicatorWrapper.appendChild(lengthIndicatorText)
const cssObject = new CSS2DObject(lengthIndicatorWrapper)
cssObject.name = SEGMENT_LENGTH_LABEL_TEXT

View File

@ -109,6 +109,7 @@ function DisplayObj({
setHasCursor(false)
}
}, [node.start, node.end, node.type])
return (
<pre
ref={ref}

View File

@ -625,7 +625,6 @@ export const ModelingMachineProvider = ({
}
const canShell = canShellSelection(selectionRanges)
console.log('canShellSelection', canShellSelection(selectionRanges))
if (err(canShell)) return false
return canShell
},

View File

@ -12,6 +12,7 @@ export const kclHighlight = styleTags({
'AddOp MultOp ExpOp': t.arithmeticOperator,
BangOp: t.logicOperator,
CompOp: t.compareOperator,
LogicOp: t.logicOperator,
'Equals Arrow': t.definitionOperator,
PipeOperator: t.controlOperator,
String: t.string,

View File

@ -5,6 +5,7 @@
mult @left
add @left
comp @left
logic @left
pipe @left
range
}
@ -40,7 +41,8 @@ expression[@isGroup=Expression] {
expression !add AddOp expression |
expression !mult MultOp expression |
expression !exp ExpOp expression |
expression !comp CompOp expression
expression !comp CompOp expression |
expression !logic LogicOp expression
} |
UnaryExpression { UnaryOp expression } |
ParenthesizedExpression { "(" expression ")" } |
@ -89,6 +91,7 @@ commaSep1NoTrailingComma<term> { term ("," term)* }
AddOp { "+" | "-" }
MultOp { "/" | "*" | "\\" }
ExpOp { "^" }
LogicOp { "|" | "&" }
BangOp { "!" }
CompOp { "==" | "!=" | "<=" | ">=" | "<" | ">" }
Equals { "=" }

View File

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

View File

@ -871,3 +871,21 @@ export function codeRefFromRange(range: SourceRange, ast: Program): CodeRef {
pathToNode: getNodePathFromSourceRange(ast, range),
}
}
/**
* Get an artifact from a code source range
*/
export function getArtifactFromRange(
range: SourceRange,
artifactGraph: ArtifactGraph
): Artifact | null {
for (const artifact of artifactGraph.values()) {
if ('codeRef' in artifact) {
const match =
artifact.codeRef.range[0] === range[0] &&
artifact.codeRef.range[1] === range[1]
if (match) return artifact
}
}
return null
}

View File

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

View File

@ -9,7 +9,7 @@ import { Selections } from 'lib/selections'
import { kclManager } from 'lib/singletons'
import { err } from 'lib/trap'
import { modelingMachine, SketchTool } from 'machines/modelingMachine'
import { revolveAxisValidator } from './validators'
import { loftValidator, revolveAxisValidator } from './validators'
type OutputFormat = Models['OutputFormat_type']
type OutputTypeKey = OutputFormat['type']
@ -297,6 +297,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
multiple: true,
required: true,
skip: false,
validation: loftValidator,
},
},
},

View File

@ -104,3 +104,52 @@ export const revolveAxisValidator = async ({
return 'Unable to revolve with selected axis'
}
}
export const loftValidator = async ({
data,
}: {
data: { [key: string]: Selections }
context: CommandBarContext
}): Promise<boolean | string> => {
if (!isSelections(data.selection)) {
return 'Unable to loft, selections are missing'
}
const { selection } = data
if (selection.graphSelections.some((s) => s.artifact?.type !== 'solid2D')) {
return 'Unable to loft, some selection are not solid2Ds'
}
const sectionIds = data.selection.graphSelections.flatMap((s) =>
s.artifact?.type === 'solid2D' ? s.artifact.pathId : []
)
if (sectionIds.length < 2) {
return 'Unable to loft, selection contains less than two solid2Ds'
}
const loftCommand = async () => {
// TODO: check what to do with these
const DEFAULT_V_DEGREE = 2
const DEFAULT_TOLERANCE = 2
const DEFAULT_BEZ_APPROXIMATE_RATIONAL = false
return await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
section_ids: sectionIds,
type: 'loft',
bez_approximate_rational: DEFAULT_BEZ_APPROXIMATE_RATIONAL,
tolerance: DEFAULT_TOLERANCE,
v_degree: DEFAULT_V_DEGREE,
},
})
}
const attempt = await dryRunWrapper(loftCommand)
if (attempt?.success) {
return true
} else {
// return error message for the toast
return 'Unable to loft with selected sketches'
}
}

View File

@ -779,6 +779,8 @@ fn rust_type_to_openapi_type(t: &str) -> String {
if t == "f64" {
return "number".to_string();
} else if t == "u32" {
return "integer".to_string();
} else if t == "str" {
return "string".to_string();
} else {
@ -803,7 +805,7 @@ fn generate_code_block_test(fn_name: &str, code_block: &str, index: usize) -> pr
quote! {
#[tokio::test(flavor = "multi_thread")]
async fn #test_name_mock() {
async fn #test_name_mock() -> miette::Result<()> {
let program = crate::Program::parse_no_errs(#code_block).unwrap();
let ctx = crate::ExecutorContext {
engine: std::sync::Arc::new(Box::new(crate::engine::conn_mock::EngineConnection::new().await.unwrap())),
@ -813,15 +815,33 @@ fn generate_code_block_test(fn_name: &str, code_block: &str, index: usize) -> pr
context_type: crate::execution::ContextType::Mock,
};
ctx.run(program.into(), &mut crate::ExecState::default()).await.unwrap();
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
return Err(miette::Report::new(crate::errors::Report {
error: e,
filename: format!("{}{}", #fn_name, #index),
kcl_source: #code_block.to_string(),
}));
}
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn #test_name() {
async fn #test_name() -> miette::Result<()> {
let code = #code_block;
// Note, `crate` must be kcl_lib
let result = crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm, None).await.unwrap();
let result = match crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm, None).await {
Err(crate::errors::ExecError::Kcl(e)) => {
return Err(miette::Report::new(crate::errors::Report {
error: e,
filename: format!("{}{}", #fn_name, #index),
kcl_source: #code_block.to_string(),
}));
}
Err(other_err)=> panic!("{}", other_err),
Ok(img) => img,
};
twenty_twenty::assert_image(&format!("tests/outputs/{}.png", #output_test_name_str), &result, 0.99);
Ok(())
}
}
}

View File

@ -1,7 +1,7 @@
#[cfg(test)]
mod test_examples_someFn {
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_someFn0() {
async fn test_mock_example_someFn0() -> miette::Result<()> {
let program = crate::Program::parse_no_errs("someFn()").unwrap();
let ctx = crate::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
@ -14,26 +14,43 @@ mod test_examples_someFn {
settings: Default::default(),
context_type: crate::execution::ContextType::Mock,
};
ctx.run(program.into(), &mut crate::ExecState::default())
.await
.unwrap();
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
return Err(miette::Report::new(crate::errors::Report {
error: e,
filename: format!("{}{}", "someFn", 0usize),
kcl_source: "someFn()".to_string(),
}));
}
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_someFn0() {
async fn kcl_test_example_someFn0() -> miette::Result<()> {
let code = "someFn()";
let result = crate::test_server::execute_and_snapshot(
let result = match crate::test_server::execute_and_snapshot(
code,
crate::settings::types::UnitLength::Mm,
None,
)
.await
.unwrap();
{
Err(crate::errors::ExecError::Kcl(e)) => {
return Err(miette::Report::new(crate::errors::Report {
error: e,
filename: format!("{}{}", "someFn", 0usize),
kcl_source: "someFn()".to_string(),
}));
}
Err(other_err) => panic!("{}", other_err),
Ok(img) => img,
};
twenty_twenty::assert_image(
&format!("tests/outputs/{}.png", "serial_test_example_someFn0"),
&result,
0.99,
);
Ok(())
}
}

View File

@ -1,7 +1,7 @@
#[cfg(test)]
mod test_examples_someFn {
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_someFn0() {
async fn test_mock_example_someFn0() -> miette::Result<()> {
let program = crate::Program::parse_no_errs("someFn()").unwrap();
let ctx = crate::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
@ -14,26 +14,43 @@ mod test_examples_someFn {
settings: Default::default(),
context_type: crate::execution::ContextType::Mock,
};
ctx.run(program.into(), &mut crate::ExecState::default())
.await
.unwrap();
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
return Err(miette::Report::new(crate::errors::Report {
error: e,
filename: format!("{}{}", "someFn", 0usize),
kcl_source: "someFn()".to_string(),
}));
}
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_someFn0() {
async fn kcl_test_example_someFn0() -> miette::Result<()> {
let code = "someFn()";
let result = crate::test_server::execute_and_snapshot(
let result = match crate::test_server::execute_and_snapshot(
code,
crate::settings::types::UnitLength::Mm,
None,
)
.await
.unwrap();
{
Err(crate::errors::ExecError::Kcl(e)) => {
return Err(miette::Report::new(crate::errors::Report {
error: e,
filename: format!("{}{}", "someFn", 0usize),
kcl_source: "someFn()".to_string(),
}));
}
Err(other_err) => panic!("{}", other_err),
Ok(img) => img,
};
twenty_twenty::assert_image(
&format!("tests/outputs/{}.png", "serial_test_example_someFn0"),
&result,
0.99,
);
Ok(())
}
}

View File

@ -1,7 +1,7 @@
#[cfg(test)]
mod test_examples_show {
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_show0() {
async fn test_mock_example_show0() -> miette::Result<()> {
let program =
crate::Program::parse_no_errs("This is another code block.\nyes sirrr.\nshow").unwrap();
let ctx = crate::ExecutorContext {
@ -15,30 +15,47 @@ mod test_examples_show {
settings: Default::default(),
context_type: crate::execution::ContextType::Mock,
};
ctx.run(program.into(), &mut crate::ExecState::default())
.await
.unwrap();
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
return Err(miette::Report::new(crate::errors::Report {
error: e,
filename: format!("{}{}", "show", 0usize),
kcl_source: "This is another code block.\nyes sirrr.\nshow".to_string(),
}));
}
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_show0() {
async fn kcl_test_example_show0() -> miette::Result<()> {
let code = "This is another code block.\nyes sirrr.\nshow";
let result = crate::test_server::execute_and_snapshot(
let result = match crate::test_server::execute_and_snapshot(
code,
crate::settings::types::UnitLength::Mm,
None,
)
.await
.unwrap();
{
Err(crate::errors::ExecError::Kcl(e)) => {
return Err(miette::Report::new(crate::errors::Report {
error: e,
filename: format!("{}{}", "show", 0usize),
kcl_source: "This is another code block.\nyes sirrr.\nshow".to_string(),
}));
}
Err(other_err) => panic!("{}", other_err),
Ok(img) => img,
};
twenty_twenty::assert_image(
&format!("tests/outputs/{}.png", "serial_test_example_show0"),
&result,
0.99,
);
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_show1() {
async fn test_mock_example_show1() -> miette::Result<()> {
let program =
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nshow").unwrap();
let ctx = crate::ExecutorContext {
@ -52,26 +69,43 @@ mod test_examples_show {
settings: Default::default(),
context_type: crate::execution::ContextType::Mock,
};
ctx.run(program.into(), &mut crate::ExecState::default())
.await
.unwrap();
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
return Err(miette::Report::new(crate::errors::Report {
error: e,
filename: format!("{}{}", "show", 1usize),
kcl_source: "This is code.\nIt does other shit.\nshow".to_string(),
}));
}
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_show1() {
async fn kcl_test_example_show1() -> miette::Result<()> {
let code = "This is code.\nIt does other shit.\nshow";
let result = crate::test_server::execute_and_snapshot(
let result = match crate::test_server::execute_and_snapshot(
code,
crate::settings::types::UnitLength::Mm,
None,
)
.await
.unwrap();
{
Err(crate::errors::ExecError::Kcl(e)) => {
return Err(miette::Report::new(crate::errors::Report {
error: e,
filename: format!("{}{}", "show", 1usize),
kcl_source: "This is code.\nIt does other shit.\nshow".to_string(),
}));
}
Err(other_err) => panic!("{}", other_err),
Ok(img) => img,
};
twenty_twenty::assert_image(
&format!("tests/outputs/{}.png", "serial_test_example_show1"),
&result,
0.99,
);
Ok(())
}
}

View File

@ -1,7 +1,7 @@
#[cfg(test)]
mod test_examples_show {
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_show0() {
async fn test_mock_example_show0() -> miette::Result<()> {
let program =
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nshow").unwrap();
let ctx = crate::ExecutorContext {
@ -15,26 +15,43 @@ mod test_examples_show {
settings: Default::default(),
context_type: crate::execution::ContextType::Mock,
};
ctx.run(program.into(), &mut crate::ExecState::default())
.await
.unwrap();
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
return Err(miette::Report::new(crate::errors::Report {
error: e,
filename: format!("{}{}", "show", 0usize),
kcl_source: "This is code.\nIt does other shit.\nshow".to_string(),
}));
}
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_show0() {
async fn kcl_test_example_show0() -> miette::Result<()> {
let code = "This is code.\nIt does other shit.\nshow";
let result = crate::test_server::execute_and_snapshot(
let result = match crate::test_server::execute_and_snapshot(
code,
crate::settings::types::UnitLength::Mm,
None,
)
.await
.unwrap();
{
Err(crate::errors::ExecError::Kcl(e)) => {
return Err(miette::Report::new(crate::errors::Report {
error: e,
filename: format!("{}{}", "show", 0usize),
kcl_source: "This is code.\nIt does other shit.\nshow".to_string(),
}));
}
Err(other_err) => panic!("{}", other_err),
Ok(img) => img,
};
twenty_twenty::assert_image(
&format!("tests/outputs/{}.png", "serial_test_example_show0"),
&result,
0.99,
);
Ok(())
}
}

View File

@ -1,7 +1,7 @@
#[cfg(test)]
mod test_examples_my_func {
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_my_func0() {
async fn test_mock_example_my_func0() -> miette::Result<()> {
let program =
crate::Program::parse_no_errs("This is another code block.\nyes sirrr.\nmyFunc")
.unwrap();
@ -16,30 +16,47 @@ mod test_examples_my_func {
settings: Default::default(),
context_type: crate::execution::ContextType::Mock,
};
ctx.run(program.into(), &mut crate::ExecState::default())
.await
.unwrap();
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
return Err(miette::Report::new(crate::errors::Report {
error: e,
filename: format!("{}{}", "my_func", 0usize),
kcl_source: "This is another code block.\nyes sirrr.\nmyFunc".to_string(),
}));
}
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_my_func0() {
async fn kcl_test_example_my_func0() -> miette::Result<()> {
let code = "This is another code block.\nyes sirrr.\nmyFunc";
let result = crate::test_server::execute_and_snapshot(
let result = match crate::test_server::execute_and_snapshot(
code,
crate::settings::types::UnitLength::Mm,
None,
)
.await
.unwrap();
{
Err(crate::errors::ExecError::Kcl(e)) => {
return Err(miette::Report::new(crate::errors::Report {
error: e,
filename: format!("{}{}", "my_func", 0usize),
kcl_source: "This is another code block.\nyes sirrr.\nmyFunc".to_string(),
}));
}
Err(other_err) => panic!("{}", other_err),
Ok(img) => img,
};
twenty_twenty::assert_image(
&format!("tests/outputs/{}.png", "serial_test_example_my_func0"),
&result,
0.99,
);
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_my_func1() {
async fn test_mock_example_my_func1() -> miette::Result<()> {
let program =
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nmyFunc").unwrap();
let ctx = crate::ExecutorContext {
@ -53,26 +70,43 @@ mod test_examples_my_func {
settings: Default::default(),
context_type: crate::execution::ContextType::Mock,
};
ctx.run(program.into(), &mut crate::ExecState::default())
.await
.unwrap();
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
return Err(miette::Report::new(crate::errors::Report {
error: e,
filename: format!("{}{}", "my_func", 1usize),
kcl_source: "This is code.\nIt does other shit.\nmyFunc".to_string(),
}));
}
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_my_func1() {
async fn kcl_test_example_my_func1() -> miette::Result<()> {
let code = "This is code.\nIt does other shit.\nmyFunc";
let result = crate::test_server::execute_and_snapshot(
let result = match crate::test_server::execute_and_snapshot(
code,
crate::settings::types::UnitLength::Mm,
None,
)
.await
.unwrap();
{
Err(crate::errors::ExecError::Kcl(e)) => {
return Err(miette::Report::new(crate::errors::Report {
error: e,
filename: format!("{}{}", "my_func", 1usize),
kcl_source: "This is code.\nIt does other shit.\nmyFunc".to_string(),
}));
}
Err(other_err) => panic!("{}", other_err),
Ok(img) => img,
};
twenty_twenty::assert_image(
&format!("tests/outputs/{}.png", "serial_test_example_my_func1"),
&result,
0.99,
);
Ok(())
}
}

View File

@ -1,7 +1,7 @@
#[cfg(test)]
mod test_examples_line_to {
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_line_to0() {
async fn test_mock_example_line_to0() -> miette::Result<()> {
let program =
crate::Program::parse_no_errs("This is another code block.\nyes sirrr.\nlineTo")
.unwrap();
@ -16,30 +16,47 @@ mod test_examples_line_to {
settings: Default::default(),
context_type: crate::execution::ContextType::Mock,
};
ctx.run(program.into(), &mut crate::ExecState::default())
.await
.unwrap();
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
return Err(miette::Report::new(crate::errors::Report {
error: e,
filename: format!("{}{}", "line_to", 0usize),
kcl_source: "This is another code block.\nyes sirrr.\nlineTo".to_string(),
}));
}
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_line_to0() {
async fn kcl_test_example_line_to0() -> miette::Result<()> {
let code = "This is another code block.\nyes sirrr.\nlineTo";
let result = crate::test_server::execute_and_snapshot(
let result = match crate::test_server::execute_and_snapshot(
code,
crate::settings::types::UnitLength::Mm,
None,
)
.await
.unwrap();
{
Err(crate::errors::ExecError::Kcl(e)) => {
return Err(miette::Report::new(crate::errors::Report {
error: e,
filename: format!("{}{}", "line_to", 0usize),
kcl_source: "This is another code block.\nyes sirrr.\nlineTo".to_string(),
}));
}
Err(other_err) => panic!("{}", other_err),
Ok(img) => img,
};
twenty_twenty::assert_image(
&format!("tests/outputs/{}.png", "serial_test_example_line_to0"),
&result,
0.99,
);
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_line_to1() {
async fn test_mock_example_line_to1() -> miette::Result<()> {
let program =
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nlineTo").unwrap();
let ctx = crate::ExecutorContext {
@ -53,26 +70,43 @@ mod test_examples_line_to {
settings: Default::default(),
context_type: crate::execution::ContextType::Mock,
};
ctx.run(program.into(), &mut crate::ExecState::default())
.await
.unwrap();
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
return Err(miette::Report::new(crate::errors::Report {
error: e,
filename: format!("{}{}", "line_to", 1usize),
kcl_source: "This is code.\nIt does other shit.\nlineTo".to_string(),
}));
}
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_line_to1() {
async fn kcl_test_example_line_to1() -> miette::Result<()> {
let code = "This is code.\nIt does other shit.\nlineTo";
let result = crate::test_server::execute_and_snapshot(
let result = match crate::test_server::execute_and_snapshot(
code,
crate::settings::types::UnitLength::Mm,
None,
)
.await
.unwrap();
{
Err(crate::errors::ExecError::Kcl(e)) => {
return Err(miette::Report::new(crate::errors::Report {
error: e,
filename: format!("{}{}", "line_to", 1usize),
kcl_source: "This is code.\nIt does other shit.\nlineTo".to_string(),
}));
}
Err(other_err) => panic!("{}", other_err),
Ok(img) => img,
};
twenty_twenty::assert_image(
&format!("tests/outputs/{}.png", "serial_test_example_line_to1"),
&result,
0.99,
);
Ok(())
}
}

View File

@ -1,7 +1,7 @@
#[cfg(test)]
mod test_examples_min {
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_min0() {
async fn test_mock_example_min0() -> miette::Result<()> {
let program =
crate::Program::parse_no_errs("This is another code block.\nyes sirrr.\nmin").unwrap();
let ctx = crate::ExecutorContext {
@ -15,30 +15,47 @@ mod test_examples_min {
settings: Default::default(),
context_type: crate::execution::ContextType::Mock,
};
ctx.run(program.into(), &mut crate::ExecState::default())
.await
.unwrap();
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
return Err(miette::Report::new(crate::errors::Report {
error: e,
filename: format!("{}{}", "min", 0usize),
kcl_source: "This is another code block.\nyes sirrr.\nmin".to_string(),
}));
}
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_min0() {
async fn kcl_test_example_min0() -> miette::Result<()> {
let code = "This is another code block.\nyes sirrr.\nmin";
let result = crate::test_server::execute_and_snapshot(
let result = match crate::test_server::execute_and_snapshot(
code,
crate::settings::types::UnitLength::Mm,
None,
)
.await
.unwrap();
{
Err(crate::errors::ExecError::Kcl(e)) => {
return Err(miette::Report::new(crate::errors::Report {
error: e,
filename: format!("{}{}", "min", 0usize),
kcl_source: "This is another code block.\nyes sirrr.\nmin".to_string(),
}));
}
Err(other_err) => panic!("{}", other_err),
Ok(img) => img,
};
twenty_twenty::assert_image(
&format!("tests/outputs/{}.png", "serial_test_example_min0"),
&result,
0.99,
);
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_min1() {
async fn test_mock_example_min1() -> miette::Result<()> {
let program =
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nmin").unwrap();
let ctx = crate::ExecutorContext {
@ -52,26 +69,43 @@ mod test_examples_min {
settings: Default::default(),
context_type: crate::execution::ContextType::Mock,
};
ctx.run(program.into(), &mut crate::ExecState::default())
.await
.unwrap();
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
return Err(miette::Report::new(crate::errors::Report {
error: e,
filename: format!("{}{}", "min", 1usize),
kcl_source: "This is code.\nIt does other shit.\nmin".to_string(),
}));
}
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_min1() {
async fn kcl_test_example_min1() -> miette::Result<()> {
let code = "This is code.\nIt does other shit.\nmin";
let result = crate::test_server::execute_and_snapshot(
let result = match crate::test_server::execute_and_snapshot(
code,
crate::settings::types::UnitLength::Mm,
None,
)
.await
.unwrap();
{
Err(crate::errors::ExecError::Kcl(e)) => {
return Err(miette::Report::new(crate::errors::Report {
error: e,
filename: format!("{}{}", "min", 1usize),
kcl_source: "This is code.\nIt does other shit.\nmin".to_string(),
}));
}
Err(other_err) => panic!("{}", other_err),
Ok(img) => img,
};
twenty_twenty::assert_image(
&format!("tests/outputs/{}.png", "serial_test_example_min1"),
&result,
0.99,
);
Ok(())
}
}

View File

@ -1,7 +1,7 @@
#[cfg(test)]
mod test_examples_show {
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_show0() {
async fn test_mock_example_show0() -> miette::Result<()> {
let program =
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nshow").unwrap();
let ctx = crate::ExecutorContext {
@ -15,26 +15,43 @@ mod test_examples_show {
settings: Default::default(),
context_type: crate::execution::ContextType::Mock,
};
ctx.run(program.into(), &mut crate::ExecState::default())
.await
.unwrap();
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
return Err(miette::Report::new(crate::errors::Report {
error: e,
filename: format!("{}{}", "show", 0usize),
kcl_source: "This is code.\nIt does other shit.\nshow".to_string(),
}));
}
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_show0() {
async fn kcl_test_example_show0() -> miette::Result<()> {
let code = "This is code.\nIt does other shit.\nshow";
let result = crate::test_server::execute_and_snapshot(
let result = match crate::test_server::execute_and_snapshot(
code,
crate::settings::types::UnitLength::Mm,
None,
)
.await
.unwrap();
{
Err(crate::errors::ExecError::Kcl(e)) => {
return Err(miette::Report::new(crate::errors::Report {
error: e,
filename: format!("{}{}", "show", 0usize),
kcl_source: "This is code.\nIt does other shit.\nshow".to_string(),
}));
}
Err(other_err) => panic!("{}", other_err),
Ok(img) => img,
};
twenty_twenty::assert_image(
&format!("tests/outputs/{}.png", "serial_test_example_show0"),
&result,
0.99,
);
Ok(())
}
}

View File

@ -1,7 +1,7 @@
#[cfg(test)]
mod test_examples_import {
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_import0() {
async fn test_mock_example_import0() -> miette::Result<()> {
let program =
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nimport").unwrap();
let ctx = crate::ExecutorContext {
@ -15,26 +15,43 @@ mod test_examples_import {
settings: Default::default(),
context_type: crate::execution::ContextType::Mock,
};
ctx.run(program.into(), &mut crate::ExecState::default())
.await
.unwrap();
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
return Err(miette::Report::new(crate::errors::Report {
error: e,
filename: format!("{}{}", "import", 0usize),
kcl_source: "This is code.\nIt does other shit.\nimport".to_string(),
}));
}
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_import0() {
async fn kcl_test_example_import0() -> miette::Result<()> {
let code = "This is code.\nIt does other shit.\nimport";
let result = crate::test_server::execute_and_snapshot(
let result = match crate::test_server::execute_and_snapshot(
code,
crate::settings::types::UnitLength::Mm,
None,
)
.await
.unwrap();
{
Err(crate::errors::ExecError::Kcl(e)) => {
return Err(miette::Report::new(crate::errors::Report {
error: e,
filename: format!("{}{}", "import", 0usize),
kcl_source: "This is code.\nIt does other shit.\nimport".to_string(),
}));
}
Err(other_err) => panic!("{}", other_err),
Ok(img) => img,
};
twenty_twenty::assert_image(
&format!("tests/outputs/{}.png", "serial_test_example_import0"),
&result,
0.99,
);
Ok(())
}
}

View File

@ -1,7 +1,7 @@
#[cfg(test)]
mod test_examples_import {
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_import0() {
async fn test_mock_example_import0() -> miette::Result<()> {
let program =
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nimport").unwrap();
let ctx = crate::ExecutorContext {
@ -15,26 +15,43 @@ mod test_examples_import {
settings: Default::default(),
context_type: crate::execution::ContextType::Mock,
};
ctx.run(program.into(), &mut crate::ExecState::default())
.await
.unwrap();
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
return Err(miette::Report::new(crate::errors::Report {
error: e,
filename: format!("{}{}", "import", 0usize),
kcl_source: "This is code.\nIt does other shit.\nimport".to_string(),
}));
}
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_import0() {
async fn kcl_test_example_import0() -> miette::Result<()> {
let code = "This is code.\nIt does other shit.\nimport";
let result = crate::test_server::execute_and_snapshot(
let result = match crate::test_server::execute_and_snapshot(
code,
crate::settings::types::UnitLength::Mm,
None,
)
.await
.unwrap();
{
Err(crate::errors::ExecError::Kcl(e)) => {
return Err(miette::Report::new(crate::errors::Report {
error: e,
filename: format!("{}{}", "import", 0usize),
kcl_source: "This is code.\nIt does other shit.\nimport".to_string(),
}));
}
Err(other_err) => panic!("{}", other_err),
Ok(img) => img,
};
twenty_twenty::assert_image(
&format!("tests/outputs/{}.png", "serial_test_example_import0"),
&result,
0.99,
);
Ok(())
}
}

View File

@ -1,7 +1,7 @@
#[cfg(test)]
mod test_examples_import {
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_import0() {
async fn test_mock_example_import0() -> miette::Result<()> {
let program =
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nimport").unwrap();
let ctx = crate::ExecutorContext {
@ -15,26 +15,43 @@ mod test_examples_import {
settings: Default::default(),
context_type: crate::execution::ContextType::Mock,
};
ctx.run(program.into(), &mut crate::ExecState::default())
.await
.unwrap();
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
return Err(miette::Report::new(crate::errors::Report {
error: e,
filename: format!("{}{}", "import", 0usize),
kcl_source: "This is code.\nIt does other shit.\nimport".to_string(),
}));
}
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_import0() {
async fn kcl_test_example_import0() -> miette::Result<()> {
let code = "This is code.\nIt does other shit.\nimport";
let result = crate::test_server::execute_and_snapshot(
let result = match crate::test_server::execute_and_snapshot(
code,
crate::settings::types::UnitLength::Mm,
None,
)
.await
.unwrap();
{
Err(crate::errors::ExecError::Kcl(e)) => {
return Err(miette::Report::new(crate::errors::Report {
error: e,
filename: format!("{}{}", "import", 0usize),
kcl_source: "This is code.\nIt does other shit.\nimport".to_string(),
}));
}
Err(other_err) => panic!("{}", other_err),
Ok(img) => img,
};
twenty_twenty::assert_image(
&format!("tests/outputs/{}.png", "serial_test_example_import0"),
&result,
0.99,
);
Ok(())
}
}

View File

@ -1,7 +1,7 @@
#[cfg(test)]
mod test_examples_show {
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_show0() {
async fn test_mock_example_show0() -> miette::Result<()> {
let program =
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nshow").unwrap();
let ctx = crate::ExecutorContext {
@ -15,26 +15,43 @@ mod test_examples_show {
settings: Default::default(),
context_type: crate::execution::ContextType::Mock,
};
ctx.run(program.into(), &mut crate::ExecState::default())
.await
.unwrap();
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
return Err(miette::Report::new(crate::errors::Report {
error: e,
filename: format!("{}{}", "show", 0usize),
kcl_source: "This is code.\nIt does other shit.\nshow".to_string(),
}));
}
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_show0() {
async fn kcl_test_example_show0() -> miette::Result<()> {
let code = "This is code.\nIt does other shit.\nshow";
let result = crate::test_server::execute_and_snapshot(
let result = match crate::test_server::execute_and_snapshot(
code,
crate::settings::types::UnitLength::Mm,
None,
)
.await
.unwrap();
{
Err(crate::errors::ExecError::Kcl(e)) => {
return Err(miette::Report::new(crate::errors::Report {
error: e,
filename: format!("{}{}", "show", 0usize),
kcl_source: "This is code.\nIt does other shit.\nshow".to_string(),
}));
}
Err(other_err) => panic!("{}", other_err),
Ok(img) => img,
};
twenty_twenty::assert_image(
&format!("tests/outputs/{}.png", "serial_test_example_show0"),
&result,
0.99,
);
Ok(())
}
}

View File

@ -1,7 +1,7 @@
#[cfg(test)]
mod test_examples_some_function {
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_some_function0() {
async fn test_mock_example_some_function0() -> miette::Result<()> {
let program = crate::Program::parse_no_errs("someFunction()").unwrap();
let ctx = crate::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
@ -14,26 +14,43 @@ mod test_examples_some_function {
settings: Default::default(),
context_type: crate::execution::ContextType::Mock,
};
ctx.run(program.into(), &mut crate::ExecState::default())
.await
.unwrap();
if let Err(e) = ctx.run(program.into(), &mut crate::ExecState::new()).await {
return Err(miette::Report::new(crate::errors::Report {
error: e,
filename: format!("{}{}", "some_function", 0usize),
kcl_source: "someFunction()".to_string(),
}));
}
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_some_function0() {
async fn kcl_test_example_some_function0() -> miette::Result<()> {
let code = "someFunction()";
let result = crate::test_server::execute_and_snapshot(
let result = match crate::test_server::execute_and_snapshot(
code,
crate::settings::types::UnitLength::Mm,
None,
)
.await
.unwrap();
{
Err(crate::errors::ExecError::Kcl(e)) => {
return Err(miette::Report::new(crate::errors::Report {
error: e,
filename: format!("{}{}", "some_function", 0usize),
kcl_source: "someFunction()".to_string(),
}));
}
Err(other_err) => panic!("{}", other_err),
Ok(img) => img,
};
twenty_twenty::assert_image(
&format!("tests/outputs/{}.png", "serial_test_example_some_function0"),
&result,
0.99,
);
Ok(())
}
}

View File

@ -164,7 +164,7 @@ async fn snapshot_endpoint(body: Bytes, state: ExecutorContext) -> Response<Body
};
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.
// There's no actual KCL associated with this reset_scene call.
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(
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();
Ok(result)

View File

@ -604,24 +604,6 @@ fn clean_function_name(name: &str) -> String {
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.
fn recurse_and_create_references(
name: &str,
@ -655,24 +637,6 @@ fn recurse_and_create_references(
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();
// If we have an object iterate over the properties and recursively create references.

View File

@ -353,7 +353,6 @@ pub struct CompilationError {
}
impl CompilationError {
#[allow(dead_code)]
pub(crate) fn err(source_range: SourceRange, message: impl ToString) -> CompilationError {
CompilationError {
source_range,

View File

@ -0,0 +1,73 @@
//! Data on available annotations.
use super::kcl_value::{UnitAngle, UnitLen};
use crate::{
errors::KclErrorDetails,
parsing::ast::types::{Expr, Node, NonCodeValue, ObjectProperty},
KclError, SourceRange,
};
pub(super) const SETTINGS: &str = "settings";
pub(super) const SETTINGS_UNIT_LENGTH: &str = "defaultLengthUnit";
pub(super) const SETTINGS_UNIT_ANGLE: &str = "defaultAngleUnit";
pub(super) fn expect_properties<'a>(
for_key: &'static str,
annotation: &'a NonCodeValue,
source_range: SourceRange,
) -> Result<&'a [Node<ObjectProperty>], KclError> {
match annotation {
NonCodeValue::Annotation { name, properties } => {
assert_eq!(name.name, for_key);
Ok(&**properties.as_ref().ok_or_else(|| {
KclError::Semantic(KclErrorDetails {
message: format!("Empty `{for_key}` annotation"),
source_ranges: vec![source_range],
})
})?)
}
_ => unreachable!(),
}
}
pub(super) fn expect_ident(expr: &Expr) -> Result<&str, KclError> {
match expr {
Expr::Identifier(id) => Ok(&id.name),
e => Err(KclError::Semantic(KclErrorDetails {
message: "Unexpected settings value, expected a simple name, e.g., `mm`".to_owned(),
source_ranges: vec![e.into()],
})),
}
}
impl UnitLen {
pub(super) fn from_str(s: &str, source_range: SourceRange) -> Result<Self, KclError> {
match s {
"mm" => Ok(UnitLen::Mm),
"cm" => Ok(UnitLen::Cm),
"m" => Ok(UnitLen::M),
"inch" | "in" => Ok(UnitLen::Inches),
"ft" => Ok(UnitLen::Feet),
"yd" => Ok(UnitLen::Yards),
value => Err(KclError::Semantic(KclErrorDetails {
message: format!(
"Unexpected settings value: `{value}`; expected one of `mm`, `cm`, `m`, `inch`, `ft`, `yd`"
),
source_ranges: vec![source_range],
})),
}
}
}
impl UnitAngle {
pub(super) fn from_str(s: &str, source_range: SourceRange) -> Result<Self, KclError> {
match s {
"deg" => Ok(UnitAngle::Degrees),
"rad" => Ok(UnitAngle::Radians),
value => Err(KclError::Semantic(KclErrorDetails {
message: format!("Unexpected settings value: `{value}`; expected one of `deg`, `rad`"),
source_ranges: vec![source_range],
})),
}
}
}

View File

@ -29,7 +29,7 @@ impl BinaryPart {
match self {
BinaryPart::Literal(literal) => Ok(literal.into()),
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())
}
BinaryPart::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, ctx).await,
@ -47,7 +47,7 @@ impl Node<MemberExpression> {
let array = match &self.object {
MemberObject::MemberExpression(member_expr) => member_expr.get_result(exec_state)?,
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()
}
};
@ -75,7 +75,7 @@ impl Node<MemberExpression> {
// TODO: Don't use recursion here, use a loop.
MemberObject::MemberExpression(member_expr) => member_expr.get_result(exec_state)?,
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()
}
};
@ -170,6 +170,42 @@ impl Node<BinaryExpression> {
}
}
// Check if we are doing logical operations on booleans.
if self.operator == BinaryOperator::Or || self.operator == BinaryOperator::And {
let KclValue::Bool {
value: left_value,
meta: _,
} = left_value
else {
return Err(KclError::Semantic(KclErrorDetails {
message: format!(
"Cannot apply logical operator to non-boolean value: {}",
left_value.human_friendly_type()
),
source_ranges: vec![self.left.clone().into()],
}));
};
let KclValue::Bool {
value: right_value,
meta: _,
} = right_value
else {
return Err(KclError::Semantic(KclErrorDetails {
message: format!(
"Cannot apply logical operator to non-boolean value: {}",
right_value.human_friendly_type()
),
source_ranges: vec![self.right.clone().into()],
}));
};
let raw_value = match self.operator {
BinaryOperator::Or => left_value || right_value,
BinaryOperator::And => left_value && right_value,
_ => unreachable!(),
};
return Ok(KclValue::Bool { value: raw_value, meta });
}
let left = parse_number_as_f64(&left_value, self.left.clone().into())?;
let right = parse_number_as_f64(&right_value, self.right.clone().into())?;
@ -222,6 +258,7 @@ impl Node<BinaryExpression> {
value: left == right,
meta,
},
BinaryOperator::And | BinaryOperator::Or => unreachable!(),
};
Ok(value)
@ -310,11 +347,11 @@ pub(crate) async fn execute_pipe_body(
// Now that we've evaluated the first child expression in the pipeline, following child expressions
// 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.
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.
let result = inner_execute_pipe_body(exec_state, body, ctx).await;
// Restore the previous pipe value.
exec_state.pipe_value = previous_pipe_value;
exec_state.mod_local.pipe_value = previous_pipe_value;
result
}
@ -340,10 +377,10 @@ async fn inner_execute_pipe_body(
let output = ctx
.execute_expr(expression, exec_state, &metadata, StatementKind::Expression)
.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.
let final_output = exec_state.pipe_value.take().unwrap();
let final_output = exec_state.mod_local.pipe_value.take().unwrap();
Ok(final_output)
}
@ -384,6 +421,7 @@ impl Node<CallExpressionKw> {
},
self.into(),
ctx.clone(),
exec_state.mod_local.pipe_value.clone().map(Arg::synthetic),
);
match ctx.stdlib.get_either(fn_name) {
FunctionKind::Core(func) => {
@ -417,7 +455,7 @@ impl Node<CallExpressionKw> {
// before running, and we will likely want to use the
// return value. The call takes ownership of the args,
// so we need to build the op before the call.
exec_state.operations.push(op);
exec_state.mod_local.operations.push(op);
}
result
};
@ -431,8 +469,8 @@ impl Node<CallExpressionKw> {
let source_range = SourceRange::from(self);
// Clone the function so that we can use a mutable reference to
// exec_state.
let func = exec_state.memory.get(fn_name, source_range)?.clone();
let fn_dynamic_state = exec_state.dynamic_state.merge(&exec_state.memory);
let func = exec_state.memory().get(fn_name, source_range)?.clone();
let fn_dynamic_state = exec_state.mod_local.dynamic_state.merge(exec_state.memory());
// Track call operation.
let op_labeled_args = args
@ -441,16 +479,20 @@ impl Node<CallExpressionKw> {
.iter()
.map(|(k, v)| (k.clone(), OpArg::new(v.source_range)))
.collect();
exec_state.operations.push(Operation::UserDefinedFunctionCall {
name: Some(fn_name.clone()),
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,
});
exec_state
.mod_local
.operations
.push(Operation::UserDefinedFunctionCall {
name: Some(fn_name.clone()),
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 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_kw(args, exec_state, ctx.clone(), callsite)
.await
@ -459,7 +501,7 @@ impl Node<CallExpressionKw> {
// TODO currently ignored by the frontend
e.add_source_ranges(vec![source_range])
});
exec_state.dynamic_state = previous_dynamic_state;
exec_state.mod_local.dynamic_state = previous_dynamic_state;
result?
};
@ -476,7 +518,10 @@ impl Node<CallExpressionKw> {
})?;
// Track return operation.
exec_state.operations.push(Operation::UserDefinedFunctionReturn);
exec_state
.mod_local
.operations
.push(Operation::UserDefinedFunctionReturn);
Ok(result)
}
@ -525,7 +570,12 @@ impl Node<CallExpression> {
};
// Attempt to call the function.
let args = crate::std::Args::new(fn_args, self.into(), ctx.clone());
let args = crate::std::Args::new(
fn_args,
self.into(),
ctx.clone(),
exec_state.mod_local.pipe_value.clone().map(Arg::synthetic),
);
let result = {
// Don't early-return in this block.
let result = func.std_lib_fn()(exec_state, args).await;
@ -537,7 +587,7 @@ impl Node<CallExpression> {
// before running, and we will likely want to use the
// return value. The call takes ownership of the args,
// so we need to build the op before the call.
exec_state.operations.push(op);
exec_state.mod_local.operations.push(op);
}
result
};
@ -551,27 +601,31 @@ impl Node<CallExpression> {
let source_range = SourceRange::from(self);
// Clone the function so that we can use a mutable reference to
// exec_state.
let func = exec_state.memory.get(fn_name, source_range)?.clone();
let fn_dynamic_state = exec_state.dynamic_state.merge(&exec_state.memory);
let func = exec_state.memory().get(fn_name, source_range)?.clone();
let fn_dynamic_state = exec_state.mod_local.dynamic_state.merge(exec_state.memory());
// Track call operation.
exec_state.operations.push(Operation::UserDefinedFunctionCall {
name: Some(fn_name.clone()),
function_source_range: func.function_def_source_range().unwrap_or_default(),
unlabeled_arg: None,
// TODO: Add the arguments for legacy positional parameters.
labeled_args: Default::default(),
source_range: callsite,
});
exec_state
.mod_local
.operations
.push(Operation::UserDefinedFunctionCall {
name: Some(fn_name.clone()),
function_source_range: func.function_def_source_range().unwrap_or_default(),
unlabeled_arg: None,
// TODO: Add the arguments for legacy positional parameters.
labeled_args: Default::default(),
source_range: callsite,
});
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| {
// Add the call expression to the source ranges.
// TODO currently ignored by the frontend
e.add_source_ranges(vec![source_range])
});
exec_state.dynamic_state = previous_dynamic_state;
exec_state.mod_local.dynamic_state = previous_dynamic_state;
result?
};
@ -588,7 +642,10 @@ impl Node<CallExpression> {
})?;
// Track return operation.
exec_state.operations.push(Operation::UserDefinedFunctionReturn);
exec_state
.mod_local
.operations
.push(Operation::UserDefinedFunctionReturn);
Ok(result)
}
@ -604,7 +661,7 @@ fn update_memory_for_tags_of_geometry(result: &mut KclValue, exec_state: &mut Ex
match result {
KclValue::Sketch { value: ref mut sketch } => {
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) => {
@ -642,7 +699,7 @@ fn update_memory_for_tags_of_geometry(result: &mut KclValue, exec_state: &mut Ex
info.sketch = solid.id;
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.
solid.sketch.tags.insert(tag.name.clone(), t);
@ -650,11 +707,8 @@ fn update_memory_for_tags_of_geometry(result: &mut KclValue, exec_state: &mut Ex
}
// Find the stale sketch in memory and update it.
if let Some(current_env) = exec_state
.memory
.environments
.get_mut(exec_state.memory.current_env.index())
{
let cur_env_index = exec_state.memory().current_env.index();
if let Some(current_env) = exec_state.mut_memory().environments.get_mut(cur_env_index) {
current_env.update_sketch_tags(&solid.sketch);
}
}
@ -673,7 +727,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())
}
@ -868,7 +924,7 @@ impl Property {
Ok(Property::String(name.to_string()))
} else {
// 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)
}
}

View File

@ -8,9 +8,12 @@ use crate::{
errors::KclErrorDetails,
exec::{ProgramMemory, Sketch},
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},
token::NumericSuffix,
},
std::{args::Arg, FnAsArg},
ExecState, ExecutorContext, KclError, SourceRange,
ExecState, ExecutorContext, KclError, ModuleId, SourceRange,
};
pub type KclObjectFields = HashMap<String, KclValue>;
@ -84,6 +87,11 @@ pub enum KclValue {
#[serde(rename = "__meta")]
meta: Vec<Metadata>,
},
Module {
value: ModuleId,
#[serde(rename = "__meta")]
meta: Vec<Metadata>,
},
KclNone {
value: KclNone,
#[serde(rename = "__meta")]
@ -143,6 +151,7 @@ impl From<KclValue> for Vec<SourceRange> {
KclValue::String { meta, .. } => to_vec_sr(&meta),
KclValue::Array { 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::KclNone { meta, .. } => to_vec_sr(&meta),
}
@ -173,6 +182,7 @@ impl From<&KclValue> for Vec<SourceRange> {
KclValue::Uuid { meta, .. } => to_vec_sr(meta),
KclValue::Array { 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),
}
}
@ -198,6 +208,7 @@ impl KclValue {
KclValue::Solids { value } => value.iter().flat_map(|sketch| &sketch.meta).copied().collect(),
KclValue::ImportedGeometry(x) => x.meta.clone(),
KclValue::Function { meta, .. } => meta.clone(),
KclValue::Module { meta, .. } => meta.clone(),
KclValue::KclNone { meta, .. } => meta.clone(),
}
}
@ -263,6 +274,7 @@ impl KclValue {
KclValue::String { .. } => "string (text)",
KclValue::Array { .. } => "array (list)",
KclValue::Object { .. } => "object",
KclValue::Module { .. } => "module",
KclValue::KclNone { .. } => "None",
}
}
@ -552,3 +564,52 @@ impl KclValue {
}
}
}
// TODO called UnitLen so as not to clash with UnitLength in settings)
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)]
#[ts(export)]
#[serde(tag = "type")]
pub enum UnitLen {
Mm,
Cm,
M,
Inches,
Feet,
Yards,
}
impl TryFrom<NumericSuffix> for UnitLen {
type Error = ();
fn try_from(suffix: NumericSuffix) -> std::result::Result<Self, Self::Error> {
match suffix {
NumericSuffix::Mm => Ok(Self::Mm),
NumericSuffix::Cm => Ok(Self::Cm),
NumericSuffix::M => Ok(Self::M),
NumericSuffix::Inch => Ok(Self::Inches),
NumericSuffix::Ft => Ok(Self::Feet),
NumericSuffix::Yd => Ok(Self::Yards),
_ => Err(()),
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)]
#[ts(export)]
#[serde(tag = "type")]
pub enum UnitAngle {
Degrees,
Radians,
}
impl TryFrom<NumericSuffix> for UnitAngle {
type Error = ();
fn try_from(suffix: NumericSuffix) -> std::result::Result<Self, Self::Error> {
match suffix {
NumericSuffix::Deg => Ok(Self::Degrees),
NumericSuffix::Rad => Ok(Self::Radians),
_ => Err(()),
}
}
}

View File

@ -22,7 +22,9 @@ type Point3D = kcmc::shared::Point3d<f64>;
pub use function_param::FunctionParam;
pub use kcl_value::{KclObjectFields, KclValue};
use uuid::Uuid;
mod annotations;
pub(crate) mod cache;
mod cad_op;
mod exec_ast;
@ -35,7 +37,8 @@ use crate::{
execution::cache::{CacheInformation, CacheResult},
fs::{FileManager, FileSystem},
parsing::ast::types::{
BodyItem, Expr, FunctionExpression, ImportSelector, ItemVisibility, Node, NodeRef, TagDeclarator, TagNode,
BodyItem, Expr, FunctionExpression, ImportSelector, ItemVisibility, Node, NodeRef, NonCodeValue,
Program as AstProgram, TagDeclarator, TagNode,
},
settings::types::UnitLength,
source_range::{ModuleId, SourceRange},
@ -47,14 +50,32 @@ use crate::{
pub use cad_op::Operation;
/// 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)]
#[serde(rename_all = "camelCase")]
pub struct ExecState {
/// Program variable bindings.
pub memory: ProgramMemory,
pub global: GlobalState,
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.
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.
pub dynamic_state: DynamicState,
/// The current value of the pipe operator returned from the previous
@ -65,29 +86,156 @@ pub struct ExecState {
/// The stack of import statements for detecting circular module imports.
/// If this is empty, we're not currently executing an import statement.
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
/// the Feature Tree.
pub operations: Vec<Operation>,
/// Settings specified from annotations.
pub settings: MetaSettings,
}
impl Default for ExecState {
fn default() -> Self {
Self::new()
}
}
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.
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 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;
new_module_id
});
if is_new {
let module_info = ModuleInfo { id, path };
self.module_infos.insert(id, module_info);
let source = ctxt.fs.read_to_string(&path, source_range).await?;
// 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
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct MetaSettings {
pub default_length_units: kcl_value::UnitLen,
pub default_angle_units: kcl_value::UnitAngle,
}
impl Default for MetaSettings {
fn default() -> Self {
MetaSettings {
default_length_units: kcl_value::UnitLen::Mm,
default_angle_units: kcl_value::UnitAngle::Degrees,
}
}
}
impl MetaSettings {
fn update_from_annotation(&mut self, annotation: &NonCodeValue, source_range: SourceRange) -> Result<(), KclError> {
let properties = annotations::expect_properties(annotations::SETTINGS, annotation, source_range)?;
for p in properties {
match &*p.inner.key.name {
annotations::SETTINGS_UNIT_LENGTH => {
let value = annotations::expect_ident(&p.inner.value)?;
let value = kcl_value::UnitLen::from_str(value, source_range)?;
self.default_length_units = value;
}
annotations::SETTINGS_UNIT_ANGLE => {
let value = annotations::expect_ident(&p.inner.value)?;
let value = kcl_value::UnitAngle::from_str(value, source_range)?;
self.default_angle_units = value;
}
name => {
return Err(KclError::Semantic(KclErrorDetails {
message: format!(
"Unexpected settings key: `{name}`; expected one of `{}`, `{}`",
annotations::SETTINGS_UNIT_LENGTH,
annotations::SETTINGS_UNIT_ANGLE
),
source_ranges: vec![source_range],
}))
}
}
}
Ok(())
}
}
@ -159,6 +307,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.
/// 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.
@ -274,18 +429,14 @@ pub struct DynamicState {
}
impl DynamicState {
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn merge(&self, memory: &ProgramMemory) -> Self {
fn merge(&self, memory: &ProgramMemory) -> Self {
let mut merged = self.clone();
merged.append(memory);
merged
}
pub fn append(&mut self, memory: &ProgramMemory) {
fn append(&mut self, memory: &ProgramMemory) {
for env in &memory.environments {
for item in env.bindings.values() {
if let KclValue::Solid(eg) = item {
@ -295,7 +446,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
.iter()
.flat_map(|eg| {
@ -553,7 +704,7 @@ pub struct Plane {
impl Plane {
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 {
crate::std::sketch::PlaneData::XY => Plane {
id,
@ -1001,13 +1152,14 @@ pub enum BodyType {
/// Info about a module. Right now, this is pretty minimal. We hope to cache
/// 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)]
pub struct ModuleInfo {
/// The ID of the module.
id: ModuleId,
/// Absolute path of the module's source file.
path: std::path::PathBuf,
parsed: Option<Node<AstProgram>>,
}
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, JsonSchema)]
@ -1796,7 +1948,7 @@ impl ExecutorContext {
source_range: crate::execution::SourceRange,
) -> Result<(), KclError> {
self.engine
.clear_scene(&mut exec_state.id_generator, source_range)
.clear_scene(&mut exec_state.global.id_generator, source_range)
.await?;
// We do not create the planes here as the post hook in wasm will do that
@ -1897,23 +2049,13 @@ impl ExecutorContext {
if cache_result.clear_scene && !self.is_mock() {
// Pop the execution state, since we are starting fresh.
let mut id_generator = exec_state.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;
*exec_state = ExecState {
id_generator,
..Default::default()
};
exec_state.reset();
// 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.
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.
self.engine.reapply_settings(&self.settings, Default::default()).await?;
@ -1931,17 +2073,35 @@ impl ExecutorContext {
exec_state: &mut ExecState,
body_type: BodyType,
) -> Result<Option<KclValue>, KclError> {
if let Some((annotation, source_range)) = program
.non_code_meta
.start_nodes
.iter()
.filter_map(|n| {
n.annotation(annotations::SETTINGS)
.map(|result| (result, n.as_source_range()))
})
.next()
{
exec_state
.mod_local
.settings
.update_from_annotation(annotation, source_range)?;
}
let mut last_expr = None;
// Iterate over the body of the program.
for statement in &program.body {
match statement {
BodyItem::ImportStatement(import_stmt) => {
let source_range = SourceRange::from(import_stmt);
let (module_memory, module_exports) =
self.open_module(&import_stmt.path, exec_state, source_range).await?;
let module_id = self.open_module(&import_stmt.path, exec_state, source_range).await?;
match &import_stmt.selector {
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 {
// Extract the item from the module.
let item =
@ -1965,18 +2125,24 @@ impl ExecutorContext {
}
// Add the item to the current module.
exec_state.memory.add(
exec_state.mut_memory().add(
import_item.identifier(),
item.clone(),
SourceRange::from(&import_item.name),
)?;
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(_) => {
let (_, module_memory, module_exports) = self
.exec_module(module_id, exec_state, ExecutionKind::Isolated, source_range)
.await?;
for name in module_exports.iter() {
let item = module_memory.get(name, source_range).map_err(|_err| {
KclError::Internal(KclErrorDetails {
@ -1984,18 +2150,20 @@ impl ExecutorContext {
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 {
exec_state.module_exports.push(name.clone());
exec_state.mod_local.module_exports.push(name.clone());
}
}
}
ImportSelector::None(_) => {
return Err(KclError::Semantic(KclErrorDetails {
message: "Importing whole module is not yet implemented, sorry.".to_owned(),
source_ranges: vec![source_range],
}));
ImportSelector::None { .. } => {
let name = import_stmt.module_name().unwrap();
let item = KclValue::Module {
value: module_id,
meta: vec![source_range.into()],
};
exec_state.mut_memory().add(&name, item, source_range)?;
}
}
last_expr = None;
@ -2025,11 +2193,11 @@ impl ExecutorContext {
StatementKind::Declaration { name: &var_name },
)
.await?;
exec_state.memory.add(&var_name, memory_item, source_range)?;
exec_state.mut_memory().add(&var_name, memory_item, source_range)?;
// Track exports.
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;
}
@ -2043,7 +2211,7 @@ impl ExecutorContext {
StatementKind::Expression,
)
.await?;
exec_state.memory.return_ = Some(value);
exec_state.mut_memory().return_ = Some(value);
last_expr = None;
}
}
@ -2069,18 +2237,19 @@ impl ExecutorContext {
path: &str,
exec_state: &mut ExecState,
source_range: SourceRange,
) -> Result<(ProgramMemory, Vec<String>), KclError> {
) -> Result<ModuleId, KclError> {
let resolved_path = if let Some(project_dir) = &self.settings.project_directory {
project_dir.join(path)
} else {
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 {
message: format!(
"circular import of modules is not allowed: {} -> {}",
exec_state
.mod_local
.import_stack
.iter()
.map(|p| p.as_path().to_string_lossy())
@ -2091,31 +2260,44 @@ impl ExecutorContext {
source_ranges: vec![source_range],
}));
}
let module_id = exec_state.add_module(resolved_path.clone());
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.add_module(resolved_path.clone(), self, source_range).await
}
exec_state.import_stack.push(resolved_path.clone());
let original_execution = self.engine.replace_execution_kind(ExecutionKind::Isolated);
let original_memory = std::mem::take(&mut exec_state.memory);
let original_exports = std::mem::take(&mut exec_state.module_exports);
async fn exec_module(
&self,
module_id: ModuleId,
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
.inner_execute(&program, exec_state, crate::execution::BodyType::Root)
.inner_execute(&info.parsed.unwrap(), exec_state, crate::execution::BodyType::Root)
.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 {
// It was an import cycle. Keep the original message.
err.override_source_ranges(vec![source_range])
} else {
KclError::Semantic(KclErrorDetails {
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()
),
source_ranges: vec![source_range],
@ -2123,7 +2305,7 @@ impl ExecutorContext {
}
})?;
Ok((module_memory, module_exports))
Ok((result, local_state.memory, local_state.module_exports))
}
#[async_recursion]
@ -2139,8 +2321,23 @@ impl ExecutorContext {
Expr::Literal(literal) => KclValue::from(literal),
Expr::TagDeclarator(tag) => tag.execute(exec_state).await?,
Expr::Identifier(identifier) => {
let value = exec_state.memory.get(&identifier.name, identifier.into())?;
value.clone()
let value = exec_state.memory().get(&identifier.name, identifier.into())?.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::FunctionExpression(function_expression) => {
@ -2151,7 +2348,7 @@ impl ExecutorContext {
expression: function_expression.clone(),
meta: vec![metadata.to_owned()],
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?,
@ -2168,7 +2365,7 @@ impl ExecutorContext {
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,
None => {
return Err(KclError::Semantic(KclErrorDetails {
@ -2188,7 +2385,9 @@ impl ExecutorContext {
let result = self
.execute_expr(&expr.expr, exec_state, metadata, statement_kind)
.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
result
}
@ -2373,12 +2572,12 @@ pub(crate) async fn call_user_defined_function(
// Execute the function body using the memory we just created.
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
.inner_execute(&function_expression.body, exec_state, BodyType::Block)
.await;
// 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)
};
@ -2403,12 +2602,12 @@ pub(crate) async fn call_user_defined_function_kw(
// Execute the function body using the memory we just created.
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
.inner_execute(&function_expression.body, exec_state, BodyType::Block)
.await;
// 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)
};
@ -2433,7 +2632,7 @@ mod tests {
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 ctx = ExecutorContext {
@ -2450,6 +2649,7 @@ mod tests {
}
/// Convenience function to get a JSON value from memory and unwrap.
#[track_caller]
fn mem_get_json(memory: &ProgramMemory, name: &str) -> KclValue {
memory.get(name, SourceRange::default()).unwrap().to_owned()
}
@ -2880,21 +3080,21 @@ let shape = layer() |> patternTransform(10, transform, %)
async fn test_math_execute_with_functions() {
let ast = r#"const myVar = 2 + min(100, -1 + legLen(5, 3))"#;
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")]
async fn test_math_execute() {
let ast = r#"const myVar = 1 + 2 * (3 - 4) / -5 + 6"#;
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")]
async fn test_math_execute_start_negative() {
let ast = r#"const myVar = -5 + 6"#;
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")]
@ -2903,7 +3103,7 @@ let shape = layer() |> patternTransform(10, transform, %)
let (_, _, exec_state) = parse_execute(ast).await.unwrap();
assert_eq!(
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 +3111,7 @@ let shape = layer() |> patternTransform(10, transform, %)
async fn test_math_define_decimal_without_leading_zero() {
let ast = r#"let thing = .4 + 7"#;
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")]
@ -2951,10 +3151,10 @@ fn check = (x) => {
check(false)
"#;
let (_, _, exec_state) = parse_execute(ast).await.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, "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(), "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(), "c").as_bool().unwrap());
assert_eq!(false, mem_get_json(exec_state.memory(), "d").as_bool().unwrap());
}
#[tokio::test(flavor = "multi_thread")]
@ -3635,4 +3835,65 @@ shell({ faces = ['end'], thickness = 0.25 }, firstSketch)"#;
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);
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.
self.client
.send_notification::<custom_notifications::MemoryUpdated>(exec_state.memory)
.send_notification::<custom_notifications::MemoryUpdated>(exec_state.mod_local.memory)
.await;
Ok(())
@ -1216,7 +1216,7 @@ impl LanguageServer for Backend {
return Ok(None);
}
// Get the completion items forem the ast.
// Get the completion items for the ast.
let Ok(variables) = ast.completion_items() else {
return Ok(Some(CompletionResponse::Array(completions)));
};

View File

@ -822,6 +822,59 @@ async fn test_kcl_lsp_completions_const_raw() {
}
}
#[tokio::test(flavor = "multi_thread")]
async fn test_kcl_lsp_completions_import() {
let server = kcl_lsp_server(false).await.unwrap();
// Send open file.
server
.did_open(tower_lsp::lsp_types::DidOpenTextDocumentParams {
text_document: tower_lsp::lsp_types::TextDocumentItem {
uri: "file:///test.kcl".try_into().unwrap(),
language_id: "kcl".to_string(),
version: 1,
text: r#"import boo, baz as bux from 'bar.kcl'
//import 'bar.kcl'
x = b"#
.to_string(),
},
})
.await;
// Send completion request.
let completions = server
.completion(tower_lsp::lsp_types::CompletionParams {
text_document_position: tower_lsp::lsp_types::TextDocumentPositionParams {
text_document: tower_lsp::lsp_types::TextDocumentIdentifier {
uri: "file:///test.kcl".try_into().unwrap(),
},
position: tower_lsp::lsp_types::Position { line: 2, character: 5 },
},
context: None,
partial_result_params: Default::default(),
work_done_progress_params: Default::default(),
})
.await
.unwrap()
.unwrap();
// Check the completions.
if let tower_lsp::lsp_types::CompletionResponse::Array(completions) = completions {
assert!(completions.len() > 10);
// Find the one with label "foo".
completions.iter().find(|completion| completion.label == "boo").unwrap();
// completions
// .iter()
// .find(|completion| completion.label == "bar")
// .unwrap();
completions.iter().find(|completion| completion.label == "bux").unwrap();
assert!(!completions.iter().any(|completion| completion.label == "baz"));
// Find the one with label "bar".
} else {
panic!("Expected array of completions");
}
}
#[tokio::test(flavor = "multi_thread")]
async fn test_kcl_lsp_on_hover() {
let server = kcl_lsp_server(false).await.unwrap();
@ -2344,6 +2397,7 @@ async fn kcl_test_kcl_lsp_diagnostics_on_execution_error() {
.await;
// Get the diagnostics.
// TODO warnings being stomped by execution errors?
assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 1);
// Update the text.

View File

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

View File

@ -1,9 +1,11 @@
//! Data types for the AST.
use std::{
cell::RefCell,
collections::HashMap,
fmt,
ops::{Deref, DerefMut, RangeInclusive},
rc::Rc,
sync::{Arc, Mutex},
};
@ -183,21 +185,24 @@ pub struct Program {
impl Node<Program> {
/// Walk the ast and get all the variables and tags as completion items.
pub fn completion_items<'a>(&'a self) -> Result<Vec<CompletionItem>> {
let completions = Arc::new(Mutex::new(vec![]));
let completions = Rc::new(RefCell::new(vec![]));
crate::walk::walk(self, |node: crate::walk::Node<'a>| {
let mut findings = completions.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
let mut findings = completions.borrow_mut();
match node {
crate::walk::Node::TagDeclarator(tag) => {
findings.push(tag.into());
}
crate::walk::Node::VariableDeclaration(variable) => {
findings.extend::<Vec<CompletionItem>>(variable.into());
findings.extend::<Vec<CompletionItem>>((&variable.inner).into());
}
crate::walk::Node::ImportStatement(i) => {
findings.extend::<Vec<CompletionItem>>((&i.inner).into());
}
_ => {}
}
Ok::<bool, anyhow::Error>(true)
})?;
let x = completions.lock().unwrap();
let x = completions.take();
Ok(x.clone())
}
@ -995,52 +1000,22 @@ pub struct NonCodeNode {
pub digest: Option<Digest>,
}
impl Node<NonCodeNode> {
pub fn format(&self, indentation: &str) -> String {
match &self.value {
NonCodeValue::InlineComment {
value,
style: CommentStyle::Line,
} => format!(" // {}\n", value),
NonCodeValue::InlineComment {
value,
style: CommentStyle::Block,
} => format!(" /* {} */", value),
NonCodeValue::BlockComment { value, style } => match style {
CommentStyle::Block => format!("{}/* {} */", indentation, value),
CommentStyle::Line => {
if value.trim().is_empty() {
format!("{}//\n", indentation)
} else {
format!("{}// {}\n", indentation, value.trim())
}
}
},
NonCodeValue::NewLineBlockComment { value, style } => {
let add_start_new_line = if self.start == 0 { "" } else { "\n\n" };
match style {
CommentStyle::Block => format!("{}{}/* {} */\n", add_start_new_line, indentation, value),
CommentStyle::Line => {
if value.trim().is_empty() {
format!("{}{}//\n", add_start_new_line, indentation)
} else {
format!("{}{}// {}\n", add_start_new_line, indentation, value.trim())
}
}
}
}
NonCodeValue::NewLine => "\n\n".to_string(),
}
}
}
impl NonCodeNode {
#[cfg(test)]
pub fn value(&self) -> String {
match &self.value {
NonCodeValue::InlineComment { value, style: _ } => value.clone(),
NonCodeValue::BlockComment { value, style: _ } => value.clone(),
NonCodeValue::NewLineBlockComment { value, style: _ } => value.clone(),
NonCodeValue::NewLine => "\n\n".to_string(),
NonCodeValue::Annotation { name, .. } => name.name.clone(),
}
}
pub fn annotation(&self, expected_name: &str) -> Option<&NonCodeValue> {
match &self.value {
a @ NonCodeValue::Annotation { name, .. } if name.name == expected_name => Some(a),
_ => None,
}
}
}
@ -1058,6 +1033,7 @@ pub enum CommentStyle {
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type", rename_all = "camelCase")]
#[allow(clippy::large_enum_variant)]
pub enum NonCodeValue {
/// An inline comment.
/// Here are examples:
@ -1090,6 +1066,10 @@ pub enum NonCodeValue {
// A new line like `\n\n` NOT a new line like `\n`.
// This is also not a comment.
NewLine,
Annotation {
name: Node<Identifier>,
properties: Option<Vec<Node<ObjectProperty>>>,
},
}
#[derive(Debug, Default, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
@ -1225,7 +1205,7 @@ pub enum ImportSelector {
Glob(Node<()>),
/// Import the module itself (the param is an optional alias).
/// E.g., `import "foo.kcl" as bar`
None(Option<Node<Identifier>>),
None { alias: Option<Node<Identifier>> },
}
impl ImportSelector {
@ -1244,8 +1224,8 @@ impl ImportSelector {
None
}
ImportSelector::Glob(_) => None,
ImportSelector::None(None) => None,
ImportSelector::None(Some(alias)) => {
ImportSelector::None { alias: None } => None,
ImportSelector::None { alias: Some(alias) } => {
let alias_source_range = SourceRange::from(&*alias);
if !alias_source_range.contains(pos) {
return None;
@ -1264,8 +1244,8 @@ impl ImportSelector {
}
}
ImportSelector::Glob(_) => {}
ImportSelector::None(None) => {}
ImportSelector::None(Some(alias)) => alias.rename(old_name, new_name),
ImportSelector::None { alias: None } => {}
ImportSelector::None { alias: Some(alias) } => alias.rename(old_name, new_name),
}
}
}
@ -1296,30 +1276,10 @@ impl Node<ImportStatement> {
false
}
ImportSelector::Glob(_) => false,
ImportSelector::None(_) => name == self.module_name().unwrap(),
ImportSelector::None { .. } => name == self.module_name().unwrap(),
}
}
/// Get the name of the module object for this import.
/// Validated during parsing and guaranteed to return `Some` if the statement imports
/// the module itself (i.e., self.selector is ImportSelector::None).
pub fn module_name(&self) -> Option<String> {
if let ImportSelector::None(Some(alias)) = &self.selector {
return Some(alias.name.clone());
}
let mut parts = self.path.split('.');
let name = parts.next()?;
let ext = parts.next()?;
let rest = parts.next();
if rest.is_some() || ext != "kcl" {
return None;
}
Some(name.to_owned())
}
pub fn get_constraint_level(&self) -> ConstraintLevel {
ConstraintLevel::Full {
source_ranges: vec![self.into()],
@ -1335,6 +1295,59 @@ impl ImportStatement {
pub fn rename_identifiers(&mut self, old_name: &str, new_name: &str) {
self.selector.rename_identifiers(old_name, new_name);
}
/// Get the name of the module object for this import.
/// Validated during parsing and guaranteed to return `Some` if the statement imports
/// the module itself (i.e., self.selector is ImportSelector::None).
pub fn module_name(&self) -> Option<String> {
if let ImportSelector::None { alias: Some(alias) } = &self.selector {
return Some(alias.name.clone());
}
let mut parts = self.path.split('.');
let name = parts.next()?;
let ext = parts.next()?;
let rest = parts.next();
if rest.is_some() || ext != "kcl" {
return None;
}
Some(name.to_owned())
}
}
impl From<&ImportStatement> for Vec<CompletionItem> {
fn from(import: &ImportStatement) -> Self {
match &import.selector {
ImportSelector::List { items } => {
items
.iter()
.map(|i| {
let as_str = match &i.alias {
Some(s) => format!(" as {}", s.name),
None => String::new(),
};
CompletionItem {
label: i.identifier().to_owned(),
// TODO we can only find this after opening the module
kind: None,
detail: Some(format!("{}{as_str} from '{}'", i.name.name, import.path)),
..CompletionItem::default()
}
})
.collect()
}
// TODO can't do completion for glob imports without static name resolution
ImportSelector::Glob(_) => vec![],
ImportSelector::None { .. } => vec![CompletionItem {
label: import.module_name().unwrap(),
kind: Some(CompletionItemKind::MODULE),
detail: Some(format!("from '{}'", import.path)),
..CompletionItem::default()
}],
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
@ -1605,30 +1618,16 @@ pub struct VariableDeclaration {
pub digest: Option<Digest>,
}
impl From<&Node<VariableDeclaration>> for Vec<CompletionItem> {
fn from(declaration: &Node<VariableDeclaration>) -> Self {
impl From<&VariableDeclaration> for Vec<CompletionItem> {
fn from(declaration: &VariableDeclaration) -> Self {
vec![CompletionItem {
label: declaration.declaration.id.name.to_string(),
label_details: None,
kind: Some(match declaration.inner.kind {
kind: Some(match declaration.kind {
VariableKind::Const => CompletionItemKind::CONSTANT,
VariableKind::Fn => CompletionItemKind::FUNCTION,
}),
detail: Some(declaration.inner.kind.to_string()),
documentation: None,
deprecated: None,
preselect: None,
sort_text: None,
filter_text: None,
insert_text: None,
insert_text_format: None,
insert_text_mode: None,
text_edit: None,
additional_text_edits: None,
command: None,
commit_characters: None,
data: None,
tags: None,
detail: Some(declaration.kind.to_string()),
..CompletionItem::default()
}]
}
}
@ -1928,6 +1927,10 @@ impl Identifier {
})
}
pub fn is_nameable(&self) -> bool {
!self.name.starts_with('_')
}
/// Rename all identifiers that have the old name to the new given name.
fn rename(&mut self, old_name: &str, new_name: &str) {
if self.name == old_name {
@ -2568,6 +2571,14 @@ pub enum BinaryOperator {
#[serde(rename = "<=")]
#[display("<=")]
Lte,
/// Are both left and right true?
#[serde(rename = "&")]
#[display("&")]
And,
/// Is either left or right true?
#[serde(rename = "|")]
#[display("|")]
Or,
}
/// Mathematical associativity.
@ -2602,6 +2613,8 @@ impl BinaryOperator {
BinaryOperator::Gte => *b"gte",
BinaryOperator::Lt => *b"ltr",
BinaryOperator::Lte => *b"lte",
BinaryOperator::And => *b"and",
BinaryOperator::Or => *b"lor",
}
}
@ -2614,6 +2627,8 @@ impl BinaryOperator {
BinaryOperator::Pow => 13,
Self::Gt | Self::Gte | Self::Lt | Self::Lte => 9,
Self::Eq | Self::Neq => 8,
Self::And => 7,
Self::Or => 6,
}
}
@ -2624,6 +2639,7 @@ impl BinaryOperator {
Self::Add | Self::Sub | Self::Mul | Self::Div | Self::Mod => Associativity::Left,
Self::Pow => Associativity::Right,
Self::Gt | Self::Gte | Self::Lt | Self::Lte | Self::Eq | Self::Neq => Associativity::Left, // I don't know if this is correct
Self::And | Self::Or => Associativity::Left,
}
}
}

View File

@ -33,7 +33,7 @@ use crate::{
SourceRange,
};
use super::ast::types::LabelledExpression;
use super::{ast::types::LabelledExpression, token::NumericSuffix};
thread_local! {
/// The current `ParseContext`. `None` if parsing is not currently happening on this thread.
@ -96,10 +96,6 @@ impl ParseContext {
*e = err;
return;
}
if e.source_range.start() > err.source_range.end() {
break;
}
}
errors.push(err);
});
@ -287,38 +283,86 @@ fn non_code_node(i: &mut TokenSlice) -> PResult<Node<NonCodeNode>> {
alt((non_code_node_leading_whitespace, non_code_node_no_leading_whitespace)).parse_next(i)
}
fn annotation(i: &mut TokenSlice) -> PResult<Node<NonCodeNode>> {
let at = at_sign.parse_next(i)?;
let name = binding_name.parse_next(i)?;
let mut end = name.end;
let properties = if peek(open_paren).parse_next(i).is_ok() {
open_paren(i)?;
ignore_whitespace(i);
let properties: Vec<_> = separated(
0..,
separated_pair(
terminated(identifier, opt(whitespace)),
terminated(one_of((TokenType::Operator, "=")), opt(whitespace)),
expression,
)
.map(|(key, value)| Node {
start: key.start,
end: value.end(),
module_id: key.module_id,
inner: ObjectProperty {
key,
value,
digest: None,
},
}),
comma_sep,
)
.parse_next(i)?;
ignore_trailing_comma(i);
ignore_whitespace(i);
end = close_paren(i)?.end;
Some(properties)
} else {
None
};
let value = NonCodeValue::Annotation { name, properties };
Ok(Node::new(
NonCodeNode { value, digest: None },
at.start,
end,
at.module_id,
))
}
// Matches remaining three cases of NonCodeValue
fn non_code_node_no_leading_whitespace(i: &mut TokenSlice) -> PResult<Node<NonCodeNode>> {
any.verify_map(|token: Token| {
if token.is_code_token() {
None
} else {
let value = match token.token_type {
TokenType::Whitespace if token.value.contains("\n\n") => NonCodeValue::NewLine,
TokenType::LineComment => NonCodeValue::BlockComment {
value: token.value.trim_start_matches("//").trim().to_owned(),
style: CommentStyle::Line,
},
TokenType::BlockComment => NonCodeValue::BlockComment {
style: CommentStyle::Block,
value: token
.value
.trim_start_matches("/*")
.trim_end_matches("*/")
.trim()
.to_owned(),
},
_ => return None,
};
Some(Node::new(
NonCodeNode { value, digest: None },
token.start,
token.end,
token.module_id,
))
}
})
.context(expected("Non-code token (comments or whitespace)"))
alt((
annotation,
any.verify_map(|token: Token| {
if token.is_code_token() {
None
} else {
let value = match token.token_type {
TokenType::Whitespace if token.value.contains("\n\n") => NonCodeValue::NewLine,
TokenType::LineComment => NonCodeValue::BlockComment {
value: token.value.trim_start_matches("//").trim().to_owned(),
style: CommentStyle::Line,
},
TokenType::BlockComment => NonCodeValue::BlockComment {
style: CommentStyle::Block,
value: token
.value
.trim_start_matches("/*")
.trim_end_matches("*/")
.trim()
.to_owned(),
},
_ => return None,
};
Some(Node::new(
NonCodeNode { value, digest: None },
token.start,
token.end,
token.module_id,
))
}
})
.context(expected("Non-code token (comments or whitespace)")),
))
.parse_next(i)
}
@ -351,22 +395,6 @@ fn pipe_expression(i: &mut TokenSlice) -> PResult<Node<PipeExpression>> {
))
.parse_next(i)?;
// All child parsers have been run.
// First, ensure they all have a % in their args.
let calls_without_substitution = tail.iter().find_map(|(_nc, call_expr, _nc2)| {
if !call_expr.has_substitution_arg() {
Some(call_expr.into())
} else {
None
}
});
if let Some(source_range) = calls_without_substitution {
let err = CompilationError::fatal(
source_range,
"All expressions in a pipeline must use the % (substitution operator)",
);
return Err(ErrMode::Cut(err.into()));
}
// Time to structure the return value.
let mut code_count = 0;
let mut max_noncode_end = 0;
@ -457,10 +485,17 @@ pub(crate) fn unsigned_number_literal(i: &mut TokenSlice) -> PResult<Node<Litera
let (value, token) = any
.try_map(|token: Token| match token.token_type {
TokenType::Number => {
let x: f64 = token.value.parse().map_err(|_| {
let x: f64 = token.numeric_value().ok_or_else(|| {
CompilationError::fatal(token.as_source_range(), format!("Invalid float: {}", token.value))
})?;
if token.numeric_suffix().is_some() {
ParseContext::err(CompilationError::err(
(&token).into(),
"Unit of Measure suffixes are experimental and currently do nothing.",
));
}
Ok((LiteralValue::Number(x), token))
}
_ => Err(CompilationError::fatal(token.as_source_range(), "invalid literal")),
@ -501,6 +536,8 @@ fn binary_operator(i: &mut TokenSlice) -> PResult<BinaryOperator> {
">=" => BinaryOperator::Gte,
"<" => BinaryOperator::Lt,
"<=" => BinaryOperator::Lte,
"|" => BinaryOperator::Or,
"&" => BinaryOperator::And,
_ => {
return Err(CompilationError::fatal(
token.as_source_range(),
@ -754,7 +791,7 @@ fn array_end_start(i: &mut TokenSlice) -> PResult<Node<ArrayRangeExpression>> {
}
fn object_property_same_key_and_val(i: &mut TokenSlice) -> PResult<Node<ObjectProperty>> {
let key = identifier.context(expected("the property's key (the name or identifier of the property), e.g. in 'height: 4', 'height' is the property key")).parse_next(i)?;
let key = nameable_identifier.context(expected("the property's key (the name or identifier of the property), e.g. in 'height: 4', 'height' is the property key")).parse_next(i)?;
ignore_whitespace(i);
Ok(Node {
start: key.start,
@ -778,7 +815,7 @@ fn object_property(i: &mut TokenSlice) -> PResult<Node<ObjectProperty>> {
))
.parse_next(i)?;
ignore_whitespace(i);
let expr = expression
let expr = expression_but_not_ascription
.context(expected(
"the value which you're setting the property to, e.g. in 'height: 4', the value is 4",
))
@ -1086,7 +1123,7 @@ fn member_expression_dot(i: &mut TokenSlice) -> PResult<(LiteralIdentifier, usiz
period.parse_next(i)?;
let property = alt((
sketch_keyword.map(Box::new).map(LiteralIdentifier::Identifier),
identifier.map(Box::new).map(LiteralIdentifier::Identifier),
nameable_identifier.map(Box::new).map(LiteralIdentifier::Identifier),
))
.parse_next(i)?;
let end = property.end();
@ -1099,7 +1136,7 @@ fn member_expression_subscript(i: &mut TokenSlice) -> PResult<(LiteralIdentifier
let property = alt((
sketch_keyword.map(Box::new).map(LiteralIdentifier::Identifier),
literal.map(LiteralIdentifier::Literal),
identifier.map(Box::new).map(LiteralIdentifier::Identifier),
nameable_identifier.map(Box::new).map(LiteralIdentifier::Identifier),
))
.parse_next(i)?;
@ -1113,7 +1150,7 @@ fn member_expression_subscript(i: &mut TokenSlice) -> PResult<(LiteralIdentifier
fn member_expression(i: &mut TokenSlice) -> PResult<Node<MemberExpression>> {
// This is an identifier, followed by a sequence of members (aka properties)
// First, the identifier.
let id = identifier.context(expected("the identifier of the object whose property you're trying to access, e.g. in 'shape.size.width', 'shape' is the identifier")).parse_next(i)?;
let id = nameable_identifier.context(expected("the identifier of the object whose property you're trying to access, e.g. in 'shape.size.width', 'shape' is the identifier")).parse_next(i)?;
// Now a sequence of members.
let member = alt((member_expression_dot, member_expression_subscript)).context(expected("a member/property, e.g. size.x and size['height'] and size[0] are all different ways to access a member/property of 'size'"));
let mut members: Vec<_> = repeat(1.., member)
@ -1186,6 +1223,7 @@ fn noncode_just_after_code(i: &mut TokenSlice) -> PResult<Node<NonCodeNode>> {
x @ NonCodeValue::InlineComment { .. } => x,
x @ NonCodeValue::NewLineBlockComment { .. } => x,
x @ NonCodeValue::NewLine => x,
x @ NonCodeValue::Annotation { .. } => x,
};
Node::new(
NonCodeNode { value, ..nc.inner },
@ -1206,6 +1244,7 @@ fn noncode_just_after_code(i: &mut TokenSlice) -> PResult<Node<NonCodeNode>> {
x @ NonCodeValue::InlineComment { .. } => x,
x @ NonCodeValue::NewLineBlockComment { .. } => x,
x @ NonCodeValue::NewLine => x,
x @ NonCodeValue::Annotation { .. } => x,
};
Node::new(NonCodeNode { value, ..nc.inner }, nc.start, nc.end, nc.module_id)
}
@ -1245,7 +1284,7 @@ fn body_items_within_function(i: &mut TokenSlice) -> PResult<WithinFunction> {
(import_stmt.map(BodyItem::ImportStatement), opt(noncode_just_after_code)).map(WithinFunction::BodyItem),
Token { ref value, .. } if value == "return" =>
(return_stmt.map(BodyItem::ReturnStatement), opt(noncode_just_after_code)).map(WithinFunction::BodyItem),
token if !token.is_code_token() => {
token if !token.is_code_token() || token.token_type == TokenType::At => {
non_code_node.map(WithinFunction::NonCode)
},
_ =>
@ -1444,7 +1483,7 @@ fn import_stmt(i: &mut TokenSlice) -> PResult<BoxNode<ImportStatement>> {
require_whitespace(i)?;
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| {
let s = t.as_source_range();
(
@ -1507,7 +1546,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(
(whitespace, import_as_keyword, whitespace),
identifier.context(expected("an identifier to alias the import")),
@ -1553,7 +1592,9 @@ fn import_stmt(i: &mut TokenSlice) -> PResult<BoxNode<ImportStatement>> {
}
fn import_item(i: &mut TokenSlice) -> PResult<Node<ImportItem>> {
let name = identifier.context(expected("an identifier to import")).parse_next(i)?;
let name = nameable_identifier
.context(expected("an identifier to import"))
.parse_next(i)?;
let start = name.start;
let module_id = name.module_id;
let alias = opt(preceded(
@ -1622,6 +1663,24 @@ fn return_stmt(i: &mut TokenSlice) -> PResult<Node<ReturnStatement>> {
/// Parse a KCL expression.
fn expression(i: &mut TokenSlice) -> PResult<Expr> {
let expr = expression_but_not_ascription.parse_next(i)?;
let ty = opt((colon, opt(whitespace), argument_type)).parse_next(i)?;
// TODO this is probably not giving ascription the right precedence, but I have no idea how Winnow is handling that.
// Since we're not creating AST nodes for ascription, I don't think it matters right now.
if let Some((colon, _, _)) = ty {
ParseContext::err(CompilationError::err(
// Sadly there is no SourceRange for the type itself
colon.into(),
"Type ascription is experimental and currently does nothing.",
));
}
Ok(expr)
}
// TODO once we remove the old record instantiation syntax, we can accept types ascription anywhere.
fn expression_but_not_ascription(i: &mut TokenSlice) -> PResult<Expr> {
alt((
pipe_expression.map(Box::new).map(Expr::PipeExpression),
expression_but_not_pipe,
@ -1678,7 +1737,7 @@ fn expr_allowed_in_pipe_expr(i: &mut TokenSlice) -> PResult<Expr> {
literal.map(Expr::Literal),
fn_call.map(Box::new).map(Expr::CallExpression),
fn_call_kw.map(Box::new).map(Expr::CallExpressionKw),
identifier.map(Box::new).map(Expr::Identifier),
nameable_identifier.map(Box::new).map(Expr::Identifier),
array,
object.map(Box::new).map(Expr::ObjectExpression),
pipe_sub.map(Box::new).map(Expr::PipeSubstitution),
@ -1697,7 +1756,7 @@ fn possible_operands(i: &mut TokenSlice) -> PResult<Expr> {
member_expression.map(Box::new).map(Expr::MemberExpression),
literal.map(Expr::Literal),
fn_call.map(Box::new).map(Expr::CallExpression),
identifier.map(Box::new).map(Expr::Identifier),
nameable_identifier.map(Box::new).map(Expr::Identifier),
binary_expr_in_parens.map(Box::new).map(Expr::BinaryExpression),
unnecessarily_bracketed,
))
@ -1873,6 +1932,24 @@ fn identifier(i: &mut TokenSlice) -> PResult<Node<Identifier>> {
.parse_next(i)
}
fn nameable_identifier(i: &mut TokenSlice) -> PResult<Node<Identifier>> {
let result = identifier.parse_next(i)?;
if !result.is_nameable() {
let desc = if result.name == "_" {
"Underscores"
} else {
"Names with a leading underscore"
};
ParseContext::err(CompilationError::err(
SourceRange::new(result.start, result.end, result.module_id),
format!("{desc} cannot be referred to, only declared."),
));
}
Ok(result)
}
fn sketch_keyword(i: &mut TokenSlice) -> PResult<Node<Identifier>> {
any.try_map(|token: Token| {
if token.token_type == TokenType::Type && token.value == "sketch" {
@ -2224,9 +2301,8 @@ fn question_mark(i: &mut TokenSlice) -> PResult<()> {
Ok(())
}
fn at_sign(i: &mut TokenSlice) -> PResult<()> {
TokenType::At.parse_from(i)?;
Ok(())
fn at_sign(i: &mut TokenSlice) -> PResult<Token> {
TokenType::At.parse_from(i)
}
fn fun(i: &mut TokenSlice) -> PResult<Token> {
@ -2257,7 +2333,7 @@ fn arguments(i: &mut TokenSlice) -> PResult<Vec<Expr>> {
fn labeled_argument(i: &mut TokenSlice) -> PResult<LabeledArg> {
separated_pair(
terminated(identifier, opt(whitespace)),
terminated(nameable_identifier, opt(whitespace)),
terminated(one_of((TokenType::Operator, "=")), opt(whitespace)),
expression,
)
@ -2293,17 +2369,31 @@ fn argument_type(i: &mut TokenSlice) -> PResult<FnArgType> {
.map_err(|err| CompilationError::fatal(token.as_source_range(), format!("Invalid type: {}", err)))
}),
// Primitive types
one_of(TokenType::Type).map(|token: Token| {
FnArgPrimitive::from_str(&token.value)
.map(FnArgType::Primitive)
.map_err(|err| CompilationError::fatal(token.as_source_range(), format!("Invalid type: {}", err)))
}),
(
one_of(TokenType::Type),
opt(delimited(open_paren, uom_for_type, close_paren)),
)
.map(|(token, suffix)| {
if suffix.is_some() {
ParseContext::err(CompilationError::err(
(&token).into(),
"Unit of Measure types are experimental and currently do nothing.",
));
}
FnArgPrimitive::from_str(&token.value)
.map(FnArgType::Primitive)
.map_err(|err| CompilationError::fatal(token.as_source_range(), format!("Invalid type: {}", err)))
}),
))
.parse_next(i)?
.map_err(|e: CompilationError| ErrMode::Backtrack(ContextError::from(e)))?;
Ok(type_)
}
fn uom_for_type(i: &mut TokenSlice) -> PResult<NumericSuffix> {
any.try_map(|t: Token| t.value.parse()).parse_next(i)
}
struct ParamDescription {
labeled: bool,
arg_name: Token,
@ -2490,7 +2580,7 @@ fn labelled_fn_call(i: &mut TokenSlice) -> PResult<Expr> {
}
fn fn_call(i: &mut TokenSlice) -> PResult<Node<CallExpression>> {
let fn_name = identifier(i)?;
let fn_name = nameable_identifier(i)?;
opt(whitespace).parse_next(i)?;
let _ = terminated(open_paren, opt(whitespace)).parse_next(i)?;
let args = arguments(i)?;
@ -2531,7 +2621,7 @@ fn fn_call(i: &mut TokenSlice) -> PResult<Node<CallExpression>> {
}
fn fn_call_kw(i: &mut TokenSlice) -> PResult<Node<CallExpressionKw>> {
let fn_name = identifier(i)?;
let fn_name = nameable_identifier(i)?;
opt(whitespace).parse_next(i)?;
let _ = open_paren.parse_next(i)?;
ignore_whitespace(i);
@ -3464,6 +3554,18 @@ mySk1 = startSketchAt([0, 0])"#;
(result.0.unwrap(), result.1)
}
#[track_caller]
fn assert_no_fatal(p: &str) -> (Node<Program>, Vec<CompilationError>) {
let result = crate::parsing::top_level_parse(p);
let result = result.0.unwrap();
assert!(
result.1.iter().all(|e| e.severity != Severity::Fatal),
"found: {:#?}",
result.1
);
(result.0.unwrap(), result.1)
}
#[track_caller]
fn assert_err(p: &str, msg: &str, src_expected: [usize; 2]) {
let result = crate::parsing::top_level_parse(p);
@ -3557,6 +3659,22 @@ height = [obj["a"] -1, 0]"#;
crate::parsing::top_level_parse("foo(42, fn(x) { return x + 1 })").unwrap();
}
#[test]
fn test_annotation_fn() {
crate::parsing::top_level_parse(
r#"fn foo() {
@annotated
return 1
}"#,
)
.unwrap();
}
#[test]
fn test_annotation_settings() {
crate::parsing::top_level_parse("@settings(units = mm)").unwrap();
}
#[test]
fn test_anon_fn_no_fn() {
assert_err_contains("foo(42, (x) { return x + 1 })", "Anonymous function requires `fn`");
@ -3861,6 +3979,25 @@ e
assert_eq!(errs.len(), 1);
}
#[test]
fn fn_decl_uom_ty() {
let some_program_string = r#"fn foo(x: number(mm)): number(_) { return 1 }"#;
let (_, errs) = assert_no_fatal(some_program_string);
assert_eq!(errs.len(), 2);
}
#[test]
fn error_underscore() {
let (_, errs) = assert_no_fatal("_foo(_blah, _)");
assert_eq!(errs.len(), 3, "found: {:#?}", errs);
}
#[test]
fn error_type_ascription() {
let (_, errs) = assert_no_fatal("a + b: number");
assert_eq!(errs.len(), 1, "found: {:#?}", errs);
}
#[test]
fn zero_param_function() {
let code = r#"
@ -4039,19 +4176,6 @@ let myBox = box([0,0], -3, -16, -10)
crate::parsing::top_level_parse(some_program_string).unwrap();
}
#[test]
fn must_use_percent_in_pipeline_fn() {
let some_program_string = r#"
foo()
|> bar(2)
"#;
assert_err(
some_program_string,
"All expressions in a pipeline must use the % (substitution operator)",
[30, 36],
);
}
#[test]
fn arg_labels() {
let input = r#"length: 3"#;
@ -4244,6 +4368,20 @@ var baz = 2
"#
);
}
#[test]
fn test_unary_not_on_keyword_bool() {
let some_program_string = r#"!true"#;
let module_id = ModuleId::default();
let tokens = crate::parsing::token::lex(some_program_string, module_id).unwrap(); // Updated import path
let actual = match unary_expression.parse(tokens.as_slice()) {
// Use tokens.as_slice() for parsing
Ok(x) => x,
Err(e) => panic!("{e:?}"),
};
assert_eq!(actual.operator, UnaryOperator::Not);
crate::parsing::top_level_parse(some_program_string).unwrap(); // Updated import path
}
}
#[cfg(test)]
@ -4502,6 +4640,11 @@ my14 = 4 ^ 2 - 3 ^ 2 * 2
r#"x = 3
obj = { x, y: 4}"#
);
snapshot_test!(bj, "true");
snapshot_test!(bk, "truee");
snapshot_test!(bl, "x = !true");
snapshot_test!(bm, "x = true & false");
snapshot_test!(bn, "x = true | false");
snapshot_test!(kw_function_unnamed_first, r#"val = foo(x, y = z)"#);
snapshot_test!(kw_function_all_named, r#"val = foo(x = a, y = b)"#);
snapshot_test!(kw_function_decl_all_labeled, r#"fn foo(x, y) { return 1 }"#);

View File

@ -0,0 +1,26 @@
---
source: kcl/src/parsing/parser.rs
assertion_line: 4521
expression: actual
snapshot_kind: text
---
{
"body": [
{
"end": 4,
"expression": {
"end": 4,
"raw": "true",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": true
},
"start": 0,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
}
],
"end": 4,
"start": 0
}

View File

@ -0,0 +1,25 @@
---
source: kcl/src/parsing/parser.rs
assertion_line: 4522
expression: actual
snapshot_kind: text
---
{
"body": [
{
"end": 5,
"expression": {
"end": 5,
"name": "truee",
"start": 0,
"type": "Identifier",
"type": "Identifier"
},
"start": 0,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
}
],
"end": 5,
"start": 0
}

View File

@ -0,0 +1,45 @@
---
source: kcl/src/parsing/parser.rs
assertion_line: 4523
expression: actual
snapshot_kind: text
---
{
"body": [
{
"declaration": {
"end": 9,
"id": {
"end": 1,
"name": "x",
"start": 0,
"type": "Identifier"
},
"init": {
"argument": {
"end": 9,
"raw": "true",
"start": 5,
"type": "Literal",
"type": "Literal",
"value": true
},
"end": 9,
"operator": "!",
"start": 4,
"type": "UnaryExpression",
"type": "UnaryExpression"
},
"start": 0,
"type": "VariableDeclarator"
},
"end": 9,
"kind": "const",
"start": 0,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
}
],
"end": 9,
"start": 0
}

View File

@ -0,0 +1,53 @@
---
source: kcl/src/parsing/parser.rs
assertion_line: 4524
expression: actual
snapshot_kind: text
---
{
"body": [
{
"declaration": {
"end": 16,
"id": {
"end": 1,
"name": "x",
"start": 0,
"type": "Identifier"
},
"init": {
"end": 16,
"left": {
"end": 8,
"raw": "true",
"start": 4,
"type": "Literal",
"type": "Literal",
"value": true
},
"operator": "&",
"right": {
"end": 16,
"raw": "false",
"start": 11,
"type": "Literal",
"type": "Literal",
"value": false
},
"start": 4,
"type": "BinaryExpression",
"type": "BinaryExpression"
},
"start": 0,
"type": "VariableDeclarator"
},
"end": 16,
"kind": "const",
"start": 0,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
}
],
"end": 16,
"start": 0
}

View File

@ -0,0 +1,53 @@
---
source: kcl/src/parsing/parser.rs
assertion_line: 4525
expression: actual
snapshot_kind: text
---
{
"body": [
{
"declaration": {
"end": 16,
"id": {
"end": 1,
"name": "x",
"start": 0,
"type": "Identifier"
},
"init": {
"end": 16,
"left": {
"end": 8,
"raw": "true",
"start": 4,
"type": "Literal",
"type": "Literal",
"value": true
},
"operator": "|",
"right": {
"end": 16,
"raw": "false",
"start": 11,
"type": "Literal",
"type": "Literal",
"value": false
},
"start": 4,
"type": "BinaryExpression",
"type": "BinaryExpression"
},
"start": 0,
"type": "VariableDeclarator"
},
"end": 16,
"kind": "const",
"start": 0,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
}
],
"end": 16,
"start": 0
}

View File

@ -1,7 +1,7 @@
// Clippy does not agree with rustc here for some reason.
#![allow(clippy::needless_lifetimes)]
use std::{fmt, iter::Enumerate, num::NonZeroUsize};
use std::{fmt, iter::Enumerate, num::NonZeroUsize, str::FromStr};
use anyhow::Result;
use parse_display::Display;
@ -17,6 +17,7 @@ use crate::{
errors::KclError,
parsing::ast::types::{ItemVisibility, VariableKind},
source_range::{ModuleId, SourceRange},
CompilationError,
};
mod tokeniser;
@ -24,6 +25,53 @@ mod tokeniser;
#[cfg(test)]
pub(crate) use tokeniser::RESERVED_WORDS;
// Note the ordering, it's important that `m` comes after `mm` and `cm`.
pub const NUM_SUFFIXES: [&str; 9] = ["mm", "cm", "m", "inch", "in", "ft", "yd", "deg", "rad"];
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum NumericSuffix {
None,
Count,
Mm,
Cm,
M,
Inch,
Ft,
Yd,
Deg,
Rad,
}
impl NumericSuffix {
#[allow(dead_code)]
pub fn is_none(self) -> bool {
self == Self::None
}
pub fn is_some(self) -> bool {
self != Self::None
}
}
impl FromStr for NumericSuffix {
type Err = CompilationError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"_" => Ok(NumericSuffix::Count),
"mm" => Ok(NumericSuffix::Mm),
"cm" => Ok(NumericSuffix::Cm),
"m" => Ok(NumericSuffix::M),
"inch" | "in" => Ok(NumericSuffix::Inch),
"ft" => Ok(NumericSuffix::Ft),
"yd" => Ok(NumericSuffix::Yd),
"deg" => Ok(NumericSuffix::Deg),
"rad" => Ok(NumericSuffix::Rad),
_ => Err(CompilationError::err(SourceRange::default(), "invalid unit of measure")),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct TokenStream {
tokens: Vec<Token>,
@ -369,6 +417,36 @@ impl Token {
}
}
pub fn numeric_value(&self) -> Option<f64> {
if self.token_type != TokenType::Number {
return None;
}
let value = &self.value;
let value = value
.split_once(|c: char| c == '_' || c.is_ascii_alphabetic())
.map(|(s, _)| s)
.unwrap_or(value);
value.parse().ok()
}
pub fn numeric_suffix(&self) -> NumericSuffix {
if self.token_type != TokenType::Number {
return NumericSuffix::None;
}
if self.value.ends_with('_') {
return NumericSuffix::Count;
}
for suffix in NUM_SUFFIXES {
if self.value.ends_with(suffix) {
return suffix.parse().unwrap();
}
}
NumericSuffix::None
}
/// Is this token the beginning of a variable/function declaration?
/// If so, what kind?
/// If not, returns None.

View File

@ -50,7 +50,6 @@ lazy_static! {
set.insert("record", TokenType::Keyword);
set.insert("struct", TokenType::Keyword);
set.insert("object", TokenType::Keyword);
set.insert("_", TokenType::Keyword);
set.insert("string", TokenType::Type);
set.insert("number", TokenType::Type);
@ -147,9 +146,9 @@ fn line_comment(i: &mut Input<'_>) -> PResult<Token> {
fn number(i: &mut Input<'_>) -> PResult<Token> {
let number_parser = alt((
// Digits before the decimal point.
(digit1, opt(('.', digit1))).map(|_| ()),
(digit1, opt(('.', digit1)), opt('_'), opt(alt(super::NUM_SUFFIXES))).map(|_| ()),
// No digits before the decimal point.
('.', digit1).map(|_| ()),
('.', digit1, opt('_'), opt(alt(super::NUM_SUFFIXES))).map(|_| ()),
));
let (value, range) = number_parser.take().with_span().parse_next(i)?;
Ok(Token::from_range(
@ -188,7 +187,7 @@ fn word(i: &mut Input<'_>) -> PResult<Token> {
fn operator(i: &mut Input<'_>) -> PResult<Token> {
let (value, range) = alt((
">=", "<=", "==", "=>", "!=", "|>", "*", "+", "-", "/", "%", "=", "<", ">", r"\", "|", "^",
">=", "<=", "==", "=>", "!=", "|>", "*", "+", "-", "/", "%", "=", "<", ">", r"\", "^", "|", "&",
))
.with_span()
.parse_next(i)?;
@ -366,6 +365,7 @@ mod tests {
use super::*;
use crate::parsing::token::TokenSlice;
fn assert_parse_err<'i, P, O, E>(mut p: P, s: &'i str)
where
O: std::fmt::Debug,
@ -379,7 +379,8 @@ mod tests {
assert!(p.parse_next(&mut input).is_err(), "parsed {s} but should have failed");
}
fn assert_parse_ok<'i, P, O, E>(mut p: P, s: &'i str)
// Returns the token and whether any more input is remaining to tokenize.
fn assert_parse_ok<'i, P, O, E>(mut p: P, s: &'i str) -> (O, bool)
where
E: std::fmt::Debug,
O: std::fmt::Debug,
@ -392,14 +393,27 @@ mod tests {
};
let res = p.parse_next(&mut input);
assert!(res.is_ok(), "failed to parse {s}, got {}", res.unwrap_err());
(res.unwrap(), !input.is_empty())
}
#[test]
fn test_number() {
for valid in [
"1", "1 abc", "1.1", "1.1 abv", "1.1 abv", "1", ".1", "5?", "5 + 6", "5 + a", "5.5", "1abc",
for (valid, expected) in [
("1", false),
("1 abc", true),
("1.1", false),
("1.1 abv", true),
("1.1 abv", true),
("1", false),
(".1", false),
("5?", true),
("5 + 6", true),
("5 + a", true),
("5.5", false),
("1abc", true),
] {
assert_parse_ok(number, valid);
let (_, remaining) = assert_parse_ok(number, valid);
assert_eq!(expected, remaining, "`{valid}` expected another token to be {expected}");
}
for invalid in ["a", "?", "?5"] {
@ -415,6 +429,27 @@ mod tests {
assert_eq!(number.parse(input).unwrap().value, "0.0000000000");
}
#[test]
fn test_number_suffix() {
for (valid, expected_val, expected_next) in [
("1_", 1.0, false),
("1_mm", 1.0, false),
("1_yd", 1.0, false),
("1m", 1.0, false),
("1inch", 1.0, false),
("1toot", 1.0, true),
("1.4inch t", 1.4, true),
] {
let (t, remaining) = assert_parse_ok(number, valid);
assert_eq!(expected_next, remaining);
assert_eq!(
Some(expected_val),
t.numeric_value(),
"{valid} has incorrect numeric value, expected {expected_val} {t:?}"
);
}
}
#[test]
fn test_word() {
for valid in ["a", "a ", "a5", "a5a"] {
@ -429,7 +464,7 @@ mod tests {
#[test]
fn test_operator() {
for valid in [
"+", "+ ", "-", "<=", "<= ", ">=", ">= ", "> ", "< ", "| ", "|> ", "^ ", "% ", "+* ",
"+", "+ ", "-", "<=", "<= ", ">=", ">= ", "> ", "< ", "|> ", "^ ", "% ", "+* ", "| ", "& ",
] {
assert_parse_ok(operator, valid);
}
@ -715,4 +750,30 @@ const things = "things"
}
}
}
#[test]
fn test_boolean_literal() {
let module_id = ModuleId::default();
let actual = lex("true", module_id).unwrap();
let expected = Token {
token_type: TokenType::Keyword,
value: "true".to_owned(),
start: 0,
end: 4,
module_id,
};
assert_eq!(actual.tokens[0], expected);
}
#[test]
fn test_word_starting_with_keyword() {
let module_id = ModuleId::default();
let actual = lex("truee", module_id).unwrap();
let expected = Token {
token_type: TokenType::Word,
value: "truee".to_owned(),
start: 0,
end: 5,
module_id,
};
assert_eq!(actual.tokens[0], expected);
}
}

View File

@ -127,7 +127,7 @@ async fn execute(test_name: &str, render_to_png: bool) {
});
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 => {
@ -711,6 +711,27 @@ mod import_glob {
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 {
const TEST_NAME: &str = "import_side_effect";
@ -1572,3 +1593,66 @@ mod kw_fn_with_defaults {
super::execute(TEST_NAME, false).await
}
}
mod boolean_logical_and {
const TEST_NAME: &str = "boolean_logical_and";
/// 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 boolean_logical_or {
const TEST_NAME: &str = "boolean_logical_or";
/// 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 boolean_logical_multiple {
const TEST_NAME: &str = "boolean_logical_multiple";
/// 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
}
}

View File

@ -61,28 +61,34 @@ impl KwArgs {
pub struct Args {
/// Positional args.
pub args: Vec<Arg>,
/// Keyword arguments
pub kw_args: KwArgs,
pub source_range: SourceRange,
pub ctx: ExecutorContext,
/// If this call happens inside a pipe (|>) expression, this holds the LHS of that |>.
/// Otherwise it's None.
pipe_value: Option<Arg>,
}
impl Args {
pub fn new(args: Vec<Arg>, source_range: SourceRange, ctx: ExecutorContext) -> Self {
pub fn new(args: Vec<Arg>, source_range: SourceRange, ctx: ExecutorContext, pipe_value: Option<Arg>) -> Self {
Self {
args,
kw_args: Default::default(),
source_range,
ctx,
pipe_value,
}
}
/// Collect the given keyword arguments.
pub fn new_kw(kw_args: KwArgs, source_range: SourceRange, ctx: ExecutorContext) -> Self {
pub fn new_kw(kw_args: KwArgs, source_range: SourceRange, ctx: ExecutorContext, pipe_value: Option<Arg>) -> Self {
Self {
args: Default::default(),
kw_args,
source_range,
ctx,
pipe_value,
}
}
@ -101,6 +107,7 @@ impl Args {
settings: Default::default(),
context_type: crate::execution::ContextType::Mock,
},
pipe_value: None,
})
}
@ -138,6 +145,7 @@ impl Args {
.unlabeled
.as_ref()
.or(self.args.first())
.or(self.pipe_value.as_ref())
.ok_or(KclError::Semantic(KclErrorDetails {
source_ranges: vec![self.source_range],
message: format!("This function requires a value for the special unlabeled first parameter, '{label}'"),
@ -185,7 +193,7 @@ impl Args {
exec_state: &'e mut ExecState,
tag: &'a TagIdentifier,
) -> 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(|| {
KclError::Type(KclErrorDetails {
message: format!("Tag `{}` does not have engine info", tag.value),
@ -251,12 +259,12 @@ impl Args {
// Find all the solids on the same shared sketch.
ids.extend(
exec_state
.memory
.memory()
.find_solids_on_sketch(solid.sketch.id)
.iter()
.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);
}

View File

@ -144,7 +144,8 @@ pub async fn reduce(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
/// stepAngle = (1/10) * tau()
///
/// // Start the decagon sketch at this point.
/// startOfDecagonSketch = startSketchAt([(cos(0)*radius), (sin(0) * radius)])
/// startOfDecagonSketch = startSketchOn('XY')
/// |> startProfileAt([(cos(0)*radius), (sin(0) * radius)], %)
///
/// // Use a `reduce` to draw the remaining decagon sides.
/// // For each number in the array 1..10, run the given function,
@ -164,7 +165,8 @@ pub async fn reduce(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
/// The `decagon` above is basically like this pseudo-code:
/// fn decagon(radius):
/// stepAngle = (1/10) * tau()
/// startOfDecagonSketch = startSketchAt([(cos(0)*radius), (sin(0) * radius)])
/// plane = startSketchOn('XY')
/// startOfDecagonSketch = startProfileAt([(cos(0)*radius), (sin(0) * radius)], plane)
///
/// // Here's the reduce part.
/// partialDecagon = startOfDecagonSketch

View File

@ -134,7 +134,7 @@ async fn inner_chamfer(
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(
id,
ModelingCmd::from(mcmd::Solid3dFilletEdge {

View File

@ -84,7 +84,7 @@ async fn inner_extrude(
exec_state: &mut ExecState,
args: Args,
) -> Result<SolidSet, KclError> {
let id = exec_state.id_generator.next_uuid();
let id = exec_state.next_uuid();
// Extrude the element(s).
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.
// We do this here in case extrude is called out of order.
args.batch_modeling_cmd(
exec_state.id_generator.next_uuid(),
exec_state.next_uuid(),
ModelingCmd::from(mcmd::EnableSketchMode {
animated: false,
ortho: false,
@ -121,7 +121,7 @@ async fn inner_extrude(
// Disable the sketch mode.
args.batch_modeling_cmd(
exec_state.id_generator.next_uuid(),
exec_state.next_uuid(),
ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable {}),
)
.await?;
@ -140,7 +140,7 @@ pub(crate) async fn do_post_extrude(
// Bring the object to the front of the scene.
// See: https://github.com/KittyCAD/modeling-app/issues/806
args.batch_modeling_cmd(
exec_state.id_generator.next_uuid(),
exec_state.next_uuid(),
ModelingCmd::from(mcmd::ObjectBringToFront { object_id: sketch.id }),
)
.await?;
@ -163,7 +163,7 @@ pub(crate) async fn do_post_extrude(
let solid3d_info = args
.send_modeling_cmd(
exec_state.id_generator.next_uuid(),
exec_state.next_uuid(),
ModelingCmd::from(mcmd::Solid3dGetExtrusionFaceInfo {
edge_id: any_edge_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)
// uses this to build the artifact graph, which the UI needs.
args.batch_modeling_cmd(
exec_state.id_generator.next_uuid(),
exec_state.next_uuid(),
ModelingCmd::from(mcmd::Solid3dGetOppositeEdge {
edge_id: curve_id,
object_id: sketch.id,
@ -206,7 +206,7 @@ pub(crate) async fn do_post_extrude(
.await?;
args.batch_modeling_cmd(
exec_state.id_generator.next_uuid(),
exec_state.next_uuid(),
ModelingCmd::from(mcmd::Solid3dGetNextAdjacentEdge {
edge_id: curve_id,
object_id: sketch.id,
@ -259,7 +259,7 @@ pub(crate) async fn do_post_extrude(
let extrude_surface = ExtrudeSurface::ExtrudePlane(crate::execution::ExtrudePlane {
// 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(),
geo_meta: GeoMeta {
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() {
// 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.end_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.next_uuid());
}
for face_info in face_infos {
match face_info.cap {

View File

@ -143,7 +143,7 @@ async fn inner_fillet(
for edge_tag in data.tags {
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(
id,
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> {
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 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 resp = args
@ -302,11 +302,11 @@ async fn inner_get_next_adjacent_edge(
args: Args,
) -> Result<Uuid, KclError> {
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 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 resp = args
@ -387,11 +387,11 @@ async fn inner_get_previous_adjacent_edge(
args: Args,
) -> Result<Uuid, KclError> {
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 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 resp = args

View File

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

View File

@ -300,13 +300,13 @@ async fn inner_import(
if args.ctx.is_mock() {
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(),
meta: vec![args.source_range.into()],
});
}
let id = exec_state.id_generator.next_uuid();
let id = exec_state.next_uuid();
let resp = args
.send_modeling_cmd(
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(
id,
ModelingCmd::from(mcmd::Loft {

View File

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

View File

@ -179,7 +179,8 @@ pub async fn pattern_transform_2d(exec_state: &mut ExecState, args: Args) -> Res
/// p2 = [ l + x, l + y]
/// p3 = [ l + x, -l + y]
///
/// return startSketchAt(p0)
/// return startSketchOn('XY')
/// |> startProfileAt(p0, %)
/// |> lineTo(p1, %)
/// |> lineTo(p2, %)
/// |> lineTo(p3, %)
@ -218,7 +219,8 @@ pub async fn pattern_transform_2d(exec_state: &mut ExecState, args: Args) -> Res
/// p2 = [ l + x, l + y]
/// p3 = [ l + x, -l + y]
///
/// return startSketchAt(p0)
/// return startSketchOn('XY')
/// |> startProfileAt(p0, %)
/// |> lineTo(p1, %)
/// |> lineTo(p2, %)
/// |> lineTo(p3, %)
@ -274,7 +276,8 @@ pub async fn pattern_transform_2d(exec_state: &mut ExecState, args: Args) -> Res
/// { rotation: { angle: 45 * i } },
/// ]
/// }
/// startSketchAt([0, 0])
/// startSketchOn('XY')
/// |> startProfileAt([0, 0], %)
/// |> polygon({
/// radius: 10,
/// numSides: 4,
@ -379,7 +382,7 @@ async fn send_pattern_transform<T: GeometryTrait>(
exec_state: &mut ExecState,
args: &Args,
) -> Result<Vec<T>, KclError> {
let id = exec_state.id_generator.next_uuid();
let id = exec_state.next_uuid();
let resp = args
.send_modeling_cmd(
@ -1032,7 +1035,7 @@ async fn pattern_circular(
exec_state: &mut ExecState,
args: Args,
) -> Result<Geometries, KclError> {
let id = exec_state.id_generator.next_uuid();
let id = exec_state.next_uuid();
let num_repetitions = match data.repetitions() {
RepetitionsNeeded::More(n) => n,
RepetitionsNeeded::None => {

View File

@ -209,7 +209,7 @@ async fn make_offset_plane_in_engine(plane: &Plane, exec_state: &mut ExecState,
// Set the color.
args.batch_modeling_cmd(
exec_state.id_generator.next_uuid(),
exec_state.next_uuid(),
ModelingCmd::from(mcmd::PlaneSetColor {
color,
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 id = exec_state.id_generator.next_uuid();
let id = exec_state.next_uuid();
match data.axis {
AxisOrEdgeReference::Axis(axis) => {
let (axis, origin) = axis.axis_and_origin()?;

View File

@ -22,7 +22,8 @@ pub async fn segment_end(exec_state: &mut ExecState, args: Args) -> Result<KclVa
///
/// ```no_run
/// w = 15
/// cube = startSketchAt([0, 0])
/// cube = startSketchOn('XY')
/// |> startProfileAt([0, 0], %)
/// |> line([w, 0], %, $line1)
/// |> line([0, w], %, $line2)
/// |> line([-w, 0], %, $line3)
@ -31,7 +32,8 @@ pub async fn segment_end(exec_state: &mut ExecState, args: Args) -> Result<KclVa
/// |> extrude(5, %)
///
/// fn cylinder(radius, tag) {
/// return startSketchAt([0, 0])
/// return startSketchOn('XY')
/// |> startProfileAt([0, 0], %)
/// |> circle({ radius = radius, center = segEnd(tag) }, %)
/// |> extrude(radius, %)
/// }
@ -141,7 +143,8 @@ pub async fn segment_start(exec_state: &mut ExecState, args: Args) -> Result<Kcl
///
/// ```no_run
/// w = 15
/// cube = startSketchAt([0, 0])
/// cube = startSketchOn('XY')
/// |> startProfileAt([0, 0], %)
/// |> line([w, 0], %, $line1)
/// |> line([0, w], %, $line2)
/// |> line([-w, 0], %, $line3)
@ -150,7 +153,8 @@ pub async fn segment_start(exec_state: &mut ExecState, args: Args) -> Result<Kcl
/// |> extrude(5, %)
///
/// fn cylinder(radius, tag) {
/// return startSketchAt([0, 0])
/// return startSketchOn('XY')
/// |> startProfileAt([0, 0], %)
/// |> circle({ radius = radius, center = segStart(tag) }, %)
/// |> extrude(radius, %)
/// }

View File

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

View File

@ -230,7 +230,7 @@ async fn inner_shell(
}
args.batch_modeling_cmd(
exec_state.id_generator.next_uuid(),
exec_state.next_uuid(),
ModelingCmd::from(mcmd::Solid3dShellFace {
hollow: false,
face_ids,
@ -316,7 +316,7 @@ async fn inner_hollow(
args.flush_batch_for_solid_set(exec_state, solid.clone().into()).await?;
args.batch_modeling_cmd(
exec_state.id_generator.next_uuid(),
exec_state.next_uuid(),
ModelingCmd::from(mcmd::Solid3dShellFace {
hollow: true,
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,
) -> Result<Sketch, KclError> {
let from = sketch.current_pen_position()?;
let id = exec_state.id_generator.next_uuid();
let id = exec_state.next_uuid();
args.batch_modeling_cmd(
id,
@ -299,7 +299,7 @@ async fn inner_line(
let from = sketch.current_pen_position()?;
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(
id,
@ -488,7 +488,7 @@ async fn inner_angled_line(
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(
id,
@ -889,6 +889,7 @@ pub async fn start_sketch_at(exec_state: &mut ExecState, args: Args) -> Result<K
/// ```
#[stdlib {
name = "startSketchAt",
deprecated = true,
}]
async fn inner_start_sketch_at(data: [f64; 2], exec_state: &mut ExecState, args: Args) -> Result<Sketch, KclError> {
// Let's assume it's the XY plane for now, this is just for backwards compatibility.
@ -1230,7 +1231,7 @@ pub(crate) async fn inner_start_profile_at(
// Hide whatever plane we are sketching on.
// This is especially helpful for offset planes, which would be visible otherwise.
args.batch_end_cmd(
exec_state.id_generator.next_uuid(),
exec_state.next_uuid(),
ModelingCmd::from(mcmd::ObjectVisible {
object_id: plane.id,
hidden: true,
@ -1243,7 +1244,7 @@ pub(crate) async fn inner_start_profile_at(
// Enter sketch mode on the surface.
// 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(
id,
ModelingCmd::from(mcmd::EnableSketchMode {
@ -1261,8 +1262,8 @@ pub(crate) async fn inner_start_profile_at(
)
.await?;
let id = exec_state.id_generator.next_uuid();
let path_id = exec_state.id_generator.next_uuid();
let id = exec_state.next_uuid();
let path_id = exec_state.next_uuid();
args.batch_modeling_cmd(path_id, ModelingCmd::from(mcmd::StartPath {}))
.await?;
@ -1427,7 +1428,7 @@ pub(crate) async fn inner_close(
let from = sketch.current_pen_position()?;
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 }))
.await?;
@ -1436,7 +1437,7 @@ pub(crate) async fn inner_close(
if let SketchSurface::Plane(_) = sketch.on {
// We were on a plane, disable the sketch mode.
args.batch_modeling_cmd(
exec_state.id_generator.next_uuid(),
exec_state.next_uuid(),
ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable {}),
)
.await?;
@ -1573,7 +1574,7 @@ pub(crate) async fn inner_arc(
}
let ccw = angle_start < angle_end;
let id = exec_state.id_generator.next_uuid();
let id = exec_state.next_uuid();
args.batch_modeling_cmd(
id,
@ -1652,7 +1653,7 @@ pub(crate) async fn inner_arc_to(
args: Args,
) -> Result<Sketch, KclError> {
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.
args.batch_modeling_cmd(
@ -1798,7 +1799,7 @@ async fn inner_tangential_arc(
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 id = exec_state.id_generator.next_uuid();
let id = exec_state.next_uuid();
let (center, to, ccw) = match data {
TangentialArcData::RadiusAndOffset { radius, offset } => {
@ -1935,7 +1936,7 @@ async fn inner_tangential_arc_to(
});
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?;
let current_path = Path::TangentialArcTo {
@ -2018,7 +2019,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?;
let current_path = Path::TangentialArcTo {
@ -2138,7 +2139,7 @@ async fn inner_bezier_curve(
let delta = data.to;
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(
id,
@ -2230,7 +2231,7 @@ async fn inner_hole(
let hole_sketches: Vec<Sketch> = hole_sketch.into();
for hole_sketch in hole_sketches {
args.batch_modeling_cmd(
exec_state.id_generator.next_uuid(),
exec_state.next_uuid(),
ModelingCmd::from(mcmd::Solid2dAddHole {
object_id: sketch.id,
hole_id: hole_sketch.id,
@ -2241,7 +2242,7 @@ async fn inner_hole(
// suggestion (mike)
// we also hide the source hole since its essentially "consumed" by this operation
args.batch_modeling_cmd(
exec_state.id_generator.next_uuid(),
exec_state.next_uuid(),
ModelingCmd::from(mcmd::ObjectVisible {
object_id: hole_sketch.id,
hidden: true,

View File

@ -87,7 +87,7 @@ async fn inner_sweep(
exec_state: &mut ExecState,
args: Args,
) -> Result<Box<Solid>, KclError> {
let id = exec_state.id_generator.next_uuid();
let id = exec_state.next_uuid();
args.batch_modeling_cmd(
id,
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?;
do_execute_and_snapshot(&ctx, ast)
.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(

View File

@ -3,10 +3,10 @@ use std::fmt::Write;
use crate::parsing::{
ast::types::{
ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, CallExpression,
CallExpressionKw, DefaultParamVal, Expr, FnArgType, FormatOptions, FunctionExpression, IfExpression,
ImportSelector, ImportStatement, ItemVisibility, LabeledArg, Literal, LiteralIdentifier, LiteralValue,
MemberExpression, MemberObject, Node, NonCodeValue, ObjectExpression, Parameter, PipeExpression, Program,
TagDeclarator, UnaryExpression, VariableDeclaration, VariableKind,
CallExpressionKw, CommentStyle, DefaultParamVal, Expr, FnArgType, FormatOptions, FunctionExpression,
IfExpression, ImportSelector, ImportStatement, ItemVisibility, LabeledArg, Literal, LiteralIdentifier,
LiteralValue, MemberExpression, MemberObject, Node, NonCodeNode, NonCodeValue, ObjectExpression, Parameter,
PipeExpression, Program, TagDeclarator, UnaryExpression, VariableDeclaration, VariableKind,
},
PIPE_OPERATOR,
};
@ -55,7 +55,7 @@ impl Program {
self.non_code_meta
.start_nodes
.iter()
.map(|start| start.format(&indentation))
.map(|start| start.recast(options, indentation_level))
.collect()
}
} else {
@ -76,7 +76,7 @@ impl Program {
.iter()
.enumerate()
.map(|(i, custom_white_space_or_comment)| {
let formatted = custom_white_space_or_comment.format(&indentation);
let formatted = custom_white_space_or_comment.recast(options, indentation_level);
if i == 0 && !formatted.trim().is_empty() {
if let NonCodeValue::BlockComment { .. } = custom_white_space_or_comment.value {
format!("\n{}", formatted)
@ -115,7 +115,75 @@ impl NonCodeValue {
fn should_cause_array_newline(&self) -> bool {
match self {
Self::InlineComment { .. } => false,
Self::BlockComment { .. } | Self::NewLineBlockComment { .. } | Self::NewLine => true,
Self::BlockComment { .. } | Self::NewLineBlockComment { .. } | Self::NewLine | Self::Annotation { .. } => {
true
}
}
}
}
impl Node<NonCodeNode> {
fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String {
let indentation = options.get_indentation(indentation_level);
match &self.value {
NonCodeValue::InlineComment {
value,
style: CommentStyle::Line,
} => format!(" // {}\n", value),
NonCodeValue::InlineComment {
value,
style: CommentStyle::Block,
} => format!(" /* {} */", value),
NonCodeValue::BlockComment { value, style } => match style {
CommentStyle::Block => format!("{}/* {} */", indentation, value),
CommentStyle::Line => {
if value.trim().is_empty() {
format!("{}//\n", indentation)
} else {
format!("{}// {}\n", indentation, value.trim())
}
}
},
NonCodeValue::NewLineBlockComment { value, style } => {
let add_start_new_line = if self.start == 0 { "" } else { "\n\n" };
match style {
CommentStyle::Block => format!("{}{}/* {} */\n", add_start_new_line, indentation, value),
CommentStyle::Line => {
if value.trim().is_empty() {
format!("{}{}//\n", add_start_new_line, indentation)
} else {
format!("{}{}// {}\n", add_start_new_line, indentation, value.trim())
}
}
}
}
NonCodeValue::NewLine => "\n\n".to_string(),
NonCodeValue::Annotation { name, properties } => {
let mut result = "@".to_owned();
result.push_str(&name.name);
if let Some(properties) = properties {
result.push('(');
result.push_str(
&properties
.iter()
.map(|prop| {
format!(
"{} = {}",
prop.key.name,
prop.value
.recast(options, indentation_level + 1, ExprContext::Other)
.trim()
)
})
.collect::<Vec<String>>()
.join(", "),
);
result.push(')');
result.push('\n');
}
result
}
}
}
}
@ -146,11 +214,11 @@ impl ImportStatement {
string.push_str(" from ");
}
ImportSelector::Glob(_) => string.push_str("* from "),
ImportSelector::None(_) => {}
ImportSelector::None { .. } => {}
}
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(&alias.name);
}
@ -343,7 +411,7 @@ impl ArrayExpression {
.iter()
.map(|nc| {
found_line_comment |= nc.value.should_cause_array_newline();
nc.format("")
nc.recast(options, 0)
})
.collect::<Vec<_>>()
} else {
@ -481,7 +549,7 @@ impl ObjectExpression {
let format_items: Vec<_> = (0..num_items)
.flat_map(|i| {
if let Some(noncode) = self.non_code_meta.non_code_nodes.get(&i) {
noncode.iter().map(|nc| nc.format("")).collect::<Vec<_>>()
noncode.iter().map(|nc| nc.recast(options, 0)).collect::<Vec<_>>()
} else {
let prop = props.next().unwrap();
// Use a comma unless it's the last item
@ -617,10 +685,13 @@ impl Node<PipeExpression> {
if let Some(non_code_meta_value) = non_code_meta.non_code_nodes.get(&index) {
for val in non_code_meta_value {
let formatted = if val.end == self.end {
let indentation = options.get_indentation(indentation_level);
val.format(&indentation).trim_end_matches('\n').to_string()
val.recast(options, indentation_level)
.trim_end_matches('\n')
.to_string()
} else {
val.format(&indentation).trim_end_matches('\n').to_string()
val.recast(options, indentation_level + 1)
.trim_end_matches('\n')
.to_string()
};
if let NonCodeValue::BlockComment { .. } = val.value {
s += "\n";
@ -1252,7 +1323,8 @@ part001 = startSketchOn('XY')
#[test]
fn test_recast_large_file() {
let some_program_string = r#"// define nts
let some_program_string = r#"@settings(units=mm)
// define nts
radius = 6.0
width = 144.0
length = 83.0
@ -1376,7 +1448,8 @@ tabs_l = startSketchOn({
// Its VERY important this comes back with zero new lines.
assert_eq!(
recasted,
r#"// define nts
r#"@settings(units = mm)
// define nts
radius = 6.0
width = 144.0
length = 83.0

View File

@ -0,0 +1,703 @@
---
source: kcl/src/simulation_tests.rs
description: Result of parsing boolean_logical_and.kcl
---
{
"Ok": {
"body": [
{
"declaration": {
"end": 17,
"id": {
"end": 2,
"name": "aa",
"start": 0,
"type": "Identifier"
},
"init": {
"end": 17,
"left": {
"end": 9,
"raw": "true",
"start": 5,
"type": "Literal",
"type": "Literal",
"value": true
},
"operator": "&",
"right": {
"end": 17,
"raw": "false",
"start": 12,
"type": "Literal",
"type": "Literal",
"value": false
},
"start": 5,
"type": "BinaryExpression",
"type": "BinaryExpression"
},
"start": 0,
"type": "VariableDeclarator"
},
"end": 17,
"kind": "const",
"start": 0,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
},
{
"declaration": {
"end": 48,
"id": {
"end": 19,
"name": "a",
"start": 18,
"type": "Identifier"
},
"init": {
"cond": {
"end": 27,
"name": "aa",
"start": 25,
"type": "Identifier",
"type": "Identifier"
},
"digest": null,
"else_ifs": [],
"end": 48,
"final_else": {
"body": [
{
"end": 46,
"expression": {
"end": 46,
"raw": "2",
"start": 45,
"type": "Literal",
"type": "Literal",
"value": 2.0
},
"start": 45,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
}
],
"end": 47,
"start": 45
},
"start": 22,
"then_val": {
"body": [
{
"end": 33,
"expression": {
"end": 33,
"raw": "1",
"start": 32,
"type": "Literal",
"type": "Literal",
"value": 1.0
},
"start": 32,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
}
],
"end": 34,
"start": 32
},
"type": "IfExpression",
"type": "IfExpression"
},
"start": 18,
"type": "VariableDeclarator"
},
"end": 48,
"kind": "const",
"start": 18,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
},
{
"end": 128,
"expression": {
"arguments": [
{
"end": 62,
"left": {
"end": 57,
"name": "a",
"start": 56,
"type": "Identifier",
"type": "Identifier"
},
"operator": "==",
"right": {
"end": 62,
"raw": "2",
"start": 61,
"type": "Literal",
"type": "Literal",
"value": 2.0
},
"start": 56,
"type": "BinaryExpression",
"type": "BinaryExpression"
},
{
"end": 127,
"raw": "\"right branch of and is false makes the whole expression false\"",
"start": 64,
"type": "Literal",
"type": "Literal",
"value": "right branch of and is false makes the whole expression false"
}
],
"callee": {
"end": 55,
"name": "assert",
"start": 49,
"type": "Identifier"
},
"end": 128,
"start": 49,
"type": "CallExpression",
"type": "CallExpression"
},
"start": 49,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
},
{
"declaration": {
"end": 147,
"id": {
"end": 132,
"name": "bb",
"start": 130,
"type": "Identifier"
},
"init": {
"end": 147,
"left": {
"end": 140,
"raw": "false",
"start": 135,
"type": "Literal",
"type": "Literal",
"value": false
},
"operator": "&",
"right": {
"end": 147,
"raw": "true",
"start": 143,
"type": "Literal",
"type": "Literal",
"value": true
},
"start": 135,
"type": "BinaryExpression",
"type": "BinaryExpression"
},
"start": 130,
"type": "VariableDeclarator"
},
"end": 147,
"kind": "const",
"start": 130,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
},
{
"declaration": {
"end": 178,
"id": {
"end": 149,
"name": "b",
"start": 148,
"type": "Identifier"
},
"init": {
"cond": {
"end": 157,
"name": "bb",
"start": 155,
"type": "Identifier",
"type": "Identifier"
},
"digest": null,
"else_ifs": [],
"end": 178,
"final_else": {
"body": [
{
"end": 176,
"expression": {
"end": 176,
"raw": "2",
"start": 175,
"type": "Literal",
"type": "Literal",
"value": 2.0
},
"start": 175,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
}
],
"end": 177,
"start": 175
},
"start": 152,
"then_val": {
"body": [
{
"end": 163,
"expression": {
"end": 163,
"raw": "1",
"start": 162,
"type": "Literal",
"type": "Literal",
"value": 1.0
},
"start": 162,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
}
],
"end": 164,
"start": 162
},
"type": "IfExpression",
"type": "IfExpression"
},
"start": 148,
"type": "VariableDeclarator"
},
"end": 178,
"kind": "const",
"start": 148,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
},
{
"end": 257,
"expression": {
"arguments": [
{
"end": 192,
"left": {
"end": 187,
"name": "b",
"start": 186,
"type": "Identifier",
"type": "Identifier"
},
"operator": "==",
"right": {
"end": 192,
"raw": "2",
"start": 191,
"type": "Literal",
"type": "Literal",
"value": 2.0
},
"start": 186,
"type": "BinaryExpression",
"type": "BinaryExpression"
},
{
"end": 256,
"raw": "\"left branch of and is false makes the whole expression false\"",
"start": 194,
"type": "Literal",
"type": "Literal",
"value": "left branch of and is false makes the whole expression false"
}
],
"callee": {
"end": 185,
"name": "assert",
"start": 179,
"type": "Identifier"
},
"end": 257,
"start": 179,
"type": "CallExpression",
"type": "CallExpression"
},
"start": 179,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
},
{
"declaration": {
"end": 275,
"id": {
"end": 261,
"name": "cc",
"start": 259,
"type": "Identifier"
},
"init": {
"end": 275,
"left": {
"end": 268,
"raw": "true",
"start": 264,
"type": "Literal",
"type": "Literal",
"value": true
},
"operator": "&",
"right": {
"end": 275,
"raw": "true",
"start": 271,
"type": "Literal",
"type": "Literal",
"value": true
},
"start": 264,
"type": "BinaryExpression",
"type": "BinaryExpression"
},
"start": 259,
"type": "VariableDeclarator"
},
"end": 275,
"kind": "const",
"start": 259,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
},
{
"declaration": {
"end": 306,
"id": {
"end": 277,
"name": "c",
"start": 276,
"type": "Identifier"
},
"init": {
"cond": {
"end": 285,
"name": "cc",
"start": 283,
"type": "Identifier",
"type": "Identifier"
},
"digest": null,
"else_ifs": [],
"end": 306,
"final_else": {
"body": [
{
"end": 304,
"expression": {
"end": 304,
"raw": "2",
"start": 303,
"type": "Literal",
"type": "Literal",
"value": 2.0
},
"start": 303,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
}
],
"end": 305,
"start": 303
},
"start": 280,
"then_val": {
"body": [
{
"end": 291,
"expression": {
"end": 291,
"raw": "1",
"start": 290,
"type": "Literal",
"type": "Literal",
"value": 1.0
},
"start": 290,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
}
],
"end": 292,
"start": 290
},
"type": "IfExpression",
"type": "IfExpression"
},
"start": 276,
"type": "VariableDeclarator"
},
"end": 306,
"kind": "const",
"start": 276,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
},
{
"end": 386,
"expression": {
"arguments": [
{
"end": 320,
"left": {
"end": 315,
"name": "c",
"start": 314,
"type": "Identifier",
"type": "Identifier"
},
"operator": "==",
"right": {
"end": 320,
"raw": "1",
"start": 319,
"type": "Literal",
"type": "Literal",
"value": 1.0
},
"start": 314,
"type": "BinaryExpression",
"type": "BinaryExpression"
},
{
"end": 385,
"raw": "\"both branches of and are true makes the whole expression true\"",
"start": 322,
"type": "Literal",
"type": "Literal",
"value": "both branches of and are true makes the whole expression true"
}
],
"callee": {
"end": 313,
"name": "assert",
"start": 307,
"type": "Identifier"
},
"end": 386,
"start": 307,
"type": "CallExpression",
"type": "CallExpression"
},
"start": 307,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
},
{
"declaration": {
"end": 406,
"id": {
"end": 390,
"name": "dd",
"start": 388,
"type": "Identifier"
},
"init": {
"end": 406,
"left": {
"end": 398,
"raw": "false",
"start": 393,
"type": "Literal",
"type": "Literal",
"value": false
},
"operator": "&",
"right": {
"end": 406,
"raw": "false",
"start": 401,
"type": "Literal",
"type": "Literal",
"value": false
},
"start": 393,
"type": "BinaryExpression",
"type": "BinaryExpression"
},
"start": 388,
"type": "VariableDeclarator"
},
"end": 406,
"kind": "const",
"start": 388,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
},
{
"declaration": {
"end": 437,
"id": {
"end": 408,
"name": "d",
"start": 407,
"type": "Identifier"
},
"init": {
"cond": {
"end": 416,
"name": "dd",
"start": 414,
"type": "Identifier",
"type": "Identifier"
},
"digest": null,
"else_ifs": [],
"end": 437,
"final_else": {
"body": [
{
"end": 435,
"expression": {
"end": 435,
"raw": "2",
"start": 434,
"type": "Literal",
"type": "Literal",
"value": 2.0
},
"start": 434,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
}
],
"end": 436,
"start": 434
},
"start": 411,
"then_val": {
"body": [
{
"end": 422,
"expression": {
"end": 422,
"raw": "1",
"start": 421,
"type": "Literal",
"type": "Literal",
"value": 1.0
},
"start": 421,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
}
],
"end": 423,
"start": 421
},
"type": "IfExpression",
"type": "IfExpression"
},
"start": 407,
"type": "VariableDeclarator"
},
"end": 437,
"kind": "const",
"start": 407,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
},
{
"end": 519,
"expression": {
"arguments": [
{
"end": 451,
"left": {
"end": 446,
"name": "d",
"start": 445,
"type": "Identifier",
"type": "Identifier"
},
"operator": "==",
"right": {
"end": 451,
"raw": "2",
"start": 450,
"type": "Literal",
"type": "Literal",
"value": 2.0
},
"start": 445,
"type": "BinaryExpression",
"type": "BinaryExpression"
},
{
"end": 518,
"raw": "\"both branches of and are false makes the whole expression false\"",
"start": 453,
"type": "Literal",
"type": "Literal",
"value": "both branches of and are false makes the whole expression false"
}
],
"callee": {
"end": 444,
"name": "assert",
"start": 438,
"type": "Identifier"
},
"end": 519,
"start": 438,
"type": "CallExpression",
"type": "CallExpression"
},
"start": 438,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
}
],
"end": 520,
"nonCodeMeta": {
"nonCodeNodes": {
"2": [
{
"end": 130,
"start": 128,
"type": "NonCodeNode",
"value": {
"type": "newLine"
}
}
],
"5": [
{
"end": 259,
"start": 257,
"type": "NonCodeNode",
"value": {
"type": "newLine"
}
}
],
"8": [
{
"end": 388,
"start": 386,
"type": "NonCodeNode",
"value": {
"type": "newLine"
}
}
]
},
"startNodes": []
},
"start": 0
}
}

View File

@ -0,0 +1,31 @@
aa = true & false
a = if aa {
1
} else {
2
}
assert(a == 2, "right branch of and is false makes the whole expression false")
bb = false & true
b = if bb {
1
} else {
2
}
assert(b == 2, "left branch of and is false makes the whole expression false")
cc = true & true
c = if cc {
1
} else {
2
}
assert(c == 1, "both branches of and are true makes the whole expression true")
dd = false & false
d = if dd {
1
} else {
2
}
assert(d == 2, "both branches of and are false makes the whole expression false")

View File

@ -0,0 +1,5 @@
---
source: kcl/src/simulation_tests.rs
description: Operations executed boolean_logical_and.kcl
---
[]

View File

@ -0,0 +1,167 @@
---
source: kcl/src/simulation_tests.rs
description: Program memory after executing boolean_logical_and.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": []
},
"a": {
"type": "Number",
"value": 2.0,
"__meta": [
{
"sourceRange": [
45,
46,
0
]
}
]
},
"aa": {
"type": "Bool",
"value": false,
"__meta": [
{
"sourceRange": [
5,
9,
0
]
},
{
"sourceRange": [
12,
17,
0
]
}
]
},
"b": {
"type": "Number",
"value": 2.0,
"__meta": [
{
"sourceRange": [
175,
176,
0
]
}
]
},
"bb": {
"type": "Bool",
"value": false,
"__meta": [
{
"sourceRange": [
135,
140,
0
]
},
{
"sourceRange": [
143,
147,
0
]
}
]
},
"c": {
"type": "Number",
"value": 1.0,
"__meta": [
{
"sourceRange": [
290,
291,
0
]
}
]
},
"cc": {
"type": "Bool",
"value": true,
"__meta": [
{
"sourceRange": [
264,
268,
0
]
},
{
"sourceRange": [
271,
275,
0
]
}
]
},
"d": {
"type": "Number",
"value": 2.0,
"__meta": [
{
"sourceRange": [
434,
435,
0
]
}
]
},
"dd": {
"type": "Bool",
"value": false,
"__meta": [
{
"sourceRange": [
393,
398,
0
]
},
{
"sourceRange": [
401,
406,
0
]
}
]
}
},
"parent": null
}
],
"currentEnv": 0,
"return": null
}

View File

@ -0,0 +1,422 @@
---
source: kcl/src/simulation_tests.rs
description: Result of parsing boolean_logical_multiple.kcl
---
{
"Ok": {
"body": [
{
"declaration": {
"end": 25,
"id": {
"end": 2,
"name": "ii",
"start": 0,
"type": "Identifier"
},
"init": {
"end": 25,
"left": {
"end": 9,
"raw": "true",
"start": 5,
"type": "Literal",
"type": "Literal",
"value": true
},
"operator": "|",
"right": {
"end": 25,
"left": {
"end": 17,
"raw": "false",
"start": 12,
"type": "Literal",
"type": "Literal",
"value": false
},
"operator": "&",
"right": {
"end": 25,
"raw": "false",
"start": 20,
"type": "Literal",
"type": "Literal",
"value": false
},
"start": 12,
"type": "BinaryExpression",
"type": "BinaryExpression"
},
"start": 5,
"type": "BinaryExpression",
"type": "BinaryExpression"
},
"start": 0,
"type": "VariableDeclarator"
},
"end": 25,
"kind": "const",
"start": 0,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
},
{
"declaration": {
"end": 56,
"id": {
"end": 27,
"name": "i",
"start": 26,
"type": "Identifier"
},
"init": {
"cond": {
"end": 35,
"name": "ii",
"start": 33,
"type": "Identifier",
"type": "Identifier"
},
"digest": null,
"else_ifs": [],
"end": 56,
"final_else": {
"body": [
{
"end": 54,
"expression": {
"end": 54,
"raw": "2",
"start": 53,
"type": "Literal",
"type": "Literal",
"value": 2.0
},
"start": 53,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
}
],
"end": 55,
"start": 53
},
"start": 30,
"then_val": {
"body": [
{
"end": 41,
"expression": {
"end": 41,
"raw": "1",
"start": 40,
"type": "Literal",
"type": "Literal",
"value": 1.0
},
"start": 40,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
}
],
"end": 42,
"start": 40
},
"type": "IfExpression",
"type": "IfExpression"
},
"start": 26,
"type": "VariableDeclarator"
},
"end": 56,
"kind": "const",
"start": 26,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
},
{
"end": 108,
"expression": {
"arguments": [
{
"end": 70,
"left": {
"end": 65,
"name": "i",
"start": 64,
"type": "Identifier",
"type": "Identifier"
},
"operator": "==",
"right": {
"end": 70,
"raw": "1",
"start": 69,
"type": "Literal",
"type": "Literal",
"value": 1.0
},
"start": 64,
"type": "BinaryExpression",
"type": "BinaryExpression"
},
{
"end": 107,
"raw": "\"and has higher precedence than or\"",
"start": 72,
"type": "Literal",
"type": "Literal",
"value": "and has higher precedence than or"
}
],
"callee": {
"end": 63,
"name": "assert",
"start": 57,
"type": "Identifier"
},
"end": 108,
"start": 57,
"type": "CallExpression",
"type": "CallExpression"
},
"start": 57,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
},
{
"declaration": {
"end": 151,
"id": {
"end": 112,
"name": "jj",
"start": 110,
"type": "Identifier"
},
"init": {
"end": 151,
"left": {
"end": 136,
"left": {
"end": 120,
"raw": "false",
"start": 115,
"type": "Literal",
"type": "Literal",
"value": false
},
"operator": "|",
"right": {
"end": 136,
"left": {
"end": 127,
"raw": "true",
"start": 123,
"type": "Literal",
"type": "Literal",
"value": true
},
"operator": "&",
"right": {
"argument": {
"end": 136,
"raw": "false",
"start": 131,
"type": "Literal",
"type": "Literal",
"value": false
},
"end": 136,
"operator": "!",
"start": 130,
"type": "UnaryExpression",
"type": "UnaryExpression"
},
"start": 123,
"type": "BinaryExpression",
"type": "BinaryExpression"
},
"start": 115,
"type": "BinaryExpression",
"type": "BinaryExpression"
},
"operator": "|",
"right": {
"end": 151,
"left": {
"end": 144,
"raw": "false",
"start": 139,
"type": "Literal",
"type": "Literal",
"value": false
},
"operator": "&",
"right": {
"end": 151,
"raw": "true",
"start": 147,
"type": "Literal",
"type": "Literal",
"value": true
},
"start": 139,
"type": "BinaryExpression",
"type": "BinaryExpression"
},
"start": 115,
"type": "BinaryExpression",
"type": "BinaryExpression"
},
"start": 110,
"type": "VariableDeclarator"
},
"end": 151,
"kind": "const",
"start": 110,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
},
{
"declaration": {
"end": 182,
"id": {
"end": 153,
"name": "j",
"start": 152,
"type": "Identifier"
},
"init": {
"cond": {
"end": 161,
"name": "jj",
"start": 159,
"type": "Identifier",
"type": "Identifier"
},
"digest": null,
"else_ifs": [],
"end": 182,
"final_else": {
"body": [
{
"end": 180,
"expression": {
"end": 180,
"raw": "2",
"start": 179,
"type": "Literal",
"type": "Literal",
"value": 2.0
},
"start": 179,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
}
],
"end": 181,
"start": 179
},
"start": 156,
"then_val": {
"body": [
{
"end": 167,
"expression": {
"end": 167,
"raw": "1",
"start": 166,
"type": "Literal",
"type": "Literal",
"value": 1.0
},
"start": 166,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
}
],
"end": 168,
"start": 166
},
"type": "IfExpression",
"type": "IfExpression"
},
"start": 152,
"type": "VariableDeclarator"
},
"end": 182,
"kind": "const",
"start": 152,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
},
{
"end": 227,
"expression": {
"arguments": [
{
"end": 196,
"left": {
"end": 191,
"name": "j",
"start": 190,
"type": "Identifier",
"type": "Identifier"
},
"operator": "==",
"right": {
"end": 196,
"raw": "1",
"start": 195,
"type": "Literal",
"type": "Literal",
"value": 1.0
},
"start": 190,
"type": "BinaryExpression",
"type": "BinaryExpression"
},
{
"end": 226,
"raw": "\"multiple logical operators\"",
"start": 198,
"type": "Literal",
"type": "Literal",
"value": "multiple logical operators"
}
],
"callee": {
"end": 189,
"name": "assert",
"start": 183,
"type": "Identifier"
},
"end": 227,
"start": 183,
"type": "CallExpression",
"type": "CallExpression"
},
"start": 183,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
}
],
"end": 228,
"nonCodeMeta": {
"nonCodeNodes": {
"2": [
{
"end": 110,
"start": 108,
"type": "NonCodeNode",
"value": {
"type": "newLine"
}
}
]
},
"startNodes": []
},
"start": 0
}
}

View File

@ -0,0 +1,15 @@
ii = true | false & false
i = if ii {
1
} else {
2
}
assert(i == 1, "and has higher precedence than or")
jj = false | true & !false | false & true
j = if jj {
1
} else {
2
}
assert(j == 1, "multiple logical operators")

View File

@ -0,0 +1,5 @@
---
source: kcl/src/simulation_tests.rs
description: Operations executed boolean_logical_multiple.kcl
---
[]

View File

@ -0,0 +1,129 @@
---
source: kcl/src/simulation_tests.rs
description: Program memory after executing boolean_logical_multiple.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": []
},
"i": {
"type": "Number",
"value": 1.0,
"__meta": [
{
"sourceRange": [
40,
41,
0
]
}
]
},
"ii": {
"type": "Bool",
"value": true,
"__meta": [
{
"sourceRange": [
5,
9,
0
]
},
{
"sourceRange": [
12,
17,
0
]
},
{
"sourceRange": [
20,
25,
0
]
}
]
},
"j": {
"type": "Number",
"value": 1.0,
"__meta": [
{
"sourceRange": [
166,
167,
0
]
}
]
},
"jj": {
"type": "Bool",
"value": true,
"__meta": [
{
"sourceRange": [
115,
120,
0
]
},
{
"sourceRange": [
123,
127,
0
]
},
{
"sourceRange": [
130,
136,
0
]
},
{
"sourceRange": [
139,
144,
0
]
},
{
"sourceRange": [
147,
151,
0
]
}
]
}
},
"parent": null
}
],
"currentEnv": 0,
"return": null
}

View File

@ -0,0 +1,703 @@
---
source: kcl/src/simulation_tests.rs
description: Result of parsing boolean_logical_or.kcl
---
{
"Ok": {
"body": [
{
"declaration": {
"end": 17,
"id": {
"end": 2,
"name": "aa",
"start": 0,
"type": "Identifier"
},
"init": {
"end": 17,
"left": {
"end": 9,
"raw": "true",
"start": 5,
"type": "Literal",
"type": "Literal",
"value": true
},
"operator": "|",
"right": {
"end": 17,
"raw": "false",
"start": 12,
"type": "Literal",
"type": "Literal",
"value": false
},
"start": 5,
"type": "BinaryExpression",
"type": "BinaryExpression"
},
"start": 0,
"type": "VariableDeclarator"
},
"end": 17,
"kind": "const",
"start": 0,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
},
{
"declaration": {
"end": 48,
"id": {
"end": 19,
"name": "a",
"start": 18,
"type": "Identifier"
},
"init": {
"cond": {
"end": 27,
"name": "aa",
"start": 25,
"type": "Identifier",
"type": "Identifier"
},
"digest": null,
"else_ifs": [],
"end": 48,
"final_else": {
"body": [
{
"end": 46,
"expression": {
"end": 46,
"raw": "2",
"start": 45,
"type": "Literal",
"type": "Literal",
"value": 2.0
},
"start": 45,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
}
],
"end": 47,
"start": 45
},
"start": 22,
"then_val": {
"body": [
{
"end": 33,
"expression": {
"end": 33,
"raw": "1",
"start": 32,
"type": "Literal",
"type": "Literal",
"value": 1.0
},
"start": 32,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
}
],
"end": 34,
"start": 32
},
"type": "IfExpression",
"type": "IfExpression"
},
"start": 18,
"type": "VariableDeclarator"
},
"end": 48,
"kind": "const",
"start": 18,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
},
{
"end": 124,
"expression": {
"arguments": [
{
"end": 62,
"left": {
"end": 57,
"name": "a",
"start": 56,
"type": "Identifier",
"type": "Identifier"
},
"operator": "==",
"right": {
"end": 62,
"raw": "1",
"start": 61,
"type": "Literal",
"type": "Literal",
"value": 1.0
},
"start": 56,
"type": "BinaryExpression",
"type": "BinaryExpression"
},
{
"end": 123,
"raw": "\"left branch of or is true makes the whole expression true\"",
"start": 64,
"type": "Literal",
"type": "Literal",
"value": "left branch of or is true makes the whole expression true"
}
],
"callee": {
"end": 55,
"name": "assert",
"start": 49,
"type": "Identifier"
},
"end": 124,
"start": 49,
"type": "CallExpression",
"type": "CallExpression"
},
"start": 49,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
},
{
"declaration": {
"end": 143,
"id": {
"end": 128,
"name": "bb",
"start": 126,
"type": "Identifier"
},
"init": {
"end": 143,
"left": {
"end": 136,
"raw": "false",
"start": 131,
"type": "Literal",
"type": "Literal",
"value": false
},
"operator": "|",
"right": {
"end": 143,
"raw": "true",
"start": 139,
"type": "Literal",
"type": "Literal",
"value": true
},
"start": 131,
"type": "BinaryExpression",
"type": "BinaryExpression"
},
"start": 126,
"type": "VariableDeclarator"
},
"end": 143,
"kind": "const",
"start": 126,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
},
{
"declaration": {
"end": 174,
"id": {
"end": 145,
"name": "b",
"start": 144,
"type": "Identifier"
},
"init": {
"cond": {
"end": 153,
"name": "bb",
"start": 151,
"type": "Identifier",
"type": "Identifier"
},
"digest": null,
"else_ifs": [],
"end": 174,
"final_else": {
"body": [
{
"end": 172,
"expression": {
"end": 172,
"raw": "2",
"start": 171,
"type": "Literal",
"type": "Literal",
"value": 2.0
},
"start": 171,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
}
],
"end": 173,
"start": 171
},
"start": 148,
"then_val": {
"body": [
{
"end": 159,
"expression": {
"end": 159,
"raw": "1",
"start": 158,
"type": "Literal",
"type": "Literal",
"value": 1.0
},
"start": 158,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
}
],
"end": 160,
"start": 158
},
"type": "IfExpression",
"type": "IfExpression"
},
"start": 144,
"type": "VariableDeclarator"
},
"end": 174,
"kind": "const",
"start": 144,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
},
{
"end": 251,
"expression": {
"arguments": [
{
"end": 188,
"left": {
"end": 183,
"name": "b",
"start": 182,
"type": "Identifier",
"type": "Identifier"
},
"operator": "==",
"right": {
"end": 188,
"raw": "1",
"start": 187,
"type": "Literal",
"type": "Literal",
"value": 1.0
},
"start": 182,
"type": "BinaryExpression",
"type": "BinaryExpression"
},
{
"end": 250,
"raw": "\"right branch of or is true makes the whole expression true\"",
"start": 190,
"type": "Literal",
"type": "Literal",
"value": "right branch of or is true makes the whole expression true"
}
],
"callee": {
"end": 181,
"name": "assert",
"start": 175,
"type": "Identifier"
},
"end": 251,
"start": 175,
"type": "CallExpression",
"type": "CallExpression"
},
"start": 175,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
},
{
"declaration": {
"end": 269,
"id": {
"end": 255,
"name": "cc",
"start": 253,
"type": "Identifier"
},
"init": {
"end": 269,
"left": {
"end": 262,
"raw": "true",
"start": 258,
"type": "Literal",
"type": "Literal",
"value": true
},
"operator": "|",
"right": {
"end": 269,
"raw": "true",
"start": 265,
"type": "Literal",
"type": "Literal",
"value": true
},
"start": 258,
"type": "BinaryExpression",
"type": "BinaryExpression"
},
"start": 253,
"type": "VariableDeclarator"
},
"end": 269,
"kind": "const",
"start": 253,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
},
{
"declaration": {
"end": 300,
"id": {
"end": 271,
"name": "c",
"start": 270,
"type": "Identifier"
},
"init": {
"cond": {
"end": 279,
"name": "cc",
"start": 277,
"type": "Identifier",
"type": "Identifier"
},
"digest": null,
"else_ifs": [],
"end": 300,
"final_else": {
"body": [
{
"end": 298,
"expression": {
"end": 298,
"raw": "2",
"start": 297,
"type": "Literal",
"type": "Literal",
"value": 2.0
},
"start": 297,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
}
],
"end": 299,
"start": 297
},
"start": 274,
"then_val": {
"body": [
{
"end": 285,
"expression": {
"end": 285,
"raw": "1",
"start": 284,
"type": "Literal",
"type": "Literal",
"value": 1.0
},
"start": 284,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
}
],
"end": 286,
"start": 284
},
"type": "IfExpression",
"type": "IfExpression"
},
"start": 270,
"type": "VariableDeclarator"
},
"end": 300,
"kind": "const",
"start": 270,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
},
{
"end": 379,
"expression": {
"arguments": [
{
"end": 314,
"left": {
"end": 309,
"name": "c",
"start": 308,
"type": "Identifier",
"type": "Identifier"
},
"operator": "==",
"right": {
"end": 314,
"raw": "1",
"start": 313,
"type": "Literal",
"type": "Literal",
"value": 1.0
},
"start": 308,
"type": "BinaryExpression",
"type": "BinaryExpression"
},
{
"end": 378,
"raw": "\"both branches of or are true makes the whole expression true\"",
"start": 316,
"type": "Literal",
"type": "Literal",
"value": "both branches of or are true makes the whole expression true"
}
],
"callee": {
"end": 307,
"name": "assert",
"start": 301,
"type": "Identifier"
},
"end": 379,
"start": 301,
"type": "CallExpression",
"type": "CallExpression"
},
"start": 301,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
},
{
"declaration": {
"end": 399,
"id": {
"end": 383,
"name": "dd",
"start": 381,
"type": "Identifier"
},
"init": {
"end": 399,
"left": {
"end": 391,
"raw": "false",
"start": 386,
"type": "Literal",
"type": "Literal",
"value": false
},
"operator": "|",
"right": {
"end": 399,
"raw": "false",
"start": 394,
"type": "Literal",
"type": "Literal",
"value": false
},
"start": 386,
"type": "BinaryExpression",
"type": "BinaryExpression"
},
"start": 381,
"type": "VariableDeclarator"
},
"end": 399,
"kind": "const",
"start": 381,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
},
{
"declaration": {
"end": 430,
"id": {
"end": 401,
"name": "d",
"start": 400,
"type": "Identifier"
},
"init": {
"cond": {
"end": 409,
"name": "dd",
"start": 407,
"type": "Identifier",
"type": "Identifier"
},
"digest": null,
"else_ifs": [],
"end": 430,
"final_else": {
"body": [
{
"end": 428,
"expression": {
"end": 428,
"raw": "2",
"start": 427,
"type": "Literal",
"type": "Literal",
"value": 2.0
},
"start": 427,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
}
],
"end": 429,
"start": 427
},
"start": 404,
"then_val": {
"body": [
{
"end": 415,
"expression": {
"end": 415,
"raw": "1",
"start": 414,
"type": "Literal",
"type": "Literal",
"value": 1.0
},
"start": 414,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
}
],
"end": 416,
"start": 414
},
"type": "IfExpression",
"type": "IfExpression"
},
"start": 400,
"type": "VariableDeclarator"
},
"end": 430,
"kind": "const",
"start": 400,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
},
{
"end": 511,
"expression": {
"arguments": [
{
"end": 444,
"left": {
"end": 439,
"name": "d",
"start": 438,
"type": "Identifier",
"type": "Identifier"
},
"operator": "==",
"right": {
"end": 444,
"raw": "2",
"start": 443,
"type": "Literal",
"type": "Literal",
"value": 2.0
},
"start": 438,
"type": "BinaryExpression",
"type": "BinaryExpression"
},
{
"end": 510,
"raw": "\"both branches of or are false makes the whole expression false\"",
"start": 446,
"type": "Literal",
"type": "Literal",
"value": "both branches of or are false makes the whole expression false"
}
],
"callee": {
"end": 437,
"name": "assert",
"start": 431,
"type": "Identifier"
},
"end": 511,
"start": 431,
"type": "CallExpression",
"type": "CallExpression"
},
"start": 431,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
}
],
"end": 512,
"nonCodeMeta": {
"nonCodeNodes": {
"2": [
{
"end": 126,
"start": 124,
"type": "NonCodeNode",
"value": {
"type": "newLine"
}
}
],
"5": [
{
"end": 253,
"start": 251,
"type": "NonCodeNode",
"value": {
"type": "newLine"
}
}
],
"8": [
{
"end": 381,
"start": 379,
"type": "NonCodeNode",
"value": {
"type": "newLine"
}
}
]
},
"startNodes": []
},
"start": 0
}
}

View File

@ -0,0 +1,31 @@
aa = true | false
a = if aa {
1
} else {
2
}
assert(a == 1, "left branch of or is true makes the whole expression true")
bb = false | true
b = if bb {
1
} else {
2
}
assert(b == 1, "right branch of or is true makes the whole expression true")
cc = true | true
c = if cc {
1
} else {
2
}
assert(c == 1, "both branches of or are true makes the whole expression true")
dd = false | false
d = if dd {
1
} else {
2
}
assert(d == 2, "both branches of or are false makes the whole expression false")

View File

@ -0,0 +1,5 @@
---
source: kcl/src/simulation_tests.rs
description: Operations executed boolean_logical_or.kcl
---
[]

View File

@ -0,0 +1,167 @@
---
source: kcl/src/simulation_tests.rs
description: Program memory after executing boolean_logical_or.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": []
},
"a": {
"type": "Number",
"value": 1.0,
"__meta": [
{
"sourceRange": [
32,
33,
0
]
}
]
},
"aa": {
"type": "Bool",
"value": true,
"__meta": [
{
"sourceRange": [
5,
9,
0
]
},
{
"sourceRange": [
12,
17,
0
]
}
]
},
"b": {
"type": "Number",
"value": 1.0,
"__meta": [
{
"sourceRange": [
158,
159,
0
]
}
]
},
"bb": {
"type": "Bool",
"value": true,
"__meta": [
{
"sourceRange": [
131,
136,
0
]
},
{
"sourceRange": [
139,
143,
0
]
}
]
},
"c": {
"type": "Number",
"value": 1.0,
"__meta": [
{
"sourceRange": [
284,
285,
0
]
}
]
},
"cc": {
"type": "Bool",
"value": true,
"__meta": [
{
"sourceRange": [
258,
262,
0
]
},
{
"sourceRange": [
265,
269,
0
]
}
]
},
"d": {
"type": "Number",
"value": 2.0,
"__meta": [
{
"sourceRange": [
427,
428,
0
]
}
]
},
"dd": {
"type": "Bool",
"value": false,
"__meta": [
{
"sourceRange": [
386,
391,
0
]
},
{
"sourceRange": [
394,
399,
0
]
}
]
}
},
"parent": null
}
],
"currentEnv": 0,
"return": null
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 132 KiB

View File

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

Some files were not shown because too many files have changed in this diff Show More