Replace values with variable helper (#84)

* Refactor getNodePathFromSourceRange

getNodePathFromSourceRange wouldn't go as deep as it should have,
stopping at pipe expressions, when it should have followed as deep
into the ast as possible.

The fact that it stopped early then had other part of the code base that
expected this behaviour and it effected a lot, so a rather large refactor

* overhaul of getNodePathFromSourceRange

* quick fix for moreNodePathFromSourceRange

* minor bugs in moreNodePathFromSourceRange

* couple more tests

* add moveValueIntoNewVariable

* add UI for replacing valuse with variable

* update button text
This commit is contained in:
Kurt Hutten
2023-04-01 16:47:00 +11:00
committed by GitHub
parent 42eb3506bb
commit 0593afc4ff
16 changed files with 936 additions and 168 deletions

View File

@ -8,6 +8,7 @@ import { EqualAngle } from './components/Toolbar/EqualAngle'
import { Intersect } from './components/Toolbar/Intersect'
import { SetHorzDistance } from './components/Toolbar/SetHorzDistance'
import { SetAngleLength } from './components/Toolbar/SetAngleLength'
import { ConvertToVariable } from './components/Toolbar/ConvertVariable'
export const Toolbar = () => {
const {
@ -162,6 +163,7 @@ export const Toolbar = () => {
)
})}
<br></br>
<ConvertToVariable />
<HorzVert horOrVert="horizontal" />
<HorzVert horOrVert="vertical" />
<EqualLength />

View File

