1
0

struct-c-syntax.js 13 KB

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