diff --git a/src/backend/shared/utils/cpp/__tests__/generateCBlocksCode.test.ts b/src/backend/shared/utils/cpp/__tests__/generateCBlocksCode.test.ts index 0c7d24fed..326977bec 100644 --- a/src/backend/shared/utils/cpp/__tests__/generateCBlocksCode.test.ts +++ b/src/backend/shared/utils/cpp/__tests__/generateCBlocksCode.test.ts @@ -1,7 +1,7 @@ import type { PLCVariable } from '../../../../../middleware/shared/ports/types' import { generateCBlocksCode } from '../generateCBlocksCode' -const makeScalarVar = (name: string, cls: 'input' | 'output', baseType: string): PLCVariable => ({ +const makeScalarVar = (name: string, cls: 'input' | 'output' | 'local', baseType: string): PLCVariable => ({ name, class: cls, type: { definition: 'base-type', value: baseType }, @@ -10,7 +10,7 @@ const makeScalarVar = (name: string, cls: 'input' | 'output', baseType: string): debug: false, }) -const makeArrayVar = (name: string, cls: 'input' | 'output', baseType: string, dimension: string): PLCVariable => ({ +const makeArrayVar = (name: string, cls: 'input' | 'output' | 'local', baseType: string, dimension: string): PLCVariable => ({ name, class: cls, type: { @@ -120,17 +120,12 @@ describe('generateCBlocksCode', () => { expect(result).toContain('// comment about setup') }) - it('filters variables by class (only input and output)', () => { + it('includes user local variables and excludes runtime-only locals', () => { const variables: PLCVariable[] = [ makeScalarVar('inVar', 'input', 'INT'), - { - name: 'localVar', - class: 'local', - type: { definition: 'base-type', value: 'INT' }, - location: '', - documentation: '', - debug: false, - }, + makeScalarVar('localVar', 'local', 'INT'), + makeArrayVar('history', 'local', 'INT', '0..2'), + makeScalarVar('hasBeenInitialized', 'local', 'BOOL'), makeScalarVar('outVar', 'output', 'INT'), ] const code = 'void setup() { }\nvoid loop() { }' @@ -138,10 +133,14 @@ describe('generateCBlocksCode', () => { const result = generateCBlocksCode([{ name: 'test', code, variables }]) expect(result).toContain('#define inVar') + expect(result).toContain('#define localVar') + expect(result).toContain('#define history (vars->HISTORY)') expect(result).toContain('#define outVar') - expect(result).not.toContain('#define localVar') + expect(result).not.toContain('#define hasBeenInitialized') expect(result).toContain('#undef inVar') + expect(result).toContain('#undef localVar') + expect(result).toContain('#undef history') expect(result).toContain('#undef outVar') - expect(result).not.toContain('#undef localVar') + expect(result).not.toContain('#undef hasBeenInitialized') }) }) diff --git a/src/backend/shared/utils/cpp/__tests__/generateCBlocksHeader.test.ts b/src/backend/shared/utils/cpp/__tests__/generateCBlocksHeader.test.ts index 4f66166f9..40b555c0b 100644 --- a/src/backend/shared/utils/cpp/__tests__/generateCBlocksHeader.test.ts +++ b/src/backend/shared/utils/cpp/__tests__/generateCBlocksHeader.test.ts @@ -1,7 +1,7 @@ import type { PLCVariable } from '../../../../../middleware/shared/ports/types' import { generateCBlocksHeader } from '../generateCBlocksHeader' -const makeScalarVar = (name: string, cls: 'input' | 'output', baseType: string): PLCVariable => ({ +const makeScalarVar = (name: string, cls: 'input' | 'output' | 'local', baseType: string): PLCVariable => ({ name, class: cls, type: { definition: 'base-type', value: baseType }, @@ -10,7 +10,7 @@ const makeScalarVar = (name: string, cls: 'input' | 'output', baseType: string): debug: false, }) -const makeArrayVar = (name: string, cls: 'input' | 'output', baseType: string, dimension: string): PLCVariable => ({ +const makeArrayVar = (name: string, cls: 'input' | 'output' | 'local', baseType: string, dimension: string): PLCVariable => ({ name, class: cls, type: { @@ -48,25 +48,22 @@ describe('generateCBlocksHeader', () => { expect(result).toContain('void myblock_loop(MYBLOCK_VARS *vars);') }) - it('includes only input and output variables in the struct', () => { + it('includes user local variables and excludes runtime-only locals in the struct', () => { const variables: PLCVariable[] = [ makeScalarVar('inVar', 'input', 'INT'), - { - name: 'localVar', - class: 'local', - type: { definition: 'base-type', value: 'BOOL' }, - location: '', - documentation: '', - debug: false, - }, + makeScalarVar('localVar', 'local', 'BOOL'), + makeArrayVar('history', 'local', 'INT', '0..2'), + makeScalarVar('hasBeenInitialized', 'local', 'BOOL'), makeScalarVar('outVar', 'output', 'BOOL'), ] const result = generateCBlocksHeader([{ name: 'test', variables }]) expect(result).toContain('IEC_INT *INVAR;') + expect(result).toContain('IEC_BOOL *LOCALVAR;') + expect(result).toContain('IEC_INT *HISTORY;') expect(result).toContain('IEC_BOOL *OUTVAR;') - expect(result).not.toContain('LOCALVAR') + expect(result).not.toContain('HASBEENINITIALIZED') }) it('generates declarations for multiple pous', () => { diff --git a/src/backend/shared/utils/cpp/generateCBlocksCode.ts b/src/backend/shared/utils/cpp/generateCBlocksCode.ts index 85bab8b31..8db31daa8 100644 --- a/src/backend/shared/utils/cpp/generateCBlocksCode.ts +++ b/src/backend/shared/utils/cpp/generateCBlocksCode.ts @@ -1,6 +1,8 @@ import { generateStructMember, isArrayVariable } from '../../../../frontend/utils/PLC/array-codegen-helpers' import type { PLCVariable } from '../../../../middleware/shared/ports/types' +import { getExposedCppVariables } from './shared' + type CppPouData = { name: string code: string @@ -26,17 +28,12 @@ const processUserCode = (pou: CppPouData): string => { const setupFunctionName = `${pou.name.toLowerCase()}_setup` const loopFunctionName = `${pou.name.toLowerCase()}_loop` - const inputVariables = pou.variables.filter((v) => v.class === 'input') - const outputVariables = pou.variables.filter((v) => v.class === 'output') + const exposedVariables = getExposedCppVariables(pou.variables) let processedCode = `//definition of external blocks - ${pou.name.toUpperCase()}\n` processedCode += `typedef struct {\n` - inputVariables.forEach((variable) => { - processedCode += generateStructMember(variable) - }) - - outputVariables.forEach((variable) => { + exposedVariables.forEach((variable) => { processedCode += generateStructMember(variable) }) @@ -45,11 +42,7 @@ const processUserCode = (pou: CppPouData): string => { processedCode += `extern "C" void ${setupFunctionName}(${structName} *vars);\n` processedCode += `extern "C" void ${loopFunctionName}(${structName} *vars);\n\n` - inputVariables.forEach((variable) => { - processedCode += generateDefine(variable) - }) - - outputVariables.forEach((variable) => { + exposedVariables.forEach((variable) => { processedCode += generateDefine(variable) }) @@ -67,10 +60,7 @@ const processUserCode = (pou: CppPouData): string => { processedCode += modifiedUserCode processedCode += '\n' - inputVariables.forEach((variable) => { - processedCode += generateUndef(variable) - }) - outputVariables.forEach((variable) => { + exposedVariables.forEach((variable) => { processedCode += generateUndef(variable) }) processedCode += '\n' diff --git a/src/backend/shared/utils/cpp/generateCBlocksHeader.ts b/src/backend/shared/utils/cpp/generateCBlocksHeader.ts index 0822ffce7..75f1c59c6 100644 --- a/src/backend/shared/utils/cpp/generateCBlocksHeader.ts +++ b/src/backend/shared/utils/cpp/generateCBlocksHeader.ts @@ -1,6 +1,8 @@ import { generateStructMember } from '../../../../frontend/utils/PLC/array-codegen-helpers' import type { PLCVariable } from '../../../../middleware/shared/ports/types' +import { getExposedCppVariables } from './shared' + type CppPouData = { name: string variables: PLCVariable[] @@ -17,17 +19,12 @@ const generateCBlocksHeader = (cppPous: CppPouData[]): string => { const setupFunctionName = `${pou.name.toLowerCase()}_setup` const loopFunctionName = `${pou.name.toLowerCase()}_loop` - const inputVariables = pou.variables.filter((v) => v.class === 'input') - const outputVariables = pou.variables.filter((v) => v.class === 'output') + const exposedVariables = getExposedCppVariables(pou.variables) headerContent += `//definition of external blocks - ${pou.name.toUpperCase()}\n` headerContent += `typedef struct {\n` - inputVariables.forEach((variable) => { - headerContent += generateStructMember(variable) - }) - - outputVariables.forEach((variable) => { + exposedVariables.forEach((variable) => { headerContent += generateStructMember(variable) }) diff --git a/src/frontend/components/_molecules/variables-table/selectable-cell.tsx b/src/frontend/components/_molecules/variables-table/selectable-cell.tsx index 7af575a2f..5cfe83d5e 100644 --- a/src/frontend/components/_molecules/variables-table/selectable-cell.tsx +++ b/src/frontend/components/_molecules/variables-table/selectable-cell.tsx @@ -439,9 +439,12 @@ const SelectableClassCell = ({ const language = 'language' in editor.meta ? editor.meta.language : null const getVariableClasses = () => { - if (language === 'python' || language === 'cpp') { + if (language === 'python') { return ['input', 'output'] } + if (language === 'cpp') { + return ['input', 'output', 'local'] + } return ['input', 'output', 'inOut', 'external', 'local', 'temp'] } diff --git a/src/frontend/components/_organisms/variables-editor/index.tsx b/src/frontend/components/_organisms/variables-editor/index.tsx index 71835f551..e1543c338 100644 --- a/src/frontend/components/_organisms/variables-editor/index.tsx +++ b/src/frontend/components/_organisms/variables-editor/index.tsx @@ -334,7 +334,7 @@ const VariablesEditor = () => { const selectedRow = parseInt(editorVariables.selectedRow) const language = 'language' in editor.meta ? editor.meta.language : null - const defaultClass: PLCVariable['class'] = language === 'python' || language === 'cpp' ? 'input' : 'local' + const defaultClass: PLCVariable['class'] = language === 'python' ? 'input' : 'local' if (variables.length === 0) { createVariable({ diff --git a/src/frontend/utils/cpp/__tests__/generateSTCode.test.ts b/src/frontend/utils/cpp/__tests__/generateSTCode.test.ts index b8678dc70..f50362316 100644 --- a/src/frontend/utils/cpp/__tests__/generateSTCode.test.ts +++ b/src/frontend/utils/cpp/__tests__/generateSTCode.test.ts @@ -1,7 +1,7 @@ import type { PLCVariable } from '../../../../middleware/shared/ports/types' import { generateSTCode } from '../generateSTCode' -const makeScalarVar = (name: string, cls: 'input' | 'output', baseType: string): PLCVariable => ({ +const makeScalarVar = (name: string, cls: 'input' | 'output' | 'local', baseType: string): PLCVariable => ({ name, class: cls, type: { definition: 'base-type', value: baseType }, @@ -10,7 +10,7 @@ const makeScalarVar = (name: string, cls: 'input' | 'output', baseType: string): debug: false, }) -const makeArrayVar = (name: string, cls: 'input' | 'output', baseType: string, dimension: string): PLCVariable => ({ +const makeArrayVar = (name: string, cls: 'input' | 'output' | 'local', baseType: string, dimension: string): PLCVariable => ({ name, class: cls, type: { @@ -128,22 +128,18 @@ describe('generateSTCode', () => { expect(result).not.toContain('data__->B.value.table[__i].value = __flat_B[__i]') }) - it('filters out local variables', () => { - const localVar: PLCVariable = { - name: 'localVal', - class: 'local', - type: { definition: 'base-type', value: 'INT' }, - location: '', - documentation: '', - debug: false, - } - + it('includes user local variables and excludes runtime-only locals', () => { const result = generateSTCode({ pouName: 'test', - allVariables: [makeScalarVar('x', 'input', 'INT'), localVar], + allVariables: [ + makeScalarVar('x', 'input', 'INT'), + makeScalarVar('localVal', 'local', 'INT'), + makeScalarVar('hasBeenInitialized', 'local', 'BOOL'), + ], }) - expect(result).not.toContain('LOCALVAL') + expect(result).toContain('vars.LOCALVAL = &data__->LOCALVAL.value;') + expect(result).not.toContain('vars.HASBEENINITIALIZED') }) it('generates correct start index offset for non-zero-based arrays', () => { @@ -155,4 +151,22 @@ describe('generateSTCode', () => { expect(result).toContain('vars.ARR = __flat_ARR - 5;') expect(result).toContain('IEC_INT __flat_ARR[6];') }) + + it('preserves local arrays through the flat staging path', () => { + const result = generateSTCode({ + pouName: 'CounterBlock', + allVariables: [ + makeScalarVar('counter', 'local', 'INT'), + makeArrayVar('history', 'local', 'INT', '0..2'), + makeScalarVar('hasBeenInitialized', 'local', 'BOOL'), + makeScalarVar('out', 'output', 'INT'), + ], + }) + + expect(result).toContain('IEC_INT __flat_HISTORY[3];') + expect(result).toContain('__flat_HISTORY[__i] = data__->HISTORY.value.table[__i].value;') + expect(result).toContain('vars.HISTORY = __flat_HISTORY - 0;') + expect(result).toContain('data__->HISTORY.value.table[__i].value = __flat_HISTORY[__i];') + expect(result).not.toContain('vars.HASBEENINITIALIZED') + }) }) diff --git a/src/frontend/utils/cpp/generateSTCode.ts b/src/frontend/utils/cpp/generateSTCode.ts index 109513bc5..afcf2d63e 100644 --- a/src/frontend/utils/cpp/generateSTCode.ts +++ b/src/frontend/utils/cpp/generateSTCode.ts @@ -6,6 +6,8 @@ import { isArrayVariable, } from '../PLC/array-codegen-helpers' +import { getExposedCppVariables } from './shared' + type STCodeGenerationParams = { pouName: string allVariables: PLCVariable[] @@ -72,14 +74,16 @@ const generateOutputArrayCopyBack = (outputVariables: PLCVariable[]): string => const generateSTCode = (params: STCodeGenerationParams): string => { const { pouName, allVariables } = params - const inputVariables = allVariables.filter((v) => v.class === 'input') - const outputVariables = allVariables.filter((v) => v.class === 'output') + const exposedVariables = getExposedCppVariables(allVariables) + const inputVariables = exposedVariables.filter((v) => v.class === 'input') + const outputVariables = exposedVariables.filter((v) => v.class === 'output') + const localVariables = exposedVariables.filter((v) => v.class === 'local') const structName = `${pouName.toUpperCase()}_VARS` const setupFunctionName = `${pouName.toLowerCase()}_setup` const loopFunctionName = `${pouName.toLowerCase()}_loop` - const allArrayVariables = [...inputVariables, ...outputVariables].filter(isArrayVariable) + const allArrayVariables = exposedVariables.filter(isArrayVariable) const flatArrayDecl = generateFlatArrayDeclarations(allArrayVariables) const flatArrayCopiesIn = generateFlatArrayCopiesIn(allArrayVariables) @@ -91,8 +95,11 @@ const generateSTCode = (params: STCodeGenerationParams): string => { outputVariables.forEach((variable) => { variableAssignments += generateVariableAssignment(variable) }) + localVariables.forEach((variable) => { + variableAssignments += generateVariableAssignment(variable) + }) - const outputCopyBack = generateOutputArrayCopyBack(outputVariables) + const outputCopyBack = generateOutputArrayCopyBack([...outputVariables, ...localVariables]) let stCode = `{{ ${structName} vars; diff --git a/src/frontend/utils/cpp/shared.ts b/src/frontend/utils/cpp/shared.ts new file mode 100644 index 000000000..488fbf627 --- /dev/null +++ b/src/frontend/utils/cpp/shared.ts @@ -0,0 +1,19 @@ +import type { PLCVariable } from '../../../middleware/shared/ports/types' + +const CPP_RUNTIME_LOCAL_VARIABLES = new Set(['hasBeenInitialized']) + +const getExposedCppVariables = (variables: PLCVariable[]): PLCVariable[] => { + return variables.filter((variable) => { + if (variable.class === 'input' || variable.class === 'output') { + return true + } + + if (variable.class === 'local') { + return !CPP_RUNTIME_LOCAL_VARIABLES.has(variable.name) + } + + return false + }) +} + +export { getExposedCppVariables }