2023-10-16 21:20:05 +11:00
|
|
|
import { Selection } from 'lib/selections'
|
2023-01-06 09:29:26 +11:00
|
|
|
import {
|
|
|
|
Program,
|
|
|
|
CallExpression,
|
|
|
|
PipeExpression,
|
|
|
|
VariableDeclaration,
|
2023-02-12 10:56:45 +11:00
|
|
|
VariableDeclarator,
|
2023-01-06 09:29:26 +11:00
|
|
|
Value,
|
2023-02-12 10:56:45 +11:00
|
|
|
Literal,
|
|
|
|
PipeSubstitution,
|
|
|
|
Identifier,
|
|
|
|
ArrayExpression,
|
|
|
|
ObjectExpression,
|
2023-03-02 21:19:11 +11:00
|
|
|
UnaryExpression,
|
|
|
|
BinaryExpression,
|
2023-09-29 11:11:01 -07:00
|
|
|
PathToNode,
|
|
|
|
ProgramMemory,
|
|
|
|
} from './wasm'
|
2023-04-01 16:47:00 +11:00
|
|
|
import {
|
|
|
|
findAllPreviousVariables,
|
|
|
|
getNodeFromPath,
|
|
|
|
getNodePathFromSourceRange,
|
|
|
|
isNodeSafeToReplace,
|
|
|
|
} from './queryAst'
|
2024-03-15 17:03:42 -04:00
|
|
|
import { addTagForSketchOnFace } from './std/sketch'
|
2023-09-13 07:23:14 -07:00
|
|
|
import { isLiteralArrayOrStatic } from './std/sketchcombos'
|
2024-02-14 08:03:20 +11:00
|
|
|
import { DefaultPlaneStr } from 'clientSideScene/sceneEntities'
|
2024-02-11 12:59:00 +11:00
|
|
|
import { roundOff } from 'lib/utils'
|
2023-01-06 09:29:26 +11:00
|
|
|
|
2024-02-11 12:59:00 +11:00
|
|
|
export function startSketchOnDefault(
|
2023-09-13 08:36:47 +10:00
|
|
|
node: Program,
|
2024-02-11 12:59:00 +11:00
|
|
|
axis: DefaultPlaneStr,
|
|
|
|
name = ''
|
2023-09-13 08:36:47 +10:00
|
|
|
): { modifiedAst: Program; id: string; pathToNode: PathToNode } {
|
|
|
|
const _node = { ...node }
|
2024-02-11 12:59:00 +11:00
|
|
|
const _name = name || findUniqueName(node, 'part')
|
2023-09-13 08:36:47 +10:00
|
|
|
|
2023-10-05 14:27:48 -07:00
|
|
|
const startSketchOn = createCallExpressionStdLib('startSketchOn', [
|
2024-02-11 12:59:00 +11:00
|
|
|
createLiteral(axis),
|
2023-09-13 08:36:47 +10:00
|
|
|
])
|
|
|
|
|
2024-02-11 12:59:00 +11:00
|
|
|
const variableDeclaration = createVariableDeclaration(_name, startSketchOn)
|
2023-09-13 08:36:47 +10:00
|
|
|
_node.body = [...node.body, variableDeclaration]
|
2024-02-11 12:59:00 +11:00
|
|
|
const sketchIndex = _node.body.length - 1
|
2023-09-13 08:36:47 +10:00
|
|
|
|
|
|
|
let pathToNode: PathToNode = [
|
|
|
|
['body', ''],
|
2024-02-11 12:59:00 +11:00
|
|
|
[sketchIndex, 'index'],
|
2023-09-13 08:36:47 +10:00
|
|
|
['declarations', 'VariableDeclaration'],
|
|
|
|
['0', 'index'],
|
|
|
|
['init', 'VariableDeclarator'],
|
|
|
|
]
|
|
|
|
|
|
|
|
return {
|
|
|
|
modifiedAst: _node,
|
|
|
|
id: _name,
|
|
|
|
pathToNode,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-11 12:59:00 +11:00
|
|
|
export function addStartProfileAt(
|
|
|
|
node: Program,
|
|
|
|
pathToNode: PathToNode,
|
|
|
|
at: [number, number]
|
|
|
|
): { modifiedAst: Program; pathToNode: PathToNode } {
|
|
|
|
const variableDeclaration = getNodeFromPath<VariableDeclaration>(
|
|
|
|
node,
|
|
|
|
pathToNode,
|
|
|
|
'VariableDeclaration'
|
|
|
|
).node
|
|
|
|
if (variableDeclaration.type !== 'VariableDeclaration') {
|
|
|
|
throw new Error('variableDeclaration.init.type !== PipeExpression')
|
|
|
|
}
|
|
|
|
const _node = { ...node }
|
|
|
|
const init = variableDeclaration.declarations[0].init
|
|
|
|
const startProfileAt = createCallExpressionStdLib('startProfileAt', [
|
|
|
|
createArrayExpression([
|
|
|
|
createLiteral(roundOff(at[0])),
|
|
|
|
createLiteral(roundOff(at[1])),
|
|
|
|
]),
|
|
|
|
createPipeSubstitution(),
|
|
|
|
])
|
|
|
|
if (init.type === 'PipeExpression') {
|
|
|
|
init.body.splice(1, 0, startProfileAt)
|
|
|
|
} else {
|
|
|
|
variableDeclaration.declarations[0].init = createPipeExpression([
|
|
|
|
init,
|
|
|
|
startProfileAt,
|
|
|
|
])
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
modifiedAst: _node,
|
|
|
|
pathToNode,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-06 09:29:26 +11:00
|
|
|
export function addSketchTo(
|
|
|
|
node: Program,
|
|
|
|
axis: 'xy' | 'xz' | 'yz',
|
|
|
|
name = ''
|
2023-04-01 16:47:00 +11:00
|
|
|
): { modifiedAst: Program; id: string; pathToNode: PathToNode } {
|
2023-01-06 09:29:26 +11:00
|
|
|
const _node = { ...node }
|
|
|
|
const _name = name || findUniqueName(node, 'part')
|
|
|
|
|
2023-10-05 14:27:48 -07:00
|
|
|
const startSketchOn = createCallExpressionStdLib('startSketchOn', [
|
|
|
|
createLiteral(axis.toUpperCase()),
|
2023-02-12 10:56:45 +11:00
|
|
|
])
|
2023-10-05 14:27:48 -07:00
|
|
|
const startProfileAt = createCallExpressionStdLib('startProfileAt', [
|
|
|
|
createLiteral('default'),
|
2023-02-12 10:56:45 +11:00
|
|
|
createPipeSubstitution(),
|
|
|
|
])
|
2023-09-05 16:02:27 -07:00
|
|
|
const initialLineTo = createCallExpressionStdLib('line', [
|
2023-03-17 15:53:20 +11:00
|
|
|
createLiteral('default'),
|
2023-02-12 10:56:45 +11:00
|
|
|
createPipeSubstitution(),
|
|
|
|
])
|
2023-01-06 09:29:26 +11:00
|
|
|
|
2023-10-05 14:27:48 -07:00
|
|
|
const pipeBody = [startSketchOn, startProfileAt, initialLineTo]
|
2023-02-12 10:56:45 +11:00
|
|
|
|
|
|
|
const variableDeclaration = createVariableDeclaration(
|
|
|
|
_name,
|
|
|
|
createPipeExpression(pipeBody)
|
|
|
|
)
|
2023-01-06 09:29:26 +11:00
|
|
|
|
2024-03-01 17:16:18 -08:00
|
|
|
_node.body = [...node.body, variableDeclaration]
|
|
|
|
let sketchIndex = _node.body.length - 1
|
2023-04-01 16:47:00 +11:00
|
|
|
let pathToNode: PathToNode = [
|
|
|
|
['body', ''],
|
|
|
|
[sketchIndex, 'index'],
|
|
|
|
['declarations', 'VariableDeclaration'],
|
|
|
|
['0', 'index'],
|
|
|
|
['init', 'VariableDeclarator'],
|
2023-01-06 09:29:26 +11:00
|
|
|
]
|
|
|
|
if (axis !== 'xy') {
|
2023-04-01 16:47:00 +11:00
|
|
|
pathToNode = [...pathToNode, ['body', ''], ['0', 'index']]
|
2023-01-06 09:29:26 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
2024-03-01 17:16:18 -08:00
|
|
|
modifiedAst: _node,
|
2023-01-06 09:29:26 +11:00
|
|
|
id: _name,
|
|
|
|
pathToNode,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-12 10:56:45 +11:00
|
|
|
export function findUniqueName(
|
2023-01-06 09:29:26 +11:00
|
|
|
ast: Program | string,
|
|
|
|
name: string,
|
2023-01-09 13:19:14 +11:00
|
|
|
pad = 3,
|
2023-01-06 09:29:26 +11:00
|
|
|
index = 1
|
|
|
|
): string {
|
2024-02-23 11:24:22 -05:00
|
|
|
let searchStr: string = typeof ast === 'string' ? ast : JSON.stringify(ast)
|
|
|
|
const indexStr = String(index).padStart(pad, '0')
|
|
|
|
|
|
|
|
const endingDigitsMatcher = /\d+$/
|
|
|
|
const nameEndsInDigits = name.match(endingDigitsMatcher)
|
|
|
|
let nameIsInString = searchStr.includes(`:"${name}"`)
|
|
|
|
|
|
|
|
if (nameEndsInDigits !== null) {
|
|
|
|
// base case: name is unique and ends in digits
|
|
|
|
if (!nameIsInString) return name
|
|
|
|
|
|
|
|
// recursive case: name is not unique and ends in digits
|
|
|
|
const newPad = nameEndsInDigits[1].length
|
|
|
|
const newIndex = parseInt(nameEndsInDigits[1]) + 1
|
|
|
|
const nameWithoutDigits = name.replace(endingDigitsMatcher, '')
|
|
|
|
|
|
|
|
return findUniqueName(searchStr, nameWithoutDigits, newPad, newIndex)
|
2023-01-06 09:29:26 +11:00
|
|
|
}
|
2024-02-23 11:24:22 -05:00
|
|
|
|
2023-01-06 09:29:26 +11:00
|
|
|
const newName = `${name}${indexStr}`
|
2024-02-23 11:24:22 -05:00
|
|
|
nameIsInString = searchStr.includes(`:"${newName}"`)
|
|
|
|
|
|
|
|
// base case: name is unique and does not end in digits
|
|
|
|
if (!nameIsInString) return newName
|
|
|
|
|
|
|
|
// recursive case: name is not unique and does not end in digits
|
2023-01-09 13:19:14 +11:00
|
|
|
return findUniqueName(searchStr, name, pad, index + 1)
|
2023-01-06 09:29:26 +11:00
|
|
|
}
|
|
|
|
|
2023-02-12 10:56:45 +11:00
|
|
|
export function mutateArrExp(
|
|
|
|
node: Value,
|
|
|
|
updateWith: ArrayExpression
|
|
|
|
): boolean {
|
|
|
|
if (node.type === 'ArrayExpression') {
|
|
|
|
node.elements.forEach((element, i) => {
|
2023-09-13 07:23:14 -07:00
|
|
|
if (isLiteralArrayOrStatic(element)) {
|
2023-02-12 10:56:45 +11:00
|
|
|
node.elements[i] = updateWith.elements[i]
|
|
|
|
}
|
|
|
|
})
|
|
|
|
return true
|
2023-01-06 09:29:26 +11:00
|
|
|
}
|
2023-02-12 10:56:45 +11:00
|
|
|
return false
|
2023-01-06 09:29:26 +11:00
|
|
|
}
|
|
|
|
|
2023-02-12 10:56:45 +11:00
|
|
|
export function mutateObjExpProp(
|
|
|
|
node: Value,
|
|
|
|
updateWith: Literal | ArrayExpression,
|
|
|
|
key: string
|
|
|
|
): boolean {
|
|
|
|
if (node.type === 'ObjectExpression') {
|
|
|
|
const keyIndex = node.properties.findIndex((a) => a.key.name === key)
|
|
|
|
if (keyIndex !== -1) {
|
|
|
|
if (
|
2023-09-13 07:23:14 -07:00
|
|
|
isLiteralArrayOrStatic(updateWith) &&
|
|
|
|
isLiteralArrayOrStatic(node.properties[keyIndex].value)
|
2023-02-12 10:56:45 +11:00
|
|
|
) {
|
|
|
|
node.properties[keyIndex].value = updateWith
|
|
|
|
return true
|
|
|
|
} else if (
|
|
|
|
node.properties[keyIndex].value.type === 'ArrayExpression' &&
|
|
|
|
updateWith.type === 'ArrayExpression'
|
|
|
|
) {
|
|
|
|
const arrExp = node.properties[keyIndex].value as ArrayExpression
|
|
|
|
arrExp.elements.forEach((element, i) => {
|
2023-09-13 07:23:14 -07:00
|
|
|
if (isLiteralArrayOrStatic(element)) {
|
2023-02-12 10:56:45 +11:00
|
|
|
arrExp.elements[i] = updateWith.elements[i]
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
} else {
|
|
|
|
node.properties.push({
|
|
|
|
type: 'ObjectProperty',
|
|
|
|
key: createIdentifier(key),
|
|
|
|
value: updateWith,
|
|
|
|
start: 0,
|
|
|
|
end: 0,
|
|
|
|
})
|
|
|
|
}
|
2023-01-06 09:29:26 +11:00
|
|
|
}
|
2023-02-12 10:56:45 +11:00
|
|
|
return false
|
2023-01-06 09:29:26 +11:00
|
|
|
}
|
2023-01-06 12:45:34 +11:00
|
|
|
|
|
|
|
export function extrudeSketch(
|
|
|
|
node: Program,
|
2023-04-01 16:47:00 +11:00
|
|
|
pathToNode: PathToNode,
|
Command bar: add extrude command, nonlinear editing, etc (#1204)
* Tweak toaster look and feel
* Add icons, tweak plus icon names
* Rename commandBarMeta to commandBarConfig
* Refactor command bar, add support for icons
* Create a tailwind plugin for aria-pressed button state
* Remove overlay from behind command bar
* Clean up toolbar
* Button and other style tweaks
* Icon tweaks follow-up: make old icons work with new sizing
* Delete unused static icons
* More CSS tweaks
* Small CSS tweak to project sidebar
* Add command bar E2E test
* fumpt
* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)
* fix typo in a comment
* Fix icon padding (built version only)
* Update onboarding and warning banner icons padding
* Misc minor style fixes
* Get Extrude opening and canceling from command bar
* Iconography tweaks
* Get extrude kind of working
* Refactor command bar config types and organization
* Move command bar configs to be co-located with each other
* Start building a state machine for the command bar
* Start converting command bar to state machine
* Add support for multiple args, confirmation step
* Submission behavior, hotkeys, code organization
* Add new test for extruding from command bar
* Polish step back and selection hotkeys, CSS tweaks
* Loading style tweaks
* Validate selection inputs, polish UX of args re-editing
* Prevent submission with multiple selection on singlular arg
* Remove stray console logs
* Tweak test, CSS nit, remove extrude "result" argument
* Fix linting warnings
* Show Ctrl+/ instead of ⌘K on all platforms but Mac
* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)
* Add "Enter sketch" to command bar
* fix command bar test
* Fix flaky cmd bar extrude test by waiting for engine select response
* Cover both button labels '⌘K' and 'Ctrl+/' in test
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-12-13 12:49:01 -05:00
|
|
|
shouldPipe = true,
|
2024-02-23 11:24:22 -05:00
|
|
|
distance = createLiteral(4) as Value
|
2023-01-13 17:58:37 +11:00
|
|
|
): {
|
|
|
|
modifiedAst: Program
|
|
|
|
pathToNode: PathToNode
|
|
|
|
pathToExtrudeArg: PathToNode
|
|
|
|
} {
|
2023-01-06 12:45:34 +11:00
|
|
|
const _node = { ...node }
|
2023-02-12 10:56:45 +11:00
|
|
|
const { node: sketchExpression } = getNodeFromPath(
|
2023-01-06 12:45:34 +11:00
|
|
|
_node,
|
|
|
|
pathToNode,
|
2023-02-12 10:56:45 +11:00
|
|
|
'SketchExpression' // TODO fix this #25
|
2023-01-13 17:58:37 +11:00
|
|
|
)
|
2023-01-06 12:45:34 +11:00
|
|
|
|
|
|
|
// determine if sketchExpression is in a pipeExpression or not
|
2023-01-13 17:58:37 +11:00
|
|
|
const { node: pipeExpression } = getNodeFromPath<PipeExpression>(
|
2023-01-06 12:45:34 +11:00
|
|
|
_node,
|
|
|
|
pathToNode,
|
2023-01-13 17:58:37 +11:00
|
|
|
'PipeExpression'
|
|
|
|
)
|
|
|
|
const isInPipeExpression = pipeExpression.type === 'PipeExpression'
|
|
|
|
|
2024-04-11 10:35:23 +10:00
|
|
|
const { node: variableDeclarator, shallowPath: pathToDecleration } =
|
2023-01-13 17:58:37 +11:00
|
|
|
getNodeFromPath<VariableDeclarator>(_node, pathToNode, 'VariableDeclarator')
|
2023-01-06 12:45:34 +11:00
|
|
|
|
2023-09-05 16:02:27 -07:00
|
|
|
const extrudeCall = createCallExpressionStdLib('extrude', [
|
2024-02-23 11:24:22 -05:00
|
|
|
distance,
|
2023-03-02 21:19:11 +11:00
|
|
|
shouldPipe
|
|
|
|
? createPipeSubstitution()
|
2024-04-11 10:35:23 +10:00
|
|
|
: createIdentifier(variableDeclarator.id.name),
|
2023-03-02 21:19:11 +11:00
|
|
|
])
|
|
|
|
|
|
|
|
if (shouldPipe) {
|
|
|
|
const pipeChain = createPipeExpression(
|
|
|
|
isInPipeExpression
|
|
|
|
? [...pipeExpression.body, extrudeCall]
|
|
|
|
: [sketchExpression as any, extrudeCall]
|
|
|
|
)
|
2023-01-06 12:45:34 +11:00
|
|
|
|
2024-04-11 10:35:23 +10:00
|
|
|
variableDeclarator.init = pipeChain
|
2023-04-01 16:47:00 +11:00
|
|
|
const pathToExtrudeArg: PathToNode = [
|
2023-01-13 17:58:37 +11:00
|
|
|
...pathToDecleration,
|
2023-04-01 16:47:00 +11:00
|
|
|
['init', 'VariableDeclarator'],
|
|
|
|
['body', ''],
|
|
|
|
[pipeChain.body.length - 1, 'index'],
|
|
|
|
['arguments', 'CallExpression'],
|
|
|
|
[0, 'index'],
|
2023-01-13 17:58:37 +11:00
|
|
|
]
|
2023-01-06 12:45:34 +11:00
|
|
|
|
|
|
|
return {
|
|
|
|
modifiedAst: _node,
|
|
|
|
pathToNode,
|
2023-01-13 17:58:37 +11:00
|
|
|
pathToExtrudeArg,
|
2023-01-06 12:45:34 +11:00
|
|
|
}
|
|
|
|
}
|
|
|
|
const name = findUniqueName(node, 'part')
|
2023-03-02 21:19:11 +11:00
|
|
|
const VariableDeclaration = createVariableDeclaration(name, extrudeCall)
|
2024-03-01 17:16:18 -08:00
|
|
|
_node.body.splice(_node.body.length, 0, VariableDeclaration)
|
2023-04-01 16:47:00 +11:00
|
|
|
const pathToExtrudeArg: PathToNode = [
|
|
|
|
['body', ''],
|
2024-03-01 17:16:18 -08:00
|
|
|
[_node.body.length, 'index'],
|
2023-04-01 16:47:00 +11:00
|
|
|
['declarations', 'VariableDeclaration'],
|
|
|
|
[0, 'index'],
|
|
|
|
['init', 'VariableDeclarator'],
|
|
|
|
['arguments', 'CallExpression'],
|
|
|
|
[0, 'index'],
|
2023-01-13 17:58:37 +11:00
|
|
|
]
|
2023-01-06 12:45:34 +11:00
|
|
|
return {
|
2023-09-21 14:32:47 +10:00
|
|
|
modifiedAst: node,
|
2024-03-01 17:16:18 -08:00
|
|
|
pathToNode: [...pathToNode.slice(0, -1), [-1, 'index']],
|
2023-01-13 17:58:37 +11:00
|
|
|
pathToExtrudeArg,
|
2023-01-06 12:45:34 +11:00
|
|
|
}
|
|
|
|
}
|
2023-01-09 13:19:14 +11:00
|
|
|
|
|
|
|
export function sketchOnExtrudedFace(
|
|
|
|
node: Program,
|
2023-04-01 16:47:00 +11:00
|
|
|
pathToNode: PathToNode,
|
2024-03-22 10:23:04 +11:00
|
|
|
programMemory: ProgramMemory,
|
|
|
|
cap: 'none' | 'start' | 'end' = 'none'
|
2023-04-01 16:47:00 +11:00
|
|
|
): { modifiedAst: Program; pathToNode: PathToNode } {
|
2023-02-12 10:56:45 +11:00
|
|
|
let _node = { ...node }
|
2023-01-09 13:19:14 +11:00
|
|
|
const newSketchName = findUniqueName(node, 'part')
|
2024-03-22 10:23:04 +11:00
|
|
|
const { node: oldSketchNode } = getNodeFromPath<VariableDeclarator>(
|
|
|
|
_node,
|
|
|
|
pathToNode,
|
|
|
|
'VariableDeclarator',
|
|
|
|
true
|
|
|
|
)
|
2023-02-12 10:56:45 +11:00
|
|
|
const oldSketchName = oldSketchNode.id.name
|
|
|
|
const { node: expression } = getNodeFromPath<CallExpression>(
|
2023-01-10 15:40:34 +11:00
|
|
|
_node,
|
|
|
|
pathToNode,
|
2023-02-12 10:56:45 +11:00
|
|
|
'CallExpression'
|
|
|
|
)
|
2023-01-09 13:19:14 +11:00
|
|
|
|
2024-03-22 10:23:04 +11:00
|
|
|
let _tag = ''
|
|
|
|
if (cap === 'none') {
|
|
|
|
const { modifiedAst, tag } = addTagForSketchOnFace(
|
|
|
|
{
|
|
|
|
previousProgramMemory: programMemory,
|
|
|
|
pathToNode,
|
|
|
|
node: _node,
|
|
|
|
},
|
|
|
|
expression.callee.name
|
|
|
|
)
|
|
|
|
_tag = tag
|
|
|
|
_node = modifiedAst
|
|
|
|
} else {
|
|
|
|
_tag = cap.toUpperCase()
|
|
|
|
}
|
2023-02-12 10:56:45 +11:00
|
|
|
|
|
|
|
const newSketch = createVariableDeclaration(
|
|
|
|
newSketchName,
|
2024-03-22 10:23:04 +11:00
|
|
|
createCallExpressionStdLib('startSketchOn', [
|
|
|
|
createIdentifier(oldSketchName),
|
|
|
|
createLiteral(_tag),
|
2023-02-12 10:56:45 +11:00
|
|
|
]),
|
|
|
|
'const'
|
|
|
|
)
|
2024-03-22 10:23:04 +11:00
|
|
|
|
|
|
|
const expressionIndex = pathToNode[1][0] as number
|
2023-02-12 10:56:45 +11:00
|
|
|
_node.body.splice(expressionIndex + 1, 0, newSketch)
|
2024-03-22 10:23:04 +11:00
|
|
|
const newpathToNode: PathToNode = [
|
|
|
|
['body', ''],
|
|
|
|
[expressionIndex + 1, 'index'],
|
|
|
|
['declarations', 'VariableDeclaration'],
|
|
|
|
[0, 'index'],
|
|
|
|
['init', 'VariableDeclarator'],
|
|
|
|
]
|
2023-02-12 10:56:45 +11:00
|
|
|
|
|
|
|
return {
|
2024-03-01 17:16:18 -08:00
|
|
|
modifiedAst: _node,
|
2024-03-22 10:23:04 +11:00
|
|
|
pathToNode: newpathToNode,
|
2023-02-12 10:56:45 +11:00
|
|
|
}
|
|
|
|
}
|
2023-01-09 13:19:14 +11:00
|
|
|
|
2023-03-10 08:35:30 +11:00
|
|
|
export const getLastIndex = (pathToNode: PathToNode): number =>
|
|
|
|
splitPathAtLastIndex(pathToNode).index
|
|
|
|
|
|
|
|
export function splitPathAtLastIndex(pathToNode: PathToNode): {
|
|
|
|
path: PathToNode
|
|
|
|
index: number
|
|
|
|
} {
|
2023-02-12 10:56:45 +11:00
|
|
|
const last = pathToNode[pathToNode.length - 1]
|
2023-04-01 16:47:00 +11:00
|
|
|
if (last && typeof last[0] === 'number') {
|
2023-03-10 08:35:30 +11:00
|
|
|
return {
|
|
|
|
path: pathToNode.slice(0, -1),
|
2023-04-01 16:47:00 +11:00
|
|
|
index: last[0],
|
2023-03-10 08:35:30 +11:00
|
|
|
}
|
2023-03-21 19:02:18 +11:00
|
|
|
} else if (pathToNode.length === 0) {
|
|
|
|
return {
|
|
|
|
path: [],
|
|
|
|
index: -1,
|
|
|
|
}
|
2023-01-09 13:19:14 +11:00
|
|
|
}
|
2023-03-10 08:35:30 +11:00
|
|
|
return splitPathAtLastIndex(pathToNode.slice(0, -1))
|
2023-02-12 10:56:45 +11:00
|
|
|
}
|
2023-01-09 13:19:14 +11:00
|
|
|
|
2023-04-01 16:47:00 +11:00
|
|
|
export function splitPathAtPipeExpression(pathToNode: PathToNode): {
|
|
|
|
path: PathToNode
|
|
|
|
index: number
|
|
|
|
} {
|
|
|
|
const last = pathToNode[pathToNode.length - 1]
|
|
|
|
|
|
|
|
if (
|
|
|
|
last &&
|
|
|
|
last[1] === 'index' &&
|
|
|
|
pathToNode?.[pathToNode.length - 2]?.[1] === 'PipeExpression' &&
|
|
|
|
typeof last[0] === 'number'
|
|
|
|
) {
|
|
|
|
return {
|
|
|
|
path: pathToNode.slice(0, -1),
|
|
|
|
index: last[0],
|
|
|
|
}
|
|
|
|
} else if (pathToNode.length === 0) {
|
|
|
|
return {
|
|
|
|
path: [],
|
|
|
|
index: -1,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return splitPathAtPipeExpression(pathToNode.slice(0, -1))
|
|
|
|
}
|
|
|
|
|
2023-02-12 10:56:45 +11:00
|
|
|
export function createLiteral(value: string | number): Literal {
|
|
|
|
return {
|
|
|
|
type: 'Literal',
|
|
|
|
start: 0,
|
|
|
|
end: 0,
|
|
|
|
value,
|
|
|
|
raw: `${value}`,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function createIdentifier(name: string): Identifier {
|
|
|
|
return {
|
|
|
|
type: 'Identifier',
|
|
|
|
start: 0,
|
|
|
|
end: 0,
|
|
|
|
name,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function createPipeSubstitution(): PipeSubstitution {
|
|
|
|
return {
|
|
|
|
type: 'PipeSubstitution',
|
|
|
|
start: 0,
|
|
|
|
end: 0,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-05 16:02:27 -07:00
|
|
|
export function createCallExpressionStdLib(
|
|
|
|
name: string,
|
|
|
|
args: CallExpression['arguments']
|
|
|
|
): CallExpression {
|
|
|
|
return {
|
|
|
|
type: 'CallExpression',
|
|
|
|
start: 0,
|
|
|
|
end: 0,
|
|
|
|
callee: {
|
|
|
|
type: 'Identifier',
|
|
|
|
start: 0,
|
|
|
|
end: 0,
|
|
|
|
name,
|
|
|
|
},
|
|
|
|
optional: false,
|
|
|
|
arguments: args,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-12 10:56:45 +11:00
|
|
|
export function createCallExpression(
|
|
|
|
name: string,
|
|
|
|
args: CallExpression['arguments']
|
|
|
|
): CallExpression {
|
|
|
|
return {
|
|
|
|
type: 'CallExpression',
|
|
|
|
start: 0,
|
|
|
|
end: 0,
|
|
|
|
callee: {
|
|
|
|
type: 'Identifier',
|
|
|
|
start: 0,
|
|
|
|
end: 0,
|
|
|
|
name,
|
|
|
|
},
|
|
|
|
optional: false,
|
|
|
|
arguments: args,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function createArrayExpression(
|
|
|
|
elements: ArrayExpression['elements']
|
|
|
|
): ArrayExpression {
|
|
|
|
return {
|
|
|
|
type: 'ArrayExpression',
|
|
|
|
start: 0,
|
|
|
|
end: 0,
|
|
|
|
elements,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function createPipeExpression(
|
|
|
|
body: PipeExpression['body']
|
|
|
|
): PipeExpression {
|
|
|
|
return {
|
2023-01-09 13:19:14 +11:00
|
|
|
type: 'PipeExpression',
|
2023-02-12 10:56:45 +11:00
|
|
|
start: 0,
|
|
|
|
end: 0,
|
|
|
|
body,
|
New parser built in Winnow (#731)
* New parser built with Winnow
This new parser uses [winnow](docs.rs/winnow) to replace the handwritten recursive parser.
## Differences
I think the Winnow parser is more readable than handwritten one, due to reusing standard combinators. If you have a parsre like `p` or `q` you can combine them with standard functions like `repeat(0..4, p)`, `opt(p)`, `alt((p, q))` and `separated1(p, ", ")`. This IMO makes it more readable once you know what those standard functions do.
It's also more accurate now -- e.g. the parser no longer swallows whitespace between comments, or inserts it where there was none before. It no longer changes // comments to /* comments depending on the surrounding whitespace.
Primary form of testing was running the same KCL program through both the old and new parsers and asserting that both parsers produce the same AST. See the test `parser::parser_impl::tests::check_parsers_work_the_same`. But occasionally the new and old parsers disagree. This is either:
- Innocuous (e.g. disagreeing on whether a comment starts at the preceding whitespace or at the //)
- Helpful (e.g. new parser recognizes comments more accurately, preserving the difference between // and /* comments)
- Acceptably bad (e.g. new parser sometimes outputs worse error messages, TODO in #784)
so those KCL programs have their own unit tests in `parser_impl.rs` demonstrating the behaviour.
If you'd like to review this PR, it's arguably more important to review changes to the existing unit tests rather than the new parser itself. Because changes to the unit tests show where my parser changes behaviour -- usually for the better, occasionally for the worse (e.g. a worse error message than before). I think overall the improvements are worth it that I'd like to merge it without spending another week fixing it up -- we can fix the error messages in a follow-up PR.
## Performance
| Benchmark | Old parser (this branch) | New parser (this branch) | Speedup |
| ------------- | ------------- | ------------- | ------------- |
| Pipes on pipes | 922 ms | 42 ms | 21x |
| Kitt SVG | 148 ms | 7 ms | 21x |
There's definitely still room to improve performance:
- https://github.com/KittyCAD/modeling-app/issues/839
- https://github.com/KittyCAD/modeling-app/issues/840
## Winnow
Y'all know I love [Nom](docs.rs/nom) and I've blogged about it a lot. But I'm very happy using Winnow, a fork. It's got some really nice usability improvements. While writing this PR I found some bugs or unclear docs in Winnow:
- https://github.com/winnow-rs/winnow/issues/339
- https://github.com/winnow-rs/winnow/issues/341
- https://github.com/winnow-rs/winnow/issues/342
- https://github.com/winnow-rs/winnow/issues/344
The maintainer was quick to close them and release new versions within a few hours, so I feel very confident building the parser on this library. It's a clear improvement over Nom and it's used in toml-edit (and therefore within Cargo) and Gitoxide, so it's becoming a staple of the Rust ecosystem, which adds confidence.
Closes #716
Closes #815
Closes #599
2023-10-12 09:42:37 -05:00
|
|
|
nonCodeMeta: { nonCodeNodes: {}, start: [] },
|
2023-01-09 13:19:14 +11:00
|
|
|
}
|
2023-02-12 10:56:45 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
export function createVariableDeclaration(
|
|
|
|
varName: string,
|
|
|
|
init: VariableDeclarator['init'],
|
|
|
|
kind: VariableDeclaration['kind'] = 'const'
|
|
|
|
): VariableDeclaration {
|
|
|
|
return {
|
2023-01-09 13:19:14 +11:00
|
|
|
type: 'VariableDeclaration',
|
2023-02-12 10:56:45 +11:00
|
|
|
start: 0,
|
|
|
|
end: 0,
|
2023-01-09 13:19:14 +11:00
|
|
|
declarations: [
|
|
|
|
{
|
|
|
|
type: 'VariableDeclarator',
|
2023-02-12 10:56:45 +11:00
|
|
|
start: 0,
|
|
|
|
end: 0,
|
|
|
|
id: createIdentifier(varName),
|
|
|
|
init,
|
2023-01-09 13:19:14 +11:00
|
|
|
},
|
|
|
|
],
|
2023-02-12 10:56:45 +11:00
|
|
|
kind,
|
2023-01-09 13:19:14 +11:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-12 10:56:45 +11:00
|
|
|
export function createObjectExpression(properties: {
|
|
|
|
[key: string]: Value
|
|
|
|
}): ObjectExpression {
|
|
|
|
return {
|
|
|
|
type: 'ObjectExpression',
|
|
|
|
start: 0,
|
|
|
|
end: 0,
|
|
|
|
properties: Object.entries(properties).map(([key, value]) => ({
|
|
|
|
type: 'ObjectProperty',
|
|
|
|
start: 0,
|
|
|
|
end: 0,
|
|
|
|
key: createIdentifier(key),
|
|
|
|
value,
|
|
|
|
})),
|
2023-01-09 13:19:14 +11:00
|
|
|
}
|
|
|
|
}
|
2023-03-02 21:19:11 +11:00
|
|
|
|
|
|
|
export function createUnaryExpression(
|
|
|
|
argument: UnaryExpression['argument'],
|
|
|
|
operator: UnaryExpression['operator'] = '-'
|
|
|
|
): UnaryExpression {
|
|
|
|
return {
|
|
|
|
type: 'UnaryExpression',
|
|
|
|
start: 0,
|
|
|
|
end: 0,
|
|
|
|
operator,
|
|
|
|
argument,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function createBinaryExpression([left, operator, right]: [
|
|
|
|
BinaryExpression['left'],
|
|
|
|
BinaryExpression['operator'],
|
|
|
|
BinaryExpression['right']
|
|
|
|
]): BinaryExpression {
|
|
|
|
return {
|
|
|
|
type: 'BinaryExpression',
|
|
|
|
start: 0,
|
|
|
|
end: 0,
|
|
|
|
operator,
|
|
|
|
left,
|
|
|
|
right,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-02 17:20:11 +10:00
|
|
|
export function createBinaryExpressionWithUnary([left, right]: [
|
|
|
|
BinaryExpression['left'],
|
|
|
|
BinaryExpression['right']
|
|
|
|
]): BinaryExpression {
|
|
|
|
if (right.type === 'UnaryExpression' && right.operator === '-')
|
|
|
|
return createBinaryExpression([left, '-', right.argument])
|
|
|
|
return createBinaryExpression([left, '+', right])
|
|
|
|
}
|
|
|
|
|
2023-03-02 21:19:11 +11:00
|
|
|
export function giveSketchFnCallTag(
|
|
|
|
ast: Program,
|
2023-04-03 16:05:25 +10:00
|
|
|
range: Selection['range'],
|
2023-03-07 15:45:59 +11:00
|
|
|
tag?: string
|
2023-04-14 07:49:36 +10:00
|
|
|
): {
|
|
|
|
modifiedAst: Program
|
|
|
|
tag: string
|
|
|
|
isTagExisting: boolean
|
|
|
|
pathToNode: PathToNode
|
|
|
|
} {
|
|
|
|
const path = getNodePathFromSourceRange(ast, range)
|
2023-03-02 21:19:11 +11:00
|
|
|
const { node: primaryCallExp } = getNodeFromPath<CallExpression>(
|
|
|
|
ast,
|
2023-04-14 07:49:36 +10:00
|
|
|
path,
|
2023-04-01 16:47:00 +11:00
|
|
|
'CallExpression'
|
2023-03-02 21:19:11 +11:00
|
|
|
)
|
2024-03-15 17:03:42 -04:00
|
|
|
// Tag is always 3rd expression now, using arg index feels brittle
|
|
|
|
// but we can come up with a better way to identify tag later.
|
|
|
|
const thirdArg = primaryCallExp.arguments?.[2]
|
|
|
|
const tagLiteral =
|
|
|
|
thirdArg || (createLiteral(tag || findUniqueName(ast, 'seg', 2)) as Literal)
|
|
|
|
const isTagExisting = !!thirdArg
|
|
|
|
if (!isTagExisting) {
|
|
|
|
primaryCallExp.arguments[2] = tagLiteral
|
|
|
|
}
|
|
|
|
if ('value' in tagLiteral) {
|
|
|
|
// Now TypeScript knows tagLiteral has a value property
|
|
|
|
return {
|
|
|
|
modifiedAst: ast,
|
|
|
|
tag: String(tagLiteral.value),
|
|
|
|
isTagExisting,
|
|
|
|
pathToNode: path,
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
throw new Error('Unable to assign tag without value')
|
2023-03-02 21:19:11 +11:00
|
|
|
}
|
|
|
|
}
|
2023-04-01 16:47:00 +11:00
|
|
|
|
|
|
|
export function moveValueIntoNewVariable(
|
|
|
|
ast: Program,
|
|
|
|
programMemory: ProgramMemory,
|
2023-04-03 16:05:25 +10:00
|
|
|
sourceRange: Selection['range'],
|
2023-04-01 16:47:00 +11:00
|
|
|
variableName: string
|
|
|
|
): {
|
|
|
|
modifiedAst: Program
|
|
|
|
} {
|
|
|
|
const { isSafe, value, replacer } = isNodeSafeToReplace(ast, sourceRange)
|
|
|
|
if (!isSafe || value.type === 'Identifier') return { modifiedAst: ast }
|
|
|
|
|
|
|
|
const { insertIndex } = findAllPreviousVariables(
|
|
|
|
ast,
|
|
|
|
programMemory,
|
|
|
|
sourceRange
|
|
|
|
)
|
|
|
|
let _node = JSON.parse(JSON.stringify(ast))
|
|
|
|
_node = replacer(_node, variableName).modifiedAst
|
|
|
|
_node.body.splice(
|
|
|
|
insertIndex,
|
|
|
|
0,
|
|
|
|
createVariableDeclaration(variableName, value)
|
|
|
|
)
|
|
|
|
return { modifiedAst: _node }
|
|
|
|
}
|