1
0

imports.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724
  1. const {
  2. getDataType,
  3. isStorageStructGroup,
  4. normalizeGroup
  5. } = require('../../domain/parameter-groups/model.js')
  6. const {
  7. parseStructCatalog,
  8. parseStructDefinition: parseStructDefinitionSource
  9. } = require('../../domain/parameter-groups/struct-parser.js')
  10. function getRegisterByteLengthFromConfig(register) {
  11. const dataType = getDataType(register.dataType).key
  12. if (dataType === 'ascii' || dataType === 'utf8') return Math.max(1, Number(register.textByteLength) || 1)
  13. if (dataType === 'float' || dataType === 'int32_t' || dataType === 'uint32_t') return 4
  14. if (dataType === 'int8_t' || dataType === 'uint8_t') return 1
  15. return 2
  16. }
  17. function getRegistersByteLength(registers = []) {
  18. const explicitByteEnds = registers.map((register) => {
  19. const byteStart = Number(register && register.byteStart)
  20. if (!Number.isFinite(byteStart)) return null
  21. if (register.isBitField) {
  22. const bitOffset = Math.min(Math.max(Math.floor(Number(register.bitOffset) || 0), 0), 7)
  23. const bitWidth = Math.max(1, Math.round(Number(register.bitWidth) || 1))
  24. return Math.max(0, Math.floor(byteStart)) + Math.max(1, Math.ceil((bitOffset + bitWidth) / 8))
  25. }
  26. return Math.max(0, Math.floor(byteStart)) + getRegisterByteLengthFromConfig(register)
  27. }).filter((value) => Number.isFinite(value))
  28. if (explicitByteEnds.length) return Math.max.apply(null, explicitByteEnds)
  29. return registers.reduce((total, register) => total + getRegisterByteLengthFromConfig(register), 0)
  30. }
  31. function normalizeSymbolText(value) {
  32. return String(value || '')
  33. .replace(/^(?:IDATA|XDATA|DATA|CODE)[\s:_-]+/i, '')
  34. .replace(/^_+/, '')
  35. .replace(/[^A-Za-z0-9]/g, '')
  36. .toLowerCase()
  37. }
  38. function getStorageStructTypeName(group = {}) {
  39. return String(group.sourceSymbolType || group.sourceSymbolName || group.name || '')
  40. }
  41. function isStructCodeInfoEntry(group = {}) {
  42. const entryKind = String(group.sourceEntryKind || '').trim().toLowerCase()
  43. return !entryKind || entryKind === 'struct'
  44. }
  45. function isVariableCodeInfoEntry(group = {}) {
  46. return String(group.sourceEntryKind || '').trim().toLowerCase() === 'variable'
  47. }
  48. function structDefinitionNameMatches(group = {}, structInfo = {}) {
  49. const expectedName = normalizeSymbolText(getStorageStructTypeName(group))
  50. const structName = normalizeSymbolText(structInfo.name)
  51. return !!expectedName && !!structName && expectedName === structName
  52. }
  53. function findStructCompletion(group, catalog) {
  54. if (isStorageStructGroup(group) && isStructCodeInfoEntry(group)) {
  55. const matchedStruct = catalog.structs.find((structInfo) => structDefinitionNameMatches(group, structInfo))
  56. return matchedStruct
  57. ? {
  58. name: group.sourceSymbolName || group.name,
  59. registers: matchedStruct.registers,
  60. structName: matchedStruct.name
  61. }
  62. : null
  63. }
  64. const symbolName = group.sourceSymbolName || group.name
  65. const direct = catalog.variablesByName[normalizeSymbolText(symbolName)]
  66. || catalog.variablesByName[symbolName]
  67. if (direct) return direct
  68. const normalizedSymbol = normalizeSymbolText(symbolName)
  69. const normalizedType = normalizeSymbolText(group.sourceSymbolType)
  70. const expectedBytes = Number(group.sourceByteLength || group.byteLength || 0)
  71. const matchedStruct = catalog.structs.find((structInfo) => {
  72. const normalizedStructName = normalizeSymbolText(structInfo.name)
  73. if (normalizedType && normalizedType === normalizedStructName) {
  74. return true
  75. }
  76. if (normalizedSymbol && (
  77. normalizedSymbol === normalizedStructName
  78. || normalizedSymbol.indexOf(normalizedStructName) >= 0
  79. || normalizedStructName.indexOf(normalizedSymbol) >= 0
  80. )) {
  81. return true
  82. }
  83. return expectedBytes > 0 && getRegistersByteLength(structInfo.registers) === expectedBytes
  84. })
  85. return matchedStruct
  86. ? {
  87. name: symbolName,
  88. registers: matchedStruct.registers,
  89. structName: matchedStruct.name
  90. }
  91. : null
  92. }
  93. function getEnumLookupNames(group = {}) {
  94. const registers = Array.isArray(group.registers) ? group.registers : []
  95. const names = [
  96. group.sourceSymbolType,
  97. group.sourceSymbolName,
  98. group.name
  99. ]
  100. registers.forEach((register) => {
  101. names.push(register.sourceSymbolType, register.sourceSymbolName, register.name)
  102. })
  103. return names.map(normalizeSymbolText).filter(Boolean)
  104. }
  105. function findEnumCompletion(group, catalog = {}) {
  106. const enums = Array.isArray(catalog.enums) ? catalog.enums : []
  107. if (!isVariableCodeInfoEntry(group) || !enums.length) return null
  108. const names = getEnumLookupNames(group)
  109. if (!names.length) return null
  110. const enumVariablesByName = catalog.enumVariablesByName || {}
  111. for (const name of names) {
  112. const variableEnum = enumVariablesByName[name]
  113. if (variableEnum) return variableEnum
  114. }
  115. return enums.find((enumInfo) => (
  116. [enumInfo.name, enumInfo.typedefName, enumInfo.tagName]
  117. .concat(enumInfo.typeNames || [])
  118. .map(normalizeSymbolText)
  119. .filter(Boolean)
  120. .some((name) => names.indexOf(name) >= 0)
  121. )) || null
  122. }
  123. function getIntegerDataTypeForByteLength(byteLength, fallback = 'uint16_t') {
  124. const length = Number(byteLength)
  125. if (length === 1) return 'uint8_t'
  126. if (length === 2) return 'uint16_t'
  127. if (length === 4) return 'uint32_t'
  128. return fallback
  129. }
  130. function cloneEnumOptions(enumInfo) {
  131. return (Array.isArray(enumInfo && enumInfo.options) ? enumInfo.options : []).map((option) => ({
  132. label: option.label || option.name,
  133. name: option.name || option.label,
  134. value: Number(option.value) || 0
  135. }))
  136. }
  137. function completeEnumVariableGroup(group, enumInfo) {
  138. if (!enumInfo) return group
  139. const enumOptions = cloneEnumOptions(enumInfo)
  140. if (!enumOptions.length) return group
  141. const registers = (Array.isArray(group.registers) ? group.registers : []).map((register) => ({
  142. ...register,
  143. dataType: getIntegerDataTypeForByteLength(
  144. register.sourceByteLength || register.byteLength || group.sourceByteLength || group.byteLength,
  145. register.dataType || enumInfo.dataType
  146. ),
  147. enumName: enumInfo.name,
  148. enumOptions,
  149. sourceSymbolType: enumInfo.name || register.sourceSymbolType
  150. }))
  151. return normalizeGroup({
  152. ...group,
  153. registers
  154. })
  155. }
  156. function createCompletedRegisters(group, completion) {
  157. const existingRemarksByByteStart = (Array.isArray(group.registers) ? group.registers : []).reduce((remarks, register) => {
  158. const byteStart = Number(register && register.byteStart)
  159. const remark = String(register && register.remark ? register.remark : '').trim()
  160. if (Number.isFinite(byteStart) && remark) remarks[Math.floor(byteStart)] = remark
  161. return remarks
  162. }, {})
  163. return completion.registers.map((register) => {
  164. const sourceAddress = (Number(group.sourceAddress) || Number(group.startAddress) || 0) + getRegisterByteStart(register)
  165. return {
  166. ...register,
  167. isStructField: true,
  168. remark: register.remark || existingRemarksByByteStart[Math.floor(Number(register.byteStart) || 0)] || '',
  169. sourceAddress,
  170. sourceAddressByteLength: group.sourceAddressByteLength,
  171. sourceAddressText: formatAddress(sourceAddress, group.sourceAddressWidth),
  172. sourceAddressWidth: group.sourceAddressWidth,
  173. sourceEntryKind: group.sourceEntryKind,
  174. sourceMemoryArea: group.sourceMemoryArea,
  175. sourceMemoryClass: group.sourceMemoryClass,
  176. sourceSymbolName: group.sourceSymbolName,
  177. sourceSymbolType: completion.structName || register.sourceSymbolType
  178. }
  179. })
  180. }
  181. function completeStructInstanceGroups(groups, sourceText, options = {}) {
  182. const catalog = parseStructCatalog(sourceText)
  183. let completedCount = 0
  184. let skippedCount = 0
  185. const nextGroups = groups.map((group) => {
  186. if (!group.sourceSymbolName || !group.sourceMemoryArea) return group
  187. if (isVariableCodeInfoEntry(group)) {
  188. const enumInfo = findEnumCompletion(group, catalog)
  189. if (!enumInfo) return group
  190. completedCount += 1
  191. return completeEnumVariableGroup(group, enumInfo)
  192. }
  193. if (!isStructCodeInfoEntry(group)) return group
  194. const completion = findStructCompletion(group, catalog)
  195. if (!completion || !completion.registers || !completion.registers.length) {
  196. skippedCount += 1
  197. return group
  198. }
  199. const expectedBytes = Number(group.sourceByteLength || group.byteLength || 0)
  200. const actualBytes = getRegistersByteLength(completion.registers)
  201. if (expectedBytes > 0 && actualBytes !== expectedBytes && options.strictLength !== false) {
  202. skippedCount += 1
  203. return group
  204. }
  205. completedCount += 1
  206. return normalizeGroup({
  207. ...group,
  208. layout: 'struct',
  209. quantity: completion.registers.length,
  210. registers: createCompletedRegisters(group, completion)
  211. })
  212. })
  213. return {
  214. completedCount,
  215. groups: nextGroups,
  216. skippedCount,
  217. structCount: catalog.structs.length,
  218. enumCount: Array.isArray(catalog.enums) ? catalog.enums.length : 0,
  219. variableCount: Object.keys(catalog.variablesByName).length
  220. }
  221. }
  222. function formatAddress(address, addressWidth) {
  223. const numberValue = Math.max(0, Math.floor(Number(address) || 0))
  224. const length = Number(addressWidth) === 32 || numberValue > 0xFFFF ? 8 : 4
  225. return `0x${numberValue.toString(16).toUpperCase().padStart(length, '0')}`
  226. }
  227. function normalizeDuplicateText(value) {
  228. return String(value === undefined || value === null ? '' : value)
  229. .trim()
  230. .toLowerCase()
  231. }
  232. function normalizeStructMatchText(value) {
  233. return String(value === undefined || value === null ? '' : value)
  234. .trim()
  235. .replace(/^(?:IDATA|XDATA|DATA|CODE)[\s:_-]+/i, '')
  236. .replace(/^struct\s+/i, '')
  237. .replace(/\s+#\d+$/i, '')
  238. .replace(/^_+/, '')
  239. .replace(/[^A-Za-z0-9]/g, '')
  240. .toLowerCase()
  241. }
  242. function normalizeAddressKey(value, textValue) {
  243. const numberValue = Number(value)
  244. if (Number.isFinite(numberValue)) return String(Math.floor(numberValue))
  245. return String(textValue === undefined || textValue === null ? '' : textValue)
  246. .trim()
  247. .toUpperCase()
  248. }
  249. function normalizeBitKey(source = {}) {
  250. const value = source.sourceBitOffset !== undefined && source.sourceBitOffset !== null && source.sourceBitOffset !== ''
  251. ? source.sourceBitOffset
  252. : source.bitOffset
  253. const numberValue = Number(value)
  254. return Number.isFinite(numberValue) ? String(Math.floor(numberValue)) : ''
  255. }
  256. function getRegisterDuplicateKey(register = {}, group = {}) {
  257. const area = normalizeDuplicateText(register.sourceMemoryArea || group.sourceMemoryArea || register.memoryArea || '')
  258. const symbolName = normalizeDuplicateText(register.sourceSymbolName || register.name || '')
  259. if (area && symbolName) return ['register', area, symbolName].join('|')
  260. const addressKey = normalizeAddressKey(
  261. register.sourceAddress !== undefined ? register.sourceAddress : register.address,
  262. register.sourceAddressText || register.addressText
  263. )
  264. const bitKey = normalizeBitKey(register)
  265. if (!area && !symbolName && !addressKey) return ''
  266. return ['register', area, symbolName, addressKey, bitKey].join('|')
  267. }
  268. function isSingleRegisterAggregateGroup(group = {}) {
  269. const groupSymbolName = normalizeDuplicateText(group.sourceSymbolName || group.name || '')
  270. const registers = Array.isArray(group.registers) ? group.registers : []
  271. return registers.some((register) => {
  272. const registerSymbolName = normalizeDuplicateText(register.sourceSymbolName || register.name || '')
  273. return registerSymbolName && groupSymbolName && registerSymbolName !== groupSymbolName
  274. })
  275. }
  276. function getGroupDuplicateKey(group = {}) {
  277. const area = normalizeDuplicateText(group.sourceMemoryArea || '')
  278. const symbolName = normalizeDuplicateText(group.sourceSymbolName || group.name || '')
  279. const addressKey = normalizeAddressKey(
  280. group.sourceAddress !== undefined ? group.sourceAddress : group.startAddress,
  281. group.sourceAddressText || group.startAddressText
  282. )
  283. if (area && symbolName && addressKey) return ['group', area, symbolName, addressKey].join('|')
  284. if (area && symbolName) return ['group', area, symbolName].join('|')
  285. if (!area && !symbolName && !addressKey) return ''
  286. return ['group', area, symbolName, addressKey].join('|')
  287. }
  288. function getAggregateGroupDuplicateKey(source = {}) {
  289. const area = normalizeDuplicateText(source.sourceMemoryArea || source.memoryArea || '')
  290. const registerType = normalizeDuplicateText(source.registerType || '')
  291. const segment = normalizeDuplicateText(source.sourceSegment || '')
  292. return ['aggregate', area, registerType, segment].join('|')
  293. }
  294. function collectImportedVariableIndexes(groups = []) {
  295. return groups.reduce((indexes, group, groupIndex) => {
  296. if (!isSingleRegisterAggregateGroup(group)) {
  297. const groupKey = getGroupDuplicateKey(group)
  298. if (groupKey) indexes.groupIndexes[groupKey] = groupIndex
  299. } else {
  300. const aggregateKey = getAggregateGroupDuplicateKey(group)
  301. if (aggregateKey) indexes.aggregateGroupIndexes[aggregateKey] = groupIndex
  302. }
  303. ;(Array.isArray(group.registers) ? group.registers : []).forEach((register) => {
  304. const registerKey = getRegisterDuplicateKey(register, group)
  305. if (registerKey) {
  306. indexes.registerIndexes[registerKey] = {
  307. groupIndex,
  308. registerIndex: group.registers.indexOf(register)
  309. }
  310. }
  311. })
  312. return indexes
  313. }, {
  314. aggregateGroupIndexes: {},
  315. groupIndexes: {},
  316. registerIndexes: {}
  317. })
  318. }
  319. function getStructMatchNames(group = {}) {
  320. const registers = Array.isArray(group.registers) ? group.registers : []
  321. const names = [
  322. group.sourceSymbolName,
  323. group.sourceSymbolType,
  324. group.name,
  325. group.displayName
  326. ]
  327. registers.forEach((register) => {
  328. names.push(register.sourceSymbolType, register.sourceSymbolName)
  329. })
  330. return names
  331. .map(normalizeStructMatchText)
  332. .filter(Boolean)
  333. .filter((name, index, list) => list.indexOf(name) === index)
  334. }
  335. function structsMatchByName(existingGroup = {}, incomingGroup = {}) {
  336. const existingNames = getStructMatchNames(existingGroup)
  337. const incomingNames = getStructMatchNames(incomingGroup)
  338. return existingNames.some((name) => incomingNames.indexOf(name) >= 0)
  339. }
  340. function getGroupByteLengthCandidates(group = {}) {
  341. const registers = Array.isArray(group.registers) ? group.registers : []
  342. const candidates = [
  343. group.sourceByteLength,
  344. group.byteLength,
  345. group.structByteLength,
  346. getRegistersByteLength(registers)
  347. ]
  348. registers.forEach((register) => {
  349. candidates.push(register.structByteLength)
  350. })
  351. return candidates
  352. .map((value) => Number(value))
  353. .filter((value) => Number.isFinite(value) && value > 0)
  354. .map((value) => Math.floor(value))
  355. .filter((value, index, list) => list.indexOf(value) === index)
  356. }
  357. function structsMatchByByteLength(existingGroup = {}, incomingGroup = {}) {
  358. const existingLengths = getGroupByteLengthCandidates(existingGroup)
  359. const incomingLengths = getGroupByteLengthCandidates(incomingGroup)
  360. return existingLengths.some((length) => incomingLengths.indexOf(length) >= 0)
  361. }
  362. function structsMatchByLocation(existingGroup = {}, incomingGroup = {}) {
  363. const existingArea = normalizeDuplicateText(existingGroup.sourceMemoryArea || '')
  364. const incomingArea = normalizeDuplicateText(incomingGroup.sourceMemoryArea || '')
  365. const existingAddress = normalizeAddressKey(
  366. existingGroup.sourceAddress !== undefined ? existingGroup.sourceAddress : existingGroup.startAddress,
  367. existingGroup.sourceAddressText || existingGroup.startAddressText
  368. )
  369. const incomingAddress = normalizeAddressKey(
  370. incomingGroup.sourceAddress !== undefined ? incomingGroup.sourceAddress : incomingGroup.startAddress,
  371. incomingGroup.sourceAddressText || incomingGroup.startAddressText
  372. )
  373. if (existingArea && incomingArea && existingArea !== incomingArea) return false
  374. if (existingAddress && incomingAddress && existingAddress !== incomingAddress) return false
  375. return true
  376. }
  377. function isIncomingPlaceholderStructGroup(group = {}) {
  378. const registers = Array.isArray(group.registers) ? group.registers : []
  379. return group.layout === 'struct'
  380. && registers.length > 0
  381. && registers.every((register) => !!register.isPlaceholderByteField)
  382. }
  383. function hasImportedStructRegisters(group = {}) {
  384. const registers = Array.isArray(group.registers) ? group.registers : []
  385. return group.layout === 'struct'
  386. && registers.length > 0
  387. && registers.some((register) => !register.isPlaceholderByteField)
  388. }
  389. function canPreserveExistingStructLayout(existingGroup, incomingGroup, options = {}) {
  390. return options.preserveExistingStructLayout
  391. && hasImportedStructRegisters(existingGroup)
  392. && isIncomingPlaceholderStructGroup(incomingGroup)
  393. && structsMatchByName(existingGroup, incomingGroup)
  394. && structsMatchByByteLength(existingGroup, incomingGroup)
  395. && structsMatchByLocation(existingGroup, incomingGroup)
  396. }
  397. function getRegisterByteStart(register = {}) {
  398. const byteStart = Number(register.byteStart)
  399. return Number.isFinite(byteStart) ? Math.max(0, Math.floor(byteStart)) : 0
  400. }
  401. function mergePreservedStructRegister(register = {}, incomingGroup = {}) {
  402. const byteStart = getRegisterByteStart(register)
  403. const sourceAddress = (Number(incomingGroup.sourceAddress) || Number(incomingGroup.startAddress) || 0) + byteStart
  404. const sourceSymbolName = incomingGroup.sourceSymbolName || register.sourceSymbolName
  405. const sourceSymbolType = incomingGroup.sourceSymbolType || register.sourceSymbolType || sourceSymbolName
  406. return {
  407. ...register,
  408. rawBytes: [],
  409. rawValue: null,
  410. rawWords: [],
  411. sourceAddress,
  412. sourceAddressByteLength: incomingGroup.sourceAddressByteLength || register.sourceAddressByteLength,
  413. sourceAddressText: formatAddress(sourceAddress, incomingGroup.sourceAddressWidth || register.sourceAddressWidth),
  414. sourceAddressWidth: incomingGroup.sourceAddressWidth || register.sourceAddressWidth,
  415. sourceEntryKind: incomingGroup.sourceEntryKind,
  416. sourceMemoryArea: incomingGroup.sourceMemoryArea,
  417. sourceMemoryClass: incomingGroup.sourceMemoryClass,
  418. sourceSymbolName,
  419. sourceSymbolType
  420. }
  421. }
  422. function resolveMergedPollEnabled(existingGroup = {}, incomingGroup = {}, options = {}) {
  423. if (options.preserveExistingPollEnabled && existingGroup.pollEnabled === false) return false
  424. return incomingGroup.pollEnabled === false ? false : true
  425. }
  426. function mergePreservedStructGroupState(existingGroup, incomingGroup, options = {}) {
  427. const preservedRegisters = (Array.isArray(existingGroup.registers) ? existingGroup.registers : [])
  428. .map((register) => mergePreservedStructRegister(register, incomingGroup))
  429. return {
  430. ...incomingGroup,
  431. deleteVisible: false,
  432. expanded: existingGroup.expanded === true,
  433. id: existingGroup.id,
  434. pollEnabled: resolveMergedPollEnabled(existingGroup, incomingGroup, options),
  435. quantity: preservedRegisters.length,
  436. registers: preservedRegisters
  437. }
  438. }
  439. function findPreservableStructGroupIndex(groups = [], incomingGroup = {}, preferredIndex, options = {}) {
  440. if (preferredIndex !== undefined && canPreserveExistingStructLayout(groups[preferredIndex], incomingGroup, options)) {
  441. return preferredIndex
  442. }
  443. if (!options.preserveExistingStructLayout || !isIncomingPlaceholderStructGroup(incomingGroup)) return undefined
  444. return groups.findIndex((group, index) => (
  445. index !== preferredIndex && canPreserveExistingStructLayout(group, incomingGroup, options)
  446. ))
  447. }
  448. function mergeImportedRegisterState(existingRegister, incomingRegister, options = {}) {
  449. if (!existingRegister) return incomingRegister
  450. const incomingRemark = incomingRegister.remark
  451. const shouldPreserveRemark = options.preserveExistingRemarks
  452. && !String(incomingRemark === undefined || incomingRemark === null ? '' : incomingRemark).trim()
  453. return {
  454. ...incomingRegister,
  455. id: existingRegister.id,
  456. inputValue: incomingRegister.inputValue !== undefined && incomingRegister.inputValue !== null
  457. ? incomingRegister.inputValue
  458. : existingRegister.inputValue,
  459. remark: shouldPreserveRemark
  460. ? existingRegister.remark
  461. : (incomingRemark !== undefined && incomingRemark !== null ? incomingRemark : existingRegister.remark),
  462. rawBytes: [],
  463. rawValue: null,
  464. rawWords: []
  465. }
  466. }
  467. function mergeImportedGroupState(existingGroup, incomingGroup, options = {}) {
  468. if (!existingGroup) return incomingGroup
  469. if (canPreserveExistingStructLayout(existingGroup, incomingGroup, options)) {
  470. return mergePreservedStructGroupState(existingGroup, incomingGroup, options)
  471. }
  472. const existingRegisters = Array.isArray(existingGroup.registers) ? existingGroup.registers : []
  473. const incomingRegisters = Array.isArray(incomingGroup.registers) ? incomingGroup.registers : []
  474. return {
  475. ...incomingGroup,
  476. deleteVisible: false,
  477. expanded: existingGroup.expanded === true,
  478. id: existingGroup.id,
  479. pollEnabled: resolveMergedPollEnabled(existingGroup, incomingGroup, options),
  480. registers: incomingRegisters.map((incomingRegister, index) => mergeImportedRegisterState(
  481. existingRegisters[index],
  482. incomingRegister,
  483. options
  484. ))
  485. }
  486. }
  487. function mergeAggregateImportedGroup(nextGroups, incomingGroup, indexes, options = {}) {
  488. const aggregateKey = getAggregateGroupDuplicateKey(incomingGroup)
  489. const aggregateGroupIndex = indexes.aggregateGroupIndexes[aggregateKey]
  490. let targetGroup = aggregateGroupIndex === undefined ? null : nextGroups[aggregateGroupIndex]
  491. let targetGroupIndex = aggregateGroupIndex
  492. let targetRegisters = targetGroup && Array.isArray(targetGroup.registers)
  493. ? targetGroup.registers.slice()
  494. : []
  495. let addedRegisterCount = 0
  496. let updatedRegisterCount = 0
  497. ;(Array.isArray(incomingGroup.registers) ? incomingGroup.registers : []).forEach((incomingRegister) => {
  498. const registerKey = getRegisterDuplicateKey(incomingRegister, incomingGroup)
  499. const existingRef = registerKey ? indexes.registerIndexes[registerKey] : null
  500. if (existingRef) {
  501. const existingGroup = nextGroups[existingRef.groupIndex]
  502. const existingRegister = existingGroup && existingGroup.registers
  503. ? existingGroup.registers[existingRef.registerIndex]
  504. : null
  505. const mergedRegister = mergeImportedRegisterState(existingRegister, incomingRegister, options)
  506. if (targetGroupIndex !== undefined && existingRef.groupIndex === targetGroupIndex) {
  507. targetRegisters[existingRef.registerIndex] = mergedRegister
  508. } else if (existingGroup) {
  509. const registers = existingGroup.registers.slice()
  510. registers[existingRef.registerIndex] = mergedRegister
  511. nextGroups[existingRef.groupIndex] = normalizeGroup({
  512. ...existingGroup,
  513. registers
  514. })
  515. }
  516. updatedRegisterCount += 1
  517. return
  518. }
  519. targetRegisters.push(incomingRegister)
  520. addedRegisterCount += 1
  521. })
  522. if (targetGroupIndex !== undefined && targetGroup) {
  523. nextGroups[targetGroupIndex] = normalizeGroup(mergeImportedGroupState(targetGroup, {
  524. ...incomingGroup,
  525. quantity: targetRegisters.length,
  526. registers: targetRegisters
  527. }, options))
  528. } else if (targetRegisters.length) {
  529. targetGroup = normalizeGroup({
  530. ...incomingGroup,
  531. quantity: targetRegisters.length,
  532. registers: targetRegisters
  533. })
  534. nextGroups.push(targetGroup)
  535. targetGroupIndex = nextGroups.length - 1
  536. }
  537. return {
  538. addedGroupCount: targetGroupIndex === aggregateGroupIndex ? 0 : (targetRegisters.length ? 1 : 0),
  539. addedRegisterCount,
  540. updatedGroupCount: targetGroupIndex === aggregateGroupIndex && (addedRegisterCount || updatedRegisterCount) ? 1 : 0,
  541. updatedRegisterCount
  542. }
  543. }
  544. function mergeImportedGroups(existingGroups = [], incomingGroups = [], options = {}) {
  545. const nextGroups = existingGroups.slice()
  546. let indexes = collectImportedVariableIndexes(nextGroups)
  547. const result = {
  548. addedGroupCount: 0,
  549. addedRegisterCount: 0,
  550. groups: nextGroups,
  551. updatedGroupCount: 0,
  552. updatedRegisterCount: 0
  553. }
  554. incomingGroups.forEach((incomingGroup) => {
  555. if (isSingleRegisterAggregateGroup(incomingGroup)) {
  556. const aggregateResult = mergeAggregateImportedGroup(nextGroups, incomingGroup, indexes, options)
  557. result.addedGroupCount += aggregateResult.addedGroupCount
  558. result.addedRegisterCount += aggregateResult.addedRegisterCount
  559. result.updatedGroupCount += aggregateResult.updatedGroupCount
  560. result.updatedRegisterCount += aggregateResult.updatedRegisterCount
  561. indexes = collectImportedVariableIndexes(nextGroups)
  562. return
  563. }
  564. const groupKey = getGroupDuplicateKey(incomingGroup)
  565. const existingGroupIndex = groupKey ? indexes.groupIndexes[groupKey] : undefined
  566. const preservableStructGroupIndex = findPreservableStructGroupIndex(
  567. nextGroups,
  568. incomingGroup,
  569. existingGroupIndex,
  570. options
  571. )
  572. const targetGroupIndex = preservableStructGroupIndex >= 0
  573. ? preservableStructGroupIndex
  574. : existingGroupIndex
  575. if (targetGroupIndex !== undefined) {
  576. const existingGroup = nextGroups[targetGroupIndex]
  577. nextGroups[targetGroupIndex] = normalizeGroup(mergeImportedGroupState(existingGroup, incomingGroup, options))
  578. result.updatedGroupCount += 1
  579. } else {
  580. nextGroups.push(incomingGroup)
  581. result.addedGroupCount += 1
  582. }
  583. indexes = collectImportedVariableIndexes(nextGroups)
  584. })
  585. result.changedCount = result.addedGroupCount
  586. + result.updatedGroupCount
  587. + result.addedRegisterCount
  588. + result.updatedRegisterCount
  589. return result
  590. }
  591. function parseStructDefinition(sourceText) {
  592. return parseStructDefinitionSource(sourceText)
  593. }
  594. module.exports = {
  595. completeStructInstanceGroups,
  596. getRegistersByteLength,
  597. mergeImportedGroups,
  598. parseStructDefinition
  599. }