Fixed test 'Should reorder when user selects last-to-first'

This commit is contained in:
Adam Chalmers
2025-01-07 16:37:04 -06:00
committed by Nick Cameron
parent e917eb9af6
commit d66c7cfe33
6 changed files with 320 additions and 57 deletions

View File

@ -32,7 +32,7 @@ import {
isNodeSafeToReplace,
traverse,
} from './queryAst'
import { addTagForSketchOnFace, getConstraintInfo } from './std/sketch'
import { addTagForSketchOnFace, ARG_TAG, getConstraintInfo } from './std/sketch'
import {
PathToNodeMap,
isLiteralArrayOrStatic,
@ -48,6 +48,7 @@ import { Models } from '@kittycad/lib'
import { ExtrudeFacePlane } from 'machines/modelingMachine'
import { Node } from 'wasm-lib/kcl/bindings/Node'
import { KclExpressionWithVariable } from 'lib/commandTypes'
import { findKwArg } from './util'
export function startSketchOnDefault(
node: Node<Program>,
@ -201,6 +202,26 @@ export function findUniqueName(
return findUniqueName(searchStr, name, pad, index + 1)
}
/**
Set the keyword argument to the given value.
Returns true if it overwrote an existing argument.
Returns false if no argument with the label existed before.
*/
export function mutateKwArg(
label: string,
node: CallExpressionKw,
val: Expr
): boolean {
node.arguments.forEach((arg, i) => {
if (arg.label.name === label) {
node.arguments[i].arg = val
return true
}
})
node.arguments.push(createLabeledArg(label, val))
return false
}
export function mutateArrExp(node: Expr, updateWith: ArrayExpression): boolean {
if (node.type === 'ArrayExpression') {
node.elements.forEach((element, i) => {
@ -987,20 +1008,46 @@ export function giveSketchFnCallTag(
}
| Error {
const path = getNodePathFromSourceRange(ast, range)
const _node1 = getNodeFromPath<CallExpression>(ast, path, 'CallExpression')
if (err(_node1)) return _node1
const { node: primaryCallExp } = _node1
const maybeTag = (() => {
const kwCallNode = getNodeFromPath<CallExpressionKw>(
ast,
path,
'CallExpressionKw'
)
if (!err(kwCallNode)) {
const { node: primaryCallExp } = kwCallNode
const existingTag = findKwArg(ARG_TAG, primaryCallExp)
const tagDeclarator =
existingTag ||
(createTagDeclarator(
tag || findUniqueName(ast, 'seg', 2)
) as TagDeclarator)
const isTagExisting = !!existingTag
kwCallNode.node.arguments.push(createLabeledArg(ARG_TAG, tagDeclarator))
return { tagDeclarator, isTagExisting }
}
// Tag is always 3rd expression now, using arg index feels brittle
// but we can come up with a better way to identify tag later.
const thirdArg = primaryCallExp.arguments?.[2]
const tagDeclarator =
thirdArg ||
(createTagDeclarator(tag || findUniqueName(ast, 'seg', 2)) as TagDeclarator)
const isTagExisting = !!thirdArg
if (!isTagExisting) {
primaryCallExp.arguments[2] = tagDeclarator
}
const _node1 = getNodeFromPath<CallExpression>(ast, path, 'CallExpression')
if (err(_node1)) return _node1
const { node: primaryCallExp } = _node1
// Tag is always 3rd expression now, using arg index feels brittle
// but we can come up with a better way to identify tag later.
const thirdArg = primaryCallExp.arguments?.[2]
const tagDeclarator =
thirdArg ||
(createTagDeclarator(
tag || findUniqueName(ast, 'seg', 2)
) as TagDeclarator)
const isTagExisting = !!thirdArg
if (!isTagExisting) {
primaryCallExp.arguments[2] = tagDeclarator
}
return { tagDeclarator, isTagExisting }
})()
if (err(maybeTag)) return maybeTag
const { tagDeclarator, isTagExisting } = maybeTag
if ('value' in tagDeclarator) {
// Now TypeScript knows tagDeclarator has a value property
return {

View File

@ -26,7 +26,11 @@ import {
isNotLiteralArrayOrStatic,
} from 'lang/std/sketchcombos'
import { toolTips, ToolTip } from 'lang/langHelpers'
import { createPipeExpression, splitPathAtPipeExpression } from '../modifyAst'
import {
createPipeExpression,
mutateKwArg,
splitPathAtPipeExpression,
} from '../modifyAst'
import {
SketchLineHelper,
@ -39,6 +43,7 @@ import {
SimplifiedArgDetails,
RawArgs,
CreatedSketchExprResult,
SketchLineHelperKw,
} from 'lang/std/stdTypes'
import {
@ -60,7 +65,9 @@ import { perpendicularDistance } from 'sketch-helpers'
import { TagDeclarator } from 'wasm-lib/kcl/bindings/TagDeclarator'
import { EdgeCutInfo } from 'machines/modelingMachine'
import { Node } from 'wasm-lib/kcl/bindings/Node'
import { findKwArg, findKwArgAny } from 'lang/util'
export const ARG_TAG = 'tag'
const ARG_END = 'end'
const ARG_END_ABSOLUTE = 'endAbsolute'
@ -149,7 +156,7 @@ const constrainInfo = (
})
const commonConstraintInfoHelper = (
callExp: CallExpression,
callExp: CallExpression | CallExpressionKw,
inputConstrainTypes: [ConstrainInfo['type'], ConstrainInfo['type']],
stdLibFnName: ConstrainInfo['stdLibFnName'],
abbreviatedInputs: [
@ -165,8 +172,19 @@ const commonConstraintInfoHelper = (
code: string,
pathToNode: PathToNode
) => {
if (callExp.type !== 'CallExpression') return []
const firstArg = callExp.arguments?.[0]
if (callExp.type !== 'CallExpression' && callExp.type !== 'CallExpressionKw')
return []
const firstArg = (() => {
switch (callExp.type) {
case 'CallExpression':
return callExp.arguments[0]
case 'CallExpressionKw':
return findKwArgAny([ARG_END, ARG_END_ABSOLUTE], callExp)
}
})()
if (firstArg === undefined) {
return []
}
const isArr = firstArg.type === 'ArrayExpression'
if (!isArr && firstArg.type !== 'ObjectExpression') return []
const pipeExpressionIndex = pathToNode.findIndex(
@ -175,7 +193,7 @@ const commonConstraintInfoHelper = (
const pathToBase = pathToNode.slice(0, pipeExpressionIndex + 2)
const pathToArrayExpression: PathToNode = [
...pathToBase,
['arguments', 'CallExpression'],
['arguments', callExp.type],
[0, 'index'],
isArr
? ['elements', 'ArrayExpression']
@ -298,6 +316,18 @@ function getTag(index = 2): SketchLineHelper['getTag'] {
}
}
function getTagKwArg(): SketchLineHelperKw['getTag'] {
return (callExp: CallExpressionKw) => {
if (callExp.type !== 'CallExpressionKw')
return new Error('Not a CallExpressionKw')
const arg = findKwArg(ARG_TAG, callExp)
if (!arg) return new Error('No argument')
if (arg.type !== 'TagDeclarator')
return new Error('Tag not a TagDeclarator')
return arg.value
}
}
export const lineTo: SketchLineHelper = {
add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => {
if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
@ -384,7 +414,7 @@ export const lineTo: SketchLineHelper = {
),
}
export const line: SketchLineHelper = {
export const line: SketchLineHelperKw = {
add: ({
node,
previousProgramMemory,
@ -396,7 +426,7 @@ export const line: SketchLineHelper = {
if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
const { from, to } = segmentInput
const _node = { ...node }
const nodeMeta = getNodeFromPath<PipeExpression | CallExpression>(
const nodeMeta = getNodeFromPath<PipeExpression | CallExpressionKw>(
_node,
pathToNode,
'PipeExpression'
@ -442,7 +472,7 @@ export const line: SketchLineHelper = {
}
}
if (replaceExistingCallback && pipe.type !== 'CallExpression') {
if (replaceExistingCallback && pipe.type !== 'CallExpressionKw') {
const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
const result = replaceExistingCallback([
{
@ -480,7 +510,7 @@ export const line: SketchLineHelper = {
pathToNode: [
...pathToNode,
['body', 'PipeExpression'],
[pipe.body.length - 1, 'CallExpression'],
[pipe.body.length - 1, 'CallExpressionKw'],
],
}
} else {
@ -495,7 +525,7 @@ export const line: SketchLineHelper = {
if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
const { to, from } = input
const _node = { ...node }
const nodeMeta = getNodeFromPath<CallExpression>(_node, pathToNode)
const nodeMeta = getNodeFromPath<CallExpressionKw>(_node, pathToNode)
if (err(nodeMeta)) return nodeMeta
const { node: callExpression } = nodeMeta
@ -504,17 +534,13 @@ export const line: SketchLineHelper = {
createLiteral(roundOff(to[1] - from[1], 2)),
])
if (callExpression.arguments?.[0].type === 'ObjectExpression') {
mutateObjExpProp(callExpression.arguments?.[0], toArrExp, 'to')
} else {
mutateArrExp(callExpression.arguments?.[0], toArrExp)
}
mutateKwArg('end', callExpression, toArrExp)
return {
modifiedAst: _node,
pathToNode,
}
},
getTag: getTag(),
getTag: getTagKwArg(),
addTag: addTag(),
getConstraintInfo: (callExp, ...args) =>
commonConstraintInfoHelper(
@ -1861,8 +1887,6 @@ export const updateStartProfileAtArgs: SketchLineHelper['updateArgs'] = ({
}
export const sketchLineHelperMap: { [key: string]: SketchLineHelper } = {
line,
lineTo,
xLine,
yLine,
xLineTo,
@ -1877,6 +1901,10 @@ export const sketchLineHelperMap: { [key: string]: SketchLineHelper } = {
circle,
} as const
export const sketchLineHelperMapKw: { [key: string]: SketchLineHelperKw } = {
line,
} as const
export function changeSketchArguments(
node: Node<Program>,
programMemory: ProgramMemory,
@ -1932,6 +1960,20 @@ export function getConstraintInfo(
)
}
export function getConstraintInfoKw(
callExpression: Node<CallExpressionKw>,
code: string,
pathToNode: PathToNode
): ConstrainInfo[] {
const fnName = callExpression?.callee?.name || ''
if (!(fnName in sketchLineHelperMap)) return []
return sketchLineHelperMapKw[fnName].getConstraintInfo(
callExpression,
code,
pathToNode
)
}
export function compareVec2Epsilon(
vec1: [number, number],
vec2: [number, number],
@ -2497,20 +2539,15 @@ export function getArgForEnd(lineCall: CallExpressionKw):
| Error {
const name = lineCall?.callee?.name
let arg
if (name == 'line') {
arg = lineCall.arguments.find((labeledArg) => {
return (
labeledArg.label.name === ARG_END ||
labeledArg.label.name === ARG_END_ABSOLUTE
)
})
if (name === 'line') {
arg = findKwArgAny([ARG_END, ARG_END_ABSOLUTE], lineCall)
} else {
return new Error('cannot find end of line function: ' + name)
}
if (arg == undefined) {
return new Error('no end of the line was found')
if (arg === undefined) {
return new Error("no end of the line was found in fn '" + name + "'")
}
return getValuesForXYFns(arg.arg)
return getValuesForXYFns(arg)
}
export function getFirstArg(callExp: CallExpression):

View File

@ -1,5 +1,6 @@
import { getNodeFromPath } from 'lang/queryAst'
import { ToolTip, toolTips } from 'lang/langHelpers'
import { Node } from 'wasm-lib/kcl/bindings/Node'
import {
Program,
VariableDeclarator,
@ -10,6 +11,7 @@ import {
PathToNode,
Expr,
topLevelRange,
LabeledArg,
} from '../wasm'
import { err } from 'lib/trap'
@ -23,13 +25,27 @@ export function getSketchSegmentFromPathToNode(
index: number
}
| Error {
// TODO: once pathTodNode is stored on program memory as part of execution,
// TODO: once pathToNode is stored on program memory as part of execution,
// we can check if the pathToNode matches the pathToNode of the sketch.
// For now we fall back to the sourceRange
const nodeMeta = getNodeFromPath<Expr>(ast, pathToNode)
const nodeMeta = getNodeFromPath<Node<Expr> | Node<LabeledArg>>(
ast,
pathToNode
)
if (err(nodeMeta)) return nodeMeta
const node = nodeMeta.node
const _node = nodeMeta.node
const node = (() => {
switch (_node.type) {
// LabeledArg wraps the expression being assigned to a parameter.
// So, undo the wrapper. Used for keyword arguments.
case 'LabeledArg':
return _node.arg
// Other nodes aren't wrapped, we can return them directly.
default:
return _node
}
})()
if (!node || typeof node.start !== 'number' || !node.end)
return new Error('no node found')
const sourceRange = topLevelRange(node.start, node.end)

View File

@ -22,6 +22,7 @@ import {
Literal,
SourceRange,
LiteralValue,
recast,
} from '../wasm'
import {
getNodeFromPath,
@ -48,6 +49,8 @@ import {
getFirstArg,
getArgForEnd,
replaceSketchLine,
ARG_TAG,
getConstraintInfoKw,
} from './sketch'
import {
getSketchSegmentFromPathToNode,
@ -55,6 +58,7 @@ import {
} from './sketchConstraints'
import { getAngle, roundOff, normaliseAngle } from '../../lib/utils'
import { Node } from 'wasm-lib/kcl/bindings/Node'
import { findKwArg } from 'lang/util'
export type LineInputsType =
| 'xAbsolute'
@ -1475,6 +1479,60 @@ function getTransformMapPath(
return false
}
function getTransformMapPathKw(
sketchFnExp: CallExpressionKw,
constraintType: ConstraintType
):
| {
toolTip: ToolTip
lineInputType: LineInputsType | 'free'
constraintType: ConstraintType
}
| false {
const name = sketchFnExp.callee.name as ToolTip
if (!toolTips.includes(name)) {
return false
}
// check if the function is locked down and so can't be transformed
const argForEnd = getArgForEnd(sketchFnExp)
if (err(argForEnd)) {
console.error(argForEnd)
return false
}
if (isNotLiteralArrayOrStatic(argForEnd.val)) {
return false
}
// check if the function has no constraints
if (isLiteralArrayOrStatic(argForEnd.val)) {
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(argForEnd.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
@ -1487,6 +1545,18 @@ export function getTransformInfo(
return info
}
export function getTransformInfoKw(
sketchFnExp: CallExpressionKw,
constraintType: ConstraintType
): TransformInfo | false {
const path = getTransformMapPathKw(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: Expr | [Expr, Expr] | [Expr, Expr, Expr],
fnName: ToolTip
@ -1527,7 +1597,10 @@ export function getTransformInfos(
constraintType: ConstraintType
): TransformInfo[] {
const nodes = selectionRanges.graphSelections.map(({ codeRef }) =>
getNodeFromPath<Expr>(ast, codeRef.pathToNode, 'CallExpression')
getNodeFromPath<Expr>(ast, codeRef.pathToNode, [
'CallExpression',
'CallExpressionKw',
])
)
try {
@ -1538,8 +1611,13 @@ export function getTransformInfos(
}
const node = nodeMeta.node
if (node?.type === 'CallExpression')
return getTransformInfo(node, constraintType)
if (node?.type === 'CallExpression') {
return getTransformInfo(node, constraintType)
}
if (node?.type === 'CallExpressionKw') {
return getTransformInfoKw(node, constraintType)
}
return false
}) as TransformInfo[]
@ -1691,17 +1769,39 @@ export function transformAstSketchLines({
const getNode = getNodeFromPathCurry(node, _pathToNode)
// Find `call` which could either be a positional-arg or keyword-arg call.
const callExp = getNode<Node<CallExpression>>('CallExpression')
if (err(callExp)) return callExp
const callExpKw = getNode<Node<CallExpressionKw>>('CallExpressionKw')
const call =
!err(callExp) && callExp.node.type === 'CallExpression'
? callExp
: callExpKw
if (err(call)) return call
const varDec = getNode<VariableDeclarator>('VariableDeclarator')
if (err(varDec)) return varDec
const callBackTag = callExp.node.arguments[2]
const _referencedSegmentNameVal =
callExp.node.arguments[0]?.type === 'ObjectExpression' &&
callExp.node.arguments[0].properties?.find(
(prop) => prop.key.name === 'intersectTag'
)?.value
const callBackTag = (() => {
switch (call.node.type) {
case 'CallExpression':
return call.node.arguments[2]
case 'CallExpressionKw':
return findKwArg(ARG_TAG, call.node)
}
})()
const _referencedSegmentNameVal = (() => {
switch (call.node.type) {
case 'CallExpressionKw':
return findKwArg('intersectTag', call.node)
case 'CallExpression':
return (
call.node.arguments[0]?.type === 'ObjectExpression' &&
call.node.arguments[0].properties?.find(
(prop) => prop.key.name === 'intersectTag'
)?.value
)
}
})()
const _referencedSegmentName =
referenceSegName ||
(_referencedSegmentNameVal &&
@ -1710,7 +1810,15 @@ export function transformAstSketchLines({
''
const inputs: InputArgs = []
getConstraintInfo(callExp.node, '', _pathToNode).forEach((a) => {
const constraints = (() => {
switch (call.node.type) {
case 'CallExpression':
return getConstraintInfo(call.node, '', _pathToNode)
case 'CallExpressionKw':
return getConstraintInfoKw(call.node, '', _pathToNode)
}
})()
constraints.forEach((a) => {
if (
a.type === 'tangentialWithPrevious' ||
a.type === 'horizontal' ||
@ -1786,7 +1894,7 @@ export function transformAstSketchLines({
programMemory,
pathToNode: _pathToNode,
referencedSegment,
fnName: transformTo || (callExp.node.callee.name as ToolTip),
fnName: transformTo || (call.node.callee.name as ToolTip),
segmentInput:
seg.type === 'Circle'
? {

View File

@ -9,6 +9,7 @@ import {
CallExpression,
Literal,
BinaryPart,
CallExpressionKw,
} from '../wasm'
import { LineInputsType } from './sketchcombos'
import { Node } from 'wasm-lib/kcl/bindings/Node'
@ -241,3 +242,31 @@ export interface SketchLineHelper {
pathToNode: PathToNode
) => ConstrainInfo[]
}
export interface SketchLineHelperKw {
add: (a: addCall) =>
| {
modifiedAst: Node<Program>
pathToNode: PathToNode
valueUsedInTransform?: number
}
| Error
updateArgs: (a: updateArgs) =>
| {
modifiedAst: Node<Program>
pathToNode: PathToNode
}
| Error
getTag: (a: CallExpressionKw) => string | Error
addTag: (a: AddTagInfo) =>
| {
modifiedAst: Node<Program>
tag: string
}
| Error
getConstraintInfo: (
callExp: Node<CallExpressionKw>,
code: string,
pathToNode: PathToNode
) => ConstrainInfo[]
}

View File

@ -6,6 +6,8 @@ import {
ArrayExpression,
BinaryExpression,
ArtifactGraph,
CallExpressionKw,
Expr,
} from './wasm'
import { filterArtifacts } from 'lang/std/artifactGraph'
import { isOverlap } from 'lib/utils'
@ -69,3 +71,27 @@ export function isLiteral(e: any): e is Literal {
export function isBinaryExpression(e: any): e is BinaryExpression {
return e && e.type === 'BinaryExpression'
}
/**
Search the keyword arguments from a call for an argument with this label.
*/
export function findKwArg(
label: string,
call: CallExpressionKw
): Expr | undefined {
return call.arguments.find((arg) => {
return arg.label.name === label
})?.arg
}
/**
Search the keyword arguments from a call for an argument with one of these labels.
*/
export function findKwArgAny(
labels: string[],
call: CallExpressionKw
): Expr | undefined {
return call.arguments.find((arg) => {
return labels.includes(arg.label.name)
})?.arg
}