1
0

struct-c-syntax.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549
  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 ENUM_PATTERNS = [
  46. /typedef\s+enum(?:\s+([A-Za-z_]\w*))?\s*\{([\s\S]*?)\}\s*([A-Za-z_]\w*)\s*;/g,
  47. /enum\s+([A-Za-z_]\w*)\s*\{([\s\S]*?)\}\s*;/g
  48. ]
  49. function normalizeLookupName(value) {
  50. return String(value || '')
  51. .replace(/^_+/, '')
  52. .replace(/[^A-Za-z0-9]/g, '')
  53. .toLowerCase()
  54. }
  55. function stripComments(source) {
  56. return String(source || '')
  57. .replace(/\/\*[\s\S]*?\*\//g, '')
  58. .replace(/\/\/.*$/gm, '')
  59. }
  60. function normalizeTypeText(typeText) {
  61. return String(typeText || '')
  62. .replace(/\*/g, ' ')
  63. .replace(/\s+/g, ' ')
  64. .trim()
  65. }
  66. function resolveType(typeText, aliases) {
  67. const normalized = normalizeTypeText(typeText)
  68. if (!normalized) return ''
  69. const compact = normalized
  70. .split(/\s+/)
  71. .filter((token) => !TYPE_QUALIFIERS[token])
  72. .join(' ')
  73. .trim()
  74. if (!compact || /^struct\b/.test(compact) || compact.indexOf('*') >= 0) {
  75. return ''
  76. }
  77. if (aliases[compact]) return aliases[compact]
  78. const tokens = compact.split(/\s+/).filter(Boolean)
  79. for (const token of tokens) {
  80. if (aliases[token]) return aliases[token]
  81. }
  82. return ''
  83. }
  84. function getEnumTypeNames(enumInfo = {}) {
  85. return [
  86. enumInfo.name,
  87. enumInfo.typedefName,
  88. enumInfo.tagName ? `enum ${enumInfo.tagName}` : '',
  89. enumInfo.tagName
  90. ]
  91. .filter(Boolean)
  92. .filter((name, index, list) => list.indexOf(name) === index)
  93. }
  94. function normalizeEnumTypeKey(value) {
  95. return normalizeTypeText(value).toLowerCase()
  96. }
  97. function createEnumTypeMap(enums = []) {
  98. return enums.reduce((map, enumInfo) => {
  99. getEnumTypeNames(enumInfo).forEach((typeName) => {
  100. map[normalizeTypeText(typeName)] = enumInfo
  101. map[normalizeEnumTypeKey(typeName)] = enumInfo
  102. })
  103. return map
  104. }, {})
  105. }
  106. function createAliasMap(source, enums = findEnums(source)) {
  107. const aliases = {
  108. ...TYPE_ALIASES
  109. }
  110. const definePattern = /^\s*#\s*define\s+([A-Za-z_]\w*)\s+([A-Za-z_]\w*)\s*$/gm
  111. let defineMatch
  112. while ((defineMatch = definePattern.exec(source))) {
  113. const name = defineMatch[1]
  114. const value = defineMatch[2]
  115. if (aliases[value]) aliases[name] = aliases[value]
  116. }
  117. const typedefPattern = /typedef\s+(?!struct\b)([^;{}]+?)\s+([A-Za-z_]\w*)\s*;/g
  118. let typedefMatch
  119. while ((typedefMatch = typedefPattern.exec(source))) {
  120. const resolvedType = resolveType(typedefMatch[1], aliases)
  121. if (resolvedType) aliases[typedefMatch[2]] = resolvedType
  122. }
  123. const typedefStructPattern = /typedef\s+struct(?:\s+([A-Za-z_]\w*))?\s*\{[\s\S]*?\}\s*([A-Za-z_]\w*)\s*;/g
  124. let structTypedefMatch
  125. while ((structTypedefMatch = typedefStructPattern.exec(source))) {
  126. const tagName = structTypedefMatch[1]
  127. const typedefName = structTypedefMatch[2]
  128. if (tagName && typedefName) aliases[`struct ${tagName}`] = typedefName
  129. }
  130. enums.forEach((enumInfo) => {
  131. getEnumTypeNames(enumInfo).forEach((typeName) => {
  132. aliases[typeName] = enumInfo.dataType || 'uint16_t'
  133. })
  134. })
  135. return aliases
  136. }
  137. function tokenizeEnumExpression(expression, symbols = {}) {
  138. const source = String(expression || '').trim()
  139. const tokens = []
  140. let index = 0
  141. while (index < source.length) {
  142. const char = source[index]
  143. if (/\s/.test(char)) {
  144. index += 1
  145. continue
  146. }
  147. const numberMatch = source.slice(index).match(/^(?:0x[0-9a-f]+|\d+)(?:u|U|l|L|ul|UL|uL|Ul|lu|LU|lU|Lu)?/i)
  148. if (numberMatch) {
  149. const raw = numberMatch[0].replace(/(?:u|U|l|L|ul|UL|uL|Ul|lu|LU|lU|Lu)+$/i, '')
  150. tokens.push({
  151. type: 'number',
  152. value: raw.toLowerCase().startsWith('0x') ? parseInt(raw, 16) : Number(raw)
  153. })
  154. index += numberMatch[0].length
  155. continue
  156. }
  157. const identifierMatch = source.slice(index).match(/^[A-Za-z_]\w*/)
  158. if (identifierMatch) {
  159. const name = identifierMatch[0]
  160. if (!Object.prototype.hasOwnProperty.call(symbols, name)) return null
  161. tokens.push({
  162. type: 'number',
  163. value: Number(symbols[name])
  164. })
  165. index += name.length
  166. continue
  167. }
  168. const twoCharOperator = source.slice(index, index + 2)
  169. if (twoCharOperator === '<<' || twoCharOperator === '>>') {
  170. tokens.push({
  171. type: 'operator',
  172. value: twoCharOperator
  173. })
  174. index += 2
  175. continue
  176. }
  177. if ('+-*/%&|^~()'.indexOf(char) >= 0) {
  178. tokens.push({
  179. type: char === '(' || char === ')' ? 'paren' : 'operator',
  180. value: char
  181. })
  182. index += 1
  183. continue
  184. }
  185. return null
  186. }
  187. return tokens
  188. }
  189. function createEnumExpressionParser(tokens) {
  190. let index = 0
  191. const precedence = {
  192. '|': 1,
  193. '^': 2,
  194. '&': 3,
  195. '<<': 4,
  196. '>>': 4,
  197. '+': 5,
  198. '-': 5,
  199. '*': 6,
  200. '/': 6,
  201. '%': 6
  202. }
  203. function peek() {
  204. return tokens[index] || null
  205. }
  206. function consume() {
  207. const token = tokens[index] || null
  208. index += 1
  209. return token
  210. }
  211. function applyBinaryOperator(operator, left, right) {
  212. if (operator === '+') return left + right
  213. if (operator === '-') return left - right
  214. if (operator === '*') return left * right
  215. if (operator === '/') return right === 0 ? NaN : Math.trunc(left / right)
  216. if (operator === '%') return right === 0 ? NaN : left % right
  217. if (operator === '<<') return left << right
  218. if (operator === '>>') return left >> right
  219. if (operator === '&') return left & right
  220. if (operator === '^') return left ^ right
  221. if (operator === '|') return left | right
  222. return NaN
  223. }
  224. function parsePrimary() {
  225. const token = consume()
  226. if (!token) return NaN
  227. if (token.type === 'number') return Number(token.value)
  228. if (token.value === '(') {
  229. const value = parseExpression(1)
  230. const endToken = consume()
  231. return endToken && endToken.value === ')' ? value : NaN
  232. }
  233. return NaN
  234. }
  235. function parseUnary() {
  236. const token = peek()
  237. if (token && token.type === 'operator' && ['+', '-', '~'].indexOf(token.value) >= 0) {
  238. consume()
  239. const value = parseUnary()
  240. if (token.value === '+') return value
  241. if (token.value === '-') return -value
  242. return ~value
  243. }
  244. return parsePrimary()
  245. }
  246. function parseExpression(minPrecedence) {
  247. let left = parseUnary()
  248. while (Number.isFinite(left)) {
  249. const token = peek()
  250. const tokenPrecedence = token && token.type === 'operator' ? precedence[token.value] : 0
  251. if (!tokenPrecedence || tokenPrecedence < minPrecedence) break
  252. consume()
  253. const right = parseExpression(tokenPrecedence + 1)
  254. left = applyBinaryOperator(token.value, left, right)
  255. }
  256. return left
  257. }
  258. return {
  259. parse() {
  260. const value = parseExpression(1)
  261. return index === tokens.length && Number.isFinite(value) ? Math.trunc(value) : null
  262. }
  263. }
  264. }
  265. function evaluateEnumExpression(expression, symbols = {}) {
  266. const tokens = tokenizeEnumExpression(expression, symbols)
  267. if (!tokens || !tokens.length) return null
  268. return createEnumExpressionParser(tokens).parse()
  269. }
  270. function inferEnumDataType(options = []) {
  271. const values = options.map((option) => Number(option.value)).filter((value) => Number.isFinite(value))
  272. const minValue = values.length ? Math.min.apply(null, values) : 0
  273. const maxValue = values.length ? Math.max.apply(null, values) : 0
  274. if (minValue < 0) {
  275. if (minValue >= -0x8000 && maxValue <= 0x7FFF) return 'int16_t'
  276. return 'int32_t'
  277. }
  278. if (maxValue <= 0xFFFF) return 'uint16_t'
  279. return 'uint32_t'
  280. }
  281. function parseEnumOptions(body, globalSymbols = {}) {
  282. const options = []
  283. const symbols = {
  284. ...globalSymbols
  285. }
  286. let currentValue = -1
  287. String(body || '').split(',').forEach((item) => {
  288. const text = item.trim()
  289. if (!text) return
  290. const match = text.match(/^([A-Za-z_]\w*)\s*(?:=\s*(.+))?$/)
  291. if (!match) return
  292. const name = match[1]
  293. const explicitValue = match[2] ? evaluateEnumExpression(match[2], symbols) : null
  294. currentValue = explicitValue === null ? currentValue + 1 : explicitValue
  295. symbols[name] = currentValue
  296. options.push({
  297. label: name,
  298. name,
  299. value: currentValue
  300. })
  301. })
  302. return {
  303. options,
  304. symbols
  305. }
  306. }
  307. function findEnums(source) {
  308. const enums = []
  309. const globalSymbols = {}
  310. ENUM_PATTERNS.forEach((pattern) => {
  311. pattern.lastIndex = 0
  312. let match
  313. while ((match = pattern.exec(source))) {
  314. const isTypedef = pattern === ENUM_PATTERNS[0]
  315. const tagName = isTypedef ? match[1] : match[1]
  316. const body = isTypedef ? match[2] : match[2]
  317. const typedefName = isTypedef ? match[3] : ''
  318. const parsed = parseEnumOptions(body, globalSymbols)
  319. if (!parsed.options.length) continue
  320. Object.assign(globalSymbols, parsed.symbols)
  321. const name = typedefName || tagName || 'enum'
  322. const enumInfo = {
  323. dataType: inferEnumDataType(parsed.options),
  324. name,
  325. options: parsed.options,
  326. tagName,
  327. typedefName,
  328. typeNames: []
  329. }
  330. enumInfo.typeNames = getEnumTypeNames(enumInfo)
  331. enums.push(enumInfo)
  332. }
  333. })
  334. const seen = {}
  335. return enums.filter((enumInfo) => {
  336. const key = enumInfo.typeNames.map(normalizeEnumTypeKey).join('|')
  337. if (!key || seen[key]) return false
  338. seen[key] = true
  339. return true
  340. })
  341. }
  342. function findStruct(source) {
  343. for (const pattern of STRUCT_PATTERNS) {
  344. pattern.lastIndex = 0
  345. const match = pattern.exec(source)
  346. if (!match) continue
  347. if (pattern === STRUCT_PATTERNS[0]) {
  348. return {
  349. body: match[1],
  350. name: match[2]
  351. }
  352. }
  353. return {
  354. body: match[2],
  355. name: match[1]
  356. }
  357. }
  358. return null
  359. }
  360. function findStructs(source) {
  361. const structs = []
  362. STRUCT_PATTERNS.forEach((pattern) => {
  363. pattern.lastIndex = 0
  364. let match
  365. while ((match = pattern.exec(source))) {
  366. if (pattern === STRUCT_PATTERNS[0]) {
  367. structs.push({
  368. body: match[1],
  369. name: match[2]
  370. })
  371. } else {
  372. structs.push({
  373. body: match[2],
  374. name: match[1],
  375. tagName: match[1]
  376. })
  377. }
  378. }
  379. })
  380. const seen = {}
  381. return structs.filter((item) => {
  382. const key = item.name
  383. if (!key || seen[key]) return false
  384. seen[key] = true
  385. return true
  386. })
  387. }
  388. function parseArrayDimensions(suffix) {
  389. const dimensions = []
  390. const pattern = /\[([^\]]*)\]/g
  391. let match
  392. while ((match = pattern.exec(suffix || ''))) {
  393. const text = String(match[1] || '').trim()
  394. const value = Number(text)
  395. if (!Number.isInteger(value) || value < 1) {
  396. throw new Error('数组长度需为正整数')
  397. }
  398. dimensions.push(value)
  399. }
  400. return dimensions
  401. }
  402. function splitDeclarations(body) {
  403. return String(body || '')
  404. .split(';')
  405. .map((item) => item.trim())
  406. .filter(Boolean)
  407. }
  408. function splitDeclarators(statement) {
  409. return String(statement || '')
  410. .split(',')
  411. .map((item) => item.trim())
  412. .filter(Boolean)
  413. }
  414. function parseFirstDeclarator(text) {
  415. const match = String(text || '').match(/^(.+?)\s+(\**\s*(?:[A-Za-z_]\w*)?(?:\s*\[[^\]]*\])*(?:\s*:\s*\d+)?)$/)
  416. if (!match) return null
  417. return {
  418. declarator: match[2],
  419. typeText: match[1]
  420. }
  421. }
  422. function parseDeclarator(text) {
  423. const rawText = String(text || '')
  424. const bitWidthMatch = rawText.match(/:\s*(\d+)\s*$/)
  425. const cleaned = rawText
  426. .replace(/=.*/, '')
  427. .replace(/:\s*\d+\s*$/, '')
  428. .replace(/\*/g, '')
  429. .trim()
  430. if (!cleaned && bitWidthMatch) {
  431. return {
  432. arrayDimensions: [],
  433. bitWidth: Number(bitWidthMatch[1]),
  434. name: ''
  435. }
  436. }
  437. const match = cleaned.match(/^([A-Za-z_]\w*)\s*((?:\[[^\]]*\])*)$/)
  438. if (!match) return null
  439. return {
  440. arrayDimensions: parseArrayDimensions(match[2]),
  441. bitWidth: bitWidthMatch ? Number(bitWidthMatch[1]) : null,
  442. name: match[1]
  443. }
  444. }
  445. module.exports = {
  446. createAliasMap,
  447. createEnumTypeMap,
  448. findStruct,
  449. findStructs,
  450. findEnums,
  451. getEnumTypeNames,
  452. normalizeLookupName,
  453. normalizeTypeText,
  454. parseDeclarator,
  455. parseFirstDeclarator,
  456. resolveType,
  457. splitDeclarations,
  458. splitDeclarators,
  459. stripComments
  460. }