@ -188,12 +188,14 @@ export const CreateNewVariable = ({
setNewVariableName,
shouldCreateVariable,
setShouldCreateVariable,
showCheckbox = true,
}: {
isNewVariableNameUnique: boolean
newVariableName: string
setNewVariableName: (a: string) => void
shouldCreateVariable: boolean
setShouldCreateVariable: (a: boolean) => void
showCheckbox?: boolean
}) => {
return (
<>
@ -204,6 +206,7 @@ export const CreateNewVariable = ({
Create new variable
</label>
<div className="mt-1 flex flex-1">
{showCheckbox && (
<input
type="checkbox"
className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md font-mono pl-1 flex-shrink"
@ -212,6 +215,7 @@ export const CreateNewVariable = ({
setShouldCreateVariable(e.target.checked)
}}
/>
)}
<input
type="text"
disabled={!shouldCreateVariable}

View File

@ -0,0 +1,86 @@
import { Dialog, Transition } from '@headlessui/react'
import { Fragment, useState } from 'react'
import { useCalc, CreateNewVariable } from './AvailableVarsHelpers'
export const SetVarNameModal = ({
isOpen,
onResolve,
onReject,
valueName,
}: {
isOpen: boolean
onResolve: (a: { variableName?: string }) => void
onReject: (a: any) => void
value: number
valueName: string
}) => {
const { isNewVariableNameUnique, newVariableName, setNewVariableName } =
useCalc({ value: '', initialVariableName: valueName })
return (
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={onReject}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-black bg-opacity-25" />
</Transition.Child>
<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-gray-900 capitalize"
>
Set {valueName}
</Dialog.Title>
<CreateNewVariable
setNewVariableName={setNewVariableName}
newVariableName={newVariableName}
isNewVariableNameUnique={isNewVariableNameUnique}
shouldCreateVariable={true}
setShouldCreateVariable={() => {}}
/>
<div className="mt-4">
<button
type="button"
disabled={!isNewVariableNameUnique}
className={`inline-flex justify-center rounded-md border border-transparent bg-blue-100 px-4 py-2 text-sm font-medium text-blue-900 hover:bg-blue-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2 ${
!isNewVariableNameUnique
? 'opacity-50 cursor-not-allowed'
: ''
}`}
onClick={() =>
onResolve({
variableName: newVariableName,
})
}
>
Add variable
</button>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
)
}

View File

@ -0,0 +1,61 @@
import { useState, useEffect } from 'react'
import { create } from 'react-modal-promise'
import { useStore } from '../../useStore'
import { isNodeSafeToReplace } from '../../lang/queryAst'
import { SetVarNameModal } from '../SetVarNameModal'
import { moveValueIntoNewVariable } from '../../lang/modifyAst'
const getModalInfo = create(SetVarNameModal as any)
export const ConvertToVariable = () => {
const { guiMode, selectionRanges, ast, programMemory, updateAst } = useStore(
(s) => ({
guiMode: s.guiMode,
ast: s.ast,
updateAst: s.updateAst,
selectionRanges: s.selectionRanges,
programMemory: s.programMemory,
})
)
const [enableAngLen, setEnableAngLen] = useState(false)
useEffect(() => {
if (!ast) return
const { isSafe, value } = isNodeSafeToReplace(ast, selectionRanges[0])
const canReplace = isSafe && value.type !== 'Identifier'
const isOnlyOneSelection = selectionRanges.length === 1
const _enableHorz = canReplace && isOnlyOneSelection
setEnableAngLen(_enableHorz)
}, [guiMode, selectionRanges])
return (
<button
onClick={async () => {
if (!ast) return
try {
const { variableName } = await getModalInfo({
valueName: 'var',
} as any)
const { modifiedAst: _modifiedAst } = moveValueIntoNewVariable(
ast,
programMemory,
selectionRanges[0],
variableName
)
updateAst(_modifiedAst)
} catch (e) {
console.log('e', e)
}
}}
className={`border m-1 px-1 rounded text-xs ${
enableAngLen ? 'bg-gray-50 text-gray-800' : 'bg-gray-200 text-gray-400'
}`}
disabled={!enableAngLen}
>
ConvertToVariable
</button>
)
}

View File

@ -34,7 +34,7 @@ export const SetAngleLength = ({
programMemory: s.programMemory,
})
)
const [enableHorz, setEnableHorz] = useState(false)
const [enableAngLen, setEnableAngLen] = useState(false)
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
useEffect(() => {
if (!ast) return
@ -42,7 +42,8 @@ export const SetAngleLength = ({
getNodePathFromSourceRange(ast, selectionRange)
)
const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(ast, pathToNode).node
(pathToNode) =>
getNodeFromPath<Value>(ast, pathToNode, 'CallExpression').node
)
const isAllTooltips = nodes.every(
(node) =>
@ -54,7 +55,7 @@ export const SetAngleLength = ({
setTransformInfos(theTransforms)
const _enableHorz = isAllTooltips && theTransforms.every(Boolean)
setEnableHorz(_enableHorz)
setEnableAngLen(_enableHorz)
}, [guiMode, selectionRanges])
if (guiMode.mode !== 'sketch') return null
@ -102,9 +103,9 @@ export const SetAngleLength = ({
}
}}
className={`border m-1 px-1 rounded text-xs ${
enableHorz ? 'bg-gray-50 text-gray-800' : 'bg-gray-200 text-gray-400'
enableAngLen ? 'bg-gray-50 text-gray-800' : 'bg-gray-200 text-gray-400'
}`}
disabled={!enableHorz}
disabled={!enableAngLen}
>
{angleOrLength}
</button>

View File

@ -1,9 +1,7 @@
import { PathToNode } from './executor'
import { Token } from './tokeniser'
import { parseExpression } from './astMathExpressions'
import { Range } from '../useStore'
type syntaxType =
export type SyntaxType =
| 'Program'
| 'ExpressionStatement'
| 'BinaryExpression'
@ -81,14 +79,14 @@ type syntaxType =
// | 'TypeAnnotation'
export interface Program {
type: syntaxType
type: SyntaxType
start: number
end: number
body: BodyItem[]
nonCodeMeta: NoneCodeMeta
}
interface GeneralStatement {
type: syntaxType
type: SyntaxType
start: number
end: number
}
@ -1102,7 +1100,7 @@ function makeBlockStatement(
}
}
interface ReturnStatement extends GeneralStatement {
export interface ReturnStatement extends GeneralStatement {
type: 'ReturnStatement'
argument: Value
}

View File

@ -229,7 +229,13 @@ show(mySketch)
value: 3,
__meta: [
{
pathToNode: ['body', 0, 'declarations', 0, 'init'],
pathToNode: [
['body', ''],
[0, 'index'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', 'VariableDeclaration'],
],
sourceRange: [14, 15],
},
],
@ -239,11 +245,23 @@ show(mySketch)
value: [1, '2', 3, 9],
__meta: [
{
pathToNode: ['body', 1, 'declarations', 0, 'init'],
pathToNode: [
['body', ''],
[1, 'index'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', 'VariableDeclaration'],
],
sourceRange: [27, 49],
},
{
pathToNode: ['body', 0, 'declarations', 0, 'init'],
pathToNode: [
['body', ''],
[0, 'index'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', 'VariableDeclaration'],
],
sourceRange: [14, 15],
},
],
@ -258,15 +276,16 @@ show(mySketch)
const { root } = exe(code)
expect(root.yo).toEqual({
type: 'userVal',
value: {
aStr: 'str',
anum: 2,
identifier: 3,
binExp: 9,
},
value: { aStr: 'str', anum: 2, identifier: 3, binExp: 9 },
__meta: [
{
pathToNode: ['body', 1, 'declarations', 0, 'init'],
pathToNode: [
['body', ''],
[1, 'index'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', 'VariableDeclaration'],
],
sourceRange: [27, 83],
},
],
@ -282,7 +301,13 @@ show(mySketch)
value: '123',
__meta: [
{
pathToNode: ['body', 1, 'declarations', 0, 'init'],
pathToNode: [
['body', ''],
[1, 'index'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', 'VariableDeclaration'],
],
sourceRange: [41, 50],
},
],

View File

@ -15,7 +15,7 @@ import { internalFns } from './std/std'
import { BufferGeometry } from 'three'
export type SourceRange = [number, number]
export type PathToNode = (string | number)[]
export type PathToNode = [string | number, string][]
export type Metadata = {
sourceRange: SourceRange
pathToNode: PathToNode
@ -130,13 +130,13 @@ export const executor = (
if (statement.type === 'VariableDeclaration') {
statement.declarations.forEach((declaration, index) => {
const variableName = declaration.id.name
const pathToNode = [
const pathToNode: PathToNode = [
...previousPathToNode,
'body',
bodyIndex,
'declarations',
index,
'init',
['body', ''],
[bodyIndex, 'index'],
['declarations', 'VariableDeclaration'],
[index, 'index'],
['init', 'VariableDeclaration'],
]
const sourceRange: SourceRange = [
declaration.init.start,

View File

@ -11,10 +11,12 @@ import {
findUniqueName,
addSketchTo,
giveSketchFnCallTag,
moveValueIntoNewVariable,
} from './modifyAst'
import { recast } from './recast'
import { lexer } from './tokeniser'
import { initPromise } from './rust'
import { executor } from './executor'
beforeAll(() => initPromise)
@ -173,3 +175,93 @@ show(part001)`
expect(isTagExisting).toBe(true)
})
})
describe('Testing moveValueIntoNewVariable', () => {
const fn = (fnName: string) => `const ${fnName} = (x) => {
return x
}
`
const code = `${fn('def')}${fn('ghi')}${fn('jkl')}${fn('hmm')}
const abc = 3
const identifierGuy = 5
const part001 = startSketchAt([-1.2, 4.83])
|> line([2.8, 0], %)
|> angledLine([100 + 100, 3.09], %)
|> angledLine([abc, 3.09], %)
|> angledLine([def('yo'), 3.09], %)
|> angledLine([ghi(%), 3.09], %)
|> angledLine([jkl('yo') + 2, 3.09], %)
const yo = 5 + 6
const yo2 = hmm([identifierGuy + 5])
show(part001)`
it('should move a value into a new variable', () => {
const ast = abstractSyntaxTree(lexer(code))
const programMemory = executor(ast)
const startIndex = code.indexOf('100 + 100') + 1
const { modifiedAst } = moveValueIntoNewVariable(
ast,
programMemory,
[startIndex, startIndex],
'newVar'
)
const newCode = recast(modifiedAst)
expect(newCode).toContain(`const newVar = 100 + 100`)
expect(newCode).toContain(`angledLine([newVar, 3.09], %)`)
})
it('should move a value into a new variable', () => {
const ast = abstractSyntaxTree(lexer(code))
const programMemory = executor(ast)
const startIndex = code.indexOf('2.8') + 1
const { modifiedAst } = moveValueIntoNewVariable(
ast,
programMemory,
[startIndex, startIndex],
'newVar'
)
const newCode = recast(modifiedAst)
expect(newCode).toContain(`const newVar = 2.8`)
expect(newCode).toContain(`line([newVar, 0], %)`)
})
it('should move a value into a new variable', () => {
const ast = abstractSyntaxTree(lexer(code))
const programMemory = executor(ast)
const startIndex = code.indexOf('def(')
const { modifiedAst } = moveValueIntoNewVariable(
ast,
programMemory,
[startIndex, startIndex],
'newVar'
)
const newCode = recast(modifiedAst)
expect(newCode).toContain(`const newVar = def('yo')`)
expect(newCode).toContain(`angledLine([newVar, 3.09], %)`)
})
it('should move a value into a new variable', () => {
const ast = abstractSyntaxTree(lexer(code))
const programMemory = executor(ast)
const startIndex = code.indexOf('jkl(') + 1
const { modifiedAst } = moveValueIntoNewVariable(
ast,
programMemory,
[startIndex, startIndex],
'newVar'
)
const newCode = recast(modifiedAst)
expect(newCode).toContain(`const newVar = jkl('yo') + 2`)
expect(newCode).toContain(`angledLine([newVar, 3.09], %)`)
})
it('should move a value into a new variable', () => {
const ast = abstractSyntaxTree(lexer(code))
const programMemory = executor(ast)
const startIndex = code.indexOf('identifierGuy +') + 1
const { modifiedAst } = moveValueIntoNewVariable(
ast,
programMemory,
[startIndex, startIndex],
'newVar'
)
const newCode = recast(modifiedAst)
expect(newCode).toContain(`const newVar = identifierGuy + 5`)
expect(newCode).toContain(`const yo2 = hmm([newVar])`)
})
})

View File

@ -15,7 +15,12 @@ import {
UnaryExpression,
BinaryExpression,
} from './abstractSyntaxTree'
import { getNodeFromPath, getNodePathFromSourceRange } from './queryAst'
import {
findAllPreviousVariables,
getNodeFromPath,
getNodePathFromSourceRange,
isNodeSafeToReplace,
} from './queryAst'
import { PathToNode, ProgramMemory } from './executor'
import {
addTagForSketchOnFace,
@ -27,7 +32,7 @@ export function addSketchTo(
node: Program,
axis: 'xy' | 'xz' | 'yz',
name = ''
): { modifiedAst: Program; id: string; pathToNode: (string | number)[] } {
): { modifiedAst: Program; id: string; pathToNode: PathToNode } {
const _node = { ...node }
const _name = name || findUniqueName(node, 'part')
@ -63,15 +68,15 @@ export function addSketchTo(
newBody.splice(showCallIndex, 0, variableDeclaration)
_node.body = newBody
}
let pathToNode: (string | number)[] = [
'body',
sketchIndex,
'declarations',
'0',
'init',
let pathToNode: PathToNode = [
['body', ''],
[sketchIndex, 'index'],
['declarations', 'VariableDeclaration'],
['0', 'index'],
['init', 'VariableDeclarator'],
]
if (axis !== 'xy') {
pathToNode = [...pathToNode, 'body', '0']
pathToNode = [...pathToNode, ['body', ''], ['0', 'index']]
}
return {
@ -194,7 +199,7 @@ export function mutateObjExpProp(
export function extrudeSketch(
node: Program,
pathToNode: (string | number)[],
pathToNode: PathToNode,
shouldPipe = true
): {
modifiedAst: Program
@ -217,7 +222,7 @@ export function extrudeSketch(
)
const isInPipeExpression = pipeExpression.type === 'PipeExpression'
const { node: variableDeclorator, path: pathToDecleration } =
const { node: variableDeclorator, shallowPath: pathToDecleration } =
getNodeFromPath<VariableDeclarator>(_node, pathToNode, 'VariableDeclarator')
const extrudeCall = createCallExpression('extrude', [
@ -239,13 +244,13 @@ export function extrudeSketch(
)
variableDeclorator.init = pipeChain
const pathToExtrudeArg = [
const pathToExtrudeArg: PathToNode = [
...pathToDecleration,
'init',
'body',
pipeChain.body.length - 1,
'arguments',
0,
['init', 'VariableDeclarator'],
['body', ''],
[pipeChain.body.length - 1, 'index'],
['arguments', 'CallExpression'],
[0, 'index'],
]
return {
@ -258,30 +263,30 @@ export function extrudeSketch(
const VariableDeclaration = createVariableDeclaration(name, extrudeCall)
const showCallIndex = getShowIndex(_node)
_node.body.splice(showCallIndex, 0, VariableDeclaration)
const pathToExtrudeArg = [
'body',
showCallIndex,
'declarations',
0,
'init',
'arguments',
0,
const pathToExtrudeArg: PathToNode = [
['body', ''],
[showCallIndex, 'index'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', 'VariableDeclarator'],
['arguments', 'CallExpression'],
[0, 'index'],
]
return {
modifiedAst: addToShow(_node, name),
pathToNode: [...pathToNode.slice(0, -1), showCallIndex],
pathToNode: [...pathToNode.slice(0, -1), [showCallIndex, 'index']],
pathToExtrudeArg,
}
}
export function sketchOnExtrudedFace(
node: Program,
pathToNode: (string | number)[],
pathToNode: PathToNode,
programMemory: ProgramMemory
): { modifiedAst: Program; pathToNode: (string | number)[] } {
): { modifiedAst: Program; pathToNode: PathToNode } {
let _node = { ...node }
const newSketchName = findUniqueName(node, 'part')
const { node: oldSketchNode, path: pathToOldSketch } =
const { node: oldSketchNode, shallowPath: pathToOldSketch } =
getNodeFromPath<VariableDeclarator>(
_node,
pathToNode,
@ -330,7 +335,7 @@ export function sketchOnExtrudedFace(
return {
modifiedAst: addToShow(_node, newSketchName),
pathToNode: [...pathToNode.slice(0, -1), expressionIndex],
pathToNode: [...pathToNode.slice(0, -1), [expressionIndex, 'index']],
}
}
@ -342,10 +347,10 @@ export function splitPathAtLastIndex(pathToNode: PathToNode): {
index: number
} {
const last = pathToNode[pathToNode.length - 1]
if (typeof last === 'number') {
if (last && typeof last[0] === 'number') {
return {
path: pathToNode.slice(0, -1),
index: last,
index: last[0],
}
} else if (pathToNode.length === 0) {
return {
@ -356,6 +361,32 @@ export function splitPathAtLastIndex(pathToNode: PathToNode): {
return splitPathAtLastIndex(pathToNode.slice(0, -1))
}
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))
}
export function createLiteral(value: string | number): Literal {
return {
type: 'Literal',
@ -499,7 +530,8 @@ export function giveSketchFnCallTag(
): { modifiedAst: Program; tag: string; isTagExisting: boolean } {
const { node: primaryCallExp } = getNodeFromPath<CallExpression>(
ast,
getNodePathFromSourceRange(ast, range)
getNodePathFromSourceRange(ast, range),
'CallExpression'
)
const firstArg = getFirstArg(primaryCallExp)
const isTagExisting = !!firstArg.tag
@ -518,3 +550,29 @@ export function giveSketchFnCallTag(
isTagExisting,
}
}
export function moveValueIntoNewVariable(
ast: Program,
programMemory: ProgramMemory,
sourceRange: Range,
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 }
}

View File

@ -1,8 +1,20 @@
import { abstractSyntaxTree } from './abstractSyntaxTree'
import { findAllPreviousVariables } from './queryAst'
import {
findAllPreviousVariables,
isNodeSafeToReplace,
isTypeInValue,
getNodePathFromSourceRange,
} from './queryAst'
import { lexer } from './tokeniser'
import { initPromise } from './rust'
import { executor } from './executor'
import {
createArrayExpression,
createCallExpression,
createLiteral,
createPipeSubstitution,
} from './modifyAst'
import { recast } from './recast'
beforeAll(() => initPromise)
@ -43,6 +55,194 @@ show(part001)`
// there are 4 number variables and 2 non-number variables before the sketch var
// ∴ the insert index should be 6
expect(insertIndex).toEqual(6)
expect(bodyPath).toEqual(['body'])
expect(bodyPath).toEqual([['body', '']])
})
})
describe('testing argIsNotIdentifier', () => {
const code = `const part001 = startSketchAt([-1.2, 4.83])
|> line([2.8, 0], %)
|> angledLine([100 + 100, 3.09], %)
|> angledLine([abc, 3.09], %)
|> angledLine([def('yo'), 3.09], %)
|> angledLine([ghi(%), 3.09], %)
|> angledLine([jkl('yo') + 2, 3.09], %)
const yo = 5 + 6
const yo2 = hmm([identifierGuy + 5])
show(part001)`
it('find a safe binaryExpression', () => {
const ast = abstractSyntaxTree(lexer(code))
const rangeStart = code.indexOf('100 + 100') + 2
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
expect(result.isSafe).toBe(true)
expect(result.value?.type).toBe('BinaryExpression')
expect(code.slice(result.value.start, result.value.end)).toBe('100 + 100')
const { modifiedAst } = result.replacer(
JSON.parse(JSON.stringify(ast)),
'replaceName'
)
const outCode = recast(modifiedAst)
expect(outCode).toContain(`angledLine([replaceName, 3.09], %)`)
})
it('find a safe Identifier', () => {
const ast = abstractSyntaxTree(lexer(code))
const rangeStart = code.indexOf('abc')
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
expect(result.isSafe).toBe(true)
expect(result.value?.type).toBe('Identifier')
expect(code.slice(result.value.start, result.value.end)).toBe('abc')
})
it('find a safe CallExpression', () => {
const ast = abstractSyntaxTree(lexer(code))
const rangeStart = code.indexOf('def')
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
expect(result.isSafe).toBe(true)
expect(result.value?.type).toBe('CallExpression')
expect(code.slice(result.value.start, result.value.end)).toBe("def('yo')")
const { modifiedAst } = result.replacer(
JSON.parse(JSON.stringify(ast)),
'replaceName'
)
const outCode = recast(modifiedAst)
expect(outCode).toContain(`angledLine([replaceName, 3.09], %)`)
})
it('find an UNsafe CallExpression, as it has a PipeSubstitution', () => {
const ast = abstractSyntaxTree(lexer(code))
const rangeStart = code.indexOf('ghi')
const range: [number, number] = [rangeStart, rangeStart]
const result = isNodeSafeToReplace(ast, range)
expect(result.isSafe).toBe(false)
expect(result.value?.type).toBe('CallExpression')
expect(code.slice(result.value.start, result.value.end)).toBe('ghi(%)')
})
it('find an UNsafe Identifier, as it is a callee', () => {
const ast = abstractSyntaxTree(lexer(code))
const rangeStart = code.indexOf('ine([2.8,')
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
expect(result.isSafe).toBe(false)
expect(result.value?.type).toBe('CallExpression')
expect(code.slice(result.value.start, result.value.end)).toBe(
'line([2.8, 0], %)'
)
})
it("find a safe BinaryExpression that's assigned to a variable", () => {
const ast = abstractSyntaxTree(lexer(code))
const rangeStart = code.indexOf('5 + 6') + 1
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
expect(result.isSafe).toBe(true)
expect(result.value?.type).toBe('BinaryExpression')
expect(code.slice(result.value.start, result.value.end)).toBe('5 + 6')
const { modifiedAst } = result.replacer(
JSON.parse(JSON.stringify(ast)),
'replaceName'
)
const outCode = recast(modifiedAst)
expect(outCode).toContain(`const yo = replaceName`)
})
it('find a safe BinaryExpression that has a CallExpression within', () => {
const ast = abstractSyntaxTree(lexer(code))
const rangeStart = code.indexOf('jkl') + 1
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
expect(result.isSafe).toBe(true)
expect(result.value?.type).toBe('BinaryExpression')
expect(code.slice(result.value.start, result.value.end)).toBe(
"jkl('yo') + 2"
)
const { modifiedAst } = result.replacer(
JSON.parse(JSON.stringify(ast)),
'replaceName'
)
const outCode = recast(modifiedAst)
expect(outCode).toContain(`angledLine([replaceName, 3.09], %)`)
})
it('find a safe BinaryExpression within a CallExpression', () => {
const ast = abstractSyntaxTree(lexer(code))
const rangeStart = code.indexOf('identifierGuy') + 1
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
expect(result.isSafe).toBe(true)
expect(result.value?.type).toBe('BinaryExpression')
expect(code.slice(result.value.start, result.value.end)).toBe(
'identifierGuy + 5'
)
const { modifiedAst } = result.replacer(
JSON.parse(JSON.stringify(ast)),
'replaceName'
)
const outCode = recast(modifiedAst)
expect(outCode).toContain(`const yo2 = hmm([replaceName])`)
})
describe('testing isTypeInValue', () => {
it('it finds the pipeSubstituion', () => {
const val = createCallExpression('yoyo', [
createArrayExpression([
createLiteral(1),
createCallExpression('yoyo2', [createPipeSubstitution()]),
createLiteral('hey'),
]),
])
expect(isTypeInValue(val, 'PipeSubstitution')).toBe(true)
})
it('There is no pipeSubstituion', () => {
const val = createCallExpression('yoyo', [
createArrayExpression([
createLiteral(1),
createCallExpression('yoyo2', [createLiteral(5)]),
createLiteral('hey'),
]),
])
expect(isTypeInValue(val, 'PipeSubstitution')).toBe(false)
})
})
})
describe('testing getNodePathFromSourceRange', () => {
const code = `const part001 = startSketchAt([0.39, -0.05])
|> line([0.94, 2.61], %)
|> line([-0.21, -1.4], %)
show(part001)`
it('it finds the second line when cursor is put at the end', () => {
const searchLn = `line([0.94, 2.61], %)`
const sourceIndex = code.indexOf(searchLn) + searchLn.length
const ast = abstractSyntaxTree(lexer(code))
const result = getNodePathFromSourceRange(ast, [sourceIndex, sourceIndex])
expect(result).toEqual([
['body', ''],
[0, 'index'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', ''],
['body', 'PipeExpression'],
[1, 'index'],
])
})
it('it finds the last line when cursor is put at the end', () => {
const searchLn = `line([-0.21, -1.4], %)`
const sourceIndex = code.indexOf(searchLn) + searchLn.length
const ast = abstractSyntaxTree(lexer(code))
const result = getNodePathFromSourceRange(ast, [sourceIndex, sourceIndex])
const expected = [
['body', ''],
[0, 'index'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', ''],
['body', 'PipeExpression'],
[2, 'index'],
]
expect(result).toEqual(expected)
// expect similar result for start of line
const startSourceIndex = code.indexOf(searchLn)
const startResult = getNodePathFromSourceRange(ast, [
startSourceIndex,
startSourceIndex,
])
expect(startResult).toEqual([...expected, ['callee', 'CallExpression']])
// expect similar result when whole line is selected
const selectWholeThing = getNodePathFromSourceRange(ast, [
startSourceIndex,
sourceIndex,
])
expect(selectWholeThing).toEqual(expected)
})
})

View File

@ -1,16 +1,28 @@
import { PathToNode, ProgramMemory } from './executor'
import { Range } from '../useStore'
import { Program } from './abstractSyntaxTree'
import { splitPathAtLastIndex } from './modifyAst'
import {
BinaryExpression,
Program,
SyntaxType,
Value,
CallExpression,
ExpressionStatement,
VariableDeclaration,
ReturnStatement,
ArrayExpression,
Identifier,
} from './abstractSyntaxTree'
import { createIdentifier, splitPathAtLastIndex } from './modifyAst'
export function getNodeFromPath<T>(
node: Program,
path: (string | number)[],
stopAt: string = '',
path: PathToNode,
stopAt: string | string[] = '',
returnEarly = false
): {
node: T
path: PathToNode
shallowPath: PathToNode
deepPath: PathToNode
} {
let currentNode = node as any
let stopAtNode = null
@ -18,21 +30,26 @@ export function getNodeFromPath<T>(
let pathsExplored: PathToNode = []
for (const pathItem of path) {
try {
if (typeof currentNode[pathItem] !== 'object')
if (typeof currentNode[pathItem[0]] !== 'object')
throw new Error('not an object')
currentNode = currentNode[pathItem]
currentNode = currentNode?.[pathItem[0]]
successfulPaths.push(pathItem)
if (!stopAtNode) {
pathsExplored.push(pathItem)
}
if (currentNode.type === stopAt) {
if (
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,
path: pathsExplored,
shallowPath: pathsExplored,
deepPath: successfulPaths,
}
}
}
@ -48,13 +65,14 @@ export function getNodeFromPath<T>(
}
return {
node: stopAtNode || currentNode,
path: pathsExplored,
shallowPath: pathsExplored,
deepPath: successfulPaths,
}
}
export function getNodeFromPathCurry(
node: Program,
path: (string | number)[]
path: PathToNode
): <T>(
stopAt: string,
returnEarly?: boolean
@ -63,18 +81,171 @@ export function getNodeFromPathCurry(
path: PathToNode
} {
return <T>(stopAt: string = '', returnEarly = false) => {
return getNodeFromPath<T>(node, path, stopAt, returnEarly)
const { node: _node, shallowPath } = getNodeFromPath<T>(
node,
path,
stopAt,
returnEarly
)
return {
node: _node,
path: shallowPath,
}
}
}
function moreNodePathFromSourceRange(
node: Value | ExpressionStatement | VariableDeclaration | ReturnStatement,
sourceRange: Range,
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
if ((_node.type === 'Identifier' || _node.type === 'Literal') && isInRange)
return path
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
}
console.error('not implemented')
return path
}
export function getNodePathFromSourceRange(
node: Program,
sourceRange: Range,
previousPath: PathToNode = []
previousPath: PathToNode = [['body', '']]
): PathToNode {
const [start, end] = sourceRange
let path: PathToNode = [...previousPath, 'body']
let path: PathToNode = [...previousPath]
const _node = { ...node }
// loop over each statement in body getting the index with a for loop
for (
let statementIndex = 0;
@ -83,53 +254,8 @@ export function getNodePathFromSourceRange(
) {
const statement = _node.body[statementIndex]
if (statement.start <= start && statement.end >= end) {
path.push(statementIndex)
if (statement.type === 'ExpressionStatement') {
const expression = statement.expression
if (expression.start <= start && expression.end >= end) {
path.push('expression')
if (expression.type === 'CallExpression') {
const callee = expression.callee
if (callee.start <= start && callee.end >= end) {
path.push('callee')
if (callee.type === 'Identifier') {
}
}
}
}
} else if (statement.type === 'VariableDeclaration') {
const declarations = statement.declarations
for (let decIndex = 0; decIndex < declarations.length; decIndex++) {
const declaration = declarations[decIndex]
if (declaration.start <= start && declaration.end >= end) {
path.push('declarations')
path.push(decIndex)
const init = declaration.init
if (init.start <= start && init.end >= end) {
path.push('init')
if (init.type === 'PipeExpression') {
const body = init.body
for (let pipeIndex = 0; pipeIndex < body.length; pipeIndex++) {
const pipe = body[pipeIndex]
if (pipe.start <= start && pipe.end >= end) {
path.push('body')
path.push(pipeIndex)
}
}
} else if (init.type === 'CallExpression') {
const callee = init.callee
if (callee.start <= start && callee.end >= end) {
path.push('callee')
if (callee.type === 'Identifier') {
}
}
}
}
}
}
}
path.push([statementIndex, 'index'])
return moreNodePathFromSourceRange(statement, sourceRange, path)
}
}
return path
@ -151,7 +277,11 @@ export function findAllPreviousVariables(
insertIndex: number
} {
const path = getNodePathFromSourceRange(ast, sourceRange)
const { path: pathToDec } = getNodeFromPath(ast, path, 'VariableDeclaration')
const { shallowPath: pathToDec } = getNodeFromPath(
ast,
path,
'VariableDeclaration'
)
const { index: insertIndex, path: bodyPath } = splitPathAtLastIndex(pathToDec)
const { node: bodyItems } = getNodeFromPath<Program['body']>(ast, bodyPath)
@ -174,3 +304,98 @@ export function findAllPreviousVariables(
variables,
}
}
type ReplacerFn = (_ast: Program, varName: string) => { modifiedAst: Program }
export function isNodeSafeToReplace(
ast: Program,
sourceRange: [number, number]
): {
isSafe: boolean
value: Value
replacer: ReplacerFn
} {
let path = getNodePathFromSourceRange(ast, sourceRange)
if (path[path.length - 1][0] === 'callee') {
path = path.slice(0, -1)
}
const acceptedNodeTypes = [
'BinaryExpression',
'Identifier',
'CallExpression',
'Literal',
]
const { node: value, deepPath: outPath } = getNodeFromPath(
ast,
path,
acceptedNodeTypes
)
const { node: binValue, shallowPath: outBinPath } = getNodeFromPath(
ast,
path,
'BinaryExpression'
)
// 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]
const startPath = finPath.slice(0, -1)
const nodeToReplace = getNodeFromPath(_ast, startPath).node as any
nodeToReplace[last[0]] = identifier
return { modifiedAst: _ast }
}
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,
}
}
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))
}

View File

@ -118,7 +118,13 @@ show(mySketch001)`
sketchMode: 'sketchEdit',
rotation: [0, 0, 0, 1],
position: [0, 0, 0],
pathToNode: ['body', 0, 'declarations', '0', 'init'],
pathToNode: [
['body', ''],
[0, 'index'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', 'VariableDeclarator'],
],
},
[0, 0]
)
@ -144,7 +150,13 @@ show(mySketch001)`
programMemory,
to: [2, 3],
fnName: 'lineTo',
pathToNode: ['body', 0, 'declarations', '0', 'init'],
pathToNode: [
['body', ''],
[0, 'index'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', 'VariableDeclarator'],
],
})
const expectedCode = `
const mySketch001 = startSketchAt([0, 0])

View File

@ -21,7 +21,7 @@ import {
} from '../queryAst'
import { lineGeo, sketchBaseGeo } from '../engine'
import { GuiModes, toolTips, TooTip } from '../../useStore'
import { getLastIndex } from '../modifyAst'
import { splitPathAtPipeExpression } from '../modifyAst'
import {
SketchLineHelper,
@ -166,7 +166,7 @@ export const lineTo: SketchLineHelper = {
createArrayExpression(newVals),
createPipeSubstitution(),
])
const callIndex = getLastIndex(pathToNode)
const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
if (replaceExisting && createCallback) {
const { callExp, valueUsedInTransform } = createCallback(
newVals,
@ -292,7 +292,7 @@ export const line: SketchLineHelper = {
const newYVal = createLiteral(roundOff(to[1] - from[1], 2))
if (replaceExisting && createCallback) {
const callIndex = getLastIndex(pathToNode)
const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
const { callExp, valueUsedInTransform } = createCallback(
[newXVal, newYVal],
referencedSegment
@ -383,7 +383,7 @@ export const xLineTo: SketchLineHelper = {
const newVal = createLiteral(roundOff(to[0], 2))
if (replaceExisting && createCallback) {
const callIndex = getLastIndex(pathToNode)
const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
const { callExp, valueUsedInTransform } = createCallback([newVal, newVal])
pipe.body[callIndex] = callExp
return {
@ -450,7 +450,7 @@ export const yLineTo: SketchLineHelper = {
const newVal = createLiteral(roundOff(to[1], 2))
if (replaceExisting && createCallback) {
const callIndex = getLastIndex(pathToNode)
const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
const { callExp, valueUsedInTransform } = createCallback([newVal, newVal])
pipe.body[callIndex] = callExp
return {
@ -514,7 +514,7 @@ export const xLine: SketchLineHelper = {
const firstArg = newVal
if (replaceExisting && createCallback) {
const callIndex = getLastIndex(pathToNode)
const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
const { callExp, valueUsedInTransform } = createCallback([
firstArg,
firstArg,
@ -577,7 +577,7 @@ export const yLine: SketchLineHelper = {
const { node: pipe } = getNode<PipeExpression>('PipeExpression')
const newVal = createLiteral(roundOff(to[1] - from[1], 2))
if (replaceExisting && createCallback) {
const callIndex = getLastIndex(pathToNode)
const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
const { callExp, valueUsedInTransform } = createCallback([newVal, newVal])
pipe.body[callIndex] = callExp
return {
@ -686,7 +686,7 @@ export const angledLine: SketchLineHelper = {
])
if (replaceExisting && createCallback) {
const callIndex = getLastIndex(pathToNode)
const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
const { callExp, valueUsedInTransform } = createCallback(
[newAngleVal, newLengthVal],
referencedSegment
@ -783,7 +783,7 @@ export const angledLineOfXLength: SketchLineHelper = {
createArrayExpression([angle, xLength]),
createPipeSubstitution(),
])
const callIndex = getLastIndex(pathToNode)
const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
if (replaceExisting) {
pipe.body[callIndex] = newLine
} else {
@ -796,7 +796,7 @@ export const angledLineOfXLength: SketchLineHelper = {
},
updateArgs: ({ node, pathToNode, to, from }) => {
const _node = { ...node }
const { node: callExpression, path } = getNodeFromPath<CallExpression>(
const { node: callExpression } = getNodeFromPath<CallExpression>(
_node,
pathToNode
)
@ -877,7 +877,7 @@ export const angledLineOfYLength: SketchLineHelper = {
createArrayExpression([angle, yLength]),
createPipeSubstitution(),
])
const callIndex = getLastIndex(pathToNode)
const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
if (replaceExisting) {
pipe.body[callIndex] = newLine
} else {
@ -890,7 +890,7 @@ export const angledLineOfYLength: SketchLineHelper = {
},
updateArgs: ({ node, pathToNode, to, from }) => {
const _node = { ...node }
const { node: callExpression, path } = getNodeFromPath<CallExpression>(
const { node: callExpression } = getNodeFromPath<CallExpression>(
_node,
pathToNode
)
@ -957,7 +957,7 @@ export const angledLineToX: SketchLineHelper = {
const xArg = createLiteral(roundOff(to[0], 2))
if (replaceExisting && createCallback) {
const { callExp, valueUsedInTransform } = createCallback([angle, xArg])
const callIndex = getLastIndex(pathToNode)
const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
pipe.body[callIndex] = callExp
return {
modifiedAst: _node,
@ -978,7 +978,7 @@ export const angledLineToX: SketchLineHelper = {
},
updateArgs: ({ node, pathToNode, to, from }) => {
const _node = { ...node }
const { node: callExpression, path } = getNodeFromPath<CallExpression>(
const { node: callExpression } = getNodeFromPath<CallExpression>(
_node,
pathToNode
)
@ -1043,7 +1043,7 @@ export const angledLineToY: SketchLineHelper = {
if (replaceExisting && createCallback) {
const { callExp, valueUsedInTransform } = createCallback([angle, yArg])
const callIndex = getLastIndex(pathToNode)
const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
pipe.body[callIndex] = callExp
return {
modifiedAst: _node,
@ -1064,7 +1064,7 @@ export const angledLineToY: SketchLineHelper = {
},
updateArgs: ({ node, pathToNode, to, from }) => {
const _node = { ...node }
const { node: callExpression, path } = getNodeFromPath<CallExpression>(
const { node: callExpression } = getNodeFromPath<CallExpression>(
_node,
pathToNode
)
@ -1152,7 +1152,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
if (replaceExisting && createCallback) {
const { callExp, valueUsedInTransform } = createCallback([angle, offset])
const callIndex = getLastIndex(pathToNode)
const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
pipe.body[callIndex] = callExp
return {
modifiedAst: _node,
@ -1164,7 +1164,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
},
updateArgs: ({ node, pathToNode, to, from, previousProgramMemory }) => {
const _node = { ...node }
const { node: callExpression, path } = getNodeFromPath<CallExpression>(
const { node: callExpression } = getNodeFromPath<CallExpression>(
_node,
pathToNode
)
@ -1234,7 +1234,7 @@ export function changeSketchArguments(
): { modifiedAst: Program } {
const _node = { ...node }
const thePath = getNodePathFromSourceRange(_node, sourceRange)
const { node: callExpression, path } = getNodeFromPath<CallExpression>(
const { node: callExpression, shallowPath } = getNodeFromPath<CallExpression>(
_node,
thePath
)
@ -1246,7 +1246,7 @@ export function changeSketchArguments(
return updateArgs({
node: _node,
previousProgramMemory: programMemory,
pathToNode: path,
pathToNode: shallowPath,
to: args,
from,
})
@ -1279,11 +1279,8 @@ export function addNewSketchLn({
pathToNode,
'VariableDeclarator'
)
const { node: pipeExp, path: pipePath } = getNodeFromPath<PipeExpression>(
node,
pathToNode,
'PipeExpression'
)
const { node: pipeExp, shallowPath: pipePath } =
getNodeFromPath<PipeExpression>(node, pathToNode, 'PipeExpression')
const maybeStartSketchAt = pipeExp.body.find(
(exp) =>
exp.type === 'CallExpression' &&
@ -1298,7 +1295,11 @@ export function addNewSketchLn({
exp.arguments[0].type === 'Literal' &&
exp.arguments[0].value === 'default'
)
const defaultLinePath = [...pipePath, 'body', maybeDefaultLine]
const defaultLinePath: PathToNode = [
...pipePath,
['body', ''],
[maybeDefaultLine, ''],
]
const variableName = varDec.id.name
const sketch = previousProgramMemory?.root?.[variableName]
if (sketch.type !== 'sketchGroup') throw new Error('not a sketchGroup')

View File

@ -1020,7 +1020,7 @@ export function getRemoveConstraintsTransform(
return false
}
export function getTransformMapPath(
function getTransformMapPath(
sketchFnExp: CallExpression,
constraintType: ConstraintType
):
@ -1137,7 +1137,8 @@ export function getTransformInfos(
getNodePathFromSourceRange(ast, selectionRange)
)
const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(ast, pathToNode).node
(pathToNode) =>
getNodeFromPath<Value>(ast, pathToNode, 'CallExpression').node
)
const theTransforms = nodes.map((node) => {
@ -1338,7 +1339,8 @@ export function getConstraintLevelFromSourceRange(
): 'free' | 'partial' | 'full' {
const { node: sketchFnExp } = getNodeFromPath<CallExpression>(
ast,
getNodePathFromSourceRange(ast, cursorRange)
getNodePathFromSourceRange(ast, cursorRange),
'CallExpression'
)
const name = sketchFnExp?.callee?.name as TooTip
if (!toolTips.includes(name)) return 'free'

View File

@ -1,6 +1,7 @@
import { ProgramMemory, Path, SourceRange } from '../executor'
import { Program, Value } from '../abstractSyntaxTree'
import { TooTip } from '../../useStore'
import { PathToNode } from '../executor'
export interface InternalFirstArg {
programMemory: ProgramMemory
@ -53,7 +54,7 @@ export type InternalFnNames =
export interface ModifyAstBase {
node: Program
previousProgramMemory: ProgramMemory
pathToNode: (string | number)[]
pathToNode: PathToNode
}
interface addCall extends ModifyAstBase {
@ -85,12 +86,12 @@ export interface SketchLineHelper {
fn: InternalFn
add: (a: addCall) => {
modifiedAst: Program
pathToNode: (string | number)[]
pathToNode: PathToNode
valueUsedInTransform?: number
}
updateArgs: (a: updateArgs) => {
modifiedAst: Program
pathToNode: (string | number)[]
pathToNode: PathToNode
}
addTag: (a: ModifyAstBase) => {
modifiedAst: Program