1
0

struct-parser.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  1. const TYPE_ALIASES = {
  2. bit: 'uint8_t',
  3. bool: 'uint8_t',
  4. char: 'int8_t',
  5. double: 'float',
  6. float: 'float',
  7. int: 'int16_t',
  8. int8: 'int8_t',
  9. int8_t: 'int8_t',
  10. int16: 'int16_t',
  11. int16_t: 'int16_t',
  12. int32: 'int32_t',
  13. int32_t: 'int32_t',
  14. long: 'int32_t',
  15. short: 'int16_t',
  16. 'signed char': 'int8_t',
  17. 'signed int': 'int16_t',
  18. 'signed long': 'int32_t',
  19. 'signed short': 'int16_t',
  20. uint8: 'uint8_t',
  21. uint8_t: 'uint8_t',
  22. uint16: 'uint16_t',
  23. uint16_t: 'uint16_t',
  24. uint32: 'uint32_t',
  25. uint32_t: 'uint32_t',
  26. 'unsigned char': 'uint8_t',
  27. 'unsigned int': 'uint16_t',
  28. 'unsigned long': 'uint32_t',
  29. 'unsigned short': 'uint16_t'
  30. }
  31. const TYPE_QUALIFIERS = {
  32. _I: true,
  33. _IO: true,
  34. _O: true,
  35. const: true,
  36. extern: true,
  37. register: true,
  38. static: true,
  39. volatile: true
  40. }
  41. const STRUCT_PATTERNS = [
  42. /typedef\s+struct(?:\s+[A-Za-z_]\w*)?\s*\{([\s\S]*?)\}\s*([A-Za-z_]\w*)\s*;/g,
  43. /struct\s+([A-Za-z_]\w*)\s*\{([\s\S]*?)\}\s*;/g
  44. ]
  45. const STRUCT_VARIABLE_QUALIFIERS = {
  46. code: true,
  47. const: true,
  48. data: true,
  49. extern: true,
  50. idata: true,
  51. pdata: true,
  52. static: true,
  53. volatile: true,
  54. xdata: true
  55. }
  56. function normalizeLookupName(value) {
  57. return String(value || '')
  58. .replace(/^_+/, '')
  59. .replace(/[^A-Za-z0-9]/g, '')
  60. .toLowerCase()
  61. }
  62. function stripComments(source) {
  63. return String(source || '')
  64. .replace(/\/\*[\s\S]*?\*\//g, '')
  65. .replace(/\/\/.*$/gm, '')
  66. }
  67. function normalizeTypeText(typeText) {
  68. return String(typeText || '')
  69. .replace(/\*/g, ' ')
  70. .replace(/\s+/g, ' ')
  71. .trim()
  72. }
  73. function createAliasMap(source) {
  74. const aliases = {
  75. ...TYPE_ALIASES
  76. }
  77. const definePattern = /^\s*#\s*define\s+([A-Za-z_]\w*)\s+([A-Za-z_]\w*)\s*$/gm
  78. let defineMatch
  79. while ((defineMatch = definePattern.exec(source))) {
  80. const name = defineMatch[1]
  81. const value = defineMatch[2]
  82. if (aliases[value]) aliases[name] = aliases[value]
  83. }
  84. const typedefPattern = /typedef\s+(?!struct\b)([^;{}]+?)\s+([A-Za-z_]\w*)\s*;/g
  85. let typedefMatch
  86. while ((typedefMatch = typedefPattern.exec(source))) {
  87. const resolvedType = resolveType(typedefMatch[1], aliases)
  88. if (resolvedType) aliases[typedefMatch[2]] = resolvedType
  89. }
  90. const typedefStructPattern = /typedef\s+struct(?:\s+([A-Za-z_]\w*))?\s*\{[\s\S]*?\}\s*([A-Za-z_]\w*)\s*;/g
  91. let structTypedefMatch
  92. while ((structTypedefMatch = typedefStructPattern.exec(source))) {
  93. const tagName = structTypedefMatch[1]
  94. const typedefName = structTypedefMatch[2]
  95. if (tagName && typedefName) aliases[`struct ${tagName}`] = typedefName
  96. }
  97. return aliases
  98. }
  99. function resolveType(typeText, aliases) {
  100. const normalized = normalizeTypeText(typeText)
  101. if (!normalized) return ''
  102. const compact = normalized
  103. .split(/\s+/)
  104. .filter((token) => !TYPE_QUALIFIERS[token])
  105. .join(' ')
  106. .trim()
  107. if (!compact || /^struct\b/.test(compact) || /^enum\b/.test(compact) || compact.indexOf('*') >= 0) {
  108. return ''
  109. }
  110. if (aliases[compact]) return aliases[compact]
  111. const tokens = compact.split(/\s+/).filter(Boolean)
  112. for (const token of tokens) {
  113. if (aliases[token]) return aliases[token]
  114. }
  115. return ''
  116. }
  117. function findStruct(source) {
  118. for (const pattern of STRUCT_PATTERNS) {
  119. pattern.lastIndex = 0
  120. const match = pattern.exec(source)
  121. if (!match) continue
  122. if (pattern === STRUCT_PATTERNS[0]) {
  123. return {
  124. body: match[1],
  125. name: match[2]
  126. }
  127. }
  128. return {
  129. body: match[2],
  130. name: match[1]
  131. }
  132. }
  133. return null
  134. }
  135. function findStructs(source) {
  136. const structs = []
  137. STRUCT_PATTERNS.forEach((pattern) => {
  138. pattern.lastIndex = 0
  139. let match
  140. while ((match = pattern.exec(source))) {
  141. if (pattern === STRUCT_PATTERNS[0]) {
  142. structs.push({
  143. body: match[1],
  144. name: match[2]
  145. })
  146. } else {
  147. structs.push({
  148. body: match[2],
  149. name: match[1],
  150. tagName: match[1]
  151. })
  152. }
  153. }
  154. })
  155. const seen = {}
  156. return structs.filter((item) => {
  157. const key = item.name
  158. if (!key || seen[key]) return false
  159. seen[key] = true
  160. return true
  161. })
  162. }
  163. function parseArrayDimensions(suffix) {
  164. const dimensions = []
  165. const pattern = /\[([^\]]*)\]/g
  166. let match
  167. while ((match = pattern.exec(suffix || ''))) {
  168. const text = String(match[1] || '').trim()
  169. const value = Number(text)
  170. if (!Number.isInteger(value) || value < 1) {
  171. throw new Error('数组长度需为正整数')
  172. }
  173. dimensions.push(value)
  174. }
  175. return dimensions
  176. }
  177. function splitDeclarations(body) {
  178. return String(body || '')
  179. .split(';')
  180. .map((item) => item.trim())
  181. .filter(Boolean)
  182. }
  183. function splitDeclarators(statement) {
  184. return String(statement || '')
  185. .split(',')
  186. .map((item) => item.trim())
  187. .filter(Boolean)
  188. }
  189. function parseFirstDeclarator(text) {
  190. const match = String(text || '').match(/^(.+?)\s+(\**\s*(?:[A-Za-z_]\w*)?(?:\s*\[[^\]]*\])*(?:\s*:\s*\d+)?)$/)
  191. if (!match) return null
  192. return {
  193. declarator: match[2],
  194. typeText: match[1]
  195. }
  196. }
  197. function parseDeclarator(text) {
  198. const rawText = String(text || '')
  199. const bitWidthMatch = rawText.match(/:\s*(\d+)\s*$/)
  200. const cleaned = rawText
  201. .replace(/=.*/, '')
  202. .replace(/:\s*\d+\s*$/, '')
  203. .replace(/\*/g, '')
  204. .trim()
  205. if (!cleaned && bitWidthMatch) {
  206. return {
  207. arrayDimensions: [],
  208. bitWidth: Number(bitWidthMatch[1]),
  209. name: ''
  210. }
  211. }
  212. const match = cleaned.match(/^([A-Za-z_]\w*)\s*((?:\[[^\]]*\])*)$/)
  213. if (!match) return null
  214. return {
  215. arrayDimensions: parseArrayDimensions(match[2]),
  216. bitWidth: bitWidthMatch ? Number(bitWidthMatch[1]) : null,
  217. name: match[1]
  218. }
  219. }
  220. function isAsciiArray(typeText, dataType, name, arrayLength) {
  221. if (!arrayLength || arrayLength < 2 || arrayLength > 32) return false
  222. const normalizedType = normalizeTypeText(typeText).toLowerCase()
  223. if (normalizedType === 'char' || normalizedType === 'signed char') return true
  224. return dataType === 'uint8_t' && /(^|_)(model|name|text|str|string|chip|version|ver|serial|sn)($|_)/i.test(name)
  225. }
  226. function getDataTypeByteLength(dataType) {
  227. if (dataType === 'float' || dataType === 'int32_t' || dataType === 'uint32_t') return 4
  228. if (dataType === 'int8_t' || dataType === 'uint8_t') return 1
  229. return 2
  230. }
  231. function getBitFieldDataType(bitWidth) {
  232. const width = Math.max(1, Math.round(Number(bitWidth) || 1))
  233. if (width <= 8) return 'uint8_t'
  234. if (width <= 16) return 'uint16_t'
  235. return 'uint32_t'
  236. }
  237. function isBitType(typeText) {
  238. return normalizeTypeText(typeText).toLowerCase() === 'bit'
  239. }
  240. function alignLayoutToByte(layoutState) {
  241. if (layoutState.bitOffset % 8 !== 0) {
  242. layoutState.bitOffset += 8 - (layoutState.bitOffset % 8)
  243. }
  244. }
  245. function getLayoutByteStart(layoutState) {
  246. return Math.floor(layoutState.bitOffset / 8)
  247. }
  248. function advanceLayoutBytes(layoutState, byteLength) {
  249. layoutState.bitOffset += Math.max(1, Number(byteLength) || 1) * 8
  250. }
  251. function createBitFieldRegister(field, bitWidth, layoutState, name) {
  252. const width = Math.max(0, Math.round(Number(bitWidth) || 0))
  253. if (width === 0) {
  254. alignLayoutToByte(layoutState)
  255. return []
  256. }
  257. const byteStart = getLayoutByteStart(layoutState)
  258. const bitOffset = layoutState.bitOffset % 8
  259. layoutState.bitOffset += width
  260. if (!name) return []
  261. return [{
  262. bitOffset,
  263. bitWidth: width,
  264. byteStart,
  265. dataType: getBitFieldDataType(width),
  266. isBitField: true,
  267. name,
  268. unit: 'bit'
  269. }]
  270. }
  271. function createRegisterFromField(field, dataType, originalTypeText, layoutState) {
  272. const arrayLength = field.arrayDimensions.reduce((total, value) => total * value, 1)
  273. const hasArray = field.arrayDimensions.length > 0
  274. const bitFieldWidth = field.bitWidth !== null && field.bitWidth !== undefined
  275. ? field.bitWidth
  276. : (isBitType(originalTypeText) ? 1 : null)
  277. if (bitFieldWidth !== null && bitFieldWidth !== undefined) {
  278. if (hasArray) {
  279. const registers = []
  280. for (let index = 0; index < arrayLength; index += 1) {
  281. registers.push(...createBitFieldRegister(
  282. field,
  283. bitFieldWidth,
  284. layoutState,
  285. field.name ? `${field.name}[${index}]` : ''
  286. ))
  287. }
  288. return registers
  289. }
  290. return createBitFieldRegister(field, bitFieldWidth, layoutState, field.name)
  291. }
  292. alignLayoutToByte(layoutState)
  293. if (hasArray && isAsciiArray(originalTypeText, dataType, field.name, arrayLength)) {
  294. const byteStart = getLayoutByteStart(layoutState)
  295. advanceLayoutBytes(layoutState, arrayLength)
  296. return [{
  297. byteStart,
  298. dataType: 'ascii',
  299. name: field.name,
  300. textByteLength: String(arrayLength)
  301. }]
  302. }
  303. if (!hasArray) {
  304. const byteStart = getLayoutByteStart(layoutState)
  305. advanceLayoutBytes(layoutState, getDataTypeByteLength(dataType))
  306. return [{
  307. byteStart,
  308. dataType,
  309. name: field.name
  310. }]
  311. }
  312. const registers = []
  313. for (let index = 0; index < arrayLength; index += 1) {
  314. const byteStart = getLayoutByteStart(layoutState)
  315. advanceLayoutBytes(layoutState, getDataTypeByteLength(dataType))
  316. registers.push({
  317. byteStart,
  318. dataType,
  319. name: `${field.name}[${index}]`
  320. })
  321. }
  322. return registers
  323. }
  324. function parseStructFields(body, aliases) {
  325. const registers = []
  326. const layoutState = {
  327. bitOffset: 0
  328. }
  329. const declarations = splitDeclarations(body)
  330. declarations.forEach((statement) => {
  331. if (!statement || statement.indexOf('(') >= 0) return
  332. const parts = splitDeclarators(statement)
  333. if (!parts.length) return
  334. const first = parseFirstDeclarator(parts[0])
  335. if (!first) return
  336. const dataType = resolveType(first.typeText, aliases)
  337. if (!dataType) return
  338. const declarators = [first.declarator].concat(parts.slice(1))
  339. declarators.forEach((declaratorText) => {
  340. const field = parseDeclarator(declaratorText)
  341. if (!field) return
  342. registers.push(...createRegisterFromField(field, dataType, first.typeText, layoutState))
  343. })
  344. })
  345. return registers.map((register) => ({
  346. ...register,
  347. structByteLength: Math.ceil(layoutState.bitOffset / 8)
  348. }))
  349. }
  350. function parseStructDefinition(sourceText) {
  351. const source = stripComments(sourceText)
  352. const aliases = createAliasMap(source)
  353. const structInfo = findStruct(source)
  354. if (!structInfo) {
  355. throw new Error('未找到结构体定义')
  356. }
  357. const registers = parseStructFields(structInfo.body, aliases)
  358. if (!registers.length) {
  359. throw new Error('结构体中没有可识别的变量定义')
  360. }
  361. return {
  362. name: structInfo.name || 'Struct',
  363. registers,
  364. structName: structInfo.name || 'Struct'
  365. }
  366. }
  367. function getStructNameAliases(structInfo, aliases) {
  368. const names = [structInfo.name]
  369. if (structInfo.tagName) names.push(`struct ${structInfo.tagName}`)
  370. Object.keys(aliases || {}).forEach((aliasName) => {
  371. if (aliases[aliasName] === structInfo.name) names.push(aliasName)
  372. })
  373. return names.filter(Boolean)
  374. }
  375. function normalizeVariableTypeText(typeText, aliases) {
  376. const normalized = normalizeTypeText(typeText)
  377. if (!normalized) return ''
  378. const tokens = normalized
  379. .split(/\s+/)
  380. .filter((token) => !STRUCT_VARIABLE_QUALIFIERS[token])
  381. const compact = tokens.join(' ')
  382. if (aliases && aliases[compact]) return aliases[compact]
  383. return compact
  384. }
  385. function parseStructVariables(source, structs, aliases) {
  386. const variablesByName = {}
  387. structs.forEach((structInfo) => {
  388. const structNames = getStructNameAliases(structInfo, aliases)
  389. structNames.forEach((structName) => {
  390. const escaped = structName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
  391. const variablePattern = new RegExp(`(^|[;\\n{}])\\s*([A-Za-z_][\\w\\s]*?\\s+)?${escaped}\\s+([^;{}()]+);`, 'g')
  392. let match
  393. while ((match = variablePattern.exec(source))) {
  394. const prefix = normalizeVariableTypeText(match[2] || '', aliases)
  395. if (prefix) continue
  396. splitDeclarators(match[3]).forEach((declaratorText) => {
  397. const field = parseDeclarator(declaratorText)
  398. if (!field) return
  399. const variableInfo = {
  400. arrayDimensions: field.arrayDimensions,
  401. name: field.name,
  402. registers: structInfo.registers,
  403. structName: structInfo.name
  404. }
  405. variablesByName[field.name] = variableInfo
  406. variablesByName[field.name.replace(/^_+/, '').toLowerCase()] = variableInfo
  407. variablesByName[normalizeLookupName(field.name)] = variableInfo
  408. })
  409. }
  410. })
  411. })
  412. return variablesByName
  413. }
  414. function parseStructCatalog(sourceText) {
  415. const source = stripComments(sourceText)
  416. const aliases = createAliasMap(source)
  417. const structs = findStructs(source).map((structInfo) => ({
  418. ...structInfo,
  419. registers: parseStructFields(structInfo.body, aliases)
  420. })).filter((structInfo) => structInfo.registers.length)
  421. if (!structs.length) {
  422. throw new Error('未找到可识别的结构体定义')
  423. }
  424. return {
  425. structs,
  426. variablesByName: parseStructVariables(source, structs, aliases)
  427. }
  428. }
  429. module.exports = {
  430. parseStructCatalog,
  431. parseStructDefinition,
  432. stripComments
  433. }