Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 12 additions & 13 deletions src/backend/shared/utils/cpp/__tests__/generateCBlocksCode.test.ts
Original file line number Diff line number Diff line change
@@ -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 },
Expand All @@ -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: {
Expand Down Expand Up @@ -120,28 +120,27 @@ 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() { }'

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')
})
})
Original file line number Diff line number Diff line change
@@ -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 },
Expand All @@ -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: {
Expand Down Expand Up @@ -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', () => {
Expand Down
22 changes: 6 additions & 16 deletions src/backend/shared/utils/cpp/generateCBlocksCode.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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)
})

Expand All @@ -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)
})

Expand All @@ -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'
Expand Down
11 changes: 4 additions & 7 deletions src/backend/shared/utils/cpp/generateCBlocksHeader.ts
Original file line number Diff line number Diff line change
@@ -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[]
Expand All @@ -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)
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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']
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
42 changes: 28 additions & 14 deletions src/frontend/utils/cpp/__tests__/generateSTCode.test.ts
Original file line number Diff line number Diff line change
@@ -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 },
Expand All @@ -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: {
Expand Down Expand Up @@ -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', () => {
Expand All @@ -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')
})
})
15 changes: 11 additions & 4 deletions src/frontend/utils/cpp/generateSTCode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {
isArrayVariable,
} from '../PLC/array-codegen-helpers'

import { getExposedCppVariables } from './shared'

type STCodeGenerationParams = {
pouName: string
allVariables: PLCVariable[]
Expand Down Expand Up @@ -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)
Expand All @@ -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;
Expand Down
19 changes: 19 additions & 0 deletions src/frontend/utils/cpp/shared.ts
Original file line number Diff line number Diff line change
@@ -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 }