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,
|
|
|
|
ExpressionStatement,
|
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,
|
2023-09-29 11:11:01 -07:00
|
|
|
SketchGroup,
|
|
|
|
SourceRange,
|
2024-05-21 16:44:08 +10:00
|
|
|
SyntaxType,
|
|
|
|
Value,
|
|
|
|
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-06-24 11:45:40 -04:00
|
|
|
import { err } from 'lib/trap'
|
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(
|
|
|
|
node: Value | ExpressionStatement | VariableDeclaration | ReturnStatement,
|
2023-04-03 16:05:25 +10:00
|
|
|
sourceRange: Selection['range'],
|
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
|
|
|
|
}
|
|
|
|
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) {
|
|
|
|
const declarations = _node.declarations
|
|
|
|
|
|
|
|
for (let decIndex = 0; decIndex < declarations.length; decIndex++) {
|
|
|
|
const declaration = declarations[decIndex]
|
|
|
|
if (declaration.start <= start && declaration.end >= end) {
|
|
|
|
path.push(['declarations', 'VariableDeclaration'])
|
|
|
|
path.push([decIndex, 'index'])
|
|
|
|
const init = declaration.init
|
|
|
|
if (init.start <= start && init.end >= end) {
|
|
|
|
path.push(['init', ''])
|
|
|
|
return moreNodePathFromSourceRange(init, sourceRange, path)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (_node.type === 'VariableDeclaration' && isInRange) {
|
|
|
|
const declarations = _node.declarations
|
|
|
|
|
|
|
|
for (let decIndex = 0; decIndex < declarations.length; decIndex++) {
|
|
|
|
const declaration = declarations[decIndex]
|
|
|
|
if (declaration.start <= start && declaration.end >= end) {
|
|
|
|
const init = declaration.init
|
|
|
|
if (init.start <= start && init.end >= end) {
|
|
|
|
path.push(['declarations', 'VariableDeclaration'])
|
|
|
|
path.push([decIndex, 'index'])
|
|
|
|
path.push(['init', ''])
|
|
|
|
return moreNodePathFromSourceRange(init, sourceRange, path)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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
|
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,
|
2023-04-03 16:05:25 +10:00
|
|
|
sourceRange: Selection['range'],
|
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-05-21 16:44:08 +10:00
|
|
|
type KCLNode =
|
|
|
|
| Value
|
|
|
|
| ExpressionStatement
|
|
|
|
| VariableDeclaration
|
|
|
|
| VariableDeclarator
|
|
|
|
| ReturnStatement
|
|
|
|
|
|
|
|
export function traverse(
|
2024-06-25 12:46:40 +10:00
|
|
|
node: KCLNode | 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') {
|
|
|
|
_node.declarations.forEach((declaration, index) =>
|
|
|
|
_traverse(declaration, [
|
|
|
|
...pathToNode,
|
|
|
|
['declarations', 'VariableDeclaration'],
|
|
|
|
[index, 'index'],
|
|
|
|
])
|
|
|
|
)
|
|
|
|
} 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
|
2023-03-10 08:35:30 +11:00
|
|
|
const varName = item.declarations[0].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,
|
|
|
|
sourceRange: Selection['range'],
|
|
|
|
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 = (
|
|
|
|
_ast: Program,
|
|
|
|
varName: string
|
2024-06-24 11:45:40 -04:00
|
|
|
) => { modifiedAst: 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
|
|
|
|
value: Value
|
|
|
|
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] =
|
|
|
|
(binValue as Value)?.type === 'BinaryExpression'
|
|
|
|
? [binValue, outBinPath]
|
|
|
|
: [value, outPath]
|
|
|
|
|
|
|
|
const replaceNodeWithIdentifier: ReplacerFn = (_ast, varName) => {
|
|
|
|
const identifier = createIdentifier(varName)
|
|
|
|
const last = finPath[finPath.length - 1]
|
2024-05-24 20:54:42 +10:00
|
|
|
const pathToReplaced = JSON.parse(JSON.stringify(finPath))
|
|
|
|
pathToReplaced[1][0] = pathToReplaced[1][0] + 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
|
|
|
}
|
|
|
|
|
|
|
|
const hasPipeSub = isTypeInValue(finVal as Value, 'PipeSubstitution')
|
|
|
|
const isIdentifierCallee = path[path.length - 1][0] !== 'callee'
|
|
|
|
return {
|
|
|
|
isSafe:
|
|
|
|
!hasPipeSub &&
|
|
|
|
isIdentifierCallee &&
|
|
|
|
acceptedNodeTypes.includes((finVal as any)?.type) &&
|
|
|
|
finPath.map(([_, type]) => type).includes('VariableDeclaration'),
|
|
|
|
value: finVal as Value,
|
|
|
|
replacer: replaceNodeWithIdentifier,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-24 20:54:42 +10:00
|
|
|
export function isNodeSafeToReplace(
|
|
|
|
ast: Program,
|
|
|
|
sourceRange: [number, number]
|
2024-06-24 11:45:40 -04:00
|
|
|
):
|
|
|
|
| {
|
|
|
|
isSafe: boolean
|
|
|
|
value: Value
|
|
|
|
replacer: ReplacerFn
|
|
|
|
}
|
|
|
|
| Error {
|
2024-05-24 20:54:42 +10:00
|
|
|
let path = getNodePathFromSourceRange(ast, sourceRange)
|
|
|
|
return isNodeSafeToReplacePath(ast, path)
|
|
|
|
}
|
|
|
|
|
2023-04-01 16:47:00 +11:00
|
|
|
export function isTypeInValue(node: Value, syntaxType: SyntaxType): boolean {
|
|
|
|
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
|
|
|
|
|
|
|
export function isValueZero(val?: Value): boolean {
|
|
|
|
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,
|
|
|
|
programMemory: ProgramMemory,
|
|
|
|
primaryLine: Selection,
|
|
|
|
secondaryLine: Selection
|
2024-06-24 11:45:40 -04:00
|
|
|
):
|
|
|
|
| {
|
|
|
|
isParallelAndConstrained: boolean
|
|
|
|
sourceRange: SourceRange
|
|
|
|
}
|
|
|
|
| Error {
|
2023-04-08 14:16:49 +10:00
|
|
|
try {
|
|
|
|
const EPSILON = 0.005
|
|
|
|
const primaryPath = getNodePathFromSourceRange(ast, primaryLine.range)
|
|
|
|
const secondaryPath = getNodePathFromSourceRange(ast, secondaryLine.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
|
2023-04-08 14:16:49 +10:00
|
|
|
const varName = (varDec as VariableDeclaration)?.declarations[0]?.id?.name
|
2024-07-22 19:43:40 -04:00
|
|
|
const path = programMemory?.get(varName) as SketchGroup
|
2024-06-24 11:45:40 -04:00
|
|
|
const _primarySegment = getSketchSegmentFromSourceRange(
|
2023-04-08 14:16:49 +10:00
|
|
|
path,
|
|
|
|
primaryLine.range
|
2024-06-24 11:45:40 -04:00
|
|
|
)
|
|
|
|
if (err(_primarySegment)) return _primarySegment
|
|
|
|
const primarySegment = _primarySegment.segment
|
|
|
|
|
|
|
|
const _segment = getSketchSegmentFromSourceRange(path, secondaryLine.range)
|
|
|
|
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
|
|
|
|
|
|
|
|
// is secordary line fully constrain, or has constrain type of 'angle'
|
|
|
|
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(
|
2023-04-08 14:16:49 +10:00
|
|
|
secondaryLine.range,
|
|
|
|
ast
|
2024-06-24 11:45:40 -04:00
|
|
|
)
|
|
|
|
if (err(constraintLevelMeta)) {
|
|
|
|
console.error(constraintLevelMeta)
|
|
|
|
return {
|
|
|
|
isParallelAndConstrained: false,
|
|
|
|
sourceRange: [0, 0],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const constraintLevel = constraintLevelMeta.level
|
|
|
|
|
2023-04-08 14:16:49 +10:00
|
|
|
const isConstrained =
|
|
|
|
constraintType === 'angle' || constraintLevel === 'full'
|
|
|
|
|
|
|
|
// get the previous segment
|
2024-07-22 19:43:40 -04:00
|
|
|
const prevSegment = (programMemory.get(varName) as SketchGroup).value[
|
2023-04-08 14:16:49 +10:00
|
|
|
secondaryIndex - 1
|
|
|
|
]
|
|
|
|
const prevSourceRange = prevSegment.__geoMeta.sourceRange
|
|
|
|
|
|
|
|
const isParallelAndConstrained =
|
|
|
|
isParallel && isConstrained && !!prevSourceRange
|
|
|
|
|
|
|
|
return {
|
|
|
|
isParallelAndConstrained,
|
|
|
|
sourceRange: prevSourceRange,
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
return {
|
|
|
|
isParallelAndConstrained: false,
|
|
|
|
sourceRange: [0, 0],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-10-11 13:36:54 +11:00
|
|
|
|
|
|
|
export function doesPipeHaveCallExp({
|
|
|
|
ast,
|
|
|
|
selection,
|
|
|
|
calleeName,
|
|
|
|
}: {
|
|
|
|
calleeName: string
|
|
|
|
ast: Program
|
|
|
|
selection: Selection
|
|
|
|
}): boolean {
|
|
|
|
const pathToNode = getNodePathFromSourceRange(ast, selection.range)
|
2024-06-24 11:45:40 -04:00
|
|
|
const pipeExpressionMeta = getNodeFromPath<PipeExpression>(
|
2023-10-11 13:36:54 +11:00
|
|
|
ast,
|
|
|
|
pathToNode,
|
|
|
|
'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
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
export function hasExtrudeSketchGroup({
|
|
|
|
ast,
|
|
|
|
selection,
|
|
|
|
programMemory,
|
|
|
|
}: {
|
|
|
|
ast: Program
|
|
|
|
selection: Selection
|
|
|
|
programMemory: ProgramMemory
|
|
|
|
}): boolean {
|
|
|
|
const pathToNode = getNodePathFromSourceRange(ast, selection.range)
|
2024-06-24 11:45:40 -04:00
|
|
|
const varDecMeta = getNodeFromPath<VariableDeclaration>(
|
2023-10-11 13:36:54 +11:00
|
|
|
ast,
|
|
|
|
pathToNode,
|
|
|
|
'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
|
|
|
|
const varName = varDec.declarations[0].id.name
|
2024-07-22 19:43:40 -04:00
|
|
|
const varValue = programMemory?.get(varName)
|
2023-10-11 13:36:54 +11:00
|
|
|
return varValue?.type === 'ExtrudeGroup' || varValue?.type === 'SketchGroup'
|
|
|
|
}
|
2024-02-19 17:23:03 +11:00
|
|
|
|
|
|
|
export function isSingleCursorInPipe(
|
|
|
|
selectionRanges: Selections,
|
|
|
|
ast: Program
|
|
|
|
) {
|
|
|
|
if (selectionRanges.codeBasedSelections.length !== 1) return false
|
|
|
|
const selection = selectionRanges.codeBasedSelections[0]
|
|
|
|
const pathToNode = getNodePathFromSourceRange(ast, selection.range)
|
|
|
|
const nodeTypes = pathToNode.map(([, type]) => type)
|
|
|
|
if (nodeTypes.includes('FunctionExpression')) return false
|
|
|
|
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
|
|
|
|
|
|
|
const varDec = getNodeFromPath<VariableDeclaration>(
|
|
|
|
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
|
|
|
|
if (tagArgValue === tag) dependentRanges.push([node.start, node.end])
|
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) {
|
|
|
|
const path = getNodePathFromSourceRange(ast, selection.range)
|
2024-06-24 11:45:40 -04:00
|
|
|
const _node = getNodeFromPath<PipeExpression>(ast, path, 'PipeExpression')
|
|
|
|
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,
|
|
|
|
path,
|
|
|
|
'VariableDeclarator'
|
2024-06-24 11:45:40 -04:00
|
|
|
)
|
|
|
|
if (err(_varDec)) return false
|
|
|
|
const varDec = _varDec.node
|
2024-06-21 13:20:42 +10:00
|
|
|
let extruded = false
|
|
|
|
traverse(ast as any, {
|
|
|
|
enter(node) {
|
|
|
|
if (
|
|
|
|
node.type === 'CallExpression' &&
|
|
|
|
node.callee.type === 'Identifier' &&
|
|
|
|
node.callee.name === 'extrude' &&
|
|
|
|
node.arguments?.[1]?.type === 'Identifier' &&
|
|
|
|
node.arguments[1].name === varDec.id.name
|
|
|
|
) {
|
|
|
|
extruded = true
|
|
|
|
}
|
|
|
|
},
|
|
|
|
})
|
|
|
|
return extruded
|
|
|
|
}
|
|
|
|
|
|
|
|
/** File must contain at least one sketch that has not been extruded already */
|
|
|
|
export function hasExtrudableGeometry(ast: Program) {
|
|
|
|
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
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (hasStartProfileAt && hasStartSketchOn && hasClose) {
|
|
|
|
theMap[node.id.name] = true
|
|
|
|
}
|
|
|
|
} else if (
|
|
|
|
node.type === 'CallExpression' &&
|
|
|
|
node.callee.name === 'extrude' &&
|
|
|
|
node.arguments[1]?.type === 'Identifier' &&
|
|
|
|
theMap?.[node?.arguments?.[1]?.name]
|
|
|
|
) {
|
|
|
|
delete theMap[node.arguments[1].name]
|
|
|
|
}
|
|
|
|
},
|
|
|
|
})
|
|
|
|
return Object.keys(theMap).length > 0
|
|
|
|
}
|