From e5d9394eeec2e946c9ed8b2b7d9d1811c9e08ef4 Mon Sep 17 00:00:00 2001 From: Manuele Conti Date: Tue, 31 Mar 2026 14:40:27 +0200 Subject: [PATCH 1/2] Support local vars in C/C++ function blocks --- .../variables-table/selectable-cell.tsx | 5 +- .../_organisms/variables-editor/index.tsx | 2 +- src/utils/cpp/generateCBlocksCode.ts | 22 ++--- src/utils/cpp/generateCBlocksHeader.ts | 11 +-- src/utils/cpp/generateCppBridge.test.ts | 88 +++++++++++++++++++ src/utils/cpp/generateSTCode.ts | 13 ++- src/utils/cpp/shared.ts | 19 ++++ 7 files changed, 132 insertions(+), 28 deletions(-) create mode 100644 src/utils/cpp/generateCppBridge.test.ts create mode 100644 src/utils/cpp/shared.ts diff --git a/src/renderer/components/_molecules/variables-table/selectable-cell.tsx b/src/renderer/components/_molecules/variables-table/selectable-cell.tsx index 3a8340bcf..8898960d0 100644 --- a/src/renderer/components/_molecules/variables-table/selectable-cell.tsx +++ b/src/renderer/components/_molecules/variables-table/selectable-cell.tsx @@ -441,9 +441,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/renderer/components/_organisms/variables-editor/index.tsx b/src/renderer/components/_organisms/variables-editor/index.tsx index 4fa046aac..0c37686c0 100644 --- a/src/renderer/components/_organisms/variables-editor/index.tsx +++ b/src/renderer/components/_organisms/variables-editor/index.tsx @@ -309,7 +309,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/utils/cpp/generateCBlocksCode.ts b/src/utils/cpp/generateCBlocksCode.ts index e51ab98dc..e3bf6a6b3 100644 --- a/src/utils/cpp/generateCBlocksCode.ts +++ b/src/utils/cpp/generateCBlocksCode.ts @@ -1,6 +1,8 @@ import { PLCVariable } from '@root/types/PLC/open-plc' import { generateStructMember, isArrayVariable } from '@root/utils/PLC/array-codegen-helpers' +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/utils/cpp/generateCBlocksHeader.ts b/src/utils/cpp/generateCBlocksHeader.ts index 1aa587be8..fb06b01a9 100644 --- a/src/utils/cpp/generateCBlocksHeader.ts +++ b/src/utils/cpp/generateCBlocksHeader.ts @@ -1,6 +1,8 @@ import { PLCVariable } from '@root/types/PLC/open-plc' import { generateStructMember } from '@root/utils/PLC/array-codegen-helpers' +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/utils/cpp/generateCppBridge.test.ts b/src/utils/cpp/generateCppBridge.test.ts new file mode 100644 index 000000000..46a5cb8c2 --- /dev/null +++ b/src/utils/cpp/generateCppBridge.test.ts @@ -0,0 +1,88 @@ +import type { PLCVariable } from '@root/types/PLC/open-plc' + +import { generateCBlocksCode } from './generateCBlocksCode' +import { generateCBlocksHeader } from './generateCBlocksHeader' +import { generateSTCode } from './generateSTCode' + +const inputVar: PLCVariable = { + name: 'IN', + class: 'input', + type: { + definition: 'base-type', + value: 'int', + }, + location: '', + documentation: '', +} + +const outputVar: PLCVariable = { + name: 'OUT', + class: 'output', + type: { + definition: 'base-type', + value: 'int', + }, + location: '', + documentation: '', +} + +const localVar: PLCVariable = { + name: 'counter', + class: 'local', + type: { + definition: 'base-type', + value: 'int', + }, + location: '', + documentation: '', + initialValue: '0', +} + +const runtimeVar: PLCVariable = { + name: 'hasBeenInitialized', + class: 'local', + type: { + definition: 'base-type', + value: 'bool', + }, + location: '', + documentation: '', + initialValue: '0', +} + +describe('C/C++ FB bridge generation', () => { + it('includes user local variables in the generated ST wrapper', () => { + const stCode = generateSTCode({ + pouName: 'CounterBlock', + allVariables: [inputVar, outputVar, localVar, runtimeVar], + }) + + expect(stCode).toContain('vars.COUNTER = &data__->COUNTER.value;') + expect(stCode).not.toContain('vars.HASBEENINITIALIZED') + }) + + it('includes user local variables in the generated header and code bridge', () => { + const variables = [inputVar, outputVar, localVar, runtimeVar] + + const header = generateCBlocksHeader([ + { + name: 'CounterBlock', + variables, + }, + ]) + + const code = generateCBlocksCode([ + { + name: 'CounterBlock', + variables, + code: `void setup() { counter = 1; }\nvoid loop() { counter += IN; OUT = counter; }`, + }, + ]) + + expect(header).toContain('IEC_INT *COUNTER;') + expect(header).not.toContain('HASBEENINITIALIZED') + + expect(code).toContain('#define counter (*(vars->COUNTER))') + expect(code).not.toContain('#define hasBeenInitialized') + }) +}) diff --git a/src/utils/cpp/generateSTCode.ts b/src/utils/cpp/generateSTCode.ts index 5a2a87a41..38869e1de 100644 --- a/src/utils/cpp/generateSTCode.ts +++ b/src/utils/cpp/generateSTCode.ts @@ -6,6 +6,8 @@ import { isArrayVariable, } from '@root/utils/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,6 +95,9 @@ const generateSTCode = (params: STCodeGenerationParams): string => { outputVariables.forEach((variable) => { variableAssignments += generateVariableAssignment(variable) }) + localVariables.forEach((variable) => { + variableAssignments += generateVariableAssignment(variable) + }) const outputCopyBack = generateOutputArrayCopyBack(outputVariables) diff --git a/src/utils/cpp/shared.ts b/src/utils/cpp/shared.ts new file mode 100644 index 000000000..4dd0e74db --- /dev/null +++ b/src/utils/cpp/shared.ts @@ -0,0 +1,19 @@ +import { PLCVariable } from '@root/types/PLC/open-plc' + +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 } From fbd989126753b63d100f877e1a41f77e86c9753e Mon Sep 17 00:00:00 2001 From: Manuele Conti Date: Tue, 31 Mar 2026 14:59:09 +0200 Subject: [PATCH 2/2] Preserve local arrays in C/C++ FB bridge --- src/utils/cpp/generateCppBridge.test.ts | 54 +++++++++++++++++++++++++ src/utils/cpp/generateSTCode.ts | 2 +- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/utils/cpp/generateCppBridge.test.ts b/src/utils/cpp/generateCppBridge.test.ts index 46a5cb8c2..932dad170 100644 --- a/src/utils/cpp/generateCppBridge.test.ts +++ b/src/utils/cpp/generateCppBridge.test.ts @@ -38,6 +38,24 @@ const localVar: PLCVariable = { initialValue: '0', } +const localArrayVar: PLCVariable = { + name: 'history', + class: 'local', + type: { + definition: 'array', + value: 'ARRAY [0..2] OF INT', + data: { + baseType: { + definition: 'base-type', + value: 'int', + }, + dimensions: [{ dimension: '0..2' }], + }, + }, + location: '', + documentation: '', +} + const runtimeVar: PLCVariable = { name: 'hasBeenInitialized', class: 'local', @@ -85,4 +103,40 @@ describe('C/C++ FB bridge generation', () => { expect(code).toContain('#define counter (*(vars->COUNTER))') expect(code).not.toContain('#define hasBeenInitialized') }) + + it('preserves local arrays through the generated bridge staging path', () => { + const variables = [inputVar, outputVar, localVar, localArrayVar, runtimeVar] + + const stCode = generateSTCode({ + pouName: 'CounterBlock', + allVariables: variables, + }) + + const header = generateCBlocksHeader([ + { + name: 'CounterBlock', + variables, + }, + ]) + + const code = generateCBlocksCode([ + { + name: 'CounterBlock', + variables, + code: `void setup() { history[0] = counter; }\nvoid loop() { history[1] = IN; history[2] = history[0] + history[1]; OUT = history[2]; }`, + }, + ]) + + expect(stCode).toContain('IEC_INT __flat_HISTORY[3];') + expect(stCode).toContain('__flat_HISTORY[__i] = data__->HISTORY.value.table[__i].value;') + expect(stCode).toContain('vars.HISTORY = __flat_HISTORY - 0;') + expect(stCode).toContain('data__->HISTORY.value.table[__i].value = __flat_HISTORY[__i];') + expect(stCode).not.toContain('vars.HASBEENINITIALIZED') + + expect(header).toContain('IEC_INT *HISTORY;') + expect(header).not.toContain('HASBEENINITIALIZED') + + expect(code).toContain('#define history (vars->HISTORY)') + expect(code).not.toContain('#define hasBeenInitialized') + }) }) diff --git a/src/utils/cpp/generateSTCode.ts b/src/utils/cpp/generateSTCode.ts index 38869e1de..c588b1d4e 100644 --- a/src/utils/cpp/generateSTCode.ts +++ b/src/utils/cpp/generateSTCode.ts @@ -99,7 +99,7 @@ const generateSTCode = (params: STCodeGenerationParams): string => { variableAssignments += generateVariableAssignment(variable) }) - const outputCopyBack = generateOutputArrayCopyBack(outputVariables) + const outputCopyBack = generateOutputArrayCopyBack([...outputVariables, ...localVariables]) let stCode = `{{ ${structName} vars;