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

View File

@ -188,12 +188,14 @@ export const CreateNewVariable = ({
setNewVariableName, setNewVariableName,
shouldCreateVariable, shouldCreateVariable,
setShouldCreateVariable, setShouldCreateVariable,
showCheckbox = true,
}: { }: {
isNewVariableNameUnique: boolean isNewVariableNameUnique: boolean
newVariableName: string newVariableName: string
setNewVariableName: (a: string) => void setNewVariableName: (a: string) => void
shouldCreateVariable: boolean shouldCreateVariable: boolean
setShouldCreateVariable: (a: boolean) => void setShouldCreateVariable: (a: boolean) => void
showCheckbox?: boolean
}) => { }) => {
return ( return (
<> <>
@ -204,6 +206,7 @@ export const CreateNewVariable = ({
Create new variable Create new variable
</label> </label>
<div className="mt-1 flex flex-1"> <div className="mt-1 flex flex-1">
{showCheckbox && (
<input <input
type="checkbox" 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" 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) setShouldCreateVariable(e.target.checked)
}} }}
/> />
)}
<input <input
type="text" type="text"
disabled={!shouldCreateVariable} 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, programMemory: s.programMemory,
}) })
) )
const [enableHorz, setEnableHorz] = useState(false) const [enableAngLen, setEnableAngLen] = useState(false)
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>() const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
useEffect(() => { useEffect(() => {
if (!ast) return if (!ast) return
@ -42,7 +42,8 @@ export const SetAngleLength = ({
getNodePathFromSourceRange(ast, selectionRange) getNodePathFromSourceRange(ast, selectionRange)
) )
const nodes = paths.map( const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(ast, pathToNode).node (pathToNode) =>
getNodeFromPath<Value>(ast, pathToNode, 'CallExpression').node
) )
const isAllTooltips = nodes.every( const isAllTooltips = nodes.every(
(node) => (node) =>
@ -54,7 +55,7 @@ export const SetAngleLength = ({
setTransformInfos(theTransforms) setTransformInfos(theTransforms)
const _enableHorz = isAllTooltips && theTransforms.every(Boolean) const _enableHorz = isAllTooltips && theTransforms.every(Boolean)
setEnableHorz(_enableHorz) setEnableAngLen(_enableHorz)
}, [guiMode, selectionRanges]) }, [guiMode, selectionRanges])
if (guiMode.mode !== 'sketch') return null if (guiMode.mode !== 'sketch') return null
@ -102,9 +103,9 @@ export const SetAngleLength = ({
} }
}} }}
className={`border m-1 px-1 rounded text-xs ${ 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} {angleOrLength}
</button> </button>

View File

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

View File

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

View File

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

View File

@ -11,10 +11,12 @@ import {
findUniqueName, findUniqueName,
addSketchTo, addSketchTo,
giveSketchFnCallTag, giveSketchFnCallTag,
moveValueIntoNewVariable,
} from './modifyAst' } from './modifyAst'
import { recast } from './recast' import { recast } from './recast'
import { lexer } from './tokeniser' import { lexer } from './tokeniser'
import { initPromise } from './rust' import { initPromise } from './rust'
import { executor } from './executor'
beforeAll(() => initPromise) beforeAll(() => initPromise)
@ -173,3 +175,93 @@ show(part001)`
expect(isTagExisting).toBe(true) 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, UnaryExpression,
BinaryExpression, BinaryExpression,
} from './abstractSyntaxTree' } from './abstractSyntaxTree'
import { getNodeFromPath, getNodePathFromSourceRange } from './queryAst' import {
findAllPreviousVariables,
getNodeFromPath,
getNodePathFromSourceRange,
isNodeSafeToReplace,
} from './queryAst'
import { PathToNode, ProgramMemory } from './executor' import { PathToNode, ProgramMemory } from './executor'
import { import {
addTagForSketchOnFace, addTagForSketchOnFace,
@ -27,7 +32,7 @@ export function addSketchTo(
node: Program, node: Program,
axis: 'xy' | 'xz' | 'yz', axis: 'xy' | 'xz' | 'yz',
name = '' name = ''
): { modifiedAst: Program; id: string; pathToNode: (string | number)[] } { ): { modifiedAst: Program; id: string; pathToNode: PathToNode } {
const _node = { ...node } const _node = { ...node }
const _name = name || findUniqueName(node, 'part') const _name = name || findUniqueName(node, 'part')
@ -63,15 +68,15 @@ export function addSketchTo(
newBody.splice(showCallIndex, 0, variableDeclaration) newBody.splice(showCallIndex, 0, variableDeclaration)
_node.body = newBody _node.body = newBody
} }
let pathToNode: (string | number)[] = [ let pathToNode: PathToNode = [
'body', ['body', ''],
sketchIndex, [sketchIndex, 'index'],
'declarations', ['declarations', 'VariableDeclaration'],
'0', ['0', 'index'],
'init', ['init', 'VariableDeclarator'],
] ]
if (axis !== 'xy') { if (axis !== 'xy') {
pathToNode = [...pathToNode, 'body', '0'] pathToNode = [...pathToNode, ['body', ''], ['0', 'index']]
} }
return { return {
@ -194,7 +199,7 @@ export function mutateObjExpProp(
export function extrudeSketch( export function extrudeSketch(
node: Program, node: Program,
pathToNode: (string | number)[], pathToNode: PathToNode,
shouldPipe = true shouldPipe = true
): { ): {
modifiedAst: Program modifiedAst: Program
@ -217,7 +222,7 @@ export function extrudeSketch(
) )
const isInPipeExpression = pipeExpression.type === 'PipeExpression' const isInPipeExpression = pipeExpression.type === 'PipeExpression'
const { node: variableDeclorator, path: pathToDecleration } = const { node: variableDeclorator, shallowPath: pathToDecleration } =
getNodeFromPath<VariableDeclarator>(_node, pathToNode, 'VariableDeclarator') getNodeFromPath<VariableDeclarator>(_node, pathToNode, 'VariableDeclarator')
const extrudeCall = createCallExpression('extrude', [ const extrudeCall = createCallExpression('extrude', [
@ -239,13 +244,13 @@ export function extrudeSketch(
) )
variableDeclorator.init = pipeChain variableDeclorator.init = pipeChain
const pathToExtrudeArg = [ const pathToExtrudeArg: PathToNode = [
...pathToDecleration, ...pathToDecleration,
'init', ['init', 'VariableDeclarator'],
'body', ['body', ''],
pipeChain.body.length - 1, [pipeChain.body.length - 1, 'index'],
'arguments', ['arguments', 'CallExpression'],
0, [0, 'index'],
] ]
return { return {
@ -258,30 +263,30 @@ export function extrudeSketch(
const VariableDeclaration = createVariableDeclaration(name, extrudeCall) const VariableDeclaration = createVariableDeclaration(name, extrudeCall)
const showCallIndex = getShowIndex(_node) const showCallIndex = getShowIndex(_node)
_node.body.splice(showCallIndex, 0, VariableDeclaration) _node.body.splice(showCallIndex, 0, VariableDeclaration)
const pathToExtrudeArg = [ const pathToExtrudeArg: PathToNode = [
'body', ['body', ''],
showCallIndex, [showCallIndex, 'index'],
'declarations', ['declarations', 'VariableDeclaration'],
0, [0, 'index'],
'init', ['init', 'VariableDeclarator'],
'arguments', ['arguments', 'CallExpression'],
0, [0, 'index'],
] ]
return { return {
modifiedAst: addToShow(_node, name), modifiedAst: addToShow(_node, name),
pathToNode: [...pathToNode.slice(0, -1), showCallIndex], pathToNode: [...pathToNode.slice(0, -1), [showCallIndex, 'index']],
pathToExtrudeArg, pathToExtrudeArg,
} }
} }
export function sketchOnExtrudedFace( export function sketchOnExtrudedFace(
node: Program, node: Program,
pathToNode: (string | number)[], pathToNode: PathToNode,
programMemory: ProgramMemory programMemory: ProgramMemory
): { modifiedAst: Program; pathToNode: (string | number)[] } { ): { modifiedAst: Program; pathToNode: PathToNode } {
let _node = { ...node } let _node = { ...node }
const newSketchName = findUniqueName(node, 'part') const newSketchName = findUniqueName(node, 'part')
const { node: oldSketchNode, path: pathToOldSketch } = const { node: oldSketchNode, shallowPath: pathToOldSketch } =
getNodeFromPath<VariableDeclarator>( getNodeFromPath<VariableDeclarator>(
_node, _node,
pathToNode, pathToNode,
@ -330,7 +335,7 @@ export function sketchOnExtrudedFace(
return { return {
modifiedAst: addToShow(_node, newSketchName), 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 index: number
} { } {
const last = pathToNode[pathToNode.length - 1] const last = pathToNode[pathToNode.length - 1]
if (typeof last === 'number') { if (last && typeof last[0] === 'number') {
return { return {
path: pathToNode.slice(0, -1), path: pathToNode.slice(0, -1),
index: last, index: last[0],
} }
} else if (pathToNode.length === 0) { } else if (pathToNode.length === 0) {
return { return {
@ -356,6 +361,32 @@ export function splitPathAtLastIndex(pathToNode: PathToNode): {
return splitPathAtLastIndex(pathToNode.slice(0, -1)) 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 { export function createLiteral(value: string | number): Literal {
return { return {
type: 'Literal', type: 'Literal',
@ -499,7 +530,8 @@ export function giveSketchFnCallTag(
): { modifiedAst: Program; tag: string; isTagExisting: boolean } { ): { modifiedAst: Program; tag: string; isTagExisting: boolean } {
const { node: primaryCallExp } = getNodeFromPath<CallExpression>( const { node: primaryCallExp } = getNodeFromPath<CallExpression>(
ast, ast,
getNodePathFromSourceRange(ast, range) getNodePathFromSourceRange(ast, range),
'CallExpression'
) )
const firstArg = getFirstArg(primaryCallExp) const firstArg = getFirstArg(primaryCallExp)
const isTagExisting = !!firstArg.tag const isTagExisting = !!firstArg.tag
@ -518,3 +550,29 @@ export function giveSketchFnCallTag(
isTagExisting, 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 { abstractSyntaxTree } from './abstractSyntaxTree'
import { findAllPreviousVariables } from './queryAst' import {
findAllPreviousVariables,
isNodeSafeToReplace,
isTypeInValue,
getNodePathFromSourceRange,
} from './queryAst'
import { lexer } from './tokeniser' import { lexer } from './tokeniser'
import { initPromise } from './rust' import { initPromise } from './rust'
import { executor } from './executor' import { executor } from './executor'
import {
createArrayExpression,
createCallExpression,
createLiteral,
createPipeSubstitution,
} from './modifyAst'
import { recast } from './recast'
beforeAll(() => initPromise) beforeAll(() => initPromise)
@ -43,6 +55,194 @@ show(part001)`
// there are 4 number variables and 2 non-number variables before the sketch var // there are 4 number variables and 2 non-number variables before the sketch var
// ∴ the insert index should be 6 // ∴ the insert index should be 6
expect(insertIndex).toEqual(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 { PathToNode, ProgramMemory } from './executor'
import { Range } from '../useStore' import { Range } from '../useStore'
import { Program } from './abstractSyntaxTree' import {
import { splitPathAtLastIndex } from './modifyAst' BinaryExpression,
Program,
SyntaxType,
Value,
CallExpression,
ExpressionStatement,
VariableDeclaration,
ReturnStatement,
ArrayExpression,
Identifier,
} from './abstractSyntaxTree'
import { createIdentifier, splitPathAtLastIndex } from './modifyAst'
export function getNodeFromPath<T>( export function getNodeFromPath<T>(
node: Program, node: Program,
path: (string | number)[], path: PathToNode,
stopAt: string = '', stopAt: string | string[] = '',
returnEarly = false returnEarly = false
): { ): {
node: T node: T
path: PathToNode shallowPath: PathToNode
deepPath: PathToNode
} { } {
let currentNode = node as any let currentNode = node as any
let stopAtNode = null let stopAtNode = null
@ -18,21 +30,26 @@ export function getNodeFromPath<T>(
let pathsExplored: PathToNode = [] let pathsExplored: PathToNode = []
for (const pathItem of path) { for (const pathItem of path) {
try { try {
if (typeof currentNode[pathItem] !== 'object') if (typeof currentNode[pathItem[0]] !== 'object')
throw new Error('not an object') throw new Error('not an object')
currentNode = currentNode[pathItem] currentNode = currentNode?.[pathItem[0]]
successfulPaths.push(pathItem) successfulPaths.push(pathItem)
if (!stopAtNode) { if (!stopAtNode) {
pathsExplored.push(pathItem) 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 // it will match the deepest node of the type
// instead of returning at the first match // instead of returning at the first match
stopAtNode = currentNode stopAtNode = currentNode
if (returnEarly) { if (returnEarly) {
return { return {
node: stopAtNode, node: stopAtNode,
path: pathsExplored, shallowPath: pathsExplored,
deepPath: successfulPaths,
} }
} }
} }
@ -48,13 +65,14 @@ export function getNodeFromPath<T>(
} }
return { return {
node: stopAtNode || currentNode, node: stopAtNode || currentNode,
path: pathsExplored, shallowPath: pathsExplored,
deepPath: successfulPaths,
} }
} }
export function getNodeFromPathCurry( export function getNodeFromPathCurry(
node: Program, node: Program,
path: (string | number)[] path: PathToNode
): <T>( ): <T>(
stopAt: string, stopAt: string,
returnEarly?: boolean returnEarly?: boolean
@ -63,18 +81,171 @@ export function getNodeFromPathCurry(
path: PathToNode path: PathToNode
} { } {
return <T>(stopAt: string = '', returnEarly = false) => { 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( export function getNodePathFromSourceRange(
node: Program, node: Program,
sourceRange: Range, sourceRange: Range,
previousPath: PathToNode = [] previousPath: PathToNode = [['body', '']]
): PathToNode { ): PathToNode {
const [start, end] = sourceRange const [start, end] = sourceRange
let path: PathToNode = [...previousPath, 'body'] let path: PathToNode = [...previousPath]
const _node = { ...node } const _node = { ...node }
// loop over each statement in body getting the index with a for loop // loop over each statement in body getting the index with a for loop
for ( for (
let statementIndex = 0; let statementIndex = 0;
@ -83,53 +254,8 @@ export function getNodePathFromSourceRange(
) { ) {
const statement = _node.body[statementIndex] const statement = _node.body[statementIndex]
if (statement.start <= start && statement.end >= end) { if (statement.start <= start && statement.end >= end) {
path.push(statementIndex) path.push([statementIndex, 'index'])
if (statement.type === 'ExpressionStatement') { return moreNodePathFromSourceRange(statement, sourceRange, path)
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') {
}
}
}
}
}
}
}
} }
} }
return path return path
@ -151,7 +277,11 @@ export function findAllPreviousVariables(
insertIndex: number insertIndex: number
} { } {
const path = getNodePathFromSourceRange(ast, sourceRange) 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 { index: insertIndex, path: bodyPath } = splitPathAtLastIndex(pathToDec)
const { node: bodyItems } = getNodeFromPath<Program['body']>(ast, bodyPath) const { node: bodyItems } = getNodeFromPath<Program['body']>(ast, bodyPath)
@ -174,3 +304,98 @@ export function findAllPreviousVariables(
variables, 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', sketchMode: 'sketchEdit',
rotation: [0, 0, 0, 1], rotation: [0, 0, 0, 1],
position: [0, 0, 0], position: [0, 0, 0],
pathToNode: ['body', 0, 'declarations', '0', 'init'], pathToNode: [
['body', ''],
[0, 'index'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', 'VariableDeclarator'],
],
}, },
[0, 0] [0, 0]
) )
@ -144,7 +150,13 @@ show(mySketch001)`
programMemory, programMemory,
to: [2, 3], to: [2, 3],
fnName: 'lineTo', fnName: 'lineTo',
pathToNode: ['body', 0, 'declarations', '0', 'init'], pathToNode: [
['body', ''],
[0, 'index'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', 'VariableDeclarator'],
],
}) })
const expectedCode = ` const expectedCode = `
const mySketch001 = startSketchAt([0, 0]) const mySketch001 = startSketchAt([0, 0])

View File

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

View File

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

View File

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