1
0

import-merge.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. const {
  2. normalizeGroup
  3. } = require('../../domain/parameter-groups/model.js')
  4. const {
  5. getRegistersByteLength
  6. } = require('./struct-completion.js')
  7. function formatAddress(address) {
  8. return `0x${Number(address || 0).toString(16).toUpperCase().padStart(4, '0')}`
  9. }
  10. function normalizeDuplicateText(value) {
  11. return String(value === undefined || value === null ? '' : value)
  12. .trim()
  13. .toLowerCase()
  14. }
  15. function normalizeStructMatchText(value) {
  16. return String(value === undefined || value === null ? '' : value)
  17. .trim()
  18. .replace(/^(?:IDATA|XDATA|DATA|CODE)[\s:_-]+/i, '')
  19. .replace(/^struct\s+/i, '')
  20. .replace(/\s+#\d+$/i, '')
  21. .replace(/^_+/, '')
  22. .replace(/[^A-Za-z0-9]/g, '')
  23. .toLowerCase()
  24. }
  25. function normalizeAddressKey(value, textValue) {
  26. const numberValue = Number(value)
  27. if (Number.isFinite(numberValue)) return String(Math.floor(numberValue))
  28. return String(textValue === undefined || textValue === null ? '' : textValue)
  29. .trim()
  30. .toUpperCase()
  31. }
  32. function normalizeBitKey(source = {}) {
  33. const value = source.sourceBitOffset !== undefined && source.sourceBitOffset !== null && source.sourceBitOffset !== ''
  34. ? source.sourceBitOffset
  35. : source.bitOffset
  36. const numberValue = Number(value)
  37. return Number.isFinite(numberValue) ? String(Math.floor(numberValue)) : ''
  38. }
  39. function getRegisterDuplicateKey(register = {}, group = {}) {
  40. const area = normalizeDuplicateText(register.sourceMemoryArea || group.sourceMemoryArea || register.memoryArea || '')
  41. const symbolName = normalizeDuplicateText(register.sourceSymbolName || register.name || '')
  42. if (area && symbolName) return ['register', area, symbolName].join('|')
  43. const addressKey = normalizeAddressKey(
  44. register.sourceAddress !== undefined ? register.sourceAddress : register.address,
  45. register.sourceAddressText || register.addressText
  46. )
  47. const bitKey = normalizeBitKey(register)
  48. if (!area && !symbolName && !addressKey) return ''
  49. return ['register', area, symbolName, addressKey, bitKey].join('|')
  50. }
  51. function isSingleRegisterAggregateGroup(group = {}) {
  52. const groupSymbolName = normalizeDuplicateText(group.sourceSymbolName || group.name || '')
  53. const registers = Array.isArray(group.registers) ? group.registers : []
  54. return registers.some((register) => {
  55. const registerSymbolName = normalizeDuplicateText(register.sourceSymbolName || register.name || '')
  56. return registerSymbolName && groupSymbolName && registerSymbolName !== groupSymbolName
  57. })
  58. }
  59. function getGroupDuplicateKey(group = {}) {
  60. const area = normalizeDuplicateText(group.sourceMemoryArea || '')
  61. const symbolName = normalizeDuplicateText(group.sourceSymbolName || group.name || '')
  62. if (area && symbolName) return ['group', area, symbolName].join('|')
  63. const addressKey = normalizeAddressKey(
  64. group.sourceAddress !== undefined ? group.sourceAddress : group.startAddress,
  65. group.sourceAddressText || group.startAddressText
  66. )
  67. if (!area && !symbolName && !addressKey) return ''
  68. return ['group', area, symbolName, addressKey].join('|')
  69. }
  70. function getAggregateGroupDuplicateKey(source = {}) {
  71. const area = normalizeDuplicateText(source.sourceMemoryArea || source.memoryArea || '')
  72. const registerType = normalizeDuplicateText(source.registerType || '')
  73. const segment = normalizeDuplicateText(source.sourceSegment || '')
  74. return ['aggregate', area, registerType, segment].join('|')
  75. }
  76. function collectImportedVariableIndexes(groups = []) {
  77. return groups.reduce((indexes, group, groupIndex) => {
  78. if (!isSingleRegisterAggregateGroup(group)) {
  79. const groupKey = getGroupDuplicateKey(group)
  80. if (groupKey) indexes.groupIndexes[groupKey] = groupIndex
  81. } else {
  82. const aggregateKey = getAggregateGroupDuplicateKey(group)
  83. if (aggregateKey) indexes.aggregateGroupIndexes[aggregateKey] = groupIndex
  84. }
  85. ;(Array.isArray(group.registers) ? group.registers : []).forEach((register) => {
  86. const registerKey = getRegisterDuplicateKey(register, group)
  87. if (registerKey) {
  88. indexes.registerIndexes[registerKey] = {
  89. groupIndex,
  90. registerIndex: group.registers.indexOf(register)
  91. }
  92. }
  93. })
  94. return indexes
  95. }, {
  96. aggregateGroupIndexes: {},
  97. groupIndexes: {},
  98. registerIndexes: {}
  99. })
  100. }
  101. function getStructMatchNames(group = {}) {
  102. const registers = Array.isArray(group.registers) ? group.registers : []
  103. const names = [
  104. group.sourceSymbolName,
  105. group.sourceSymbolType,
  106. group.name,
  107. group.displayName
  108. ]
  109. registers.forEach((register) => {
  110. names.push(register.sourceSymbolType, register.sourceSymbolName)
  111. })
  112. return names
  113. .map(normalizeStructMatchText)
  114. .filter(Boolean)
  115. .filter((name, index, list) => list.indexOf(name) === index)
  116. }
  117. function structsMatchByName(existingGroup = {}, incomingGroup = {}) {
  118. const existingNames = getStructMatchNames(existingGroup)
  119. const incomingNames = getStructMatchNames(incomingGroup)
  120. return existingNames.some((name) => incomingNames.indexOf(name) >= 0)
  121. }
  122. function getGroupByteLengthCandidates(group = {}) {
  123. const registers = Array.isArray(group.registers) ? group.registers : []
  124. const candidates = [
  125. group.sourceByteLength,
  126. group.byteLength,
  127. group.structByteLength,
  128. getRegistersByteLength(registers)
  129. ]
  130. registers.forEach((register) => {
  131. candidates.push(register.structByteLength)
  132. })
  133. return candidates
  134. .map((value) => Number(value))
  135. .filter((value) => Number.isFinite(value) && value > 0)
  136. .map((value) => Math.floor(value))
  137. .filter((value, index, list) => list.indexOf(value) === index)
  138. }
  139. function structsMatchByByteLength(existingGroup = {}, incomingGroup = {}) {
  140. const existingLengths = getGroupByteLengthCandidates(existingGroup)
  141. const incomingLengths = getGroupByteLengthCandidates(incomingGroup)
  142. return existingLengths.some((length) => incomingLengths.indexOf(length) >= 0)
  143. }
  144. function isIncomingPlaceholderStructGroup(group = {}) {
  145. const registers = Array.isArray(group.registers) ? group.registers : []
  146. return group.layout === 'struct'
  147. && registers.length > 0
  148. && registers.every((register) => !!register.isPlaceholderByteField)
  149. }
  150. function hasImportedStructRegisters(group = {}) {
  151. const registers = Array.isArray(group.registers) ? group.registers : []
  152. return group.layout === 'struct'
  153. && registers.length > 0
  154. && registers.some((register) => !register.isPlaceholderByteField)
  155. }
  156. function canPreserveExistingStructLayout(existingGroup, incomingGroup, options = {}) {
  157. return options.preserveExistingStructLayout
  158. && hasImportedStructRegisters(existingGroup)
  159. && isIncomingPlaceholderStructGroup(incomingGroup)
  160. && structsMatchByName(existingGroup, incomingGroup)
  161. && structsMatchByByteLength(existingGroup, incomingGroup)
  162. }
  163. function getRegisterByteStart(register = {}) {
  164. const byteStart = Number(register.byteStart)
  165. return Number.isFinite(byteStart) ? Math.max(0, Math.floor(byteStart)) : 0
  166. }
  167. function mergePreservedStructRegister(register = {}, incomingGroup = {}) {
  168. const byteStart = getRegisterByteStart(register)
  169. const sourceAddress = ((Number(incomingGroup.sourceAddress) || Number(incomingGroup.startAddress) || 0) + byteStart) & 0xFFFF
  170. const sourceSymbolName = incomingGroup.sourceSymbolName || register.sourceSymbolName
  171. const sourceSymbolType = incomingGroup.sourceSymbolType || register.sourceSymbolType || sourceSymbolName
  172. return {
  173. ...register,
  174. rawBytes: [],
  175. rawValue: null,
  176. rawWords: [],
  177. sourceAddress,
  178. sourceAddressText: formatAddress(sourceAddress),
  179. sourceMemoryArea: incomingGroup.sourceMemoryArea,
  180. sourceMemoryClass: incomingGroup.sourceMemoryClass,
  181. sourceSymbolName,
  182. sourceSymbolType
  183. }
  184. }
  185. function mergePreservedStructGroupState(existingGroup, incomingGroup) {
  186. const preservedRegisters = (Array.isArray(existingGroup.registers) ? existingGroup.registers : [])
  187. .map((register) => mergePreservedStructRegister(register, incomingGroup))
  188. return {
  189. ...incomingGroup,
  190. deleteVisible: false,
  191. expanded: existingGroup.expanded === true,
  192. id: existingGroup.id,
  193. quantity: preservedRegisters.length,
  194. registers: preservedRegisters
  195. }
  196. }
  197. function findPreservableStructGroupIndex(groups = [], incomingGroup = {}, preferredIndex, options = {}) {
  198. if (preferredIndex !== undefined && canPreserveExistingStructLayout(groups[preferredIndex], incomingGroup, options)) {
  199. return preferredIndex
  200. }
  201. if (!options.preserveExistingStructLayout || !isIncomingPlaceholderStructGroup(incomingGroup)) return undefined
  202. return groups.findIndex((group, index) => (
  203. index !== preferredIndex && canPreserveExistingStructLayout(group, incomingGroup, options)
  204. ))
  205. }
  206. function mergeImportedRegisterState(existingRegister, incomingRegister, options = {}) {
  207. if (!existingRegister) return incomingRegister
  208. const incomingRemark = incomingRegister.remark
  209. const shouldPreserveRemark = options.preserveExistingRemarks
  210. && !String(incomingRemark === undefined || incomingRemark === null ? '' : incomingRemark).trim()
  211. return {
  212. ...incomingRegister,
  213. id: existingRegister.id,
  214. inputValue: incomingRegister.inputValue !== undefined && incomingRegister.inputValue !== null
  215. ? incomingRegister.inputValue
  216. : existingRegister.inputValue,
  217. remark: shouldPreserveRemark
  218. ? existingRegister.remark
  219. : (incomingRemark !== undefined && incomingRemark !== null ? incomingRemark : existingRegister.remark),
  220. rawBytes: [],
  221. rawValue: null,
  222. rawWords: []
  223. }
  224. }
  225. function mergeImportedGroupState(existingGroup, incomingGroup, options = {}) {
  226. if (!existingGroup) return incomingGroup
  227. if (canPreserveExistingStructLayout(existingGroup, incomingGroup, options)) {
  228. return mergePreservedStructGroupState(existingGroup, incomingGroup)
  229. }
  230. const existingRegisters = Array.isArray(existingGroup.registers) ? existingGroup.registers : []
  231. const incomingRegisters = Array.isArray(incomingGroup.registers) ? incomingGroup.registers : []
  232. return {
  233. ...incomingGroup,
  234. deleteVisible: false,
  235. expanded: existingGroup.expanded === true,
  236. id: existingGroup.id,
  237. registers: incomingRegisters.map((incomingRegister, index) => mergeImportedRegisterState(
  238. existingRegisters[index],
  239. incomingRegister,
  240. options
  241. ))
  242. }
  243. }
  244. function mergeAggregateImportedGroup(nextGroups, incomingGroup, indexes, options = {}) {
  245. const aggregateKey = getAggregateGroupDuplicateKey(incomingGroup)
  246. const aggregateGroupIndex = indexes.aggregateGroupIndexes[aggregateKey]
  247. let targetGroup = aggregateGroupIndex === undefined ? null : nextGroups[aggregateGroupIndex]
  248. let targetGroupIndex = aggregateGroupIndex
  249. let targetRegisters = targetGroup && Array.isArray(targetGroup.registers)
  250. ? targetGroup.registers.slice()
  251. : []
  252. let addedRegisterCount = 0
  253. let updatedRegisterCount = 0
  254. ;(Array.isArray(incomingGroup.registers) ? incomingGroup.registers : []).forEach((incomingRegister) => {
  255. const registerKey = getRegisterDuplicateKey(incomingRegister, incomingGroup)
  256. const existingRef = registerKey ? indexes.registerIndexes[registerKey] : null
  257. if (existingRef) {
  258. const existingGroup = nextGroups[existingRef.groupIndex]
  259. const existingRegister = existingGroup && existingGroup.registers
  260. ? existingGroup.registers[existingRef.registerIndex]
  261. : null
  262. const mergedRegister = mergeImportedRegisterState(existingRegister, incomingRegister, options)
  263. if (targetGroupIndex !== undefined && existingRef.groupIndex === targetGroupIndex) {
  264. targetRegisters[existingRef.registerIndex] = mergedRegister
  265. } else if (existingGroup) {
  266. const registers = existingGroup.registers.slice()
  267. registers[existingRef.registerIndex] = mergedRegister
  268. nextGroups[existingRef.groupIndex] = normalizeGroup({
  269. ...existingGroup,
  270. registers
  271. })
  272. }
  273. updatedRegisterCount += 1
  274. return
  275. }
  276. targetRegisters.push(incomingRegister)
  277. addedRegisterCount += 1
  278. })
  279. if (targetGroupIndex !== undefined && targetGroup) {
  280. nextGroups[targetGroupIndex] = normalizeGroup(mergeImportedGroupState(targetGroup, {
  281. ...incomingGroup,
  282. quantity: targetRegisters.length,
  283. registers: targetRegisters
  284. }))
  285. } else if (targetRegisters.length) {
  286. targetGroup = normalizeGroup({
  287. ...incomingGroup,
  288. quantity: targetRegisters.length,
  289. registers: targetRegisters
  290. })
  291. nextGroups.push(targetGroup)
  292. targetGroupIndex = nextGroups.length - 1
  293. }
  294. return {
  295. addedGroupCount: targetGroupIndex === aggregateGroupIndex ? 0 : (targetRegisters.length ? 1 : 0),
  296. addedRegisterCount,
  297. updatedGroupCount: targetGroupIndex === aggregateGroupIndex && (addedRegisterCount || updatedRegisterCount) ? 1 : 0,
  298. updatedRegisterCount
  299. }
  300. }
  301. function mergeImportedGroups(existingGroups = [], incomingGroups = [], options = {}) {
  302. const nextGroups = existingGroups.slice()
  303. let indexes = collectImportedVariableIndexes(nextGroups)
  304. const result = {
  305. addedGroupCount: 0,
  306. addedRegisterCount: 0,
  307. groups: nextGroups,
  308. updatedGroupCount: 0,
  309. updatedRegisterCount: 0
  310. }
  311. incomingGroups.forEach((incomingGroup) => {
  312. if (isSingleRegisterAggregateGroup(incomingGroup)) {
  313. const aggregateResult = mergeAggregateImportedGroup(nextGroups, incomingGroup, indexes, options)
  314. result.addedGroupCount += aggregateResult.addedGroupCount
  315. result.addedRegisterCount += aggregateResult.addedRegisterCount
  316. result.updatedGroupCount += aggregateResult.updatedGroupCount
  317. result.updatedRegisterCount += aggregateResult.updatedRegisterCount
  318. indexes = collectImportedVariableIndexes(nextGroups)
  319. return
  320. }
  321. const groupKey = getGroupDuplicateKey(incomingGroup)
  322. const existingGroupIndex = groupKey ? indexes.groupIndexes[groupKey] : undefined
  323. const preservableStructGroupIndex = findPreservableStructGroupIndex(
  324. nextGroups,
  325. incomingGroup,
  326. existingGroupIndex,
  327. options
  328. )
  329. const targetGroupIndex = preservableStructGroupIndex >= 0
  330. ? preservableStructGroupIndex
  331. : existingGroupIndex
  332. if (targetGroupIndex !== undefined) {
  333. const existingGroup = nextGroups[targetGroupIndex]
  334. nextGroups[targetGroupIndex] = normalizeGroup(mergeImportedGroupState(existingGroup, incomingGroup, options))
  335. result.updatedGroupCount += 1
  336. } else {
  337. nextGroups.push(incomingGroup)
  338. result.addedGroupCount += 1
  339. }
  340. indexes = collectImportedVariableIndexes(nextGroups)
  341. })
  342. result.changedCount = result.addedGroupCount
  343. + result.updatedGroupCount
  344. + result.addedRegisterCount
  345. + result.updatedRegisterCount
  346. return result
  347. }
  348. module.exports = {
  349. mergeImportedGroups
  350. }