Add equal-length constraints & implement UnaryExpressions (#35)
* add segLen help to lang std * adding helpers functions to sketchConstraints * update tokeniser tests because they were annoying me not being 100% * compare async lexer with sync lexer instead * add helper functions * remove unneeded nesting * update add ast modifier function for angledLine * initial equal ast modification It adds a tag to the primary line, and converts any secondary lines to angledLine, but doesn't reference the taged/primary line yet * Update fn call with refernce to previous line using segLen * add test for giveSketchFnCallTag * fix excutor bug, executing call expression in array expression * fix small issue in executor * add CallExpressions to BinaryExpressions * add unary Expressions * tweaks to unaryExpression logic * add recasting for unaryExpressions and CallExpressions in BinaryExpressions * ensure pipe substitution info is passed down to unary expressions and others * allow binary expressions in function argumentns * inital setup, new way of organising sketch fn transforms Starting with equal length * overhaul equalLength button * add equal length support for angledLine * line with one variable supports signed legLength * fix indentation when recasting long arrayExpressions in a pipeExpression * improve modifyAst consision * further modify ast tidy * equalLength transfroms far angledLineOfXLength * add transforms for line-yRelative * add equal constraint for angledLineOfYLength * quick test fix * add equal length constrain transforms for lineTo * add equal length constraints for angledLineToX * add equalLength constraints for angledLineToY * test tidy * setup new vertical-horizontal constraints * Add equal Length constraints for vertical/horizontal lines * migrate old tests, and refactor callback tag * tweaks and refactor horzVert component * fix leg len with small negative leg length
This commit is contained in:
@ -94,7 +94,6 @@ function App() {
|
|||||||
}
|
}
|
||||||
const tokens = await asyncLexer(code)
|
const tokens = await asyncLexer(code)
|
||||||
const _ast = abstractSyntaxTree(tokens)
|
const _ast = abstractSyntaxTree(tokens)
|
||||||
console.log('setting ast')
|
|
||||||
setAst(_ast)
|
setAst(_ast)
|
||||||
resetLogs()
|
resetLogs()
|
||||||
const programMemory = executor(_ast, {
|
const programMemory = executor(_ast, {
|
||||||
|
@ -2,6 +2,7 @@ import { useStore, toolTips } from './useStore'
|
|||||||
import { extrudeSketch, sketchOnExtrudedFace } from './lang/modifyAst'
|
import { extrudeSketch, sketchOnExtrudedFace } from './lang/modifyAst'
|
||||||
import { getNodePathFromSourceRange } from './lang/abstractSyntaxTree'
|
import { getNodePathFromSourceRange } from './lang/abstractSyntaxTree'
|
||||||
import { HorzVert } from './components/Toolbar/HorzVert'
|
import { HorzVert } from './components/Toolbar/HorzVert'
|
||||||
|
import { Equal } from './components/Toolbar/Equal'
|
||||||
|
|
||||||
export const Toolbar = () => {
|
export const Toolbar = () => {
|
||||||
const {
|
const {
|
||||||
@ -152,7 +153,9 @@ export const Toolbar = () => {
|
|||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
<br></br>
|
<br></br>
|
||||||
<HorzVert />
|
<HorzVert horOrVert="horizontal" />
|
||||||
|
<HorzVert horOrVert="vertical" />
|
||||||
|
<Equal />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
94
src/components/Toolbar/Equal.tsx
Normal file
94
src/components/Toolbar/Equal.tsx
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
import { useState, useEffect } from 'react'
|
||||||
|
import { toolTips, useStore } from '../../useStore'
|
||||||
|
import {
|
||||||
|
getNodePathFromSourceRange,
|
||||||
|
getNodeFromPath,
|
||||||
|
Value,
|
||||||
|
VariableDeclarator,
|
||||||
|
} from '../../lang/abstractSyntaxTree'
|
||||||
|
import { isSketchVariablesLinked } from '../../lang/std/sketchConstraints'
|
||||||
|
import {
|
||||||
|
TransformInfo,
|
||||||
|
transformAstForSketchLines,
|
||||||
|
getTransformInfos,
|
||||||
|
} from '../../lang/std/sketchcombos'
|
||||||
|
|
||||||
|
export const Equal = () => {
|
||||||
|
const { guiMode, selectionRanges, ast, programMemory, updateAst } = useStore(
|
||||||
|
(s) => ({
|
||||||
|
guiMode: s.guiMode,
|
||||||
|
ast: s.ast,
|
||||||
|
updateAst: s.updateAst,
|
||||||
|
selectionRanges: s.selectionRanges,
|
||||||
|
programMemory: s.programMemory,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
const [enableEqual, setEnableEqual] = useState(false)
|
||||||
|
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
|
||||||
|
useEffect(() => {
|
||||||
|
if (!ast) return
|
||||||
|
const paths = selectionRanges.map((selectionRange) =>
|
||||||
|
getNodePathFromSourceRange(ast, selectionRange)
|
||||||
|
)
|
||||||
|
const nodes = paths.map(
|
||||||
|
(pathToNode) => getNodeFromPath<Value>(ast, pathToNode).node
|
||||||
|
)
|
||||||
|
const varDecs = paths.map(
|
||||||
|
(pathToNode) =>
|
||||||
|
getNodeFromPath<VariableDeclarator>(
|
||||||
|
ast,
|
||||||
|
pathToNode,
|
||||||
|
'VariableDeclarator'
|
||||||
|
)?.node
|
||||||
|
)
|
||||||
|
const primaryLine = varDecs[0]
|
||||||
|
const secondaryVarDecs = varDecs.slice(1)
|
||||||
|
const isOthersLinkedToPrimary = secondaryVarDecs.every((secondary) =>
|
||||||
|
isSketchVariablesLinked(secondary, primaryLine, ast)
|
||||||
|
)
|
||||||
|
const isAllTooltips = nodes.every(
|
||||||
|
(node) =>
|
||||||
|
node?.type === 'CallExpression' &&
|
||||||
|
toolTips.includes(node.callee.name as any)
|
||||||
|
)
|
||||||
|
|
||||||
|
const theTransforms = getTransformInfos(
|
||||||
|
selectionRanges.slice(1),
|
||||||
|
ast,
|
||||||
|
'equalLength'
|
||||||
|
)
|
||||||
|
setTransformInfos(theTransforms)
|
||||||
|
|
||||||
|
const _enableEqual =
|
||||||
|
!!secondaryVarDecs.length &&
|
||||||
|
isAllTooltips &&
|
||||||
|
isOthersLinkedToPrimary &&
|
||||||
|
theTransforms.every(Boolean)
|
||||||
|
setEnableEqual(_enableEqual)
|
||||||
|
}, [guiMode, selectionRanges])
|
||||||
|
if (guiMode.mode !== 'sketch') return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={() =>
|
||||||
|
transformInfos &&
|
||||||
|
ast &&
|
||||||
|
updateAst(
|
||||||
|
transformAstForSketchLines({
|
||||||
|
ast,
|
||||||
|
selectionRanges,
|
||||||
|
transformInfos,
|
||||||
|
programMemory,
|
||||||
|
})?.modifiedAst
|
||||||
|
)
|
||||||
|
}
|
||||||
|
className={`border m-1 px-1 rounded ${
|
||||||
|
enableEqual ? 'bg-gray-50 text-gray-800' : 'bg-gray-200 text-gray-400'
|
||||||
|
}`}
|
||||||
|
disabled={!enableEqual}
|
||||||
|
title="yo dawg"
|
||||||
|
>
|
||||||
|
Equal
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
@ -1,16 +1,21 @@
|
|||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { TooTip, useStore } from '../../useStore'
|
import { toolTips, useStore } from '../../useStore'
|
||||||
import {
|
import {
|
||||||
getNodePathFromSourceRange,
|
getNodePathFromSourceRange,
|
||||||
getNodeFromPath,
|
getNodeFromPath,
|
||||||
Value,
|
Value,
|
||||||
CallExpression,
|
|
||||||
} from '../../lang/abstractSyntaxTree'
|
} from '../../lang/abstractSyntaxTree'
|
||||||
import { toolTips } from '../../useStore'
|
import {
|
||||||
import { allowedTransforms } from '../../lang/std/sketch'
|
TransformInfo,
|
||||||
import { swapSketchHelper } from '../../lang/std/sketchConstraints'
|
getTransformInfos,
|
||||||
|
transformAstForHorzVert,
|
||||||
|
} from '../../lang/std/sketchcombos'
|
||||||
|
|
||||||
export const HorzVert = () => {
|
export const HorzVert = ({
|
||||||
|
horOrVert,
|
||||||
|
}: {
|
||||||
|
horOrVert: 'vertical' | 'horizontal'
|
||||||
|
}) => {
|
||||||
const { guiMode, selectionRanges, ast, programMemory, updateAst } = useStore(
|
const { guiMode, selectionRanges, ast, programMemory, updateAst } = useStore(
|
||||||
(s) => ({
|
(s) => ({
|
||||||
guiMode: s.guiMode,
|
guiMode: s.guiMode,
|
||||||
@ -21,116 +26,50 @@ export const HorzVert = () => {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
const [enableHorz, setEnableHorz] = useState(false)
|
const [enableHorz, setEnableHorz] = useState(false)
|
||||||
const [enableVert, setEnableVert] = useState(false)
|
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
|
||||||
const [allowedTransformsMap, setAllowedTransformsMap] = useState<
|
|
||||||
ReturnType<typeof allowedTransforms>[]
|
|
||||||
>([])
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ast) return
|
if (!ast) return
|
||||||
const islineFn = (expression: Value): boolean => {
|
|
||||||
if (expression?.type !== 'CallExpression') return false
|
|
||||||
if (!toolTips.includes(expression.callee.name as any)) return false
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
const paths = selectionRanges.map((selectionRange) =>
|
const paths = selectionRanges.map((selectionRange) =>
|
||||||
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).node
|
||||||
)
|
)
|
||||||
const allowedSwaps = paths.map((a) =>
|
const isAllTooltips = nodes.every(
|
||||||
allowedTransforms({
|
(node) =>
|
||||||
node: ast,
|
node?.type === 'CallExpression' &&
|
||||||
pathToNode: a,
|
toolTips.includes(node.callee.name as any)
|
||||||
previousProgramMemory: programMemory,
|
|
||||||
})
|
|
||||||
)
|
)
|
||||||
setAllowedTransformsMap(allowedSwaps)
|
|
||||||
const allowedSwapsnames = allowedSwaps.map((a) =>
|
const theTransforms = getTransformInfos(selectionRanges, ast, horOrVert)
|
||||||
Object.keys(a)
|
setTransformInfos(theTransforms)
|
||||||
) as TooTip[][]
|
|
||||||
const horzAllowed = includedInAll(allowedSwapsnames, ['xLine', 'xLineTo'])
|
const _enableHorz = isAllTooltips && theTransforms.every(Boolean)
|
||||||
const vertAllowed = includedInAll(allowedSwapsnames, ['yLine', 'yLineTo'])
|
setEnableHorz(_enableHorz)
|
||||||
const isCursorsInLineFns = nodes.every(islineFn)
|
}, [guiMode, selectionRanges])
|
||||||
const _enableHorz =
|
|
||||||
isCursorsInLineFns &&
|
|
||||||
horzAllowed &&
|
|
||||||
guiMode.mode === 'sketch' &&
|
|
||||||
guiMode.sketchMode === 'sketchEdit'
|
|
||||||
if (enableHorz !== _enableHorz) setEnableHorz(_enableHorz)
|
|
||||||
const _enableVert =
|
|
||||||
isCursorsInLineFns &&
|
|
||||||
vertAllowed &&
|
|
||||||
guiMode.mode === 'sketch' &&
|
|
||||||
guiMode.sketchMode === 'sketchEdit'
|
|
||||||
if (enableVert !== _enableVert) setEnableVert(_enableVert)
|
|
||||||
}, [guiMode, selectionRanges, guiMode])
|
|
||||||
if (guiMode.mode !== 'sketch') return null
|
if (guiMode.mode !== 'sketch') return null
|
||||||
|
|
||||||
const onClick = (vertOrHor: 'vert' | 'horz') => () => {
|
|
||||||
if (ast) {
|
|
||||||
// deep clone since we are mutating in a loop, of which any could fail
|
|
||||||
let node = JSON.parse(JSON.stringify(ast))
|
|
||||||
selectionRanges.forEach((range, index) => {
|
|
||||||
const { node: callExpression } = getNodeFromPath<CallExpression>(
|
|
||||||
node,
|
|
||||||
getNodePathFromSourceRange(node, range)
|
|
||||||
)
|
|
||||||
const [relLine, absLine]: [TooTip, TooTip] =
|
|
||||||
vertOrHor === 'vert' ? ['yLine', 'yLineTo'] : ['xLine', 'xLineTo']
|
|
||||||
const finalLine = [
|
|
||||||
'line',
|
|
||||||
'angledLine',
|
|
||||||
'angledLineOfXLength',
|
|
||||||
'angledLineOfYLength',
|
|
||||||
].includes(callExpression.callee.name)
|
|
||||||
? relLine
|
|
||||||
: absLine
|
|
||||||
const createCallBackHelper = allowedTransformsMap[index][finalLine]
|
|
||||||
if (!createCallBackHelper) throw new Error('no callback helper')
|
|
||||||
const { modifiedAst } = swapSketchHelper(
|
|
||||||
programMemory,
|
|
||||||
node,
|
|
||||||
range,
|
|
||||||
finalLine,
|
|
||||||
createCallBackHelper
|
|
||||||
)
|
|
||||||
node = modifiedAst
|
|
||||||
})
|
|
||||||
updateAst(node)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
|
||||||
<button
|
<button
|
||||||
onClick={onClick('horz')}
|
onClick={() =>
|
||||||
|
transformInfos &&
|
||||||
|
ast &&
|
||||||
|
updateAst(
|
||||||
|
transformAstForHorzVert({
|
||||||
|
ast,
|
||||||
|
selectionRanges,
|
||||||
|
transformInfos,
|
||||||
|
programMemory,
|
||||||
|
})?.modifiedAst
|
||||||
|
)
|
||||||
|
}
|
||||||
className={`border m-1 px-1 rounded ${
|
className={`border m-1 px-1 rounded ${
|
||||||
enableHorz ? 'bg-gray-50 text-gray-800' : 'bg-gray-200 text-gray-400'
|
enableHorz ? 'bg-gray-50 text-gray-800' : 'bg-gray-200 text-gray-400'
|
||||||
}`}
|
}`}
|
||||||
disabled={!enableHorz}
|
disabled={!enableHorz}
|
||||||
title="yo dawg"
|
title="yo dawg"
|
||||||
>
|
>
|
||||||
Horz
|
{horOrVert === 'horizontal' ? 'Horz' : 'Vert'}
|
||||||
</button>
|
</button>
|
||||||
<button
|
|
||||||
onClick={onClick('vert')}
|
|
||||||
className={`border m-1 px-1 rounded ${
|
|
||||||
enableVert ? 'bg-gray-50 text-gray-800' : 'bg-gray-200 text-gray-400'
|
|
||||||
}`}
|
|
||||||
disabled={!enableVert}
|
|
||||||
>
|
|
||||||
Vert
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function includedInAll(
|
|
||||||
allowedsOfEach: TooTip[][],
|
|
||||||
isIncludes: TooTip[]
|
|
||||||
): boolean {
|
|
||||||
return allowedsOfEach.every((alloweds) =>
|
|
||||||
isIncludes.some((isInclude) => alloweds.includes(isInclude))
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1742,13 +1742,13 @@ describe('testing findEndofBinaryExpression', () => {
|
|||||||
const code = `1 + 2 * 3\nconst yo = 5`
|
const code = `1 + 2 * 3\nconst yo = 5`
|
||||||
const tokens = lexer(code)
|
const tokens = lexer(code)
|
||||||
const end = findEndOfBinaryExpression(tokens, 0)
|
const end = findEndOfBinaryExpression(tokens, 0)
|
||||||
expect(end).toBe(8)
|
expect(tokens[end].value).toBe('3')
|
||||||
})
|
})
|
||||||
it('(1 + 2) / 5 - 3', () => {
|
it('(1 + 2) / 5 - 3', () => {
|
||||||
const code = `(1 + 25) / 5 - 3\nconst yo = 5`
|
const code = `(1 + 25) / 5 - 3\nconst yo = 5`
|
||||||
const tokens = lexer(code)
|
const tokens = lexer(code)
|
||||||
const end = findEndOfBinaryExpression(tokens, 0)
|
const end = findEndOfBinaryExpression(tokens, 0)
|
||||||
expect(end).toBe(14)
|
expect(tokens[end].value).toBe('3')
|
||||||
|
|
||||||
// expect to have the same end if started later in the string at a legitimate place
|
// expect to have the same end if started later in the string at a legitimate place
|
||||||
const indexOf5 = code.indexOf('5')
|
const indexOf5 = code.indexOf('5')
|
||||||
@ -1759,30 +1759,103 @@ describe('testing findEndofBinaryExpression', () => {
|
|||||||
const code = '((1 + 2) / 5 - 3)\nconst yo = 5'
|
const code = '((1 + 2) / 5 - 3)\nconst yo = 5'
|
||||||
const tokens = lexer(code)
|
const tokens = lexer(code)
|
||||||
const end = findEndOfBinaryExpression(tokens, 0)
|
const end = findEndOfBinaryExpression(tokens, 0)
|
||||||
expect(end).toBe(code.indexOf('3)') + 1)
|
expect(tokens[end].end).toBe(code.indexOf('3)') + 2)
|
||||||
})
|
})
|
||||||
it('whole thing wraped but given index after the first brace: ((1 + 2) / 5 - 3)', () => {
|
it('whole thing wraped but given index after the first brace: ((1 + 2) / 5 - 3)', () => {
|
||||||
const code = '((1 + 2) / 5 - 3)\nconst yo = 5'
|
const code = '((1 + 2) / 5 - 3)\nconst yo = 5'
|
||||||
const tokens = lexer(code)
|
const tokens = lexer(code)
|
||||||
const end = findEndOfBinaryExpression(tokens, 1)
|
const end = findEndOfBinaryExpression(tokens, 1)
|
||||||
expect(end).toBe(code.indexOf('3'))
|
expect(tokens[end].value).toBe('3')
|
||||||
})
|
})
|
||||||
it('given the index of a small wrapped section i.e. `1 + 2` in ((1 + 2) / 5 - 3)', () => {
|
it('given the index of a small wrapped section i.e. `1 + 2` in ((1 + 2) / 5 - 3)', () => {
|
||||||
const code = '((1 + 2) / 5 - 3)\nconst yo = 5'
|
const code = '((1 + 2) / 5 - 3)\nconst yo = 5'
|
||||||
const tokens = lexer(code)
|
const tokens = lexer(code)
|
||||||
const end = findEndOfBinaryExpression(tokens, 2)
|
const end = findEndOfBinaryExpression(tokens, 2)
|
||||||
expect(end).toBe(code.indexOf('2'))
|
expect(tokens[end].value).toBe('2')
|
||||||
})
|
})
|
||||||
it('lots of silly nesting: (1 + 2) / (5 - (3))', () => {
|
it('lots of silly nesting: (1 + 2) / (5 - (3))', () => {
|
||||||
const code = '(1 + 2) / (5 - (3))\nconst yo = 5'
|
const code = '(1 + 2) / (5 - (3))\nconst yo = 5'
|
||||||
const tokens = lexer(code)
|
const tokens = lexer(code)
|
||||||
const end = findEndOfBinaryExpression(tokens, 0)
|
const end = findEndOfBinaryExpression(tokens, 0)
|
||||||
expect(end).toBe(code.indexOf('))') + 1)
|
expect(tokens[end].end).toBe(code.indexOf('))') + 2)
|
||||||
})
|
})
|
||||||
it('with pipe operator at the end', () => {
|
it('with pipe operator at the end', () => {
|
||||||
const code = '(1 + 2) / (5 - (3))\n |> fn(%)'
|
const code = '(1 + 2) / (5 - (3))\n |> fn(%)'
|
||||||
const tokens = lexer(code)
|
const tokens = lexer(code)
|
||||||
const end = findEndOfBinaryExpression(tokens, 0)
|
const end = findEndOfBinaryExpression(tokens, 0)
|
||||||
expect(end).toBe(code.indexOf('))') + 1)
|
expect(tokens[end].end).toBe(code.indexOf('))') + 2)
|
||||||
|
})
|
||||||
|
it('with call expression at the start of binary expression', () => {
|
||||||
|
const code = 'yo(2) + 3\n |> fn(%)'
|
||||||
|
const tokens = lexer(code)
|
||||||
|
const end = findEndOfBinaryExpression(tokens, 0)
|
||||||
|
expect(tokens[end].value).toBe('3')
|
||||||
|
})
|
||||||
|
it('with call expression at the end of binary expression', () => {
|
||||||
|
const code = '3 + yo(2)\n |> fn(%)'
|
||||||
|
const tokens = lexer(code)
|
||||||
|
const end = findEndOfBinaryExpression(tokens, 0)
|
||||||
|
expect(tokens[end].value).toBe(')')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('test UnaryExpression', () => {
|
||||||
|
it('should parse a unary expression in simple var dec situation', () => {
|
||||||
|
const code = `const myVar = -min(4, 100)`
|
||||||
|
const { body } = abstractSyntaxTree(lexer(code))
|
||||||
|
const myVarInit = (body?.[0] as any).declarations[0]?.init
|
||||||
|
expect(myVarInit).toEqual({
|
||||||
|
type: 'UnaryExpression',
|
||||||
|
operator: '-',
|
||||||
|
start: 14,
|
||||||
|
end: 26,
|
||||||
|
argument: {
|
||||||
|
type: 'CallExpression',
|
||||||
|
start: 15,
|
||||||
|
end: 26,
|
||||||
|
callee: { type: 'Identifier', start: 15, end: 18, name: 'min' },
|
||||||
|
arguments: [
|
||||||
|
{ type: 'Literal', start: 19, end: 20, value: 4, raw: '4' },
|
||||||
|
{ type: 'Literal', start: 22, end: 25, value: 100, raw: '100' },
|
||||||
|
],
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('testing nested call expressions', () => {
|
||||||
|
it('callExp in a binExp in a callExp', () => {
|
||||||
|
const code = 'const myVar = min(100, 1 + legLen(5, 3))'
|
||||||
|
const { body } = abstractSyntaxTree(lexer(code))
|
||||||
|
const myVarInit = (body?.[0] as any).declarations[0]?.init
|
||||||
|
expect(myVarInit).toEqual({
|
||||||
|
type: 'CallExpression',
|
||||||
|
start: 14,
|
||||||
|
end: 40,
|
||||||
|
callee: { type: 'Identifier', start: 14, end: 17, name: 'min' },
|
||||||
|
arguments: [
|
||||||
|
{ type: 'Literal', start: 18, end: 21, value: 100, raw: '100' },
|
||||||
|
{
|
||||||
|
type: 'BinaryExpression',
|
||||||
|
operator: '+',
|
||||||
|
start: 23,
|
||||||
|
end: 39,
|
||||||
|
left: { type: 'Literal', value: 1, raw: '1', start: 23, end: 24 },
|
||||||
|
right: {
|
||||||
|
type: 'CallExpression',
|
||||||
|
start: 27,
|
||||||
|
end: 39,
|
||||||
|
callee: { type: 'Identifier', start: 27, end: 33, name: 'legLen' },
|
||||||
|
arguments: [
|
||||||
|
{ type: 'Literal', start: 34, end: 35, value: 5, raw: '5' },
|
||||||
|
{ type: 'Literal', start: 37, end: 38, value: 3, raw: '3' },
|
||||||
|
],
|
||||||
|
optional: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
optional: false,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { PathToNode } from './executor'
|
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 =
|
type syntaxType =
|
||||||
| 'Program'
|
| 'Program'
|
||||||
@ -21,13 +22,13 @@ type syntaxType =
|
|||||||
| 'PipeSubstitution'
|
| 'PipeSubstitution'
|
||||||
| 'Literal'
|
| 'Literal'
|
||||||
| 'NoneCodeNode'
|
| 'NoneCodeNode'
|
||||||
|
| 'UnaryExpression'
|
||||||
// | 'NumberLiteral'
|
// | 'NumberLiteral'
|
||||||
// | 'StringLiteral'
|
// | 'StringLiteral'
|
||||||
// | 'IfStatement'
|
// | 'IfStatement'
|
||||||
// | 'WhileStatement'
|
// | 'WhileStatement'
|
||||||
// | 'FunctionDeclaration'
|
// | 'FunctionDeclaration'
|
||||||
// | 'AssignmentExpression'
|
// | 'AssignmentExpression'
|
||||||
// | 'UnaryExpression'
|
|
||||||
// | 'Property'
|
// | 'Property'
|
||||||
// | 'LogicalExpression'
|
// | 'LogicalExpression'
|
||||||
// | 'ConditionalExpression'
|
// | 'ConditionalExpression'
|
||||||
@ -173,7 +174,7 @@ export interface CallExpression extends GeneralStatement {
|
|||||||
optional: boolean
|
optional: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeCallExpression(
|
export function makeCallExpression(
|
||||||
tokens: Token[],
|
tokens: Token[],
|
||||||
index: number
|
index: number
|
||||||
): {
|
): {
|
||||||
@ -239,6 +240,23 @@ function makeArguments(
|
|||||||
expression,
|
expression,
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
argumentToken.token.type === 'operator' &&
|
||||||
|
argumentToken.token.value === '-'
|
||||||
|
) {
|
||||||
|
const { expression, lastIndex } = makeUnaryExpression(
|
||||||
|
tokens,
|
||||||
|
argumentToken.index
|
||||||
|
)
|
||||||
|
const nextCommarOrBraceTokenIndex = nextMeaningfulToken(
|
||||||
|
tokens,
|
||||||
|
lastIndex
|
||||||
|
).index
|
||||||
|
return makeArguments(tokens, nextCommarOrBraceTokenIndex, [
|
||||||
|
...previousArgs,
|
||||||
|
expression,
|
||||||
|
])
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
argumentToken.token.type === 'brace' &&
|
argumentToken.token.type === 'brace' &&
|
||||||
argumentToken.token.value === '{'
|
argumentToken.token.value === '{'
|
||||||
@ -256,8 +274,31 @@ function makeArguments(
|
|||||||
expression,
|
expression,
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
(argumentToken.token.type === 'word' ||
|
||||||
|
argumentToken.token.type === 'number' ||
|
||||||
|
argumentToken.token.type === 'string') &&
|
||||||
|
nextBraceOrCommaToken.token.type === 'operator'
|
||||||
|
) {
|
||||||
|
const { expression, lastIndex } = makeBinaryExpression(
|
||||||
|
tokens,
|
||||||
|
argumentToken.index
|
||||||
|
)
|
||||||
|
const nextCommarOrBraceTokenIndex = nextMeaningfulToken(
|
||||||
|
tokens,
|
||||||
|
lastIndex
|
||||||
|
).index
|
||||||
|
return makeArguments(tokens, nextCommarOrBraceTokenIndex, [
|
||||||
|
...previousArgs,
|
||||||
|
expression,
|
||||||
|
])
|
||||||
|
}
|
||||||
if (!isIdentifierOrLiteral) {
|
if (!isIdentifierOrLiteral) {
|
||||||
const { expression, lastIndex } = makeBinaryExpression(tokens, index)
|
// I think this if statement might be dead code
|
||||||
|
const { expression, lastIndex } = makeBinaryExpression(
|
||||||
|
tokens,
|
||||||
|
nextBraceOrCommaToken.index
|
||||||
|
)
|
||||||
return makeArguments(tokens, lastIndex, [...previousArgs, expression])
|
return makeArguments(tokens, lastIndex, [...previousArgs, expression])
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
@ -362,15 +403,30 @@ export type Value =
|
|||||||
| ArrayExpression
|
| ArrayExpression
|
||||||
| ObjectExpression
|
| ObjectExpression
|
||||||
| MemberExpression
|
| MemberExpression
|
||||||
|
| UnaryExpression
|
||||||
|
|
||||||
function makeValue(
|
function makeValue(
|
||||||
tokens: Token[],
|
tokens: Token[],
|
||||||
index: number
|
index: number
|
||||||
): { value: Value; lastIndex: number } {
|
): { value: Value; lastIndex: number } {
|
||||||
const currentToken = tokens[index]
|
const currentToken = tokens[index]
|
||||||
const { token: nextToken } = nextMeaningfulToken(tokens, index)
|
const { token: nextToken, index: nextTokenIndex } = nextMeaningfulToken(
|
||||||
// nextToken might be empty if it's at the end of the file
|
tokens,
|
||||||
|
index
|
||||||
|
)
|
||||||
if (nextToken?.type === 'brace' && nextToken.value === '(') {
|
if (nextToken?.type === 'brace' && nextToken.value === '(') {
|
||||||
|
const endIndex = findClosingBrace(tokens, nextTokenIndex)
|
||||||
|
const tokenAfterCallExpression = nextMeaningfulToken(tokens, endIndex)
|
||||||
|
if (
|
||||||
|
tokenAfterCallExpression?.token?.type === 'operator' &&
|
||||||
|
tokenAfterCallExpression.token.value !== '|>'
|
||||||
|
) {
|
||||||
|
const { expression, lastIndex } = makeBinaryExpression(tokens, index)
|
||||||
|
return {
|
||||||
|
value: expression,
|
||||||
|
lastIndex,
|
||||||
|
}
|
||||||
|
}
|
||||||
const { expression, lastIndex } = makeCallExpression(tokens, index)
|
const { expression, lastIndex } = makeCallExpression(tokens, index)
|
||||||
return {
|
return {
|
||||||
value: expression,
|
value: expression,
|
||||||
@ -445,6 +501,10 @@ function makeValue(
|
|||||||
throw new Error('TODO - handle expression with braces')
|
throw new Error('TODO - handle expression with braces')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (currentToken.type === 'operator' && currentToken.value === '-') {
|
||||||
|
const { expression, lastIndex } = makeUnaryExpression(tokens, index)
|
||||||
|
return { value: expression, lastIndex }
|
||||||
|
}
|
||||||
throw new Error('Expected a previous Value if statement to match')
|
throw new Error('Expected a previous Value if statement to match')
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -501,12 +561,15 @@ function makeVariableDeclarators(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type BinaryPart = Literal | Identifier | BinaryExpression
|
export type BinaryPart =
|
||||||
// | CallExpression
|
| Literal
|
||||||
|
| Identifier
|
||||||
|
| BinaryExpression
|
||||||
|
| CallExpression
|
||||||
|
| UnaryExpression
|
||||||
// | MemberExpression
|
// | MemberExpression
|
||||||
// | ArrayExpression
|
// | ArrayExpression
|
||||||
// | ObjectExpression
|
// | ObjectExpression
|
||||||
// | UnaryExpression
|
|
||||||
// | LogicalExpression
|
// | LogicalExpression
|
||||||
// | ConditionalExpression
|
// | ConditionalExpression
|
||||||
|
|
||||||
@ -805,6 +868,22 @@ export function findEndOfBinaryExpression(
|
|||||||
const nextRight = nextMeaningfulToken(tokens, maybeAnotherOperator.index)
|
const nextRight = nextMeaningfulToken(tokens, maybeAnotherOperator.index)
|
||||||
return findEndOfBinaryExpression(tokens, nextRight.index)
|
return findEndOfBinaryExpression(tokens, nextRight.index)
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
currentToken.type === 'word' &&
|
||||||
|
tokens?.[index + 1]?.type === 'brace' &&
|
||||||
|
tokens[index + 1].value === '('
|
||||||
|
) {
|
||||||
|
const closingParenthesis = findClosingBrace(tokens, index + 1)
|
||||||
|
const maybeAnotherOperator = nextMeaningfulToken(tokens, closingParenthesis)
|
||||||
|
if (
|
||||||
|
maybeAnotherOperator?.token?.type !== 'operator' ||
|
||||||
|
maybeAnotherOperator?.token?.value === '|>'
|
||||||
|
) {
|
||||||
|
return closingParenthesis
|
||||||
|
}
|
||||||
|
const nextRight = nextMeaningfulToken(tokens, maybeAnotherOperator.index)
|
||||||
|
return findEndOfBinaryExpression(tokens, nextRight.index)
|
||||||
|
}
|
||||||
const maybeOperator = nextMeaningfulToken(tokens, index)
|
const maybeOperator = nextMeaningfulToken(tokens, index)
|
||||||
if (
|
if (
|
||||||
maybeOperator?.token?.type !== 'operator' ||
|
maybeOperator?.token?.type !== 'operator' ||
|
||||||
@ -828,6 +907,34 @@ function makeBinaryExpression(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UnaryExpression extends GeneralStatement {
|
||||||
|
type: 'UnaryExpression'
|
||||||
|
operator: '-' | '!'
|
||||||
|
argument: BinaryPart
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeUnaryExpression(
|
||||||
|
tokens: Token[],
|
||||||
|
index: number
|
||||||
|
): { expression: UnaryExpression; lastIndex: number } {
|
||||||
|
const currentToken = tokens[index]
|
||||||
|
const nextToken = nextMeaningfulToken(tokens, index)
|
||||||
|
const { value: argument, lastIndex: argumentLastIndex } = makeValue(
|
||||||
|
tokens,
|
||||||
|
nextToken.index
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
expression: {
|
||||||
|
type: 'UnaryExpression',
|
||||||
|
operator: currentToken.value === '!' ? '!' : '-',
|
||||||
|
start: currentToken.start,
|
||||||
|
end: tokens[argumentLastIndex].end,
|
||||||
|
argument: argument as BinaryPart,
|
||||||
|
},
|
||||||
|
lastIndex: argumentLastIndex,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export interface PipeExpression extends GeneralStatement {
|
export interface PipeExpression extends GeneralStatement {
|
||||||
type: 'PipeExpression'
|
type: 'PipeExpression'
|
||||||
body: Value[]
|
body: Value[]
|
||||||
@ -1478,7 +1585,7 @@ export function getNodeFromPath<T>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(
|
console.error(
|
||||||
`Could not find path ${pathItem} in node ${JSON.stringify(
|
`Could not find path ${pathItem} in node ${JSON.stringify(
|
||||||
currentNode,
|
currentNode,
|
||||||
null,
|
null,
|
||||||
@ -1510,7 +1617,7 @@ export function getNodeFromPathCurry(
|
|||||||
|
|
||||||
export function getNodePathFromSourceRange(
|
export function getNodePathFromSourceRange(
|
||||||
node: Program,
|
node: Program,
|
||||||
sourceRange: [number, number],
|
sourceRange: Range,
|
||||||
previousPath: PathToNode = []
|
previousPath: PathToNode = []
|
||||||
): PathToNode {
|
): PathToNode {
|
||||||
const [start, end] = sourceRange
|
const [start, end] = sourceRange
|
||||||
|
@ -3,6 +3,9 @@ import {
|
|||||||
Literal,
|
Literal,
|
||||||
Identifier,
|
Identifier,
|
||||||
isNotCodeToken,
|
isNotCodeToken,
|
||||||
|
findClosingBrace,
|
||||||
|
CallExpression,
|
||||||
|
makeCallExpression,
|
||||||
} from './abstractSyntaxTree'
|
} from './abstractSyntaxTree'
|
||||||
import { Token } from './tokeniser'
|
import { Token } from './tokeniser'
|
||||||
|
|
||||||
@ -12,10 +15,21 @@ export function reversePolishNotation(
|
|||||||
operators: Token[] = []
|
operators: Token[] = []
|
||||||
): Token[] {
|
): Token[] {
|
||||||
if (tokens.length === 0) {
|
if (tokens.length === 0) {
|
||||||
return [...previousPostfix, ...operators.slice().reverse()] // reverse mutates, so clone is needed
|
return [...previousPostfix, ...operators.slice().reverse()] // reverse mutates, so slice/clone is needed
|
||||||
}
|
}
|
||||||
const currentToken = tokens[0]
|
const currentToken = tokens[0]
|
||||||
if (
|
if (
|
||||||
|
currentToken.type === 'word' &&
|
||||||
|
tokens?.[1]?.type === 'brace' &&
|
||||||
|
tokens?.[1]?.value === '('
|
||||||
|
) {
|
||||||
|
const closingBrace = findClosingBrace(tokens, 1)
|
||||||
|
return reversePolishNotation(
|
||||||
|
tokens.slice(closingBrace + 1),
|
||||||
|
[...previousPostfix, ...tokens.slice(0, closingBrace + 1)],
|
||||||
|
operators
|
||||||
|
)
|
||||||
|
} else if (
|
||||||
currentToken.type === 'number' ||
|
currentToken.type === 'number' ||
|
||||||
currentToken.type === 'word' ||
|
currentToken.type === 'word' ||
|
||||||
currentToken.type === 'string'
|
currentToken.type === 'string'
|
||||||
@ -88,6 +102,7 @@ const buildTree = (
|
|||||||
| Literal
|
| Literal
|
||||||
| Identifier
|
| Identifier
|
||||||
| ParenthesisToken
|
| ParenthesisToken
|
||||||
|
| CallExpression
|
||||||
)[] = []
|
)[] = []
|
||||||
): BinaryExpression => {
|
): BinaryExpression => {
|
||||||
if (reversePolishNotationTokens.length === 0) {
|
if (reversePolishNotationTokens.length === 0) {
|
||||||
@ -109,6 +124,16 @@ const buildTree = (
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
} else if (currentToken.type === 'word') {
|
} else if (currentToken.type === 'word') {
|
||||||
|
if (
|
||||||
|
reversePolishNotationTokens?.[1]?.type === 'brace' &&
|
||||||
|
reversePolishNotationTokens?.[1]?.value === '('
|
||||||
|
) {
|
||||||
|
const closingBrace = findClosingBrace(reversePolishNotationTokens, 1)
|
||||||
|
return buildTree(reversePolishNotationTokens.slice(closingBrace + 1), [
|
||||||
|
...stack,
|
||||||
|
makeCallExpression(reversePolishNotationTokens, 0).expression,
|
||||||
|
])
|
||||||
|
}
|
||||||
return buildTree(reversePolishNotationTokens.slice(1), [
|
return buildTree(reversePolishNotationTokens.slice(1), [
|
||||||
...stack,
|
...stack,
|
||||||
{
|
{
|
||||||
|
@ -327,15 +327,83 @@ describe('testing math operators', () => {
|
|||||||
const { root } = exe(code)
|
const { root } = exe(code)
|
||||||
expect(root.myVar.value).toBe(12.5)
|
expect(root.myVar.value).toBe(12.5)
|
||||||
})
|
})
|
||||||
// TODO
|
it('with callExpression at start', () => {
|
||||||
// it('with callExpression', () => {
|
const code = 'const myVar = min(4, 100) + 2'
|
||||||
// const code = [
|
const { root } = exe(code)
|
||||||
// 'const yo = (a) => a * 2',
|
expect(root.myVar.value).toBe(6)
|
||||||
// 'const myVar = yo(2) + 2'
|
})
|
||||||
// ].join('\n')
|
it('with callExpression at end', () => {
|
||||||
// const { root } = exe(code)
|
const code = 'const myVar = 2 + min(4, 100)'
|
||||||
// expect(root.myVar.value).toBe(6)
|
const { root } = exe(code)
|
||||||
// })
|
expect(root.myVar.value).toBe(6)
|
||||||
|
})
|
||||||
|
it('with nested callExpression', () => {
|
||||||
|
const code = 'const myVar = 2 + min(100, legLen(5, 3))'
|
||||||
|
const { root } = exe(code)
|
||||||
|
expect(root.myVar.value).toBe(6)
|
||||||
|
})
|
||||||
|
it('with unaryExpression', () => {
|
||||||
|
const code = 'const myVar = -min(100, 3)'
|
||||||
|
const { root } = exe(code)
|
||||||
|
expect(root.myVar.value).toBe(-3)
|
||||||
|
})
|
||||||
|
it('with unaryExpression in callExpression', () => {
|
||||||
|
const code = 'const myVar = min(-legLen(5, 4), 5)'
|
||||||
|
const code2 = 'const myVar = min(5 , -legLen(5, 4))'
|
||||||
|
const { root } = exe(code)
|
||||||
|
const { root: root2 } = exe(code2)
|
||||||
|
expect(root.myVar.value).toBe(-3)
|
||||||
|
expect(root.myVar.value).toBe(root2.myVar.value)
|
||||||
|
})
|
||||||
|
it('with unaryExpression in ArrayExpression', () => {
|
||||||
|
const code = 'const myVar = [1,-legLen(5, 4)]'
|
||||||
|
const { root } = exe(code)
|
||||||
|
expect(root.myVar.value).toEqual([1, -3])
|
||||||
|
})
|
||||||
|
it('with unaryExpression in ArrayExpression in CallExpression, checking nothing funny happens when used in a sketch', () => {
|
||||||
|
const code = [
|
||||||
|
'const part001 = startSketchAt([0, 0])',
|
||||||
|
'|> line([-2.21, -legLen(5, min(3, 999))], %)',
|
||||||
|
].join('\n')
|
||||||
|
const { root } = exe(code)
|
||||||
|
const sketch = removeGeoFromSketch(root.part001 as SketchGroup)
|
||||||
|
// result of `-legLen(5, min(3, 999))` should be -4
|
||||||
|
const yVal = sketch.value?.[0]?.to?.[1]
|
||||||
|
expect(yVal).toBe(-4)
|
||||||
|
})
|
||||||
|
it('test that % substitution feeds down CallExp->ArrExp->UnaryExp->CallExp', () => {
|
||||||
|
const code = [
|
||||||
|
`const myVar = 3`,
|
||||||
|
`const part001 = startSketchAt([0, 0])`,
|
||||||
|
` |> line({ to: [3, 4], tag: 'seg01' }, %)`,
|
||||||
|
` |> line([`,
|
||||||
|
` min(segLen('seg01', %), myVar),`,
|
||||||
|
` -legLen(segLen('seg01', %), myVar)`,
|
||||||
|
`], %)`,
|
||||||
|
``,
|
||||||
|
`show(part001)`,
|
||||||
|
].join('\n')
|
||||||
|
const { root } = exe(code)
|
||||||
|
const sketch = removeGeoFromSketch(root.part001 as SketchGroup)
|
||||||
|
// expect -legLen(segLen('seg01', %), myVar) to equal -4 setting the y value back to 0
|
||||||
|
expect(sketch.value?.[1]?.from).toEqual([3, 4])
|
||||||
|
expect(sketch.value?.[1]?.to).toEqual([6, 0])
|
||||||
|
const removedUnaryExp = code.replace(
|
||||||
|
`-legLen(segLen('seg01', %), myVar)`,
|
||||||
|
`legLen(segLen('seg01', %), myVar)`
|
||||||
|
)
|
||||||
|
const { root: removedUnaryExpRoot } = exe(removedUnaryExp)
|
||||||
|
const removedUnaryExpRootSketch = removeGeoFromSketch(
|
||||||
|
removedUnaryExpRoot.part001 as SketchGroup
|
||||||
|
)
|
||||||
|
// without the minus sign, the y value should be 8
|
||||||
|
expect(removedUnaryExpRootSketch.value?.[1]?.to).toEqual([6, 8])
|
||||||
|
})
|
||||||
|
it('with nested callExpression and binaryExpression', () => {
|
||||||
|
const code = 'const myVar = 2 + min(100, -1 + legLen(5, 3))'
|
||||||
|
const { root } = exe(code)
|
||||||
|
expect(root.myVar.value).toBe(5)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// helpers
|
// helpers
|
||||||
@ -349,7 +417,7 @@ function exe(
|
|||||||
return executor(ast, programMemory)
|
return executor(ast, programMemory)
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeGeoFromSketch(sketch: SketchGroup): any {
|
function removeGeoFromSketch(sketch: SketchGroup): SketchGroup {
|
||||||
return {
|
return {
|
||||||
...sketch,
|
...sketch,
|
||||||
value: removeGeoFromPaths(sketch.value),
|
value: removeGeoFromPaths(sketch.value),
|
||||||
|
@ -8,6 +8,7 @@ import {
|
|||||||
Identifier,
|
Identifier,
|
||||||
CallExpression,
|
CallExpression,
|
||||||
ArrayExpression,
|
ArrayExpression,
|
||||||
|
UnaryExpression,
|
||||||
} from './abstractSyntaxTree'
|
} from './abstractSyntaxTree'
|
||||||
import { InternalFnNames } from './std/stdTypes'
|
import { InternalFnNames } from './std/stdTypes'
|
||||||
import { internalFns } from './std/std'
|
import { internalFns } from './std/std'
|
||||||
@ -171,6 +172,12 @@ export const executor = (
|
|||||||
value: getBinaryExpressionResult(declaration.init, _programMemory),
|
value: getBinaryExpressionResult(declaration.init, _programMemory),
|
||||||
__meta,
|
__meta,
|
||||||
}
|
}
|
||||||
|
} else if (declaration.init.type === 'UnaryExpression') {
|
||||||
|
_programMemory.root[variableName] = {
|
||||||
|
type: 'userVal',
|
||||||
|
value: getUnaryExpressionResult(declaration.init, _programMemory),
|
||||||
|
__meta,
|
||||||
|
}
|
||||||
} else if (declaration.init.type === 'ArrayExpression') {
|
} else if (declaration.init.type === 'ArrayExpression') {
|
||||||
const valueInfo: { value: any; __meta?: Metadata }[] =
|
const valueInfo: { value: any; __meta?: Metadata }[] =
|
||||||
declaration.init.elements.map(
|
declaration.init.elements.map(
|
||||||
@ -197,6 +204,10 @@ export const executor = (
|
|||||||
value: node.value,
|
value: node.value,
|
||||||
__meta: node.__meta[node.__meta.length - 1],
|
__meta: node.__meta[node.__meta.length - 1],
|
||||||
}
|
}
|
||||||
|
} else if (element.type === 'UnaryExpression') {
|
||||||
|
return {
|
||||||
|
value: getUnaryExpressionResult(element, _programMemory),
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unexpected element type ${element.type} in array expression`
|
`Unexpected element type ${element.type} in array expression`
|
||||||
@ -327,19 +338,26 @@ function getMemberExpressionResult(
|
|||||||
|
|
||||||
function getBinaryExpressionResult(
|
function getBinaryExpressionResult(
|
||||||
expression: BinaryExpression,
|
expression: BinaryExpression,
|
||||||
programMemory: ProgramMemory
|
programMemory: ProgramMemory,
|
||||||
|
pipeInfo: {
|
||||||
|
isInPipe: boolean
|
||||||
|
previousResults: any[]
|
||||||
|
expressionIndex: number
|
||||||
|
body: PipeExpression['body']
|
||||||
|
sourceRangeOverride?: SourceRange
|
||||||
|
} = {
|
||||||
|
isInPipe: false,
|
||||||
|
previousResults: [],
|
||||||
|
expressionIndex: 0,
|
||||||
|
body: [],
|
||||||
|
}
|
||||||
) {
|
) {
|
||||||
const getVal = (part: BinaryPart): any => {
|
const _pipeInfo = {
|
||||||
if (part.type === 'Literal') {
|
...pipeInfo,
|
||||||
return part.value
|
isInPipe: false,
|
||||||
} else if (part.type === 'Identifier') {
|
|
||||||
return programMemory.root[part.name].value
|
|
||||||
} else if (part.type === 'BinaryExpression') {
|
|
||||||
return getBinaryExpressionResult(part, programMemory)
|
|
||||||
}
|
}
|
||||||
}
|
const left = getBinaryPartResult(expression.left, programMemory, _pipeInfo)
|
||||||
const left = getVal(expression.left)
|
const right = getBinaryPartResult(expression.right, programMemory, _pipeInfo)
|
||||||
const right = getVal(expression.right)
|
|
||||||
if (expression.operator === '+') return left + right
|
if (expression.operator === '+') return left + right
|
||||||
if (expression.operator === '-') return left - right
|
if (expression.operator === '-') return left - right
|
||||||
if (expression.operator === '*') return left * right
|
if (expression.operator === '*') return left * right
|
||||||
@ -347,6 +365,59 @@ function getBinaryExpressionResult(
|
|||||||
if (expression.operator === '%') return left % right
|
if (expression.operator === '%') return left % right
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getBinaryPartResult(
|
||||||
|
part: BinaryPart,
|
||||||
|
programMemory: ProgramMemory,
|
||||||
|
pipeInfo: {
|
||||||
|
isInPipe: boolean
|
||||||
|
previousResults: any[]
|
||||||
|
expressionIndex: number
|
||||||
|
body: PipeExpression['body']
|
||||||
|
sourceRangeOverride?: SourceRange
|
||||||
|
} = {
|
||||||
|
isInPipe: false,
|
||||||
|
previousResults: [],
|
||||||
|
expressionIndex: 0,
|
||||||
|
body: [],
|
||||||
|
}
|
||||||
|
): any {
|
||||||
|
const _pipeInfo = {
|
||||||
|
...pipeInfo,
|
||||||
|
isInPipe: false,
|
||||||
|
}
|
||||||
|
if (part.type === 'Literal') {
|
||||||
|
return part.value
|
||||||
|
} else if (part.type === 'Identifier') {
|
||||||
|
return programMemory.root[part.name].value
|
||||||
|
} else if (part.type === 'BinaryExpression') {
|
||||||
|
return getBinaryExpressionResult(part, programMemory, _pipeInfo)
|
||||||
|
} else if (part.type === 'CallExpression') {
|
||||||
|
return executeCallExpression(programMemory, part, [], _pipeInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getUnaryExpressionResult(
|
||||||
|
expression: UnaryExpression,
|
||||||
|
programMemory: ProgramMemory,
|
||||||
|
pipeInfo: {
|
||||||
|
isInPipe: boolean
|
||||||
|
previousResults: any[]
|
||||||
|
expressionIndex: number
|
||||||
|
body: PipeExpression['body']
|
||||||
|
sourceRangeOverride?: SourceRange
|
||||||
|
} = {
|
||||||
|
isInPipe: false,
|
||||||
|
previousResults: [],
|
||||||
|
expressionIndex: 0,
|
||||||
|
body: [],
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
return -getBinaryPartResult(expression.argument, programMemory, {
|
||||||
|
...pipeInfo,
|
||||||
|
isInPipe: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function getPipeExpressionResult(
|
function getPipeExpressionResult(
|
||||||
expression: PipeExpression,
|
expression: PipeExpression,
|
||||||
programMemory: ProgramMemory,
|
programMemory: ProgramMemory,
|
||||||
@ -443,17 +514,46 @@ function executeObjectExpression(
|
|||||||
|
|
||||||
function executeArrayExpression(
|
function executeArrayExpression(
|
||||||
_programMemory: ProgramMemory,
|
_programMemory: ProgramMemory,
|
||||||
arrExp: ArrayExpression
|
arrExp: ArrayExpression,
|
||||||
|
pipeInfo: {
|
||||||
|
isInPipe: boolean
|
||||||
|
previousResults: any[]
|
||||||
|
expressionIndex: number
|
||||||
|
body: PipeExpression['body']
|
||||||
|
sourceRangeOverride?: SourceRange
|
||||||
|
} = {
|
||||||
|
isInPipe: false,
|
||||||
|
previousResults: [],
|
||||||
|
expressionIndex: 0,
|
||||||
|
body: [],
|
||||||
|
}
|
||||||
) {
|
) {
|
||||||
|
const _pipeInfo = {
|
||||||
|
...pipeInfo,
|
||||||
|
isInPipe: false,
|
||||||
|
}
|
||||||
return arrExp.elements.map((el) => {
|
return arrExp.elements.map((el) => {
|
||||||
if (el.type === 'Literal') {
|
if (el.type === 'Literal') {
|
||||||
return el.value
|
return el.value
|
||||||
} else if (el.type === 'Identifier') {
|
} else if (el.type === 'Identifier') {
|
||||||
return _programMemory.root?.[el.name]?.value
|
return _programMemory.root?.[el.name]?.value
|
||||||
} else if (el.type === 'BinaryExpression') {
|
} else if (el.type === 'BinaryExpression') {
|
||||||
return getBinaryExpressionResult(el, _programMemory)
|
return getBinaryExpressionResult(el, _programMemory, _pipeInfo)
|
||||||
} else if (el.type === 'ObjectExpression') {
|
} else if (el.type === 'ObjectExpression') {
|
||||||
return executeObjectExpression(_programMemory, el)
|
return executeObjectExpression(_programMemory, el)
|
||||||
|
} else if (el.type === 'CallExpression') {
|
||||||
|
const result: any = executeCallExpression(
|
||||||
|
_programMemory,
|
||||||
|
el,
|
||||||
|
[],
|
||||||
|
_pipeInfo
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
} else if (el.type === 'UnaryExpression') {
|
||||||
|
return getUnaryExpressionResult(el, _programMemory, {
|
||||||
|
...pipeInfo,
|
||||||
|
isInPipe: false,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
throw new Error('Invalid argument type')
|
throw new Error('Invalid argument type')
|
||||||
})
|
})
|
||||||
@ -483,8 +583,12 @@ function executeCallExpression(
|
|||||||
body,
|
body,
|
||||||
sourceRangeOverride,
|
sourceRangeOverride,
|
||||||
} = pipeInfo
|
} = pipeInfo
|
||||||
const functionName = expression.callee.name
|
const functionName = expression?.callee?.name
|
||||||
const fnArgs = expression.arguments.map((arg) => {
|
const _pipeInfo = {
|
||||||
|
...pipeInfo,
|
||||||
|
isInPipe: false,
|
||||||
|
}
|
||||||
|
const fnArgs = expression?.arguments?.map((arg) => {
|
||||||
if (arg.type === 'Literal') {
|
if (arg.type === 'Literal') {
|
||||||
return arg.value
|
return arg.value
|
||||||
} else if (arg.type === 'Identifier') {
|
} else if (arg.type === 'Identifier') {
|
||||||
@ -493,16 +597,21 @@ function executeCallExpression(
|
|||||||
} else if (arg.type === 'PipeSubstitution') {
|
} else if (arg.type === 'PipeSubstitution') {
|
||||||
return previousResults[expressionIndex - 1]
|
return previousResults[expressionIndex - 1]
|
||||||
} else if (arg.type === 'ArrayExpression') {
|
} else if (arg.type === 'ArrayExpression') {
|
||||||
return executeArrayExpression(programMemory, arg)
|
return executeArrayExpression(programMemory, arg, pipeInfo)
|
||||||
} else if (arg.type === 'CallExpression') {
|
} else if (arg.type === 'CallExpression') {
|
||||||
const result: any = executeCallExpression(
|
const result: any = executeCallExpression(
|
||||||
programMemory,
|
programMemory,
|
||||||
arg,
|
arg,
|
||||||
previousPathToNode
|
previousPathToNode,
|
||||||
|
_pipeInfo
|
||||||
)
|
)
|
||||||
return result
|
return result
|
||||||
} else if (arg.type === 'ObjectExpression') {
|
} else if (arg.type === 'ObjectExpression') {
|
||||||
return executeObjectExpression(programMemory, arg)
|
return executeObjectExpression(programMemory, arg)
|
||||||
|
} else if (arg.type === 'UnaryExpression') {
|
||||||
|
return getUnaryExpressionResult(arg, programMemory, _pipeInfo)
|
||||||
|
} else if (arg.type === 'BinaryExpression') {
|
||||||
|
return getBinaryExpressionResult(arg, programMemory, _pipeInfo)
|
||||||
}
|
}
|
||||||
throw new Error('Invalid argument type in function call')
|
throw new Error('Invalid argument type in function call')
|
||||||
})
|
})
|
||||||
@ -514,7 +623,8 @@ function executeCallExpression(
|
|||||||
sourceRange: sourceRangeOverride || [expression.start, expression.end],
|
sourceRange: sourceRangeOverride || [expression.start, expression.end],
|
||||||
},
|
},
|
||||||
fnArgs[0],
|
fnArgs[0],
|
||||||
fnArgs[1]
|
fnArgs[1],
|
||||||
|
fnArgs[2]
|
||||||
)
|
)
|
||||||
return isInPipe
|
return isInPipe
|
||||||
? executePipeBody(
|
? executePipeBody(
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { abstractSyntaxTree } from './abstractSyntaxTree'
|
||||||
import {
|
import {
|
||||||
createLiteral,
|
createLiteral,
|
||||||
createIdentifier,
|
createIdentifier,
|
||||||
@ -9,8 +10,13 @@ import {
|
|||||||
createPipeExpression,
|
createPipeExpression,
|
||||||
findUniqueName,
|
findUniqueName,
|
||||||
addSketchTo,
|
addSketchTo,
|
||||||
|
giveSketchFnCallTag,
|
||||||
} from './modifyAst'
|
} from './modifyAst'
|
||||||
import { recast } from './recast'
|
import { recast } from './recast'
|
||||||
|
import { lexer } from './tokeniser'
|
||||||
|
import { initPromise } from './rust'
|
||||||
|
|
||||||
|
beforeAll(() => initPromise)
|
||||||
|
|
||||||
describe('Testing createLiteral', () => {
|
describe('Testing createLiteral', () => {
|
||||||
it('should create a literal', () => {
|
it('should create a literal', () => {
|
||||||
@ -111,3 +117,56 @@ describe('Testing addSketchTo', () => {
|
|||||||
show(part001)`)
|
show(part001)`)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function giveSketchFnCallTagTestHelper(
|
||||||
|
code: string,
|
||||||
|
searchStr: string
|
||||||
|
): { tag: string; newCode: string } {
|
||||||
|
// giveSketchFnCallTag inputs and outputs an ast, which is very verbose for testing
|
||||||
|
// this wrapper changes the input and output to code
|
||||||
|
// making it more of an integration test, but easier to read the test intention is the goal
|
||||||
|
const ast = abstractSyntaxTree(lexer(code))
|
||||||
|
const start = code.indexOf(searchStr)
|
||||||
|
const range: [number, number] = [start, start + searchStr.length]
|
||||||
|
const { modifiedAst, tag } = giveSketchFnCallTag(ast, range)
|
||||||
|
const newCode = recast(modifiedAst)
|
||||||
|
return { tag, newCode }
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Testing giveSketchFnCallTag', () => {
|
||||||
|
const code = `const part001 = startSketchAt([0, 0])
|
||||||
|
|> line([-2.57, -0.13], %)
|
||||||
|
|> line([0, 0.83], %)
|
||||||
|
|> line([0.82, 0.34], %)
|
||||||
|
show(part001)`
|
||||||
|
it('Should add tag to a sketch function call', () => {
|
||||||
|
const { newCode, tag } = giveSketchFnCallTagTestHelper(
|
||||||
|
code,
|
||||||
|
'line([0, 0.83], %)'
|
||||||
|
)
|
||||||
|
expect(newCode).toContain("line({ to: [0, 0.83], tag: 'seg01' }, %)")
|
||||||
|
expect(tag).toBe('seg01')
|
||||||
|
})
|
||||||
|
it('Should create a unique tag if seg01 already exists', () => {
|
||||||
|
let _code = code.replace(
|
||||||
|
'line([-2.57, -0.13], %)',
|
||||||
|
"line({ to: [-2.57, -0.13], tag: 'seg01' }, %)"
|
||||||
|
)
|
||||||
|
const { newCode, tag } = giveSketchFnCallTagTestHelper(
|
||||||
|
_code,
|
||||||
|
'line([0, 0.83], %)'
|
||||||
|
)
|
||||||
|
expect(newCode).toContain("line({ to: [0, 0.83], tag: 'seg02' }, %)")
|
||||||
|
expect(tag).toBe('seg02')
|
||||||
|
})
|
||||||
|
it('Should return existing tag if it already exists', () => {
|
||||||
|
const lineButWithTag = "line({ to: [-2.57, -0.13], tag: 'butts' }, %)"
|
||||||
|
let _code = code.replace('line([-2.57, -0.13], %)', lineButWithTag)
|
||||||
|
const { newCode, tag } = giveSketchFnCallTagTestHelper(
|
||||||
|
_code,
|
||||||
|
lineButWithTag
|
||||||
|
)
|
||||||
|
expect(newCode).toContain(lineButWithTag) // no change
|
||||||
|
expect(tag).toBe('butts')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { Range, TooTip } from '../useStore'
|
||||||
import {
|
import {
|
||||||
Program,
|
Program,
|
||||||
CallExpression,
|
CallExpression,
|
||||||
@ -12,9 +13,16 @@ import {
|
|||||||
Identifier,
|
Identifier,
|
||||||
ArrayExpression,
|
ArrayExpression,
|
||||||
ObjectExpression,
|
ObjectExpression,
|
||||||
|
getNodePathFromSourceRange,
|
||||||
|
UnaryExpression,
|
||||||
|
BinaryExpression,
|
||||||
} from './abstractSyntaxTree'
|
} from './abstractSyntaxTree'
|
||||||
import { PathToNode, ProgramMemory } from './executor'
|
import { PathToNode, ProgramMemory } from './executor'
|
||||||
import { addTagForSketchOnFace } from './std/sketch'
|
import {
|
||||||
|
addTagForSketchOnFace,
|
||||||
|
getFirstArg,
|
||||||
|
createFirstArg,
|
||||||
|
} from './std/sketch'
|
||||||
|
|
||||||
export function addSketchTo(
|
export function addSketchTo(
|
||||||
node: Program,
|
node: Program,
|
||||||
@ -101,23 +109,7 @@ function addToShow(node: Program, name: string): Program {
|
|||||||
const dumbyStartend = { start: 0, end: 0 }
|
const dumbyStartend = { start: 0, end: 0 }
|
||||||
const showCallIndex = getShowIndex(_node)
|
const showCallIndex = getShowIndex(_node)
|
||||||
if (showCallIndex === -1) {
|
if (showCallIndex === -1) {
|
||||||
const showCall: CallExpression = {
|
const showCall = createCallExpression('show', [createIdentifier(name)])
|
||||||
type: 'CallExpression',
|
|
||||||
...dumbyStartend,
|
|
||||||
callee: {
|
|
||||||
type: 'Identifier',
|
|
||||||
...dumbyStartend,
|
|
||||||
name: 'show',
|
|
||||||
},
|
|
||||||
optional: false,
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
type: 'Identifier',
|
|
||||||
...dumbyStartend,
|
|
||||||
name,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
const showExpressionStatement: ExpressionStatement = {
|
const showExpressionStatement: ExpressionStatement = {
|
||||||
type: 'ExpressionStatement',
|
type: 'ExpressionStatement',
|
||||||
...dumbyStartend,
|
...dumbyStartend,
|
||||||
@ -128,25 +120,8 @@ function addToShow(node: Program, name: string): Program {
|
|||||||
}
|
}
|
||||||
const showCall = { ..._node.body[showCallIndex] } as ExpressionStatement
|
const showCall = { ..._node.body[showCallIndex] } as ExpressionStatement
|
||||||
const showCallArgs = (showCall.expression as CallExpression).arguments
|
const showCallArgs = (showCall.expression as CallExpression).arguments
|
||||||
const newShowCallArgs: Value[] = [
|
const newShowCallArgs: Value[] = [...showCallArgs, createIdentifier(name)]
|
||||||
...showCallArgs,
|
const newShowExpression = createCallExpression('show', newShowCallArgs)
|
||||||
{
|
|
||||||
type: 'Identifier',
|
|
||||||
...dumbyStartend,
|
|
||||||
name,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
const newShowExpression: CallExpression = {
|
|
||||||
type: 'CallExpression',
|
|
||||||
...dumbyStartend,
|
|
||||||
callee: {
|
|
||||||
type: 'Identifier',
|
|
||||||
...dumbyStartend,
|
|
||||||
name: 'show',
|
|
||||||
},
|
|
||||||
optional: false,
|
|
||||||
arguments: newShowCallArgs,
|
|
||||||
}
|
|
||||||
|
|
||||||
_node.body[showCallIndex] = {
|
_node.body[showCallIndex] = {
|
||||||
...showCall,
|
...showCall,
|
||||||
@ -247,16 +222,7 @@ export function extrudeSketch(
|
|||||||
const { node: variableDeclorator, path: pathToDecleration } =
|
const { node: variableDeclorator, path: pathToDecleration } =
|
||||||
getNodeFromPath<VariableDeclarator>(_node, pathToNode, 'VariableDeclarator')
|
getNodeFromPath<VariableDeclarator>(_node, pathToNode, 'VariableDeclarator')
|
||||||
|
|
||||||
const extrudeCall: CallExpression = {
|
const extrudeCall = createCallExpression('extrude', [
|
||||||
type: 'CallExpression',
|
|
||||||
...dumbyStartend,
|
|
||||||
callee: {
|
|
||||||
type: 'Identifier',
|
|
||||||
...dumbyStartend,
|
|
||||||
name: 'extrude',
|
|
||||||
},
|
|
||||||
optional: false,
|
|
||||||
arguments: [
|
|
||||||
createLiteral(4),
|
createLiteral(4),
|
||||||
shouldPipe
|
shouldPipe
|
||||||
? createPipeSubstitution()
|
? createPipeSubstitution()
|
||||||
@ -265,22 +231,14 @@ export function extrudeSketch(
|
|||||||
...dumbyStartend,
|
...dumbyStartend,
|
||||||
name: variableDeclorator.id.name,
|
name: variableDeclorator.id.name,
|
||||||
},
|
},
|
||||||
],
|
])
|
||||||
}
|
|
||||||
if (shouldPipe) {
|
if (shouldPipe) {
|
||||||
const pipeChain: PipeExpression = isInPipeExpression
|
const pipeChain = createPipeExpression(
|
||||||
? {
|
isInPipeExpression
|
||||||
type: 'PipeExpression',
|
? [...pipeExpression.body, extrudeCall]
|
||||||
nonCodeMeta: {},
|
: [sketchExpression as any, extrudeCall]
|
||||||
...dumbyStartend,
|
)
|
||||||
body: [...pipeExpression.body, extrudeCall],
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
type: 'PipeExpression',
|
|
||||||
nonCodeMeta: {},
|
|
||||||
...dumbyStartend,
|
|
||||||
body: [sketchExpression as any, extrudeCall], // TODO fix this #25
|
|
||||||
}
|
|
||||||
|
|
||||||
variableDeclorator.init = pipeChain
|
variableDeclorator.init = pipeChain
|
||||||
const pathToExtrudeArg = [
|
const pathToExtrudeArg = [
|
||||||
@ -299,23 +257,7 @@ export function extrudeSketch(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const name = findUniqueName(node, 'part')
|
const name = findUniqueName(node, 'part')
|
||||||
const VariableDeclaration: VariableDeclaration = {
|
const VariableDeclaration = createVariableDeclaration(name, extrudeCall)
|
||||||
type: 'VariableDeclaration',
|
|
||||||
...dumbyStartend,
|
|
||||||
declarations: [
|
|
||||||
{
|
|
||||||
type: 'VariableDeclarator',
|
|
||||||
...dumbyStartend,
|
|
||||||
id: {
|
|
||||||
type: 'Identifier',
|
|
||||||
...dumbyStartend,
|
|
||||||
name,
|
|
||||||
},
|
|
||||||
init: extrudeCall,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
kind: 'const',
|
|
||||||
}
|
|
||||||
const showCallIndex = getShowIndex(_node)
|
const showCallIndex = getShowIndex(_node)
|
||||||
_node.body.splice(showCallIndex, 0, VariableDeclaration)
|
_node.body.splice(showCallIndex, 0, VariableDeclaration)
|
||||||
const pathToExtrudeArg = [
|
const pathToExtrudeArg = [
|
||||||
@ -509,3 +451,55 @@ export function createObjectExpression(properties: {
|
|||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createUnaryExpression(
|
||||||
|
argument: UnaryExpression['argument'],
|
||||||
|
operator: UnaryExpression['operator'] = '-'
|
||||||
|
): UnaryExpression {
|
||||||
|
return {
|
||||||
|
type: 'UnaryExpression',
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
operator,
|
||||||
|
argument,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createBinaryExpression([left, operator, right]: [
|
||||||
|
BinaryExpression['left'],
|
||||||
|
BinaryExpression['operator'],
|
||||||
|
BinaryExpression['right']
|
||||||
|
]): BinaryExpression {
|
||||||
|
return {
|
||||||
|
type: 'BinaryExpression',
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
operator,
|
||||||
|
left,
|
||||||
|
right,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function giveSketchFnCallTag(
|
||||||
|
ast: Program,
|
||||||
|
range: Range
|
||||||
|
): { modifiedAst: Program; tag: string } {
|
||||||
|
const { node: primaryCallExp } = getNodeFromPath<CallExpression>(
|
||||||
|
ast,
|
||||||
|
getNodePathFromSourceRange(ast, range)
|
||||||
|
)
|
||||||
|
const firstArg = getFirstArg(primaryCallExp)
|
||||||
|
const tagValue = (firstArg.tag ||
|
||||||
|
createLiteral(findUniqueName(ast, 'seg', 2))) as Literal
|
||||||
|
const tagStr = String(tagValue.value)
|
||||||
|
const newFirstArg = createFirstArg(
|
||||||
|
primaryCallExp.callee.name as TooTip,
|
||||||
|
firstArg.val,
|
||||||
|
tagValue
|
||||||
|
)
|
||||||
|
primaryCallExp.arguments[0] = newFirstArg
|
||||||
|
return {
|
||||||
|
modifiedAst: ast,
|
||||||
|
tag: tagStr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -273,6 +273,36 @@ const mySk1 = startSketchAt([0, 0])
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('testing call Expressions in BinaryExpressions and UnaryExpressions', () => {
|
||||||
|
it('nested callExpression in binaryExpression', () => {
|
||||||
|
const code = 'const myVar = 2 + min(100, legLen(5, 3))'
|
||||||
|
const { ast } = code2ast(code)
|
||||||
|
const recasted = recast(ast)
|
||||||
|
expect(recasted).toBe(code)
|
||||||
|
})
|
||||||
|
it('nested callExpression in unaryExpression', () => {
|
||||||
|
const code = 'const myVar = -min(100, legLen(5, 3))'
|
||||||
|
const { ast } = code2ast(code)
|
||||||
|
const recasted = recast(ast)
|
||||||
|
expect(recasted).toBe(code)
|
||||||
|
})
|
||||||
|
it('with unaryExpression in callExpression', () => {
|
||||||
|
const code = 'const myVar = min(5, -legLen(5, 4))'
|
||||||
|
const { ast } = code2ast(code)
|
||||||
|
const recasted = recast(ast)
|
||||||
|
expect(recasted).toBe(code)
|
||||||
|
})
|
||||||
|
it('with unaryExpression in sketch situation', () => {
|
||||||
|
const code = [
|
||||||
|
'const part001 = startSketchAt([0, 0])',
|
||||||
|
'|> line([-2.21, -legLen(5, min(3, 999))], %)',
|
||||||
|
].join('\n')
|
||||||
|
const { ast } = code2ast(code)
|
||||||
|
const recasted = recast(ast)
|
||||||
|
expect(recasted).toBe(code)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
// helpers
|
// helpers
|
||||||
|
|
||||||
function code2ast(code: string): { ast: Program; tokens: Token[] } {
|
function code2ast(code: string): { ast: Program; tokens: Token[] } {
|
||||||
|
@ -11,6 +11,7 @@ import {
|
|||||||
ObjectExpression,
|
ObjectExpression,
|
||||||
MemberExpression,
|
MemberExpression,
|
||||||
PipeExpression,
|
PipeExpression,
|
||||||
|
UnaryExpression,
|
||||||
} from './abstractSyntaxTree'
|
} from './abstractSyntaxTree'
|
||||||
import { precedence } from './astMathExpressions'
|
import { precedence } from './astMathExpressions'
|
||||||
|
|
||||||
@ -93,6 +94,10 @@ function recastBinaryExpression(expression: BinaryExpression): string {
|
|||||||
} ${maybeWrapIt(recastBinaryPart(expression.right), shouldWrapRight)}`
|
} ${maybeWrapIt(recastBinaryPart(expression.right), shouldWrapRight)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function recastUnaryExpression(expression: UnaryExpression): string {
|
||||||
|
return `${expression.operator}${recastValue(expression.argument)}`
|
||||||
|
}
|
||||||
|
|
||||||
function recastArrayExpression(
|
function recastArrayExpression(
|
||||||
expression: ArrayExpression,
|
expression: ArrayExpression,
|
||||||
indentation = ''
|
indentation = ''
|
||||||
@ -105,9 +110,9 @@ function recastArrayExpression(
|
|||||||
const _indentation = indentation + ' '
|
const _indentation = indentation + ' '
|
||||||
return `[
|
return `[
|
||||||
${_indentation}${expression.elements
|
${_indentation}${expression.elements
|
||||||
.map((el) => recastValue(el))
|
.map((el) => recastValue(el, _indentation))
|
||||||
.join(`,\n${_indentation}`)}
|
.join(`,\n${_indentation}`)}
|
||||||
]`
|
${indentation}]`
|
||||||
}
|
}
|
||||||
return flatRecast
|
return flatRecast
|
||||||
}
|
}
|
||||||
@ -138,6 +143,8 @@ function recastBinaryPart(part: BinaryPart): string {
|
|||||||
return part.name
|
return part.name
|
||||||
} else if (part.type === 'BinaryExpression') {
|
} else if (part.type === 'BinaryExpression') {
|
||||||
return recastBinaryExpression(part)
|
return recastBinaryExpression(part)
|
||||||
|
} else if (part.type === 'CallExpression') {
|
||||||
|
return recastCallExpression(part)
|
||||||
}
|
}
|
||||||
return ''
|
return ''
|
||||||
// throw new Error(`Cannot recast BinaryPart ${part}`)
|
// throw new Error(`Cannot recast BinaryPart ${part}`)
|
||||||
@ -151,13 +158,16 @@ function recastLiteral(literal: Literal): string {
|
|||||||
return String(literal?.value)
|
return String(literal?.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
function recastCallExpression(expression: CallExpression): string {
|
function recastCallExpression(
|
||||||
|
expression: CallExpression,
|
||||||
|
indentation = ''
|
||||||
|
): string {
|
||||||
return `${expression.callee.name}(${expression.arguments
|
return `${expression.callee.name}(${expression.arguments
|
||||||
.map(recastArgument)
|
.map((arg) => recastArgument(arg, indentation))
|
||||||
.join(', ')})`
|
.join(', ')})`
|
||||||
}
|
}
|
||||||
|
|
||||||
function recastArgument(argument: Value): string {
|
function recastArgument(argument: Value, indentation = ''): string {
|
||||||
if (argument.type === 'Literal') {
|
if (argument.type === 'Literal') {
|
||||||
return recastLiteral(argument)
|
return recastLiteral(argument)
|
||||||
} else if (argument.type === 'Identifier') {
|
} else if (argument.type === 'Identifier') {
|
||||||
@ -165,7 +175,7 @@ function recastArgument(argument: Value): string {
|
|||||||
} else if (argument.type === 'BinaryExpression') {
|
} else if (argument.type === 'BinaryExpression') {
|
||||||
return recastBinaryExpression(argument)
|
return recastBinaryExpression(argument)
|
||||||
} else if (argument.type === 'ArrayExpression') {
|
} else if (argument.type === 'ArrayExpression') {
|
||||||
return recastArrayExpression(argument)
|
return recastArrayExpression(argument, indentation)
|
||||||
} else if (argument.type === 'ObjectExpression') {
|
} else if (argument.type === 'ObjectExpression') {
|
||||||
return recastObjectExpression(argument)
|
return recastObjectExpression(argument)
|
||||||
} else if (argument.type === 'CallExpression') {
|
} else if (argument.type === 'CallExpression') {
|
||||||
@ -174,6 +184,8 @@ function recastArgument(argument: Value): string {
|
|||||||
return recastFunction(argument)
|
return recastFunction(argument)
|
||||||
} else if (argument.type === 'PipeSubstitution') {
|
} else if (argument.type === 'PipeSubstitution') {
|
||||||
return '%'
|
return '%'
|
||||||
|
} else if (argument.type === 'UnaryExpression') {
|
||||||
|
return recastUnaryExpression(argument)
|
||||||
}
|
}
|
||||||
throw new Error(`Cannot recast argument ${argument}`)
|
throw new Error(`Cannot recast argument ${argument}`)
|
||||||
}
|
}
|
||||||
@ -215,11 +227,13 @@ function recastValue(node: Value, indentation = ''): string {
|
|||||||
} else if (node.type === 'FunctionExpression') {
|
} else if (node.type === 'FunctionExpression') {
|
||||||
return recastFunction(node)
|
return recastFunction(node)
|
||||||
} else if (node.type === 'CallExpression') {
|
} else if (node.type === 'CallExpression') {
|
||||||
return recastCallExpression(node)
|
return recastCallExpression(node, indentation)
|
||||||
} else if (node.type === 'Identifier') {
|
} else if (node.type === 'Identifier') {
|
||||||
return node.name
|
return node.name
|
||||||
} else if (node.type === 'PipeExpression') {
|
} else if (node.type === 'PipeExpression') {
|
||||||
return recastPipeExpression(node)
|
return recastPipeExpression(node)
|
||||||
|
} else if (node.type === 'UnaryExpression') {
|
||||||
|
return recastUnaryExpression(node)
|
||||||
}
|
}
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
@ -230,7 +244,7 @@ function recastPipeExpression(expression: PipeExpression): string {
|
|||||||
let str = ''
|
let str = ''
|
||||||
let indentation = ' '
|
let indentation = ' '
|
||||||
let maybeLineBreak = '\n'
|
let maybeLineBreak = '\n'
|
||||||
str = recastValue(statement)
|
str = recastValue(statement, indentation)
|
||||||
if (
|
if (
|
||||||
expression.nonCodeMeta?.[index]?.value &&
|
expression.nonCodeMeta?.[index]?.value &&
|
||||||
expression.nonCodeMeta?.[index].value !== ' '
|
expression.nonCodeMeta?.[index].value !== ' '
|
||||||
|
@ -66,7 +66,7 @@ function createCallWrapper(
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
function createFirstArg(
|
export function createFirstArg(
|
||||||
sketchFn: TooTip,
|
sketchFn: TooTip,
|
||||||
val: Value | [Value, Value],
|
val: Value | [Value, Value],
|
||||||
tag?: Value
|
tag?: Value
|
||||||
@ -125,218 +125,6 @@ function tranformerDefaults(
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
const lineAndLineToAllowedTransforms: SketchLineHelper['allowedTransforms'] = ({
|
|
||||||
node,
|
|
||||||
pathToNode,
|
|
||||||
}: {
|
|
||||||
node: Program
|
|
||||||
pathToNode: PathToNode
|
|
||||||
}) => {
|
|
||||||
const { node: callExpression } = getNodeFromPath<CallExpression>(
|
|
||||||
node,
|
|
||||||
pathToNode
|
|
||||||
)
|
|
||||||
if (
|
|
||||||
callExpression.type !== 'CallExpression' ||
|
|
||||||
!toolTips.includes(callExpression.callee.name as any)
|
|
||||||
)
|
|
||||||
return {}
|
|
||||||
const fnName = callExpression.callee.name as TooTip
|
|
||||||
const firstArg = callExpression.arguments?.[0]
|
|
||||||
if (
|
|
||||||
firstArg.type !== 'ArrayExpression' &&
|
|
||||||
!(firstArg.type === 'ObjectExpression')
|
|
||||||
)
|
|
||||||
return {}
|
|
||||||
const { val, tag } = getFirstArgValuesForXYFns(callExpression)
|
|
||||||
const [x, y] = val
|
|
||||||
if (x.type !== 'Literal' && y.type !== 'Literal') return {}
|
|
||||||
if (x.type !== 'Literal' && y.type === 'Literal' && fnName === 'line')
|
|
||||||
return {
|
|
||||||
xLine: (args) => createCallWrapper('xLine', x, tag),
|
|
||||||
angledLineOfXLength: (args) =>
|
|
||||||
createCallWrapper('angledLineOfXLength', [args[0], x], tag),
|
|
||||||
}
|
|
||||||
if (x.type !== 'Literal' && y.type === 'Literal' && fnName === 'lineTo')
|
|
||||||
return {
|
|
||||||
xLineTo: (args) => createCallWrapper('xLineTo', x, tag),
|
|
||||||
angledLineToX: (args) =>
|
|
||||||
createCallWrapper('angledLineToX', [args[0], x], tag),
|
|
||||||
}
|
|
||||||
if (x.type === 'Literal' && y.type !== 'Literal' && fnName === 'line')
|
|
||||||
return {
|
|
||||||
yLine: (args) => createCallWrapper('yLine', y, tag),
|
|
||||||
angledLineOfYLength: (args) =>
|
|
||||||
createCallWrapper('angledLineOfYLength', [args[0], y], tag),
|
|
||||||
}
|
|
||||||
if (x.type === 'Literal' && y.type !== 'Literal' && fnName === 'lineTo')
|
|
||||||
return {
|
|
||||||
yLineTo: (args) => createCallWrapper('yLineTo', y, tag),
|
|
||||||
angledLineToY: (args) =>
|
|
||||||
createCallWrapper('angledLineToY', [args[0], y], tag),
|
|
||||||
}
|
|
||||||
if (x.type === 'Literal' && y.type === 'Literal')
|
|
||||||
return tranformerDefaults([], tag)
|
|
||||||
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
|
|
||||||
const xyLineAllowedTransforms: SketchLineHelper['allowedTransforms'] = ({
|
|
||||||
node,
|
|
||||||
pathToNode,
|
|
||||||
}: {
|
|
||||||
node: Program
|
|
||||||
pathToNode: PathToNode
|
|
||||||
}) => {
|
|
||||||
const { node: callExpression } = getNodeFromPath<CallExpression>(
|
|
||||||
node,
|
|
||||||
pathToNode
|
|
||||||
)
|
|
||||||
if (
|
|
||||||
callExpression.type !== 'CallExpression' ||
|
|
||||||
!toolTips.includes(callExpression.callee.name as any)
|
|
||||||
)
|
|
||||||
return {}
|
|
||||||
const fnName = callExpression.callee.name
|
|
||||||
const firstArg = callExpression.arguments?.[0]
|
|
||||||
if (firstArg.type !== 'Literal' && !(firstArg.type === 'ObjectExpression'))
|
|
||||||
return {}
|
|
||||||
const { val, tag } = getFirstArgValuesForXYLineFns(callExpression)
|
|
||||||
const x = val
|
|
||||||
if (x.type !== 'Literal' && fnName === 'xLine')
|
|
||||||
return {
|
|
||||||
xLine: (args) => createCallWrapper('xLine', x, tag),
|
|
||||||
line: (args) => createCallWrapper('line', [x, args[1]], tag),
|
|
||||||
angledLineOfXLength: (args) =>
|
|
||||||
createCallWrapper('angledLineOfXLength', [args[0], x], tag),
|
|
||||||
}
|
|
||||||
if (x.type !== 'Literal' && fnName === 'xLineTo')
|
|
||||||
return {
|
|
||||||
xLineTo: (args) => createCallWrapper('xLineTo', x, tag),
|
|
||||||
lineTo: (args) => createCallWrapper('lineTo', [x, args[1]], tag),
|
|
||||||
angledLineToX: (args) =>
|
|
||||||
createCallWrapper('angledLineToX', [args[0], x], tag),
|
|
||||||
}
|
|
||||||
if (x.type !== 'Literal' && fnName === 'yLine')
|
|
||||||
return {
|
|
||||||
yLine: (args) => createCallWrapper('yLine', x, tag),
|
|
||||||
line: (args) => createCallWrapper('line', [args[0], x], tag),
|
|
||||||
angledLineOfYLength: (args) =>
|
|
||||||
createCallWrapper('angledLineOfYLength', [args[0], x], tag),
|
|
||||||
}
|
|
||||||
if (x.type !== 'Literal' && fnName === 'yLineTo')
|
|
||||||
return {
|
|
||||||
yLineTo: (args) => createCallWrapper('yLineTo', x, tag),
|
|
||||||
lineTo: (args) => createCallWrapper('lineTo', [args[0], x], tag),
|
|
||||||
angledLineToY: (args) =>
|
|
||||||
createCallWrapper('angledLineToY', [args[0], x], tag),
|
|
||||||
}
|
|
||||||
if (x.type === 'Literal' && fnName.startsWith('yLine'))
|
|
||||||
return tranformerDefaults(['yLine'], tag)
|
|
||||||
if (x.type === 'Literal' && fnName.startsWith('xLine'))
|
|
||||||
return tranformerDefaults(['xLine'], tag)
|
|
||||||
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
|
|
||||||
const angledLineAllowedTransforms: SketchLineHelper['allowedTransforms'] = ({
|
|
||||||
node,
|
|
||||||
pathToNode,
|
|
||||||
}: {
|
|
||||||
node: Program
|
|
||||||
pathToNode: PathToNode
|
|
||||||
}) => {
|
|
||||||
const { node: callExpression } = getNodeFromPath<CallExpression>(
|
|
||||||
node,
|
|
||||||
pathToNode
|
|
||||||
)
|
|
||||||
if (
|
|
||||||
callExpression.type !== 'CallExpression' ||
|
|
||||||
!toolTips.includes(callExpression.callee.name as any)
|
|
||||||
)
|
|
||||||
return {}
|
|
||||||
const fnName = callExpression.callee.name as TooTip
|
|
||||||
const firstArg = callExpression.arguments?.[0]
|
|
||||||
if (
|
|
||||||
firstArg.type !== 'ArrayExpression' &&
|
|
||||||
!(firstArg.type === 'ObjectExpression')
|
|
||||||
)
|
|
||||||
return {}
|
|
||||||
const { val, tag } = getFirstArgValuesForAngleFns(callExpression)
|
|
||||||
const [angle, length] = val
|
|
||||||
if (angle.type !== 'Literal' && length.type !== 'Literal') return {}
|
|
||||||
if (angle.type !== 'Literal' && length.type === 'Literal')
|
|
||||||
return {
|
|
||||||
angledLineOfYLength: (args) =>
|
|
||||||
createCallWrapper('angledLineOfYLength', [angle, args[1]], tag),
|
|
||||||
angledLineOfXLength: (args) =>
|
|
||||||
createCallWrapper('angledLineOfXLength', [angle, args[1]], tag),
|
|
||||||
angledLineToY: (args) =>
|
|
||||||
createCallWrapper('angledLineToY', [angle, args[1]], tag),
|
|
||||||
angledLineToX: (args) =>
|
|
||||||
createCallWrapper('angledLineToX', [angle, args[1]], tag),
|
|
||||||
angledLine: (args) =>
|
|
||||||
createCallWrapper('angledLine', [angle, args[1]], tag),
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
angle.type === 'Literal' &&
|
|
||||||
length.type !== 'Literal' &&
|
|
||||||
fnName === 'angledLine'
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
angledLine: (args) =>
|
|
||||||
createCallWrapper('angledLine', [args[0], length], tag),
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
angle.type === 'Literal' &&
|
|
||||||
length.type !== 'Literal' &&
|
|
||||||
fnName === 'angledLineOfXLength'
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
angledLineOfXLength: (args) =>
|
|
||||||
createCallWrapper('angledLineOfXLength', [angle, args[1]], tag),
|
|
||||||
line: (args) => createCallWrapper('line', [length, args[1]], tag),
|
|
||||||
xLine: (args) => createCallWrapper('xLine', length, tag),
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
angle.type === 'Literal' &&
|
|
||||||
length.type !== 'Literal' &&
|
|
||||||
fnName === 'angledLineOfYLength'
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
angledLineOfYLength: (args) =>
|
|
||||||
createCallWrapper('angledLineOfYLength', [args[0], length], tag),
|
|
||||||
line: (args) => createCallWrapper('line', [args[0], length], tag),
|
|
||||||
yLine: (args) => createCallWrapper('yLine', length, tag),
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
angle.type === 'Literal' &&
|
|
||||||
length.type !== 'Literal' &&
|
|
||||||
fnName === 'angledLineToX'
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
angledLineToX: (args) =>
|
|
||||||
createCallWrapper('angledLineToX', [args[0], length], tag),
|
|
||||||
lineTo: (args) => createCallWrapper('lineTo', [length, args[1]], tag),
|
|
||||||
xLineTo: (args) => createCallWrapper('xLineTo', length, tag),
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
angle.type === 'Literal' &&
|
|
||||||
length.type !== 'Literal' &&
|
|
||||||
fnName === 'angledLineToY'
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
angledLineToY: (args) =>
|
|
||||||
createCallWrapper('angledLineToY', [args[0], length], tag),
|
|
||||||
lineTo: (args) => createCallWrapper('lineTo', [args[0], length], tag),
|
|
||||||
yLineTo: (args) => createCallWrapper('yLineTo', length, tag),
|
|
||||||
}
|
|
||||||
if (angle.type === 'Literal' && length.type === 'Literal')
|
|
||||||
return tranformerDefaults([], tag)
|
|
||||||
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const lineTo: SketchLineHelper = {
|
export const lineTo: SketchLineHelper = {
|
||||||
fn: (
|
fn: (
|
||||||
{ sourceRange, programMemory },
|
{ sourceRange, programMemory },
|
||||||
@ -422,7 +210,6 @@ export const lineTo: SketchLineHelper = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
addTag: addTagWithTo('default'),
|
addTag: addTagWithTo('default'),
|
||||||
allowedTransforms: lineAndLineToAllowedTransforms,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const line: SketchLineHelper = {
|
export const line: SketchLineHelper = {
|
||||||
@ -478,7 +265,9 @@ export const line: SketchLineHelper = {
|
|||||||
previousProgramMemory,
|
previousProgramMemory,
|
||||||
pathToNode,
|
pathToNode,
|
||||||
to,
|
to,
|
||||||
// from: [number, number],
|
from,
|
||||||
|
replaceExisting,
|
||||||
|
createCallback,
|
||||||
}) => {
|
}) => {
|
||||||
const _node = { ...node }
|
const _node = { ...node }
|
||||||
const { node: pipe } = getNodeFromPath<PipeExpression>(
|
const { node: pipe } = getNodeFromPath<PipeExpression>(
|
||||||
@ -486,6 +275,7 @@ export const line: SketchLineHelper = {
|
|||||||
pathToNode,
|
pathToNode,
|
||||||
'PipeExpression'
|
'PipeExpression'
|
||||||
)
|
)
|
||||||
|
if (!from) throw new Error('no from') // todo #29 remove
|
||||||
const { node: varDec } = getNodeFromPath<VariableDeclarator>(
|
const { node: varDec } = getNodeFromPath<VariableDeclarator>(
|
||||||
_node,
|
_node,
|
||||||
pathToNode,
|
pathToNode,
|
||||||
@ -494,15 +284,23 @@ export const line: SketchLineHelper = {
|
|||||||
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')
|
||||||
const last = sketch.value[sketch.value.length - 1]
|
|
||||||
const newLine = createCallExpression('line', [
|
const newXVal = createLiteral(roundOff(to[0] - from[0], 2))
|
||||||
createArrayExpression([
|
const newYVal = createLiteral(roundOff(to[1] - from[1], 2))
|
||||||
createLiteral(roundOff(to[0] - last.to[0], 2)),
|
|
||||||
createLiteral(roundOff(to[1] - last.to[1], 2)),
|
const newLine = createCallback
|
||||||
]),
|
? createCallback([newXVal, newYVal])
|
||||||
|
: createCallExpression('line', [
|
||||||
|
createArrayExpression([newXVal, newYVal]),
|
||||||
createPipeSubstitution(),
|
createPipeSubstitution(),
|
||||||
])
|
])
|
||||||
|
const callIndex = getLastIndex(pathToNode)
|
||||||
|
if (replaceExisting) {
|
||||||
|
pipe.body[callIndex] = newLine
|
||||||
|
} else {
|
||||||
pipe.body = [...pipe.body, newLine]
|
pipe.body = [...pipe.body, newLine]
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
modifiedAst: _node,
|
modifiedAst: _node,
|
||||||
pathToNode,
|
pathToNode,
|
||||||
@ -529,7 +327,6 @@ export const line: SketchLineHelper = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
addTag: addTagWithTo('default'),
|
addTag: addTagWithTo('default'),
|
||||||
allowedTransforms: lineAndLineToAllowedTransforms,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const xLineTo: SketchLineHelper = {
|
export const xLineTo: SketchLineHelper = {
|
||||||
@ -558,10 +355,9 @@ export const xLineTo: SketchLineHelper = {
|
|||||||
const { node: pipe } = getNode<PipeExpression>('PipeExpression')
|
const { node: pipe } = getNode<PipeExpression>('PipeExpression')
|
||||||
|
|
||||||
const newVal = createLiteral(roundOff(to[0], 2))
|
const newVal = createLiteral(roundOff(to[0], 2))
|
||||||
const firstArg = newVal
|
|
||||||
const newLine = createCallback
|
const newLine = createCallback
|
||||||
? createCallback([firstArg, firstArg])
|
? createCallback([newVal, newVal])
|
||||||
: createCallExpression('xLineTo', [firstArg, createPipeSubstitution()])
|
: createCallExpression('xLineTo', [newVal, createPipeSubstitution()])
|
||||||
|
|
||||||
const callIndex = getLastIndex(pathToNode)
|
const callIndex = getLastIndex(pathToNode)
|
||||||
if (replaceExisting) {
|
if (replaceExisting) {
|
||||||
@ -592,7 +388,6 @@ export const xLineTo: SketchLineHelper = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
addTag: addTagWithTo('default'),
|
addTag: addTagWithTo('default'),
|
||||||
allowedTransforms: xyLineAllowedTransforms,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const yLineTo: SketchLineHelper = {
|
export const yLineTo: SketchLineHelper = {
|
||||||
@ -653,7 +448,6 @@ export const yLineTo: SketchLineHelper = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
addTag: addTagWithTo('default'),
|
addTag: addTagWithTo('default'),
|
||||||
allowedTransforms: xyLineAllowedTransforms,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const xLine: SketchLineHelper = {
|
export const xLine: SketchLineHelper = {
|
||||||
@ -709,7 +503,6 @@ export const xLine: SketchLineHelper = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
addTag: addTagWithTo('length'),
|
addTag: addTagWithTo('length'),
|
||||||
allowedTransforms: xyLineAllowedTransforms,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const yLine: SketchLineHelper = {
|
export const yLine: SketchLineHelper = {
|
||||||
@ -764,7 +557,6 @@ export const yLine: SketchLineHelper = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
addTag: addTagWithTo('length'),
|
addTag: addTagWithTo('length'),
|
||||||
allowedTransforms: xyLineAllowedTransforms,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const angledLine: SketchLineHelper = {
|
export const angledLine: SketchLineHelper = {
|
||||||
@ -819,35 +611,27 @@ export const angledLine: SketchLineHelper = {
|
|||||||
value: [...sketchGroup.value, currentPath],
|
value: [...sketchGroup.value, currentPath],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
add: ({
|
add: ({ node, pathToNode, to, from, createCallback, replaceExisting }) => {
|
||||||
node,
|
|
||||||
previousProgramMemory,
|
|
||||||
pathToNode,
|
|
||||||
to,
|
|
||||||
// from: [number, number],
|
|
||||||
}) => {
|
|
||||||
const _node = { ...node }
|
const _node = { ...node }
|
||||||
const { node: pipe } = getNodeFromPath<PipeExpression>(
|
const getNode = getNodeFromPathCurry(_node, pathToNode)
|
||||||
_node,
|
const { node: pipe } = getNode<PipeExpression>('PipeExpression')
|
||||||
pathToNode,
|
|
||||||
'PipeExpression'
|
if (!from) throw new Error('no from') // todo #29 remove
|
||||||
)
|
const newAngleVal = createLiteral(roundOff(getAngle(from, to), 0))
|
||||||
const { node: varDec } = getNodeFromPath<VariableDeclarator>(
|
const newLengthVal = createLiteral(roundOff(getLength(from, to), 2))
|
||||||
_node,
|
const newLine = createCallback
|
||||||
pathToNode,
|
? createCallback([newAngleVal, newLengthVal])
|
||||||
'VariableDeclarator'
|
: createCallExpression('angledLine', [
|
||||||
)
|
createArrayExpression([newAngleVal, newLengthVal]),
|
||||||
const variableName = varDec.id.name
|
|
||||||
const sketch = previousProgramMemory?.root?.[variableName]
|
|
||||||
if (sketch.type !== 'sketchGroup') throw new Error('not a sketchGroup')
|
|
||||||
const last = sketch.value[sketch.value.length - 1]
|
|
||||||
const angle = roundOff(getAngle(last.to, to), 0)
|
|
||||||
const lineLength = roundOff(getLength(last.to, to), 2)
|
|
||||||
const newLine = createCallExpression('angledLine', [
|
|
||||||
createArrayExpression([createLiteral(angle), createLiteral(lineLength)]),
|
|
||||||
createPipeSubstitution(),
|
createPipeSubstitution(),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
const callIndex = getLastIndex(pathToNode)
|
||||||
|
if (replaceExisting) {
|
||||||
|
pipe.body[callIndex] = newLine
|
||||||
|
} else {
|
||||||
pipe.body = [...pipe.body, newLine]
|
pipe.body = [...pipe.body, newLine]
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
modifiedAst: _node,
|
modifiedAst: _node,
|
||||||
pathToNode,
|
pathToNode,
|
||||||
@ -877,7 +661,6 @@ export const angledLine: SketchLineHelper = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
addTag: addTagWithTo('angleLength'),
|
addTag: addTagWithTo('angleLength'),
|
||||||
allowedTransforms: angledLineAllowedTransforms,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const angledLineOfXLength: SketchLineHelper = {
|
export const angledLineOfXLength: SketchLineHelper = {
|
||||||
@ -907,7 +690,9 @@ export const angledLineOfXLength: SketchLineHelper = {
|
|||||||
previousProgramMemory,
|
previousProgramMemory,
|
||||||
pathToNode,
|
pathToNode,
|
||||||
to,
|
to,
|
||||||
// from: [number, number],
|
from,
|
||||||
|
createCallback,
|
||||||
|
replaceExisting,
|
||||||
}) => {
|
}) => {
|
||||||
const _node = { ...node }
|
const _node = { ...node }
|
||||||
const { node: pipe } = getNodeFromPath<PipeExpression>(
|
const { node: pipe } = getNodeFromPath<PipeExpression>(
|
||||||
@ -923,14 +708,21 @@ export const angledLineOfXLength: SketchLineHelper = {
|
|||||||
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')
|
||||||
const last = sketch.value[sketch.value.length - 1]
|
if (!from) throw new Error('no from') // todo #29 remove
|
||||||
const angle = roundOff(getAngle(last.to, to), 0)
|
const angle = createLiteral(roundOff(getAngle(from, to), 0))
|
||||||
const xLength = roundOff(Math.abs(last.to[0] - to[0]), 2) || 0.1
|
const xLength = createLiteral(roundOff(Math.abs(from[0] - to[0]), 2) || 0.1)
|
||||||
const newLine = createCallExpression('angledLineOfXLength', [
|
const newLine = createCallback
|
||||||
createArrayExpression([createLiteral(angle), createLiteral(xLength)]),
|
? createCallback([angle, xLength])
|
||||||
|
: createCallExpression('angledLineOfXLength', [
|
||||||
|
createArrayExpression([angle, xLength]),
|
||||||
createPipeSubstitution(),
|
createPipeSubstitution(),
|
||||||
])
|
])
|
||||||
|
const callIndex = getLastIndex(pathToNode)
|
||||||
|
if (replaceExisting) {
|
||||||
|
pipe.body[callIndex] = newLine
|
||||||
|
} else {
|
||||||
pipe.body = [...pipe.body, newLine]
|
pipe.body = [...pipe.body, newLine]
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
modifiedAst: _node,
|
modifiedAst: _node,
|
||||||
pathToNode,
|
pathToNode,
|
||||||
@ -964,7 +756,6 @@ export const angledLineOfXLength: SketchLineHelper = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
addTag: addTagWithTo('angleLength'),
|
addTag: addTagWithTo('angleLength'),
|
||||||
allowedTransforms: angledLineAllowedTransforms,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const angledLineOfYLength: SketchLineHelper = {
|
export const angledLineOfYLength: SketchLineHelper = {
|
||||||
@ -993,7 +784,9 @@ export const angledLineOfYLength: SketchLineHelper = {
|
|||||||
previousProgramMemory,
|
previousProgramMemory,
|
||||||
pathToNode,
|
pathToNode,
|
||||||
to,
|
to,
|
||||||
// from: [number, number],
|
from,
|
||||||
|
createCallback,
|
||||||
|
replaceExisting,
|
||||||
}) => {
|
}) => {
|
||||||
const _node = { ...node }
|
const _node = { ...node }
|
||||||
const { node: pipe } = getNodeFromPath<PipeExpression>(
|
const { node: pipe } = getNodeFromPath<PipeExpression>(
|
||||||
@ -1009,14 +802,22 @@ export const angledLineOfYLength: SketchLineHelper = {
|
|||||||
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')
|
||||||
const last = sketch.value[sketch.value.length - 1]
|
if (!from) throw new Error('no from') // todo #29 remove
|
||||||
const angle = roundOff(getAngle(last.to, to), 0)
|
|
||||||
const yLength = roundOff(Math.abs(last.to[1] - to[1]), 2) || 0.1
|
const angle = createLiteral(roundOff(getAngle(from, to), 0))
|
||||||
const newLine = createCallExpression('angledLineOfYLength', [
|
const yLength = createLiteral(roundOff(Math.abs(from[1] - to[1]), 2) || 0.1)
|
||||||
createArrayExpression([createLiteral(angle), createLiteral(yLength)]),
|
const newLine = createCallback
|
||||||
|
? createCallback([angle, yLength])
|
||||||
|
: createCallExpression('angledLineOfYLength', [
|
||||||
|
createArrayExpression([angle, yLength]),
|
||||||
createPipeSubstitution(),
|
createPipeSubstitution(),
|
||||||
])
|
])
|
||||||
|
const callIndex = getLastIndex(pathToNode)
|
||||||
|
if (replaceExisting) {
|
||||||
|
pipe.body[callIndex] = newLine
|
||||||
|
} else {
|
||||||
pipe.body = [...pipe.body, newLine]
|
pipe.body = [...pipe.body, newLine]
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
modifiedAst: _node,
|
modifiedAst: _node,
|
||||||
pathToNode,
|
pathToNode,
|
||||||
@ -1050,7 +851,6 @@ export const angledLineOfYLength: SketchLineHelper = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
addTag: addTagWithTo('angleLength'),
|
addTag: addTagWithTo('angleLength'),
|
||||||
allowedTransforms: angledLineAllowedTransforms,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const angledLineToX: SketchLineHelper = {
|
export const angledLineToX: SketchLineHelper = {
|
||||||
@ -1083,9 +883,11 @@ export const angledLineToX: SketchLineHelper = {
|
|||||||
},
|
},
|
||||||
add: ({
|
add: ({
|
||||||
node,
|
node,
|
||||||
previousProgramMemory,
|
|
||||||
pathToNode,
|
pathToNode,
|
||||||
to,
|
to,
|
||||||
|
from,
|
||||||
|
createCallback,
|
||||||
|
replaceExisting,
|
||||||
// from: [number, number],
|
// from: [number, number],
|
||||||
}) => {
|
}) => {
|
||||||
const _node = { ...node }
|
const _node = { ...node }
|
||||||
@ -1094,22 +896,21 @@ export const angledLineToX: SketchLineHelper = {
|
|||||||
pathToNode,
|
pathToNode,
|
||||||
'PipeExpression'
|
'PipeExpression'
|
||||||
)
|
)
|
||||||
const { node: varDec } = getNodeFromPath<VariableDeclarator>(
|
if (!from) throw new Error('no from') // todo #29 remove
|
||||||
_node,
|
const angle = createLiteral(roundOff(getAngle(from, to), 0))
|
||||||
pathToNode,
|
const xArg = createLiteral(roundOff(to[0], 2))
|
||||||
'VariableDeclarator'
|
const newLine = createCallback
|
||||||
)
|
? createCallback([angle, xArg])
|
||||||
const variableName = varDec.id.name
|
: createCallExpression('angledLineToX', [
|
||||||
const sketch = previousProgramMemory?.root?.[variableName]
|
createArrayExpression([angle, xArg]),
|
||||||
if (sketch.type !== 'sketchGroup') throw new Error('not a sketchGroup')
|
|
||||||
const last = sketch.value[sketch.value.length - 1]
|
|
||||||
const angle = roundOff(getAngle(last.to, to), 0)
|
|
||||||
const xArg = roundOff(to[0], 2)
|
|
||||||
const newLine = createCallExpression('angledLineToX', [
|
|
||||||
createArrayExpression([createLiteral(angle), createLiteral(xArg)]),
|
|
||||||
createPipeSubstitution(),
|
createPipeSubstitution(),
|
||||||
])
|
])
|
||||||
|
const callIndex = getLastIndex(pathToNode)
|
||||||
|
if (replaceExisting) {
|
||||||
|
pipe.body[callIndex] = newLine
|
||||||
|
} else {
|
||||||
pipe.body = [...pipe.body, newLine]
|
pipe.body = [...pipe.body, newLine]
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
modifiedAst: _node,
|
modifiedAst: _node,
|
||||||
pathToNode,
|
pathToNode,
|
||||||
@ -1143,7 +944,6 @@ export const angledLineToX: SketchLineHelper = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
addTag: addTagWithTo('angleTo'),
|
addTag: addTagWithTo('angleTo'),
|
||||||
allowedTransforms: angledLineAllowedTransforms,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const angledLineToY: SketchLineHelper = {
|
export const angledLineToY: SketchLineHelper = {
|
||||||
@ -1179,7 +979,9 @@ export const angledLineToY: SketchLineHelper = {
|
|||||||
previousProgramMemory,
|
previousProgramMemory,
|
||||||
pathToNode,
|
pathToNode,
|
||||||
to,
|
to,
|
||||||
// from: [number, number],
|
from,
|
||||||
|
createCallback,
|
||||||
|
replaceExisting,
|
||||||
}) => {
|
}) => {
|
||||||
const _node = { ...node }
|
const _node = { ...node }
|
||||||
const { node: pipe } = getNodeFromPath<PipeExpression>(
|
const { node: pipe } = getNodeFromPath<PipeExpression>(
|
||||||
@ -1187,22 +989,21 @@ export const angledLineToY: SketchLineHelper = {
|
|||||||
pathToNode,
|
pathToNode,
|
||||||
'PipeExpression'
|
'PipeExpression'
|
||||||
)
|
)
|
||||||
const { node: varDec } = getNodeFromPath<VariableDeclarator>(
|
if (!from) throw new Error('no from') // todo #29 remove
|
||||||
_node,
|
const angle = createLiteral(roundOff(getAngle(from, to), 0))
|
||||||
pathToNode,
|
const yArg = createLiteral(roundOff(to[1], 2))
|
||||||
'VariableDeclarator'
|
const newLine = createCallback
|
||||||
)
|
? createCallback([angle, yArg])
|
||||||
const variableName = varDec.id.name
|
: createCallExpression('angledLineToY', [
|
||||||
const sketch = previousProgramMemory?.root?.[variableName]
|
createArrayExpression([angle, yArg]),
|
||||||
if (sketch.type !== 'sketchGroup') throw new Error('not a sketchGroup')
|
|
||||||
const last = sketch.value[sketch.value.length - 1]
|
|
||||||
const angle = roundOff(getAngle(last.to, to), 0)
|
|
||||||
const yArg = roundOff(to[1], 2)
|
|
||||||
const newLine = createCallExpression('angledLineToY', [
|
|
||||||
createArrayExpression([createLiteral(angle), createLiteral(yArg)]),
|
|
||||||
createPipeSubstitution(),
|
createPipeSubstitution(),
|
||||||
])
|
])
|
||||||
|
const callIndex = getLastIndex(pathToNode)
|
||||||
|
if (replaceExisting) {
|
||||||
|
pipe.body[callIndex] = newLine
|
||||||
|
} else {
|
||||||
pipe.body = [...pipe.body, newLine]
|
pipe.body = [...pipe.body, newLine]
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
modifiedAst: _node,
|
modifiedAst: _node,
|
||||||
pathToNode,
|
pathToNode,
|
||||||
@ -1236,7 +1037,6 @@ export const angledLineToY: SketchLineHelper = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
addTag: addTagWithTo('angleTo'),
|
addTag: addTagWithTo('angleTo'),
|
||||||
allowedTransforms: angledLineAllowedTransforms,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const sketchLineHelperMap: { [key: string]: SketchLineHelper } = {
|
export const sketchLineHelperMap: { [key: string]: SketchLineHelper } = {
|
||||||
@ -1619,17 +1419,27 @@ function getFirstArgValuesForXYLineFns(callExpression: CallExpression): {
|
|||||||
throw new Error('expected ArrayExpression or ObjectExpression')
|
throw new Error('expected ArrayExpression or ObjectExpression')
|
||||||
}
|
}
|
||||||
|
|
||||||
export function allowedTransforms(
|
export function getFirstArg(callExp: CallExpression): {
|
||||||
a: ModifyAstBase
|
val: Value | [Value, Value]
|
||||||
): Partial<SketchCallTransfromMap> {
|
tag?: Value
|
||||||
const { node, pathToNode } = a
|
} {
|
||||||
const { node: callExpression } = getNodeFromPath<CallExpression>(
|
const name = callExp?.callee?.name
|
||||||
node,
|
if (['lineTo', 'line'].includes(name)) {
|
||||||
pathToNode
|
return getFirstArgValuesForXYFns(callExp)
|
||||||
)
|
}
|
||||||
if (callExpression.type !== 'CallExpression') return {}
|
if (
|
||||||
const expressionName = callExpression?.callee?.name
|
[
|
||||||
const fn = sketchLineHelperMap?.[expressionName]?.allowedTransforms
|
'angledLine',
|
||||||
if (fn) return fn(a)
|
'angledLineOfXLength',
|
||||||
return {}
|
'angledLineToX',
|
||||||
|
'angledLineOfYLength',
|
||||||
|
'angledLineToY',
|
||||||
|
].includes(name)
|
||||||
|
) {
|
||||||
|
return getFirstArgValuesForAngleFns(callExp)
|
||||||
|
}
|
||||||
|
if (['xLine', 'yLine', 'xLineTo', 'yLineTo'].includes(name)) {
|
||||||
|
return getFirstArgValuesForXYLineFns(callExp)
|
||||||
|
}
|
||||||
|
throw new Error('unexpected call expression')
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
import {
|
import { abstractSyntaxTree } from '../abstractSyntaxTree'
|
||||||
abstractSyntaxTree,
|
|
||||||
getNodePathFromSourceRange,
|
|
||||||
} from '../abstractSyntaxTree'
|
|
||||||
import { executor } from '../executor'
|
import { executor } from '../executor'
|
||||||
import { lexer } from '../tokeniser'
|
import { lexer } from '../tokeniser'
|
||||||
import { swapSketchHelper } from './sketchConstraints'
|
import {
|
||||||
import { allowedTransforms } from './sketch'
|
ConstraintType,
|
||||||
|
getTransformInfos,
|
||||||
|
transformAstForHorzVert,
|
||||||
|
} from './sketchcombos'
|
||||||
import { recast } from '../recast'
|
import { recast } from '../recast'
|
||||||
import { initPromise } from '../rust'
|
import { initPromise } from '../rust'
|
||||||
import { TooTip } from '../../useStore'
|
|
||||||
|
|
||||||
beforeAll(() => initPromise)
|
beforeAll(() => initPromise)
|
||||||
|
|
||||||
@ -16,13 +15,11 @@ beforeAll(() => initPromise)
|
|||||||
function testingSwapSketchFnCall({
|
function testingSwapSketchFnCall({
|
||||||
inputCode,
|
inputCode,
|
||||||
callToSwap,
|
callToSwap,
|
||||||
// expectedNewCall,
|
constraintType,
|
||||||
toFnCallName,
|
|
||||||
}: {
|
}: {
|
||||||
inputCode: string
|
inputCode: string
|
||||||
callToSwap: string
|
callToSwap: string
|
||||||
// expectedNewCall: string
|
constraintType: ConstraintType
|
||||||
toFnCallName: TooTip
|
|
||||||
}): {
|
}): {
|
||||||
newCode: string
|
newCode: string
|
||||||
originalRange: [number, number]
|
originalRange: [number, number]
|
||||||
@ -32,21 +29,15 @@ function testingSwapSketchFnCall({
|
|||||||
const tokens = lexer(inputCode)
|
const tokens = lexer(inputCode)
|
||||||
const ast = abstractSyntaxTree(tokens)
|
const ast = abstractSyntaxTree(tokens)
|
||||||
const programMemory = executor(ast)
|
const programMemory = executor(ast)
|
||||||
const pathToNode = getNodePathFromSourceRange(ast, range)
|
const transformInfos = getTransformInfos([range], ast, constraintType)
|
||||||
const _allowedTransforms = allowedTransforms({
|
|
||||||
node: ast,
|
if (!transformInfos) throw new Error('nope')
|
||||||
previousProgramMemory: programMemory,
|
const { modifiedAst } = transformAstForHorzVert({
|
||||||
pathToNode,
|
|
||||||
})
|
|
||||||
const transformCallback = _allowedTransforms[toFnCallName]
|
|
||||||
if (!transformCallback) throw new Error('nope')
|
|
||||||
const { modifiedAst } = swapSketchHelper(
|
|
||||||
programMemory,
|
|
||||||
ast,
|
ast,
|
||||||
range,
|
programMemory,
|
||||||
toFnCallName,
|
selectionRanges: [range],
|
||||||
transformCallback
|
transformInfos,
|
||||||
)
|
})
|
||||||
return {
|
return {
|
||||||
newCode: recast(modifiedAst),
|
newCode: recast(modifiedAst),
|
||||||
originalRange: range,
|
originalRange: range,
|
||||||
@ -99,7 +90,7 @@ describe('testing swaping out sketch calls with xLine/xLineTo', () => {
|
|||||||
const { newCode, originalRange } = testingSwapSketchFnCall({
|
const { newCode, originalRange } = testingSwapSketchFnCall({
|
||||||
inputCode: bigExample,
|
inputCode: bigExample,
|
||||||
callToSwap,
|
callToSwap,
|
||||||
toFnCallName: 'xLine',
|
constraintType: 'horizontal',
|
||||||
})
|
})
|
||||||
expect(newCode).toContain(expectedLine)
|
expect(newCode).toContain(expectedLine)
|
||||||
// new line should start at the same place as the old line
|
// new line should start at the same place as the old line
|
||||||
@ -111,7 +102,7 @@ describe('testing swaping out sketch calls with xLine/xLineTo', () => {
|
|||||||
const { newCode, originalRange } = testingSwapSketchFnCall({
|
const { newCode, originalRange } = testingSwapSketchFnCall({
|
||||||
inputCode: bigExample,
|
inputCode: bigExample,
|
||||||
callToSwap,
|
callToSwap,
|
||||||
toFnCallName: 'xLine',
|
constraintType: 'horizontal',
|
||||||
})
|
})
|
||||||
expect(newCode).toContain(expectedLine)
|
expect(newCode).toContain(expectedLine)
|
||||||
// new line should start at the same place as the old line
|
// new line should start at the same place as the old line
|
||||||
@ -121,7 +112,7 @@ describe('testing swaping out sketch calls with xLine/xLineTo', () => {
|
|||||||
const { newCode, originalRange } = testingSwapSketchFnCall({
|
const { newCode, originalRange } = testingSwapSketchFnCall({
|
||||||
inputCode: bigExample,
|
inputCode: bigExample,
|
||||||
callToSwap: "lineTo({ to: [1, 1], tag: 'abc1' }, %)",
|
callToSwap: "lineTo({ to: [1, 1], tag: 'abc1' }, %)",
|
||||||
toFnCallName: 'xLineTo',
|
constraintType: 'horizontal',
|
||||||
})
|
})
|
||||||
const expectedLine = "xLineTo({ to: 1, tag: 'abc1' }, %)"
|
const expectedLine = "xLineTo({ to: 1, tag: 'abc1' }, %)"
|
||||||
expect(newCode).toContain(expectedLine)
|
expect(newCode).toContain(expectedLine)
|
||||||
@ -132,7 +123,7 @@ describe('testing swaping out sketch calls with xLine/xLineTo', () => {
|
|||||||
const { newCode, originalRange } = testingSwapSketchFnCall({
|
const { newCode, originalRange } = testingSwapSketchFnCall({
|
||||||
inputCode: bigExample,
|
inputCode: bigExample,
|
||||||
callToSwap: 'lineTo([2.55, 3.58], %)',
|
callToSwap: 'lineTo([2.55, 3.58], %)',
|
||||||
toFnCallName: 'xLineTo',
|
constraintType: 'horizontal',
|
||||||
})
|
})
|
||||||
const expectedLine = 'xLineTo(2.55, %) // lineTo'
|
const expectedLine = 'xLineTo(2.55, %) // lineTo'
|
||||||
expect(newCode).toContain(expectedLine)
|
expect(newCode).toContain(expectedLine)
|
||||||
@ -149,7 +140,7 @@ describe('testing swaping out sketch calls with xLine/xLineTo', () => {
|
|||||||
` tag: 'abc3'`,
|
` tag: 'abc3'`,
|
||||||
`}, %)`,
|
`}, %)`,
|
||||||
].join('\n'),
|
].join('\n'),
|
||||||
toFnCallName: 'xLine',
|
constraintType: 'horizontal',
|
||||||
})
|
})
|
||||||
const expectedLine = "xLine({ length: -1.56, tag: 'abc3' }, %)"
|
const expectedLine = "xLine({ length: -1.56, tag: 'abc3' }, %)"
|
||||||
expect(newCode).toContain(expectedLine)
|
expect(newCode).toContain(expectedLine)
|
||||||
@ -160,7 +151,7 @@ describe('testing swaping out sketch calls with xLine/xLineTo', () => {
|
|||||||
const { newCode, originalRange } = testingSwapSketchFnCall({
|
const { newCode, originalRange } = testingSwapSketchFnCall({
|
||||||
inputCode: bigExample,
|
inputCode: bigExample,
|
||||||
callToSwap: 'angledLine([63, 1.38], %)',
|
callToSwap: 'angledLine([63, 1.38], %)',
|
||||||
toFnCallName: 'xLine',
|
constraintType: 'horizontal',
|
||||||
})
|
})
|
||||||
const expectedLine = 'xLine(0.63, %) // angledLine'
|
const expectedLine = 'xLine(0.63, %) // angledLine'
|
||||||
expect(newCode).toContain(expectedLine)
|
expect(newCode).toContain(expectedLine)
|
||||||
@ -177,7 +168,7 @@ describe('testing swaping out sketch calls with xLine/xLineTo', () => {
|
|||||||
` tag: 'abc4'`,
|
` tag: 'abc4'`,
|
||||||
`}, %)`,
|
`}, %)`,
|
||||||
].join('\n'),
|
].join('\n'),
|
||||||
toFnCallName: 'xLine',
|
constraintType: 'horizontal',
|
||||||
})
|
})
|
||||||
const expectedLine = "xLine({ length: -0.86, tag: 'abc4' }, %)"
|
const expectedLine = "xLine({ length: -0.86, tag: 'abc4' }, %)"
|
||||||
// hmm "-0.86" is correct since the angle is 104, but need to make sure this is compatiable `-myVar`
|
// hmm "-0.86" is correct since the angle is 104, but need to make sure this is compatiable `-myVar`
|
||||||
@ -189,7 +180,7 @@ describe('testing swaping out sketch calls with xLine/xLineTo', () => {
|
|||||||
const { newCode, originalRange } = testingSwapSketchFnCall({
|
const { newCode, originalRange } = testingSwapSketchFnCall({
|
||||||
inputCode: bigExample,
|
inputCode: bigExample,
|
||||||
callToSwap: 'angledLineOfXLength([319, 1.15], %)',
|
callToSwap: 'angledLineOfXLength([319, 1.15], %)',
|
||||||
toFnCallName: 'xLine',
|
constraintType: 'horizontal',
|
||||||
})
|
})
|
||||||
const expectedLine = 'xLine(1.15, %) // angledLineOfXLength'
|
const expectedLine = 'xLine(1.15, %) // angledLineOfXLength'
|
||||||
expect(newCode).toContain(expectedLine)
|
expect(newCode).toContain(expectedLine)
|
||||||
@ -206,7 +197,7 @@ describe('testing swaping out sketch calls with xLine/xLineTo', () => {
|
|||||||
` tag: 'abc5'`,
|
` tag: 'abc5'`,
|
||||||
`}, %)`,
|
`}, %)`,
|
||||||
].join('\n'),
|
].join('\n'),
|
||||||
toFnCallName: 'yLine',
|
constraintType: 'vertical',
|
||||||
})
|
})
|
||||||
const expectedLine = "yLine({ length: 1.58, tag: 'abc5' }, %)"
|
const expectedLine = "yLine({ length: 1.58, tag: 'abc5' }, %)"
|
||||||
expect(newCode).toContain(expectedLine)
|
expect(newCode).toContain(expectedLine)
|
||||||
@ -217,7 +208,7 @@ describe('testing swaping out sketch calls with xLine/xLineTo', () => {
|
|||||||
const { newCode, originalRange } = testingSwapSketchFnCall({
|
const { newCode, originalRange } = testingSwapSketchFnCall({
|
||||||
inputCode: bigExample,
|
inputCode: bigExample,
|
||||||
callToSwap: 'angledLineOfYLength([50, 1.35], %)',
|
callToSwap: 'angledLineOfYLength([50, 1.35], %)',
|
||||||
toFnCallName: 'yLine',
|
constraintType: 'vertical',
|
||||||
})
|
})
|
||||||
const expectedLine = 'yLine(1.35, %) // angledLineOfYLength'
|
const expectedLine = 'yLine(1.35, %) // angledLineOfYLength'
|
||||||
expect(newCode).toContain(expectedLine)
|
expect(newCode).toContain(expectedLine)
|
||||||
@ -228,7 +219,7 @@ describe('testing swaping out sketch calls with xLine/xLineTo', () => {
|
|||||||
const { newCode, originalRange } = testingSwapSketchFnCall({
|
const { newCode, originalRange } = testingSwapSketchFnCall({
|
||||||
inputCode: bigExample,
|
inputCode: bigExample,
|
||||||
callToSwap: "angledLineToX({ angle: 55, to: -2.89, tag: 'abc6' }, %)",
|
callToSwap: "angledLineToX({ angle: 55, to: -2.89, tag: 'abc6' }, %)",
|
||||||
toFnCallName: 'xLineTo',
|
constraintType: 'horizontal',
|
||||||
})
|
})
|
||||||
const expectedLine = "xLineTo({ to: -2.89, tag: 'abc6' }, %)"
|
const expectedLine = "xLineTo({ to: -2.89, tag: 'abc6' }, %)"
|
||||||
expect(newCode).toContain(expectedLine)
|
expect(newCode).toContain(expectedLine)
|
||||||
@ -239,7 +230,7 @@ describe('testing swaping out sketch calls with xLine/xLineTo', () => {
|
|||||||
const { newCode, originalRange } = testingSwapSketchFnCall({
|
const { newCode, originalRange } = testingSwapSketchFnCall({
|
||||||
inputCode: bigExample,
|
inputCode: bigExample,
|
||||||
callToSwap: 'angledLineToX([291, 6.66], %)',
|
callToSwap: 'angledLineToX([291, 6.66], %)',
|
||||||
toFnCallName: 'xLineTo',
|
constraintType: 'horizontal',
|
||||||
})
|
})
|
||||||
const expectedLine = 'xLineTo(6.66, %) // angledLineToX'
|
const expectedLine = 'xLineTo(6.66, %) // angledLineToX'
|
||||||
expect(newCode).toContain(expectedLine)
|
expect(newCode).toContain(expectedLine)
|
||||||
@ -250,7 +241,7 @@ describe('testing swaping out sketch calls with xLine/xLineTo', () => {
|
|||||||
const { newCode, originalRange } = testingSwapSketchFnCall({
|
const { newCode, originalRange } = testingSwapSketchFnCall({
|
||||||
inputCode: bigExample,
|
inputCode: bigExample,
|
||||||
callToSwap: "angledLineToY({ angle: 330, to: 2.53, tag: 'abc7' }, %)",
|
callToSwap: "angledLineToY({ angle: 330, to: 2.53, tag: 'abc7' }, %)",
|
||||||
toFnCallName: 'yLineTo',
|
constraintType: 'vertical',
|
||||||
})
|
})
|
||||||
const expectedLine = "yLineTo({ to: 2.53, tag: 'abc7' }, %)"
|
const expectedLine = "yLineTo({ to: 2.53, tag: 'abc7' }, %)"
|
||||||
expect(newCode).toContain(expectedLine)
|
expect(newCode).toContain(expectedLine)
|
||||||
@ -261,7 +252,7 @@ describe('testing swaping out sketch calls with xLine/xLineTo', () => {
|
|||||||
const { newCode, originalRange } = testingSwapSketchFnCall({
|
const { newCode, originalRange } = testingSwapSketchFnCall({
|
||||||
inputCode: bigExample,
|
inputCode: bigExample,
|
||||||
callToSwap: 'angledLineToY([228, 2.14], %)',
|
callToSwap: 'angledLineToY([228, 2.14], %)',
|
||||||
toFnCallName: 'yLineTo',
|
constraintType: 'vertical',
|
||||||
})
|
})
|
||||||
const expectedLine = 'yLineTo(2.14, %) // angledLineToY'
|
const expectedLine = 'yLineTo(2.14, %) // angledLineToY'
|
||||||
expect(newCode).toContain(expectedLine)
|
expect(newCode).toContain(expectedLine)
|
||||||
@ -297,7 +288,7 @@ describe('testing swaping out sketch calls with xLine/xLineTo while keeping vari
|
|||||||
const { newCode, originalRange } = testingSwapSketchFnCall({
|
const { newCode, originalRange } = testingSwapSketchFnCall({
|
||||||
inputCode: varExample,
|
inputCode: varExample,
|
||||||
callToSwap: 'line([lineX, 2.13], %)',
|
callToSwap: 'line([lineX, 2.13], %)',
|
||||||
toFnCallName: 'xLine',
|
constraintType: 'horizontal',
|
||||||
})
|
})
|
||||||
const expectedLine = 'xLine(lineX, %)'
|
const expectedLine = 'xLine(lineX, %)'
|
||||||
expect(newCode).toContain(expectedLine)
|
expect(newCode).toContain(expectedLine)
|
||||||
@ -308,7 +299,7 @@ describe('testing swaping out sketch calls with xLine/xLineTo while keeping vari
|
|||||||
const { newCode, originalRange } = testingSwapSketchFnCall({
|
const { newCode, originalRange } = testingSwapSketchFnCall({
|
||||||
inputCode: varExample,
|
inputCode: varExample,
|
||||||
callToSwap: 'lineTo([lineToX, 2.85], %)',
|
callToSwap: 'lineTo([lineToX, 2.85], %)',
|
||||||
toFnCallName: 'xLineTo',
|
constraintType: 'horizontal',
|
||||||
})
|
})
|
||||||
const expectedLine = 'xLineTo(lineToX, %)'
|
const expectedLine = 'xLineTo(lineToX, %)'
|
||||||
expect(newCode).toContain(expectedLine)
|
expect(newCode).toContain(expectedLine)
|
||||||
@ -319,7 +310,7 @@ describe('testing swaping out sketch calls with xLine/xLineTo while keeping vari
|
|||||||
const { newCode, originalRange } = testingSwapSketchFnCall({
|
const { newCode, originalRange } = testingSwapSketchFnCall({
|
||||||
inputCode: varExample,
|
inputCode: varExample,
|
||||||
callToSwap: 'angledLineOfXLength([329, angledLineOfXLengthX], %)',
|
callToSwap: 'angledLineOfXLength([329, angledLineOfXLengthX], %)',
|
||||||
toFnCallName: 'xLine',
|
constraintType: 'horizontal',
|
||||||
})
|
})
|
||||||
const expectedLine = 'xLine(angledLineOfXLengthX, %)'
|
const expectedLine = 'xLine(angledLineOfXLengthX, %)'
|
||||||
expect(newCode).toContain(expectedLine)
|
expect(newCode).toContain(expectedLine)
|
||||||
@ -330,9 +321,9 @@ describe('testing swaping out sketch calls with xLine/xLineTo while keeping vari
|
|||||||
const { newCode, originalRange } = testingSwapSketchFnCall({
|
const { newCode, originalRange } = testingSwapSketchFnCall({
|
||||||
inputCode: varExample,
|
inputCode: varExample,
|
||||||
callToSwap: 'angledLineOfYLength([222, angledLineOfYLengthY], %)',
|
callToSwap: 'angledLineOfYLength([222, angledLineOfYLengthY], %)',
|
||||||
toFnCallName: 'yLine',
|
constraintType: 'vertical',
|
||||||
})
|
})
|
||||||
const expectedLine = 'yLine(angledLineOfYLengthY, %)'
|
const expectedLine = 'yLine(-angledLineOfYLengthY, %)'
|
||||||
expect(newCode).toContain(expectedLine)
|
expect(newCode).toContain(expectedLine)
|
||||||
// new line should start at the same place as the old line
|
// new line should start at the same place as the old line
|
||||||
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
|
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
|
||||||
@ -341,7 +332,7 @@ describe('testing swaping out sketch calls with xLine/xLineTo while keeping vari
|
|||||||
const { newCode, originalRange } = testingSwapSketchFnCall({
|
const { newCode, originalRange } = testingSwapSketchFnCall({
|
||||||
inputCode: varExample,
|
inputCode: varExample,
|
||||||
callToSwap: 'angledLineToX([330, angledLineToXx], %)',
|
callToSwap: 'angledLineToX([330, angledLineToXx], %)',
|
||||||
toFnCallName: 'xLineTo',
|
constraintType: 'horizontal',
|
||||||
})
|
})
|
||||||
const expectedLine = 'xLineTo(angledLineToXx, %)'
|
const expectedLine = 'xLineTo(angledLineToXx, %)'
|
||||||
expect(newCode).toContain(expectedLine)
|
expect(newCode).toContain(expectedLine)
|
||||||
@ -352,7 +343,7 @@ describe('testing swaping out sketch calls with xLine/xLineTo while keeping vari
|
|||||||
const { newCode, originalRange } = testingSwapSketchFnCall({
|
const { newCode, originalRange } = testingSwapSketchFnCall({
|
||||||
inputCode: varExample,
|
inputCode: varExample,
|
||||||
callToSwap: 'angledLineToY([217, angledLineToYy], %)',
|
callToSwap: 'angledLineToY([217, angledLineToYy], %)',
|
||||||
toFnCallName: 'yLineTo',
|
constraintType: 'vertical',
|
||||||
})
|
})
|
||||||
const expectedLine = 'yLineTo(angledLineToYy, %)'
|
const expectedLine = 'yLineTo(angledLineToYy, %)'
|
||||||
expect(newCode).toContain(expectedLine)
|
expect(newCode).toContain(expectedLine)
|
||||||
@ -365,7 +356,7 @@ describe('testing swaping out sketch calls with xLine/xLineTo while keeping vari
|
|||||||
testingSwapSketchFnCall({
|
testingSwapSketchFnCall({
|
||||||
inputCode: varExample,
|
inputCode: varExample,
|
||||||
callToSwap: 'angledLineToY([217, angledLineToYy], %)',
|
callToSwap: 'angledLineToY([217, angledLineToYy], %)',
|
||||||
toFnCallName: 'xLineTo',
|
constraintType: 'horizontal',
|
||||||
})
|
})
|
||||||
expect(illegalConvert).toThrowError()
|
expect(illegalConvert).toThrowError()
|
||||||
})
|
})
|
||||||
|
@ -1,46 +1,13 @@
|
|||||||
import { Range, TooTip } from '../../useStore'
|
import { Range, TooTip, toolTips } from '../../useStore'
|
||||||
import {
|
import {
|
||||||
getNodePathFromSourceRange,
|
|
||||||
getNodeFromPath,
|
|
||||||
Program,
|
Program,
|
||||||
VariableDeclarator,
|
VariableDeclarator,
|
||||||
} from '../../lang/abstractSyntaxTree'
|
CallExpression,
|
||||||
import { replaceSketchLine } from '../../lang/std/sketch'
|
} from '../abstractSyntaxTree'
|
||||||
import { ProgramMemory, SketchGroup } from '../../lang/executor'
|
import { SketchGroup } from '../executor'
|
||||||
import { TransformCallback } from '../../lang/std/stdTypes'
|
import { InternalFn } from './stdTypes'
|
||||||
|
|
||||||
export function swapSketchHelper(
|
export function getSketchSegmentIndexFromSourceRange(
|
||||||
programMemory: ProgramMemory,
|
|
||||||
ast: Program,
|
|
||||||
range: Range,
|
|
||||||
newFnName: TooTip,
|
|
||||||
createCallback: TransformCallback
|
|
||||||
): { modifiedAst: Program } {
|
|
||||||
const path = getNodePathFromSourceRange(ast, range)
|
|
||||||
const varDec = getNodeFromPath<VariableDeclarator>(
|
|
||||||
ast,
|
|
||||||
path,
|
|
||||||
'VariableDeclarator'
|
|
||||||
).node
|
|
||||||
const varName = varDec.id.name
|
|
||||||
const sketchGroup = programMemory.root?.[varName]
|
|
||||||
if (!sketchGroup || sketchGroup.type !== 'sketchGroup')
|
|
||||||
throw new Error('not a sketch group')
|
|
||||||
const seg = getSketchSegmentIndexFromSourceRange(sketchGroup, range)
|
|
||||||
const { to, from } = seg
|
|
||||||
const { modifiedAst } = replaceSketchLine({
|
|
||||||
node: ast,
|
|
||||||
sourceRange: range,
|
|
||||||
programMemory,
|
|
||||||
fnName: newFnName,
|
|
||||||
to,
|
|
||||||
from,
|
|
||||||
createCallback,
|
|
||||||
})
|
|
||||||
return { modifiedAst }
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSketchSegmentIndexFromSourceRange(
|
|
||||||
sketchGroup: SketchGroup,
|
sketchGroup: SketchGroup,
|
||||||
[rangeStart, rangeEnd]: Range
|
[rangeStart, rangeEnd]: Range
|
||||||
): SketchGroup['value'][number] {
|
): SketchGroup['value'][number] {
|
||||||
@ -51,3 +18,94 @@ function getSketchSegmentIndexFromSourceRange(
|
|||||||
if (!line) throw new Error('could not find matching line')
|
if (!line) throw new Error('could not find matching line')
|
||||||
return line
|
return line
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const segLen: InternalFn = (
|
||||||
|
_,
|
||||||
|
segName: string,
|
||||||
|
sketchGroup: SketchGroup
|
||||||
|
): number => {
|
||||||
|
const line = sketchGroup?.value.find((seg) => seg.name === segName)
|
||||||
|
// maybe this should throw, but the language doesn't have a way to handle errors yet
|
||||||
|
if (!line) return 0
|
||||||
|
|
||||||
|
return Math.sqrt(
|
||||||
|
(line.from[1] - line.to[1]) ** 2 + (line.from[0] - line.to[0]) ** 2
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function angleToMatchLengthFactory(which: 'x' | 'y'): InternalFn {
|
||||||
|
return (_, segName: string, to: number, sketchGroup: SketchGroup): number => {
|
||||||
|
const isX = which === 'x'
|
||||||
|
const lineToMatch = sketchGroup?.value.find((seg) => seg.name === segName)
|
||||||
|
// maybe this should throw, but the language doesn't have a way to handle errors yet
|
||||||
|
if (!lineToMatch) return 0
|
||||||
|
const lengthToMatch = Math.sqrt(
|
||||||
|
(lineToMatch.from[1] - lineToMatch.to[1]) ** 2 +
|
||||||
|
(lineToMatch.from[0] - lineToMatch.to[0]) ** 2
|
||||||
|
)
|
||||||
|
|
||||||
|
const lastLine = sketchGroup?.value[sketchGroup.value.length - 1]
|
||||||
|
const diff = Math.abs(to - (isX ? lastLine.to[0] : lastLine.to[1]))
|
||||||
|
|
||||||
|
const angleR = Math[isX ? 'acos' : 'asin'](diff / lengthToMatch)
|
||||||
|
|
||||||
|
return diff > lengthToMatch ? 0 : (angleR * 180) / Math.PI
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const angleToMatchLengthX: InternalFn = angleToMatchLengthFactory('x')
|
||||||
|
export const angleToMatchLengthY: InternalFn = angleToMatchLengthFactory('y')
|
||||||
|
|
||||||
|
export function isSketchVariablesLinked(
|
||||||
|
secondaryVarDec: VariableDeclarator,
|
||||||
|
primaryVarDec: VariableDeclarator,
|
||||||
|
ast: Program
|
||||||
|
): boolean {
|
||||||
|
/*
|
||||||
|
checks if two callExpressions are part of the same pipe
|
||||||
|
if not than checks if the second argument is a variable that is linked to the primary variable declaration
|
||||||
|
and will keep checking the second arguments recursively until it runs out of variable declarations
|
||||||
|
to check or it finds a match.
|
||||||
|
that way it can find fn calls that are linked to each other through variables eg:
|
||||||
|
const part001 = startSketchAt([0, 0])
|
||||||
|
|> xLineTo(1.69, %)
|
||||||
|
|> line([myVar, 0.38], %) // ❗️ <- cursor in this fn call (the primary)
|
||||||
|
|> line([0.41, baz], %)
|
||||||
|
|> xLine(0.91, %)
|
||||||
|
|> angledLine([37, 2], %)
|
||||||
|
const yo = line([myVar, 0.38], part001)
|
||||||
|
|> line([1, 1], %)
|
||||||
|
const yo2 = line([myVar, 0.38], yo)
|
||||||
|
|> line([1, 1], %) // ❗️ <- and cursor here (secondary) is linked to the one above through variables
|
||||||
|
*/
|
||||||
|
const secondaryVarName = secondaryVarDec?.id?.name
|
||||||
|
if (!secondaryVarName) return false
|
||||||
|
if (secondaryVarName === primaryVarDec?.id?.name) return true
|
||||||
|
const { init } = secondaryVarDec
|
||||||
|
if (
|
||||||
|
!init ||
|
||||||
|
!(init.type === 'CallExpression' || init.type === 'PipeExpression')
|
||||||
|
)
|
||||||
|
return false
|
||||||
|
const firstCallExp = // first in pipe expression or just the call expression
|
||||||
|
init?.type === 'CallExpression' ? init : (init?.body[0] as CallExpression)
|
||||||
|
if (!firstCallExp || !toolTips.includes(firstCallExp?.callee?.name as TooTip))
|
||||||
|
return false
|
||||||
|
// convention for sketch fns is that the second argument is the sketch group
|
||||||
|
const secondArg = firstCallExp?.arguments[1]
|
||||||
|
if (!secondArg || secondArg?.type !== 'Identifier') return false
|
||||||
|
if (secondArg.name === primaryVarDec?.id?.name) return true
|
||||||
|
|
||||||
|
let nextVarDec: VariableDeclarator | undefined
|
||||||
|
for (const node of ast.body) {
|
||||||
|
if (node.type !== 'VariableDeclaration') continue
|
||||||
|
const found = node.declarations.find(
|
||||||
|
({ id }) => id?.name === secondArg.name
|
||||||
|
)
|
||||||
|
if (!found) continue
|
||||||
|
nextVarDec = found
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (!nextVarDec) return false
|
||||||
|
return isSketchVariablesLinked(nextVarDec, primaryVarDec, ast)
|
||||||
|
}
|
||||||
|
350
src/lang/std/sketchcombos.test.ts
Normal file
350
src/lang/std/sketchcombos.test.ts
Normal file
@ -0,0 +1,350 @@
|
|||||||
|
import { abstractSyntaxTree, Value } from '../abstractSyntaxTree'
|
||||||
|
import { lexer } from '../tokeniser'
|
||||||
|
import {
|
||||||
|
getConstraintType,
|
||||||
|
getTransformInfos,
|
||||||
|
transformAstForSketchLines,
|
||||||
|
transformAstForHorzVert,
|
||||||
|
} from './sketchcombos'
|
||||||
|
import { initPromise } from '../rust'
|
||||||
|
import { Ranges, TooTip } from '../../useStore'
|
||||||
|
import { executor } from '../../lang/executor'
|
||||||
|
import { recast } from '../../lang/recast'
|
||||||
|
|
||||||
|
beforeAll(() => initPromise)
|
||||||
|
|
||||||
|
describe('testing getConstraintType', () => {
|
||||||
|
const helper = getConstraintTypeFromSourceHelper
|
||||||
|
it('testing line', () => {
|
||||||
|
expect(helper(`line([5, myVar], %)`)).toBe('yRelative')
|
||||||
|
expect(helper(`line([myVar, 5], %)`)).toBe('xRelative')
|
||||||
|
})
|
||||||
|
it('testing lineTo', () => {
|
||||||
|
expect(helper(`lineTo([5, myVar], %)`)).toBe('yAbsolute')
|
||||||
|
expect(helper(`lineTo([myVar, 5], %)`)).toBe('xAbsolute')
|
||||||
|
})
|
||||||
|
it('testing angledLine', () => {
|
||||||
|
expect(helper(`angledLine([5, myVar], %)`)).toBe('length')
|
||||||
|
expect(helper(`angledLine([myVar, 5], %)`)).toBe('angle')
|
||||||
|
})
|
||||||
|
it('testing angledLineOfXLength', () => {
|
||||||
|
expect(helper(`angledLineOfXLength([5, myVar], %)`)).toBe('xRelative')
|
||||||
|
expect(helper(`angledLineOfXLength([myVar, 5], %)`)).toBe('angle')
|
||||||
|
})
|
||||||
|
it('testing angledLineToX', () => {
|
||||||
|
expect(helper(`angledLineToX([5, myVar], %)`)).toBe('xAbsolute')
|
||||||
|
expect(helper(`angledLineToX([myVar, 5], %)`)).toBe('angle')
|
||||||
|
})
|
||||||
|
it('testing angledLineOfYLength', () => {
|
||||||
|
expect(helper(`angledLineOfYLength([5, myVar], %)`)).toBe('yRelative')
|
||||||
|
expect(helper(`angledLineOfYLength([myVar, 5], %)`)).toBe('angle')
|
||||||
|
})
|
||||||
|
it('testing angledLineToY', () => {
|
||||||
|
expect(helper(`angledLineToY([5, myVar], %)`)).toBe('yAbsolute')
|
||||||
|
expect(helper(`angledLineToY([myVar, 5], %)`)).toBe('angle')
|
||||||
|
})
|
||||||
|
const helper2 = getConstraintTypeFromSourceHelper2
|
||||||
|
it('testing xLine', () => {
|
||||||
|
expect(helper2(`xLine(5, %)`)).toBe('yRelative')
|
||||||
|
})
|
||||||
|
it('testing xLine', () => {
|
||||||
|
expect(helper2(`yLine(5, %)`)).toBe('xRelative')
|
||||||
|
})
|
||||||
|
it('testing xLineTo', () => {
|
||||||
|
expect(helper2(`xLineTo(5, %)`)).toBe('yAbsolute')
|
||||||
|
})
|
||||||
|
it('testing yLineTo', () => {
|
||||||
|
expect(helper2(`yLineTo(5, %)`)).toBe('xAbsolute')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
function getConstraintTypeFromSourceHelper(
|
||||||
|
code: string
|
||||||
|
): ReturnType<typeof getConstraintType> {
|
||||||
|
const ast = abstractSyntaxTree(lexer(code))
|
||||||
|
const args = (ast.body[0] as any).expression.arguments[0].elements as [
|
||||||
|
Value,
|
||||||
|
Value
|
||||||
|
]
|
||||||
|
const fnName = (ast.body[0] as any).expression.callee.name as TooTip
|
||||||
|
return getConstraintType(args, fnName)
|
||||||
|
}
|
||||||
|
function getConstraintTypeFromSourceHelper2(
|
||||||
|
code: string
|
||||||
|
): ReturnType<typeof getConstraintType> {
|
||||||
|
const ast = abstractSyntaxTree(lexer(code))
|
||||||
|
const arg = (ast.body[0] as any).expression.arguments[0] as Value
|
||||||
|
const fnName = (ast.body[0] as any).expression.callee.name as TooTip
|
||||||
|
return getConstraintType(arg, fnName)
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('testing transformAstForSketchLines for equal length constraint', () => {
|
||||||
|
const inputScript = `const myVar = 3
|
||||||
|
const myVar2 = 5
|
||||||
|
const myVar3 = 6
|
||||||
|
const myAng = 40
|
||||||
|
const myAng2 = 134
|
||||||
|
const part001 = startSketchAt([0, 0])
|
||||||
|
|> line([1, 3.82], %) // ln-should-get-tag
|
||||||
|
|> lineTo([myVar, 1], %) // ln-lineTo-xAbsolute should use angleToMatchLengthX helper
|
||||||
|
|> lineTo([1, myVar], %) // ln-lineTo-yAbsolute should use angleToMatchLengthY helper
|
||||||
|
|> lineTo([2, 4], %) // ln-lineTo-free should become angledLine
|
||||||
|
|> angledLineToX([45, 2.5], %) // ln-angledLineToX-free should become angledLine
|
||||||
|
|> angledLineToX([myAng, 3], %) // ln-angledLineToX-angle should become angledLine
|
||||||
|
|> angledLineToX([45, myVar2], %) // ln-angledLineToX-xAbsolute should use angleToMatchLengthX to get angle
|
||||||
|
|> angledLineToY([135, 5], %) // ln-angledLineToY-free should become angledLine
|
||||||
|
|> angledLineToY([myAng2, 4], %) // ln-angledLineToY-angle should become angledLine
|
||||||
|
|> angledLineToY([45, myVar3], %) // ln-angledLineToY-yAbsolute should use angleToMatchLengthY to get angle
|
||||||
|
|> line([myVar, 1], %) // ln-should use legLen for y
|
||||||
|
|> line([myVar, -1], %) // ln-legLen but negative
|
||||||
|
|> line([-0.62, -1.54], %) // ln-should become angledLine
|
||||||
|
|> angledLine([myVar, 1.04], %) // ln-use segLen for secound arg
|
||||||
|
|> angledLine([45, 1.04], %) // ln-segLen again
|
||||||
|
|> angledLineOfXLength([54, 2.35], %) // ln-should be transformed to angledLine
|
||||||
|
|> angledLineOfXLength([50, myVar], %) // ln-should use legAngX to calculate angle
|
||||||
|
|> angledLineOfXLength([209, myVar], %) // ln-same as above but should have + 180 to match original quadrant
|
||||||
|
|> line([1, myVar], %) // ln-legLen again but yRelative
|
||||||
|
|> line([-1, myVar], %) // ln-negative legLen yRelative
|
||||||
|
|> angledLineOfYLength([58, 0.7], %) // ln-angledLineOfYLength-free should become angledLine
|
||||||
|
|> angledLineOfYLength([myAng, 0.7], %) // ln-angledLineOfYLength-angle should become angledLine
|
||||||
|
|> angledLineOfYLength([35, myVar], %) // ln-angledLineOfYLength-yRelative use legAngY
|
||||||
|
|> angledLineOfYLength([305, myVar], %) // ln-angledLineOfYLength-yRelative with angle > 90 use binExp
|
||||||
|
|> xLine(1.03, %) // ln-xLine-free should sub in segLen
|
||||||
|
|> yLine(1.04, %) // ln-yLine-free should sub in segLen
|
||||||
|
|> xLineTo(30, %) // ln-xLineTo-free should convert to xLine
|
||||||
|
|> yLineTo(20, %) // ln-yLineTo-free should convert to yLine
|
||||||
|
show(part001)`
|
||||||
|
const expectModifiedScript = `const myVar = 3
|
||||||
|
const myVar2 = 5
|
||||||
|
const myVar3 = 6
|
||||||
|
const myAng = 40
|
||||||
|
const myAng2 = 134
|
||||||
|
const part001 = startSketchAt([0, 0])
|
||||||
|
|> line({ to: [1, 3.82], tag: 'seg01' }, %) // ln-should-get-tag
|
||||||
|
|> angledLineToX([
|
||||||
|
-angleToMatchLengthX('seg01', myVar, %),
|
||||||
|
myVar
|
||||||
|
], %) // ln-lineTo-xAbsolute should use angleToMatchLengthX helper
|
||||||
|
|> angledLineToY([
|
||||||
|
-angleToMatchLengthY('seg01', myVar, %),
|
||||||
|
myVar
|
||||||
|
], %) // ln-lineTo-yAbsolute should use angleToMatchLengthY helper
|
||||||
|
|> angledLine([45, segLen('seg01', %)], %) // ln-lineTo-free should become angledLine
|
||||||
|
|> angledLine([45, segLen('seg01', %)], %) // ln-angledLineToX-free should become angledLine
|
||||||
|
|> angledLine([myAng, segLen('seg01', %)], %) // ln-angledLineToX-angle should become angledLine
|
||||||
|
|> angledLineToX([
|
||||||
|
angleToMatchLengthX('seg01', myVar2, %),
|
||||||
|
myVar2
|
||||||
|
], %) // ln-angledLineToX-xAbsolute should use angleToMatchLengthX to get angle
|
||||||
|
|> angledLine([315, segLen('seg01', %)], %) // ln-angledLineToY-free should become angledLine
|
||||||
|
|> angledLine([myAng2, segLen('seg01', %)], %) // ln-angledLineToY-angle should become angledLine
|
||||||
|
|> angledLineToY([
|
||||||
|
angleToMatchLengthY('seg01', myVar3, %),
|
||||||
|
myVar3
|
||||||
|
], %) // ln-angledLineToY-yAbsolute should use angleToMatchLengthY to get angle
|
||||||
|
|> line([
|
||||||
|
min(segLen('seg01', %), myVar),
|
||||||
|
legLen(segLen('seg01', %), myVar)
|
||||||
|
], %) // ln-should use legLen for y
|
||||||
|
|> line([
|
||||||
|
min(segLen('seg01', %), myVar),
|
||||||
|
-legLen(segLen('seg01', %), myVar)
|
||||||
|
], %) // ln-legLen but negative
|
||||||
|
|> angledLine([248, segLen('seg01', %)], %) // ln-should become angledLine
|
||||||
|
|> angledLine([myVar, segLen('seg01', %)], %) // ln-use segLen for secound arg
|
||||||
|
|> angledLine([45, segLen('seg01', %)], %) // ln-segLen again
|
||||||
|
|> angledLine([54, segLen('seg01', %)], %) // ln-should be transformed to angledLine
|
||||||
|
|> angledLineOfXLength([
|
||||||
|
legAngX(segLen('seg01', %), myVar),
|
||||||
|
min(segLen('seg01', %), myVar)
|
||||||
|
], %) // ln-should use legAngX to calculate angle
|
||||||
|
|> angledLineOfXLength([
|
||||||
|
180 + legAngX(segLen('seg01', %), myVar),
|
||||||
|
min(segLen('seg01', %), myVar)
|
||||||
|
], %) // ln-same as above but should have + 180 to match original quadrant
|
||||||
|
|> line([
|
||||||
|
legLen(segLen('seg01', %), myVar),
|
||||||
|
min(segLen('seg01', %), myVar)
|
||||||
|
], %) // ln-legLen again but yRelative
|
||||||
|
|> line([
|
||||||
|
-legLen(segLen('seg01', %), myVar),
|
||||||
|
min(segLen('seg01', %), myVar)
|
||||||
|
], %) // ln-negative legLen yRelative
|
||||||
|
|> angledLine([58, segLen('seg01', %)], %) // ln-angledLineOfYLength-free should become angledLine
|
||||||
|
|> angledLine([myAng, segLen('seg01', %)], %) // ln-angledLineOfYLength-angle should become angledLine
|
||||||
|
|> angledLineOfXLength([
|
||||||
|
legAngY(segLen('seg01', %), myVar),
|
||||||
|
min(segLen('seg01', %), myVar)
|
||||||
|
], %) // ln-angledLineOfYLength-yRelative use legAngY
|
||||||
|
|> angledLineOfXLength([
|
||||||
|
270 + legAngY(segLen('seg01', %), myVar),
|
||||||
|
min(segLen('seg01', %), myVar)
|
||||||
|
], %) // ln-angledLineOfYLength-yRelative with angle > 90 use binExp
|
||||||
|
|> xLine(segLen('seg01', %), %) // ln-xLine-free should sub in segLen
|
||||||
|
|> yLine(segLen('seg01', %), %) // ln-yLine-free should sub in segLen
|
||||||
|
|> xLine(segLen('seg01', %), %) // ln-xLineTo-free should convert to xLine
|
||||||
|
|> yLine(segLen('seg01', %), %) // ln-yLineTo-free should convert to yLine
|
||||||
|
show(part001)`
|
||||||
|
it('It should transform the ast', () => {
|
||||||
|
const ast = abstractSyntaxTree(lexer(inputScript))
|
||||||
|
const selectionRanges = inputScript
|
||||||
|
.split('\n')
|
||||||
|
.filter((ln) => ln.includes('//'))
|
||||||
|
.map((ln) => {
|
||||||
|
const comment = ln.split('//')[1]
|
||||||
|
const start = inputScript.indexOf('//' + comment) - 7
|
||||||
|
return [start, start]
|
||||||
|
}) as [number, number][]
|
||||||
|
|
||||||
|
const programMemory = executor(ast)
|
||||||
|
const transformInfos = getTransformInfos(
|
||||||
|
selectionRanges.slice(1),
|
||||||
|
ast,
|
||||||
|
'equalLength'
|
||||||
|
)
|
||||||
|
|
||||||
|
const newAst = transformAstForSketchLines({
|
||||||
|
ast,
|
||||||
|
selectionRanges,
|
||||||
|
transformInfos,
|
||||||
|
programMemory,
|
||||||
|
})?.modifiedAst
|
||||||
|
const newCode = recast(newAst)
|
||||||
|
expect(newCode).toBe(expectModifiedScript)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('testing transformAstForSketchLines for vertical and horizontal constraint', () => {
|
||||||
|
const inputScript = `const myVar = 2
|
||||||
|
const myVar2 = 12
|
||||||
|
const myVar3 = -10
|
||||||
|
const part001 = startSketchAt([0, 0])
|
||||||
|
|> lineTo([1, 1], %)
|
||||||
|
|> line([-6.28, 1.4], %) // select for horizontal constraint 1
|
||||||
|
|> line([-1.07, myVar], %) // select for vertical constraint 1
|
||||||
|
|> line([myVar, 4.32], %) // select for horizontal constraint 2
|
||||||
|
|> line([6.35, -1.12], %) // select for vertical constraint 2
|
||||||
|
|> lineTo([5, 8], %) // select for horizontal constraint 3
|
||||||
|
|> lineTo([3, 11], %) // select for vertical constraint 3
|
||||||
|
|> lineTo([myVar2, 12.63], %) // select for horizontal constraint 4
|
||||||
|
|> lineTo([4.08, myVar2], %) // select for vertical constraint 4
|
||||||
|
|> angledLine([156, 1.34], %) // select for horizontal constraint 5
|
||||||
|
|> angledLine([103, 1.44], %) // select for vertical constraint 5
|
||||||
|
|> angledLine([-178, myVar], %) // select for horizontal constraint 6
|
||||||
|
|> angledLine([129, myVar], %) // select for vertical constraint 6
|
||||||
|
|> angledLineOfXLength([237, 1.05], %) // select for horizontal constraint 7
|
||||||
|
|> angledLineOfYLength([196, 1.11], %) // select for vertical constraint 7
|
||||||
|
|> angledLineOfXLength([194, myVar], %) // select for horizontal constraint 8
|
||||||
|
|> angledLineOfYLength([248, myVar], %) // select for vertical constraint 8
|
||||||
|
|> angledLineToX([202, -10.92], %) // select for horizontal constraint 9
|
||||||
|
|> angledLineToY([223, 7.68], %) // select for vertical constraint 9
|
||||||
|
|> angledLineToX([333, myVar3], %) // select for horizontal constraint 10
|
||||||
|
|> angledLineToY([301, myVar], %) // select for vertical constraint 10
|
||||||
|
show(part001)`
|
||||||
|
it('It should transform horizontal lines the ast', () => {
|
||||||
|
// const inputScript = `const myVar = 2
|
||||||
|
// const part001 = startSketchAt([0, 0])
|
||||||
|
// |> lineTo([1, 1], %)
|
||||||
|
// |> line([-6.28, 1.4], %) // select for horizontal constraint 1
|
||||||
|
// |> line([-1.07, myVar], %) // select for vertical constraint 1
|
||||||
|
// |> line([myVar, 4.32], %) // select for horizontal constraint 2
|
||||||
|
// |> line([6.35, -1.12], %) // select for vertical constraint 2
|
||||||
|
// show(part001)`
|
||||||
|
const expectModifiedScript = `const myVar = 2
|
||||||
|
const myVar2 = 12
|
||||||
|
const myVar3 = -10
|
||||||
|
const part001 = startSketchAt([0, 0])
|
||||||
|
|> lineTo([1, 1], %)
|
||||||
|
|> xLine(-6.28, %) // select for horizontal constraint 1
|
||||||
|
|> line([-1.07, myVar], %) // select for vertical constraint 1
|
||||||
|
|> xLine(myVar, %) // select for horizontal constraint 2
|
||||||
|
|> line([6.35, -1.12], %) // select for vertical constraint 2
|
||||||
|
|> xLineTo(5, %) // select for horizontal constraint 3
|
||||||
|
|> lineTo([3, 11], %) // select for vertical constraint 3
|
||||||
|
|> xLineTo(myVar2, %) // select for horizontal constraint 4
|
||||||
|
|> lineTo([4.08, myVar2], %) // select for vertical constraint 4
|
||||||
|
|> xLine(-1.22, %) // select for horizontal constraint 5
|
||||||
|
|> angledLine([103, 1.44], %) // select for vertical constraint 5
|
||||||
|
|> xLine(-myVar, %) // select for horizontal constraint 6
|
||||||
|
|> angledLine([129, myVar], %) // select for vertical constraint 6
|
||||||
|
|> xLine(-1.05, %) // select for horizontal constraint 7
|
||||||
|
|> angledLineOfYLength([196, 1.11], %) // select for vertical constraint 7
|
||||||
|
|> xLine(-myVar, %) // select for horizontal constraint 8
|
||||||
|
|> angledLineOfYLength([248, myVar], %) // select for vertical constraint 8
|
||||||
|
|> xLineTo(-10.92, %) // select for horizontal constraint 9
|
||||||
|
|> angledLineToY([223, 7.68], %) // select for vertical constraint 9
|
||||||
|
|> xLineTo(myVar3, %) // select for horizontal constraint 10
|
||||||
|
|> angledLineToY([301, myVar], %) // select for vertical constraint 10
|
||||||
|
show(part001)`
|
||||||
|
const ast = abstractSyntaxTree(lexer(inputScript))
|
||||||
|
const selectionRanges = inputScript
|
||||||
|
.split('\n')
|
||||||
|
.filter((ln) => ln.includes('// select for horizontal constraint'))
|
||||||
|
.map((ln) => {
|
||||||
|
const comment = ln.split('//')[1]
|
||||||
|
const start = inputScript.indexOf('//' + comment) - 7
|
||||||
|
return [start, start]
|
||||||
|
}) as [number, number][]
|
||||||
|
|
||||||
|
const programMemory = executor(ast)
|
||||||
|
const transformInfos = getTransformInfos(selectionRanges, ast, 'horizontal')
|
||||||
|
|
||||||
|
const newAst = transformAstForHorzVert({
|
||||||
|
ast,
|
||||||
|
selectionRanges,
|
||||||
|
transformInfos,
|
||||||
|
programMemory,
|
||||||
|
})?.modifiedAst
|
||||||
|
const newCode = recast(newAst)
|
||||||
|
expect(newCode).toBe(expectModifiedScript)
|
||||||
|
})
|
||||||
|
it('It should transform vertical lines the ast', () => {
|
||||||
|
const expectModifiedScript = `const myVar = 2
|
||||||
|
const myVar2 = 12
|
||||||
|
const myVar3 = -10
|
||||||
|
const part001 = startSketchAt([0, 0])
|
||||||
|
|> lineTo([1, 1], %)
|
||||||
|
|> line([-6.28, 1.4], %) // select for horizontal constraint 1
|
||||||
|
|> yLine(myVar, %) // select for vertical constraint 1
|
||||||
|
|> line([myVar, 4.32], %) // select for horizontal constraint 2
|
||||||
|
|> yLine(-1.12, %) // select for vertical constraint 2
|
||||||
|
|> lineTo([5, 8], %) // select for horizontal constraint 3
|
||||||
|
|> yLineTo(11, %) // select for vertical constraint 3
|
||||||
|
|> lineTo([myVar2, 12.63], %) // select for horizontal constraint 4
|
||||||
|
|> yLineTo(myVar2, %) // select for vertical constraint 4
|
||||||
|
|> angledLine([156, 1.34], %) // select for horizontal constraint 5
|
||||||
|
|> yLine(1.4, %) // select for vertical constraint 5
|
||||||
|
|> angledLine([-178, myVar], %) // select for horizontal constraint 6
|
||||||
|
|> yLine(myVar, %) // select for vertical constraint 6
|
||||||
|
|> angledLineOfXLength([237, 1.05], %) // select for horizontal constraint 7
|
||||||
|
|> yLine(-1.11, %) // select for vertical constraint 7
|
||||||
|
|> angledLineOfXLength([194, myVar], %) // select for horizontal constraint 8
|
||||||
|
|> yLine(-myVar, %) // select for vertical constraint 8
|
||||||
|
|> angledLineToX([202, -10.92], %) // select for horizontal constraint 9
|
||||||
|
|> yLineTo(7.68, %) // select for vertical constraint 9
|
||||||
|
|> angledLineToX([333, myVar3], %) // select for horizontal constraint 10
|
||||||
|
|> yLineTo(myVar, %) // select for vertical constraint 10
|
||||||
|
show(part001)`
|
||||||
|
const ast = abstractSyntaxTree(lexer(inputScript))
|
||||||
|
const selectionRanges = inputScript
|
||||||
|
.split('\n')
|
||||||
|
.filter((ln) => ln.includes('// select for vertical constraint'))
|
||||||
|
.map((ln) => {
|
||||||
|
const comment = ln.split('//')[1]
|
||||||
|
const start = inputScript.indexOf('//' + comment) - 7
|
||||||
|
return [start, start]
|
||||||
|
}) as [number, number][]
|
||||||
|
|
||||||
|
const programMemory = executor(ast)
|
||||||
|
const transformInfos = getTransformInfos(selectionRanges, ast, 'vertical')
|
||||||
|
|
||||||
|
const newAst = transformAstForHorzVert({
|
||||||
|
ast,
|
||||||
|
selectionRanges,
|
||||||
|
transformInfos,
|
||||||
|
programMemory,
|
||||||
|
})?.modifiedAst
|
||||||
|
const newCode = recast(newAst)
|
||||||
|
expect(newCode).toBe(expectModifiedScript)
|
||||||
|
})
|
||||||
|
})
|
851
src/lang/std/sketchcombos.ts
Normal file
851
src/lang/std/sketchcombos.ts
Normal file
@ -0,0 +1,851 @@
|
|||||||
|
import { TransformCallback } from './stdTypes'
|
||||||
|
import { Range, Ranges, toolTips, TooTip } from '../../useStore'
|
||||||
|
import {
|
||||||
|
BinaryPart,
|
||||||
|
CallExpression,
|
||||||
|
getNodeFromPath,
|
||||||
|
getNodeFromPathCurry,
|
||||||
|
getNodePathFromSourceRange,
|
||||||
|
Program,
|
||||||
|
Value,
|
||||||
|
VariableDeclarator,
|
||||||
|
} from '../abstractSyntaxTree'
|
||||||
|
import {
|
||||||
|
createBinaryExpression,
|
||||||
|
createCallExpression,
|
||||||
|
createIdentifier,
|
||||||
|
createLiteral,
|
||||||
|
createPipeSubstitution,
|
||||||
|
createUnaryExpression,
|
||||||
|
giveSketchFnCallTag,
|
||||||
|
} from '../modifyAst'
|
||||||
|
import { createFirstArg, getFirstArg, replaceSketchLine } from './sketch'
|
||||||
|
import { ProgramMemory } from '../executor'
|
||||||
|
import { getSketchSegmentIndexFromSourceRange } from './sketchConstraints'
|
||||||
|
|
||||||
|
type LineInputsType =
|
||||||
|
| 'xAbsolute'
|
||||||
|
| 'yAbsolute'
|
||||||
|
| 'xRelative'
|
||||||
|
| 'yRelative'
|
||||||
|
| 'angle'
|
||||||
|
| 'length'
|
||||||
|
|
||||||
|
export type ConstraintType =
|
||||||
|
| 'equalLength'
|
||||||
|
| 'vertical'
|
||||||
|
| 'horizontal'
|
||||||
|
| 'equalangle'
|
||||||
|
|
||||||
|
function createCallWrapper(
|
||||||
|
a: TooTip,
|
||||||
|
val: [Value, Value] | Value,
|
||||||
|
tag?: Value
|
||||||
|
) {
|
||||||
|
return createCallExpression(a, [
|
||||||
|
createFirstArg(a, val, tag),
|
||||||
|
createPipeSubstitution(),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
export function replaceSketchCall(
|
||||||
|
programMemory: ProgramMemory,
|
||||||
|
ast: Program,
|
||||||
|
range: Range,
|
||||||
|
transformTo: TooTip,
|
||||||
|
createCallback: TransformCallback
|
||||||
|
): { modifiedAst: Program } {
|
||||||
|
const path = getNodePathFromSourceRange(ast, range)
|
||||||
|
const getNode = getNodeFromPathCurry(ast, path)
|
||||||
|
const varDec = getNode<VariableDeclarator>('VariableDeclarator').node
|
||||||
|
const callExp = getNode<CallExpression>('CallExpression').node
|
||||||
|
const varName = varDec.id.name
|
||||||
|
const sketchGroup = programMemory.root?.[varName]
|
||||||
|
if (!sketchGroup || sketchGroup.type !== 'sketchGroup')
|
||||||
|
throw new Error('not a sketch group')
|
||||||
|
const seg = getSketchSegmentIndexFromSourceRange(sketchGroup, range)
|
||||||
|
const { to, from } = seg
|
||||||
|
const { modifiedAst } = replaceSketchLine({
|
||||||
|
node: ast,
|
||||||
|
programMemory,
|
||||||
|
sourceRange: range,
|
||||||
|
fnName: transformTo || (callExp.callee.name as TooTip),
|
||||||
|
to,
|
||||||
|
from,
|
||||||
|
createCallback,
|
||||||
|
})
|
||||||
|
return { modifiedAst }
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TransformInfo = {
|
||||||
|
tooltip: TooTip
|
||||||
|
createNode: (a: {
|
||||||
|
varValA: Value // x / angle
|
||||||
|
varValB: Value // y / length or x y for angledLineOfXlength etc
|
||||||
|
referenceSegName: string
|
||||||
|
tag?: Value
|
||||||
|
}) => (args: [Value, Value]) => Value
|
||||||
|
}
|
||||||
|
|
||||||
|
type TransformMap = {
|
||||||
|
[key in TooTip]: {
|
||||||
|
[key in LineInputsType | 'free']?: {
|
||||||
|
[key in ConstraintType]?: TransformInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const basicAngledLineCreateNode: TransformInfo['createNode'] =
|
||||||
|
({ referenceSegName, tag }) =>
|
||||||
|
(args) =>
|
||||||
|
createCallWrapper(
|
||||||
|
'angledLine',
|
||||||
|
[args[0], createSegLen(referenceSegName)],
|
||||||
|
tag
|
||||||
|
)
|
||||||
|
const angledLineAngleCreateNode: TransformInfo['createNode'] =
|
||||||
|
({ referenceSegName, varValA, tag }) =>
|
||||||
|
() =>
|
||||||
|
createCallWrapper(
|
||||||
|
'angledLine',
|
||||||
|
[varValA, createSegLen(referenceSegName)],
|
||||||
|
tag
|
||||||
|
)
|
||||||
|
|
||||||
|
const getMinAndSegLenVals = (
|
||||||
|
referenceSegName: string,
|
||||||
|
varVal: Value
|
||||||
|
): [Value, BinaryPart] => {
|
||||||
|
const segLenVal = createSegLen(referenceSegName)
|
||||||
|
return [
|
||||||
|
createCallExpression('min', [segLenVal, varVal]),
|
||||||
|
createCallExpression('legLen', [segLenVal, varVal]),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const getMinAndSegAngVals = (
|
||||||
|
referenceSegName: string,
|
||||||
|
varVal: Value,
|
||||||
|
fnName: 'legAngX' | 'legAngY' = 'legAngX'
|
||||||
|
): [Value, BinaryPart] => {
|
||||||
|
const minVal = createCallExpression('min', [
|
||||||
|
createSegLen(referenceSegName),
|
||||||
|
varVal,
|
||||||
|
])
|
||||||
|
const legAngle = createCallExpression(fnName, [
|
||||||
|
createSegLen(referenceSegName),
|
||||||
|
varVal,
|
||||||
|
])
|
||||||
|
return [minVal, legAngle]
|
||||||
|
}
|
||||||
|
|
||||||
|
const getSignedLeg = (arg: Value, legLenVal: BinaryPart) =>
|
||||||
|
arg.type === 'Literal' && Number(arg.value) < 0
|
||||||
|
? createUnaryExpression(legLenVal)
|
||||||
|
: legLenVal
|
||||||
|
|
||||||
|
const getLegAng = (arg: Value, legAngleVal: BinaryPart) => {
|
||||||
|
const ang = (arg.type === 'Literal' && Number(arg.value)) || 0
|
||||||
|
const normalisedAngle = ((ang % 360) + 360) % 360 // between 0 and 360
|
||||||
|
const truncatedTo90 = Math.floor(normalisedAngle / 90) * 90
|
||||||
|
const binExp = createBinaryExpression([
|
||||||
|
createLiteral(truncatedTo90),
|
||||||
|
'+',
|
||||||
|
legAngleVal,
|
||||||
|
])
|
||||||
|
return truncatedTo90 == 0 ? legAngleVal : binExp
|
||||||
|
}
|
||||||
|
|
||||||
|
const getAngleLengthSign = (arg: Value, legAngleVal: BinaryPart) => {
|
||||||
|
const ang = (arg.type === 'Literal' && Number(arg.value)) || 0
|
||||||
|
const normalisedAngle = ((ang % 180) + 180) % 180 // between 0 and 180
|
||||||
|
return normalisedAngle > 90 ? createUnaryExpression(legAngleVal) : legAngleVal
|
||||||
|
}
|
||||||
|
|
||||||
|
const transformMap: TransformMap = {
|
||||||
|
line: {
|
||||||
|
xRelative: {
|
||||||
|
equalLength: {
|
||||||
|
tooltip: 'line',
|
||||||
|
createNode: ({ referenceSegName, varValA, tag }) => {
|
||||||
|
const [minVal, legLenVal] = getMinAndSegLenVals(
|
||||||
|
referenceSegName,
|
||||||
|
varValA
|
||||||
|
)
|
||||||
|
return (args) =>
|
||||||
|
createCallWrapper(
|
||||||
|
'line',
|
||||||
|
[minVal, getSignedLeg(args[1], legLenVal)],
|
||||||
|
tag
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
horizontal: {
|
||||||
|
tooltip: 'xLine',
|
||||||
|
createNode:
|
||||||
|
({ varValA, tag }) =>
|
||||||
|
() =>
|
||||||
|
createCallWrapper('xLine', varValA, tag),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
yRelative: {
|
||||||
|
equalLength: {
|
||||||
|
tooltip: 'line',
|
||||||
|
createNode: ({ referenceSegName, varValB, tag }) => {
|
||||||
|
const [minVal, legLenVal] = getMinAndSegLenVals(
|
||||||
|
referenceSegName,
|
||||||
|
varValB
|
||||||
|
)
|
||||||
|
return (args) =>
|
||||||
|
createCallWrapper(
|
||||||
|
'line',
|
||||||
|
[getSignedLeg(args[0], legLenVal), minVal],
|
||||||
|
tag
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
vertical: {
|
||||||
|
tooltip: 'yLine',
|
||||||
|
createNode:
|
||||||
|
({ varValB, tag }) =>
|
||||||
|
() =>
|
||||||
|
createCallWrapper('yLine', varValB, tag),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
free: {
|
||||||
|
equalLength: {
|
||||||
|
tooltip: 'angledLine',
|
||||||
|
createNode: basicAngledLineCreateNode,
|
||||||
|
},
|
||||||
|
horizontal: {
|
||||||
|
tooltip: 'xLine',
|
||||||
|
createNode:
|
||||||
|
({ tag }) =>
|
||||||
|
(args) =>
|
||||||
|
createCallWrapper('xLine', args[0], tag),
|
||||||
|
},
|
||||||
|
vertical: {
|
||||||
|
tooltip: 'yLine',
|
||||||
|
createNode:
|
||||||
|
({ tag }) =>
|
||||||
|
(args) =>
|
||||||
|
createCallWrapper('yLine', args[1], tag),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
lineTo: {
|
||||||
|
free: {
|
||||||
|
equalLength: {
|
||||||
|
tooltip: 'angledLine',
|
||||||
|
createNode: basicAngledLineCreateNode,
|
||||||
|
},
|
||||||
|
horizontal: {
|
||||||
|
tooltip: 'xLineTo',
|
||||||
|
createNode:
|
||||||
|
({ tag }) =>
|
||||||
|
(args) =>
|
||||||
|
createCallWrapper('xLineTo', args[0], tag),
|
||||||
|
},
|
||||||
|
vertical: {
|
||||||
|
tooltip: 'yLineTo',
|
||||||
|
createNode:
|
||||||
|
({ tag }) =>
|
||||||
|
(args) =>
|
||||||
|
createCallWrapper('yLineTo', args[1], tag),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
xAbsolute: {
|
||||||
|
equalLength: {
|
||||||
|
tooltip: 'angledLineToX',
|
||||||
|
createNode:
|
||||||
|
({ referenceSegName, varValA, tag }) =>
|
||||||
|
(args) => {
|
||||||
|
const angleToMatchLengthXCall = createCallExpression(
|
||||||
|
'angleToMatchLengthX',
|
||||||
|
[
|
||||||
|
createLiteral(referenceSegName),
|
||||||
|
varValA,
|
||||||
|
createPipeSubstitution(),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
return createCallWrapper(
|
||||||
|
'angledLineToX',
|
||||||
|
[getAngleLengthSign(args[0], angleToMatchLengthXCall), varValA],
|
||||||
|
tag
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
horizontal: {
|
||||||
|
tooltip: 'xLineTo',
|
||||||
|
createNode:
|
||||||
|
({ varValA, tag }) =>
|
||||||
|
() =>
|
||||||
|
createCallWrapper('xLineTo', varValA, tag),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
yAbsolute: {
|
||||||
|
equalLength: {
|
||||||
|
tooltip: 'angledLineToY',
|
||||||
|
createNode:
|
||||||
|
({ referenceSegName, varValB, tag }) =>
|
||||||
|
(args) => {
|
||||||
|
const angleToMatchLengthYCall = createCallExpression(
|
||||||
|
'angleToMatchLengthY',
|
||||||
|
[
|
||||||
|
createLiteral(referenceSegName),
|
||||||
|
varValB,
|
||||||
|
createPipeSubstitution(),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
return createCallWrapper(
|
||||||
|
'angledLineToY',
|
||||||
|
[getAngleLengthSign(args[0], angleToMatchLengthYCall), varValB],
|
||||||
|
tag
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
vertical: {
|
||||||
|
tooltip: 'yLineTo',
|
||||||
|
createNode:
|
||||||
|
({ varValB, tag }) =>
|
||||||
|
() =>
|
||||||
|
createCallWrapper('yLineTo', varValB, tag),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
angledLine: {
|
||||||
|
angle: {
|
||||||
|
equalLength: {
|
||||||
|
tooltip: 'angledLine',
|
||||||
|
createNode:
|
||||||
|
({ referenceSegName, varValA, tag }) =>
|
||||||
|
() =>
|
||||||
|
createCallWrapper(
|
||||||
|
'angledLine',
|
||||||
|
[varValA, createSegLen(referenceSegName)],
|
||||||
|
tag
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
free: {
|
||||||
|
equalLength: {
|
||||||
|
tooltip: 'angledLine',
|
||||||
|
createNode: basicAngledLineCreateNode,
|
||||||
|
},
|
||||||
|
vertical: {
|
||||||
|
tooltip: 'yLine',
|
||||||
|
createNode:
|
||||||
|
({ tag }) =>
|
||||||
|
(args) =>
|
||||||
|
createCallWrapper('yLine', args[1], tag),
|
||||||
|
},
|
||||||
|
horizontal: {
|
||||||
|
tooltip: 'xLine',
|
||||||
|
createNode:
|
||||||
|
({ tag }) =>
|
||||||
|
(args) =>
|
||||||
|
createCallWrapper('xLine', args[0], tag),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
length: {
|
||||||
|
vertical: {
|
||||||
|
tooltip: 'yLine',
|
||||||
|
createNode:
|
||||||
|
({ varValB, tag }) =>
|
||||||
|
([arg0]) => {
|
||||||
|
const val =
|
||||||
|
arg0.type === 'Literal' && Number(arg0.value) < 0
|
||||||
|
? createUnaryExpression(varValB as BinaryPart)
|
||||||
|
: varValB
|
||||||
|
return createCallWrapper('yLine', val, tag)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
horizontal: {
|
||||||
|
tooltip: 'xLine',
|
||||||
|
createNode:
|
||||||
|
({ varValB, tag }) =>
|
||||||
|
([arg0]) => {
|
||||||
|
const val =
|
||||||
|
arg0.type === 'Literal' && Number(arg0.value) < 0
|
||||||
|
? createUnaryExpression(varValB as BinaryPart)
|
||||||
|
: varValB
|
||||||
|
return createCallWrapper('xLine', val, tag)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
angledLineOfXLength: {
|
||||||
|
free: {
|
||||||
|
equalLength: {
|
||||||
|
tooltip: 'angledLine',
|
||||||
|
createNode: basicAngledLineCreateNode,
|
||||||
|
},
|
||||||
|
horizontal: {
|
||||||
|
tooltip: 'xLine',
|
||||||
|
createNode:
|
||||||
|
({ tag }) =>
|
||||||
|
(args) =>
|
||||||
|
createCallWrapper('xLine', args[0], tag),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
angle: {
|
||||||
|
equalLength: {
|
||||||
|
tooltip: 'angledLine',
|
||||||
|
createNode: angledLineAngleCreateNode,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
xRelative: {
|
||||||
|
equalLength: {
|
||||||
|
tooltip: 'angledLineOfXLength',
|
||||||
|
createNode: ({ referenceSegName, varValB, tag }) => {
|
||||||
|
const [minVal, legAngle] = getMinAndSegAngVals(
|
||||||
|
referenceSegName,
|
||||||
|
varValB
|
||||||
|
)
|
||||||
|
return (args) =>
|
||||||
|
createCallWrapper(
|
||||||
|
'angledLineOfXLength',
|
||||||
|
[getLegAng(args[0], legAngle), minVal],
|
||||||
|
tag
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
horizontal: {
|
||||||
|
tooltip: 'xLine',
|
||||||
|
createNode:
|
||||||
|
({ varValB, tag }) =>
|
||||||
|
([arg0]) => {
|
||||||
|
const val =
|
||||||
|
arg0.type === 'Literal' && Number(arg0.value) < 0
|
||||||
|
? createUnaryExpression(varValB as BinaryPart)
|
||||||
|
: varValB
|
||||||
|
return createCallWrapper('xLine', val, tag)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
angledLineOfYLength: {
|
||||||
|
free: {
|
||||||
|
equalLength: {
|
||||||
|
tooltip: 'angledLine',
|
||||||
|
createNode: basicAngledLineCreateNode,
|
||||||
|
},
|
||||||
|
vertical: {
|
||||||
|
tooltip: 'yLine',
|
||||||
|
createNode:
|
||||||
|
({ tag }) =>
|
||||||
|
(args) =>
|
||||||
|
createCallWrapper('yLine', args[1], tag),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
angle: {
|
||||||
|
equalLength: {
|
||||||
|
tooltip: 'angledLine',
|
||||||
|
createNode: angledLineAngleCreateNode,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
yRelative: {
|
||||||
|
equalLength: {
|
||||||
|
tooltip: 'angledLineOfYLength',
|
||||||
|
createNode: ({ referenceSegName, varValB, tag }) => {
|
||||||
|
const [minVal, legAngle] = getMinAndSegAngVals(
|
||||||
|
referenceSegName,
|
||||||
|
varValB,
|
||||||
|
'legAngY'
|
||||||
|
)
|
||||||
|
return (args) =>
|
||||||
|
createCallWrapper(
|
||||||
|
'angledLineOfXLength',
|
||||||
|
[getLegAng(args[0], legAngle), minVal],
|
||||||
|
tag
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
vertical: {
|
||||||
|
tooltip: 'yLine',
|
||||||
|
createNode:
|
||||||
|
({ varValB, tag }) =>
|
||||||
|
([arg0]) => {
|
||||||
|
const val =
|
||||||
|
arg0.type === 'Literal' && Number(arg0.value) < 0
|
||||||
|
? createUnaryExpression(varValB as BinaryPart)
|
||||||
|
: varValB
|
||||||
|
return createCallWrapper('yLine', val, tag)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
angledLineToX: {
|
||||||
|
free: {
|
||||||
|
equalLength: {
|
||||||
|
tooltip: 'angledLine',
|
||||||
|
createNode: basicAngledLineCreateNode,
|
||||||
|
},
|
||||||
|
horizontal: {
|
||||||
|
tooltip: 'xLineTo',
|
||||||
|
createNode:
|
||||||
|
({ tag }) =>
|
||||||
|
(args) =>
|
||||||
|
createCallWrapper('xLineTo', args[0], tag),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
angle: {
|
||||||
|
equalLength: {
|
||||||
|
tooltip: 'angledLine',
|
||||||
|
createNode: angledLineAngleCreateNode,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
xAbsolute: {
|
||||||
|
equalLength: {
|
||||||
|
tooltip: 'angledLineToX',
|
||||||
|
createNode:
|
||||||
|
({ referenceSegName, varValB, tag }) =>
|
||||||
|
(args) => {
|
||||||
|
const angleToMatchLengthXCall = createCallExpression(
|
||||||
|
'angleToMatchLengthX',
|
||||||
|
[
|
||||||
|
createLiteral(referenceSegName),
|
||||||
|
varValB,
|
||||||
|
createPipeSubstitution(),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
return createCallWrapper(
|
||||||
|
'angledLineToX',
|
||||||
|
[getAngleLengthSign(args[0], angleToMatchLengthXCall), varValB],
|
||||||
|
tag
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
horizontal: {
|
||||||
|
tooltip: 'xLineTo',
|
||||||
|
createNode:
|
||||||
|
({ varValB, tag }) =>
|
||||||
|
([arg0]) =>
|
||||||
|
createCallWrapper('xLineTo', varValB, tag),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
angledLineToY: {
|
||||||
|
free: {
|
||||||
|
equalLength: {
|
||||||
|
tooltip: 'angledLine',
|
||||||
|
createNode: basicAngledLineCreateNode,
|
||||||
|
},
|
||||||
|
vertical: {
|
||||||
|
tooltip: 'yLineTo',
|
||||||
|
createNode:
|
||||||
|
({ tag }) =>
|
||||||
|
(args) =>
|
||||||
|
createCallWrapper('yLineTo', args[1], tag),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
angle: {
|
||||||
|
equalLength: {
|
||||||
|
tooltip: 'angledLine',
|
||||||
|
createNode: angledLineAngleCreateNode,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
yAbsolute: {
|
||||||
|
equalLength: {
|
||||||
|
tooltip: 'angledLineToY',
|
||||||
|
createNode:
|
||||||
|
({ referenceSegName, varValB, tag }) =>
|
||||||
|
(args) => {
|
||||||
|
const angleToMatchLengthXCall = createCallExpression(
|
||||||
|
'angleToMatchLengthY',
|
||||||
|
[
|
||||||
|
createLiteral(referenceSegName),
|
||||||
|
varValB,
|
||||||
|
createPipeSubstitution(),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
return createCallWrapper(
|
||||||
|
'angledLineToY',
|
||||||
|
[getAngleLengthSign(args[0], angleToMatchLengthXCall), varValB],
|
||||||
|
tag
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
vertical: {
|
||||||
|
tooltip: 'yLineTo',
|
||||||
|
createNode:
|
||||||
|
({ varValB, tag }) =>
|
||||||
|
() =>
|
||||||
|
createCallWrapper('yLineTo', varValB, tag),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
xLine: {
|
||||||
|
free: {
|
||||||
|
equalLength: {
|
||||||
|
tooltip: 'xLine',
|
||||||
|
createNode:
|
||||||
|
({ referenceSegName, tag }) =>
|
||||||
|
() =>
|
||||||
|
createCallWrapper('xLine', createSegLen(referenceSegName), tag),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
yLine: {
|
||||||
|
free: {
|
||||||
|
equalLength: {
|
||||||
|
tooltip: 'yLine',
|
||||||
|
createNode:
|
||||||
|
({ referenceSegName, tag }) =>
|
||||||
|
() =>
|
||||||
|
createCallWrapper('yLine', createSegLen(referenceSegName), tag),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
xLineTo: {
|
||||||
|
free: {
|
||||||
|
equalLength: {
|
||||||
|
tooltip: 'xLine',
|
||||||
|
createNode:
|
||||||
|
({ referenceSegName, tag }) =>
|
||||||
|
() =>
|
||||||
|
createCallWrapper('xLine', createSegLen(referenceSegName), tag),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
yLineTo: {
|
||||||
|
free: {
|
||||||
|
equalLength: {
|
||||||
|
tooltip: 'yLine',
|
||||||
|
createNode:
|
||||||
|
({ referenceSegName, tag }) =>
|
||||||
|
() =>
|
||||||
|
createCallWrapper('yLine', createSegLen(referenceSegName), tag),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTransformMapPath(
|
||||||
|
sketchFnExp: CallExpression,
|
||||||
|
constraintType: ConstraintType
|
||||||
|
):
|
||||||
|
| {
|
||||||
|
toolTip: TooTip
|
||||||
|
lineInputType: LineInputsType | 'free'
|
||||||
|
constraintType: ConstraintType
|
||||||
|
}
|
||||||
|
| false {
|
||||||
|
const name = sketchFnExp.callee.name as TooTip
|
||||||
|
if (!toolTips.includes(name)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the function is locked down and so can't be transformed
|
||||||
|
const firstArg = getFirstArg(sketchFnExp)
|
||||||
|
if (Array.isArray(firstArg.val)) {
|
||||||
|
const [a, b] = firstArg.val
|
||||||
|
if (a?.type !== 'Literal' && b?.type !== 'Literal') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (firstArg.val?.type !== 'Literal') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the function has no constraints
|
||||||
|
const isTwoValFree =
|
||||||
|
Array.isArray(firstArg.val) &&
|
||||||
|
firstArg.val?.[0]?.type === 'Literal' &&
|
||||||
|
firstArg.val?.[1]?.type === 'Literal'
|
||||||
|
const isOneValFree =
|
||||||
|
!Array.isArray(firstArg.val) && firstArg.val?.type === 'Literal'
|
||||||
|
if (isTwoValFree || isOneValFree) {
|
||||||
|
const info = transformMap?.[name]?.free?.[constraintType]
|
||||||
|
if (info)
|
||||||
|
return {
|
||||||
|
toolTip: name,
|
||||||
|
lineInputType: 'free',
|
||||||
|
constraintType,
|
||||||
|
}
|
||||||
|
// if (info) return info
|
||||||
|
}
|
||||||
|
|
||||||
|
// check what constraints the function has
|
||||||
|
const lineInputType = getConstraintType(firstArg.val, name)
|
||||||
|
if (lineInputType) {
|
||||||
|
const info = transformMap?.[name]?.[lineInputType]?.[constraintType]
|
||||||
|
if (info)
|
||||||
|
return {
|
||||||
|
toolTip: name,
|
||||||
|
lineInputType,
|
||||||
|
constraintType,
|
||||||
|
}
|
||||||
|
// if (info) return info
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTransformInfo(
|
||||||
|
sketchFnExp: CallExpression,
|
||||||
|
constraintType: ConstraintType
|
||||||
|
): TransformInfo | false {
|
||||||
|
const path = getTransformMapPath(sketchFnExp, constraintType)
|
||||||
|
if (!path) return false
|
||||||
|
const { toolTip, lineInputType, constraintType: _constraintType } = path
|
||||||
|
const info = transformMap?.[toolTip]?.[lineInputType]?.[_constraintType]
|
||||||
|
if (!info) return false
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getConstraintType(
|
||||||
|
val: Value | [Value, Value],
|
||||||
|
fnName: TooTip
|
||||||
|
): LineInputsType | null {
|
||||||
|
// this function assumes that for two val sketch functions that one arg is locked down not both
|
||||||
|
// and for one val sketch functions that the arg is NOT locked down
|
||||||
|
// these conditions should have been checked previously.
|
||||||
|
// completely locked down or not locked down at all does not depend on the fnName so we can check that first
|
||||||
|
const isArr = Array.isArray(val)
|
||||||
|
if (!isArr) {
|
||||||
|
if (fnName === 'xLine') return 'yRelative'
|
||||||
|
if (fnName === 'yLine') return 'xRelative'
|
||||||
|
if (fnName === 'xLineTo') return 'yAbsolute'
|
||||||
|
if (fnName === 'yLineTo') return 'xAbsolute'
|
||||||
|
} else {
|
||||||
|
const isFirstArgLockedDown = val?.[0]?.type !== 'Literal'
|
||||||
|
if (fnName === 'line')
|
||||||
|
return isFirstArgLockedDown ? 'xRelative' : 'yRelative'
|
||||||
|
if (fnName === 'lineTo')
|
||||||
|
return isFirstArgLockedDown ? 'xAbsolute' : 'yAbsolute'
|
||||||
|
if (fnName === 'angledLine')
|
||||||
|
return isFirstArgLockedDown ? 'angle' : 'length'
|
||||||
|
if (fnName === 'angledLineOfXLength')
|
||||||
|
return isFirstArgLockedDown ? 'angle' : 'xRelative'
|
||||||
|
if (fnName === 'angledLineToX')
|
||||||
|
return isFirstArgLockedDown ? 'angle' : 'xAbsolute'
|
||||||
|
if (fnName === 'angledLineOfYLength')
|
||||||
|
return isFirstArgLockedDown ? 'angle' : 'yRelative'
|
||||||
|
if (fnName === 'angledLineToY')
|
||||||
|
return isFirstArgLockedDown ? 'angle' : 'yAbsolute'
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTransformInfos(
|
||||||
|
selectionRanges: Ranges,
|
||||||
|
ast: Program,
|
||||||
|
constraintType: ConstraintType
|
||||||
|
): TransformInfo[] {
|
||||||
|
const paths = selectionRanges.map((selectionRange) =>
|
||||||
|
getNodePathFromSourceRange(ast, selectionRange)
|
||||||
|
)
|
||||||
|
const nodes = paths.map(
|
||||||
|
(pathToNode) => getNodeFromPath<Value>(ast, pathToNode).node
|
||||||
|
)
|
||||||
|
|
||||||
|
const theTransforms = nodes.map((node) => {
|
||||||
|
if (node?.type === 'CallExpression')
|
||||||
|
return getTransformInfo(node, constraintType)
|
||||||
|
|
||||||
|
return false
|
||||||
|
}) as TransformInfo[]
|
||||||
|
return theTransforms
|
||||||
|
}
|
||||||
|
|
||||||
|
export function transformAstForSketchLines({
|
||||||
|
ast,
|
||||||
|
selectionRanges,
|
||||||
|
transformInfos,
|
||||||
|
programMemory,
|
||||||
|
}: {
|
||||||
|
ast: Program
|
||||||
|
selectionRanges: Ranges
|
||||||
|
transformInfos: TransformInfo[]
|
||||||
|
programMemory: ProgramMemory
|
||||||
|
}): { modifiedAst: Program } {
|
||||||
|
// deep clone since we are mutating in a loop, of which any could fail
|
||||||
|
let node = JSON.parse(JSON.stringify(ast))
|
||||||
|
const primarySelection = selectionRanges[0]
|
||||||
|
|
||||||
|
const { modifiedAst, tag } = giveSketchFnCallTag(node, primarySelection)
|
||||||
|
node = modifiedAst
|
||||||
|
|
||||||
|
selectionRanges.slice(1).forEach((range, index) => {
|
||||||
|
const callBack = transformInfos?.[index].createNode
|
||||||
|
const transformTo = transformInfos?.[index].tooltip
|
||||||
|
if (!callBack || !transformTo) throw new Error('no callback helper')
|
||||||
|
|
||||||
|
const callExpPath = getNodePathFromSourceRange(node, range)
|
||||||
|
const callExp = getNodeFromPath<CallExpression>(
|
||||||
|
node,
|
||||||
|
callExpPath,
|
||||||
|
'CallExpression'
|
||||||
|
)?.node
|
||||||
|
const { val } = getFirstArg(callExp)
|
||||||
|
const [varValA, varValB] = Array.isArray(val) ? val : [val, val]
|
||||||
|
|
||||||
|
const { modifiedAst } = replaceSketchCall(
|
||||||
|
programMemory,
|
||||||
|
node,
|
||||||
|
range,
|
||||||
|
transformTo,
|
||||||
|
callBack({
|
||||||
|
referenceSegName: tag,
|
||||||
|
varValA,
|
||||||
|
varValB,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
node = modifiedAst
|
||||||
|
})
|
||||||
|
return { modifiedAst: node }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function transformAstForHorzVert({
|
||||||
|
ast,
|
||||||
|
selectionRanges,
|
||||||
|
transformInfos,
|
||||||
|
programMemory,
|
||||||
|
}: {
|
||||||
|
ast: Program
|
||||||
|
selectionRanges: Ranges
|
||||||
|
transformInfos: TransformInfo[]
|
||||||
|
programMemory: ProgramMemory
|
||||||
|
}): { modifiedAst: Program } {
|
||||||
|
// deep clone since we are mutating in a loop, of which any could fail
|
||||||
|
let node = JSON.parse(JSON.stringify(ast))
|
||||||
|
|
||||||
|
selectionRanges.forEach((range, index) => {
|
||||||
|
const callBack = transformInfos?.[index]?.createNode
|
||||||
|
const transformTo = transformInfos?.[index].tooltip
|
||||||
|
if (!callBack || !transformTo) throw new Error('no callback helper')
|
||||||
|
|
||||||
|
const callExpPath = getNodePathFromSourceRange(node, range)
|
||||||
|
const callExp = getNodeFromPath<CallExpression>(
|
||||||
|
node,
|
||||||
|
callExpPath,
|
||||||
|
'CallExpression'
|
||||||
|
)?.node
|
||||||
|
const { val, tag } = getFirstArg(callExp)
|
||||||
|
const [varValA, varValB] = Array.isArray(val) ? val : [val, val]
|
||||||
|
|
||||||
|
const { modifiedAst } = replaceSketchCall(
|
||||||
|
programMemory,
|
||||||
|
node,
|
||||||
|
range,
|
||||||
|
transformTo,
|
||||||
|
callBack({
|
||||||
|
referenceSegName: '',
|
||||||
|
varValA,
|
||||||
|
varValB,
|
||||||
|
tag,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
node = modifiedAst
|
||||||
|
})
|
||||||
|
return { modifiedAst: node }
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSegLen(referenceSegName: string): Value {
|
||||||
|
return createCallExpression('segLen', [
|
||||||
|
createLiteral(referenceSegName),
|
||||||
|
createPipeSubstitution(),
|
||||||
|
])
|
||||||
|
}
|
@ -13,6 +13,11 @@ import {
|
|||||||
closee,
|
closee,
|
||||||
startSketchAt,
|
startSketchAt,
|
||||||
} from './sketch'
|
} from './sketch'
|
||||||
|
import {
|
||||||
|
segLen,
|
||||||
|
angleToMatchLengthX,
|
||||||
|
angleToMatchLengthY,
|
||||||
|
} from './sketchConstraints'
|
||||||
import { extrude, getExtrudeWallTransform } from './extrude'
|
import { extrude, getExtrudeWallTransform } from './extrude'
|
||||||
import { Quaternion, Vector3 } from 'three'
|
import { Quaternion, Vector3 } from 'three'
|
||||||
import { SketchGroup, ExtrudeGroup, Position, Rotation } from '../executor'
|
import { SketchGroup, ExtrudeGroup, Position, Rotation } from '../executor'
|
||||||
@ -70,6 +75,17 @@ const translate: InternalFn = <T extends SketchGroup | ExtrudeGroup>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const min: InternalFn = (_, a: number, b: number): number => Math.min(a, b)
|
||||||
|
|
||||||
|
const legLen: InternalFn = (_, hypotenuse: number, leg: number): number =>
|
||||||
|
Math.sqrt(hypotenuse ** 2 - Math.min(Math.abs(leg), Math.abs(hypotenuse)) ** 2)
|
||||||
|
|
||||||
|
const legAngX: InternalFn = (_, hypotenuse: number, leg: number): number =>
|
||||||
|
(Math.acos(Math.min(leg, hypotenuse) / hypotenuse) * 180) / Math.PI
|
||||||
|
|
||||||
|
const legAngY: InternalFn = (_, hypotenuse: number, leg: number): number =>
|
||||||
|
(Math.asin(Math.min(leg, hypotenuse) / hypotenuse) * 180) / Math.PI
|
||||||
|
|
||||||
export const internalFns: { [key in InternalFnNames]: InternalFn } = {
|
export const internalFns: { [key in InternalFnNames]: InternalFn } = {
|
||||||
rx: rotateOnAxis([1, 0, 0]),
|
rx: rotateOnAxis([1, 0, 0]),
|
||||||
ry: rotateOnAxis([0, 1, 0]),
|
ry: rotateOnAxis([0, 1, 0]),
|
||||||
@ -78,6 +94,13 @@ export const internalFns: { [key in InternalFnNames]: InternalFn } = {
|
|||||||
translate,
|
translate,
|
||||||
transform,
|
transform,
|
||||||
getExtrudeWallTransform,
|
getExtrudeWallTransform,
|
||||||
|
min,
|
||||||
|
legLen,
|
||||||
|
legAngX,
|
||||||
|
legAngY,
|
||||||
|
segLen,
|
||||||
|
angleToMatchLengthX,
|
||||||
|
angleToMatchLengthY,
|
||||||
lineTo: lineTo.fn,
|
lineTo: lineTo.fn,
|
||||||
xLineTo: xLineTo.fn,
|
xLineTo: xLineTo.fn,
|
||||||
yLineTo: yLineTo.fn,
|
yLineTo: yLineTo.fn,
|
||||||
|
@ -20,6 +20,13 @@ export type InternalFnNames =
|
|||||||
| 'translate'
|
| 'translate'
|
||||||
| 'transform'
|
| 'transform'
|
||||||
| 'getExtrudeWallTransform'
|
| 'getExtrudeWallTransform'
|
||||||
|
| 'min'
|
||||||
|
| 'legLen'
|
||||||
|
| 'legAngX'
|
||||||
|
| 'legAngY'
|
||||||
|
| 'segLen'
|
||||||
|
| 'angleToMatchLengthX'
|
||||||
|
| 'angleToMatchLengthY'
|
||||||
| 'rx'
|
| 'rx'
|
||||||
| 'ry'
|
| 'ry'
|
||||||
| 'rz'
|
| 'rz'
|
||||||
@ -75,5 +82,4 @@ export interface SketchLineHelper {
|
|||||||
modifiedAst: Program
|
modifiedAst: Program
|
||||||
tag: string
|
tag: string
|
||||||
}
|
}
|
||||||
allowedTransforms: (a: ModifyAstBase) => Partial<SketchCallTransfromMap>
|
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,19 @@
|
|||||||
import { lexer } from './tokeniser'
|
import { lexer, asyncLexer } from './tokeniser'
|
||||||
import { initPromise } from './rust'
|
import { initPromise } from './rust'
|
||||||
|
|
||||||
beforeAll(() => initPromise)
|
beforeAll(() => initPromise)
|
||||||
|
|
||||||
describe('testing lexer', () => {
|
describe('testing lexer', () => {
|
||||||
|
it('async lexer works too', async () => {
|
||||||
|
const code = '1 + 2'
|
||||||
|
const code2 = `const yo = {key: 'value'}`
|
||||||
|
const code3 = `const yo = 45 /* this is a comment
|
||||||
|
const ya = 6 */
|
||||||
|
const yi=45`
|
||||||
|
expect(await asyncLexer(code)).toEqual(lexer(code))
|
||||||
|
expect(await asyncLexer(code2)).toEqual(lexer(code2))
|
||||||
|
expect(await asyncLexer(code3)).toEqual(lexer(code3))
|
||||||
|
})
|
||||||
it('test lexer', () => {
|
it('test lexer', () => {
|
||||||
expect(stringSummaryLexer('1 + 2')).toEqual([
|
expect(stringSummaryLexer('1 + 2')).toEqual([
|
||||||
"number '1' from 0 to 1",
|
"number '1' from 0 to 1",
|
||||||
|
Reference in New Issue
Block a user