internal: KCL modules, part 1 (#4149)
Addresses #4080. (Not ready to close it yet.) # Important Requires a fix for #4147 before it can work in ZMA. # Overview ```kcl // numbers.kcl export fn inc = (x) => { return x + 1 } ``` ```kcl import inc from "numbers.kcl" answer = inc(41) ``` This also implements multiple imports with optional renaming. ```kcl import inc, dec from "numbers.kcl" import identity as id, length as len from "utils.kcl" ``` Note: Imported files _must_ be in the same directory. Things for a follow-up PR: - #4147. Currently, we cannot read files in WebAssembly, i.e. ZMA. - Docs - Should be an error to `import` anywhere besides the top level. Needs parser restructuring to track the context of a "function body". - Should be an error to have `export` anywhere besides the top level. It has no effect, but we should tell people it's not valid instead of silently ignoring it. - Error message for cycle detection is funky because the Rust side doesn't actually know the name of the first file. Message will say "b -> a -> b" instead of "a -> b -> a" when "a" is the top-level file. - Cache imported files so that they don't need to be re-parsed and re-executed.
This commit is contained in:
@ -82690,6 +82690,58 @@
|
||||
},
|
||||
"BodyItem": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"end",
|
||||
"items",
|
||||
"path",
|
||||
"raw_path",
|
||||
"start",
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ImportStatement"
|
||||
]
|
||||
},
|
||||
"start": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"end": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ImportItem"
|
||||
}
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"raw_path": {
|
||||
"type": "string"
|
||||
},
|
||||
"digest": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer",
|
||||
"format": "uint8",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"maxItems": 32,
|
||||
"minItems": 32,
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
@ -82763,6 +82815,9 @@
|
||||
"$ref": "#/components/schemas/VariableDeclarator"
|
||||
}
|
||||
},
|
||||
"visibility": {
|
||||
"$ref": "#/components/schemas/ItemVisibility"
|
||||
},
|
||||
"kind": {
|
||||
"$ref": "#/components/schemas/VariableKind"
|
||||
},
|
||||
@ -82822,6 +82877,54 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"ImportItem": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"end",
|
||||
"name",
|
||||
"start"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name of the item to import.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Identifier"
|
||||
}
|
||||
]
|
||||
},
|
||||
"alias": {
|
||||
"description": "Rename the item using an identifier after \"as\".",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Identifier"
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
},
|
||||
"start": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"end": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"digest": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer",
|
||||
"format": "uint8",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"maxItems": 32,
|
||||
"minItems": 32,
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"Expr": {
|
||||
"description": "An expression can be evaluated to yield a single KCL value.",
|
||||
"oneOf": [
|
||||
@ -84475,6 +84578,13 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"ItemVisibility": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"default",
|
||||
"export"
|
||||
]
|
||||
},
|
||||
"VariableKind": {
|
||||
"oneOf": [
|
||||
{
|
||||
@ -86338,6 +86448,58 @@
|
||||
},
|
||||
"BodyItem": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"end",
|
||||
"items",
|
||||
"path",
|
||||
"raw_path",
|
||||
"start",
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ImportStatement"
|
||||
]
|
||||
},
|
||||
"start": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"end": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ImportItem"
|
||||
}
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"raw_path": {
|
||||
"type": "string"
|
||||
},
|
||||
"digest": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer",
|
||||
"format": "uint8",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"maxItems": 32,
|
||||
"minItems": 32,
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
@ -86411,6 +86573,9 @@
|
||||
"$ref": "#/components/schemas/VariableDeclarator"
|
||||
}
|
||||
},
|
||||
"visibility": {
|
||||
"$ref": "#/components/schemas/ItemVisibility"
|
||||
},
|
||||
"kind": {
|
||||
"$ref": "#/components/schemas/VariableKind"
|
||||
},
|
||||
@ -86470,6 +86635,54 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"ImportItem": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"end",
|
||||
"name",
|
||||
"start"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name of the item to import.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Identifier"
|
||||
}
|
||||
]
|
||||
},
|
||||
"alias": {
|
||||
"description": "Rename the item using an identifier after \"as\".",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Identifier"
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
},
|
||||
"start": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"end": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"digest": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer",
|
||||
"format": "uint8",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"maxItems": 32,
|
||||
"minItems": 32,
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"Expr": {
|
||||
"description": "An expression can be evaluated to yield a single KCL value.",
|
||||
"oneOf": [
|
||||
@ -88123,6 +88336,13 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"ItemVisibility": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"default",
|
||||
"export"
|
||||
]
|
||||
},
|
||||
"VariableKind": {
|
||||
"oneOf": [
|
||||
{
|
||||
@ -89990,6 +90210,58 @@
|
||||
},
|
||||
"BodyItem": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"end",
|
||||
"items",
|
||||
"path",
|
||||
"raw_path",
|
||||
"start",
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ImportStatement"
|
||||
]
|
||||
},
|
||||
"start": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"end": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ImportItem"
|
||||
}
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"raw_path": {
|
||||
"type": "string"
|
||||
},
|
||||
"digest": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer",
|
||||
"format": "uint8",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"maxItems": 32,
|
||||
"minItems": 32,
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
@ -90063,6 +90335,9 @@
|
||||
"$ref": "#/components/schemas/VariableDeclarator"
|
||||
}
|
||||
},
|
||||
"visibility": {
|
||||
"$ref": "#/components/schemas/ItemVisibility"
|
||||
},
|
||||
"kind": {
|
||||
"$ref": "#/components/schemas/VariableKind"
|
||||
},
|
||||
@ -90122,6 +90397,54 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"ImportItem": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"end",
|
||||
"name",
|
||||
"start"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name of the item to import.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Identifier"
|
||||
}
|
||||
]
|
||||
},
|
||||
"alias": {
|
||||
"description": "Rename the item using an identifier after \"as\".",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Identifier"
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
},
|
||||
"start": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"end": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"digest": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer",
|
||||
"format": "uint8",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"maxItems": 32,
|
||||
"minItems": 32,
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"Expr": {
|
||||
"description": "An expression can be evaluated to yield a single KCL value.",
|
||||
"oneOf": [
|
||||
@ -91775,6 +92098,13 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"ItemVisibility": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"default",
|
||||
"export"
|
||||
]
|
||||
},
|
||||
"VariableKind": {
|
||||
"oneOf": [
|
||||
{
|
||||
@ -114494,6 +114824,58 @@
|
||||
},
|
||||
"BodyItem": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"end",
|
||||
"items",
|
||||
"path",
|
||||
"raw_path",
|
||||
"start",
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ImportStatement"
|
||||
]
|
||||
},
|
||||
"start": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"end": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ImportItem"
|
||||
}
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"raw_path": {
|
||||
"type": "string"
|
||||
},
|
||||
"digest": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer",
|
||||
"format": "uint8",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"maxItems": 32,
|
||||
"minItems": 32,
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
@ -114567,6 +114949,9 @@
|
||||
"$ref": "#/components/schemas/VariableDeclarator"
|
||||
}
|
||||
},
|
||||
"visibility": {
|
||||
"$ref": "#/components/schemas/ItemVisibility"
|
||||
},
|
||||
"kind": {
|
||||
"$ref": "#/components/schemas/VariableKind"
|
||||
},
|
||||
@ -114626,6 +115011,54 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"ImportItem": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"end",
|
||||
"name",
|
||||
"start"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name of the item to import.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Identifier"
|
||||
}
|
||||
]
|
||||
},
|
||||
"alias": {
|
||||
"description": "Rename the item using an identifier after \"as\".",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Identifier"
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
},
|
||||
"start": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"end": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"digest": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer",
|
||||
"format": "uint8",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"maxItems": 32,
|
||||
"minItems": 32,
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"Expr": {
|
||||
"description": "An expression can be evaluated to yield a single KCL value.",
|
||||
"oneOf": [
|
||||
@ -116279,6 +116712,13 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"ItemVisibility": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"default",
|
||||
"export"
|
||||
]
|
||||
},
|
||||
"VariableKind": {
|
||||
"oneOf": [
|
||||
{
|
||||
@ -118535,6 +118975,58 @@
|
||||
},
|
||||
"BodyItem": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"end",
|
||||
"items",
|
||||
"path",
|
||||
"raw_path",
|
||||
"start",
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ImportStatement"
|
||||
]
|
||||
},
|
||||
"start": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"end": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ImportItem"
|
||||
}
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"raw_path": {
|
||||
"type": "string"
|
||||
},
|
||||
"digest": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer",
|
||||
"format": "uint8",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"maxItems": 32,
|
||||
"minItems": 32,
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
@ -118608,6 +119100,9 @@
|
||||
"$ref": "#/components/schemas/VariableDeclarator"
|
||||
}
|
||||
},
|
||||
"visibility": {
|
||||
"$ref": "#/components/schemas/ItemVisibility"
|
||||
},
|
||||
"kind": {
|
||||
"$ref": "#/components/schemas/VariableKind"
|
||||
},
|
||||
@ -118667,6 +119162,54 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"ImportItem": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"end",
|
||||
"name",
|
||||
"start"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name of the item to import.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Identifier"
|
||||
}
|
||||
]
|
||||
},
|
||||
"alias": {
|
||||
"description": "Rename the item using an identifier after \"as\".",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Identifier"
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
},
|
||||
"start": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"end": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"digest": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer",
|
||||
"format": "uint8",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"maxItems": 32,
|
||||
"minItems": 32,
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"Expr": {
|
||||
"description": "An expression can be evaluated to yield a single KCL value.",
|
||||
"oneOf": [
|
||||
@ -120320,6 +120863,13 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"ItemVisibility": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"default",
|
||||
"export"
|
||||
]
|
||||
},
|
||||
"VariableKind": {
|
||||
"oneOf": [
|
||||
{
|
||||
@ -122183,6 +122733,58 @@
|
||||
},
|
||||
"BodyItem": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"end",
|
||||
"items",
|
||||
"path",
|
||||
"raw_path",
|
||||
"start",
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ImportStatement"
|
||||
]
|
||||
},
|
||||
"start": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"end": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ImportItem"
|
||||
}
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"raw_path": {
|
||||
"type": "string"
|
||||
},
|
||||
"digest": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer",
|
||||
"format": "uint8",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"maxItems": 32,
|
||||
"minItems": 32,
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
@ -122256,6 +122858,9 @@
|
||||
"$ref": "#/components/schemas/VariableDeclarator"
|
||||
}
|
||||
},
|
||||
"visibility": {
|
||||
"$ref": "#/components/schemas/ItemVisibility"
|
||||
},
|
||||
"kind": {
|
||||
"$ref": "#/components/schemas/VariableKind"
|
||||
},
|
||||
@ -122315,6 +122920,54 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"ImportItem": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"end",
|
||||
"name",
|
||||
"start"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name of the item to import.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Identifier"
|
||||
}
|
||||
]
|
||||
},
|
||||
"alias": {
|
||||
"description": "Rename the item using an identifier after \"as\".",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Identifier"
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
},
|
||||
"start": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"end": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"digest": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer",
|
||||
"format": "uint8",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"maxItems": 32,
|
||||
"minItems": 32,
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"Expr": {
|
||||
"description": "An expression can be evaluated to yield a single KCL value.",
|
||||
"oneOf": [
|
||||
@ -123968,6 +124621,13 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"ItemVisibility": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"default",
|
||||
"export"
|
||||
]
|
||||
},
|
||||
"VariableKind": {
|
||||
"oneOf": [
|
||||
{
|
||||
@ -125829,6 +126489,58 @@
|
||||
},
|
||||
"BodyItem": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"end",
|
||||
"items",
|
||||
"path",
|
||||
"raw_path",
|
||||
"start",
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ImportStatement"
|
||||
]
|
||||
},
|
||||
"start": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"end": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ImportItem"
|
||||
}
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"raw_path": {
|
||||
"type": "string"
|
||||
},
|
||||
"digest": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer",
|
||||
"format": "uint8",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"maxItems": 32,
|
||||
"minItems": 32,
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
@ -125902,6 +126614,9 @@
|
||||
"$ref": "#/components/schemas/VariableDeclarator"
|
||||
}
|
||||
},
|
||||
"visibility": {
|
||||
"$ref": "#/components/schemas/ItemVisibility"
|
||||
},
|
||||
"kind": {
|
||||
"$ref": "#/components/schemas/VariableKind"
|
||||
},
|
||||
@ -125961,6 +126676,54 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"ImportItem": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"end",
|
||||
"name",
|
||||
"start"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name of the item to import.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Identifier"
|
||||
}
|
||||
]
|
||||
},
|
||||
"alias": {
|
||||
"description": "Rename the item using an identifier after \"as\".",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Identifier"
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
},
|
||||
"start": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"end": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"digest": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer",
|
||||
"format": "uint8",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"maxItems": 32,
|
||||
"minItems": 32,
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"Expr": {
|
||||
"description": "An expression can be evaluated to yield a single KCL value.",
|
||||
"oneOf": [
|
||||
@ -127614,6 +128377,13 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"ItemVisibility": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"default",
|
||||
"export"
|
||||
]
|
||||
},
|
||||
"VariableKind": {
|
||||
"oneOf": [
|
||||
{
|
||||
|
@ -18,6 +18,27 @@ layout: manual
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `ImportStatement`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `items` |`[` [`ImportItem`](/docs/kcl/types/ImportItem) `]`| | No |
|
||||
| `path` |`string`| | No |
|
||||
| `raw_path` |`string`| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
@ -45,6 +66,7 @@ layout: manual
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `declarations` |`[` [`VariableDeclarator`](/docs/kcl/types/VariableDeclarator) `]`| | No |
|
||||
| `visibility` |[`ItemVisibility`](/docs/kcl/types/ItemVisibility)| | No |
|
||||
| `kind` |[`VariableKind`](/docs/kcl/types/VariableKind)| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
|
||||
|
24
docs/kcl/types/ImportItem.md
Normal file
24
docs/kcl/types/ImportItem.md
Normal file
@ -0,0 +1,24 @@
|
||||
---
|
||||
title: "ImportItem"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `name` |[`Identifier`](/docs/kcl/types/Identifier)| Name of the item to import. | No |
|
||||
| `alias` |[`Identifier`](/docs/kcl/types/Identifier)| Rename the item using an identifier after "as". | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
|
||||
|
16
docs/kcl/types/ItemVisibility.md
Normal file
16
docs/kcl/types/ItemVisibility.md
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
title: "ItemVisibility"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
**enum:** `default`, `export`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -1,6 +1,9 @@
|
||||
import { styleTags, tags as t } from '@lezer/highlight'
|
||||
|
||||
export const kclHighlight = styleTags({
|
||||
'import export': t.moduleKeyword,
|
||||
ImportItemAs: t.definitionKeyword,
|
||||
ImportFrom: t.moduleKeyword,
|
||||
'fn var let const': t.definitionKeyword,
|
||||
'if else': t.controlKeyword,
|
||||
return: t.controlKeyword,
|
||||
|
@ -15,8 +15,9 @@
|
||||
}
|
||||
|
||||
statement[@isGroup=Statement] {
|
||||
FunctionDeclaration { kw<"fn"> VariableDefinition Equals ParamList Arrow Body } |
|
||||
VariableDeclaration { (kw<"var"> | kw<"let"> | kw<"const">)? VariableDefinition Equals expression } |
|
||||
ImportStatement { kw<"import"> ImportItems ImportFrom String } |
|
||||
FunctionDeclaration { kw<"export">? kw<"fn"> VariableDefinition Equals ParamList Arrow Body } |
|
||||
VariableDeclaration { kw<"export">? (kw<"var"> | kw<"let"> | kw<"const">)? VariableDefinition Equals expression } |
|
||||
ReturnStatement { kw<"return"> expression } |
|
||||
ExpressionStatement { expression }
|
||||
}
|
||||
@ -25,6 +26,9 @@ ParamList { "(" commaSep<Parameter { VariableDefinition "?"? (":" type)? }> ")"
|
||||
|
||||
Body { "{" statement* "}" }
|
||||
|
||||
ImportItems { commaSep1NoTrailingComma<ImportItem> }
|
||||
ImportItem { identifier (ImportItemAs identifier)? }
|
||||
|
||||
expression[@isGroup=Expression] {
|
||||
String |
|
||||
Number |
|
||||
@ -74,6 +78,8 @@ kw<term> { @specialize[@name={term}]<identifier, term> }
|
||||
|
||||
commaSep<term> { (term ("," term)*)? ","? }
|
||||
|
||||
commaSep1NoTrailingComma<term> { term ("," term)* }
|
||||
|
||||
@tokens {
|
||||
String[isolate] { "'" ("\\" _ | !['\\])* "'" | '"' ("\\" _ | !["\\])* '"' }
|
||||
|
||||
@ -106,6 +112,9 @@ commaSep<term> { (term ("," term)*)? ","? }
|
||||
|
||||
Shebang { "#!" ![\n]* }
|
||||
|
||||
ImportItemAs { "as" }
|
||||
ImportFrom { "from" }
|
||||
|
||||
"(" ")"
|
||||
"{" "}"
|
||||
"[" "]"
|
||||
|
@ -501,6 +501,7 @@ export function sketchOnExtrudedFace(
|
||||
createIdentifier(extrudeName ? extrudeName : oldSketchName),
|
||||
_tag,
|
||||
]),
|
||||
undefined,
|
||||
'const'
|
||||
)
|
||||
|
||||
@ -682,6 +683,7 @@ export function createPipeExpression(
|
||||
export function createVariableDeclaration(
|
||||
varName: string,
|
||||
init: VariableDeclarator['init'],
|
||||
visibility: VariableDeclaration['visibility'] = 'default',
|
||||
kind: VariableDeclaration['kind'] = 'const'
|
||||
): VariableDeclaration {
|
||||
return {
|
||||
@ -699,6 +701,7 @@ export function createVariableDeclaration(
|
||||
init,
|
||||
},
|
||||
],
|
||||
visibility,
|
||||
kind,
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ import {
|
||||
getConstraintType,
|
||||
} from './std/sketchcombos'
|
||||
import { err } from 'lib/trap'
|
||||
import { ImportStatement } from 'wasm-lib/kcl/bindings/ImportStatement'
|
||||
|
||||
/**
|
||||
* Retrieves a node from a given path within a Program node structure, optionally stopping at a specified node type.
|
||||
@ -120,7 +121,12 @@ export function getNodeFromPathCurry(
|
||||
}
|
||||
|
||||
function moreNodePathFromSourceRange(
|
||||
node: Expr | ExpressionStatement | VariableDeclaration | ReturnStatement,
|
||||
node:
|
||||
| Expr
|
||||
| ImportStatement
|
||||
| ExpressionStatement
|
||||
| VariableDeclaration
|
||||
| ReturnStatement,
|
||||
sourceRange: Selection['range'],
|
||||
previousPath: PathToNode = [['body', '']]
|
||||
): PathToNode {
|
||||
|
@ -426,6 +426,7 @@ export const _executor = async (
|
||||
baseUnit,
|
||||
engineCommandManager,
|
||||
fileSystemManager,
|
||||
undefined,
|
||||
isMock
|
||||
)
|
||||
return execStateFromRaw(execState)
|
||||
|
@ -762,7 +762,7 @@ fn generate_code_block_test(fn_name: &str, code_block: &str, index: usize) -> pr
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
|
||||
ctx.run(&program, None, id_generator).await.unwrap();
|
||||
ctx.run(&program, None, id_generator, None).await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
|
@ -17,7 +17,7 @@ mod test_examples_someFn {
|
||||
settings: Default::default(),
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, None, id_generator).await.unwrap();
|
||||
ctx.run(&program, None, id_generator, None).await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
|
@ -17,7 +17,7 @@ mod test_examples_someFn {
|
||||
settings: Default::default(),
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, None, id_generator).await.unwrap();
|
||||
ctx.run(&program, None, id_generator, None).await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
|
@ -17,7 +17,7 @@ mod test_examples_show {
|
||||
settings: Default::default(),
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, None, id_generator).await.unwrap();
|
||||
ctx.run(&program, None, id_generator, None).await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
@ -51,7 +51,7 @@ mod test_examples_show {
|
||||
settings: Default::default(),
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, None, id_generator).await.unwrap();
|
||||
ctx.run(&program, None, id_generator, None).await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
|
@ -17,7 +17,7 @@ mod test_examples_show {
|
||||
settings: Default::default(),
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, None, id_generator).await.unwrap();
|
||||
ctx.run(&program, None, id_generator, None).await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
|
@ -18,7 +18,7 @@ mod test_examples_my_func {
|
||||
settings: Default::default(),
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, None, id_generator).await.unwrap();
|
||||
ctx.run(&program, None, id_generator, None).await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
@ -52,7 +52,7 @@ mod test_examples_my_func {
|
||||
settings: Default::default(),
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, None, id_generator).await.unwrap();
|
||||
ctx.run(&program, None, id_generator, None).await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
|
@ -18,7 +18,7 @@ mod test_examples_line_to {
|
||||
settings: Default::default(),
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, None, id_generator).await.unwrap();
|
||||
ctx.run(&program, None, id_generator, None).await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
@ -52,7 +52,7 @@ mod test_examples_line_to {
|
||||
settings: Default::default(),
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, None, id_generator).await.unwrap();
|
||||
ctx.run(&program, None, id_generator, None).await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
|
@ -17,7 +17,7 @@ mod test_examples_min {
|
||||
settings: Default::default(),
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, None, id_generator).await.unwrap();
|
||||
ctx.run(&program, None, id_generator, None).await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
@ -51,7 +51,7 @@ mod test_examples_min {
|
||||
settings: Default::default(),
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, None, id_generator).await.unwrap();
|
||||
ctx.run(&program, None, id_generator, None).await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
|
@ -17,7 +17,7 @@ mod test_examples_show {
|
||||
settings: Default::default(),
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, None, id_generator).await.unwrap();
|
||||
ctx.run(&program, None, id_generator, None).await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
|
@ -17,7 +17,7 @@ mod test_examples_import {
|
||||
settings: Default::default(),
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, None, id_generator).await.unwrap();
|
||||
ctx.run(&program, None, id_generator, None).await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
|
@ -17,7 +17,7 @@ mod test_examples_import {
|
||||
settings: Default::default(),
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, None, id_generator).await.unwrap();
|
||||
ctx.run(&program, None, id_generator, None).await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
|
@ -17,7 +17,7 @@ mod test_examples_import {
|
||||
settings: Default::default(),
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, None, id_generator).await.unwrap();
|
||||
ctx.run(&program, None, id_generator, None).await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
|
@ -17,7 +17,7 @@ mod test_examples_show {
|
||||
settings: Default::default(),
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, None, id_generator).await.unwrap();
|
||||
ctx.run(&program, None, id_generator, None).await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
|
@ -17,7 +17,7 @@ mod test_examples_some_function {
|
||||
settings: Default::default(),
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, None, id_generator).await.unwrap();
|
||||
ctx.run(&program, None, id_generator, None).await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
|
@ -1,7 +1,7 @@
|
||||
extern crate alloc;
|
||||
use kcl_lib::ast::types::{
|
||||
BodyItem, Expr, Identifier, Literal, LiteralValue, NonCodeMeta, Program, VariableDeclaration, VariableDeclarator,
|
||||
VariableKind,
|
||||
BodyItem, Expr, Identifier, ItemVisibility, Literal, LiteralValue, NonCodeMeta, Program, VariableDeclaration,
|
||||
VariableDeclarator, VariableKind,
|
||||
};
|
||||
use kcl_macros::parse;
|
||||
use pretty_assertions::assert_eq;
|
||||
@ -33,6 +33,7 @@ fn basic() {
|
||||
})),
|
||||
digest: None,
|
||||
}],
|
||||
visibility: ItemVisibility::Default,
|
||||
kind: VariableKind::Const,
|
||||
digest: None,
|
||||
})],
|
||||
|
@ -178,7 +178,7 @@ async fn snapshot_endpoint(body: Bytes, state: ExecutorContext) -> Response<Body
|
||||
// Let users know if the test is taking a long time.
|
||||
let (done_tx, done_rx) = oneshot::channel::<()>();
|
||||
let timer = time_until(done_rx);
|
||||
let snapshot = match state.execute_and_prepare_snapshot(&program, id_generator).await {
|
||||
let snapshot = match state.execute_and_prepare_snapshot(&program, id_generator, None).await {
|
||||
Ok(sn) => sn,
|
||||
Err(e) => return kcl_err(e),
|
||||
};
|
||||
|
@ -1,6 +1,7 @@
|
||||
use anyhow::Result;
|
||||
use indexmap::IndexMap;
|
||||
use kcl_lib::{
|
||||
engine::ExecutionKind,
|
||||
errors::KclError,
|
||||
executor::{DefaultPlanes, IdGenerator},
|
||||
};
|
||||
@ -26,6 +27,7 @@ pub struct EngineConnection {
|
||||
batch_end: Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, kcl_lib::executor::SourceRange)>>>,
|
||||
core_test: Arc<Mutex<String>>,
|
||||
default_planes: Arc<RwLock<Option<DefaultPlanes>>>,
|
||||
execution_kind: Arc<Mutex<ExecutionKind>>,
|
||||
}
|
||||
|
||||
impl EngineConnection {
|
||||
@ -39,6 +41,7 @@ impl EngineConnection {
|
||||
batch_end: Arc::new(Mutex::new(IndexMap::new())),
|
||||
core_test: result,
|
||||
default_planes: Default::default(),
|
||||
execution_kind: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
@ -360,6 +363,18 @@ impl kcl_lib::engine::EngineManager for EngineConnection {
|
||||
self.batch_end.clone()
|
||||
}
|
||||
|
||||
fn execution_kind(&self) -> ExecutionKind {
|
||||
let guard = self.execution_kind.lock().unwrap();
|
||||
*guard
|
||||
}
|
||||
|
||||
fn replace_execution_kind(&self, execution_kind: ExecutionKind) -> ExecutionKind {
|
||||
let mut guard = self.execution_kind.lock().unwrap();
|
||||
let original = *guard;
|
||||
*guard = execution_kind;
|
||||
original
|
||||
}
|
||||
|
||||
async fn default_planes(
|
||||
&self,
|
||||
id_generator: &mut IdGenerator,
|
||||
|
@ -23,7 +23,7 @@ pub async fn kcl_to_engine_core(code: &str) -> Result<String> {
|
||||
settings: Default::default(),
|
||||
context_type: kcl_lib::executor::ContextType::MockCustomForwarded,
|
||||
};
|
||||
let _memory = ctx.run(&program, None, IdGenerator::default()).await?;
|
||||
let _memory = ctx.run(&program, None, IdGenerator::default(), None).await?;
|
||||
|
||||
let result = result.lock().expect("mutex lock").clone();
|
||||
Ok(result)
|
||||
|
@ -48,7 +48,10 @@ pub async fn modify_ast_for_sketch(
|
||||
|
||||
// Get the information about the sketch.
|
||||
if let Some(ast_sketch) = program.get_variable(sketch_name) {
|
||||
let constraint_level = ast_sketch.get_constraint_level();
|
||||
let constraint_level = match ast_sketch {
|
||||
super::types::Definition::Variable(var) => var.get_constraint_level(),
|
||||
super::types::Definition::Import(import) => import.get_constraint_level(),
|
||||
};
|
||||
match &constraint_level {
|
||||
ConstraintLevel::None { source_ranges: _ } => {}
|
||||
ConstraintLevel::Ignore { source_ranges: _ } => {}
|
||||
|
@ -40,6 +40,11 @@ mod none;
|
||||
/// Position-independent digest of the AST node.
|
||||
pub type Digest = [u8; 32];
|
||||
|
||||
pub enum Definition<'a> {
|
||||
Variable(&'a VariableDeclarator),
|
||||
Import(&'a ImportStatement),
|
||||
}
|
||||
|
||||
/// A KCL program top level, or function body.
|
||||
#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Bake)]
|
||||
#[databake(path = kcl_lib::ast::types)]
|
||||
@ -198,6 +203,7 @@ impl Program {
|
||||
|
||||
// Recurse over the item.
|
||||
match item {
|
||||
BodyItem::ImportStatement(_) => None,
|
||||
BodyItem::ExpressionStatement(expression_statement) => Some(&expression_statement.expression),
|
||||
BodyItem::VariableDeclaration(variable_declaration) => variable_declaration.get_expr_for_position(pos),
|
||||
BodyItem::ReturnStatement(return_statement) => Some(&return_statement.argument),
|
||||
@ -214,6 +220,7 @@ impl Program {
|
||||
|
||||
// Recurse over the item.
|
||||
let expr = match item {
|
||||
BodyItem::ImportStatement(_) => None,
|
||||
BodyItem::ExpressionStatement(expression_statement) => Some(&expression_statement.expression),
|
||||
BodyItem::VariableDeclaration(variable_declaration) => variable_declaration.get_expr_for_position(pos),
|
||||
BodyItem::ReturnStatement(return_statement) => Some(&return_statement.argument),
|
||||
@ -257,6 +264,7 @@ impl Program {
|
||||
// We only care about the top level things in the program.
|
||||
for item in &self.body {
|
||||
match item {
|
||||
BodyItem::ImportStatement(_) => continue,
|
||||
BodyItem::ExpressionStatement(expression_statement) => {
|
||||
if let Some(folding_range) = expression_statement.expression.get_lsp_folding_range() {
|
||||
ranges.push(folding_range)
|
||||
@ -280,6 +288,12 @@ impl Program {
|
||||
let mut old_name = None;
|
||||
for item in &mut self.body {
|
||||
match item {
|
||||
BodyItem::ImportStatement(stmt) => {
|
||||
if let Some(var_old_name) = stmt.rename_symbol(new_name, pos) {
|
||||
old_name = Some(var_old_name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
BodyItem::ExpressionStatement(_expression_statement) => {
|
||||
continue;
|
||||
}
|
||||
@ -306,6 +320,7 @@ impl Program {
|
||||
|
||||
// Recurse over the item.
|
||||
let mut value = match item {
|
||||
BodyItem::ImportStatement(_) => None, // TODO
|
||||
BodyItem::ExpressionStatement(ref mut expression_statement) => {
|
||||
Some(&mut expression_statement.expression)
|
||||
}
|
||||
@ -337,6 +352,9 @@ impl Program {
|
||||
fn rename_identifiers(&mut self, old_name: &str, new_name: &str) {
|
||||
for item in &mut self.body {
|
||||
match item {
|
||||
BodyItem::ImportStatement(ref mut stmt) => {
|
||||
stmt.rename_identifiers(old_name, new_name);
|
||||
}
|
||||
BodyItem::ExpressionStatement(ref mut expression_statement) => {
|
||||
expression_statement.expression.rename_identifiers(old_name, new_name);
|
||||
}
|
||||
@ -354,6 +372,9 @@ impl Program {
|
||||
pub fn replace_variable(&mut self, name: &str, declarator: VariableDeclarator) {
|
||||
for item in &mut self.body {
|
||||
match item {
|
||||
BodyItem::ImportStatement(_) => {
|
||||
continue;
|
||||
}
|
||||
BodyItem::ExpressionStatement(_expression_statement) => {
|
||||
continue;
|
||||
}
|
||||
@ -374,6 +395,7 @@ impl Program {
|
||||
pub fn replace_value(&mut self, source_range: SourceRange, new_value: Expr) {
|
||||
for item in &mut self.body {
|
||||
match item {
|
||||
BodyItem::ImportStatement(_) => {} // TODO
|
||||
BodyItem::ExpressionStatement(ref mut expression_statement) => expression_statement
|
||||
.expression
|
||||
.replace_value(source_range, new_value.clone()),
|
||||
@ -388,16 +410,23 @@ impl Program {
|
||||
}
|
||||
|
||||
/// Get the variable declaration with the given name.
|
||||
pub fn get_variable(&self, name: &str) -> Option<&VariableDeclarator> {
|
||||
pub fn get_variable(&self, name: &str) -> Option<Definition<'_>> {
|
||||
for item in &self.body {
|
||||
match item {
|
||||
BodyItem::ImportStatement(stmt) => {
|
||||
for import_item in &stmt.items {
|
||||
if import_item.identifier() == name {
|
||||
return Some(Definition::Import(stmt.as_ref()));
|
||||
}
|
||||
}
|
||||
}
|
||||
BodyItem::ExpressionStatement(_expression_statement) => {
|
||||
continue;
|
||||
}
|
||||
BodyItem::VariableDeclaration(variable_declaration) => {
|
||||
for declaration in &variable_declaration.declarations {
|
||||
if declaration.id.name == name {
|
||||
return Some(declaration);
|
||||
return Some(Definition::Variable(declaration));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -454,6 +483,7 @@ pub(crate) use impl_value_meta;
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum BodyItem {
|
||||
ImportStatement(Box<ImportStatement>),
|
||||
ExpressionStatement(ExpressionStatement),
|
||||
VariableDeclaration(VariableDeclaration),
|
||||
ReturnStatement(ReturnStatement),
|
||||
@ -462,6 +492,7 @@ pub enum BodyItem {
|
||||
impl BodyItem {
|
||||
pub fn compute_digest(&mut self) -> Digest {
|
||||
match self {
|
||||
BodyItem::ImportStatement(s) => s.compute_digest(),
|
||||
BodyItem::ExpressionStatement(es) => es.compute_digest(),
|
||||
BodyItem::VariableDeclaration(vs) => vs.compute_digest(),
|
||||
BodyItem::ReturnStatement(rs) => rs.compute_digest(),
|
||||
@ -470,6 +501,7 @@ impl BodyItem {
|
||||
|
||||
pub fn start(&self) -> usize {
|
||||
match self {
|
||||
BodyItem::ImportStatement(stmt) => stmt.start(),
|
||||
BodyItem::ExpressionStatement(expression_statement) => expression_statement.start(),
|
||||
BodyItem::VariableDeclaration(variable_declaration) => variable_declaration.start(),
|
||||
BodyItem::ReturnStatement(return_statement) => return_statement.start(),
|
||||
@ -478,6 +510,7 @@ impl BodyItem {
|
||||
|
||||
pub fn end(&self) -> usize {
|
||||
match self {
|
||||
BodyItem::ImportStatement(stmt) => stmt.end(),
|
||||
BodyItem::ExpressionStatement(expression_statement) => expression_statement.end(),
|
||||
BodyItem::VariableDeclaration(variable_declaration) => variable_declaration.end(),
|
||||
BodyItem::ReturnStatement(return_statement) => return_statement.end(),
|
||||
@ -1123,6 +1156,124 @@ impl NonCodeMeta {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Bake)]
|
||||
#[databake(path = kcl_lib::ast::types)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
pub struct ImportItem {
|
||||
/// Name of the item to import.
|
||||
pub name: Identifier,
|
||||
/// Rename the item using an identifier after "as".
|
||||
pub alias: Option<Identifier>,
|
||||
|
||||
pub start: usize,
|
||||
pub end: usize,
|
||||
|
||||
pub digest: Option<Digest>,
|
||||
}
|
||||
|
||||
impl_value_meta!(ImportItem);
|
||||
|
||||
impl ImportItem {
|
||||
compute_digest!(|slf, hasher| {
|
||||
let name = slf.name.name.as_bytes();
|
||||
hasher.update(name.len().to_ne_bytes());
|
||||
hasher.update(name);
|
||||
if let Some(alias) = &mut slf.alias {
|
||||
hasher.update([1]);
|
||||
hasher.update(alias.compute_digest());
|
||||
} else {
|
||||
hasher.update([0]);
|
||||
}
|
||||
});
|
||||
|
||||
pub fn identifier(&self) -> &str {
|
||||
match &self.alias {
|
||||
Some(alias) => &alias.name,
|
||||
None => &self.name.name,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rename_symbol(&mut self, new_name: &str, pos: usize) -> Option<String> {
|
||||
match &mut self.alias {
|
||||
Some(alias) => {
|
||||
let alias_source_range = SourceRange::from(&*alias);
|
||||
if !alias_source_range.contains(pos) {
|
||||
return None;
|
||||
}
|
||||
let old_name = std::mem::replace(&mut alias.name, new_name.to_owned());
|
||||
Some(old_name)
|
||||
}
|
||||
None => {
|
||||
let use_source_range = SourceRange::from(&*self);
|
||||
if use_source_range.contains(pos) {
|
||||
self.alias = Some(Identifier::new(new_name));
|
||||
}
|
||||
// Return implicit name.
|
||||
return Some(self.identifier().to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rename_identifiers(&mut self, old_name: &str, new_name: &str) {
|
||||
if let Some(alias) = &mut self.alias {
|
||||
alias.rename(old_name, new_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Bake)]
|
||||
#[databake(path = kcl_lib::ast::types)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
pub struct ImportStatement {
|
||||
pub start: usize,
|
||||
pub end: usize,
|
||||
pub items: Vec<ImportItem>,
|
||||
pub path: String,
|
||||
pub raw_path: String,
|
||||
|
||||
pub digest: Option<Digest>,
|
||||
}
|
||||
|
||||
impl_value_meta!(ImportStatement);
|
||||
|
||||
impl ImportStatement {
|
||||
compute_digest!(|slf, hasher| {
|
||||
for item in &mut slf.items {
|
||||
hasher.update(item.compute_digest());
|
||||
}
|
||||
let path = slf.path.as_bytes();
|
||||
hasher.update(path.len().to_ne_bytes());
|
||||
hasher.update(path);
|
||||
});
|
||||
|
||||
pub fn get_constraint_level(&self) -> ConstraintLevel {
|
||||
ConstraintLevel::Full {
|
||||
source_ranges: vec![self.into()],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rename_symbol(&mut self, new_name: &str, pos: usize) -> Option<String> {
|
||||
for item in &mut self.items {
|
||||
let source_range = SourceRange::from(&*item);
|
||||
if source_range.contains(pos) {
|
||||
let old_name = item.rename_symbol(new_name, pos);
|
||||
if old_name.is_some() {
|
||||
return old_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn rename_identifiers(&mut self, old_name: &str, new_name: &str) {
|
||||
for item in &mut self.items {
|
||||
item.rename_identifiers(old_name, new_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Bake)]
|
||||
#[databake(path = kcl_lib::ast::types)]
|
||||
#[ts(export)]
|
||||
@ -1284,6 +1435,32 @@ impl PartialEq for Function {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug, Default, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, FromStr, Display, Bake,
|
||||
)]
|
||||
#[databake(path = kcl_lib::ast::types)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[display(style = "snake_case")]
|
||||
pub enum ItemVisibility {
|
||||
#[default]
|
||||
Default,
|
||||
Export,
|
||||
}
|
||||
|
||||
impl ItemVisibility {
|
||||
fn digestable_id(&self) -> [u8; 1] {
|
||||
match self {
|
||||
ItemVisibility::Default => [0],
|
||||
ItemVisibility::Export => [1],
|
||||
}
|
||||
}
|
||||
|
||||
fn is_default(&self) -> bool {
|
||||
matches!(self, Self::Default)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Bake)]
|
||||
#[databake(path = kcl_lib::ast::types)]
|
||||
#[ts(export)]
|
||||
@ -1292,6 +1469,8 @@ pub struct VariableDeclaration {
|
||||
pub start: usize,
|
||||
pub end: usize,
|
||||
pub declarations: Vec<VariableDeclarator>,
|
||||
#[serde(default, skip_serializing_if = "ItemVisibility::is_default")]
|
||||
pub visibility: ItemVisibility,
|
||||
pub kind: VariableKind, // Change to enum if there are specific values
|
||||
|
||||
pub digest: Option<Digest>,
|
||||
@ -1337,14 +1516,16 @@ impl VariableDeclaration {
|
||||
for declarator in &mut slf.declarations {
|
||||
hasher.update(declarator.compute_digest());
|
||||
}
|
||||
hasher.update(slf.visibility.digestable_id());
|
||||
hasher.update(slf.kind.digestable_id());
|
||||
});
|
||||
|
||||
pub fn new(declarations: Vec<VariableDeclarator>, kind: VariableKind) -> Self {
|
||||
pub fn new(declarations: Vec<VariableDeclarator>, visibility: ItemVisibility, kind: VariableKind) -> Self {
|
||||
Self {
|
||||
start: 0,
|
||||
end: 0,
|
||||
declarations,
|
||||
visibility,
|
||||
kind,
|
||||
digest: None,
|
||||
}
|
||||
|
@ -24,6 +24,8 @@ use crate::{
|
||||
executor::{DefaultPlanes, IdGenerator},
|
||||
};
|
||||
|
||||
use super::ExecutionKind;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum SocketHealth {
|
||||
Active,
|
||||
@ -46,6 +48,8 @@ pub struct EngineConnection {
|
||||
default_planes: Arc<RwLock<Option<DefaultPlanes>>>,
|
||||
/// If the server sends session data, it'll be copied to here.
|
||||
session_data: Arc<Mutex<Option<ModelingSessionData>>>,
|
||||
|
||||
execution_kind: Arc<Mutex<ExecutionKind>>,
|
||||
}
|
||||
|
||||
pub struct TcpRead {
|
||||
@ -300,6 +304,7 @@ impl EngineConnection {
|
||||
batch_end: Arc::new(Mutex::new(IndexMap::new())),
|
||||
default_planes: Default::default(),
|
||||
session_data,
|
||||
execution_kind: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -314,6 +319,18 @@ impl EngineManager for EngineConnection {
|
||||
self.batch_end.clone()
|
||||
}
|
||||
|
||||
fn execution_kind(&self) -> ExecutionKind {
|
||||
let guard = self.execution_kind.lock().unwrap();
|
||||
*guard
|
||||
}
|
||||
|
||||
fn replace_execution_kind(&self, execution_kind: ExecutionKind) -> ExecutionKind {
|
||||
let mut guard = self.execution_kind.lock().unwrap();
|
||||
let original = *guard;
|
||||
*guard = execution_kind;
|
||||
original
|
||||
}
|
||||
|
||||
async fn default_planes(
|
||||
&self,
|
||||
id_generator: &mut IdGenerator,
|
||||
|
@ -22,10 +22,13 @@ use crate::{
|
||||
executor::{DefaultPlanes, IdGenerator},
|
||||
};
|
||||
|
||||
use super::ExecutionKind;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EngineConnection {
|
||||
batch: Arc<Mutex<Vec<(WebSocketRequest, crate::executor::SourceRange)>>>,
|
||||
batch_end: Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, crate::executor::SourceRange)>>>,
|
||||
execution_kind: Arc<Mutex<ExecutionKind>>,
|
||||
}
|
||||
|
||||
impl EngineConnection {
|
||||
@ -33,6 +36,7 @@ impl EngineConnection {
|
||||
Ok(EngineConnection {
|
||||
batch: Arc::new(Mutex::new(Vec::new())),
|
||||
batch_end: Arc::new(Mutex::new(IndexMap::new())),
|
||||
execution_kind: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -47,6 +51,18 @@ impl crate::engine::EngineManager for EngineConnection {
|
||||
self.batch_end.clone()
|
||||
}
|
||||
|
||||
fn execution_kind(&self) -> ExecutionKind {
|
||||
let guard = self.execution_kind.lock().unwrap();
|
||||
*guard
|
||||
}
|
||||
|
||||
fn replace_execution_kind(&self, execution_kind: ExecutionKind) -> ExecutionKind {
|
||||
let mut guard = self.execution_kind.lock().unwrap();
|
||||
let original = *guard;
|
||||
*guard = execution_kind;
|
||||
original
|
||||
}
|
||||
|
||||
async fn default_planes(
|
||||
&self,
|
||||
_id_generator: &mut IdGenerator,
|
||||
|
@ -9,6 +9,7 @@ use kittycad_modeling_cmds as kcmc;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::{
|
||||
engine::ExecutionKind,
|
||||
errors::{KclError, KclErrorDetails},
|
||||
executor::{DefaultPlanes, IdGenerator},
|
||||
};
|
||||
@ -42,6 +43,7 @@ pub struct EngineConnection {
|
||||
manager: Arc<EngineCommandManager>,
|
||||
batch: Arc<Mutex<Vec<(WebSocketRequest, crate::executor::SourceRange)>>>,
|
||||
batch_end: Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, crate::executor::SourceRange)>>>,
|
||||
execution_kind: Arc<Mutex<ExecutionKind>>,
|
||||
}
|
||||
|
||||
// Safety: WebAssembly will only ever run in a single-threaded context.
|
||||
@ -54,6 +56,7 @@ impl EngineConnection {
|
||||
manager: Arc::new(manager),
|
||||
batch: Arc::new(Mutex::new(Vec::new())),
|
||||
batch_end: Arc::new(Mutex::new(IndexMap::new())),
|
||||
execution_kind: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -68,6 +71,18 @@ impl crate::engine::EngineManager for EngineConnection {
|
||||
self.batch_end.clone()
|
||||
}
|
||||
|
||||
fn execution_kind(&self) -> ExecutionKind {
|
||||
let guard = self.execution_kind.lock().unwrap();
|
||||
*guard
|
||||
}
|
||||
|
||||
fn replace_execution_kind(&self, execution_kind: ExecutionKind) -> ExecutionKind {
|
||||
let mut guard = self.execution_kind.lock().unwrap();
|
||||
let original = *guard;
|
||||
*guard = execution_kind;
|
||||
original
|
||||
}
|
||||
|
||||
async fn default_planes(
|
||||
&self,
|
||||
_id_generator: &mut IdGenerator,
|
||||
|
@ -41,6 +41,23 @@ lazy_static::lazy_static! {
|
||||
pub static ref GRID_SCALE_TEXT_OBJECT_ID: uuid::Uuid = uuid::Uuid::parse_str("10782f33-f588-4668-8bcd-040502d26590").unwrap();
|
||||
}
|
||||
|
||||
/// The mode of execution. When isolated, like during an import, attempting to
|
||||
/// send a command results in an error.
|
||||
#[derive(Debug, Default, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum ExecutionKind {
|
||||
#[default]
|
||||
Normal,
|
||||
Isolated,
|
||||
}
|
||||
|
||||
impl ExecutionKind {
|
||||
pub fn is_isolated(&self) -> bool {
|
||||
matches!(self, ExecutionKind::Isolated)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
/// Get the batch of commands to be sent to the engine.
|
||||
@ -49,6 +66,13 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
/// Get the batch of end commands to be sent to the engine.
|
||||
fn batch_end(&self) -> Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, crate::executor::SourceRange)>>>;
|
||||
|
||||
/// Get the current execution kind.
|
||||
fn execution_kind(&self) -> ExecutionKind;
|
||||
|
||||
/// Replace the current execution kind with a new value and return the
|
||||
/// existing value.
|
||||
fn replace_execution_kind(&self, execution_kind: ExecutionKind) -> ExecutionKind;
|
||||
|
||||
/// Get the default planes.
|
||||
async fn default_planes(
|
||||
&self,
|
||||
@ -102,6 +126,10 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
source_range: crate::executor::SourceRange,
|
||||
cmd: &ModelingCmd,
|
||||
) -> Result<(), crate::errors::KclError> {
|
||||
let execution_kind = self.execution_kind();
|
||||
if execution_kind.is_isolated() {
|
||||
return Err(KclError::Semantic(KclErrorDetails { message: "Cannot send modeling commands while importing. Wrap your code in a function if you want to import the file.".to_owned(), source_ranges: vec![source_range] }));
|
||||
}
|
||||
let req = WebSocketRequest::ModelingCmdReq(ModelingCmdReq {
|
||||
cmd: cmd.clone(),
|
||||
cmd_id: id.into(),
|
||||
|
@ -14,6 +14,8 @@ pub enum KclError {
|
||||
Syntax(KclErrorDetails),
|
||||
#[error("semantic: {0:?}")]
|
||||
Semantic(KclErrorDetails),
|
||||
#[error("import cycle: {0:?}")]
|
||||
ImportCycle(KclErrorDetails),
|
||||
#[error("type: {0:?}")]
|
||||
Type(KclErrorDetails),
|
||||
#[error("unimplemented: {0:?}")]
|
||||
@ -52,6 +54,7 @@ impl KclError {
|
||||
KclError::Lexical(_) => "lexical",
|
||||
KclError::Syntax(_) => "syntax",
|
||||
KclError::Semantic(_) => "semantic",
|
||||
KclError::ImportCycle(_) => "import cycle",
|
||||
KclError::Type(_) => "type",
|
||||
KclError::Unimplemented(_) => "unimplemented",
|
||||
KclError::Unexpected(_) => "unexpected",
|
||||
@ -68,6 +71,7 @@ impl KclError {
|
||||
KclError::Lexical(e) => e.source_ranges.clone(),
|
||||
KclError::Syntax(e) => e.source_ranges.clone(),
|
||||
KclError::Semantic(e) => e.source_ranges.clone(),
|
||||
KclError::ImportCycle(e) => e.source_ranges.clone(),
|
||||
KclError::Type(e) => e.source_ranges.clone(),
|
||||
KclError::Unimplemented(e) => e.source_ranges.clone(),
|
||||
KclError::Unexpected(e) => e.source_ranges.clone(),
|
||||
@ -85,6 +89,7 @@ impl KclError {
|
||||
KclError::Lexical(e) => &e.message,
|
||||
KclError::Syntax(e) => &e.message,
|
||||
KclError::Semantic(e) => &e.message,
|
||||
KclError::ImportCycle(e) => &e.message,
|
||||
KclError::Type(e) => &e.message,
|
||||
KclError::Unimplemented(e) => &e.message,
|
||||
KclError::Unexpected(e) => &e.message,
|
||||
@ -102,6 +107,7 @@ impl KclError {
|
||||
KclError::Lexical(e) => e.source_ranges = source_ranges,
|
||||
KclError::Syntax(e) => e.source_ranges = source_ranges,
|
||||
KclError::Semantic(e) => e.source_ranges = source_ranges,
|
||||
KclError::ImportCycle(e) => e.source_ranges = source_ranges,
|
||||
KclError::Type(e) => e.source_ranges = source_ranges,
|
||||
KclError::Unimplemented(e) => e.source_ranges = source_ranges,
|
||||
KclError::Unexpected(e) => e.source_ranges = source_ranges,
|
||||
@ -121,6 +127,7 @@ impl KclError {
|
||||
KclError::Lexical(e) => e.source_ranges.extend(source_ranges),
|
||||
KclError::Syntax(e) => e.source_ranges.extend(source_ranges),
|
||||
KclError::Semantic(e) => e.source_ranges.extend(source_ranges),
|
||||
KclError::ImportCycle(e) => e.source_ranges.extend(source_ranges),
|
||||
KclError::Type(e) => e.source_ranges.extend(source_ranges),
|
||||
KclError::Unimplemented(e) => e.source_ranges.extend(source_ranges),
|
||||
KclError::Unexpected(e) => e.source_ranges.extend(source_ranges),
|
||||
|
@ -1,6 +1,9 @@
|
||||
//! The executor for the AST.
|
||||
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
use async_recursion::async_recursion;
|
||||
@ -23,12 +26,12 @@ type Point3D = kcmc::shared::Point3d<f64>;
|
||||
|
||||
use crate::{
|
||||
ast::types::{
|
||||
human_friendly_type, BodyItem, Expr, ExpressionStatement, FunctionExpression, KclNone, Program,
|
||||
ReturnStatement, TagDeclarator,
|
||||
human_friendly_type, BodyItem, Expr, ExpressionStatement, FunctionExpression, ImportStatement, ItemVisibility,
|
||||
KclNone, Program, ReturnStatement, TagDeclarator,
|
||||
},
|
||||
engine::EngineManager,
|
||||
engine::{EngineManager, ExecutionKind},
|
||||
errors::{KclError, KclErrorDetails},
|
||||
fs::FileManager,
|
||||
fs::{FileManager, FileSystem},
|
||||
settings::types::UnitLength,
|
||||
std::{FnAsArg, StdLib},
|
||||
};
|
||||
@ -47,6 +50,14 @@ pub struct ExecState {
|
||||
/// The current value of the pipe operator returned from the previous
|
||||
/// expression. If we're not currently in a pipeline, this will be None.
|
||||
pub pipe_value: Option<KclValue>,
|
||||
/// Identifiers that have been exported from the current module.
|
||||
pub module_exports: HashSet<String>,
|
||||
/// 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>,
|
||||
/// The directory of the current project. This is used for resolving import
|
||||
/// paths. If None is given, the current working directory is used.
|
||||
pub project_directory: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
@ -391,6 +402,20 @@ impl KclValue {
|
||||
KclValue::Face(_) => "Face",
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_function(&self) -> bool {
|
||||
match self {
|
||||
KclValue::UserVal(..)
|
||||
| KclValue::TagIdentifier(..)
|
||||
| KclValue::TagDeclarator(..)
|
||||
| KclValue::Plane(..)
|
||||
| KclValue::Face(..)
|
||||
| KclValue::Solid(..)
|
||||
| KclValue::Solids { .. }
|
||||
| KclValue::ImportedGeometry(..) => false,
|
||||
KclValue::Function { .. } => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SketchSet> for KclValue {
|
||||
@ -1504,6 +1529,14 @@ impl From<SourceRange> for Metadata {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ImportStatement> for Metadata {
|
||||
fn from(stmt: &ImportStatement) -> Self {
|
||||
Self {
|
||||
source_range: SourceRange::new(stmt.start, stmt.end),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ExpressionStatement> for Metadata {
|
||||
fn from(exp_statement: &ExpressionStatement) -> Self {
|
||||
Self {
|
||||
@ -1967,8 +2000,9 @@ impl ExecutorContext {
|
||||
program: &crate::ast::types::Program,
|
||||
memory: Option<ProgramMemory>,
|
||||
id_generator: IdGenerator,
|
||||
project_directory: Option<String>,
|
||||
) -> Result<ExecState, KclError> {
|
||||
self.run_with_session_data(program, memory, id_generator)
|
||||
self.run_with_session_data(program, memory, id_generator, project_directory)
|
||||
.await
|
||||
.map(|x| x.0)
|
||||
}
|
||||
@ -1980,6 +2014,7 @@ impl ExecutorContext {
|
||||
program: &crate::ast::types::Program,
|
||||
memory: Option<ProgramMemory>,
|
||||
id_generator: IdGenerator,
|
||||
project_directory: Option<String>,
|
||||
) -> Result<(ExecState, Option<ModelingSessionData>), KclError> {
|
||||
let memory = if let Some(memory) = memory {
|
||||
memory.clone()
|
||||
@ -1989,6 +2024,7 @@ impl ExecutorContext {
|
||||
let mut exec_state = ExecState {
|
||||
memory,
|
||||
id_generator,
|
||||
project_directory,
|
||||
..Default::default()
|
||||
};
|
||||
// Before we even start executing the program, set the units.
|
||||
@ -2027,6 +2063,91 @@ impl ExecutorContext {
|
||||
// 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 path = import_stmt.path.clone();
|
||||
let resolved_path = if let Some(project_dir) = &exec_state.project_directory {
|
||||
std::path::PathBuf::from(project_dir).join(&path)
|
||||
} else {
|
||||
std::path::PathBuf::from(&path)
|
||||
};
|
||||
if exec_state.import_stack.contains(&resolved_path) {
|
||||
return Err(KclError::ImportCycle(KclErrorDetails {
|
||||
message: format!(
|
||||
"circular import of modules is not allowed: {} -> {}",
|
||||
exec_state
|
||||
.import_stack
|
||||
.iter()
|
||||
.map(|p| p.as_path().to_string_lossy())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" -> "),
|
||||
resolved_path.to_string_lossy()
|
||||
),
|
||||
source_ranges: vec![import_stmt.into()],
|
||||
}));
|
||||
}
|
||||
let source = self.fs.read_to_string(&resolved_path, source_range).await?;
|
||||
let program = crate::parser::parse(&source)?;
|
||||
let (module_memory, module_exports) = {
|
||||
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);
|
||||
let result = self
|
||||
.inner_execute(&program, exec_state, crate::executor::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| {
|
||||
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}: {}",
|
||||
err.message()
|
||||
),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
}
|
||||
})?;
|
||||
|
||||
(module_memory, module_exports)
|
||||
};
|
||||
for import_item in &import_stmt.items {
|
||||
// Extract the item from the module.
|
||||
let item = module_memory
|
||||
.get(&import_item.name.name, import_item.into())
|
||||
.map_err(|_err| {
|
||||
KclError::UndefinedValue(KclErrorDetails {
|
||||
message: format!("{} is not defined in module", import_item.name.name),
|
||||
source_ranges: vec![SourceRange::from(&import_item.name)],
|
||||
})
|
||||
})?;
|
||||
// Check that the item is allowed to be imported.
|
||||
if !module_exports.contains(&import_item.name.name) {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
"Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.",
|
||||
import_item.name.name
|
||||
),
|
||||
source_ranges: vec![SourceRange::from(&import_item.name)],
|
||||
}));
|
||||
}
|
||||
|
||||
// Add the item to the current module.
|
||||
exec_state.memory.add(
|
||||
import_item.identifier(),
|
||||
item.clone(),
|
||||
SourceRange::from(&import_item.name),
|
||||
)?;
|
||||
}
|
||||
last_expr = None;
|
||||
}
|
||||
BodyItem::ExpressionStatement(expression_statement) => {
|
||||
let metadata = Metadata::from(expression_statement);
|
||||
last_expr = Some(
|
||||
@ -2053,7 +2174,21 @@ impl ExecutorContext {
|
||||
StatementKind::Declaration { name: &var_name },
|
||||
)
|
||||
.await?;
|
||||
let is_function = memory_item.is_function();
|
||||
exec_state.memory.add(&var_name, memory_item, source_range)?;
|
||||
// Track exports.
|
||||
match variable_declaration.visibility {
|
||||
ItemVisibility::Export => {
|
||||
if !is_function {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: "Only functions can be exported".to_owned(),
|
||||
source_ranges: vec![source_range],
|
||||
}));
|
||||
}
|
||||
exec_state.module_exports.insert(var_name);
|
||||
}
|
||||
ItemVisibility::Default => {}
|
||||
}
|
||||
}
|
||||
last_expr = None;
|
||||
}
|
||||
@ -2158,8 +2293,9 @@ impl ExecutorContext {
|
||||
&self,
|
||||
program: &Program,
|
||||
id_generator: IdGenerator,
|
||||
project_directory: Option<String>,
|
||||
) -> Result<TakeSnapshot> {
|
||||
let _ = self.run(program, None, id_generator).await?;
|
||||
let _ = self.run(program, None, id_generator, project_directory).await?;
|
||||
|
||||
// Zoom to fit.
|
||||
self.engine
|
||||
@ -2304,7 +2440,7 @@ mod tests {
|
||||
settings: Default::default(),
|
||||
context_type: ContextType::Mock,
|
||||
};
|
||||
let exec_state = ctx.run(&program, None, IdGenerator::default()).await?;
|
||||
let exec_state = ctx.run(&program, None, IdGenerator::default(), None).await?;
|
||||
|
||||
Ok(exec_state.memory)
|
||||
}
|
||||
|
@ -37,6 +37,19 @@ impl FileSystem for FileManager {
|
||||
})
|
||||
}
|
||||
|
||||
async fn read_to_string<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
|
||||
&self,
|
||||
path: P,
|
||||
source_range: crate::executor::SourceRange,
|
||||
) -> Result<String, KclError> {
|
||||
tokio::fs::read_to_string(&path).await.map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("Failed to read file `{}`: {}", path.as_ref().display(), e),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async fn exists<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
|
||||
&self,
|
||||
path: P,
|
||||
|
@ -23,6 +23,13 @@ pub trait FileSystem: Clone {
|
||||
source_range: crate::executor::SourceRange,
|
||||
) -> Result<Vec<u8>, crate::errors::KclError>;
|
||||
|
||||
/// Read a file from the local file system.
|
||||
async fn read_to_string<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
|
||||
&self,
|
||||
path: P,
|
||||
source_range: crate::executor::SourceRange,
|
||||
) -> Result<String, crate::errors::KclError>;
|
||||
|
||||
/// Check if a file exists on the local file system.
|
||||
async fn exists<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
|
||||
&self,
|
||||
|
@ -78,6 +78,22 @@ impl FileSystem for FileManager {
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
async fn read_to_string<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
|
||||
&self,
|
||||
path: P,
|
||||
source_range: crate::executor::SourceRange,
|
||||
) -> Result<String, KclError> {
|
||||
let bytes = self.read(path, source_range).await?;
|
||||
let string = String::from_utf8(bytes).map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("Failed to convert bytes to string: {:?}", e),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
|
||||
Ok(string)
|
||||
}
|
||||
|
||||
async fn exists<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
|
||||
&self,
|
||||
path: P,
|
||||
|
@ -596,7 +596,7 @@ impl Backend {
|
||||
.clear_scene(&mut id_generator, SourceRange::default())
|
||||
.await?;
|
||||
|
||||
let exec_state = match executor_ctx.run(ast, None, id_generator).await {
|
||||
let exec_state = match executor_ctx.run(ast, None, id_generator, None).await {
|
||||
Ok(exec_state) => exec_state,
|
||||
Err(err) => {
|
||||
self.memory_map.remove(params.uri.as_str());
|
||||
|
@ -12,6 +12,13 @@ pub(crate) mod parser_impl;
|
||||
pub const PIPE_SUBSTITUTION_OPERATOR: &str = "%";
|
||||
pub const PIPE_OPERATOR: &str = "|>";
|
||||
|
||||
/// Parse the given KCL code into an AST.
|
||||
pub fn parse(code: &str) -> Result<Program, KclError> {
|
||||
let tokens = crate::token::lexer(code)?;
|
||||
let parser = Parser::new(tokens);
|
||||
parser.ast()
|
||||
}
|
||||
|
||||
pub struct Parser {
|
||||
pub tokens: Vec<Token>,
|
||||
pub unknown_tokens: Vec<Token>,
|
||||
|
@ -12,10 +12,10 @@ use crate::{
|
||||
ast::types::{
|
||||
ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, CallExpression,
|
||||
CommentStyle, ElseIf, Expr, ExpressionStatement, FnArgPrimitive, FnArgType, FunctionExpression, Identifier,
|
||||
IfExpression, Literal, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, NonCodeMeta,
|
||||
NonCodeNode, NonCodeValue, ObjectExpression, ObjectProperty, Parameter, PipeExpression, PipeSubstitution,
|
||||
Program, ReturnStatement, TagDeclarator, UnaryExpression, UnaryOperator, ValueMeta, VariableDeclaration,
|
||||
VariableDeclarator, VariableKind,
|
||||
IfExpression, ImportItem, ImportStatement, ItemVisibility, Literal, LiteralIdentifier, LiteralValue,
|
||||
MemberExpression, MemberObject, NonCodeMeta, NonCodeNode, NonCodeValue, ObjectExpression, ObjectProperty,
|
||||
Parameter, PipeExpression, PipeSubstitution, Program, ReturnStatement, TagDeclarator, UnaryExpression,
|
||||
UnaryOperator, ValueMeta, VariableDeclaration, VariableDeclarator, VariableKind,
|
||||
},
|
||||
errors::{KclError, KclErrorDetails},
|
||||
executor::SourceRange,
|
||||
@ -956,8 +956,10 @@ fn body_items_within_function(i: TokenSlice) -> PResult<WithinFunction> {
|
||||
// Any of the body item variants, each of which can optionally be followed by a comment.
|
||||
// If there is a comment, it may be preceded by whitespace.
|
||||
let item = dispatch! {peek(any);
|
||||
token if token.declaration_keyword().is_some() =>
|
||||
token if token.declaration_keyword().is_some() || token.visibility_keyword().is_some() =>
|
||||
(declaration.map(BodyItem::VariableDeclaration), opt(noncode_just_after_code)).map(WithinFunction::BodyItem),
|
||||
token if token.value == "import" && matches!(token.token_type, TokenType::Keyword) =>
|
||||
(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() => {
|
||||
@ -1122,6 +1124,111 @@ pub fn function_body(i: TokenSlice) -> PResult<Program> {
|
||||
})
|
||||
}
|
||||
|
||||
fn import_stmt(i: TokenSlice) -> PResult<Box<ImportStatement>> {
|
||||
let import_token = any
|
||||
.try_map(|token: Token| {
|
||||
if matches!(token.token_type, TokenType::Keyword) && token.value == "import" {
|
||||
Ok(token)
|
||||
} else {
|
||||
Err(KclError::Syntax(KclErrorDetails {
|
||||
source_ranges: token.as_source_ranges(),
|
||||
message: format!("{} is not the 'import' keyword", token.value.as_str()),
|
||||
}))
|
||||
}
|
||||
})
|
||||
.context(expected("the 'import' keyword"))
|
||||
.parse_next(i)?;
|
||||
let start = import_token.start;
|
||||
|
||||
require_whitespace(i)?;
|
||||
|
||||
let items = separated(1.., import_item, comma_sep)
|
||||
.parse_next(i)
|
||||
.map_err(|e| e.cut())?;
|
||||
|
||||
require_whitespace(i)?;
|
||||
|
||||
any.try_map(|token: Token| {
|
||||
if matches!(token.token_type, TokenType::Keyword | TokenType::Word) && token.value == "from" {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(KclError::Syntax(KclErrorDetails {
|
||||
source_ranges: token.as_source_ranges(),
|
||||
message: format!("{} is not the 'from' keyword", token.value.as_str()),
|
||||
}))
|
||||
}
|
||||
})
|
||||
.context(expected("the 'from' keyword"))
|
||||
.parse_next(i)
|
||||
.map_err(|e| e.cut())?;
|
||||
|
||||
require_whitespace(i)?;
|
||||
|
||||
let path = string_literal(i)?;
|
||||
let end = path.end();
|
||||
let path_string = match path.value {
|
||||
LiteralValue::String(s) => s,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
if path_string
|
||||
.chars()
|
||||
.any(|c| !c.is_ascii_alphanumeric() && c != '_' && c != '-' && c != '.')
|
||||
{
|
||||
return Err(ErrMode::Cut(
|
||||
KclError::Syntax(KclErrorDetails {
|
||||
source_ranges: vec![SourceRange::new(path.start, path.end)],
|
||||
message: "import path may only contain alphanumeric characters, underscore, hyphen, and period. Files in other directories are not yet supported.".to_owned(),
|
||||
})
|
||||
.into(),
|
||||
));
|
||||
}
|
||||
Ok(Box::new(ImportStatement {
|
||||
items,
|
||||
path: path_string,
|
||||
raw_path: path.raw,
|
||||
start,
|
||||
end,
|
||||
digest: None,
|
||||
}))
|
||||
}
|
||||
|
||||
fn import_item(i: TokenSlice) -> PResult<ImportItem> {
|
||||
let name = identifier.context(expected("an identifier to import")).parse_next(i)?;
|
||||
let start = name.start;
|
||||
let alias = opt(preceded(
|
||||
(whitespace, import_as_keyword, whitespace),
|
||||
identifier.context(expected("an identifier to alias the import")),
|
||||
))
|
||||
.parse_next(i)?;
|
||||
let end = if let Some(ref alias) = alias {
|
||||
alias.end()
|
||||
} else {
|
||||
name.end()
|
||||
};
|
||||
Ok(ImportItem {
|
||||
name,
|
||||
alias,
|
||||
start,
|
||||
end,
|
||||
digest: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn import_as_keyword(i: TokenSlice) -> PResult<Token> {
|
||||
any.try_map(|token: Token| {
|
||||
if matches!(token.token_type, TokenType::Keyword | TokenType::Word) && token.value == "as" {
|
||||
Ok(token)
|
||||
} else {
|
||||
Err(KclError::Syntax(KclErrorDetails {
|
||||
source_ranges: token.as_source_ranges(),
|
||||
message: format!("{} is not the 'as' keyword", token.value.as_str()),
|
||||
}))
|
||||
}
|
||||
})
|
||||
.context(expected("the 'as' keyword"))
|
||||
.parse_next(i)
|
||||
}
|
||||
|
||||
/// Parse a return statement of a user-defined function, e.g. `return x`.
|
||||
pub fn return_stmt(i: TokenSlice) -> PResult<ReturnStatement> {
|
||||
let start = any
|
||||
@ -1214,6 +1321,19 @@ fn possible_operands(i: TokenSlice) -> PResult<Expr> {
|
||||
.parse_next(i)
|
||||
}
|
||||
|
||||
/// Parse an item visibility specifier, e.g. export.
|
||||
fn item_visibility(i: TokenSlice) -> PResult<(ItemVisibility, Token)> {
|
||||
any.verify_map(|token: Token| {
|
||||
if token.token_type == TokenType::Keyword && token.value == "export" {
|
||||
Some((ItemVisibility::Export, token))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.context(expected("item visibility, e.g. 'export'"))
|
||||
.parse_next(i)
|
||||
}
|
||||
|
||||
fn declaration_keyword(i: TokenSlice) -> PResult<(VariableKind, Token)> {
|
||||
let res = any
|
||||
.verify_map(|token: Token| token.declaration_keyword().map(|kw| (kw, token)))
|
||||
@ -1223,6 +1343,9 @@ fn declaration_keyword(i: TokenSlice) -> PResult<(VariableKind, Token)> {
|
||||
|
||||
/// Parse a variable/constant declaration.
|
||||
fn declaration(i: TokenSlice) -> PResult<VariableDeclaration> {
|
||||
let (visibility, visibility_token) = opt(terminated(item_visibility, whitespace))
|
||||
.parse_next(i)?
|
||||
.map_or((ItemVisibility::Default, None), |pair| (pair.0, Some(pair.1)));
|
||||
let decl_token = opt(declaration_keyword).parse_next(i)?;
|
||||
if decl_token.is_some() {
|
||||
// If there was a declaration keyword like `fn`, then it must be followed by some spaces.
|
||||
@ -1235,11 +1358,14 @@ fn declaration(i: TokenSlice) -> PResult<VariableDeclaration> {
|
||||
"an identifier, which becomes name you're binding the value to",
|
||||
))
|
||||
.parse_next(i)?;
|
||||
let (kind, start, dec_end) = if let Some((kind, token)) = &decl_token {
|
||||
let (kind, mut start, dec_end) = if let Some((kind, token)) = &decl_token {
|
||||
(*kind, token.start, token.end)
|
||||
} else {
|
||||
(VariableKind::Const, id.start(), id.end())
|
||||
};
|
||||
if let Some(token) = visibility_token {
|
||||
start = token.start;
|
||||
}
|
||||
|
||||
ignore_whitespace(i);
|
||||
equals(i)?;
|
||||
@ -1288,6 +1414,7 @@ fn declaration(i: TokenSlice) -> PResult<VariableDeclaration> {
|
||||
init: val,
|
||||
digest: None,
|
||||
}],
|
||||
visibility,
|
||||
kind,
|
||||
digest: None,
|
||||
})
|
||||
|
@ -1,6 +1,5 @@
|
||||
---
|
||||
source: kcl/src/parser/parser_impl.rs
|
||||
assertion_line: 3423
|
||||
expression: actual
|
||||
---
|
||||
{
|
||||
|
@ -1,6 +1,5 @@
|
||||
---
|
||||
source: kcl/src/parser/parser_impl.rs
|
||||
assertion_line: 3470
|
||||
expression: actual
|
||||
---
|
||||
{
|
||||
|
@ -30,7 +30,7 @@ async fn do_execute_and_snapshot(ctx: &ExecutorContext, code: &str) -> anyhow::R
|
||||
let program = parser.ast()?;
|
||||
|
||||
let snapshot = ctx
|
||||
.execute_and_prepare_snapshot(&program, IdGenerator::default())
|
||||
.execute_and_prepare_snapshot(&program, IdGenerator::default(), None)
|
||||
.await?;
|
||||
|
||||
// Create a temporary file to write the output to.
|
||||
|
@ -7,7 +7,11 @@ use serde::{Deserialize, Serialize};
|
||||
use tower_lsp::lsp_types::SemanticTokenType;
|
||||
use winnow::stream::ContainsToken;
|
||||
|
||||
use crate::{ast::types::VariableKind, errors::KclError, executor::SourceRange};
|
||||
use crate::{
|
||||
ast::types::{ItemVisibility, VariableKind},
|
||||
errors::KclError,
|
||||
executor::SourceRange,
|
||||
};
|
||||
|
||||
mod tokeniser;
|
||||
|
||||
@ -196,6 +200,16 @@ impl Token {
|
||||
vec![self.as_source_range()]
|
||||
}
|
||||
|
||||
pub fn visibility_keyword(&self) -> Option<ItemVisibility> {
|
||||
if !matches!(self.token_type, TokenType::Keyword) {
|
||||
return None;
|
||||
}
|
||||
match self.value.as_str() {
|
||||
"export" => Some(ItemVisibility::Export),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Is this token the beginning of a variable/function declaration?
|
||||
/// If so, what kind?
|
||||
/// If not, returns None.
|
||||
|
@ -169,11 +169,17 @@ fn string(i: &mut Located<&str>) -> PResult<Token> {
|
||||
Ok(Token::from_range(range, TokenType::String, value.to_string()))
|
||||
}
|
||||
|
||||
fn keyword(i: &mut Located<&str>) -> PResult<Token> {
|
||||
fn import_keyword(i: &mut Located<&str>) -> PResult<Token> {
|
||||
let (value, range) = "import".with_span().parse_next(i)?;
|
||||
let token_type = peek(alt((' '.map(|_| TokenType::Keyword), '('.map(|_| TokenType::Word)))).parse_next(i)?;
|
||||
Ok(Token::from_range(range, token_type, value.to_owned()))
|
||||
}
|
||||
|
||||
fn unambiguous_keywords(i: &mut Located<&str>) -> PResult<Token> {
|
||||
// These are the keywords themselves.
|
||||
let keyword_candidates = alt((
|
||||
"if", "else", "for", "while", "return", "break", "continue", "fn", "let", "mut", "loop", "true", "false",
|
||||
"nil", "and", "or", "not", "var", "const",
|
||||
"nil", "and", "or", "not", "var", "const", "export",
|
||||
));
|
||||
// Look ahead. If any of these characters follow the keyword, then it's not a keyword, it's just
|
||||
// the start of a normal word.
|
||||
@ -185,6 +191,10 @@ fn keyword(i: &mut Located<&str>) -> PResult<Token> {
|
||||
Ok(Token::from_range(range, TokenType::Keyword, value.to_owned()))
|
||||
}
|
||||
|
||||
fn keyword(i: &mut Located<&str>) -> PResult<Token> {
|
||||
alt((import_keyword, unambiguous_keywords)).parse_next(i)
|
||||
}
|
||||
|
||||
fn type_(i: &mut Located<&str>) -> PResult<Token> {
|
||||
// These are the types themselves.
|
||||
let type_candidates = alt(("string", "number", "bool", "sketch", "sketch_surface", "solid"));
|
||||
@ -1572,4 +1582,28 @@ const things = "things"
|
||||
|
||||
assert_tokens(expected, actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn import_keyword() {
|
||||
let actual = lexer("import foo").unwrap();
|
||||
let expected = Token {
|
||||
token_type: TokenType::Keyword,
|
||||
value: "import".to_owned(),
|
||||
start: 0,
|
||||
end: 6,
|
||||
};
|
||||
assert_eq!(actual[0], expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn import_function() {
|
||||
let actual = lexer("import(3)").unwrap();
|
||||
let expected = Token {
|
||||
token_type: TokenType::Word,
|
||||
value: "import".to_owned(),
|
||||
start: 0,
|
||||
end: 6,
|
||||
};
|
||||
assert_eq!(actual[0], expected);
|
||||
}
|
||||
}
|
||||
|
@ -3,9 +3,9 @@ use std::fmt::Write;
|
||||
use crate::{
|
||||
ast::types::{
|
||||
ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, CallExpression,
|
||||
Expr, FormatOptions, FunctionExpression, IfExpression, Literal, LiteralIdentifier, LiteralValue,
|
||||
MemberExpression, MemberObject, NonCodeValue, ObjectExpression, PipeExpression, Program, TagDeclarator,
|
||||
UnaryExpression, VariableDeclaration, VariableKind,
|
||||
Expr, FormatOptions, FunctionExpression, IfExpression, ImportStatement, ItemVisibility, Literal,
|
||||
LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, NonCodeValue, ObjectExpression,
|
||||
PipeExpression, Program, TagDeclarator, UnaryExpression, VariableDeclaration, VariableKind,
|
||||
},
|
||||
parser::PIPE_OPERATOR,
|
||||
};
|
||||
@ -17,6 +17,7 @@ impl Program {
|
||||
.body
|
||||
.iter()
|
||||
.map(|statement| match statement.clone() {
|
||||
BodyItem::ImportStatement(stmt) => stmt.recast(options, indentation_level),
|
||||
BodyItem::ExpressionStatement(expression_statement) => {
|
||||
expression_statement
|
||||
.expression
|
||||
@ -108,6 +109,27 @@ impl NonCodeValue {
|
||||
}
|
||||
}
|
||||
|
||||
impl ImportStatement {
|
||||
pub fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String {
|
||||
let indentation = options.get_indentation(indentation_level);
|
||||
let mut string = format!("{}import ", indentation);
|
||||
for (i, item) in self.items.iter().enumerate() {
|
||||
if i > 0 {
|
||||
string.push_str(", ");
|
||||
}
|
||||
string.push_str(&item.name.name);
|
||||
if let Some(alias) = &item.alias {
|
||||
// If the alias is the same, don't output it.
|
||||
if item.name.name != alias.name {
|
||||
string.push_str(&format!(" as {}", alias.name));
|
||||
}
|
||||
}
|
||||
}
|
||||
string.push_str(&format!(" from {}", self.raw_path));
|
||||
string
|
||||
}
|
||||
}
|
||||
|
||||
impl Expr {
|
||||
pub(crate) fn recast(&self, options: &FormatOptions, indentation_level: usize, is_in_pipe: bool) -> String {
|
||||
match &self {
|
||||
@ -168,7 +190,11 @@ impl CallExpression {
|
||||
impl VariableDeclaration {
|
||||
pub fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String {
|
||||
let indentation = options.get_indentation(indentation_level);
|
||||
self.declarations.iter().fold(String::new(), |mut output, declaration| {
|
||||
let output = match self.visibility {
|
||||
ItemVisibility::Default => String::new(),
|
||||
ItemVisibility::Export => "export ".to_owned(),
|
||||
};
|
||||
self.declarations.iter().fold(output, |mut output, declaration| {
|
||||
let keyword = match self.kind {
|
||||
VariableKind::Fn => "fn ",
|
||||
VariableKind::Const => "",
|
||||
@ -581,6 +607,46 @@ mod tests {
|
||||
assert_eq!(output, input);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_recast_import() {
|
||||
let input = r#"import a from "a.kcl"
|
||||
import a as aaa from "a.kcl"
|
||||
import a, b from "a.kcl"
|
||||
import a as aaa, b from "a.kcl"
|
||||
import a, b as bbb from "a.kcl"
|
||||
import a as aaa, b as bbb from "a.kcl"
|
||||
"#;
|
||||
let tokens = crate::token::lexer(input).unwrap();
|
||||
let parser = crate::parser::Parser::new(tokens);
|
||||
let program = parser.ast().unwrap();
|
||||
let output = program.recast(&Default::default(), 0);
|
||||
assert_eq!(output, input);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_recast_import_as_same_name() {
|
||||
let input = r#"import a as a from "a.kcl"
|
||||
"#;
|
||||
let program = crate::parser::parse(input).unwrap();
|
||||
let output = program.recast(&Default::default(), 0);
|
||||
let expected = r#"import a from "a.kcl"
|
||||
"#;
|
||||
assert_eq!(output, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_recast_export_fn() {
|
||||
let input = r#"export fn a = () => {
|
||||
return 0
|
||||
}
|
||||
"#;
|
||||
let tokens = crate::token::lexer(input).unwrap();
|
||||
let parser = crate::parser::Parser::new(tokens);
|
||||
let program = parser.ast().unwrap();
|
||||
let output = program.recast(&Default::default(), 0);
|
||||
assert_eq!(output, input);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_recast_bug_fn_in_fn() {
|
||||
let some_program_string = r#"// Start point (top left)
|
||||
|
@ -9,6 +9,7 @@ use crate::{
|
||||
pub enum Node<'a> {
|
||||
Program(&'a types::Program),
|
||||
|
||||
ImportStatement(&'a types::ImportStatement),
|
||||
ExpressionStatement(&'a types::ExpressionStatement),
|
||||
VariableDeclaration(&'a types::VariableDeclaration),
|
||||
ReturnStatement(&'a types::ReturnStatement),
|
||||
@ -42,6 +43,7 @@ impl From<&Node<'_>> for SourceRange {
|
||||
fn from(node: &Node) -> Self {
|
||||
match node {
|
||||
Node::Program(p) => SourceRange([p.start, p.end]),
|
||||
Node::ImportStatement(e) => SourceRange([e.start(), e.end()]),
|
||||
Node::ExpressionStatement(e) => SourceRange([e.start(), e.end()]),
|
||||
Node::VariableDeclaration(v) => SourceRange([v.start(), v.end()]),
|
||||
Node::ReturnStatement(r) => SourceRange([r.start(), r.end()]),
|
||||
@ -79,6 +81,7 @@ macro_rules! impl_from {
|
||||
}
|
||||
|
||||
impl_from!(Node, Program);
|
||||
impl_from!(Node, ImportStatement);
|
||||
impl_from!(Node, ExpressionStatement);
|
||||
impl_from!(Node, VariableDeclaration);
|
||||
impl_from!(Node, ReturnStatement);
|
||||
|
@ -277,6 +277,12 @@ where
|
||||
// We don't walk a BodyItem since it's an enum itself.
|
||||
|
||||
match node {
|
||||
BodyItem::ImportStatement(xs) => {
|
||||
if !f.walk(xs.as_ref().into())? {
|
||||
return Ok(false);
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
BodyItem::ExpressionStatement(xs) => {
|
||||
if !f.walk(xs.into())? {
|
||||
return Ok(false);
|
||||
|
@ -20,6 +20,7 @@ pub async fn execute_wasm(
|
||||
units: &str,
|
||||
engine_manager: kcl_lib::engine::conn_wasm::EngineCommandManager,
|
||||
fs_manager: kcl_lib::fs::wasm::FileSystemManager,
|
||||
project_directory: Option<String>,
|
||||
is_mock: bool,
|
||||
) -> Result<JsValue, String> {
|
||||
console_error_panic_hook::set_once();
|
||||
@ -62,7 +63,7 @@ pub async fn execute_wasm(
|
||||
};
|
||||
|
||||
let exec_state = ctx
|
||||
.run(&program, Some(memory), id_generator)
|
||||
.run(&program, Some(memory), id_generator, project_directory)
|
||||
.await
|
||||
.map_err(String::from)?;
|
||||
|
||||
|
@ -0,0 +1 @@
|
||||
export three = 3
|
@ -0,0 +1,5 @@
|
||||
export fn foo = () => { return 0 }
|
||||
|
||||
// This interacts with the engine.
|
||||
part001 = startSketchOn('XY')
|
||||
|> startProfileAt([0, 0], %)
|
@ -0,0 +1,3 @@
|
||||
export fn identity = (x) => {
|
||||
return x
|
||||
}
|
@ -0,0 +1 @@
|
||||
import three from "export_constant.kcl"
|
@ -0,0 +1,3 @@
|
||||
import two from "import_cycle2.kcl"
|
||||
|
||||
export fn one = () => { return two() - 1 }
|
@ -0,0 +1,3 @@
|
||||
import three from "import_cycle3.kcl"
|
||||
|
||||
export fn two = () => { return three() - 1 }
|
@ -0,0 +1,3 @@
|
||||
import one from "import_cycle1.kcl"
|
||||
|
||||
export fn three = () => { return one() + one() + one() }
|
@ -0,0 +1 @@
|
||||
import cube from "../cube.kcl"
|
@ -0,0 +1,4 @@
|
||||
fn foo = () => {
|
||||
import identity from "identity.kcl"
|
||||
return 1
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
if true {
|
||||
import identity from "identity.kcl"
|
||||
1
|
||||
} else {
|
||||
2
|
||||
}
|
@ -0,0 +1 @@
|
||||
import foo from "export_side_effect.kcl"
|
@ -0,0 +1,25 @@
|
||||
import identity from "identity.kcl"
|
||||
|
||||
answer = identity(42)
|
||||
assertEqual(answer, 42, 0.0001, "identity")
|
||||
|
||||
import identity as id from "identity.kcl"
|
||||
|
||||
answer43 = id(43)
|
||||
assertEqual(answer43, 43, 0.0001, "identity")
|
||||
|
||||
import increment, decrement from "numbers.kcl"
|
||||
|
||||
answer3 = increment(2)
|
||||
assertEqual(answer3, 3, 0.0001, "increment")
|
||||
|
||||
answer5 = decrement(6)
|
||||
assertEqual(answer5, 5, 0.0001, "decrement")
|
||||
|
||||
import increment as inc, decrement as dec from "numbers.kcl"
|
||||
|
||||
answer4 = inc(3)
|
||||
assertEqual(answer4, 4, 0.0001, "inc")
|
||||
|
||||
answer6 = dec(7)
|
||||
assertEqual(answer6, 6, 0.0001, "dec")
|
@ -0,0 +1,7 @@
|
||||
export fn increment = (x) => {
|
||||
return x + 1
|
||||
}
|
||||
|
||||
export fn decrement = (x) => {
|
||||
return x - 1
|
||||
}
|
@ -2,6 +2,7 @@ use kcl_lib::{
|
||||
ast::types::Program,
|
||||
errors::KclError,
|
||||
executor::{ExecutorContext, IdGenerator},
|
||||
parser,
|
||||
};
|
||||
|
||||
macro_rules! gen_test {
|
||||
@ -25,10 +26,28 @@ macro_rules! gen_test_fail {
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! gen_test_parse_fail {
|
||||
($file:ident, $expected:literal) => {
|
||||
#[tokio::test]
|
||||
async fn $file() {
|
||||
let code = include_str!(concat!("inputs/no_visuals/", stringify!($file), ".kcl"));
|
||||
let actual = run_parse_fail(&code).await;
|
||||
assert_eq!(actual.get_message(), $expected);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async fn run(code: &str) {
|
||||
let (ctx, program, id_generator) = setup(code).await;
|
||||
|
||||
let res = ctx.run(&program, None, id_generator).await;
|
||||
let res = ctx
|
||||
.run(
|
||||
&program,
|
||||
None,
|
||||
id_generator,
|
||||
Some("tests/executor/inputs/no_visuals/".to_owned()),
|
||||
)
|
||||
.await;
|
||||
match res {
|
||||
Ok(state) => {
|
||||
println!("{:#?}", state.memory);
|
||||
@ -57,12 +76,27 @@ async fn setup(program: &str) -> (ExecutorContext, Program, IdGenerator) {
|
||||
|
||||
async fn run_fail(code: &str) -> KclError {
|
||||
let (ctx, program, id_generator) = setup(code).await;
|
||||
let Err(e) = ctx.run(&program, None, id_generator).await else {
|
||||
let Err(e) = ctx
|
||||
.run(
|
||||
&program,
|
||||
None,
|
||||
id_generator,
|
||||
Some("tests/executor/inputs/no_visuals/".to_owned()),
|
||||
)
|
||||
.await
|
||||
else {
|
||||
panic!("Expected this KCL program to fail, but it (incorrectly) never threw an error.");
|
||||
};
|
||||
e
|
||||
}
|
||||
|
||||
async fn run_parse_fail(code: &str) -> KclError {
|
||||
let Err(e) = parser::parse(code) else {
|
||||
panic!("Expected this KCL program to fail to parse, but it (incorrectly) never threw an error.");
|
||||
};
|
||||
e
|
||||
}
|
||||
|
||||
gen_test!(property_of_object);
|
||||
gen_test!(index_of_array);
|
||||
gen_test!(comparisons);
|
||||
@ -111,5 +145,31 @@ gen_test!(if_else);
|
||||
// "syntax: blocks inside an if/else expression must end in an expression"
|
||||
// );
|
||||
gen_test_fail!(comparisons_multiple, "syntax: Invalid number: true");
|
||||
gen_test!(import_simple);
|
||||
gen_test_fail!(
|
||||
import_cycle1,
|
||||
"import cycle: circular import of modules is not allowed: tests/executor/inputs/no_visuals/import_cycle2.kcl -> tests/executor/inputs/no_visuals/import_cycle3.kcl -> tests/executor/inputs/no_visuals/import_cycle1.kcl -> tests/executor/inputs/no_visuals/import_cycle2.kcl"
|
||||
);
|
||||
gen_test_fail!(
|
||||
import_constant,
|
||||
"semantic: Error loading imported file. Open it to view more details. export_constant.kcl: Only functions can be exported"
|
||||
);
|
||||
gen_test_fail!(
|
||||
import_side_effect,
|
||||
"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."
|
||||
);
|
||||
gen_test_parse_fail!(
|
||||
import_from_other_directory,
|
||||
"syntax: import path may only contain alphanumeric characters, underscore, hyphen, and period. Files in other directories are not yet supported."
|
||||
);
|
||||
// TODO: We'd like these tests.
|
||||
// gen_test_fail!(
|
||||
// import_in_if,
|
||||
// "syntax: Can import only import at the top level"
|
||||
// );
|
||||
// gen_test_fail!(
|
||||
// import_in_function,
|
||||
// "syntax: Can import only import at the top level"
|
||||
// );
|
||||
gen_test!(add_lots);
|
||||
gen_test!(double_map);
|
||||
|
@ -35,7 +35,7 @@ async fn setup(code: &str, name: &str) -> Result<(ExecutorContext, Program, uuid
|
||||
let parser = kcl_lib::parser::Parser::new(tokens);
|
||||
let program = parser.ast()?;
|
||||
let ctx = kcl_lib::executor::ExecutorContext::new(&client, Default::default()).await?;
|
||||
let exec_state = ctx.run(&program, None, IdGenerator::default()).await?;
|
||||
let exec_state = ctx.run(&program, None, IdGenerator::default(), None).await?;
|
||||
|
||||
// We need to get the sketch ID.
|
||||
// Get the sketch ID from memory.
|
||||
|
Reference in New Issue
Block a user