tablegen.js 89 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187
  1. import * as fs from 'fs/promises';
  2. import * as path from 'path';
  3. const tablegen = {};
  4. tablegen.Location = class {
  5. constructor(file, line, column) {
  6. this.file = file;
  7. this.line = line;
  8. this.column = column;
  9. }
  10. toString() {
  11. return `${this.file}:${this.line}:${this.column}`;
  12. }
  13. };
  14. tablegen.Token = class {
  15. constructor(type, value, location) {
  16. this.type = type;
  17. this.value = value;
  18. this.location = location;
  19. }
  20. };
  21. tablegen.Tokenizer = class {
  22. constructor(text, file) {
  23. this._text = text;
  24. this._file = file;
  25. this._position = 0;
  26. this._line = 1;
  27. this._column = 1;
  28. this._keywords = new Set([
  29. 'assert', 'bit', 'bits', 'class', 'code', 'dag', 'def', 'defm', 'defset', 'defvar',
  30. 'dump', 'else', 'false', 'field', 'foreach', 'if', 'in', 'include', 'int',
  31. 'let', 'list', 'multiclass', 'string', 'then', 'true'
  32. ]);
  33. this._token = this._tokenize();
  34. }
  35. location() {
  36. return new tablegen.Location(this._file, this._line, this._column);
  37. }
  38. current() {
  39. return this._token;
  40. }
  41. peek(offset = 0) {
  42. const pos = this._position + offset;
  43. return pos < this._text.length ? this._text[pos] : null;
  44. }
  45. read() {
  46. const prev = this._token;
  47. this._token = this._tokenize();
  48. return prev;
  49. }
  50. _tokenize() {
  51. this._skipWhitespace();
  52. if (this._position >= this._text.length) {
  53. return new tablegen.Token('eof', null, this.location());
  54. }
  55. const location = this.location();
  56. const c = this.peek();
  57. if (c === '"') {
  58. return this._readString(location);
  59. }
  60. if (c === '[' && this.peek(1) === '{') {
  61. return this._readCodeBlock(location);
  62. }
  63. if (c === ':' && this.peek(1) === ':') {
  64. this._next();
  65. this._next();
  66. return new tablegen.Token('::', '::', location);
  67. }
  68. if (c === '.' && this.peek(1) === '.' && this.peek(2) === '.') {
  69. this._next();
  70. this._next();
  71. this._next();
  72. return new tablegen.Token('...', '...', location);
  73. }
  74. if (c === '-' && this.peek(1) === '>') {
  75. this._next();
  76. this._next();
  77. return new tablegen.Token('->', '->', location);
  78. }
  79. if (c === '=' && this.peek(1) === '=') {
  80. this._next();
  81. this._next();
  82. return new tablegen.Token('==', '==', location);
  83. }
  84. if (c === '!' && this.peek(1) === '=') {
  85. this._next();
  86. this._next();
  87. return new tablegen.Token('!=', '!=', location);
  88. }
  89. if (c === '<' && this.peek(1) === '<') {
  90. this._next();
  91. this._next();
  92. return new tablegen.Token('<<', '<<', location);
  93. }
  94. if (this._isDigit(c)) {
  95. let pos = 1;
  96. while (this.peek(pos) && this._isDigit(this.peek(pos))) {
  97. pos++;
  98. }
  99. if (this.peek(pos) && /[a-zA-Z_]/.test(this.peek(pos))) {
  100. return this._readIdentifier(location);
  101. }
  102. return this._readNumber(location);
  103. }
  104. if (c === '-' && this._isDigit(this.peek(1))) {
  105. return this._readNumber(location);
  106. }
  107. if (this._isIdentifierStart(c)) {
  108. return this._readIdentifier(location);
  109. }
  110. if ('{}[]()<>,;:=!#$.?^'.includes(c)) {
  111. this._next();
  112. return new tablegen.Token(c, c, location);
  113. }
  114. throw new tablegen.Error(`Unexpected character '${c}' at ${location}`);
  115. }
  116. _next() {
  117. if (this._position < this._text.length) {
  118. if (this._text[this._position] === '\n') {
  119. this._line++;
  120. this._column = 1;
  121. } else {
  122. this._column++;
  123. }
  124. this._position++;
  125. }
  126. }
  127. _skipWhitespace() {
  128. while (this._position < this._text.length) {
  129. const c = this.peek();
  130. if (/\s/.test(c)) {
  131. this._next();
  132. continue;
  133. }
  134. if (c === '/' && this.peek(1) === '/') {
  135. this._next();
  136. this._next();
  137. while (this._position < this._text.length && this.peek() !== '\n') {
  138. this._next();
  139. }
  140. continue;
  141. }
  142. if (c === '/' && this.peek(1) === '*') {
  143. this._next();
  144. this._next();
  145. let depth = 1;
  146. while (this._position < this._text.length && depth > 0) {
  147. if (this.peek() === '/' && this.peek(1) === '*') {
  148. this._next();
  149. this._next();
  150. depth++;
  151. } else if (this.peek() === '*' && this.peek(1) === '/') {
  152. this._next();
  153. this._next();
  154. depth--;
  155. } else {
  156. this._next();
  157. }
  158. }
  159. continue;
  160. }
  161. break;
  162. }
  163. }
  164. _isDigit(c) {
  165. return c !== null && /[0-9]/.test(c);
  166. }
  167. _isIdentifierStart(c) {
  168. return c !== null && /[a-zA-Z_]/.test(c);
  169. }
  170. _isIdentifierChar(c) {
  171. return c !== null && /[a-zA-Z0-9_]/.test(c);
  172. }
  173. _readString(location) {
  174. let value = '';
  175. this._next(); // opening "
  176. while (this._position < this._text.length && this.peek() !== '"') {
  177. if (this.peek() === '\\') {
  178. this._next();
  179. const c = this.peek();
  180. switch (c) {
  181. case 'n': value += '\n'; break;
  182. case 't': value += '\t'; break;
  183. case 'r': value += '\r'; break;
  184. case '\\': value += '\\'; break;
  185. case '"': value += '"'; break;
  186. default: value += c; break;
  187. }
  188. this._next();
  189. } else {
  190. value += this.peek();
  191. this._next();
  192. }
  193. }
  194. if (this.peek() === '"') {
  195. this._next();
  196. }
  197. return new tablegen.Token('string', value, location);
  198. }
  199. _readCodeBlock(location) {
  200. let value = '';
  201. this._next(); // [
  202. this._next(); // {
  203. let depth = 1;
  204. let lineStart = true;
  205. let lineContent = '';
  206. while (this._position < this._text.length && depth > 0) {
  207. const c = this.peek();
  208. const next = this.peek(1);
  209. if (c === '[' && next === '{') {
  210. const trimmedLine = lineContent.trim();
  211. if (lineStart || trimmedLine === '' || /^(let|def|class)\s/.test(trimmedLine)) {
  212. depth++;
  213. }
  214. value += c;
  215. this._next();
  216. value += next;
  217. this._next();
  218. lineContent += c + next;
  219. } else if (c === '}' && next === ']') {
  220. depth--;
  221. if (depth === 0) {
  222. this._next();
  223. this._next();
  224. break;
  225. }
  226. value += c;
  227. this._next();
  228. value += next;
  229. this._next();
  230. lineContent += c + next;
  231. } else {
  232. if (c === '\n') {
  233. lineStart = true;
  234. lineContent = '';
  235. } else if (c !== ' ' && c !== '\t') {
  236. lineStart = false;
  237. }
  238. if (c !== '\n') {
  239. lineContent += c;
  240. }
  241. value += c;
  242. this._next();
  243. }
  244. }
  245. return new tablegen.Token('code', value.trim(), location);
  246. }
  247. _readNumber(location) {
  248. let value = '';
  249. if (this.peek() === '-') {
  250. value += this.peek();
  251. this._next();
  252. }
  253. // Hexadecimal
  254. if (this.peek() === '0' && this.peek(1) === 'x') {
  255. value += this.peek();
  256. this._next();
  257. value += this.peek();
  258. this._next();
  259. while (this._position < this._text.length && /[0-9a-fA-F]/.test(this.peek())) {
  260. value += this.peek();
  261. this._next();
  262. }
  263. return new tablegen.Token('number', parseInt(value, 16), location);
  264. }
  265. // Binary
  266. if (this.peek() === '0' && this.peek(1) === 'b') {
  267. value += this.peek();
  268. this._next();
  269. value += this.peek();
  270. this._next();
  271. while (this._position < this._text.length && /[01]/.test(this.peek())) {
  272. value += this.peek();
  273. this._next();
  274. }
  275. return new tablegen.Token('number', parseInt(value.substring(2), 2), location);
  276. }
  277. // Decimal
  278. while (this._position < this._text.length && this._isDigit(this.peek())) {
  279. value += this.peek();
  280. this._next();
  281. }
  282. return new tablegen.Token('number', parseInt(value, 10), location);
  283. }
  284. _readIdentifier(location) {
  285. let value = '';
  286. while (this._position < this._text.length && this._isIdentifierChar(this.peek())) {
  287. value += this.peek();
  288. this._next();
  289. }
  290. while (this.peek() === '.' && this._isIdentifierStart(this.peek(1))) {
  291. value += this.peek(); // add dot
  292. this._next();
  293. while (this._position < this._text.length && this._isIdentifierChar(this.peek())) {
  294. value += this.peek();
  295. this._next();
  296. }
  297. }
  298. const type = this._keywords.has(value) ? 'keyword' : 'id';
  299. return new tablegen.Token(type, value, location);
  300. }
  301. };
  302. tablegen.Value = class {
  303. constructor(type, value) {
  304. this.type = type; // 'int', 'string', 'bit', 'bits', 'list', 'dag', 'code', 'def'
  305. this.value = value;
  306. }
  307. };
  308. tablegen.DAG = class {
  309. constructor(operator, operands) {
  310. this.operator = operator; // string or Value
  311. this.operands = operands; // array of {value, name}
  312. }
  313. };
  314. tablegen.Type = class {
  315. constructor(name) {
  316. this.name = name;
  317. this.args = [];
  318. }
  319. toString() {
  320. if (this.args.length === 0) {
  321. return this.name;
  322. }
  323. return `${this.name}<${this.args.map((a) => a.toString()).join(', ')}>`;
  324. }
  325. };
  326. tablegen.RecordVal = class {
  327. constructor(name, type, value) {
  328. this.name = name;
  329. this.type = type; // tablegen.Type
  330. this.value = value; // tablegen.Value or null
  331. }
  332. };
  333. tablegen.Record = class {
  334. constructor(name, parser = null) {
  335. this.name = name;
  336. this.parents = [];
  337. this.fields = new Map();
  338. this.templateArgs = [];
  339. this.templateBindings = new Map(); // parameter name -> bound value
  340. this.location = null;
  341. this.parser = parser;
  342. }
  343. getValue(name) {
  344. return this.fields.get(name) || null;
  345. }
  346. // Unified resolution function that handles both parent field copying and post-inheritance resolution
  347. // Inspired by LLVM's Resolver pattern to avoid code duplication
  348. _resolveInit(init, resolver, visited = new Set()) {
  349. if (!init || typeof init !== 'object') {
  350. return init;
  351. }
  352. const key = JSON.stringify(init);
  353. if (visited.has(key)) {
  354. return init;
  355. }
  356. visited.add(key);
  357. // Resolve def/id type references (template parameters or field access)
  358. if ((init.type === 'def' || init.type === 'id') && typeof init.value === 'string') {
  359. // Handle field access syntax (e.g., "meta.mnemonic" or "addrKind.name")
  360. if (init.value.includes('.')) {
  361. const [baseName, ...fieldParts] = init.value.split('.');
  362. // Try to resolve the base
  363. let current = resolver.resolveReference(baseName);
  364. if (current) {
  365. // Walk through field access chain
  366. for (const fieldName of fieldParts) {
  367. // Handle def reference
  368. if (current && current.type === 'def' && typeof current.value === 'string') {
  369. const defName = current.value;
  370. const def = this.parser.getDef(defName) || this.parser.getClass(defName);
  371. if (def) {
  372. const field = def.getValue(fieldName);
  373. if (field && field.value) {
  374. current = field.value;
  375. } else {
  376. return init; // Field not found
  377. }
  378. } else {
  379. return init; // Def not found
  380. }
  381. } else if (current && current.type === 'dag' && current.value) {
  382. // Handle DAG (anonymous class instantiation)
  383. const className = current.value.operator;
  384. const templateArgs = current.value.operands.map((op) => op.value);
  385. // Instantiate an anonymous class with template arguments
  386. // Used for resolving field access like meta.mnemonic where meta is ROCDL_TrLoadOpMeta<...>
  387. let instantiated = null;
  388. const baseClass = this.parser.getClass(className);
  389. if (baseClass) {
  390. // Create an anonymous record that inherits from the class
  391. instantiated = new tablegen.Record(`<anonymous ${className}>`, this.parser);
  392. instantiated.parents = [{ name: className, args: templateArgs }];
  393. this.parser.addSubClass(instantiated);
  394. instantiated.resolveReferences();
  395. }
  396. if (instantiated) {
  397. const field = instantiated.getValue(fieldName);
  398. if (field && field.value) {
  399. current = field.value;
  400. } else {
  401. return init;
  402. }
  403. } else {
  404. return init;
  405. }
  406. } else {
  407. return init; // Can't resolve further
  408. }
  409. }
  410. // Successfully resolved the field access chain
  411. return this._resolveInit(current, resolver, visited);
  412. }
  413. } else {
  414. // Simple reference without dots
  415. const resolved = resolver.resolveReference(init.value);
  416. if (resolved) {
  417. return this._resolveInit(resolved, resolver, visited);
  418. }
  419. }
  420. }
  421. // Recursively resolve nested structures
  422. if (init.type === 'dag' && init.value) {
  423. return {
  424. type: 'dag',
  425. value: {
  426. operator: init.value.operator,
  427. operands: init.value.operands.map((op) => ({
  428. value: this._resolveInit(op.value, resolver, visited),
  429. name: op.name
  430. }))
  431. }
  432. };
  433. }
  434. if (init.type === 'list' && Array.isArray(init.value)) {
  435. return {
  436. type: 'list',
  437. value: init.value.map((v) => this._resolveInit(v, resolver, visited))
  438. };
  439. }
  440. if (init.type === 'bang' && init.value) {
  441. return {
  442. type: 'bang',
  443. value: {
  444. op: init.value.op,
  445. args: init.value.args.map((arg) => this._resolveInit(arg, resolver, visited))
  446. }
  447. };
  448. }
  449. if (init.type === 'concat' && Array.isArray(init.value)) {
  450. const resolvedParts = init.value.map((v) => this._resolveInit(v, resolver, visited));
  451. // Flatten nested concats recursively: if a resolved part is itself a concat,
  452. // extract its parts to avoid nested concat structures that cause double evaluation
  453. const flattenedParts = [];
  454. const flattenConcat = (part) => {
  455. if (part && part.type === 'concat' && Array.isArray(part.value)) {
  456. for (const subPart of part.value) {
  457. flattenConcat(subPart);
  458. }
  459. } else {
  460. flattenedParts.push(part);
  461. }
  462. };
  463. for (const part of resolvedParts) {
  464. flattenConcat(part);
  465. }
  466. return {
  467. type: 'concat',
  468. value: flattenedParts
  469. };
  470. }
  471. // For other types, return shallow copy for parent field copying, as-is for post-inheritance
  472. return resolver.shouldCopy ? { ...init } : init;
  473. }
  474. // Helper to deep copy a field and resolve parameter references in a specific context
  475. _copyAndResolveField(field, bindings, parentClass) {
  476. const resolver = {
  477. shouldCopy: true,
  478. resolveReference: (name) => bindings.get(name) || parentClass.templateBindings.get(name) || null
  479. };
  480. return {
  481. name: field.name,
  482. type: field.type,
  483. value: this._resolveInit(field.value, resolver)
  484. };
  485. }
  486. // Resolve template parameter references in field values
  487. // This matches C++ Record::resolveReferences() behavior
  488. // After inheriting from a templated class, substitute all template parameter
  489. // references in field values with their bound values from templateBindings
  490. resolveReferences() {
  491. if (this.templateBindings.size === 0) {
  492. return; // No template parameters to substitute
  493. }
  494. const findTemplateBinding = (paramName, record = this, visited = new Set()) => {
  495. if (visited.has(record.name)) {
  496. return null;
  497. }
  498. visited.add(record.name);
  499. if (record.templateBindings.has(paramName)) {
  500. return record.templateBindings.get(paramName);
  501. }
  502. for (const parent of record.parents) {
  503. const parentClass = this.parser.classes.get(parent.name);
  504. if (parentClass) {
  505. const paramIndex = parentClass.templateArgs.findIndex((arg) => arg.name === paramName);
  506. if (paramIndex !== -1 && parent.args && parent.args[paramIndex]) {
  507. return parent.args[paramIndex];
  508. }
  509. const binding = findTemplateBinding(paramName, parentClass, visited);
  510. if (binding) {
  511. return binding;
  512. }
  513. }
  514. }
  515. return null;
  516. };
  517. const resolver = {
  518. shouldCopy: false,
  519. resolveReference: (name) => {
  520. const binding = findTemplateBinding(name);
  521. if (binding) {
  522. return binding;
  523. }
  524. const field = this.getValue(name);
  525. if (field && field.value) {
  526. return field.value;
  527. }
  528. return null;
  529. }
  530. };
  531. for (const [, field] of this.fields) {
  532. if (field.value) {
  533. const resolved = this._resolveInit(field.value, resolver);
  534. if (resolved !== field.value) {
  535. field.value = resolved;
  536. }
  537. }
  538. }
  539. }
  540. getValueAsString(fieldName) {
  541. const field = this.getValue(fieldName);
  542. if (!field || !field.value) {
  543. return null;
  544. }
  545. const evaluated = this.evaluateValue(field.value);
  546. if (typeof evaluated === 'string') {
  547. return evaluated;
  548. }
  549. return null;
  550. }
  551. getValueAsBit(fieldName) {
  552. const field = this.getValue(fieldName);
  553. if (!field || !field.value) {
  554. return null;
  555. }
  556. const evaluated = this.evaluateValue(field.value);
  557. if (typeof evaluated === 'boolean') {
  558. return evaluated;
  559. }
  560. if (typeof evaluated === 'number') {
  561. return evaluated !== 0;
  562. }
  563. return null;
  564. }
  565. getValueAsDag(fieldName) {
  566. const field = this.getValue(fieldName);
  567. if (!field || !field.value) {
  568. return null;
  569. }
  570. const evaluated = this.evaluateValue(field.value);
  571. if (evaluated && typeof evaluated === 'object' && evaluated.operator) {
  572. return evaluated;
  573. }
  574. return null;
  575. }
  576. getValueAsDef(fieldName) {
  577. const field = this.getValue(fieldName);
  578. if (!field || !field.value) {
  579. return null;
  580. }
  581. if (field.value.type === 'def' && typeof field.value.value === 'string') {
  582. const defName = field.value.value;
  583. return this.parser.getDef(defName) || this.parser.getClass(defName);
  584. }
  585. return null;
  586. }
  587. isEnumAttr() {
  588. const enumBaseClasses = [
  589. 'EnumAttr', // Wrapper for dialect-specific enums
  590. 'IntEnumAttr', 'I32EnumAttr', 'I64EnumAttr',
  591. 'BitEnumAttr', 'I8BitEnumAttr', 'I16BitEnumAttr', 'I32BitEnumAttr', 'I64BitEnumAttr',
  592. 'IntEnum', 'I32IntEnum', 'I64IntEnum', // Integer enum types
  593. 'BitEnum', 'I8BitEnum', 'I16BitEnum', 'I32BitEnum', 'I64BitEnum' // Bit enum types
  594. ];
  595. const checkParents = (record, visited = new Set()) => {
  596. if (record && !visited.has(record.name)) {
  597. visited.add(record.name);
  598. if (enumBaseClasses.includes(record.name)) {
  599. return true;
  600. }
  601. for (const parent of record.parents) {
  602. const parentClass = this.parser.getClass(parent.name);
  603. if (parentClass && checkParents(parentClass, visited)) {
  604. return true;
  605. }
  606. }
  607. }
  608. return false;
  609. };
  610. return checkParents(this);
  611. }
  612. isEnumProp() {
  613. const enumPropBaseClasses = [
  614. 'EnumProp', // Property wrapping an enum
  615. 'EnumPropWithAttrForm' // Property with attribute form
  616. ];
  617. const checkParents = (record, visited = new Set()) => {
  618. if (record && !visited.has(record.name)) {
  619. visited.add(record.name);
  620. if (enumPropBaseClasses.includes(record.name)) {
  621. return true;
  622. }
  623. for (const parent of record.parents) {
  624. const parentClass = this.parser.getClass(parent.name);
  625. if (parentClass && checkParents(parentClass, visited)) {
  626. return true;
  627. }
  628. }
  629. }
  630. return false;
  631. };
  632. return checkParents(this);
  633. }
  634. // Get enum cases from an enum attribute or property definition
  635. getEnumCases() {
  636. if (!this.isEnumAttr() && !this.isEnumProp()) {
  637. return null;
  638. }
  639. // Handle EnumProp - the first argument is the underlying enum
  640. if (this.isEnumProp()) {
  641. for (const parent of this.parents) {
  642. if (parent.name === 'EnumProp' || parent.name === 'EnumPropWithAttrForm') {
  643. if (parent.args && parent.args.length >= 1) {
  644. const [enumInfoArg] = parent.args;
  645. if (enumInfoArg && enumInfoArg.type === 'def' && typeof enumInfoArg.value === 'string') {
  646. const enumName = enumInfoArg.value;
  647. const underlyingEnum = this.parser.getDef(enumName) || this.parser.getClass(enumName);
  648. if (underlyingEnum) {
  649. // Recursively get cases from the underlying enum
  650. return underlyingEnum.getEnumCases();
  651. }
  652. }
  653. }
  654. }
  655. // Recursively search parent classes
  656. const parentClass = this.parser.getClass(parent.name);
  657. if (parentClass && parentClass.isEnumProp()) {
  658. const cases = parentClass.getEnumCases();
  659. if (cases) {
  660. return cases;
  661. }
  662. }
  663. }
  664. }
  665. // Helper to search for EnumAttr in the parent hierarchy
  666. const findEnumAttrParent = (record, visited = new Set()) => {
  667. if (!record || visited.has(record.name)) {
  668. return null;
  669. }
  670. visited.add(record.name);
  671. for (const parent of record.parents) {
  672. if (parent.name === 'EnumAttr') {
  673. return parent;
  674. }
  675. // Recursively search parent classes
  676. const parentClass = this.parser.getClass(parent.name);
  677. if (parentClass) {
  678. const found = findEnumAttrParent(parentClass, visited);
  679. if (found) {
  680. return found;
  681. }
  682. }
  683. }
  684. return null;
  685. };
  686. const enumAttrParent = findEnumAttrParent(this);
  687. // Pattern 1a: EnumAttr<Dialect, EnumInfo (as def), name>
  688. // The 2nd argument is a reference to the underlying enum (e.g., GPU_Dimension)
  689. if (enumAttrParent && enumAttrParent.args && enumAttrParent.args.length >= 2) {
  690. const [dialectArg, enumInfoArg] = enumAttrParent.args;
  691. if (enumInfoArg && enumInfoArg.type === 'def' && typeof enumInfoArg.value === 'string') {
  692. // Get the expected namespace from the dialect
  693. let expectedNamespace = null;
  694. if (dialectArg && dialectArg.type === 'def') {
  695. const dialectDef = this.parser.getDef(dialectArg.value) || this.parser.getClass(dialectArg.value);
  696. if (dialectDef) {
  697. expectedNamespace = dialectDef.getValueAsString('cppNamespace');
  698. }
  699. }
  700. // Find the enum with the matching name and namespace
  701. // Try all defs with this name in case of conflicts (different namespaces)
  702. const enumName = enumInfoArg.value;
  703. let underlyingEnum = null;
  704. if (expectedNamespace) {
  705. // Normalize namespace for comparison (handle both "vector" and "::mlir::vector" formats)
  706. const normalizeNamespace = (ns) => {
  707. if (!ns) {
  708. return null;
  709. }
  710. // Remove leading :: and extract the last component
  711. const parts = ns.replace(/^::/, '').split('::');
  712. return parts[parts.length - 1];
  713. };
  714. const normalizedExpected = normalizeNamespace(expectedNamespace);
  715. // Search through all defs to find one with matching name AND namespace
  716. for (const def of this.parser.defs) {
  717. if (def.name === enumName) {
  718. const defNamespace = def.getValueAsString('cppNamespace');
  719. const normalizedDef = normalizeNamespace(defNamespace);
  720. if (normalizedDef === normalizedExpected) {
  721. underlyingEnum = def;
  722. break;
  723. }
  724. }
  725. }
  726. }
  727. // Fallback to regular lookup if namespace matching fails
  728. if (!underlyingEnum) {
  729. underlyingEnum = this.parser.getDef(enumName) || this.parser.getClass(enumName);
  730. }
  731. if (underlyingEnum) {
  732. return underlyingEnum.getEnumCases();
  733. }
  734. }
  735. // Pattern 1b: EnumAttr<Dialect, EnumInfo (as DAG), name>
  736. // The 2nd argument is a DAG that instantiates an enum class template
  737. // e.g., EnumAttr<SPIRV_Dialect, SPIRV_I32Enum<"Scope", "desc", [cases]>, "scope">
  738. if (enumInfoArg && enumInfoArg.type === 'dag' && enumInfoArg.value) {
  739. // The DAG operator is the enum class template (e.g., SPIRV_I32Enum)
  740. // We need to find the actual cases by looking at this record's parent args
  741. // which should have the instantiated template parameters
  742. // Search through this record's parents to find one with the cases list
  743. for (const parent of this.parents) {
  744. if (parent.args && parent.args.length >= 3) {
  745. // Look for a list argument that contains enum case defs
  746. for (const arg of parent.args) {
  747. if (arg.type === 'list' && Array.isArray(arg.value)) {
  748. // Check if this looks like an enum case list
  749. const [firstItem] = arg.value;
  750. if (firstItem && firstItem.type === 'def') {
  751. // Try to extract cases from this list
  752. const cases = [];
  753. for (const caseValue of arg.value) {
  754. if (caseValue.type === 'def' && typeof caseValue.value === 'string') {
  755. const caseDef = this.parser.getDef(caseValue.value) || this.parser.getClass(caseValue.value);
  756. if (caseDef) {
  757. const str = caseDef.getValueAsString('str');
  758. if (str) {
  759. cases.push(str);
  760. }
  761. }
  762. }
  763. }
  764. if (cases.length > 0) {
  765. return cases;
  766. }
  767. }
  768. }
  769. }
  770. }
  771. }
  772. }
  773. }
  774. // Pattern 2: I64EnumAttr<name, summary, [cases]>
  775. // Cases are in the 3rd template argument
  776. for (const parent of this.parents) {
  777. // The 3rd argument should be the cases list
  778. if (parent.args && parent.args.length >= 3) {
  779. const [,,casesArg] = parent.args;
  780. if (casesArg && casesArg.type === 'list' && Array.isArray(casesArg.value)) {
  781. const cases = [];
  782. for (const caseValue of casesArg.value) {
  783. // Each case can be either a DAG or a def reference
  784. if (caseValue.type === 'dag' && caseValue.value) {
  785. // DAG format: I64EnumAttrCase<"symbol", value, "string">
  786. // The string representation is in the 3rd operand, or 1st if only 2 operands
  787. const operands = caseValue.value.operands;
  788. if (operands && operands.length > 0) {
  789. // Try the 3rd operand first (string), fall back to 1st (symbol)
  790. const strOperand = operands.length >= 3 ? operands[2] : operands[0];
  791. if (strOperand && strOperand.value) {
  792. const str = this.evaluateValue(strOperand.value);
  793. if (str && typeof str === 'string') {
  794. cases.push(str);
  795. }
  796. }
  797. }
  798. } else if (caseValue.type === 'def' && typeof caseValue.value === 'string') {
  799. // Def reference format
  800. const caseDef = this.parser.getDef(caseValue.value) || this.parser.getClass(caseValue.value);
  801. if (caseDef) {
  802. const str = caseDef.getValueAsString('str');
  803. if (str) {
  804. cases.push(str);
  805. }
  806. }
  807. }
  808. }
  809. return cases.length > 0 ? cases : null;
  810. }
  811. }
  812. }
  813. // Pattern 3: LLVM_EnumAttr<name, cppType, summary, [cases]>
  814. // Cases are in the 4th template argument
  815. for (const parent of this.parents) {
  816. if (parent.args && parent.args.length >= 4) {
  817. const [,,,casesArg] = parent.args;
  818. if (casesArg && casesArg.type === 'list' && Array.isArray(casesArg.value)) {
  819. const cases = [];
  820. for (const caseValue of casesArg.value) {
  821. // Each case is a def reference
  822. if (caseValue.type === 'def' && typeof caseValue.value === 'string') {
  823. const caseDef = this.parser.getDef(caseValue.value) || this.parser.getClass(caseValue.value);
  824. if (caseDef) {
  825. const str = caseDef.getValueAsString('str');
  826. if (str) {
  827. cases.push(str);
  828. }
  829. }
  830. }
  831. }
  832. return cases.length > 0 ? cases : null;
  833. }
  834. }
  835. }
  836. return null;
  837. }
  838. evaluateValue(value) {
  839. if (!value) {
  840. return null;
  841. }
  842. // Handle named values (e.g., { name: 'clauses', value: { type: 'list', ... } })
  843. // These come from named arguments in template instantiation
  844. if (!value.type && value.name && value.value) {
  845. return this.evaluateValue(value.value);
  846. }
  847. switch (value.type) {
  848. case 'string':
  849. return value.value.replace(/^"|"$/g, '');
  850. case 'code':
  851. return value.value;
  852. case 'int':
  853. return parseInt(value.value, 10);
  854. case 'bit':
  855. return value.value === 'true' || value.value === '1';
  856. case 'list':
  857. return value.value.map((v) => this.evaluateValue(v));
  858. case 'concat': {
  859. const parts = value.value.map((v) => this.evaluateValue(v)).filter((v) => v !== null && v !== undefined && v !== '');
  860. return parts.join('');
  861. }
  862. case 'id': {
  863. const fieldName = value.value;
  864. if (this.templateBindings.has(fieldName)) {
  865. return this.evaluateValue(this.templateBindings.get(fieldName));
  866. }
  867. const field = this.getValue(fieldName);
  868. if (field && field.value) {
  869. return this.evaluateValue(field.value);
  870. }
  871. return null;
  872. }
  873. case 'def': {
  874. const defName = typeof value.value === 'string' ? value.value : value.value.value;
  875. if (defName.includes('.')) {
  876. const parts = defName.split('.');
  877. const [baseName, ...fieldPath] = parts;
  878. let baseValue = null;
  879. if (this.templateBindings.has(baseName)) {
  880. baseValue = this.evaluateValue(this.templateBindings.get(baseName));
  881. } else {
  882. const field = this.getValue(baseName);
  883. if (field && field.value) {
  884. baseValue = this.evaluateValue(field.value);
  885. }
  886. }
  887. if (typeof baseValue === 'string') {
  888. const def = this.parser.getDef(baseValue) || this.parser.getClass(baseValue);
  889. if (def) {
  890. let current = def;
  891. for (const fieldName of fieldPath) {
  892. const field = current.getValue(fieldName);
  893. if (!field || !field.value) {
  894. return null;
  895. }
  896. const evaluated = current.evaluateValue(field.value);
  897. // If it's another def name, continue navigation
  898. if (typeof evaluated === 'string' && (this.parser.getDef(evaluated) || this.parser.getClass(evaluated))) {
  899. current = this.parser.getDef(evaluated) || this.parser.getClass(evaluated);
  900. } else {
  901. return evaluated;
  902. }
  903. }
  904. return null;
  905. }
  906. }
  907. return null;
  908. }
  909. if (this.templateBindings.has(defName)) {
  910. return this.evaluateValue(this.templateBindings.get(defName));
  911. }
  912. const field = this.getValue(defName);
  913. if (field && field.value) {
  914. return this.evaluateValue(field.value);
  915. }
  916. const def = this.parser.getDef(defName) || this.parser.getClass(defName);
  917. return def ? defName : null;
  918. }
  919. case 'bang': {
  920. const { op, args } = value.value;
  921. switch (op) {
  922. case 'if': {
  923. if (args.length < 2) {
  924. return null;
  925. }
  926. const condition = this.evaluateValue(args[0]);
  927. if (condition) {
  928. return args.length > 1 ? this.evaluateValue(args[1]) : null;
  929. }
  930. return args.length > 2 ? this.evaluateValue(args[2]) : null;
  931. }
  932. case 'empty': {
  933. if (args.length < 1) {
  934. return true;
  935. }
  936. const val = this.evaluateValue(args[0]);
  937. if (Array.isArray(val)) {
  938. return val.length === 0;
  939. }
  940. if (typeof val === 'string') {
  941. return val.length === 0;
  942. }
  943. return val === null || val === undefined;
  944. }
  945. case 'interleave': {
  946. if (args.length < 2) {
  947. return '';
  948. }
  949. const list = this.evaluateValue(args[0]);
  950. const separator = this.evaluateValue(args[1]);
  951. if (!Array.isArray(list)) {
  952. return '';
  953. }
  954. return list.filter((x) => x !== null && x !== '').join(separator || '');
  955. }
  956. case 'not':
  957. if (args.length < 1) {
  958. return true;
  959. }
  960. return !this.evaluateValue(args[0]);
  961. case 'or':
  962. for (const arg of args) {
  963. if (this.evaluateValue(arg)) {
  964. return true;
  965. }
  966. }
  967. return false;
  968. case 'and':
  969. for (const arg of args) {
  970. if (!this.evaluateValue(arg)) {
  971. return false;
  972. }
  973. }
  974. return true;
  975. case 'foldl': {
  976. // !foldl(init, list, acc, item, expr)
  977. // Fold left: iterate over list, accumulating results
  978. if (args.length < 5) {
  979. return null;
  980. }
  981. let accumulator = this.evaluateValue(args[0]); // init value
  982. const list = this.evaluateValue(args[1]); // list to fold over
  983. // args[2] is the accumulator variable name (not evaluated)
  984. // args[3] is the item variable name (not evaluated)
  985. // args[4] is the expression to evaluate
  986. if (!Array.isArray(list)) {
  987. return accumulator;
  988. }
  989. for (const item of list) {
  990. const accName = args[2].value; // variable name
  991. const itemName = args[3].value; // variable name
  992. const prevAcc = this.fields.get(accName);
  993. const prevItem = this.fields.get(itemName);
  994. let accValue = null;
  995. if (accumulator && typeof accumulator === 'object' && accumulator.operator) {
  996. // It's a DAG object
  997. accValue = new tablegen.Value('dag', accumulator);
  998. } else if (Array.isArray(accumulator)) {
  999. // It's a list
  1000. accValue = new tablegen.Value('list', accumulator);
  1001. } else if (typeof accumulator === 'string') {
  1002. accValue = new tablegen.Value('string', accumulator);
  1003. } else if (typeof accumulator === 'number') {
  1004. accValue = new tablegen.Value('int', accumulator);
  1005. } else {
  1006. accValue = accumulator;
  1007. }
  1008. this.fields.set(accName, new tablegen.RecordVal(accName, null, accValue));
  1009. let itemValue = item;
  1010. if (typeof item === 'string') {
  1011. itemValue = new tablegen.Value('def', item);
  1012. } else if (typeof item === 'number') {
  1013. itemValue = new tablegen.Value('int', item);
  1014. } else if (typeof item === 'boolean') {
  1015. itemValue = new tablegen.Value('bit', item ? '1' : '0');
  1016. } else if (item && typeof item === 'object' && !item.type) {
  1017. itemValue = new tablegen.Value('dag', item);
  1018. }
  1019. this.fields.set(itemName, new tablegen.RecordVal(itemName, null, itemValue));
  1020. accumulator = this.evaluateValue(args[4]);
  1021. if (prevAcc) {
  1022. this.fields.set(accName, prevAcc);
  1023. } else {
  1024. this.fields.delete(accName);
  1025. }
  1026. if (prevItem) {
  1027. this.fields.set(itemName, prevItem);
  1028. } else {
  1029. this.fields.delete(itemName);
  1030. }
  1031. }
  1032. return accumulator;
  1033. }
  1034. case 'foreach': {
  1035. // !foreach(item, list, expr)
  1036. // Map: iterate over list, transforming each item
  1037. if (args.length < 3) {
  1038. return [];
  1039. }
  1040. const itemName = args[0].value;
  1041. const list = this.evaluateValue(args[1]);
  1042. const results = [];
  1043. if (Array.isArray(list)) {
  1044. for (const item of list) {
  1045. const prevItem = this.fields.get(itemName);
  1046. let itemValue = item;
  1047. if (typeof item === 'string') {
  1048. itemValue = new tablegen.Value('def', item);
  1049. } else if (typeof item === 'number') {
  1050. itemValue = new tablegen.Value('int', item);
  1051. } else if (typeof item === 'boolean') {
  1052. itemValue = new tablegen.Value('bit', item ? '1' : '0');
  1053. } else if (item && typeof item === 'object' && !item.type) {
  1054. itemValue = new tablegen.Value('dag', item);
  1055. }
  1056. this.fields.set(itemName, new tablegen.RecordVal(itemName, null, itemValue));
  1057. const result = this.evaluateValue(args[2]);
  1058. results.push(result);
  1059. if (prevItem) {
  1060. this.fields.set(itemName, prevItem);
  1061. } else {
  1062. this.fields.delete(itemName);
  1063. }
  1064. }
  1065. }
  1066. return results;
  1067. }
  1068. case 'filter': {
  1069. // !filter(item, list, predicate)
  1070. // Filter: keep items where predicate is true
  1071. if (args.length < 3) {
  1072. return [];
  1073. }
  1074. const itemName = args[0].value;
  1075. const list = this.evaluateValue(args[1]);
  1076. const results = [];
  1077. if (Array.isArray(list)) {
  1078. for (const item of list) {
  1079. const prevItem = this.fields.get(itemName);
  1080. // Wrap item in a Value so it can be used in expressions
  1081. let itemValue = item;
  1082. if (typeof item === 'string') {
  1083. // If it's a def name, wrap it as a 'def' Value
  1084. itemValue = new tablegen.Value('def', item);
  1085. } else if (typeof item === 'number') {
  1086. itemValue = new tablegen.Value('int', item);
  1087. } else if (typeof item === 'boolean') {
  1088. itemValue = new tablegen.Value('bit', item ? '1' : '0');
  1089. } else if (item && typeof item === 'object' && !item.type) {
  1090. // If it's a raw object (like a DAG), wrap it
  1091. itemValue = new tablegen.Value('dag', item);
  1092. }
  1093. // If item is already a Value, use it as is
  1094. this.fields.set(itemName, new tablegen.RecordVal(itemName, null, itemValue));
  1095. const keep = this.evaluateValue(args[2]);
  1096. if (keep) {
  1097. results.push(item);
  1098. }
  1099. if (prevItem) {
  1100. this.fields.set(itemName, prevItem);
  1101. } else {
  1102. this.fields.delete(itemName);
  1103. }
  1104. }
  1105. }
  1106. return results;
  1107. }
  1108. case 'con': {
  1109. // !con(dag1, dag2, ...)
  1110. // Concatenate dags - merge operands from multiple dags
  1111. if (args.length === 0) {
  1112. return new tablegen.DAG('ins', []);
  1113. }
  1114. let operator = 'ins';
  1115. const allOperands = [];
  1116. for (const arg of args) {
  1117. let dagToProcess = null;
  1118. const evaluated = this.evaluateValue(arg);
  1119. if (evaluated && typeof evaluated === 'object') {
  1120. if (evaluated.operator && evaluated.operands) {
  1121. dagToProcess = evaluated;
  1122. } else if (evaluated.type === 'dag' && evaluated.value) {
  1123. dagToProcess = evaluated.value;
  1124. }
  1125. }
  1126. if (!dagToProcess && arg.type === 'dag') {
  1127. dagToProcess = arg.value;
  1128. }
  1129. if (dagToProcess && dagToProcess.operands) {
  1130. if (operator === 'ins' && dagToProcess.operator) {
  1131. operator = dagToProcess.operator;
  1132. }
  1133. allOperands.push(...dagToProcess.operands);
  1134. }
  1135. }
  1136. return new tablegen.DAG(operator, allOperands);
  1137. }
  1138. case 'listconcat': {
  1139. // !listconcat(list1, list2, ...)
  1140. // Concatenate multiple lists into one
  1141. const result = [];
  1142. for (const arg of args) {
  1143. const list = this.evaluateValue(arg);
  1144. if (Array.isArray(list)) {
  1145. result.push(...list);
  1146. }
  1147. }
  1148. return result;
  1149. }
  1150. case 'listremove': {
  1151. // !listremove(list, items_to_remove)
  1152. // Remove all occurrences of items_to_remove from list
  1153. if (args.length < 2) {
  1154. return [];
  1155. }
  1156. const list = this.evaluateValue(args[0]);
  1157. const toRemove = this.evaluateValue(args[1]);
  1158. if (!Array.isArray(list)) {
  1159. return [];
  1160. }
  1161. const removeSet = new Set();
  1162. if (Array.isArray(toRemove)) {
  1163. for (const item of toRemove) {
  1164. removeSet.add(JSON.stringify(item));
  1165. }
  1166. } else {
  1167. removeSet.add(JSON.stringify(toRemove));
  1168. }
  1169. return list.filter((item) => !removeSet.has(JSON.stringify(item)));
  1170. }
  1171. case 'cast': {
  1172. // !cast<type>(value)
  1173. // Cast value to type - for now just convert to string
  1174. if (args.length < 1) {
  1175. return null;
  1176. }
  1177. const val = this.evaluateValue(args[0]);
  1178. if (val === null || val === undefined) {
  1179. return null;
  1180. }
  1181. return String(val);
  1182. }
  1183. case 'eq': {
  1184. // !eq(a, b)
  1185. // Return true if a equals b
  1186. if (args.length < 2) {
  1187. return false;
  1188. }
  1189. const a = this.evaluateValue(args[0]);
  1190. const b = this.evaluateValue(args[1]);
  1191. return a === b;
  1192. }
  1193. case 'ne': {
  1194. // !ne(a, b)
  1195. // Return true if a not equals b
  1196. if (args.length < 2) {
  1197. return false;
  1198. }
  1199. const a = this.evaluateValue(args[0]);
  1200. const b = this.evaluateValue(args[1]);
  1201. return a !== b;
  1202. }
  1203. case 'lt': {
  1204. // !lt(a, b)
  1205. // Return true if a < b
  1206. if (args.length < 2) {
  1207. return false;
  1208. }
  1209. const a = this.evaluateValue(args[0]);
  1210. const b = this.evaluateValue(args[1]);
  1211. return a < b;
  1212. }
  1213. case 'le': {
  1214. // !le(a, b)
  1215. // Return true if a <= b
  1216. if (args.length < 2) {
  1217. return false;
  1218. }
  1219. const a = this.evaluateValue(args[0]);
  1220. const b = this.evaluateValue(args[1]);
  1221. return a <= b;
  1222. }
  1223. case 'gt': {
  1224. // !gt(a, b)
  1225. // Return true if a > b
  1226. if (args.length < 2) {
  1227. return false;
  1228. }
  1229. const a = this.evaluateValue(args[0]);
  1230. const b = this.evaluateValue(args[1]);
  1231. return a > b;
  1232. }
  1233. case 'ge': {
  1234. // !ge(a, b)
  1235. // Return true if a >= b
  1236. if (args.length < 2) {
  1237. return false;
  1238. }
  1239. const a = this.evaluateValue(args[0]);
  1240. const b = this.evaluateValue(args[1]);
  1241. return a >= b;
  1242. }
  1243. case 'range': {
  1244. // !range(n) or !range(start, end) or !range(start, end, step)
  1245. // Generate a list of integers
  1246. if (args.length === 0) {
  1247. return [];
  1248. }
  1249. let start = 0;
  1250. let end = 0;
  1251. let step = 1;
  1252. if (args.length === 1) {
  1253. end = this.evaluateValue(args[0]);
  1254. } else if (args.length === 2) {
  1255. start = this.evaluateValue(args[0]);
  1256. end = this.evaluateValue(args[1]);
  1257. } else {
  1258. start = this.evaluateValue(args[0]);
  1259. end = this.evaluateValue(args[1]);
  1260. step = this.evaluateValue(args[2]);
  1261. }
  1262. const result = [];
  1263. if (step > 0) {
  1264. for (let i = start; i < end; i += step) {
  1265. result.push(i);
  1266. }
  1267. } else if (step < 0) {
  1268. for (let i = start; i > end; i += step) {
  1269. result.push(i);
  1270. }
  1271. }
  1272. return result;
  1273. }
  1274. case 'listsplat': {
  1275. // !listsplat(element, n)
  1276. // Create a list with n copies of element
  1277. if (args.length < 2) {
  1278. return [];
  1279. }
  1280. const [element] = args; // Don't evaluate yet, keep as Value
  1281. const count = this.evaluateValue(args[1]);
  1282. const result = [];
  1283. for (let i = 0; i < count; i++) {
  1284. result.push(element);
  1285. }
  1286. return result;
  1287. }
  1288. case 'cond': {
  1289. // !cond(condition1: value1, condition2: value2, ..., true: defaultValue)
  1290. // Evaluate conditions in order, return first matching value
  1291. for (let i = 0; i < args.length; i++) {
  1292. const arg = args[i];
  1293. if (arg.condition) {
  1294. const condition = this.evaluateValue(arg.condition);
  1295. if (condition === true || condition === 1) {
  1296. return this.evaluateValue(arg.value);
  1297. }
  1298. }
  1299. }
  1300. return null;
  1301. }
  1302. case 'dag': {
  1303. // !dag(operator, operands_list, names_list)
  1304. // Construct a DAG from operator, operands, and names
  1305. if (args.length < 2) {
  1306. return new tablegen.DAG('ins', []);
  1307. }
  1308. const operatorArg = this.evaluateValue(args[0]);
  1309. const operator = typeof operatorArg === 'string' ? operatorArg : 'ins';
  1310. const operandsList = this.evaluateValue(args[1]);
  1311. const namesList = args.length > 2 ? this.evaluateValue(args[2]) : [];
  1312. const operands = [];
  1313. if (Array.isArray(operandsList)) {
  1314. for (let i = 0; i < operandsList.length; i++) {
  1315. const value = operandsList[i];
  1316. const name = Array.isArray(namesList) && i < namesList.length ? namesList[i] : '';
  1317. operands.push({ value, name });
  1318. }
  1319. }
  1320. return new tablegen.DAG(operator, operands);
  1321. }
  1322. default:
  1323. return null;
  1324. }
  1325. }
  1326. case 'dag':
  1327. return value.value;
  1328. case 'uninitialized':
  1329. return null;
  1330. default:
  1331. return null;
  1332. }
  1333. }
  1334. };
  1335. tablegen.Reader = class {
  1336. constructor() {
  1337. this._paths = [];
  1338. this._includes = new Set();
  1339. this._defs = new Map();
  1340. this.defs = [];
  1341. this.classes = new Map();
  1342. }
  1343. async parse(files, paths) {
  1344. this._paths = paths || [];
  1345. for (const file of files) {
  1346. // eslint-disable-next-line no-await-in-loop
  1347. await this._parseFile(file);
  1348. }
  1349. }
  1350. getDef(name) {
  1351. return this._defs.get(name);
  1352. }
  1353. getClass(name) {
  1354. return this.classes.get(name);
  1355. }
  1356. // Add a subclass to a record, processing template parameters and copying fields
  1357. // This mirrors LLVM's TGParser::AddSubClass behavior
  1358. addSubClass(record) {
  1359. // Track which fields were explicitly defined in the def via 'let' statements
  1360. // These should never be overwritten by parent class fields
  1361. const explicitFields = new Set(record.fields.keys());
  1362. // Step 1: Build initial template bindings for this record from its immediate parents
  1363. const recordBindings = new Map();
  1364. for (const parent of record.parents) {
  1365. const parentClass = this.classes.get(parent.name);
  1366. if (parentClass && parentClass.templateArgs && parentClass.templateArgs.length > 0) {
  1367. const templateArgs = parent.args || [];
  1368. for (let i = 0; i < parentClass.templateArgs.length; i++) {
  1369. const param = parentClass.templateArgs[i];
  1370. let boundValue = null;
  1371. // Check for named argument, positional argument, or default value
  1372. const namedArg = templateArgs.find((arg) => arg.name === param.name);
  1373. if (namedArg) {
  1374. boundValue = namedArg.value;
  1375. } else if (i < templateArgs.length) {
  1376. const arg = templateArgs[i];
  1377. boundValue = arg.name ? arg.value : arg;
  1378. } else if (param.defaultValue) {
  1379. boundValue = param.defaultValue;
  1380. }
  1381. if (boundValue) {
  1382. recordBindings.set(param.name, boundValue);
  1383. }
  1384. }
  1385. }
  1386. }
  1387. record.templateBindings = recordBindings;
  1388. // Step 2: Process parents and flatten fields
  1389. // Helper to resolve Init values using a set of bindings
  1390. // Uses record._resolveInit to avoid code duplication
  1391. const resolveInitValue = (value, bindings) => {
  1392. const resolver = {
  1393. shouldCopy: false,
  1394. resolveReference: (name) => bindings.get(name) || null
  1395. };
  1396. return record._resolveInit(value, resolver);
  1397. };
  1398. const processParent = (parent, currentBindings, visited = new Set()) => {
  1399. if (visited.has(parent.name)) {
  1400. return;
  1401. }
  1402. visited.add(parent.name);
  1403. const parentClass = this.classes.get(parent.name);
  1404. if (!parentClass) {
  1405. return;
  1406. }
  1407. const parentBindings = new Map();
  1408. if (parentClass.templateArgs && parent.args) {
  1409. for (let i = 0; i < parentClass.templateArgs.length && i < parent.args.length; i++) {
  1410. const paramName = parentClass.templateArgs[i].name;
  1411. const argValue = parent.args[i];
  1412. const resolvedArg = resolveInitValue(argValue, currentBindings);
  1413. parentBindings.set(paramName, resolvedArg);
  1414. }
  1415. }
  1416. for (const grandparent of parentClass.parents) {
  1417. processParent(grandparent, parentBindings, visited);
  1418. }
  1419. for (const [fieldName, field] of parentClass.fields) {
  1420. // Only protect fields that were explicitly defined in the def via 'let'
  1421. // Fields inherited from grandparents should be overwritten by parent class fields
  1422. if (explicitFields.has(fieldName)) {
  1423. // Check if the explicit field is empty/uninitialized - if so, copy from parent
  1424. const existingField = record.fields.get(fieldName);
  1425. const existingIsUninit = existingField.value?.type === 'uninitialized';
  1426. const existingIsEmptyDag = existingField.value?.type === 'dag' && existingField.value?.value?.operands?.length === 0;
  1427. const existingIsEmptyString = existingField.value?.type === 'string' && existingField.value?.value === '';
  1428. const existingIsFalseBit = existingField.value?.type === 'int' && existingField.value?.value === 0 && existingField.type?.name === 'bit';
  1429. if (existingIsUninit || existingIsEmptyDag || existingIsEmptyString || existingIsFalseBit) {
  1430. const resolvedField = record._copyAndResolveField(field, parentBindings, parentClass);
  1431. record.fields.set(fieldName, resolvedField);
  1432. }
  1433. // Otherwise, keep the explicit field (child's 'let' wins)
  1434. } else {
  1435. // Field is not explicit - copy from parent (overwrites grandparent values)
  1436. const resolvedField = record._copyAndResolveField(field, parentBindings, parentClass);
  1437. record.fields.set(fieldName, resolvedField);
  1438. }
  1439. }
  1440. };
  1441. for (const parent of record.parents) {
  1442. processParent(parent, recordBindings, new Set());
  1443. }
  1444. }
  1445. async access(file) {
  1446. try {
  1447. await fs.access(file);
  1448. return true;
  1449. } catch {
  1450. // continue regardless of error
  1451. }
  1452. return false;
  1453. }
  1454. async _parseFile(file) {
  1455. let location = null;
  1456. for (const current of this._paths) {
  1457. const test = path.join(current, file);
  1458. // eslint-disable-next-line no-await-in-loop
  1459. if (await this.access(test)) {
  1460. location = path.resolve(test);
  1461. break;
  1462. }
  1463. }
  1464. if (!location) {
  1465. throw new tablegen.Error(`File not found '${file}'.`);
  1466. }
  1467. if (!this._includes.has(location)) {
  1468. this._includes.add(location);
  1469. const content = await fs.readFile(location, 'utf-8');
  1470. this._tokenizer = new tablegen.Tokenizer(content, location);
  1471. while (!this._match('eof')) {
  1472. const token = this._tokenizer.current();
  1473. if (token.type === 'keyword') {
  1474. switch (token.value) {
  1475. case 'include':
  1476. // eslint-disable-next-line no-await-in-loop
  1477. await this._parseInclude();
  1478. break;
  1479. case 'class': this._parseClass(); break;
  1480. case 'def': this._parseDef(); break;
  1481. case 'defm': this._parseDefm(); break;
  1482. case 'let': this._parseLet(); break;
  1483. case 'multiclass': this._parseMulticlass(); break;
  1484. case 'defvar': this._parseDefvar(); break;
  1485. case 'defset': this._parseDefset(); break;
  1486. case 'foreach': this._parseForeach(); break;
  1487. default:
  1488. this._read();
  1489. break;
  1490. }
  1491. } else {
  1492. this._read();
  1493. }
  1494. }
  1495. }
  1496. }
  1497. async _parseInclude() {
  1498. this._read();
  1499. const file = this._expect('string');
  1500. const tokenizer = this._tokenizer;
  1501. await this._parseFile(file);
  1502. this._tokenizer = tokenizer;
  1503. }
  1504. _parseClass() {
  1505. this._read();
  1506. let name = null;
  1507. if (this._match('id')) {
  1508. name = this._expect('id');
  1509. } else if (this._match('number')) {
  1510. name = String(this._expect('number'));
  1511. } else {
  1512. throw new tablegen.Error(`Expected class name but got '${this._tokenizer.current().type}' at ${this._tokenizer.location()}`);
  1513. }
  1514. const record = new tablegen.Record(name, this);
  1515. record.location = this._tokenizer.location();
  1516. if (this._match('<')) {
  1517. record.templateArgs = this._parseTemplateParams();
  1518. }
  1519. if (this._match(':')) {
  1520. record.parents = this._parseParentClassList();
  1521. }
  1522. // Don't process parent classes for class definitions - only for defs/instances
  1523. // Classes are templates that get instantiated later with concrete template arguments
  1524. // Processing parents here with unbound template parameters causes incorrect resolutions
  1525. // this.addSubClass(record);
  1526. if (this._match('{')) {
  1527. this._parseRecordBody(record);
  1528. }
  1529. this.classes.set(name, record);
  1530. }
  1531. _parseDef() {
  1532. this._read();
  1533. let name = '';
  1534. if (this._match('id')) {
  1535. name = this._read();
  1536. } else if (this._match('number')) {
  1537. name = this._read().toString();
  1538. }
  1539. const def = new tablegen.Record(name, this);
  1540. def.location = this._tokenizer.location();
  1541. if (this._match(':')) {
  1542. def.parents = this._parseParentClassList();
  1543. }
  1544. if (this._match('{')) {
  1545. this._parseRecordBody(def);
  1546. }
  1547. this.addSubClass(def);
  1548. def.resolveReferences();
  1549. if (name) {
  1550. this._defs.set(name, def);
  1551. this.defs.push(def);
  1552. }
  1553. }
  1554. _parseDefm() {
  1555. this._read();
  1556. this._skipUntil([';', 'def', 'class', 'defm', 'let', 'multiclass']);
  1557. this._eat(';');
  1558. }
  1559. _parseMulticlass() {
  1560. this._read();
  1561. let depth = 0;
  1562. while (!this._match('eof')) {
  1563. if (this._eat('{')) {
  1564. depth++;
  1565. } else if (this._eat('}')) {
  1566. depth--;
  1567. if (depth === 0) {
  1568. break;
  1569. }
  1570. } else {
  1571. this._read();
  1572. }
  1573. }
  1574. }
  1575. _parseLet() {
  1576. this._read();
  1577. this._skipUntil(['in', '{', ';']);
  1578. this._eat('in');
  1579. this._eat(';');
  1580. }
  1581. _parseDefvar() {
  1582. this._read(); // consume 'defvar'
  1583. this._expect('id'); // variable name
  1584. this._expect('=');
  1585. // Parse the value (could be complex expression)
  1586. this._parseValue();
  1587. this._expect(';');
  1588. // Note: defvar defines local variables used in templates
  1589. // We don't need to store them as they're only used during TableGen evaluation
  1590. // Just consume the syntax for now
  1591. }
  1592. _parseDefset() {
  1593. this._read();
  1594. let depth = 0;
  1595. while (!this._match('eof')) {
  1596. if (this._eat('{')) {
  1597. depth++;
  1598. } else if (this._eat('}')) {
  1599. depth--;
  1600. if (depth === 0) {
  1601. break;
  1602. }
  1603. } else {
  1604. this._read();
  1605. }
  1606. }
  1607. }
  1608. _parseForeach() {
  1609. this._read();
  1610. let depth = 0;
  1611. while (!this._match('eof')) {
  1612. if (this._eat('{')) {
  1613. depth++;
  1614. } else if (this._eat('}')) {
  1615. depth--;
  1616. if (depth === 0) {
  1617. break;
  1618. }
  1619. } else {
  1620. this._read();
  1621. }
  1622. }
  1623. }
  1624. _parseTemplateParams() {
  1625. this._read(); // <
  1626. const params = [];
  1627. while (!this._match('>') && !this._match('eof')) {
  1628. const type = this._parseType();
  1629. const name = this._expect('id');
  1630. let defaultValue = null;
  1631. if (this._match('=')) {
  1632. this._read();
  1633. defaultValue = this._parseValue();
  1634. }
  1635. params.push({ name, type, defaultValue });
  1636. if (this._match(',')) {
  1637. this._read();
  1638. }
  1639. }
  1640. this._expect('>');
  1641. return params;
  1642. }
  1643. _parseParentClassList() {
  1644. this._read();
  1645. const parents = [];
  1646. while (!this._match('{') && !this._match(';') && !this._match('eof')) {
  1647. const parent = this._parseType();
  1648. parents.push(parent);
  1649. if (!this._eat(',')) {
  1650. break;
  1651. }
  1652. }
  1653. return parents;
  1654. }
  1655. _parseRecordBody(record) {
  1656. this._read();
  1657. while (!this._match('}') && !this._match('eof')) {
  1658. if (this._match('keyword', 'let')) {
  1659. this._read();
  1660. const name = this._expect('id');
  1661. this._expect('=');
  1662. const value = this._parseValue();
  1663. const field = new tablegen.RecordVal(name, null, value);
  1664. record.fields.set(name, field);
  1665. this._eat(';');
  1666. } else if (this._match('keyword', 'defvar')) {
  1667. this._read();
  1668. const name = this._expect('id');
  1669. this._expect('=');
  1670. const value = this._parseValue();
  1671. const field = new tablegen.RecordVal(name, null, value);
  1672. record.fields.set(name, field);
  1673. this._eat(';');
  1674. } else if (this._match('keyword', 'assert')) {
  1675. this._read();
  1676. this._parseValue(); // condition
  1677. this._eat(',');
  1678. this._parseValue(); // message
  1679. this._eat(';');
  1680. } else if (this._match('keyword', 'bit') || this._match('keyword', 'bits') || this._match('keyword', 'int') ||
  1681. this._match('keyword', 'string') || this._match('keyword', 'list') || this._match('keyword', 'dag') ||
  1682. this._match('keyword', 'code') || this._match('id')) {
  1683. const type = this._parseType();
  1684. // Skip if next token is not an id (handles edge cases in complex nested structures)
  1685. if (!this._match('id')) {
  1686. if (this._match(',') || this._match('>') || this._match(')') || this._match(']')) {
  1687. continue;
  1688. }
  1689. }
  1690. const name = this._expect('id');
  1691. let value = null;
  1692. if (this._eat('=')) {
  1693. value = this._parseValue();
  1694. }
  1695. const field = new tablegen.RecordVal(name, type, value);
  1696. record.fields.set(name, field);
  1697. this._eat(';');
  1698. } else {
  1699. this._read();
  1700. }
  1701. }
  1702. this._expect('}');
  1703. }
  1704. _parseType() {
  1705. let typeName = '';
  1706. if (this._match('keyword', 'bit') || this._match('keyword', 'bits') || this._match('keyword', 'int') ||
  1707. this._match('keyword', 'string') || this._match('keyword', 'list') || this._match('keyword', 'dag') ||
  1708. this._match('keyword', 'code')) {
  1709. typeName = this._read();
  1710. } else if (this._match('id')) {
  1711. typeName = this._read();
  1712. } else if (this._match('number')) {
  1713. typeName = this._read().toString();
  1714. if (this._match('id')) {
  1715. typeName += this._read();
  1716. }
  1717. } else {
  1718. throw new tablegen.Error(`Expected type at ${this._tokenizer.location()}`);
  1719. }
  1720. const type = new tablegen.Type(typeName);
  1721. if (this._eat('<')) {
  1722. type.args = this._parseTemplateArgList();
  1723. }
  1724. return type;
  1725. }
  1726. _parseTemplateArgList() {
  1727. // Parse template arguments directly from token stream
  1728. // Supports both positional (arg1, arg2) and named (name=value) arguments
  1729. const args = [];
  1730. while (!this._match('>') && !this._match('eof')) {
  1731. // Check if this is a named argument: id = value
  1732. if (this._match('id')) {
  1733. const name = this._read();
  1734. if (this._match('=')) {
  1735. // Named argument
  1736. this._read(); // Consume '='
  1737. const value = this._parseValue();
  1738. args.push({ name, value });
  1739. } else {
  1740. // Positional argument that starts with an id
  1741. // Reconstruct the value - id might be part of concat, field access, etc.
  1742. let value = new tablegen.Value('def', name);
  1743. // Handle < > for template instantiation - manually parse with depth tracking
  1744. if (this._eat('<')) {
  1745. const nestedArgs = [];
  1746. let depth = 1;
  1747. let currentArg = [];
  1748. while (depth > 0 && !this._match('eof')) {
  1749. if (this._match('<')) {
  1750. currentArg.push(this._read());
  1751. depth++;
  1752. } else if (this._match('>')) {
  1753. if (depth === 1) {
  1754. // End of this template arg list
  1755. if (currentArg.length > 0) {
  1756. // Parse accumulated tokens properly
  1757. const argStr = currentArg.join(' ');
  1758. // Try to parse as number first
  1759. if (/^-?\d+$/.test(argStr)) {
  1760. nestedArgs.push(new tablegen.Value('int', argStr));
  1761. } else {
  1762. nestedArgs.push(new tablegen.Value('def', argStr));
  1763. }
  1764. }
  1765. this._read(); // consume the >
  1766. depth--;
  1767. } else {
  1768. currentArg.push(this._read());
  1769. depth--;
  1770. }
  1771. } else if (this._match(',') && depth === 1) {
  1772. // Argument separator at current depth
  1773. if (currentArg.length > 0) {
  1774. // Parse accumulated tokens properly
  1775. const argStr = currentArg.join(' ');
  1776. // Try to parse as number first
  1777. if (/^-?\d+$/.test(argStr)) {
  1778. nestedArgs.push(new tablegen.Value('int', argStr));
  1779. } else {
  1780. nestedArgs.push(new tablegen.Value('def', argStr));
  1781. }
  1782. currentArg = [];
  1783. }
  1784. this._read(); // consume comma
  1785. } else {
  1786. // Regular token - add to current arg
  1787. currentArg.push(this._read());
  1788. }
  1789. }
  1790. // Convert to DAG operands
  1791. const operands = nestedArgs.map((arg) => ({ value: arg, name: null }));
  1792. value = new tablegen.Value('dag', new tablegen.DAG(name, operands));
  1793. }
  1794. // Handle field access
  1795. if (this._eat('.')) {
  1796. const field = this._expect('id');
  1797. if (value.type === 'def') {
  1798. value = new tablegen.Value('def', `${name}.${field}`);
  1799. }
  1800. }
  1801. // Handle :: suffix
  1802. if (this._eat('::')) {
  1803. const suffix = this._expect('id');
  1804. if (value.type === 'def') {
  1805. value = new tablegen.Value('def', `${name}::${suffix}`);
  1806. }
  1807. }
  1808. // Handle # concatenation
  1809. if (this._match('#')) {
  1810. const values = [value];
  1811. while (this._match('#')) {
  1812. this._read();
  1813. values.push(this._parsePrimaryValue());
  1814. }
  1815. value = new tablegen.Value('concat', values);
  1816. }
  1817. args.push(value);
  1818. }
  1819. } else {
  1820. const value = this._parseValue();
  1821. args.push(value);
  1822. }
  1823. if (!this._eat(',')) {
  1824. break;
  1825. }
  1826. }
  1827. this._expect('>');
  1828. return args;
  1829. }
  1830. _parseValue() {
  1831. const values = [];
  1832. values.push(this._parsePrimaryValue());
  1833. while (this._match('#') || (values[values.length - 1] && values[values.length - 1].type === 'string' && this._match('string'))) {
  1834. if (this._match('#')) {
  1835. this._read();
  1836. }
  1837. values.push(this._parsePrimaryValue());
  1838. }
  1839. if (values.length === 1) {
  1840. return values[0];
  1841. }
  1842. return new tablegen.Value('concat', values);
  1843. }
  1844. _parseListItem() {
  1845. // Handle $variable as a standalone value in list/dag context
  1846. if (this._eat('$')) {
  1847. const name = this._expect('id');
  1848. return new tablegen.Value('var', name);
  1849. }
  1850. // Special handling for dag-like constructs (id followed by <...>)
  1851. // These need to be parsed as DAGs for trait information
  1852. if (this._match('id')) {
  1853. const name = this._read();
  1854. if (this._eat('<')) {
  1855. const templateArgs = this._parseTemplateArgList();
  1856. const operands = templateArgs.map((arg) => {
  1857. if (arg && typeof arg === 'object' && arg.name && arg.value) {
  1858. return { value: arg.value, name: arg.name };
  1859. }
  1860. return { value: arg, name: null };
  1861. });
  1862. const result = new tablegen.Value('dag', new tablegen.DAG(name, operands));
  1863. if (this._eat('.')) {
  1864. result.field = this._expect('id');
  1865. }
  1866. return result;
  1867. }
  1868. // Not a template instantiation, but might be an identifier with suffixes
  1869. // Put the token back conceptually by creating a def value and checking for suffixes
  1870. let result = new tablegen.Value('def', name);
  1871. // Check for subscripts, field access, etc.
  1872. while (this._eat('[')) {
  1873. const index = this._parseValue();
  1874. this._expect(']');
  1875. result = new tablegen.Value('bang', { op: 'subscript', args: [result, index], field: null });
  1876. }
  1877. if (this._eat('.')) {
  1878. const field = this._expect('id');
  1879. if (result.type === 'def') {
  1880. result = new tablegen.Value('def', `${result.value}.${field}`);
  1881. } else {
  1882. result.value.field = field;
  1883. }
  1884. }
  1885. return result;
  1886. }
  1887. return this._parseValue();
  1888. }
  1889. _parsePrimaryValue() {
  1890. if (this._match('string')) {
  1891. const value = this._read();
  1892. return new tablegen.Value('string', value);
  1893. }
  1894. if (this._match('number')) {
  1895. const value = this._read();
  1896. return new tablegen.Value('int', value);
  1897. }
  1898. if (this._match('true') || this._match('false')) {
  1899. const value = this._read();
  1900. return new tablegen.Value('bit', value);
  1901. }
  1902. if (this._match('code')) {
  1903. const value = this._read();
  1904. return new tablegen.Value('code', value);
  1905. }
  1906. if (this._eat('[')) {
  1907. const items = [];
  1908. while (!this._match(']') && !this._match('eof')) {
  1909. items.push(this._parseListItem());
  1910. this._eat(',');
  1911. }
  1912. this._expect(']');
  1913. if (this._match('<')) {
  1914. this._skip('<', '>');
  1915. }
  1916. return new tablegen.Value('list', items);
  1917. }
  1918. if (this._eat('(')) {
  1919. let operator = null;
  1920. let operatorName = null;
  1921. if (this._match('id')) {
  1922. operator = this._read();
  1923. // Handle template arguments on the operator: (NativeCodeCallVoid<...> ...)
  1924. if (this._match('<')) {
  1925. this._skip('<', '>');
  1926. }
  1927. // Handle operator binding: (OpQ:$op ...)
  1928. if (this._eat(':') && this._eat('$')) {
  1929. if (this._match('id') || this._match('keyword')) {
  1930. operatorName = this._read();
  1931. }
  1932. }
  1933. }
  1934. const operands = [];
  1935. while (!this._match(')') && !this._match('eof')) {
  1936. if (this._eat(',')) {
  1937. continue;
  1938. }
  1939. // Use _parseListItem() to handle template instantiations like Arg<TTG_MemDescType>
  1940. const value = this._parseListItem();
  1941. let name = null;
  1942. if (this._eat(':') && this._eat('$')) {
  1943. if (this._match('id') || this._match('keyword')) {
  1944. name = this._read();
  1945. }
  1946. }
  1947. const operand = { value, name };
  1948. operands.push(operand);
  1949. this._eat(',');
  1950. }
  1951. this._expect(')');
  1952. const dag = new tablegen.DAG(operator, operands);
  1953. if (operatorName) {
  1954. dag.operatorName = operatorName;
  1955. }
  1956. return new tablegen.Value('dag', dag);
  1957. }
  1958. if (this._eat('{')) {
  1959. const fields = new Map();
  1960. while (!this._match('}') && !this._match('eof')) {
  1961. const name = this._expect('id');
  1962. this._expect('=');
  1963. const value = this._parseValue();
  1964. fields.set(name, value);
  1965. this._eat(',');
  1966. }
  1967. this._expect('}');
  1968. return new tablegen.Value('record', fields);
  1969. }
  1970. if (this._eat('!')) {
  1971. let op = null;
  1972. if (this._match('id') || this._match('keyword')) {
  1973. op = this._read();
  1974. } else {
  1975. throw new tablegen.Error(`Expected operator after '!' but got '${this._tokenizer.current().type}' at ${this._tokenizer.location()}`);
  1976. }
  1977. if (this._match('<')) {
  1978. this._skip('<', '>');
  1979. }
  1980. const args = [];
  1981. if (this._eat('(')) {
  1982. if (op === 'cond') {
  1983. while (!this._match(')') && !this._match('eof')) {
  1984. const condition = this._parseValue();
  1985. this._expect(':');
  1986. const value = this._parseValue();
  1987. args.push({ condition, value });
  1988. this._eat(',');
  1989. }
  1990. } else {
  1991. while (!this._match(')') && !this._match('eof')) {
  1992. args.push(this._parseValue());
  1993. this._eat(',');
  1994. }
  1995. }
  1996. this._expect(')');
  1997. }
  1998. let field = null;
  1999. if (this._eat('.')) {
  2000. field = this._expect('id');
  2001. }
  2002. return new tablegen.Value('bang', { op, args, field });
  2003. }
  2004. if (this._match('id') || this._match('keyword')) {
  2005. const value = this._read();
  2006. let result = new tablegen.Value('def', value);
  2007. // Handle various suffixes: templates, subscripts, field access, scope resolution
  2008. while (true) {
  2009. if (this._match('<')) {
  2010. // Template arguments are skipped here - they're parsed properly in
  2011. // DAG context by _parseListItem() which creates DAG values with template args
  2012. this._skip('<', '>');
  2013. } else if (this._eat('[')) {
  2014. // Array subscripting: x[0]
  2015. const index = this._parseValue();
  2016. this._expect(']');
  2017. result = new tablegen.Value('bang', { op: 'subscript', args: [result, index], field: null });
  2018. } else if (this._eat('.')) {
  2019. // Field access: x.field or x[0].field
  2020. const field = this._expect('id');
  2021. if (result.type === 'def') {
  2022. result = new tablegen.Value('def', `${result.value}.${field}`);
  2023. } else {
  2024. // For subscript results, add field access
  2025. result.value.field = field;
  2026. }
  2027. } else if (this._eat('::')) {
  2028. // Scope resolution
  2029. const suffix = this._expect('id');
  2030. if (result.type === 'def') {
  2031. result = new tablegen.Value('def', `${result.value}::${suffix}`);
  2032. }
  2033. break;
  2034. } else {
  2035. break;
  2036. }
  2037. }
  2038. return result;
  2039. }
  2040. if (this._eat('?')) {
  2041. return new tablegen.Value('uninitialized', null);
  2042. }
  2043. if (this._eat('$')) {
  2044. const name = this._expect('id');
  2045. return new tablegen.Value('var', name);
  2046. }
  2047. throw new tablegen.Error(`Unexpected value at ${this._tokenizer.location()}`);
  2048. }
  2049. _read() {
  2050. return this._tokenizer.read().value;
  2051. }
  2052. _match(type, value) {
  2053. const token = this._tokenizer.current();
  2054. return token.type === type && (!value || token.value === value);
  2055. }
  2056. _eat(type, value) {
  2057. if (this._match(type, value)) {
  2058. this._read();
  2059. return true;
  2060. }
  2061. return false;
  2062. }
  2063. _expect(type, value) {
  2064. if (this._match(type, value)) {
  2065. return this._read();
  2066. }
  2067. const token = this._tokenizer.current();
  2068. throw new tablegen.Error(`Expected '${type}' but got '${token.type}' at ${this._tokenizer.location()}`);
  2069. }
  2070. _skip(open, close) {
  2071. if (!this._match(open)) {
  2072. return;
  2073. }
  2074. this._read(); // consume opening token
  2075. let depth = 1;
  2076. while (depth > 0 && !this._match('eof')) {
  2077. if (this._match(open)) {
  2078. depth++;
  2079. } else if (this._match(close)) {
  2080. depth--;
  2081. }
  2082. this._read();
  2083. }
  2084. }
  2085. _skipUntil(types) {
  2086. while (!this._match('eof') && !types.includes(this._tokenizer.current().type)) {
  2087. this._read();
  2088. }
  2089. }
  2090. };
  2091. tablegen.Error = class extends Error {
  2092. constructor(message) {
  2093. super(message);
  2094. this.name = 'TableGen Error';
  2095. }
  2096. };
  2097. export const Reader = tablegen.Reader;