2024-07-02 17:16:27 +10:00
|
|
|
import { ToolTip } from 'lang/langHelpers'
|
2024-02-19 17:23:03 +11:00
|
|
|
import { Selection, Selections } from 'lib/selections'
|
2023-04-01 16:47:00 +11:00
|
|
|
import {
|
2024-05-21 16:44:08 +10:00
|
|
|
ArrayExpression,
|
2023-04-01 16:47:00 +11:00
|
|
|
BinaryExpression,
|
|
|
|
CallExpression,
|
2024-09-26 18:25:05 +10:00
|
|
|
Expr,
|
2023-04-01 16:47:00 +11:00
|
|
|
ExpressionStatement,
|
2024-09-26 18:25:05 +10:00
|
|
|
ObjectExpression,
|
|
|
|
ObjectProperty,
|
2023-09-29 11:11:01 -07:00
|
|
|
PathToNode,
|
2024-05-21 16:44:08 +10:00
|
|
|
PipeExpression,
|
|
|
|
Program,
|
2023-09-29 11:11:01 -07:00
|
|
|
ProgramMemory,
|
2024-05-21 16:44:08 +10:00
|
|
|
ReturnStatement,
|
2024-09-27 15:44:44 -07:00
|
|
|
sketchFromKclValue,
|
2024-11-19 17:34:54 -05:00
|
|
|
sketchFromKclValueOptional,
|
2023-09-29 11:11:01 -07:00
|
|
|
SourceRange,
|
2024-12-06 13:57:31 +13:00
|
|
|
sourceRangeFromRust,
|
2024-05-21 16:44:08 +10:00
|
|
|
SyntaxType,
|
|
|
|
VariableDeclaration,
|
|
|
|
VariableDeclarator,
|
2023-09-29 11:11:01 -07:00
|
|
|
} from './wasm'
|
2023-04-01 16:47:00 +11:00
|
|
|
import { createIdentifier, splitPathAtLastIndex } from './modifyAst'
|
2023-04-08 14:16:49 +10:00
|
|
|
import { getSketchSegmentFromSourceRange } from './std/sketchConstraints'
|
|
|
|
import { getAngle } from '../lib/utils'
|
|
|
|
import { getFirstArg } from './std/sketch'
|
|
|
|
import {
|
|
|
|
getConstraintLevelFromSourceRange,
|
|
|
|
getConstraintType,
|
|
|
|
} from './std/sketchcombos'
|
2024-11-19 17:34:54 -05:00
|
|
|
import { err, Reason } from 'lib/trap'
|
2024-10-17 00:48:33 -04:00
|
|
|
import { ImportStatement } from 'wasm-lib/kcl/bindings/ImportStatement'
|
2024-10-30 16:52:17 -04:00
|
|
|
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
2024-11-21 15:04:30 +11:00
|
|
|
import { ArtifactGraph, codeRefFromRange } from './std/artifactGraph'
|
2023-03-03 20:35:48 +11:00
|
|
|
|
2024-05-24 20:54:42 +10:00
|
|
|
/**
|
|
|
|
* Retrieves a node from a given path within a Program node structure, optionally stopping at a specified node type.
|
|
|
|
* This function navigates through the AST (Abstract Syntax Tree) based on the provided path, attempting to locate
|
|
|
|
* and return the node at the end of this path.
|
|
|
|
* By default it will return the node of the deepest "stopAt" type encountered, or the node at the end of the path if no "stopAt" type is provided.
|
|
|
|
* If the "returnEarly" flag is set to true, the function will return as soon as a node of the specified type is found.
|
|
|
|
*/
|
2023-03-03 20:35:48 +11:00
|
|
|
export function getNodeFromPath<T>(
|
|
|
|
node: Program,
|
2023-04-01 16:47:00 +11:00
|
|
|
path: PathToNode,
|
2024-05-24 20:54:42 +10:00
|
|
|
stopAt?: SyntaxType | SyntaxType[],
|
2023-03-03 20:35:48 +11:00
|
|
|
returnEarly = false
|
2024-06-24 11:45:40 -04:00
|
|
|
):
|
|
|
|
| {
|
|
|
|
node: T
|
|
|
|
shallowPath: PathToNode
|
|
|
|
deepPath: PathToNode
|
|
|
|
}
|
|
|
|
| Error {
|
2023-03-03 20:35:48 +11:00
|
|
|
let currentNode = node as any
|
|
|
|
let stopAtNode = null
|
|
|
|
let successfulPaths: PathToNode = []
|
|
|
|
let pathsExplored: PathToNode = []
|
|
|
|
for (const pathItem of path) {
|
2024-07-10 07:01:49 +10:00
|
|
|
if (typeof currentNode[pathItem[0]] !== 'object') {
|
|
|
|
if (stopAtNode) {
|
|
|
|
return {
|
|
|
|
node: stopAtNode,
|
|
|
|
shallowPath: pathsExplored,
|
|
|
|
deepPath: successfulPaths,
|
|
|
|
}
|
|
|
|
}
|
2024-06-24 11:45:40 -04:00
|
|
|
return new Error('not an object')
|
2024-07-10 07:01:49 +10:00
|
|
|
}
|
2024-06-24 11:45:40 -04:00
|
|
|
currentNode = currentNode?.[pathItem[0]]
|
|
|
|
successfulPaths.push(pathItem)
|
|
|
|
if (!stopAtNode) {
|
|
|
|
pathsExplored.push(pathItem)
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
typeof stopAt !== 'undefined' &&
|
|
|
|
(Array.isArray(stopAt)
|
|
|
|
? stopAt.includes(currentNode.type)
|
|
|
|
: currentNode.type === stopAt)
|
|
|
|
) {
|
|
|
|
// it will match the deepest node of the type
|
|
|
|
// instead of returning at the first match
|
|
|
|
stopAtNode = currentNode
|
|
|
|
if (returnEarly) {
|
|
|
|
return {
|
|
|
|
node: stopAtNode,
|
|
|
|
shallowPath: pathsExplored,
|
|
|
|
deepPath: successfulPaths,
|
2023-03-03 20:35:48 +11:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
node: stopAtNode || currentNode,
|
2023-04-01 16:47:00 +11:00
|
|
|
shallowPath: pathsExplored,
|
|
|
|
deepPath: successfulPaths,
|
2023-03-03 20:35:48 +11:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-24 20:54:42 +10:00
|
|
|
/**
|
|
|
|
* Functions the same as getNodeFromPath, but returns a curried function that can be called with the stopAt and returnEarly arguments.
|
|
|
|
*/
|
2023-03-03 20:35:48 +11:00
|
|
|
export function getNodeFromPathCurry(
|
|
|
|
node: Program,
|
2023-04-01 16:47:00 +11:00
|
|
|
path: PathToNode
|
2023-03-03 20:35:48 +11:00
|
|
|
): <T>(
|
2024-05-24 20:54:42 +10:00
|
|
|
stopAt?: SyntaxType | SyntaxType[],
|
2023-03-03 20:35:48 +11:00
|
|
|
returnEarly?: boolean
|
2024-06-24 11:45:40 -04:00
|
|
|
) =>
|
|
|
|
| {
|
|
|
|
node: T
|
|
|
|
path: PathToNode
|
|
|
|
}
|
|
|
|
| Error {
|
2024-05-24 20:54:42 +10:00
|
|
|
return <T>(stopAt?: SyntaxType | SyntaxType[], returnEarly = false) => {
|
2024-06-24 11:45:40 -04:00
|
|
|
const _node1 = getNodeFromPath<T>(node, path, stopAt, returnEarly)
|
|
|
|
if (err(_node1)) return _node1
|
|
|
|
const { node: _node, shallowPath } = _node1
|
2023-04-01 16:47:00 +11:00
|
|
|
return {
|
|
|
|
node: _node,
|
|
|
|
path: shallowPath,
|
|
|
|
}
|
2023-03-03 20:35:48 +11:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-01 16:47:00 +11:00
|
|
|
function moreNodePathFromSourceRange(
|
2024-10-30 16:52:17 -04:00
|
|
|
node: Node<
|
2024-10-17 00:48:33 -04:00
|
|
|
| Expr
|
|
|
|
| ImportStatement
|
|
|
|
| ExpressionStatement
|
|
|
|
| VariableDeclaration
|
2024-10-30 16:52:17 -04:00
|
|
|
| ReturnStatement
|
|
|
|
>,
|
2024-11-21 15:04:30 +11:00
|
|
|
sourceRange: SourceRange,
|
2023-04-01 16:47:00 +11:00
|
|
|
previousPath: PathToNode = [['body', '']]
|
|
|
|
): PathToNode {
|
|
|
|
const [start, end] = sourceRange
|
|
|
|
let path: PathToNode = [...previousPath]
|
|
|
|
const _node = { ...node }
|
|
|
|
|
|
|
|
if (start < _node.start || end > _node.end) return path
|
|
|
|
|
|
|
|
const isInRange = _node.start <= start && _node.end >= end
|
|
|
|
|
2024-07-22 19:43:40 -04:00
|
|
|
if (
|
|
|
|
(_node.type === 'Identifier' ||
|
|
|
|
_node.type === 'Literal' ||
|
|
|
|
_node.type === 'TagDeclarator') &&
|
|
|
|
isInRange
|
|
|
|
) {
|
2023-04-01 16:47:00 +11:00
|
|
|
return path
|
2024-07-22 19:43:40 -04:00
|
|
|
}
|
2023-04-01 16:47:00 +11:00
|
|
|
|
|
|
|
if (_node.type === 'CallExpression' && isInRange) {
|
|
|
|
const { callee, arguments: args } = _node
|
|
|
|
if (
|
|
|
|
callee.type === 'Identifier' &&
|
|
|
|
callee.start <= start &&
|
|
|
|
callee.end >= end
|
|
|
|
) {
|
|
|
|
path.push(['callee', 'CallExpression'])
|
|
|
|
return path
|
|
|
|
}
|
|
|
|
if (args.length > 0) {
|
|
|
|
for (let argIndex = 0; argIndex < args.length; argIndex++) {
|
|
|
|
const arg = args[argIndex]
|
|
|
|
if (arg.start <= start && arg.end >= end) {
|
|
|
|
path.push(['arguments', 'CallExpression'])
|
|
|
|
path.push([argIndex, 'index'])
|
|
|
|
return moreNodePathFromSourceRange(arg, sourceRange, path)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return path
|
|
|
|
}
|
2024-12-05 14:27:51 -06:00
|
|
|
|
|
|
|
if (_node.type === 'CallExpressionKw' && isInRange) {
|
|
|
|
const { callee, arguments: args } = _node
|
|
|
|
if (
|
|
|
|
callee.type === 'Identifier' &&
|
|
|
|
callee.start <= start &&
|
|
|
|
callee.end >= end
|
|
|
|
) {
|
|
|
|
path.push(['callee', 'CallExpressionKw'])
|
|
|
|
return path
|
|
|
|
}
|
|
|
|
if (args.length > 0) {
|
|
|
|
for (let argIndex = 0; argIndex < args.length; argIndex++) {
|
|
|
|
const arg = args[argIndex].arg
|
|
|
|
if (arg.start <= start && arg.end >= end) {
|
|
|
|
path.push(['arguments', 'CallExpressionKw'])
|
|
|
|
path.push([argIndex, 'index'])
|
|
|
|
return moreNodePathFromSourceRange(arg, sourceRange, path)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return path
|
|
|
|
}
|
|
|
|
|
2023-04-01 16:47:00 +11:00
|
|
|
if (_node.type === 'BinaryExpression' && isInRange) {
|
|
|
|
const { left, right } = _node
|
|
|
|
if (left.start <= start && left.end >= end) {
|
|
|
|
path.push(['left', 'BinaryExpression'])
|
|
|
|
return moreNodePathFromSourceRange(left, sourceRange, path)
|
|
|
|
}
|
|
|
|
if (right.start <= start && right.end >= end) {
|
|
|
|
path.push(['right', 'BinaryExpression'])
|
|
|
|
return moreNodePathFromSourceRange(right, sourceRange, path)
|
|
|
|
}
|
|
|
|
return path
|
|
|
|
}
|
|
|
|
if (_node.type === 'PipeExpression' && isInRange) {
|
|
|
|
const { body } = _node
|
|
|
|
for (let i = 0; i < body.length; i++) {
|
|
|
|
const pipe = body[i]
|
|
|
|
if (pipe.start <= start && pipe.end >= end) {
|
|
|
|
path.push(['body', 'PipeExpression'])
|
|
|
|
path.push([i, 'index'])
|
|
|
|
return moreNodePathFromSourceRange(pipe, sourceRange, path)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return path
|
|
|
|
}
|
|
|
|
if (_node.type === 'ArrayExpression' && isInRange) {
|
|
|
|
const { elements } = _node
|
|
|
|
for (let elIndex = 0; elIndex < elements.length; elIndex++) {
|
|
|
|
const element = elements[elIndex]
|
|
|
|
if (element.start <= start && element.end >= end) {
|
|
|
|
path.push(['elements', 'ArrayExpression'])
|
|
|
|
path.push([elIndex, 'index'])
|
|
|
|
return moreNodePathFromSourceRange(element, sourceRange, path)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return path
|
|
|
|
}
|
|
|
|
if (_node.type === 'ObjectExpression' && isInRange) {
|
|
|
|
const { properties } = _node
|
|
|
|
for (let propIndex = 0; propIndex < properties.length; propIndex++) {
|
|
|
|
const property = properties[propIndex]
|
|
|
|
if (property.start <= start && property.end >= end) {
|
|
|
|
path.push(['properties', 'ObjectExpression'])
|
|
|
|
path.push([propIndex, 'index'])
|
|
|
|
if (property.key.start <= start && property.key.end >= end) {
|
|
|
|
path.push(['key', 'Property'])
|
|
|
|
return moreNodePathFromSourceRange(property.key, sourceRange, path)
|
|
|
|
}
|
|
|
|
if (property.value.start <= start && property.value.end >= end) {
|
|
|
|
path.push(['value', 'Property'])
|
|
|
|
return moreNodePathFromSourceRange(property.value, sourceRange, path)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return path
|
|
|
|
}
|
|
|
|
if (_node.type === 'ExpressionStatement' && isInRange) {
|
|
|
|
const { expression } = _node
|
|
|
|
path.push(['expression', 'ExpressionStatement'])
|
|
|
|
return moreNodePathFromSourceRange(expression, sourceRange, path)
|
|
|
|
}
|
|
|
|
if (_node.type === 'VariableDeclaration' && isInRange) {
|
2024-12-07 07:16:04 +13:00
|
|
|
const declaration = _node.declaration
|
|
|
|
|
|
|
|
if (declaration.start <= start && declaration.end >= end) {
|
|
|
|
path.push(['declaration', 'VariableDeclaration'])
|
|
|
|
const init = declaration.init
|
|
|
|
if (init.start <= start && init.end >= end) {
|
|
|
|
path.push(['init', ''])
|
|
|
|
return moreNodePathFromSourceRange(init, sourceRange, path)
|
2023-04-01 16:47:00 +11:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (_node.type === 'VariableDeclaration' && isInRange) {
|
2024-12-07 07:16:04 +13:00
|
|
|
const declaration = _node.declaration
|
|
|
|
|
|
|
|
if (declaration.start <= start && declaration.end >= end) {
|
|
|
|
const init = declaration.init
|
|
|
|
if (init.start <= start && init.end >= end) {
|
|
|
|
path.push(['declaration', 'VariableDeclaration'])
|
|
|
|
path.push(['init', ''])
|
|
|
|
return moreNodePathFromSourceRange(init, sourceRange, path)
|
2023-04-01 16:47:00 +11:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return path
|
|
|
|
}
|
|
|
|
if (_node.type === 'UnaryExpression' && isInRange) {
|
|
|
|
const { argument } = _node
|
|
|
|
if (argument.start <= start && argument.end >= end) {
|
|
|
|
path.push(['argument', 'UnaryExpression'])
|
|
|
|
return moreNodePathFromSourceRange(argument, sourceRange, path)
|
|
|
|
}
|
|
|
|
return path
|
|
|
|
}
|
2023-09-21 15:40:41 +10:00
|
|
|
if (_node.type === 'FunctionExpression' && isInRange) {
|
|
|
|
for (let i = 0; i < _node.params.length; i++) {
|
|
|
|
const param = _node.params[i]
|
2023-11-20 11:19:08 -06:00
|
|
|
if (param.identifier.start <= start && param.identifier.end >= end) {
|
2023-09-21 15:40:41 +10:00
|
|
|
path.push(['params', 'FunctionExpression'])
|
|
|
|
path.push([i, 'index'])
|
2023-11-20 11:19:08 -06:00
|
|
|
return moreNodePathFromSourceRange(param.identifier, sourceRange, path)
|
2023-09-21 15:40:41 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (_node.body.start <= start && _node.body.end >= end) {
|
|
|
|
path.push(['body', 'FunctionExpression'])
|
|
|
|
const fnBody = _node.body.body
|
|
|
|
for (let i = 0; i < fnBody.length; i++) {
|
|
|
|
const statement = fnBody[i]
|
|
|
|
if (statement.start <= start && statement.end >= end) {
|
|
|
|
path.push(['body', 'FunctionExpression'])
|
|
|
|
path.push([i, 'index'])
|
|
|
|
return moreNodePathFromSourceRange(statement, sourceRange, path)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-07-22 19:43:40 -04:00
|
|
|
return path
|
|
|
|
}
|
|
|
|
if (_node.type === 'ReturnStatement' && isInRange) {
|
|
|
|
const { argument } = _node
|
|
|
|
if (argument.start <= start && argument.end >= end) {
|
|
|
|
path.push(['argument', 'ReturnStatement'])
|
|
|
|
return moreNodePathFromSourceRange(argument, sourceRange, path)
|
|
|
|
}
|
|
|
|
return path
|
2023-09-21 15:40:41 +10:00
|
|
|
}
|
2024-06-25 12:46:40 +10:00
|
|
|
if (_node.type === 'MemberExpression' && isInRange) {
|
|
|
|
const { object, property } = _node
|
|
|
|
if (object.start <= start && object.end >= end) {
|
|
|
|
path.push(['object', 'MemberExpression'])
|
|
|
|
return moreNodePathFromSourceRange(object, sourceRange, path)
|
|
|
|
}
|
|
|
|
if (property.start <= start && property.end >= end) {
|
|
|
|
path.push(['property', 'MemberExpression'])
|
|
|
|
return moreNodePathFromSourceRange(property, sourceRange, path)
|
|
|
|
}
|
|
|
|
return path
|
|
|
|
}
|
2024-06-29 18:10:07 -07:00
|
|
|
|
2024-02-11 12:59:00 +11:00
|
|
|
if (_node.type === 'PipeSubstitution' && isInRange) return path
|
2024-11-17 18:03:21 -05:00
|
|
|
|
|
|
|
if (_node.type === 'IfExpression' && isInRange) {
|
|
|
|
const { cond, then_val, else_ifs, final_else } = _node
|
|
|
|
if (cond.start <= start && cond.end >= end) {
|
|
|
|
path.push(['cond', 'IfExpression'])
|
|
|
|
return moreNodePathFromSourceRange(cond, sourceRange, path)
|
|
|
|
}
|
|
|
|
if (then_val.start <= start && then_val.end >= end) {
|
|
|
|
path.push(['then_val', 'IfExpression'])
|
|
|
|
path.push(['body', 'IfExpression'])
|
|
|
|
return getNodePathFromSourceRange(then_val, sourceRange, path)
|
|
|
|
}
|
|
|
|
for (let i = 0; i < else_ifs.length; i++) {
|
|
|
|
const else_if = else_ifs[i]
|
|
|
|
if (else_if.start <= start && else_if.end >= end) {
|
|
|
|
path.push(['else_ifs', 'IfExpression'])
|
|
|
|
path.push([i, 'index'])
|
|
|
|
const { cond, then_val } = else_if
|
|
|
|
if (cond.start <= start && cond.end >= end) {
|
|
|
|
path.push(['cond', 'IfExpression'])
|
|
|
|
return moreNodePathFromSourceRange(cond, sourceRange, path)
|
|
|
|
}
|
|
|
|
path.push(['then_val', 'IfExpression'])
|
|
|
|
path.push(['body', 'IfExpression'])
|
|
|
|
return getNodePathFromSourceRange(then_val, sourceRange, path)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (final_else.start <= start && final_else.end >= end) {
|
|
|
|
path.push(['final_else', 'IfExpression'])
|
|
|
|
path.push(['body', 'IfExpression'])
|
|
|
|
return getNodePathFromSourceRange(final_else, sourceRange, path)
|
|
|
|
}
|
|
|
|
return path
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_node.type === 'ImportStatement' && isInRange) {
|
2024-12-07 07:16:04 +13:00
|
|
|
if (_node.selector && _node.selector.type === 'List') {
|
|
|
|
path.push(['selector', 'ImportStatement'])
|
|
|
|
const { items } = _node.selector
|
|
|
|
for (let i = 0; i < items.length; i++) {
|
|
|
|
const item = items[i]
|
|
|
|
if (item.start <= start && item.end >= end) {
|
|
|
|
path.push(['items', 'ImportSelector'])
|
|
|
|
path.push([i, 'index'])
|
|
|
|
if (item.name.start <= start && item.name.end >= end) {
|
|
|
|
path.push(['name', 'ImportItem'])
|
|
|
|
return path
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
item.alias &&
|
|
|
|
item.alias.start <= start &&
|
|
|
|
item.alias.end >= end
|
|
|
|
) {
|
|
|
|
path.push(['alias', 'ImportItem'])
|
|
|
|
return path
|
|
|
|
}
|
2024-11-17 18:03:21 -05:00
|
|
|
return path
|
|
|
|
}
|
|
|
|
}
|
2024-12-07 07:16:04 +13:00
|
|
|
return path
|
2024-11-17 18:03:21 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-21 15:40:41 +10:00
|
|
|
console.error('not implemented: ' + node.type)
|
2024-06-29 18:10:07 -07:00
|
|
|
|
2023-04-01 16:47:00 +11:00
|
|
|
return path
|
|
|
|
}
|
|
|
|
|
2023-03-03 20:35:48 +11:00
|
|
|
export function getNodePathFromSourceRange(
|
|
|
|
node: Program,
|
2024-11-21 15:04:30 +11:00
|
|
|
sourceRange: SourceRange,
|
2023-04-01 16:47:00 +11:00
|
|
|
previousPath: PathToNode = [['body', '']]
|
2023-03-03 20:35:48 +11:00
|
|
|
): PathToNode {
|
2023-04-08 14:16:49 +10:00
|
|
|
const [start, end] = sourceRange || []
|
2023-04-01 16:47:00 +11:00
|
|
|
let path: PathToNode = [...previousPath]
|
2023-03-03 20:35:48 +11:00
|
|
|
const _node = { ...node }
|
2023-04-01 16:47:00 +11:00
|
|
|
|
2023-03-03 20:35:48 +11:00
|
|
|
// loop over each statement in body getting the index with a for loop
|
|
|
|
for (
|
|
|
|
let statementIndex = 0;
|
|
|
|
statementIndex < _node.body.length;
|
|
|
|
statementIndex++
|
|
|
|
) {
|
|
|
|
const statement = _node.body[statementIndex]
|
|
|
|
if (statement.start <= start && statement.end >= end) {
|
2023-04-01 16:47:00 +11:00
|
|
|
path.push([statementIndex, 'index'])
|
|
|
|
return moreNodePathFromSourceRange(statement, sourceRange, path)
|
2023-03-03 20:35:48 +11:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return path
|
|
|
|
}
|
2023-03-10 08:35:30 +11:00
|
|
|
|
2024-10-30 16:52:17 -04:00
|
|
|
type KCLNode = Node<
|
2024-08-12 15:38:42 -05:00
|
|
|
| Expr
|
2024-05-21 16:44:08 +10:00
|
|
|
| ExpressionStatement
|
|
|
|
| VariableDeclaration
|
|
|
|
| VariableDeclarator
|
|
|
|
| ReturnStatement
|
2024-10-30 16:52:17 -04:00
|
|
|
>
|
2024-05-21 16:44:08 +10:00
|
|
|
|
|
|
|
export function traverse(
|
2024-10-30 16:52:17 -04:00
|
|
|
node: KCLNode | Node<Program>,
|
2024-05-21 16:44:08 +10:00
|
|
|
option: {
|
2024-06-25 12:46:40 +10:00
|
|
|
enter?: (node: KCLNode, pathToNode: PathToNode) => void
|
2024-05-21 16:44:08 +10:00
|
|
|
leave?: (node: KCLNode) => void
|
2024-06-25 12:46:40 +10:00
|
|
|
},
|
|
|
|
pathToNode: PathToNode = []
|
2024-05-21 16:44:08 +10:00
|
|
|
) {
|
2024-06-25 12:46:40 +10:00
|
|
|
const _node = node as KCLNode
|
|
|
|
option?.enter?.(_node, pathToNode)
|
|
|
|
const _traverse = (node: KCLNode, pathToNode: PathToNode) =>
|
|
|
|
traverse(node, option, pathToNode)
|
2024-05-21 16:44:08 +10:00
|
|
|
|
2024-06-25 12:46:40 +10:00
|
|
|
if (_node.type === 'VariableDeclaration') {
|
2024-12-07 07:16:04 +13:00
|
|
|
_traverse(_node.declaration, [
|
|
|
|
...pathToNode,
|
|
|
|
['declaration', 'VariableDeclaration'],
|
|
|
|
])
|
2024-06-25 12:46:40 +10:00
|
|
|
} else if (_node.type === 'VariableDeclarator') {
|
|
|
|
_traverse(_node.init, [...pathToNode, ['init', '']])
|
|
|
|
} else if (_node.type === 'PipeExpression') {
|
|
|
|
_node.body.forEach((expression, index) =>
|
|
|
|
_traverse(expression, [
|
|
|
|
...pathToNode,
|
|
|
|
['body', 'PipeExpression'],
|
|
|
|
[index, 'index'],
|
|
|
|
])
|
|
|
|
)
|
|
|
|
} else if (_node.type === 'CallExpression') {
|
|
|
|
_traverse(_node.callee, [...pathToNode, ['callee', 'CallExpression']])
|
|
|
|
_node.arguments.forEach((arg, index) =>
|
|
|
|
_traverse(arg, [
|
|
|
|
...pathToNode,
|
|
|
|
['arguments', 'CallExpression'],
|
|
|
|
[index, 'index'],
|
|
|
|
])
|
|
|
|
)
|
|
|
|
} else if (_node.type === 'BinaryExpression') {
|
|
|
|
_traverse(_node.left, [...pathToNode, ['left', 'BinaryExpression']])
|
|
|
|
_traverse(_node.right, [...pathToNode, ['right', 'BinaryExpression']])
|
|
|
|
} else if (_node.type === 'Identifier') {
|
2024-05-21 16:44:08 +10:00
|
|
|
// do nothing
|
2024-06-25 12:46:40 +10:00
|
|
|
} else if (_node.type === 'Literal') {
|
2024-05-21 16:44:08 +10:00
|
|
|
// do nothing
|
2024-06-24 22:39:04 -07:00
|
|
|
} else if (_node.type === 'TagDeclarator') {
|
|
|
|
// do nothing
|
2024-06-25 12:46:40 +10:00
|
|
|
} else if (_node.type === 'ArrayExpression') {
|
|
|
|
_node.elements.forEach((el, index) =>
|
|
|
|
_traverse(el, [
|
|
|
|
...pathToNode,
|
|
|
|
['elements', 'ArrayExpression'],
|
|
|
|
[index, 'index'],
|
|
|
|
])
|
|
|
|
)
|
|
|
|
} else if (_node.type === 'ObjectExpression') {
|
|
|
|
_node.properties.forEach(({ key, value }, index) => {
|
|
|
|
_traverse(key, [
|
|
|
|
...pathToNode,
|
|
|
|
['properties', 'ObjectExpression'],
|
|
|
|
[index, 'index'],
|
|
|
|
['key', 'Property'],
|
|
|
|
])
|
|
|
|
_traverse(value, [
|
|
|
|
...pathToNode,
|
|
|
|
['properties', 'ObjectExpression'],
|
|
|
|
[index, 'index'],
|
|
|
|
['value', 'Property'],
|
|
|
|
])
|
2024-05-21 16:44:08 +10:00
|
|
|
})
|
2024-06-25 12:46:40 +10:00
|
|
|
} else if (_node.type === 'UnaryExpression') {
|
|
|
|
_traverse(_node.argument, [...pathToNode, ['argument', 'UnaryExpression']])
|
|
|
|
} else if (_node.type === 'MemberExpression') {
|
2024-05-21 16:44:08 +10:00
|
|
|
// hmm this smell
|
2024-06-25 12:46:40 +10:00
|
|
|
_traverse(_node.object, [...pathToNode, ['object', 'MemberExpression']])
|
|
|
|
_traverse(_node.property, [...pathToNode, ['property', 'MemberExpression']])
|
|
|
|
} else if ('body' in _node && Array.isArray(_node.body)) {
|
|
|
|
_node.body.forEach((expression, index) =>
|
|
|
|
_traverse(expression, [...pathToNode, ['body', ''], [index, 'index']])
|
|
|
|
)
|
2024-05-21 16:44:08 +10:00
|
|
|
}
|
2024-06-25 12:46:40 +10:00
|
|
|
option?.leave?.(_node)
|
2024-05-21 16:44:08 +10:00
|
|
|
}
|
|
|
|
|
2023-03-10 08:48:50 +11:00
|
|
|
export interface PrevVariable<T> {
|
2023-03-10 08:35:30 +11:00
|
|
|
key: string
|
|
|
|
value: T
|
|
|
|
}
|
|
|
|
|
2024-05-24 20:54:42 +10:00
|
|
|
export function findAllPreviousVariablesPath(
|
2023-03-10 08:35:30 +11:00
|
|
|
ast: Program,
|
|
|
|
programMemory: ProgramMemory,
|
2024-05-24 20:54:42 +10:00
|
|
|
path: PathToNode,
|
2023-03-10 08:35:30 +11:00
|
|
|
type: 'number' | 'string' = 'number'
|
|
|
|
): {
|
|
|
|
variables: PrevVariable<typeof type extends 'number' ? number : string>[]
|
|
|
|
bodyPath: PathToNode
|
|
|
|
insertIndex: number
|
|
|
|
} {
|
2024-06-24 11:45:40 -04:00
|
|
|
const _node1 = getNodeFromPath(ast, path, 'VariableDeclaration')
|
|
|
|
if (err(_node1)) {
|
|
|
|
console.error(_node1)
|
|
|
|
return {
|
|
|
|
variables: [],
|
|
|
|
bodyPath: [],
|
|
|
|
insertIndex: 0,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const { shallowPath: pathToDec, node } = _node1
|
2024-05-24 20:54:42 +10:00
|
|
|
|
|
|
|
const startRange = (node as any).start
|
|
|
|
|
2023-03-10 08:35:30 +11:00
|
|
|
const { index: insertIndex, path: bodyPath } = splitPathAtLastIndex(pathToDec)
|
|
|
|
|
2024-06-24 11:45:40 -04:00
|
|
|
const _node2 = getNodeFromPath<Program['body']>(ast, bodyPath)
|
|
|
|
if (err(_node2)) {
|
|
|
|
console.error(_node2)
|
|
|
|
return {
|
|
|
|
variables: [],
|
|
|
|
bodyPath: [],
|
|
|
|
insertIndex: 0,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const { node: bodyItems } = _node2
|
2023-03-10 08:35:30 +11:00
|
|
|
|
|
|
|
const variables: PrevVariable<any>[] = []
|
2023-03-21 19:02:18 +11:00
|
|
|
bodyItems?.forEach?.((item) => {
|
2024-05-24 20:54:42 +10:00
|
|
|
if (item.type !== 'VariableDeclaration' || item.end > startRange) return
|
2024-12-07 07:16:04 +13:00
|
|
|
const varName = item.declaration.id.name
|
2024-07-22 19:43:40 -04:00
|
|
|
const varValue = programMemory?.get(varName)
|
|
|
|
if (!varValue || typeof varValue?.value !== type) return
|
2023-03-10 08:35:30 +11:00
|
|
|
variables.push({
|
|
|
|
key: varName,
|
|
|
|
value: varValue.value,
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
return {
|
|
|
|
insertIndex,
|
|
|
|
bodyPath: bodyPath,
|
|
|
|
variables,
|
|
|
|
}
|
|
|
|
}
|
2023-04-01 16:47:00 +11:00
|
|
|
|
2024-05-24 20:54:42 +10:00
|
|
|
export function findAllPreviousVariables(
|
|
|
|
ast: Program,
|
|
|
|
programMemory: ProgramMemory,
|
2024-11-21 15:04:30 +11:00
|
|
|
sourceRange: SourceRange,
|
2024-05-24 20:54:42 +10:00
|
|
|
type: 'number' | 'string' = 'number'
|
|
|
|
): {
|
|
|
|
variables: PrevVariable<typeof type extends 'number' ? number : string>[]
|
|
|
|
bodyPath: PathToNode
|
|
|
|
insertIndex: number
|
|
|
|
} {
|
|
|
|
const path = getNodePathFromSourceRange(ast, sourceRange)
|
|
|
|
return findAllPreviousVariablesPath(ast, programMemory, path, type)
|
|
|
|
}
|
2023-04-01 16:47:00 +11:00
|
|
|
|
2024-05-24 20:54:42 +10:00
|
|
|
type ReplacerFn = (
|
2024-10-30 16:52:17 -04:00
|
|
|
_ast: Node<Program>,
|
2024-05-24 20:54:42 +10:00
|
|
|
varName: string
|
2024-10-30 16:52:17 -04:00
|
|
|
) => { modifiedAst: Node<Program>; pathToReplaced: PathToNode } | Error
|
2024-05-24 20:54:42 +10:00
|
|
|
|
|
|
|
export function isNodeSafeToReplacePath(
|
2023-04-01 16:47:00 +11:00
|
|
|
ast: Program,
|
2024-05-24 20:54:42 +10:00
|
|
|
path: PathToNode
|
2024-06-24 11:45:40 -04:00
|
|
|
):
|
|
|
|
| {
|
|
|
|
isSafe: boolean
|
2024-08-12 15:38:42 -05:00
|
|
|
value: Expr
|
2024-06-24 11:45:40 -04:00
|
|
|
replacer: ReplacerFn
|
|
|
|
}
|
|
|
|
| Error {
|
2023-04-01 16:47:00 +11:00
|
|
|
if (path[path.length - 1][0] === 'callee') {
|
|
|
|
path = path.slice(0, -1)
|
|
|
|
}
|
2024-05-24 20:54:42 +10:00
|
|
|
const acceptedNodeTypes: SyntaxType[] = [
|
2023-04-01 16:47:00 +11:00
|
|
|
'BinaryExpression',
|
|
|
|
'Identifier',
|
|
|
|
'CallExpression',
|
|
|
|
'Literal',
|
2024-05-24 20:54:42 +10:00
|
|
|
'UnaryExpression',
|
2023-04-01 16:47:00 +11:00
|
|
|
]
|
2024-06-24 11:45:40 -04:00
|
|
|
const _node1 = getNodeFromPath(ast, path, acceptedNodeTypes)
|
|
|
|
if (err(_node1)) return _node1
|
|
|
|
const { node: value, deepPath: outPath } = _node1
|
|
|
|
|
|
|
|
const _node2 = getNodeFromPath(ast, path, 'BinaryExpression')
|
|
|
|
if (err(_node2)) return _node2
|
|
|
|
const { node: binValue, shallowPath: outBinPath } = _node2
|
|
|
|
|
2023-04-01 16:47:00 +11:00
|
|
|
// binaryExpression should take precedence
|
|
|
|
const [finVal, finPath] =
|
2024-08-12 15:38:42 -05:00
|
|
|
(binValue as Expr)?.type === 'BinaryExpression'
|
2023-04-01 16:47:00 +11:00
|
|
|
? [binValue, outBinPath]
|
|
|
|
: [value, outPath]
|
|
|
|
|
|
|
|
const replaceNodeWithIdentifier: ReplacerFn = (_ast, varName) => {
|
|
|
|
const identifier = createIdentifier(varName)
|
|
|
|
const last = finPath[finPath.length - 1]
|
2024-07-25 20:11:46 -04:00
|
|
|
const pathToReplaced = structuredClone(finPath)
|
|
|
|
const index = pathToReplaced[1][0]
|
|
|
|
if (typeof index !== 'number') {
|
|
|
|
return new Error(
|
|
|
|
`Expected number index, but found: ${typeof index} ${index}`
|
|
|
|
)
|
|
|
|
}
|
|
|
|
pathToReplaced[1][0] = index + 1
|
2023-04-01 16:47:00 +11:00
|
|
|
const startPath = finPath.slice(0, -1)
|
2024-06-24 11:45:40 -04:00
|
|
|
const _nodeToReplace = getNodeFromPath(_ast, startPath)
|
|
|
|
if (err(_nodeToReplace)) return _nodeToReplace
|
|
|
|
const nodeToReplace = _nodeToReplace.node as any
|
2023-04-01 16:47:00 +11:00
|
|
|
nodeToReplace[last[0]] = identifier
|
2024-05-24 20:54:42 +10:00
|
|
|
return { modifiedAst: _ast, pathToReplaced }
|
2023-04-01 16:47:00 +11:00
|
|
|
}
|
|
|
|
|
2024-08-12 15:38:42 -05:00
|
|
|
const hasPipeSub = isTypeInValue(finVal as Expr, 'PipeSubstitution')
|
2023-04-01 16:47:00 +11:00
|
|
|
const isIdentifierCallee = path[path.length - 1][0] !== 'callee'
|
|
|
|
return {
|
|
|
|
isSafe:
|
|
|
|
!hasPipeSub &&
|
|
|
|
isIdentifierCallee &&
|
|
|
|
acceptedNodeTypes.includes((finVal as any)?.type) &&
|
|
|
|
finPath.map(([_, type]) => type).includes('VariableDeclaration'),
|
2024-08-12 15:38:42 -05:00
|
|
|
value: finVal as Expr,
|
2023-04-01 16:47:00 +11:00
|
|
|
replacer: replaceNodeWithIdentifier,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-24 20:54:42 +10:00
|
|
|
export function isNodeSafeToReplace(
|
2024-10-30 16:52:17 -04:00
|
|
|
ast: Node<Program>,
|
2024-12-06 13:57:31 +13:00
|
|
|
sourceRange: SourceRange
|
2024-06-24 11:45:40 -04:00
|
|
|
):
|
|
|
|
| {
|
|
|
|
isSafe: boolean
|
2024-10-30 16:52:17 -04:00
|
|
|
value: Node<Expr>
|
2024-06-24 11:45:40 -04:00
|
|
|
replacer: ReplacerFn
|
|
|
|
}
|
|
|
|
| Error {
|
2024-05-24 20:54:42 +10:00
|
|
|
let path = getNodePathFromSourceRange(ast, sourceRange)
|
|
|
|
return isNodeSafeToReplacePath(ast, path)
|
|
|
|
}
|
|
|
|
|
2024-08-12 15:38:42 -05:00
|
|
|
export function isTypeInValue(node: Expr, syntaxType: SyntaxType): boolean {
|
2023-04-01 16:47:00 +11:00
|
|
|
if (node.type === syntaxType) return true
|
|
|
|
if (node.type === 'BinaryExpression') return isTypeInBinExp(node, syntaxType)
|
|
|
|
if (node.type === 'CallExpression') return isTypeInCallExp(node, syntaxType)
|
|
|
|
if (node.type === 'ArrayExpression') return isTypeInArrayExp(node, syntaxType)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
function isTypeInBinExp(
|
|
|
|
node: BinaryExpression,
|
|
|
|
syntaxType: SyntaxType
|
|
|
|
): boolean {
|
|
|
|
if (node.type === syntaxType) return true
|
|
|
|
if (node.left.type === syntaxType) return true
|
|
|
|
if (node.right.type === syntaxType) return true
|
|
|
|
|
|
|
|
return (
|
|
|
|
isTypeInValue(node.left, syntaxType) ||
|
|
|
|
isTypeInValue(node.right, syntaxType)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
function isTypeInCallExp(
|
|
|
|
node: CallExpression,
|
|
|
|
syntaxType: SyntaxType
|
|
|
|
): boolean {
|
|
|
|
if (node.callee.type === syntaxType) return true
|
|
|
|
return node.arguments.some((arg) => isTypeInValue(arg, syntaxType))
|
|
|
|
}
|
|
|
|
|
|
|
|
function isTypeInArrayExp(
|
|
|
|
node: ArrayExpression,
|
|
|
|
syntaxType: SyntaxType
|
|
|
|
): boolean {
|
|
|
|
return node.elements.some((el) => isTypeInValue(el, syntaxType))
|
|
|
|
}
|
2023-04-06 12:45:56 +10:00
|
|
|
|
2024-08-12 15:38:42 -05:00
|
|
|
export function isValueZero(val?: Expr): boolean {
|
2023-04-06 12:45:56 +10:00
|
|
|
return (
|
|
|
|
(val?.type === 'Literal' && Number(val.value) === 0) ||
|
|
|
|
(val?.type === 'UnaryExpression' &&
|
|
|
|
val.operator === '-' &&
|
|
|
|
val.argument.type === 'Literal' &&
|
|
|
|
Number(val.argument.value) === 0)
|
|
|
|
)
|
|
|
|
}
|
2023-04-08 14:16:49 +10:00
|
|
|
|
|
|
|
export function isLinesParallelAndConstrained(
|
|
|
|
ast: Program,
|
2024-11-21 15:04:30 +11:00
|
|
|
artifactGraph: ArtifactGraph,
|
2023-04-08 14:16:49 +10:00
|
|
|
programMemory: ProgramMemory,
|
|
|
|
primaryLine: Selection,
|
|
|
|
secondaryLine: Selection
|
2024-06-24 11:45:40 -04:00
|
|
|
):
|
|
|
|
| {
|
|
|
|
isParallelAndConstrained: boolean
|
2024-11-21 15:04:30 +11:00
|
|
|
selection: Selection | null
|
2024-06-24 11:45:40 -04:00
|
|
|
}
|
|
|
|
| Error {
|
2023-04-08 14:16:49 +10:00
|
|
|
try {
|
|
|
|
const EPSILON = 0.005
|
2024-11-21 15:04:30 +11:00
|
|
|
const primaryPath = getNodePathFromSourceRange(
|
|
|
|
ast,
|
|
|
|
primaryLine?.codeRef?.range
|
|
|
|
)
|
|
|
|
const secondaryPath = getNodePathFromSourceRange(
|
|
|
|
ast,
|
|
|
|
secondaryLine?.codeRef?.range
|
|
|
|
)
|
2024-06-24 11:45:40 -04:00
|
|
|
const _secondaryNode = getNodeFromPath<CallExpression>(
|
2023-04-08 14:16:49 +10:00
|
|
|
ast,
|
|
|
|
secondaryPath,
|
|
|
|
'CallExpression'
|
2024-06-24 11:45:40 -04:00
|
|
|
)
|
|
|
|
if (err(_secondaryNode)) return _secondaryNode
|
|
|
|
const secondaryNode = _secondaryNode.node
|
|
|
|
const _varDec = getNodeFromPath(ast, primaryPath, 'VariableDeclaration')
|
|
|
|
if (err(_varDec)) return _varDec
|
|
|
|
const varDec = _varDec.node
|
2024-12-07 07:16:04 +13:00
|
|
|
const varName = (varDec as VariableDeclaration)?.declaration.id?.name
|
2024-09-27 15:44:44 -07:00
|
|
|
const sg = sketchFromKclValue(programMemory?.get(varName), varName)
|
Remove KclValue::SketchGroup variant (#3446)
We can store Rust types like `SketchGroup` as their own variant of `KclValue`, or as `KclValue::UserVal`. Sometimes we store in one and try to read from the other, which fails. This causes bugs, like #3338.
Instead, we should use either ::SketchGroup or ::UserVal, and stop using the other. If we stopped using ::UserVal, we'd need a new variant for every Rust type we wanted to build, including user-defined types. So I don't think that's practical.
Instead, we should store every KCL value by de/serializing it into UserVal. This is a first step along that path, removing just the SketchGroup variants. If it goes well, we can remove the other specialized variants too.
My only concern is there might be performance implications from how frequently we convert between serde_json::Value and Rust types via Serde. But I'm not too worried -- there's no parsing JSON strings, just traversing serde_json::Value trees. This isn't great for performance but I think it'll probably be miniscule in comparison to doing all the API calls.
2024-08-21 11:06:48 -05:00
|
|
|
if (err(sg)) return sg
|
2024-06-24 11:45:40 -04:00
|
|
|
const _primarySegment = getSketchSegmentFromSourceRange(
|
Remove KclValue::SketchGroup variant (#3446)
We can store Rust types like `SketchGroup` as their own variant of `KclValue`, or as `KclValue::UserVal`. Sometimes we store in one and try to read from the other, which fails. This causes bugs, like #3338.
Instead, we should use either ::SketchGroup or ::UserVal, and stop using the other. If we stopped using ::UserVal, we'd need a new variant for every Rust type we wanted to build, including user-defined types. So I don't think that's practical.
Instead, we should store every KCL value by de/serializing it into UserVal. This is a first step along that path, removing just the SketchGroup variants. If it goes well, we can remove the other specialized variants too.
My only concern is there might be performance implications from how frequently we convert between serde_json::Value and Rust types via Serde. But I'm not too worried -- there's no parsing JSON strings, just traversing serde_json::Value trees. This isn't great for performance but I think it'll probably be miniscule in comparison to doing all the API calls.
2024-08-21 11:06:48 -05:00
|
|
|
sg,
|
2024-11-21 15:04:30 +11:00
|
|
|
primaryLine?.codeRef?.range
|
2024-06-24 11:45:40 -04:00
|
|
|
)
|
|
|
|
if (err(_primarySegment)) return _primarySegment
|
|
|
|
const primarySegment = _primarySegment.segment
|
|
|
|
|
2024-11-21 15:04:30 +11:00
|
|
|
const _segment = getSketchSegmentFromSourceRange(
|
|
|
|
sg,
|
|
|
|
secondaryLine?.codeRef?.range
|
|
|
|
)
|
2024-06-24 11:45:40 -04:00
|
|
|
if (err(_segment)) return _segment
|
|
|
|
const { segment: secondarySegment, index: secondaryIndex } = _segment
|
2023-04-08 14:16:49 +10:00
|
|
|
const primaryAngle = getAngle(primarySegment.from, primarySegment.to)
|
|
|
|
const secondaryAngle = getAngle(secondarySegment.from, secondarySegment.to)
|
|
|
|
const secondaryAngleAlt = getAngle(
|
|
|
|
secondarySegment.to,
|
|
|
|
secondarySegment.from
|
|
|
|
)
|
|
|
|
const isParallel =
|
|
|
|
Math.abs(primaryAngle - secondaryAngle) < EPSILON ||
|
|
|
|
Math.abs(primaryAngle - secondaryAngleAlt) < EPSILON
|
|
|
|
|
2024-11-21 15:04:30 +11:00
|
|
|
// is secondary line fully constrain, or has constrain type of 'angle'
|
2023-04-08 14:16:49 +10:00
|
|
|
const secondaryFirstArg = getFirstArg(secondaryNode)
|
2024-06-24 11:45:40 -04:00
|
|
|
if (err(secondaryFirstArg)) return secondaryFirstArg
|
|
|
|
|
2023-04-08 14:16:49 +10:00
|
|
|
const constraintType = getConstraintType(
|
|
|
|
secondaryFirstArg.val,
|
2023-09-15 11:48:23 -04:00
|
|
|
secondaryNode.callee.name as ToolTip
|
2023-04-08 14:16:49 +10:00
|
|
|
)
|
2024-06-24 11:45:40 -04:00
|
|
|
|
|
|
|
const constraintLevelMeta = getConstraintLevelFromSourceRange(
|
2024-11-21 15:04:30 +11:00
|
|
|
secondaryLine?.codeRef.range,
|
2023-04-08 14:16:49 +10:00
|
|
|
ast
|
2024-06-24 11:45:40 -04:00
|
|
|
)
|
|
|
|
if (err(constraintLevelMeta)) {
|
|
|
|
console.error(constraintLevelMeta)
|
|
|
|
return {
|
|
|
|
isParallelAndConstrained: false,
|
2024-11-21 15:04:30 +11:00
|
|
|
selection: null,
|
2024-06-24 11:45:40 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
const constraintLevel = constraintLevelMeta.level
|
|
|
|
|
2023-04-08 14:16:49 +10:00
|
|
|
const isConstrained =
|
|
|
|
constraintType === 'angle' || constraintLevel === 'full'
|
|
|
|
|
|
|
|
// get the previous segment
|
2024-10-23 12:42:54 -05:00
|
|
|
const prevSegment = sg.paths[secondaryIndex - 1]
|
2023-04-08 14:16:49 +10:00
|
|
|
const prevSourceRange = prevSegment.__geoMeta.sourceRange
|
|
|
|
|
|
|
|
const isParallelAndConstrained =
|
|
|
|
isParallel && isConstrained && !!prevSourceRange
|
|
|
|
|
|
|
|
return {
|
|
|
|
isParallelAndConstrained,
|
2024-11-21 15:04:30 +11:00
|
|
|
selection: {
|
2024-12-06 13:57:31 +13:00
|
|
|
codeRef: codeRefFromRange(sourceRangeFromRust(prevSourceRange), ast),
|
2024-11-21 15:04:30 +11:00
|
|
|
artifact: artifactGraph.get(prevSegment.__geoMeta.id),
|
|
|
|
},
|
2023-04-08 14:16:49 +10:00
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
return {
|
|
|
|
isParallelAndConstrained: false,
|
2024-11-21 15:04:30 +11:00
|
|
|
selection: null,
|
2023-04-08 14:16:49 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-10-11 13:36:54 +11:00
|
|
|
|
|
|
|
export function doesPipeHaveCallExp({
|
|
|
|
ast,
|
|
|
|
selection,
|
|
|
|
calleeName,
|
|
|
|
}: {
|
|
|
|
calleeName: string
|
|
|
|
ast: Program
|
|
|
|
selection: Selection
|
|
|
|
}): boolean {
|
2024-06-24 11:45:40 -04:00
|
|
|
const pipeExpressionMeta = getNodeFromPath<PipeExpression>(
|
2023-10-11 13:36:54 +11:00
|
|
|
ast,
|
2024-11-21 15:04:30 +11:00
|
|
|
selection?.codeRef?.pathToNode,
|
2023-10-11 13:36:54 +11:00
|
|
|
'PipeExpression'
|
2024-06-24 11:45:40 -04:00
|
|
|
)
|
|
|
|
if (err(pipeExpressionMeta)) {
|
|
|
|
console.error(pipeExpressionMeta)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
const pipeExpression = pipeExpressionMeta.node
|
2023-10-11 13:36:54 +11:00
|
|
|
if (pipeExpression.type !== 'PipeExpression') return false
|
|
|
|
return pipeExpression.body.some(
|
|
|
|
(expression) =>
|
|
|
|
expression.type === 'CallExpression' &&
|
|
|
|
expression.callee.name === calleeName
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2024-09-27 15:44:44 -07:00
|
|
|
export function hasExtrudeSketch({
|
2023-10-11 13:36:54 +11:00
|
|
|
ast,
|
|
|
|
selection,
|
|
|
|
programMemory,
|
|
|
|
}: {
|
|
|
|
ast: Program
|
|
|
|
selection: Selection
|
|
|
|
programMemory: ProgramMemory
|
|
|
|
}): boolean {
|
2024-06-24 11:45:40 -04:00
|
|
|
const varDecMeta = getNodeFromPath<VariableDeclaration>(
|
2023-10-11 13:36:54 +11:00
|
|
|
ast,
|
2024-11-21 15:04:30 +11:00
|
|
|
selection?.codeRef?.pathToNode,
|
2023-10-11 13:36:54 +11:00
|
|
|
'VariableDeclaration'
|
2024-06-24 11:45:40 -04:00
|
|
|
)
|
|
|
|
if (err(varDecMeta)) {
|
|
|
|
console.error(varDecMeta)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
const varDec = varDecMeta.node
|
2023-10-11 13:36:54 +11:00
|
|
|
if (varDec.type !== 'VariableDeclaration') return false
|
2024-12-07 07:16:04 +13:00
|
|
|
const varName = varDec.declaration.id.name
|
2024-07-22 19:43:40 -04:00
|
|
|
const varValue = programMemory?.get(varName)
|
Remove KclValue::SketchGroup variant (#3446)
We can store Rust types like `SketchGroup` as their own variant of `KclValue`, or as `KclValue::UserVal`. Sometimes we store in one and try to read from the other, which fails. This causes bugs, like #3338.
Instead, we should use either ::SketchGroup or ::UserVal, and stop using the other. If we stopped using ::UserVal, we'd need a new variant for every Rust type we wanted to build, including user-defined types. So I don't think that's practical.
Instead, we should store every KCL value by de/serializing it into UserVal. This is a first step along that path, removing just the SketchGroup variants. If it goes well, we can remove the other specialized variants too.
My only concern is there might be performance implications from how frequently we convert between serde_json::Value and Rust types via Serde. But I'm not too worried -- there's no parsing JSON strings, just traversing serde_json::Value trees. This isn't great for performance but I think it'll probably be miniscule in comparison to doing all the API calls.
2024-08-21 11:06:48 -05:00
|
|
|
return (
|
2024-11-19 17:34:54 -05:00
|
|
|
varValue?.type === 'Solid' ||
|
|
|
|
!(sketchFromKclValueOptional(varValue, varName) instanceof Reason)
|
Remove KclValue::SketchGroup variant (#3446)
We can store Rust types like `SketchGroup` as their own variant of `KclValue`, or as `KclValue::UserVal`. Sometimes we store in one and try to read from the other, which fails. This causes bugs, like #3338.
Instead, we should use either ::SketchGroup or ::UserVal, and stop using the other. If we stopped using ::UserVal, we'd need a new variant for every Rust type we wanted to build, including user-defined types. So I don't think that's practical.
Instead, we should store every KCL value by de/serializing it into UserVal. This is a first step along that path, removing just the SketchGroup variants. If it goes well, we can remove the other specialized variants too.
My only concern is there might be performance implications from how frequently we convert between serde_json::Value and Rust types via Serde. But I'm not too worried -- there's no parsing JSON strings, just traversing serde_json::Value trees. This isn't great for performance but I think it'll probably be miniscule in comparison to doing all the API calls.
2024-08-21 11:06:48 -05:00
|
|
|
)
|
2023-10-11 13:36:54 +11:00
|
|
|
}
|
2024-02-19 17:23:03 +11:00
|
|
|
|
|
|
|
export function isSingleCursorInPipe(
|
|
|
|
selectionRanges: Selections,
|
|
|
|
ast: Program
|
|
|
|
) {
|
2024-11-21 15:04:30 +11:00
|
|
|
if (selectionRanges.graphSelections.length !== 1) return false
|
|
|
|
const selection = selectionRanges.graphSelections[0]
|
|
|
|
const pathToNode = getNodePathFromSourceRange(ast, selection?.codeRef?.range)
|
2024-02-19 17:23:03 +11:00
|
|
|
const nodeTypes = pathToNode.map(([, type]) => type)
|
|
|
|
if (nodeTypes.includes('FunctionExpression')) return false
|
2024-08-06 16:17:30 +10:00
|
|
|
if (!nodeTypes.includes('VariableDeclaration')) return false
|
2024-02-19 17:23:03 +11:00
|
|
|
if (nodeTypes.includes('PipeExpression')) return true
|
|
|
|
return false
|
|
|
|
}
|
2024-05-24 20:54:42 +10:00
|
|
|
|
|
|
|
export function findUsesOfTagInPipe(
|
|
|
|
ast: Program,
|
|
|
|
pathToNode: PathToNode
|
|
|
|
): SourceRange[] {
|
|
|
|
const stdlibFunctionsThatTakeTagInputs = [
|
|
|
|
'segAng',
|
|
|
|
'segEndX',
|
|
|
|
'segEndY',
|
|
|
|
'segLen',
|
|
|
|
]
|
2024-06-24 11:45:40 -04:00
|
|
|
const nodeMeta = getNodeFromPath<CallExpression>(
|
2024-05-24 20:54:42 +10:00
|
|
|
ast,
|
|
|
|
pathToNode,
|
|
|
|
'CallExpression'
|
2024-06-24 11:45:40 -04:00
|
|
|
)
|
|
|
|
if (err(nodeMeta)) {
|
|
|
|
console.error(nodeMeta)
|
|
|
|
return []
|
|
|
|
}
|
|
|
|
const node = nodeMeta.node
|
2024-05-24 20:54:42 +10:00
|
|
|
if (node.type !== 'CallExpression') return []
|
|
|
|
const tagIndex = node.callee.name === 'close' ? 1 : 2
|
|
|
|
const thirdParam = node.arguments[tagIndex]
|
2024-06-24 22:39:04 -07:00
|
|
|
if (
|
|
|
|
!(thirdParam?.type === 'TagDeclarator' || thirdParam?.type === 'Identifier')
|
|
|
|
)
|
|
|
|
return []
|
|
|
|
const tag =
|
|
|
|
thirdParam?.type === 'TagDeclarator'
|
|
|
|
? String(thirdParam.value)
|
|
|
|
: thirdParam.name
|
2024-05-24 20:54:42 +10:00
|
|
|
|
2024-10-30 16:52:17 -04:00
|
|
|
const varDec = getNodeFromPath<Node<VariableDeclaration>>(
|
2024-05-24 20:54:42 +10:00
|
|
|
ast,
|
|
|
|
pathToNode,
|
|
|
|
'VariableDeclaration'
|
2024-06-24 11:45:40 -04:00
|
|
|
)
|
|
|
|
if (err(varDec)) {
|
|
|
|
console.error(varDec)
|
|
|
|
return []
|
|
|
|
}
|
2024-05-24 20:54:42 +10:00
|
|
|
const dependentRanges: SourceRange[] = []
|
|
|
|
|
2024-06-24 11:45:40 -04:00
|
|
|
traverse(varDec.node, {
|
2024-05-24 20:54:42 +10:00
|
|
|
enter: (node) => {
|
|
|
|
if (
|
|
|
|
node.type !== 'CallExpression' ||
|
|
|
|
!stdlibFunctionsThatTakeTagInputs.includes(node.callee.name)
|
|
|
|
)
|
|
|
|
return
|
|
|
|
const tagArg = node.arguments[0]
|
2024-06-24 22:39:04 -07:00
|
|
|
if (!(tagArg.type === 'TagDeclarator' || tagArg.type === 'Identifier'))
|
|
|
|
return
|
|
|
|
const tagArgValue =
|
|
|
|
tagArg.type === 'TagDeclarator' ? String(tagArg.value) : tagArg.name
|
2024-12-06 13:57:31 +13:00
|
|
|
if (tagArgValue === tag)
|
|
|
|
dependentRanges.push([node.start, node.end, true])
|
2024-05-24 20:54:42 +10:00
|
|
|
},
|
|
|
|
})
|
|
|
|
return dependentRanges
|
|
|
|
}
|
2024-06-21 13:20:42 +10:00
|
|
|
|
|
|
|
export function hasSketchPipeBeenExtruded(selection: Selection, ast: Program) {
|
2024-11-19 21:30:26 +01:00
|
|
|
const _node = getNodeFromPath<Node<PipeExpression>>(
|
|
|
|
ast,
|
2024-11-21 15:04:30 +11:00
|
|
|
selection.codeRef.pathToNode,
|
2024-11-19 21:30:26 +01:00
|
|
|
'PipeExpression'
|
|
|
|
)
|
2024-06-24 11:45:40 -04:00
|
|
|
if (err(_node)) return false
|
|
|
|
const { node: pipeExpression } = _node
|
2024-06-21 13:20:42 +10:00
|
|
|
if (pipeExpression.type !== 'PipeExpression') return false
|
2024-06-24 11:45:40 -04:00
|
|
|
const _varDec = getNodeFromPath<VariableDeclarator>(
|
2024-06-21 13:20:42 +10:00
|
|
|
ast,
|
2024-11-21 15:04:30 +11:00
|
|
|
selection.codeRef.pathToNode,
|
2024-06-21 13:20:42 +10:00
|
|
|
'VariableDeclarator'
|
2024-06-24 11:45:40 -04:00
|
|
|
)
|
|
|
|
if (err(_varDec)) return false
|
|
|
|
const varDec = _varDec.node
|
2024-07-29 13:09:12 -04:00
|
|
|
if (varDec.type !== 'VariableDeclarator') return false
|
2024-06-21 13:20:42 +10:00
|
|
|
let extruded = false
|
2024-11-19 21:30:26 +01:00
|
|
|
// option 1: extrude or revolve is called in the sketch pipe
|
|
|
|
traverse(pipeExpression, {
|
2024-06-21 13:20:42 +10:00
|
|
|
enter(node) {
|
|
|
|
if (
|
|
|
|
node.type === 'CallExpression' &&
|
2024-11-19 21:30:26 +01:00
|
|
|
(node.callee.name === 'extrude' || node.callee.name === 'revolve')
|
2024-06-21 13:20:42 +10:00
|
|
|
) {
|
|
|
|
extruded = true
|
|
|
|
}
|
|
|
|
},
|
|
|
|
})
|
2024-11-19 21:30:26 +01:00
|
|
|
// option 2: extrude or revolve is called in the separate pipe
|
|
|
|
if (!extruded) {
|
|
|
|
traverse(ast as any, {
|
|
|
|
enter(node) {
|
|
|
|
if (
|
|
|
|
node.type === 'CallExpression' &&
|
|
|
|
node.callee.type === 'Identifier' &&
|
2024-12-04 17:24:16 -05:00
|
|
|
(node.callee.name === 'extrude' ||
|
|
|
|
node.callee.name === 'revolve' ||
|
|
|
|
node.callee.name === 'loft') &&
|
2024-11-19 21:30:26 +01:00
|
|
|
node.arguments?.[1]?.type === 'Identifier' &&
|
|
|
|
node.arguments[1].name === varDec.id.name
|
|
|
|
) {
|
|
|
|
extruded = true
|
|
|
|
}
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
2024-06-21 13:20:42 +10:00
|
|
|
return extruded
|
|
|
|
}
|
|
|
|
|
|
|
|
/** File must contain at least one sketch that has not been extruded already */
|
2024-12-04 17:24:16 -05:00
|
|
|
export function doesSceneHaveSweepableSketch(ast: Node<Program>, count = 1) {
|
2024-06-21 13:20:42 +10:00
|
|
|
const theMap: any = {}
|
|
|
|
traverse(ast as any, {
|
|
|
|
enter(node) {
|
|
|
|
if (
|
|
|
|
node.type === 'VariableDeclarator' &&
|
|
|
|
node.init?.type === 'PipeExpression'
|
|
|
|
) {
|
|
|
|
let hasStartProfileAt = false
|
|
|
|
let hasStartSketchOn = false
|
|
|
|
let hasClose = false
|
2024-09-23 22:42:51 +10:00
|
|
|
let hasCircle = false
|
2024-06-21 13:20:42 +10:00
|
|
|
for (const pipe of node.init.body) {
|
|
|
|
if (
|
|
|
|
pipe.type === 'CallExpression' &&
|
|
|
|
pipe.callee.name === 'startProfileAt'
|
|
|
|
) {
|
|
|
|
hasStartProfileAt = true
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
pipe.type === 'CallExpression' &&
|
|
|
|
pipe.callee.name === 'startSketchOn'
|
|
|
|
) {
|
|
|
|
hasStartSketchOn = true
|
|
|
|
}
|
|
|
|
if (pipe.type === 'CallExpression' && pipe.callee.name === 'close') {
|
|
|
|
hasClose = true
|
|
|
|
}
|
2024-09-23 22:42:51 +10:00
|
|
|
if (pipe.type === 'CallExpression' && pipe.callee.name === 'circle') {
|
|
|
|
hasCircle = true
|
|
|
|
}
|
2024-06-21 13:20:42 +10:00
|
|
|
}
|
2024-09-23 22:42:51 +10:00
|
|
|
if (
|
|
|
|
(hasStartProfileAt || hasCircle) &&
|
|
|
|
hasStartSketchOn &&
|
|
|
|
(hasClose || hasCircle)
|
|
|
|
) {
|
2024-06-21 13:20:42 +10:00
|
|
|
theMap[node.id.name] = true
|
|
|
|
}
|
|
|
|
} else if (
|
|
|
|
node.type === 'CallExpression' &&
|
2024-09-17 08:29:52 -05:00
|
|
|
(node.callee.name === 'extrude' || node.callee.name === 'revolve') &&
|
2024-06-21 13:20:42 +10:00
|
|
|
node.arguments[1]?.type === 'Identifier' &&
|
|
|
|
theMap?.[node?.arguments?.[1]?.name]
|
|
|
|
) {
|
|
|
|
delete theMap[node.arguments[1].name]
|
|
|
|
}
|
|
|
|
},
|
|
|
|
})
|
2024-12-04 17:24:16 -05:00
|
|
|
return Object.keys(theMap).length >= count
|
2024-06-21 13:20:42 +10:00
|
|
|
}
|
2024-09-13 21:14:14 +10:00
|
|
|
|
|
|
|
export function getObjExprProperty(
|
|
|
|
node: ObjectExpression,
|
|
|
|
propName: string
|
2024-09-26 18:25:05 +10:00
|
|
|
): { expr: ObjectProperty['value']; index: number } | null {
|
2024-09-13 21:14:14 +10:00
|
|
|
const index = node.properties.findIndex(({ key }) => key.name === propName)
|
|
|
|
if (index === -1) return null
|
|
|
|
return { expr: node.properties[index].value, index }
|
|
|
|
}
|