Parse units on numeric literals and keep them in the AST (#5061)

* Code changes

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* test changes

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Frontend changes

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Refactor asNum

Co-authored-by: Jonathan Tran <jonnytran@gmail.com>

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
This commit is contained in:
Nick Cameron
2025-01-22 08:29:30 +13:00
committed by GitHub
parent a022b8ef6c
commit 965cb18059
173 changed files with 15324 additions and 4005 deletions

View File

@ -20,12 +20,12 @@ import {
sketchFromKclValue,
Literal,
SourceRange,
LiteralValue,
} from '../wasm'
import {
getNodeFromPath,
getNodeFromPathCurry,
getNodePathFromSourceRange,
isValueZero,
} from '../queryAst'
import {
createArrayExpression,
@ -79,11 +79,32 @@ export type ConstraintType =
| 'setAngleBetween'
const REF_NUM_ERR = new Error('Referenced segment does not have a to value')
function asNum(val: LiteralValue): number | Error {
if (typeof val === 'object') return val.value
return REF_NUM_ERR
}
function forceNum(arg: Literal): number {
if (typeof arg.value === 'boolean' || typeof arg.value === 'string') {
return Number(arg.value)
} else {
return arg.value.value
}
}
function isUndef(val: any): val is undefined {
return typeof val === 'undefined'
}
function isNum(val: any): val is number {
return typeof val === 'number'
function isValueZero(val?: Expr): boolean {
return (
(val?.type === 'Literal' && forceNum(val) === 0) ||
(val?.type === 'UnaryExpression' &&
val.operator === '-' &&
val.argument.type === 'Literal' &&
Number(val.argument.value) === 0)
)
}
function createCallWrapper(
@ -190,7 +211,7 @@ const xyLineSetLength =
: referenceSeg
? segRef
: args[0].expr
const literalARg = getArgLiteralVal(args[0].expr)
const literalARg = asNum(args[0].expr.value)
if (err(literalARg)) return literalARg
return createCallWrapper(xOrY, lineVal, tag, literalARg)
}
@ -211,13 +232,14 @@ const basicAngledLineCreateNode =
referencedSegment: path,
}) => {
const refAng = path ? getAngle(path?.from, path?.to) : 0
if (!isNum(args[0].expr.value)) return REF_NUM_ERR
const argValue = asNum(args[0].expr.value)
if (err(argValue)) return argValue
const nonForcedAng =
varValToUse === 'ang'
? inputs[0].expr
: referenceSeg === 'ang'
? getClosesAngleDirection(
args[0].expr.value,
argValue,
refAng,
createSegAngle(referenceSegName)
)
@ -230,8 +252,8 @@ const basicAngledLineCreateNode =
: args[1].expr
const shouldForceAng = valToForce === 'ang' && forceValueUsedInTransform
const shouldForceLen = valToForce === 'len' && forceValueUsedInTransform
const literalArg = getArgLiteralVal(
valToForce === 'ang' ? args[0].expr : args[1].expr
const literalArg = asNum(
valToForce === 'ang' ? args[0].expr.value : args[1].expr.value
)
if (err(literalArg)) return literalArg
return createCallWrapper(
@ -283,7 +305,7 @@ const getMinAndSegAngVals = (
}
const getSignedLeg = (arg: Literal, legLenVal: BinaryPart) =>
Number(arg.value) < 0 ? createUnaryExpression(legLenVal) : legLenVal
forceNum(arg) < 0 ? createUnaryExpression(legLenVal) : legLenVal
const getLegAng = (ang: number, legAngleVal: BinaryPart) => {
const normalisedAngle = ((ang % 360) + 360) % 360 // between 0 and 360
@ -322,8 +344,7 @@ const setHorzVertDistanceCreateNode =
referencedSegment,
}) => {
const refNum = referencedSegment?.to?.[index]
const literalArg = getArgLiteralVal(args?.[index].expr)
if (err(literalArg)) return literalArg
const literalArg = asNum(args?.[index].expr.value)
if (isUndef(refNum) || err(literalArg)) return REF_NUM_ERR
const valueUsedInTransform = roundOff(literalArg - refNum, 2)
@ -352,7 +373,7 @@ const setHorzVertDistanceForAngleLineCreateNode =
referencedSegment,
}) => {
const refNum = referencedSegment?.to?.[index]
const literalArg = getArgLiteralVal(args?.[1].expr)
const literalArg = asNum(args?.[1].expr.value)
if (isUndef(refNum) || err(literalArg)) return REF_NUM_ERR
const valueUsedInTransform = roundOff(literalArg - refNum, 2)
const binExp = createBinaryExpressionWithUnary([
@ -374,8 +395,8 @@ const setAbsDistanceCreateNode =
index = xOrY === 'x' ? 0 : 1
): CreateStdLibSketchCallExpr =>
({ tag, forceValueUsedInTransform, rawArgs: args }) => {
const literalArg = getArgLiteralVal(args?.[index].expr)
if (err(literalArg)) return REF_NUM_ERR
const literalArg = asNum(args?.[index].expr.value)
if (err(literalArg)) return literalArg
const valueUsedInTransform = roundOff(literalArg, 2)
const val = forceValueUsedInTransform || createLiteral(valueUsedInTransform)
if (isXOrYLine) {
@ -396,8 +417,8 @@ const setAbsDistanceCreateNode =
const setAbsDistanceForAngleLineCreateNode =
(xOrY: 'x' | 'y'): CreateStdLibSketchCallExpr =>
({ tag, forceValueUsedInTransform, inputs, rawArgs: args }) => {
const literalArg = getArgLiteralVal(args?.[1].expr)
if (err(literalArg)) return REF_NUM_ERR
const literalArg = asNum(args?.[1].expr.value)
if (err(literalArg)) return literalArg
const valueUsedInTransform = roundOff(literalArg, 2)
const val = forceValueUsedInTransform || createLiteral(valueUsedInTransform)
return createCallWrapper(
@ -419,7 +440,7 @@ const setHorVertDistanceForXYLines =
}) => {
const index = xOrY === 'x' ? 0 : 1
const refNum = referencedSegment?.to?.[index]
const literalArg = getArgLiteralVal(args?.[index].expr)
const literalArg = asNum(args?.[index].expr.value)
if (isUndef(refNum) || err(literalArg)) return REF_NUM_ERR
const valueUsedInTransform = roundOff(literalArg - refNum, 2)
const makeBinExp = createBinaryExpressionWithUnary([
@ -445,9 +466,9 @@ const setHorzVertDistanceConstraintLineCreateNode =
])
const makeBinExp = (index: 0 | 1) => {
const arg = getArgLiteralVal(args?.[index].expr)
const arg = asNum(args?.[index].expr.value)
const refNum = referencedSegment?.to?.[index]
if (err(arg) || !isNum(refNum)) return REF_NUM_ERR
if (err(arg) || isUndef(refNum)) return REF_NUM_ERR
return createBinaryExpressionWithUnary([
createSegEnd(referenceSegName, isX),
createLiteral(roundOff(arg - refNum, 2)),
@ -468,9 +489,9 @@ const setAngledIntersectLineForLines: CreateStdLibSketchCallExpr = ({
forceValueUsedInTransform,
rawArgs: args,
}) => {
const val = args[1].expr.value,
angle = args[0].expr.value
if (!isNum(val) || !isNum(angle)) return REF_NUM_ERR
const val = asNum(args[1].expr.value),
angle = asNum(args[0].expr.value)
if (err(val) || err(angle)) return REF_NUM_ERR
const valueUsedInTransform = roundOff(val, 2)
const varNamMap: { [key: number]: string } = {
0: 'ZERO',
@ -498,8 +519,8 @@ const setAngledIntersectForAngledLines: CreateStdLibSketchCallExpr = ({
inputs,
rawArgs: args,
}) => {
const val = args[1].expr.value
if (!isNum(val)) return REF_NUM_ERR
const val = asNum(args[1].expr.value)
if (err(val)) return val
const valueUsedInTransform = roundOff(val, 2)
return intersectCallWrapper({
fnName: 'angledLineThatIntersects',
@ -524,8 +545,8 @@ const setAngleBetweenCreateNode =
const refAngle = referencedSegment
? getAngle(referencedSegment?.from, referencedSegment?.to)
: 0
const val = args[0].expr.value
if (!isNum(val)) return REF_NUM_ERR
const val = asNum(args[0].expr.value)
if (err(val)) return val
let valueUsedInTransform = roundOff(normaliseAngle(val - refAngle))
let firstHalfValue = createSegAngle(referenceSegName)
if (Math.abs(valueUsedInTransform) > 90) {
@ -706,13 +727,11 @@ const transformMap: TransformMap = {
createPipeSubstitution(),
]
)
if (!isNum(args[0].expr.value)) return REF_NUM_ERR
const val = asNum(args[0].expr.value)
if (err(val)) return val
return createCallWrapper(
'angledLineToX',
[
getAngleLengthSign(args[0].expr.value, angleToMatchLengthXCall),
inputs[0].expr,
],
[getAngleLengthSign(val, angleToMatchLengthXCall), inputs[0].expr],
tag
)
},
@ -739,13 +758,11 @@ const transformMap: TransformMap = {
createPipeSubstitution(),
]
)
if (!isNum(args[0].expr.value)) return REF_NUM_ERR
const val = asNum(args[0].expr.value)
if (err(val)) return val
return createCallWrapper(
'angledLineToY',
[
getAngleLengthSign(args[0].expr.value, angleToMatchLengthYCall),
inputs[1].expr,
],
[getAngleLengthSign(val, angleToMatchLengthYCall), inputs[1].expr],
tag
)
},
@ -763,7 +780,7 @@ const transformMap: TransformMap = {
forceValueUsedInTransform,
rawArgs: args,
}) => {
const val = getArgLiteralVal(args[0].expr)
const val = asNum(args[0].expr.value)
if (err(val)) return val
return createCallWrapper(
'angledLineToY',
@ -844,7 +861,7 @@ const transformMap: TransformMap = {
tooltip: 'yLine',
createNode: ({ inputs, tag, rawArgs: args }) => {
const expr = inputs[1].expr
if (Number(args[0].expr.value) >= 0)
if (forceNum(args[0].expr) >= 0)
return createCallWrapper('yLine', expr, tag)
if (isExprBinaryPart(expr))
return createCallWrapper('yLine', createUnaryExpression(expr), tag)
@ -856,7 +873,7 @@ const transformMap: TransformMap = {
tooltip: 'xLine',
createNode: ({ inputs, tag, rawArgs: args }) => {
const expr = inputs[1].expr
if (Number(args[0].expr.value) >= 0)
if (forceNum(args[0].expr) >= 0)
return createCallWrapper('xLine', expr, tag)
if (isExprBinaryPart(expr))
return createCallWrapper('xLine', createUnaryExpression(expr), tag)
@ -900,10 +917,11 @@ const transformMap: TransformMap = {
referenceSegName,
getInputOfType(inputs, 'xRelative').expr
)
if (!isNum(args[0].expr.value)) return REF_NUM_ERR
const val = asNum(args[0].expr.value)
if (err(val)) return val
return createCallWrapper(
'angledLineOfXLength',
[getLegAng(args[0].expr.value, legAngle), minVal],
[getLegAng(val, legAngle), minVal],
tag
)
},
@ -912,7 +930,7 @@ const transformMap: TransformMap = {
tooltip: 'xLine',
createNode: ({ inputs, tag, rawArgs: args }) => {
const expr = inputs[1].expr
if (Number(args[0].expr.value) >= 0)
if (forceNum(args[0].expr) >= 0)
return createCallWrapper('xLine', expr, tag)
if (isExprBinaryPart(expr))
return createCallWrapper('xLine', createUnaryExpression(expr), tag)
@ -953,10 +971,11 @@ const transformMap: TransformMap = {
inputs[1].expr,
'legAngY'
)
if (!isNum(args[0].expr.value)) return REF_NUM_ERR
const val = asNum(args[0].expr.value)
if (err(val)) return val
return createCallWrapper(
'angledLineOfXLength',
[getLegAng(args[0].expr.value, legAngle), minVal],
[getLegAng(val, legAngle), minVal],
tag
)
},
@ -965,7 +984,7 @@ const transformMap: TransformMap = {
tooltip: 'yLine',
createNode: ({ inputs, tag, rawArgs: args }) => {
const expr = inputs[1].expr
if (Number(args[0].expr.value) >= 0)
if (forceNum(args[0].expr) >= 0)
return createCallWrapper('yLine', expr, tag)
if (isExprBinaryPart(expr))
return createCallWrapper('yLine', createUnaryExpression(expr), tag)
@ -1005,13 +1024,11 @@ const transformMap: TransformMap = {
createPipeSubstitution(),
]
)
if (!isNum(args[0].expr.value)) return REF_NUM_ERR
const val = asNum(args[0].expr.value)
if (err(val)) return val
return createCallWrapper(
'angledLineToX',
[
getAngleLengthSign(args[0].expr.value, angleToMatchLengthXCall),
inputs[1].expr,
],
[getAngleLengthSign(val, angleToMatchLengthXCall), inputs[1].expr],
tag
)
},
@ -1057,13 +1074,11 @@ const transformMap: TransformMap = {
createPipeSubstitution(),
]
)
if (!isNum(args[0].expr.value)) return REF_NUM_ERR
const val = asNum(args[0].expr.value)
if (err(val)) return val
return createCallWrapper(
'angledLineToY',
[
getAngleLengthSign(args[0].expr.value, angleToMatchLengthXCall),
inputs[1].expr,
],
[getAngleLengthSign(val, angleToMatchLengthXCall), inputs[1].expr],
tag
)
},
@ -1080,7 +1095,7 @@ const transformMap: TransformMap = {
equalLength: {
tooltip: 'xLine',
createNode: ({ referenceSegName, tag, rawArgs: args }) => {
const argVal = getArgLiteralVal(args[0].expr)
const argVal = asNum(args[0].expr.value)
if (err(argVal)) return argVal
const segLen = createSegLen(referenceSegName)
if (argVal > 0) return createCallWrapper('xLine', segLen, tag, argVal)
@ -1118,7 +1133,7 @@ const transformMap: TransformMap = {
equalLength: {
tooltip: 'yLine',
createNode: ({ referenceSegName, tag, rawArgs: args }) => {
const argVal = getArgLiteralVal(args[0].expr)
const argVal = asNum(args[0].expr.value)
if (err(argVal)) return argVal
let segLen = createSegLen(referenceSegName)
if (argVal < 0) segLen = createUnaryExpression(segLen)
@ -1823,11 +1838,6 @@ function createLastSeg(isX: boolean): Node<CallExpression> {
])
}
function getArgLiteralVal(arg: Literal): number | Error {
if (!isNum(arg.value)) return REF_NUM_ERR
return arg.value
}
export type ConstraintLevel = 'free' | 'partial' | 'full'
export function getConstraintLevelFromSourceRange(