Compare commits
2 Commits
v0.35.0
...
franknoiro
Author | SHA1 | Date | |
---|---|---|---|
e3400705f4 | |||
10ed312b28 |
44
src/lib/kclHelpers.test.ts
Normal file
44
src/lib/kclHelpers.test.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { getCalculatedKclExpressionValue } from './kclHelpers'
|
||||
|
||||
describe('KCL expression calculations', () => {
|
||||
it('calculates a simple expression', async () => {
|
||||
const actual = await getCalculatedKclExpressionValue({
|
||||
value: '1 + 2',
|
||||
variables: [],
|
||||
})
|
||||
expect(actual?.valueAsString).toEqual('3')
|
||||
expect(actual?.astNode).toBeDefined()
|
||||
})
|
||||
it('calculates a simple expression with a variable', async () => {
|
||||
const actual = await getCalculatedKclExpressionValue({
|
||||
value: '1 + x',
|
||||
variables: [{ key: 'x', value: 2 }],
|
||||
})
|
||||
expect(actual?.valueAsString).toEqual('3')
|
||||
expect(actual?.astNode).toBeDefined()
|
||||
})
|
||||
it('returns NAN for an invalid expression', async () => {
|
||||
const actual = await getCalculatedKclExpressionValue({
|
||||
value: '1 + x',
|
||||
variables: [],
|
||||
})
|
||||
expect(actual?.valueAsString).toEqual('NAN')
|
||||
expect(actual?.astNode).toBeDefined()
|
||||
})
|
||||
it('returns NAN for an expression with an invalid variable', async () => {
|
||||
const actual = await getCalculatedKclExpressionValue({
|
||||
value: '1 + x',
|
||||
variables: [{ key: 'y', value: 2 }],
|
||||
})
|
||||
expect(actual?.valueAsString).toEqual('NAN')
|
||||
expect(actual?.astNode).toBeDefined()
|
||||
})
|
||||
it('calculates a more complex expression with a variable', async () => {
|
||||
const actual = await getCalculatedKclExpressionValue({
|
||||
value: '(1 + x * x) * 2',
|
||||
variables: [{ key: 'x', value: 2 }],
|
||||
})
|
||||
expect(actual?.valueAsString).toEqual('10')
|
||||
expect(actual?.astNode).toBeDefined()
|
||||
})
|
||||
})
|
74
src/lib/kclHelpers.ts
Normal file
74
src/lib/kclHelpers.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import { err, trap } from './trap'
|
||||
import { engineCommandManager, kclManager } from 'lib/singletons'
|
||||
import { parse, ProgramMemory, resultIsOk } from 'lang/wasm'
|
||||
import { PrevVariable } from 'lang/queryAst'
|
||||
import { executeAst } from 'lang/langHelpers'
|
||||
|
||||
const DUMMY_VARIABLE_NAME = '__result__'
|
||||
|
||||
/**
|
||||
* Calculate the value of the KCL expression,
|
||||
* given the value and the variables that are available
|
||||
*/
|
||||
export async function getCalculatedKclExpressionValue({
|
||||
value,
|
||||
variables,
|
||||
}: {
|
||||
value: string
|
||||
variables: PrevVariable<string | number>[]
|
||||
}) {
|
||||
// Create a one-line program that assigns the value to a variable
|
||||
const dummyProgramCode = `const ${DUMMY_VARIABLE_NAME} = ${value}`
|
||||
const pResult = parse(dummyProgramCode)
|
||||
if (err(pResult) || !resultIsOk(pResult)) return
|
||||
const ast = pResult.program
|
||||
|
||||
// Populate the program memory with the passed-in variables
|
||||
const programMemoryOverride: ProgramMemory = ProgramMemory.empty()
|
||||
for (const { key, value } of variables) {
|
||||
const error = programMemoryOverride.set(
|
||||
key,
|
||||
typeof value === 'number'
|
||||
? {
|
||||
type: 'Number',
|
||||
value,
|
||||
__meta: [],
|
||||
}
|
||||
: {
|
||||
type: 'String',
|
||||
value,
|
||||
__meta: [],
|
||||
}
|
||||
)
|
||||
if (trap(error, { suppress: true })) return
|
||||
}
|
||||
|
||||
console.log(
|
||||
'programMemoryOverride',
|
||||
JSON.stringify(programMemoryOverride, null, 2)
|
||||
)
|
||||
|
||||
// Execute the program without hitting the engine
|
||||
const { execState } = await executeAst({
|
||||
ast,
|
||||
engineCommandManager,
|
||||
programMemoryOverride,
|
||||
})
|
||||
|
||||
// Find the variable declaration for the result
|
||||
const resultDeclaration = ast.body.find(
|
||||
(a) =>
|
||||
a.type === 'VariableDeclaration' &&
|
||||
a.declaration.id?.name === DUMMY_VARIABLE_NAME
|
||||
)
|
||||
const variableDeclaratorAstNode =
|
||||
resultDeclaration?.type === 'VariableDeclaration' &&
|
||||
resultDeclaration?.declaration.init
|
||||
const resultRawValue = execState.memory?.get(DUMMY_VARIABLE_NAME)?.value
|
||||
|
||||
return {
|
||||
astNode: variableDeclaratorAstNode,
|
||||
valueAsString:
|
||||
typeof resultRawValue === 'number' ? String(resultRawValue) : 'NAN',
|
||||
}
|
||||
}
|
@ -1,12 +1,11 @@
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
import { kclManager, engineCommandManager } from 'lib/singletons'
|
||||
import { kclManager } from 'lib/singletons'
|
||||
import { useKclContext } from 'lang/KclProvider'
|
||||
import { findUniqueName } from 'lang/modifyAst'
|
||||
import { PrevVariable, findAllPreviousVariables } from 'lang/queryAst'
|
||||
import { ProgramMemory, Expr, parse, resultIsOk } from 'lang/wasm'
|
||||
import { Expr } from 'lang/wasm'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { executeAst } from 'lang/langHelpers'
|
||||
import { err, trap } from 'lib/trap'
|
||||
import { getCalculatedKclExpressionValue } from './kclHelpers'
|
||||
|
||||
const isValidVariableName = (name: string) =>
|
||||
/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)
|
||||
@ -86,37 +85,12 @@ export function useCalculateKclExpression({
|
||||
|
||||
useEffect(() => {
|
||||
const execAstAndSetResult = async () => {
|
||||
const _code = `const __result__ = ${value}`
|
||||
const pResult = parse(_code)
|
||||
if (err(pResult) || !resultIsOk(pResult)) return
|
||||
const ast = pResult.program
|
||||
|
||||
const _programMem: ProgramMemory = ProgramMemory.empty()
|
||||
for (const { key, value } of availableVarInfo.variables) {
|
||||
const error = _programMem.set(key, {
|
||||
type: 'String',
|
||||
value,
|
||||
__meta: [],
|
||||
})
|
||||
if (trap(error, { suppress: true })) return
|
||||
}
|
||||
const { execState } = await executeAst({
|
||||
ast,
|
||||
engineCommandManager,
|
||||
// We make sure to send an empty program memory to denote we mean mock mode.
|
||||
programMemoryOverride: kclManager.programMemory.clone(),
|
||||
const result = await getCalculatedKclExpressionValue({
|
||||
value,
|
||||
variables: availableVarInfo.variables,
|
||||
})
|
||||
const resultDeclaration = ast.body.find(
|
||||
(a) =>
|
||||
a.type === 'VariableDeclaration' &&
|
||||
a.declaration.id?.name === '__result__'
|
||||
)
|
||||
const init =
|
||||
resultDeclaration?.type === 'VariableDeclaration' &&
|
||||
resultDeclaration?.declaration.init
|
||||
const result = execState.memory?.get('__result__')?.value
|
||||
setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
|
||||
init && setValueNode(init)
|
||||
setCalcResult(result?.valueAsString || 'NAN')
|
||||
result?.astNode && setValueNode(result.astNode)
|
||||
}
|
||||
if (!value) return
|
||||
execAstAndSetResult().catch(() => {
|
||||
|
Reference in New Issue
Block a user