1
0

imports.js 26 KB

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