3474 lines
145 KiB
JavaScript
3474 lines
145 KiB
JavaScript
|
|
import * as fs from 'fs/promises';
|
|
import * as path from 'path';
|
|
|
|
const tablegen = {};
|
|
|
|
tablegen.Location = class {
|
|
|
|
constructor(file, line, column) {
|
|
this.file = file;
|
|
this.line = line;
|
|
this.column = column;
|
|
}
|
|
|
|
toString() {
|
|
return `${this.file}:${this.line}:${this.column}`;
|
|
}
|
|
};
|
|
|
|
tablegen.Token = class {
|
|
|
|
constructor(type, value, location) {
|
|
this.type = type;
|
|
this.value = value;
|
|
this.location = location;
|
|
}
|
|
};
|
|
|
|
tablegen.Tokenizer = class {
|
|
|
|
constructor(text, file) {
|
|
this._text = text;
|
|
this._file = file;
|
|
this._position = 0;
|
|
this._line = 1;
|
|
this._column = 1;
|
|
this._keywords = new Set([
|
|
'assert', 'bit', 'bits', 'class', 'code', 'dag', 'def', 'defm', 'defset', 'defvar',
|
|
'dump', 'else', 'false', 'field', 'foreach', 'if', 'in', 'include', 'int',
|
|
'let', 'list', 'multiclass', 'string', 'then', 'true'
|
|
]);
|
|
this._token = this._tokenize();
|
|
}
|
|
|
|
location() {
|
|
return new tablegen.Location(this._file, this._line, this._column);
|
|
}
|
|
|
|
current() {
|
|
return this._token;
|
|
}
|
|
|
|
peek(offset = 0) {
|
|
const pos = this._position + offset;
|
|
return pos < this._text.length ? this._text[pos] : null;
|
|
}
|
|
|
|
read() {
|
|
const prev = this._token;
|
|
this._token = this._tokenize();
|
|
return prev;
|
|
}
|
|
|
|
_tokenize() {
|
|
this._skipWhitespace();
|
|
if (this._position >= this._text.length) {
|
|
return new tablegen.Token('eof', null, this.location());
|
|
}
|
|
const location = this.location();
|
|
const c = this.peek();
|
|
if (c === '"') {
|
|
return this._readString(location);
|
|
}
|
|
if (c === '[' && this.peek(1) === '{') {
|
|
return this._readCodeBlock(location);
|
|
}
|
|
if (c === ':' && this.peek(1) === ':') {
|
|
this._next();
|
|
this._next();
|
|
return new tablegen.Token('::', '::', location);
|
|
}
|
|
if (c === '.' && this.peek(1) === '.' && this.peek(2) === '.') {
|
|
this._next();
|
|
this._next();
|
|
this._next();
|
|
return new tablegen.Token('...', '...', location);
|
|
}
|
|
if (c === '-' && this.peek(1) === '>') {
|
|
this._next();
|
|
this._next();
|
|
return new tablegen.Token('->', '->', location);
|
|
}
|
|
if (c === '=' && this.peek(1) === '=') {
|
|
this._next();
|
|
this._next();
|
|
return new tablegen.Token('==', '==', location);
|
|
}
|
|
if (c === '!' && this.peek(1) === '=') {
|
|
this._next();
|
|
this._next();
|
|
return new tablegen.Token('!=', '!=', location);
|
|
}
|
|
if (c === '<' && this.peek(1) === '<') {
|
|
this._next();
|
|
this._next();
|
|
return new tablegen.Token('<<', '<<', location);
|
|
}
|
|
if (this._isDigit(c)) {
|
|
let pos = 1;
|
|
while (this.peek(pos) && this._isDigit(this.peek(pos))) {
|
|
pos++;
|
|
}
|
|
if (this.peek(pos) && /[a-zA-Z_]/.test(this.peek(pos))) {
|
|
return this._readIdentifier(location);
|
|
}
|
|
return this._readNumber(location);
|
|
}
|
|
if (c === '-' && this._isDigit(this.peek(1))) {
|
|
return this._readNumber(location);
|
|
}
|
|
if (this._isIdentifierStart(c)) {
|
|
return this._readIdentifier(location);
|
|
}
|
|
if ('{}[]()<>,;:=!#$.?^'.includes(c)) {
|
|
this._next();
|
|
return new tablegen.Token(c, c, location);
|
|
}
|
|
throw new tablegen.Error(`Unexpected character '${c}' at ${location}`);
|
|
}
|
|
|
|
_next() {
|
|
if (this._position < this._text.length) {
|
|
if (this._text[this._position] === '\n') {
|
|
this._line++;
|
|
this._column = 1;
|
|
} else {
|
|
this._column++;
|
|
}
|
|
this._position++;
|
|
}
|
|
}
|
|
|
|
_skipWhitespace() {
|
|
while (this._position < this._text.length) {
|
|
const c = this.peek();
|
|
if (/\s/.test(c)) {
|
|
this._next();
|
|
continue;
|
|
}
|
|
if (c === '/' && this.peek(1) === '/') {
|
|
this._next();
|
|
this._next();
|
|
while (this._position < this._text.length && this.peek() !== '\n') {
|
|
this._next();
|
|
}
|
|
continue;
|
|
}
|
|
if (c === '/' && this.peek(1) === '*') {
|
|
this._next();
|
|
this._next();
|
|
let depth = 1;
|
|
while (this._position < this._text.length && depth > 0) {
|
|
if (this.peek() === '/' && this.peek(1) === '*') {
|
|
this._next();
|
|
this._next();
|
|
depth++;
|
|
} else if (this.peek() === '*' && this.peek(1) === '/') {
|
|
this._next();
|
|
this._next();
|
|
depth--;
|
|
} else {
|
|
this._next();
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
_isDigit(c) {
|
|
return c !== null && /[0-9]/.test(c);
|
|
}
|
|
|
|
_isIdentifierStart(c) {
|
|
return c !== null && /[a-zA-Z_]/.test(c);
|
|
}
|
|
|
|
_isIdentifierChar(c) {
|
|
return c !== null && /[a-zA-Z0-9_]/.test(c);
|
|
}
|
|
|
|
_readString(location) {
|
|
let value = '';
|
|
this._next(); // opening "
|
|
while (this._position < this._text.length && this.peek() !== '"') {
|
|
if (this.peek() === '\\') {
|
|
this._next();
|
|
const c = this.peek();
|
|
switch (c) {
|
|
case 'n': value += '\n'; break;
|
|
case 't': value += '\t'; break;
|
|
case 'r': value += '\r'; break;
|
|
case '\\': value += '\\'; break;
|
|
case '"': value += '"'; break;
|
|
default: value += c; break;
|
|
}
|
|
this._next();
|
|
} else {
|
|
value += this.peek();
|
|
this._next();
|
|
}
|
|
}
|
|
if (this.peek() === '"') {
|
|
this._next();
|
|
}
|
|
return new tablegen.Token('string', value, location);
|
|
}
|
|
|
|
_readCodeBlock(location) {
|
|
let value = '';
|
|
this._next(); // [
|
|
this._next(); // {
|
|
|
|
let depth = 1;
|
|
let lineStart = true;
|
|
let lineContent = '';
|
|
while (this._position < this._text.length && depth > 0) {
|
|
const c = this.peek();
|
|
const next = this.peek(1);
|
|
|
|
if (c === '[' && next === '{') {
|
|
const trimmedLine = lineContent.trim();
|
|
if (lineStart || trimmedLine === '' || /^(let|def|class)\s/.test(trimmedLine)) {
|
|
depth++;
|
|
}
|
|
value += c;
|
|
this._next();
|
|
value += next;
|
|
this._next();
|
|
lineContent += c + next;
|
|
} else if (c === '}' && next === ']') {
|
|
depth--;
|
|
if (depth === 0) {
|
|
this._next();
|
|
this._next();
|
|
break;
|
|
}
|
|
value += c;
|
|
this._next();
|
|
value += next;
|
|
this._next();
|
|
lineContent += c + next;
|
|
} else {
|
|
if (c === '\n') {
|
|
lineStart = true;
|
|
lineContent = '';
|
|
} else if (c !== ' ' && c !== '\t') {
|
|
lineStart = false;
|
|
}
|
|
if (c !== '\n') {
|
|
lineContent += c;
|
|
}
|
|
value += c;
|
|
this._next();
|
|
}
|
|
}
|
|
return new tablegen.Token('code', value.trim(), location);
|
|
}
|
|
|
|
_readNumber(location) {
|
|
let value = '';
|
|
if (this.peek() === '-') {
|
|
value += this.peek();
|
|
this._next();
|
|
}
|
|
// Hexadecimal
|
|
if (this.peek() === '0' && this.peek(1) === 'x') {
|
|
value += this.peek();
|
|
this._next();
|
|
value += this.peek();
|
|
this._next();
|
|
while (this._position < this._text.length && /[0-9a-fA-F]/.test(this.peek())) {
|
|
value += this.peek();
|
|
this._next();
|
|
}
|
|
return new tablegen.Token('number', parseInt(value, 16), location);
|
|
}
|
|
// Binary
|
|
if (this.peek() === '0' && this.peek(1) === 'b') {
|
|
const isNegative = value === '-';
|
|
value += this.peek();
|
|
this._next();
|
|
value += this.peek();
|
|
this._next();
|
|
while (this._position < this._text.length && /[01]/.test(this.peek())) {
|
|
value += this.peek();
|
|
this._next();
|
|
}
|
|
const binaryDigits = isNegative ? value.substring(3) : value.substring(2);
|
|
const result = parseInt(binaryDigits, 2);
|
|
return new tablegen.Token('number', isNegative ? -result : result, location);
|
|
}
|
|
// Decimal
|
|
while (this._position < this._text.length && this._isDigit(this.peek())) {
|
|
value += this.peek();
|
|
this._next();
|
|
}
|
|
return new tablegen.Token('number', parseInt(value, 10), location);
|
|
}
|
|
|
|
_readIdentifier(location) {
|
|
let value = '';
|
|
while (this._position < this._text.length && this._isIdentifierChar(this.peek())) {
|
|
value += this.peek();
|
|
this._next();
|
|
}
|
|
while (this.peek() === '.' && this._isIdentifierStart(this.peek(1))) {
|
|
value += this.peek(); // add dot
|
|
this._next();
|
|
while (this._position < this._text.length && this._isIdentifierChar(this.peek())) {
|
|
value += this.peek();
|
|
this._next();
|
|
}
|
|
}
|
|
const type = this._keywords.has(value) ? 'keyword' : 'id';
|
|
return new tablegen.Token(type, value, location);
|
|
}
|
|
};
|
|
|
|
tablegen.Value = class {
|
|
|
|
constructor(type, value) {
|
|
this.type = type; // 'int', 'string', 'bit', 'bits', 'list', 'dag', 'code', 'def'
|
|
this.value = value;
|
|
}
|
|
};
|
|
|
|
tablegen.DAG = class {
|
|
|
|
constructor(operator, operands) {
|
|
this.operator = operator; // string or Value
|
|
this.operands = operands; // array of {value, name}
|
|
}
|
|
};
|
|
|
|
tablegen.Type = class {
|
|
|
|
constructor(name) {
|
|
this.name = name;
|
|
this.args = [];
|
|
}
|
|
|
|
toString() {
|
|
if (this.args.length === 0) {
|
|
return this.name;
|
|
}
|
|
return `${this.name}<${this.args.map((a) => a.toString()).join(', ')}>`;
|
|
}
|
|
};
|
|
|
|
tablegen.RecordVal = class {
|
|
|
|
constructor(name, type, value) {
|
|
this.name = name;
|
|
this.type = type; // tablegen.Type
|
|
this.value = value; // tablegen.Value or null
|
|
}
|
|
};
|
|
|
|
tablegen.Record = class {
|
|
|
|
constructor(name, parser = null) {
|
|
this.name = name;
|
|
this.parents = [];
|
|
this.fields = new Map();
|
|
this.templateArgs = [];
|
|
this.templateBindings = new Map(); // parameter name -> bound value
|
|
this.location = null;
|
|
this.parser = parser;
|
|
}
|
|
|
|
getValue(name) {
|
|
return this.fields.get(name) || null;
|
|
}
|
|
|
|
// Unified resolution function that handles both parent field copying and post-inheritance resolution
|
|
// Inspired by LLVM's Resolver pattern to avoid code duplication
|
|
_resolveInit(init, resolver, visited = new Set()) {
|
|
if (!init || typeof init !== 'object') {
|
|
return init;
|
|
}
|
|
const key = JSON.stringify(init);
|
|
if (visited.has(key)) {
|
|
return init;
|
|
}
|
|
visited.add(key);
|
|
// Resolve def/id type references (template parameters or field access)
|
|
if ((init.type === 'def' || init.type === 'id') && typeof init.value === 'string') {
|
|
// Handle field access syntax (e.g., "meta.mnemonic" or "addrKind.name")
|
|
if (init.value.includes('.')) {
|
|
const [baseName, ...fieldParts] = init.value.split('.');
|
|
// Try to resolve the base
|
|
let current = resolver.resolveReference(baseName);
|
|
if (current) {
|
|
// Walk through field access chain
|
|
for (const fieldName of fieldParts) {
|
|
// Handle def reference
|
|
if (current && current.type === 'def' && typeof current.value === 'string') {
|
|
const defName = current.value;
|
|
const def = this.parser.getDef(defName) || this.parser.getClass(defName);
|
|
if (def) {
|
|
const field = def.getValue(fieldName);
|
|
if (field && field.value) {
|
|
current = field.value;
|
|
} else {
|
|
return init; // Field not found
|
|
}
|
|
} else {
|
|
return init; // Def not found
|
|
}
|
|
} else if (current && current.type === 'dag' && current.value) {
|
|
// Handle DAG (anonymous class instantiation)
|
|
const className = current.value.operator;
|
|
const templateArgs = current.value.operands.map((op) => op.value);
|
|
// Instantiate an anonymous class with template arguments
|
|
// Used for resolving field access like meta.mnemonic where meta is ROCDL_TrLoadOpMeta<...>
|
|
let instantiated = null;
|
|
const baseClass = this.parser.getClass(className);
|
|
if (baseClass) {
|
|
// Create an anonymous record that inherits from the class
|
|
instantiated = new tablegen.Record(`<anonymous ${className}>`, this.parser);
|
|
instantiated.parents = [{ name: className, args: templateArgs }];
|
|
this.parser.addSubClass(instantiated);
|
|
instantiated.resolveReferences();
|
|
}
|
|
if (instantiated) {
|
|
const field = instantiated.getValue(fieldName);
|
|
if (field && field.value) {
|
|
current = field.value;
|
|
} else {
|
|
return init;
|
|
}
|
|
} else {
|
|
return init;
|
|
}
|
|
} else {
|
|
return init; // Can't resolve further
|
|
}
|
|
}
|
|
|
|
// Successfully resolved the field access chain
|
|
return this._resolveInit(current, resolver, visited);
|
|
}
|
|
} else {
|
|
// Simple reference without dots
|
|
const resolved = resolver.resolveReference(init.value);
|
|
if (resolved) {
|
|
return this._resolveInit(resolved, resolver, visited);
|
|
}
|
|
}
|
|
}
|
|
// Recursively resolve nested structures
|
|
if (init.type === 'dag' && init.value) {
|
|
return {
|
|
type: 'dag',
|
|
value: {
|
|
operator: init.value.operator,
|
|
operands: init.value.operands.map((op) => ({
|
|
value: this._resolveInit(op.value, resolver, visited),
|
|
name: op.name
|
|
}))
|
|
}
|
|
};
|
|
}
|
|
if (init.type === 'list' && Array.isArray(init.value)) {
|
|
return {
|
|
type: 'list',
|
|
value: init.value.map((v) => this._resolveInit(v, resolver, visited))
|
|
};
|
|
}
|
|
if (init.type === 'bang' && init.value) {
|
|
return {
|
|
type: 'bang',
|
|
value: {
|
|
op: init.value.op,
|
|
args: init.value.args.map((arg) => this._resolveInit(arg, resolver, visited))
|
|
}
|
|
};
|
|
}
|
|
if (init.type === 'concat' && Array.isArray(init.value)) {
|
|
const resolvedParts = init.value.map((v) => this._resolveInit(v, resolver, visited));
|
|
// Flatten nested concats recursively: if a resolved part is itself a concat,
|
|
// extract its parts to avoid nested concat structures that cause double evaluation
|
|
const flattenedParts = [];
|
|
const flattenConcat = (part) => {
|
|
if (part && part.type === 'concat' && Array.isArray(part.value)) {
|
|
for (const subPart of part.value) {
|
|
flattenConcat(subPart);
|
|
}
|
|
} else {
|
|
flattenedParts.push(part);
|
|
}
|
|
};
|
|
for (const part of resolvedParts) {
|
|
flattenConcat(part);
|
|
}
|
|
return {
|
|
type: 'concat',
|
|
value: flattenedParts
|
|
};
|
|
}
|
|
// For other types, return shallow copy for parent field copying, as-is for post-inheritance
|
|
return resolver.shouldCopy ? { ...init } : init;
|
|
}
|
|
|
|
// Helper to deep copy a field and resolve parameter references in a specific context
|
|
_copyAndResolveField(field, bindings, parentClass) {
|
|
const resolver = {
|
|
shouldCopy: true,
|
|
resolveReference: (name) => bindings.get(name) || parentClass.templateBindings.get(name) || null
|
|
};
|
|
return {
|
|
name: field.name,
|
|
type: field.type,
|
|
value: this._resolveInit(field.value, resolver)
|
|
};
|
|
}
|
|
|
|
// Resolve template parameter references in field values
|
|
// This matches C++ Record::resolveReferences() behavior
|
|
// After inheriting from a templated class, substitute all template parameter
|
|
// references in field values with their bound values from templateBindings
|
|
resolveReferences() {
|
|
if (this.templateBindings.size === 0) {
|
|
return; // No template parameters to substitute
|
|
}
|
|
const findTemplateBinding = (paramName, record = this, visited = new Set()) => {
|
|
if (visited.has(record.name)) {
|
|
return null;
|
|
}
|
|
visited.add(record.name);
|
|
if (record.templateBindings.has(paramName)) {
|
|
return record.templateBindings.get(paramName);
|
|
}
|
|
for (const parent of record.parents) {
|
|
const parentClass = this.parser.classes.get(parent.name);
|
|
if (parentClass) {
|
|
const paramIndex = parentClass.templateArgs.findIndex((arg) => arg.name === paramName);
|
|
if (paramIndex !== -1 && parent.args && parent.args[paramIndex]) {
|
|
return parent.args[paramIndex];
|
|
}
|
|
const binding = findTemplateBinding(paramName, parentClass, visited);
|
|
if (binding) {
|
|
return binding;
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
};
|
|
const resolver = {
|
|
shouldCopy: false,
|
|
resolveReference: (name) => {
|
|
const binding = findTemplateBinding(name);
|
|
if (binding) {
|
|
return binding;
|
|
}
|
|
const field = this.getValue(name);
|
|
if (field && field.value) {
|
|
return field.value;
|
|
}
|
|
return null;
|
|
}
|
|
};
|
|
for (const [, field] of this.fields) {
|
|
if (field.value) {
|
|
const resolved = this._resolveInit(field.value, resolver);
|
|
if (resolved !== field.value) {
|
|
field.value = resolved;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
getValueAsString(fieldName) {
|
|
const field = this.getValue(fieldName);
|
|
if (!field || !field.value) {
|
|
return null;
|
|
}
|
|
const evaluated = this.evaluateValue(field.value);
|
|
if (typeof evaluated === 'string') {
|
|
return evaluated;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
getValueAsBit(fieldName) {
|
|
const field = this.getValue(fieldName);
|
|
if (!field || !field.value) {
|
|
return null;
|
|
}
|
|
const evaluated = this.evaluateValue(field.value);
|
|
if (typeof evaluated === 'boolean') {
|
|
return evaluated;
|
|
}
|
|
if (typeof evaluated === 'number') {
|
|
return evaluated !== 0;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
getValueAsDag(fieldName) {
|
|
const field = this.getValue(fieldName);
|
|
if (!field || !field.value) {
|
|
return null;
|
|
}
|
|
const evaluated = this.evaluateValue(field.value);
|
|
if (evaluated && typeof evaluated === 'object' && evaluated.operator) {
|
|
return evaluated;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
getValueAsDef(fieldName) {
|
|
const field = this.getValue(fieldName);
|
|
if (!field || !field.value) {
|
|
return null;
|
|
}
|
|
if (field.value.type === 'def' && typeof field.value.value === 'string') {
|
|
const defName = field.value.value;
|
|
const candidates = [];
|
|
for (const def of this.parser.defs) {
|
|
if (def.name === defName) {
|
|
candidates.push(def);
|
|
}
|
|
}
|
|
for (const cls of this.parser.classes.values()) {
|
|
if (cls.name === defName) {
|
|
candidates.push(cls);
|
|
}
|
|
}
|
|
if (candidates.length === 0) {
|
|
return null;
|
|
}
|
|
if (candidates.length === 1) {
|
|
return candidates[0];
|
|
}
|
|
const sourceFile = this.location?.file || '';
|
|
if (sourceFile) {
|
|
const sourceParts = sourceFile.split('/');
|
|
let [bestMatch] = candidates;
|
|
let bestPrefixLength = -1;
|
|
for (const candidate of candidates) {
|
|
const candidateFile = candidate.location?.file || '';
|
|
const candidateParts = candidateFile.split('/');
|
|
let prefixLength = 0;
|
|
for (let i = 0; i < Math.min(sourceParts.length, candidateParts.length); i++) {
|
|
if (sourceParts[i] !== candidateParts[i]) {
|
|
break;
|
|
}
|
|
prefixLength++;
|
|
}
|
|
if (prefixLength > bestPrefixLength) {
|
|
bestPrefixLength = prefixLength;
|
|
bestMatch = candidate;
|
|
}
|
|
}
|
|
return bestMatch;
|
|
}
|
|
return candidates[0];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
isEnumAttr() {
|
|
const enumBaseClasses = [
|
|
'EnumAttr', // Wrapper for dialect-specific enums
|
|
'IntEnumAttr', 'I32EnumAttr', 'I64EnumAttr',
|
|
'BitEnumAttr', 'I8BitEnumAttr', 'I16BitEnumAttr', 'I32BitEnumAttr', 'I64BitEnumAttr',
|
|
'IntEnum', 'I32IntEnum', 'I64IntEnum', // Integer enum types
|
|
'BitEnum', 'I8BitEnum', 'I16BitEnum', 'I32BitEnum', 'I64BitEnum' // Bit enum types
|
|
];
|
|
const checkParents = (record, visited = new Set()) => {
|
|
if (record && !visited.has(record.name)) {
|
|
visited.add(record.name);
|
|
if (enumBaseClasses.includes(record.name)) {
|
|
return true;
|
|
}
|
|
for (const parent of record.parents) {
|
|
const parentClass = this.parser.getClass(parent.name);
|
|
if (parentClass && checkParents(parentClass, visited)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
return checkParents(this);
|
|
}
|
|
|
|
isEnumProp() {
|
|
const enumPropBaseClasses = [
|
|
'EnumProp', // Property wrapping an enum
|
|
'EnumPropWithAttrForm' // Property with attribute form
|
|
];
|
|
const checkParents = (record, visited = new Set()) => {
|
|
if (record && !visited.has(record.name)) {
|
|
visited.add(record.name);
|
|
if (enumPropBaseClasses.includes(record.name)) {
|
|
return true;
|
|
}
|
|
for (const parent of record.parents) {
|
|
const parentClass = this.parser.getClass(parent.name);
|
|
if (parentClass && checkParents(parentClass, visited)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
return checkParents(this);
|
|
}
|
|
|
|
// Get enum cases from an enum attribute or property definition
|
|
getEnumCases() {
|
|
if (!this.isEnumAttr() && !this.isEnumProp()) {
|
|
return null;
|
|
}
|
|
|
|
// Handle EnumProp - the first argument is the underlying enum
|
|
if (this.isEnumProp()) {
|
|
for (const parent of this.parents) {
|
|
if (parent.name === 'EnumProp' || parent.name === 'EnumPropWithAttrForm') {
|
|
if (parent.args && parent.args.length >= 1) {
|
|
const [enumInfoArg] = parent.args;
|
|
if (enumInfoArg && enumInfoArg.type === 'def' && typeof enumInfoArg.value === 'string') {
|
|
const enumName = enumInfoArg.value;
|
|
const underlyingEnum = this.parser.getDef(enumName) || this.parser.getClass(enumName);
|
|
if (underlyingEnum) {
|
|
// Recursively get cases from the underlying enum
|
|
return underlyingEnum.getEnumCases();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Recursively search parent classes
|
|
const parentClass = this.parser.getClass(parent.name);
|
|
if (parentClass && parentClass.isEnumProp()) {
|
|
const cases = parentClass.getEnumCases();
|
|
if (cases) {
|
|
return cases;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Helper to search for EnumAttr in the parent hierarchy
|
|
const findEnumAttrParent = (record, visited = new Set()) => {
|
|
if (!record || visited.has(record.name)) {
|
|
return null;
|
|
}
|
|
visited.add(record.name);
|
|
for (const parent of record.parents) {
|
|
if (parent.name === 'EnumAttr') {
|
|
return parent;
|
|
}
|
|
// Recursively search parent classes
|
|
const parentClass = this.parser.getClass(parent.name);
|
|
if (parentClass) {
|
|
const found = findEnumAttrParent(parentClass, visited);
|
|
if (found) {
|
|
return found;
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
};
|
|
const enumAttrParent = findEnumAttrParent(this);
|
|
// Pattern 0: Handle wrapper classes like OpenMP_EnumAttr, GPU_EnumAttr, etc.
|
|
// These are classes that inherit from EnumAttr and take the underlying enum as first arg.
|
|
// When we call findEnumAttrParent, it returns EnumAttr's template params (e.g., "enumInfo"),
|
|
// not the actual values. So we need to look at this.parents to find the wrapper class
|
|
// and extract the actual enum from its args.
|
|
for (const parent of this.parents) {
|
|
const parentClass = this.parser.getClass(parent.name);
|
|
if (parentClass && parentClass !== this) {
|
|
// Check if this parent class inherits from EnumAttr
|
|
const parentEnumAttr = findEnumAttrParent(parentClass);
|
|
if (parentEnumAttr && parent.args && parent.args.length >= 1) {
|
|
// The first arg of wrapper classes is typically the underlying enum
|
|
for (const arg of parent.args) {
|
|
if (arg.type === 'def' && typeof arg.value === 'string') {
|
|
const potentialEnum = this.parser.getDef(arg.value) || this.parser.getClass(arg.value);
|
|
if (potentialEnum && potentialEnum.isEnumAttr()) {
|
|
const cases = potentialEnum.getEnumCases();
|
|
if (cases && cases.length > 0) {
|
|
return cases;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Pattern 1a: EnumAttr<Dialect, EnumInfo (as def), name>
|
|
// The 2nd argument is a reference to the underlying enum (e.g., GPU_Dimension)
|
|
if (enumAttrParent && enumAttrParent.args && enumAttrParent.args.length >= 2) {
|
|
const [dialectArg, enumInfoArg] = enumAttrParent.args;
|
|
if (enumInfoArg && enumInfoArg.type === 'def' && typeof enumInfoArg.value === 'string') {
|
|
// Get the expected namespace from the dialect
|
|
let expectedNamespace = null;
|
|
if (dialectArg && dialectArg.type === 'def') {
|
|
const dialectDef = this.parser.getDef(dialectArg.value) || this.parser.getClass(dialectArg.value);
|
|
if (dialectDef) {
|
|
expectedNamespace = dialectDef.getValueAsString('cppNamespace');
|
|
}
|
|
}
|
|
// Find the enum with the matching name and namespace
|
|
// Try all defs with this name in case of conflicts (different namespaces)
|
|
const enumName = enumInfoArg.value;
|
|
let underlyingEnum = null;
|
|
if (expectedNamespace) {
|
|
// Normalize namespace for comparison (handle both "vector" and "::mlir::vector" formats)
|
|
const normalizeNamespace = (ns) => {
|
|
if (!ns) {
|
|
return null;
|
|
}
|
|
// Remove leading :: and extract the last component
|
|
const parts = ns.replace(/^::/, '').split('::');
|
|
return parts[parts.length - 1];
|
|
};
|
|
const normalizedExpected = normalizeNamespace(expectedNamespace);
|
|
|
|
// Search through all defs to find one with matching name AND namespace
|
|
for (const def of this.parser.defs) {
|
|
if (def.name === enumName) {
|
|
const defNamespace = def.getValueAsString('cppNamespace');
|
|
const normalizedDef = normalizeNamespace(defNamespace);
|
|
if (normalizedDef === normalizedExpected) {
|
|
underlyingEnum = def;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fallback to regular lookup if namespace matching fails
|
|
if (!underlyingEnum) {
|
|
underlyingEnum = this.parser.getDef(enumName) || this.parser.getClass(enumName);
|
|
}
|
|
|
|
if (underlyingEnum) {
|
|
return underlyingEnum.getEnumCases();
|
|
}
|
|
}
|
|
// Pattern 1b: EnumAttr<Dialect, EnumInfo (as DAG), name>
|
|
// The 2nd argument is a DAG that instantiates an enum class template
|
|
// e.g., EnumAttr<SPIRV_Dialect, SPIRV_I32Enum<"Scope", "desc", [cases]>, "scope">
|
|
if (enumInfoArg && enumInfoArg.type === 'dag' && enumInfoArg.value) {
|
|
// The DAG operator is the enum class template (e.g., SPIRV_I32Enum)
|
|
// We need to find the actual cases by looking at this record's parent args
|
|
// which should have the instantiated template parameters
|
|
// Search through this record's parents to find one with the cases list
|
|
for (const parent of this.parents) {
|
|
if (parent.args && parent.args.length >= 3) {
|
|
// Look for a list argument that contains enum case defs
|
|
for (const arg of parent.args) {
|
|
if (arg.type === 'list' && Array.isArray(arg.value)) {
|
|
// Check if this looks like an enum case list
|
|
const [firstItem] = arg.value;
|
|
if (firstItem && firstItem.type === 'def') {
|
|
// Try to extract cases from this list
|
|
const cases = [];
|
|
for (const caseValue of arg.value) {
|
|
if (caseValue.type === 'def' && typeof caseValue.value === 'string') {
|
|
const caseDef = this.parser.getDef(caseValue.value) || this.parser.getClass(caseValue.value);
|
|
if (caseDef) {
|
|
const str = caseDef.getValueAsString('str');
|
|
if (str) {
|
|
cases.push(str);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (cases.length > 0) {
|
|
return cases;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Pattern 2: I64EnumAttr<name, summary, [cases]>
|
|
// Cases are in the 3rd template argument
|
|
for (const parent of this.parents) {
|
|
// The 3rd argument should be the cases list
|
|
if (parent.args && parent.args.length >= 3) {
|
|
const [,,casesArg] = parent.args;
|
|
if (casesArg && casesArg.type === 'list' && Array.isArray(casesArg.value)) {
|
|
const cases = [];
|
|
for (const caseValue of casesArg.value) {
|
|
// Each case can be either a DAG or a def reference
|
|
if (caseValue.type === 'dag' && caseValue.value) {
|
|
// DAG format: I64EnumAttrCase<"symbol", value, "string">
|
|
// The string representation is in the 3rd operand, or 1st if only 2 operands
|
|
const operands = caseValue.value.operands;
|
|
if (operands && operands.length > 0) {
|
|
// Try the 3rd operand first (string), fall back to 1st (symbol)
|
|
const strOperand = operands.length >= 3 ? operands[2] : operands[0];
|
|
if (strOperand && strOperand.value) {
|
|
const str = this.evaluateValue(strOperand.value);
|
|
if (str && typeof str === 'string') {
|
|
cases.push(str);
|
|
}
|
|
}
|
|
}
|
|
} else if (caseValue.type === 'def' && typeof caseValue.value === 'string') {
|
|
// Def reference format
|
|
const caseDef = this.parser.getDef(caseValue.value) || this.parser.getClass(caseValue.value);
|
|
if (caseDef) {
|
|
const str = caseDef.getValueAsString('str');
|
|
if (str) {
|
|
cases.push(str);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return cases.length > 0 ? cases : null;
|
|
}
|
|
}
|
|
}
|
|
// Pattern 3: LLVM_EnumAttr<name, cppType, summary, [cases]>
|
|
// Cases are in the 4th template argument
|
|
for (const parent of this.parents) {
|
|
if (parent.args && parent.args.length >= 4) {
|
|
const [,,,casesArg] = parent.args;
|
|
if (casesArg && casesArg.type === 'list' && Array.isArray(casesArg.value)) {
|
|
const cases = [];
|
|
for (const caseValue of casesArg.value) {
|
|
// Each case can be either a DAG or a def reference
|
|
if (caseValue.type === 'dag' && caseValue.value) {
|
|
// DAG format: I32EnumAttrCase<"symbol", value>
|
|
// The first operand is the symbol name
|
|
const operands = caseValue.value.operands;
|
|
if (operands && operands.length > 0) {
|
|
const strOperand = operands[0];
|
|
if (strOperand && strOperand.value) {
|
|
const str = this.evaluateValue(strOperand.value);
|
|
if (str && typeof str === 'string') {
|
|
cases.push(str);
|
|
}
|
|
}
|
|
}
|
|
} else if (caseValue.type === 'def' && typeof caseValue.value === 'string') {
|
|
const caseDef = this.parser.getDef(caseValue.value) || this.parser.getClass(caseValue.value);
|
|
if (caseDef) {
|
|
const str = caseDef.getValueAsString('str');
|
|
if (str) {
|
|
cases.push(str);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return cases.length > 0 ? cases : null;
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
evaluateValue(value, visited = new Set()) {
|
|
if (!value) {
|
|
return null;
|
|
}
|
|
// Handle named values (e.g., { name: 'clauses', value: { type: 'list', ... } })
|
|
// These come from named arguments in template instantiation
|
|
if (!value.type && value.name && value.value) {
|
|
return this.evaluateValue(value.value, visited);
|
|
}
|
|
switch (value.type) {
|
|
case 'string':
|
|
return value.value.replace(/^"|"$/g, '');
|
|
case 'code':
|
|
return value.value;
|
|
case 'int':
|
|
return parseInt(value.value, 10);
|
|
case 'bit':
|
|
return value.value === 'true' || value.value === '1';
|
|
case 'list':
|
|
return value.value.map((v) => this.evaluateValue(v, visited));
|
|
case 'concat': {
|
|
const parts = value.value.map((v) => this.evaluateValue(v, visited)).filter((v) => v !== null && v !== undefined && v !== '');
|
|
return parts.join('');
|
|
}
|
|
case 'id': {
|
|
const fieldName = value.value;
|
|
// Guard against circular references
|
|
const idKey = `id:${fieldName}`;
|
|
if (visited.has(idKey)) {
|
|
return null;
|
|
}
|
|
visited.add(idKey);
|
|
if (this.templateBindings.has(fieldName)) {
|
|
return this.evaluateValue(this.templateBindings.get(fieldName), visited);
|
|
}
|
|
const field = this.getValue(fieldName);
|
|
if (field && field.value) {
|
|
return this.evaluateValue(field.value, visited);
|
|
}
|
|
return null;
|
|
}
|
|
case 'def': {
|
|
const defName = typeof value.value === 'string' ? value.value : value.value.value;
|
|
// Handle TableGen builtin constants
|
|
if (defName === 'true') {
|
|
return true;
|
|
}
|
|
if (defName === 'false') {
|
|
return false;
|
|
}
|
|
// Guard against circular references
|
|
const defKey = `def:${defName}`;
|
|
if (visited.has(defKey)) {
|
|
return defName; // Return the def name itself to break the cycle
|
|
}
|
|
visited.add(defKey);
|
|
if (defName.includes('.')) {
|
|
const parts = defName.split('.');
|
|
const [baseName, ...fieldPath] = parts;
|
|
let baseValue = null;
|
|
if (this.templateBindings.has(baseName)) {
|
|
baseValue = this.evaluateValue(this.templateBindings.get(baseName), visited);
|
|
} else {
|
|
const field = this.getValue(baseName);
|
|
if (field && field.value) {
|
|
baseValue = this.evaluateValue(field.value, visited);
|
|
}
|
|
}
|
|
if (typeof baseValue === 'string') {
|
|
const def = this.parser.getDef(baseValue) || this.parser.getClass(baseValue);
|
|
if (def) {
|
|
let current = def;
|
|
for (const fieldName of fieldPath) {
|
|
const field = current.getValue(fieldName);
|
|
if (!field || !field.value) {
|
|
return null;
|
|
}
|
|
const evaluated = current.evaluateValue(field.value, visited);
|
|
// If it's another def name, continue navigation
|
|
if (typeof evaluated === 'string' && (this.parser.getDef(evaluated) || this.parser.getClass(evaluated))) {
|
|
current = this.parser.getDef(evaluated) || this.parser.getClass(evaluated);
|
|
} else {
|
|
return evaluated;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
// Handle DAG values representing class instantiations with template args
|
|
// e.g., OpenMP_MapClauseSkip<assemblyFormat = true> is stored as a DAG
|
|
if (baseValue && typeof baseValue === 'object' && baseValue.operator) {
|
|
const className = baseValue.operator;
|
|
const baseClass = this.parser.getClass(className);
|
|
if (baseClass) {
|
|
const templateArgs = baseValue.operands ? baseValue.operands.map((op) => {
|
|
if (op.name && op.value) {
|
|
return op;
|
|
}
|
|
return op.value || op;
|
|
}) : [];
|
|
const instantiated = new tablegen.Record(`<anonymous ${className}>`, this.parser);
|
|
instantiated.parents = [{ name: className, args: templateArgs }];
|
|
this.parser.addSubClass(instantiated);
|
|
instantiated.resolveReferences();
|
|
let current = instantiated;
|
|
for (const fieldName of fieldPath) {
|
|
const field = current.getValue(fieldName);
|
|
if (!field || !field.value) {
|
|
return null;
|
|
}
|
|
const evaluated = current.evaluateValue(field.value, visited);
|
|
if (typeof evaluated === 'string' && (this.parser.getDef(evaluated) || this.parser.getClass(evaluated))) {
|
|
current = this.parser.getDef(evaluated) || this.parser.getClass(evaluated);
|
|
} else {
|
|
return evaluated;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
if (this.templateBindings.has(defName)) {
|
|
return this.evaluateValue(this.templateBindings.get(defName), visited);
|
|
}
|
|
const field = this.getValue(defName);
|
|
if (field && field.value) {
|
|
return this.evaluateValue(field.value, visited);
|
|
}
|
|
const def = this.parser.getDef(defName) || this.parser.getClass(defName);
|
|
return def ? defName : null;
|
|
}
|
|
case 'bang': {
|
|
const { op, args } = value.value;
|
|
switch (op) {
|
|
case 'if': {
|
|
if (args.length < 2) {
|
|
return null;
|
|
}
|
|
const condition = this.evaluateValue(args[0], visited);
|
|
if (condition) {
|
|
return args.length > 1 ? this.evaluateValue(args[1], visited) : null;
|
|
}
|
|
return args.length > 2 ? this.evaluateValue(args[2], visited) : null;
|
|
}
|
|
case 'empty': {
|
|
if (args.length < 1) {
|
|
return true;
|
|
}
|
|
const val = this.evaluateValue(args[0], visited);
|
|
if (Array.isArray(val)) {
|
|
return val.length === 0;
|
|
}
|
|
if (typeof val === 'string') {
|
|
return val.length === 0;
|
|
}
|
|
return val === null || val === undefined;
|
|
}
|
|
case 'interleave': {
|
|
if (args.length < 2) {
|
|
return '';
|
|
}
|
|
const list = this.evaluateValue(args[0], visited);
|
|
const separator = this.evaluateValue(args[1], visited);
|
|
if (!Array.isArray(list)) {
|
|
return '';
|
|
}
|
|
return list.filter((x) => x !== null && x !== '').join(separator || '');
|
|
}
|
|
case 'not':
|
|
if (args.length < 1) {
|
|
return true;
|
|
}
|
|
return !this.evaluateValue(args[0], visited);
|
|
case 'or':
|
|
for (const arg of args) {
|
|
if (this.evaluateValue(arg, visited)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
case 'and':
|
|
for (const arg of args) {
|
|
if (!this.evaluateValue(arg, visited)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
case 'foldl': {
|
|
// !foldl(init, list, acc, item, expr)
|
|
// Fold left: iterate over list, accumulating results
|
|
if (args.length < 5) {
|
|
return null;
|
|
}
|
|
let accumulator = this.evaluateValue(args[0], visited); // init value
|
|
const list = this.evaluateValue(args[1], visited); // list to fold over
|
|
// args[2] is the accumulator variable name (not evaluated)
|
|
// args[3] is the item variable name (not evaluated)
|
|
// args[4] is the expression to evaluate
|
|
if (!Array.isArray(list)) {
|
|
return accumulator;
|
|
}
|
|
for (const item of list) {
|
|
const accName = args[2].value; // variable name
|
|
const itemName = args[3].value; // variable name
|
|
const prevAcc = this.fields.get(accName);
|
|
const prevItem = this.fields.get(itemName);
|
|
let accValue = null;
|
|
if (accumulator && typeof accumulator === 'object' && accumulator.operator) {
|
|
// It's a DAG object
|
|
accValue = new tablegen.Value('dag', accumulator);
|
|
} else if (Array.isArray(accumulator)) {
|
|
// It's a list
|
|
accValue = new tablegen.Value('list', accumulator);
|
|
} else if (typeof accumulator === 'string') {
|
|
accValue = new tablegen.Value('string', accumulator);
|
|
} else if (typeof accumulator === 'number') {
|
|
accValue = new tablegen.Value('int', accumulator);
|
|
} else {
|
|
accValue = accumulator;
|
|
}
|
|
|
|
this.fields.set(accName, new tablegen.RecordVal(accName, null, accValue));
|
|
let itemValue = item;
|
|
if (typeof item === 'string') {
|
|
itemValue = new tablegen.Value('def', item);
|
|
} else if (typeof item === 'number') {
|
|
itemValue = new tablegen.Value('int', item);
|
|
} else if (typeof item === 'boolean') {
|
|
itemValue = new tablegen.Value('bit', item ? '1' : '0');
|
|
} else if (item && typeof item === 'object' && !item.type) {
|
|
itemValue = new tablegen.Value('dag', item);
|
|
}
|
|
this.fields.set(itemName, new tablegen.RecordVal(itemName, null, itemValue));
|
|
accumulator = this.evaluateValue(args[4], new Set()); // Fresh visited set for each iteration
|
|
if (prevAcc) {
|
|
this.fields.set(accName, prevAcc);
|
|
} else {
|
|
this.fields.delete(accName);
|
|
}
|
|
if (prevItem) {
|
|
this.fields.set(itemName, prevItem);
|
|
} else {
|
|
this.fields.delete(itemName);
|
|
}
|
|
}
|
|
return accumulator;
|
|
}
|
|
case 'foreach': {
|
|
// !foreach(item, list, expr)
|
|
// Map: iterate over list, transforming each item
|
|
if (args.length < 3) {
|
|
return [];
|
|
}
|
|
const itemName = args[0].value;
|
|
const list = this.evaluateValue(args[1], visited);
|
|
const results = [];
|
|
if (Array.isArray(list)) {
|
|
for (const item of list) {
|
|
const prevItem = this.fields.get(itemName);
|
|
let itemValue = item;
|
|
if (typeof item === 'string') {
|
|
itemValue = new tablegen.Value('def', item);
|
|
} else if (typeof item === 'number') {
|
|
itemValue = new tablegen.Value('int', item);
|
|
} else if (typeof item === 'boolean') {
|
|
itemValue = new tablegen.Value('bit', item ? '1' : '0');
|
|
} else if (item && typeof item === 'object' && !item.type) {
|
|
itemValue = new tablegen.Value('dag', item);
|
|
}
|
|
this.fields.set(itemName, new tablegen.RecordVal(itemName, null, itemValue));
|
|
const result = this.evaluateValue(args[2], new Set()); // Fresh visited set for each iteration
|
|
results.push(result);
|
|
if (prevItem) {
|
|
this.fields.set(itemName, prevItem);
|
|
} else {
|
|
this.fields.delete(itemName);
|
|
}
|
|
}
|
|
}
|
|
return results;
|
|
}
|
|
case 'filter': {
|
|
// !filter(item, list, predicate)
|
|
// Filter: keep items where predicate is true
|
|
if (args.length < 3) {
|
|
return [];
|
|
}
|
|
const itemName = args[0].value;
|
|
const list = this.evaluateValue(args[1], visited);
|
|
const results = [];
|
|
if (Array.isArray(list)) {
|
|
for (const item of list) {
|
|
const prevItem = this.fields.get(itemName);
|
|
// Wrap item in a Value so it can be used in expressions
|
|
let itemValue = item;
|
|
if (typeof item === 'string') {
|
|
// If it's a def name, wrap it as a 'def' Value
|
|
itemValue = new tablegen.Value('def', item);
|
|
} else if (typeof item === 'number') {
|
|
itemValue = new tablegen.Value('int', item);
|
|
} else if (typeof item === 'boolean') {
|
|
itemValue = new tablegen.Value('bit', item ? '1' : '0');
|
|
} else if (item && typeof item === 'object' && !item.type) {
|
|
// If it's a raw object (like a DAG), wrap it
|
|
itemValue = new tablegen.Value('dag', item);
|
|
}
|
|
// If item is already a Value, use it as is
|
|
this.fields.set(itemName, new tablegen.RecordVal(itemName, null, itemValue));
|
|
const keep = this.evaluateValue(args[2], new Set()); // Fresh visited set for each iteration
|
|
if (keep) {
|
|
results.push(item);
|
|
}
|
|
if (prevItem) {
|
|
this.fields.set(itemName, prevItem);
|
|
} else {
|
|
this.fields.delete(itemName);
|
|
}
|
|
}
|
|
}
|
|
return results;
|
|
}
|
|
case 'con': {
|
|
// !con(dag1, dag2, ...)
|
|
// Concatenate dags - merge operands from multiple dags
|
|
if (args.length === 0) {
|
|
return new tablegen.DAG('ins', []);
|
|
}
|
|
let operator = 'ins';
|
|
const allOperands = [];
|
|
for (const arg of args) {
|
|
let dagToProcess = null;
|
|
const evaluated = this.evaluateValue(arg, visited);
|
|
if (evaluated && typeof evaluated === 'object') {
|
|
if (evaluated.operator && evaluated.operands) {
|
|
dagToProcess = evaluated;
|
|
} else if (evaluated.type === 'dag' && evaluated.value) {
|
|
dagToProcess = evaluated.value;
|
|
}
|
|
}
|
|
if (!dagToProcess && arg.type === 'dag') {
|
|
dagToProcess = arg.value;
|
|
}
|
|
if (dagToProcess && dagToProcess.operands) {
|
|
if (operator === 'ins' && dagToProcess.operator) {
|
|
operator = dagToProcess.operator;
|
|
}
|
|
allOperands.push(...dagToProcess.operands);
|
|
}
|
|
}
|
|
return new tablegen.DAG(operator, allOperands);
|
|
}
|
|
case 'listconcat': {
|
|
// !listconcat(list1, list2, ...)
|
|
// Concatenate multiple lists into one
|
|
const result = [];
|
|
for (const arg of args) {
|
|
const list = this.evaluateValue(arg, visited);
|
|
if (Array.isArray(list)) {
|
|
result.push(...list);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
case 'listremove': {
|
|
// !listremove(list, items_to_remove)
|
|
// Remove all occurrences of items_to_remove from list
|
|
if (args.length < 2) {
|
|
return [];
|
|
}
|
|
const list = this.evaluateValue(args[0], visited);
|
|
const toRemove = this.evaluateValue(args[1], visited);
|
|
if (!Array.isArray(list)) {
|
|
return [];
|
|
}
|
|
const removeSet = new Set();
|
|
if (Array.isArray(toRemove)) {
|
|
for (const item of toRemove) {
|
|
removeSet.add(JSON.stringify(item));
|
|
}
|
|
} else {
|
|
removeSet.add(JSON.stringify(toRemove));
|
|
}
|
|
return list.filter((item) => !removeSet.has(JSON.stringify(item)));
|
|
}
|
|
case 'cast': {
|
|
// !cast<type>(value)
|
|
// Cast value to type - for now just convert to string
|
|
if (args.length < 1) {
|
|
return null;
|
|
}
|
|
const val = this.evaluateValue(args[0], visited);
|
|
if (val === null || val === undefined) {
|
|
return null;
|
|
}
|
|
return String(val);
|
|
}
|
|
case 'eq': {
|
|
// !eq(a, b)
|
|
// Return true if a equals b
|
|
if (args.length < 2) {
|
|
return false;
|
|
}
|
|
const a = this.evaluateValue(args[0], visited);
|
|
const b = this.evaluateValue(args[1], visited);
|
|
return a === b;
|
|
}
|
|
case 'ne': {
|
|
// !ne(a, b)
|
|
// Return true if a not equals b
|
|
if (args.length < 2) {
|
|
return false;
|
|
}
|
|
const a = this.evaluateValue(args[0], visited);
|
|
const b = this.evaluateValue(args[1], visited);
|
|
return a !== b;
|
|
}
|
|
case 'lt': {
|
|
// !lt(a, b)
|
|
// Return true if a < b
|
|
if (args.length < 2) {
|
|
return false;
|
|
}
|
|
const a = this.evaluateValue(args[0], visited);
|
|
const b = this.evaluateValue(args[1], visited);
|
|
return a < b;
|
|
}
|
|
case 'le': {
|
|
// !le(a, b)
|
|
// Return true if a <= b
|
|
if (args.length < 2) {
|
|
return false;
|
|
}
|
|
const a = this.evaluateValue(args[0], visited);
|
|
const b = this.evaluateValue(args[1], visited);
|
|
return a <= b;
|
|
}
|
|
case 'gt': {
|
|
// !gt(a, b)
|
|
// Return true if a > b
|
|
if (args.length < 2) {
|
|
return false;
|
|
}
|
|
const a = this.evaluateValue(args[0], visited);
|
|
const b = this.evaluateValue(args[1], visited);
|
|
return a > b;
|
|
}
|
|
case 'ge': {
|
|
// !ge(a, b)
|
|
// Return true if a >= b
|
|
if (args.length < 2) {
|
|
return false;
|
|
}
|
|
const a = this.evaluateValue(args[0], visited);
|
|
const b = this.evaluateValue(args[1], visited);
|
|
return a >= b;
|
|
}
|
|
case 'range': {
|
|
// !range(n) or !range(start, end) or !range(start, end, step)
|
|
// Generate a list of integers
|
|
if (args.length === 0) {
|
|
return [];
|
|
}
|
|
let start = 0;
|
|
let end = 0;
|
|
let step = 1;
|
|
if (args.length === 1) {
|
|
end = this.evaluateValue(args[0], visited);
|
|
} else if (args.length === 2) {
|
|
start = this.evaluateValue(args[0], visited);
|
|
end = this.evaluateValue(args[1], visited);
|
|
} else {
|
|
start = this.evaluateValue(args[0], visited);
|
|
end = this.evaluateValue(args[1], visited);
|
|
step = this.evaluateValue(args[2], visited);
|
|
}
|
|
const result = [];
|
|
if (step > 0) {
|
|
for (let i = start; i < end; i += step) {
|
|
result.push(i);
|
|
}
|
|
} else if (step < 0) {
|
|
for (let i = start; i > end; i += step) {
|
|
result.push(i);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
case 'listsplat': {
|
|
// !listsplat(element, n)
|
|
// Create a list with n copies of element
|
|
if (args.length < 2) {
|
|
return [];
|
|
}
|
|
const [element] = args; // Don't evaluate yet, keep as Value
|
|
const count = this.evaluateValue(args[1], visited);
|
|
const result = [];
|
|
for (let i = 0; i < count; i++) {
|
|
result.push(element);
|
|
}
|
|
return result;
|
|
}
|
|
case 'cond': {
|
|
// !cond(condition1: value1, condition2: value2, ..., true: defaultValue)
|
|
// Evaluate conditions in order, return first matching value
|
|
for (let i = 0; i < args.length; i++) {
|
|
const arg = args[i];
|
|
if (arg.condition) {
|
|
const condition = this.evaluateValue(arg.condition, visited);
|
|
if (condition === true || condition === 1) {
|
|
return this.evaluateValue(arg.value, visited);
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
case 'dag': {
|
|
// !dag(operator, operands_list, names_list)
|
|
// Construct a DAG from operator, operands, and names
|
|
if (args.length < 2) {
|
|
return new tablegen.DAG('ins', []);
|
|
}
|
|
const operatorArg = this.evaluateValue(args[0], visited);
|
|
const operator = typeof operatorArg === 'string' ? operatorArg : 'ins';
|
|
const operandsList = this.evaluateValue(args[1], visited);
|
|
const namesList = args.length > 2 ? this.evaluateValue(args[2], visited) : [];
|
|
const operands = [];
|
|
if (Array.isArray(operandsList)) {
|
|
for (let i = 0; i < operandsList.length; i++) {
|
|
const value = operandsList[i];
|
|
const name = Array.isArray(namesList) && i < namesList.length ? namesList[i] : '';
|
|
operands.push({ value, name });
|
|
}
|
|
}
|
|
return new tablegen.DAG(operator, operands);
|
|
}
|
|
case 'tolower': {
|
|
// !tolower(string)
|
|
// Convert string to lowercase
|
|
if (args.length < 1) {
|
|
return null;
|
|
}
|
|
const str = this.evaluateValue(args[0], visited);
|
|
if (typeof str === 'string') {
|
|
return str.toLowerCase();
|
|
}
|
|
return null;
|
|
}
|
|
case 'toupper': {
|
|
// !toupper(string)
|
|
// Convert string to uppercase
|
|
if (args.length < 1) {
|
|
return null;
|
|
}
|
|
const str = this.evaluateValue(args[0], visited);
|
|
if (typeof str === 'string') {
|
|
return str.toUpperCase();
|
|
}
|
|
return null;
|
|
}
|
|
case 'strconcat': {
|
|
// !strconcat(str1, str2, ...)
|
|
// Concatenate strings
|
|
const parts = [];
|
|
for (const arg of args) {
|
|
const val = this.evaluateValue(arg, visited);
|
|
if (val !== null && val !== undefined) {
|
|
parts.push(String(val));
|
|
}
|
|
}
|
|
return parts.join('');
|
|
}
|
|
case 'subst': {
|
|
// !subst(pattern, replacement, string)
|
|
// Replace all occurrences of pattern with replacement in string
|
|
if (args.length < 3) {
|
|
return null;
|
|
}
|
|
const pattern = this.evaluateValue(args[0], visited);
|
|
const replacement = this.evaluateValue(args[1], visited);
|
|
const str = this.evaluateValue(args[2], visited);
|
|
if (typeof str === 'string' && typeof pattern === 'string') {
|
|
const rep = replacement !== null && replacement !== undefined ? String(replacement) : '';
|
|
// Use split/join for global replacement
|
|
return str.split(pattern).join(rep);
|
|
}
|
|
return str;
|
|
}
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
case 'dag': {
|
|
const dag = value.value;
|
|
const evaluatedOperands = dag.operands.map((operand) => {
|
|
if (!operand.value) {
|
|
return operand;
|
|
}
|
|
const valType = operand.value.type;
|
|
if (valType === 'def' || valType === 'id') {
|
|
const refName = operand.value.value;
|
|
if (this.templateBindings.has(refName)) {
|
|
const evaluated = this.evaluateValue(operand.value, visited);
|
|
if (typeof evaluated === 'string') {
|
|
return {
|
|
value: { type: 'def', value: evaluated },
|
|
name: operand.name
|
|
};
|
|
}
|
|
}
|
|
}
|
|
return operand;
|
|
});
|
|
return new tablegen.DAG(dag.operator, evaluatedOperands);
|
|
}
|
|
case 'uninitialized':
|
|
return null;
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
};
|
|
|
|
tablegen.Reader = class {
|
|
|
|
constructor() {
|
|
this._paths = [];
|
|
this._includes = new Set();
|
|
this._defs = new Map();
|
|
this._defvars = new Map();
|
|
this.defs = [];
|
|
this.classes = new Map();
|
|
this.multiclasses = new Map();
|
|
}
|
|
|
|
async parse(files, paths) {
|
|
this._paths = paths || [];
|
|
for (const file of files) {
|
|
// eslint-disable-next-line no-await-in-loop
|
|
await this._parseFile(file);
|
|
}
|
|
}
|
|
|
|
getDef(name) {
|
|
// Check if this is a defvar alias and resolve it
|
|
if (this._defvars.has(name)) {
|
|
const alias = this._defvars.get(name);
|
|
if (alias && alias.type === 'def' && typeof alias.value === 'string') {
|
|
return this.getDef(alias.value) || this._defs.get(name);
|
|
}
|
|
}
|
|
return this._defs.get(name);
|
|
}
|
|
|
|
getClass(name) {
|
|
// Check if this is a defvar alias and resolve it
|
|
if (this._defvars.has(name)) {
|
|
const alias = this._defvars.get(name);
|
|
if (alias && alias.type === 'def' && typeof alias.value === 'string') {
|
|
return this.getClass(alias.value) || this.classes.get(name);
|
|
}
|
|
}
|
|
return this.classes.get(name);
|
|
}
|
|
|
|
// Add a subclass to a record, processing template parameters and copying fields
|
|
// This mirrors LLVM's TGParser::AddSubClass behavior
|
|
addSubClass(record) {
|
|
// Track which fields were explicitly defined in the def via 'let' statements
|
|
// These should never be overwritten by parent class fields
|
|
const explicitFields = new Set(record.fields.keys());
|
|
// 'let append'/'let prepend' fields concatenate with the inherited value rather
|
|
// than replacing it. Pull them out so the parent value resolves normally, then
|
|
// concatenate the child value against the fully inherited result below.
|
|
const concatFields = [];
|
|
for (const [fieldName, field] of record.fields) {
|
|
if (field.concat) {
|
|
concatFields.push({ name: fieldName, mode: field.concat, value: field.value });
|
|
explicitFields.delete(fieldName);
|
|
record.fields.delete(fieldName);
|
|
}
|
|
}
|
|
// Step 1: Build initial template bindings for this record from its immediate parents
|
|
const recordBindings = new Map();
|
|
for (const parent of record.parents) {
|
|
const parentClass = this.classes.get(parent.name);
|
|
if (parentClass && parentClass.templateArgs && parentClass.templateArgs.length > 0) {
|
|
const templateArgs = parent.args || [];
|
|
for (let i = 0; i < parentClass.templateArgs.length; i++) {
|
|
const param = parentClass.templateArgs[i];
|
|
let boundValue = null;
|
|
// Check for named argument, positional argument, or default value
|
|
const namedArg = templateArgs.find((arg) => arg.name === param.name);
|
|
if (namedArg) {
|
|
boundValue = namedArg.value;
|
|
} else if (i < templateArgs.length) {
|
|
const arg = templateArgs[i];
|
|
boundValue = arg.name ? arg.value : arg;
|
|
} else if (param.defaultValue) {
|
|
boundValue = param.defaultValue;
|
|
}
|
|
if (boundValue) {
|
|
recordBindings.set(param.name, boundValue);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
record.templateBindings = recordBindings;
|
|
// Step 2: Process parents and flatten fields
|
|
// Helper to resolve Init values using a set of bindings
|
|
// Uses record._resolveInit to avoid code duplication
|
|
const resolveInitValue = (value, bindings) => {
|
|
const resolver = {
|
|
shouldCopy: false,
|
|
resolveReference: (name) => bindings.get(name) || null
|
|
};
|
|
return record._resolveInit(value, resolver);
|
|
};
|
|
const processParent = (parent, currentBindings, visited = new Set()) => {
|
|
if (visited.has(parent.name)) {
|
|
return;
|
|
}
|
|
visited.add(parent.name);
|
|
const parentClass = this.classes.get(parent.name);
|
|
if (!parentClass) {
|
|
return;
|
|
}
|
|
const parentBindings = new Map();
|
|
if (parentClass.templateArgs && parent.args) {
|
|
// Handle named and positional arguments correctly
|
|
// Named arguments have {name, value} format and should match by name
|
|
// Positional arguments should fill remaining slots in order
|
|
const templateArgs = parent.args;
|
|
for (let i = 0; i < parentClass.templateArgs.length; i++) {
|
|
const param = parentClass.templateArgs[i];
|
|
let boundValue = null;
|
|
// Check for named argument first
|
|
const namedArg = templateArgs.find((arg) => arg.name === param.name);
|
|
if (namedArg) {
|
|
boundValue = namedArg.value;
|
|
} else if (i < templateArgs.length) {
|
|
// Positional argument - use if it's not a named arg for another param
|
|
const arg = templateArgs[i];
|
|
boundValue = arg.name ? arg.value : arg;
|
|
} else if (param.defaultValue) {
|
|
boundValue = param.defaultValue;
|
|
}
|
|
if (boundValue) {
|
|
// When resolving default values, also check parentBindings for previously bound template params
|
|
// This handles cases like: class Foo<string sym, string str = sym>
|
|
// where 'str' default value references 'sym' which was bound earlier
|
|
const combinedBindings = new Map([...currentBindings, ...parentBindings]);
|
|
const resolvedArg = resolveInitValue(boundValue, combinedBindings);
|
|
parentBindings.set(param.name, resolvedArg);
|
|
}
|
|
}
|
|
}
|
|
for (const grandparent of parentClass.parents) {
|
|
processParent(grandparent, parentBindings, visited);
|
|
}
|
|
for (const [fieldName, field] of parentClass.fields) {
|
|
// Only protect fields that were explicitly defined in the def via 'let'
|
|
// Fields inherited from grandparents should be overwritten by parent class fields
|
|
if (explicitFields.has(fieldName)) {
|
|
// Check if the explicit field is empty/uninitialized - if so, copy from parent
|
|
const existingField = record.fields.get(fieldName);
|
|
const existingIsUninit = existingField.value && existingField.value.type === 'uninitialized';
|
|
const existingIsEmptyDag = existingField.value && existingField.value.type === 'dag' && existingField.value.value && existingField.value.value.operands && existingField.value.value.operands.length === 0;
|
|
const existingIsEmptyString = existingField.value && existingField.value.type === 'string' && existingField.value.value === '';
|
|
const existingIsFalseBit = existingField.value && existingField.value.type === 'int' && existingField.value.value === 0 && existingField.type && existingField.type.name === 'bit';
|
|
if (existingIsUninit || existingIsEmptyDag || existingIsEmptyString || existingIsFalseBit) {
|
|
const resolvedField = record._copyAndResolveField(field, parentBindings, parentClass);
|
|
record.fields.set(fieldName, resolvedField);
|
|
}
|
|
// Otherwise, keep the explicit field (child's 'let' wins)
|
|
} else {
|
|
// Field is not explicit - copy from parent (overwrites grandparent values)
|
|
const resolvedField = record._copyAndResolveField(field, parentBindings, parentClass);
|
|
record.fields.set(fieldName, resolvedField);
|
|
}
|
|
}
|
|
};
|
|
for (const parent of record.parents) {
|
|
processParent(parent, recordBindings, new Set());
|
|
}
|
|
// Concatenate 'let append'/'let prepend' values against the inherited value.
|
|
for (const { name, mode, value } of concatFields) {
|
|
const inherited = record.fields.get(name);
|
|
if (inherited && inherited.value) {
|
|
const parts = mode === 'prepend' ?
|
|
[value, inherited.value] : [inherited.value, value];
|
|
inherited.value = new tablegen.Value('concat', parts);
|
|
} else {
|
|
record.fields.set(name, new tablegen.RecordVal(name, null, value));
|
|
}
|
|
}
|
|
}
|
|
|
|
async access(file) {
|
|
try {
|
|
await fs.access(file);
|
|
return true;
|
|
} catch {
|
|
// continue regardless of error
|
|
}
|
|
return false;
|
|
}
|
|
|
|
async _parseFile(file) {
|
|
let location = null;
|
|
for (const current of this._paths) {
|
|
const test = path.join(current, file);
|
|
// eslint-disable-next-line no-await-in-loop
|
|
if (await this.access(test)) {
|
|
location = path.resolve(test);
|
|
break;
|
|
}
|
|
}
|
|
if (!location) {
|
|
throw new tablegen.Error(`File not found '${file}'.`);
|
|
}
|
|
if (!this._includes.has(location)) {
|
|
this._includes.add(location);
|
|
const content = await fs.readFile(location, 'utf-8');
|
|
this._tokenizer = new tablegen.Tokenizer(content, location);
|
|
while (!this._match('eof')) {
|
|
const token = this._tokenizer.current();
|
|
if (token.type === 'keyword') {
|
|
switch (token.value) {
|
|
case 'include':
|
|
// eslint-disable-next-line no-await-in-loop
|
|
await this._parseInclude();
|
|
break;
|
|
case 'class': this._parseClass(); break;
|
|
case 'def': this._parseDef(); break;
|
|
case 'defm': this._parseDefm(); break;
|
|
case 'let': this._parseLet(); break;
|
|
case 'multiclass': this._parseMulticlass(); break;
|
|
case 'defvar': this._parseDefvar(); break;
|
|
case 'defset': this._parseDefset(); break;
|
|
case 'foreach': this._parseForeach(); break;
|
|
default:
|
|
this._read();
|
|
break;
|
|
}
|
|
} else {
|
|
this._read();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
async _parseInclude() {
|
|
this._read();
|
|
const file = this._expect('string');
|
|
const tokenizer = this._tokenizer;
|
|
await this._parseFile(file);
|
|
this._tokenizer = tokenizer;
|
|
}
|
|
|
|
_parseClass() {
|
|
this._read();
|
|
let name = null;
|
|
if (this._match('id')) {
|
|
name = this._expect('id');
|
|
} else if (this._match('number')) {
|
|
name = String(this._expect('number'));
|
|
} else {
|
|
throw new tablegen.Error(`Expected class name but got '${this._tokenizer.current().type}' at ${this._tokenizer.location()}`);
|
|
}
|
|
const record = new tablegen.Record(name, this);
|
|
record.location = this._tokenizer.location();
|
|
if (this._match('<')) {
|
|
record.templateArgs = this._parseTemplateParams();
|
|
}
|
|
if (this._match(':')) {
|
|
record.parents = this._parseParentClassList();
|
|
}
|
|
// Don't process parent classes for class definitions - only for defs/instances
|
|
// Classes are templates that get instantiated later with concrete template arguments
|
|
// Processing parents here with unbound template parameters causes incorrect resolutions
|
|
// this.addSubClass(record);
|
|
if (this._match('{')) {
|
|
this._parseRecordBody(record);
|
|
}
|
|
this.classes.set(name, record);
|
|
}
|
|
|
|
_parseDef() {
|
|
this._read();
|
|
let name = '';
|
|
if (this._match('id')) {
|
|
name = this._read();
|
|
} else if (this._match('number')) {
|
|
name = this._read().toString();
|
|
}
|
|
const def = new tablegen.Record(name, this);
|
|
def.location = this._tokenizer.location();
|
|
if (this._match(':')) {
|
|
def.parents = this._parseParentClassList();
|
|
}
|
|
if (this._match('{')) {
|
|
this._parseRecordBody(def);
|
|
}
|
|
this.addSubClass(def);
|
|
def.resolveReferences();
|
|
if (name) {
|
|
this._defs.set(name, def);
|
|
this.defs.push(def);
|
|
}
|
|
}
|
|
|
|
_parseDefm() {
|
|
this._read(); // consume 'defm'
|
|
// Parse the defm name prefix (may be empty or a concatenated name)
|
|
const namePrefix = [];
|
|
while (!this._match(':') && !this._match(';') && !this._match('eof')) {
|
|
if (this._match('id')) {
|
|
const value = this._read();
|
|
if (this._eat('.')) {
|
|
const field = this._expect('id');
|
|
namePrefix.push({ type: 'field_access', base: value, field });
|
|
} else {
|
|
namePrefix.push({ type: 'id', value });
|
|
}
|
|
} else if (this._match('string')) {
|
|
namePrefix.push({ type: 'string', value: this._read() });
|
|
} else if (this._match('number')) {
|
|
namePrefix.push({ type: 'number', value: this._read() });
|
|
} else if (this._eat('#')) {
|
|
namePrefix.push({ type: 'concat' });
|
|
} else if (this._match('!')) {
|
|
const bangValue = this._parseValue();
|
|
if (bangValue && bangValue.type === 'bang') {
|
|
namePrefix.push({ type: 'bang', value: bangValue.value });
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
// Parse the parent multiclass references
|
|
let parents = [];
|
|
if (this._match(':')) {
|
|
parents = this._parseParentClassList();
|
|
}
|
|
this._eat(';');
|
|
// Instantiate each parent multiclass
|
|
for (const parent of parents) {
|
|
this._instantiateMulticlass(parent, namePrefix, new Map());
|
|
}
|
|
}
|
|
|
|
_instantiateMulticlass(parent, namePrefix, substitutions) {
|
|
const multiclass = this.multiclasses.get(parent.name);
|
|
if (!multiclass) {
|
|
// Might be a regular class, not a multiclass - skip
|
|
return;
|
|
}
|
|
// Build template argument bindings
|
|
const bindings = new Map(substitutions);
|
|
if (multiclass.templateArgs && parent.args) {
|
|
for (let i = 0; i < multiclass.templateArgs.length && i < parent.args.length; i++) {
|
|
const param = multiclass.templateArgs[i];
|
|
const arg = parent.args[i];
|
|
// Handle both {name, value} format (named arg) and tablegen.Value format (positional arg)
|
|
// For named args like {name: 'foo', value: <Value>}, use the value property
|
|
// For positional args that are already Values, use them directly
|
|
let argValue = null;
|
|
if (arg && arg.name && arg.value !== undefined) {
|
|
// Named argument: {name: 'foo', value: <Value>}
|
|
argValue = arg.value;
|
|
} else if (arg && arg.type !== undefined) {
|
|
// This is a tablegen.Value object, use it directly
|
|
argValue = arg;
|
|
} else {
|
|
argValue = arg;
|
|
}
|
|
bindings.set(param.name, argValue);
|
|
}
|
|
}
|
|
// Process inherited parent multiclasses first
|
|
for (const mcParent of multiclass.parents) {
|
|
const resolvedParent = this._resolveMulticlassParent(mcParent, bindings);
|
|
this._instantiateMulticlass(resolvedParent, namePrefix, bindings);
|
|
}
|
|
// Process entries
|
|
for (const entry of multiclass.entries) {
|
|
if (entry.type === 'def') {
|
|
this._instantiateMulticlassDef(entry.data, namePrefix, bindings);
|
|
} else if (entry.type === 'defm') {
|
|
// Nested defm - instantiate the referenced multiclass(es)
|
|
for (const defmParent of entry.data.parents) {
|
|
const resolvedParent = this._resolveMulticlassParent(defmParent, bindings);
|
|
const combinedPrefix = [...namePrefix, ...entry.data.nameTemplate];
|
|
this._instantiateMulticlass(resolvedParent, combinedPrefix, bindings);
|
|
}
|
|
} else if (entry.type === 'foreach') {
|
|
this._resolveMulticlassForeach(entry.data, namePrefix, bindings);
|
|
} else if (entry.type === 'defvar') {
|
|
const value = this._evaluateDefvar(entry.data.value, bindings);
|
|
bindings.set(entry.data.name, value);
|
|
}
|
|
}
|
|
}
|
|
|
|
_resolveMulticlassParent(parent, bindings) {
|
|
// Resolve template arguments in parent reference
|
|
const resolvedArgs = [];
|
|
if (parent.args) {
|
|
for (const arg of parent.args) {
|
|
const argValue = arg.value === undefined ? arg : arg.value;
|
|
if (argValue && argValue.type === 'def' && bindings.has(argValue.value)) {
|
|
resolvedArgs.push({ value: bindings.get(argValue.value) });
|
|
} else {
|
|
resolvedArgs.push(arg);
|
|
}
|
|
}
|
|
}
|
|
return { name: parent.name, args: resolvedArgs };
|
|
}
|
|
|
|
_resolveMulticlassForeach(loop, namePrefix, bindings) {
|
|
if (loop.isConditional) {
|
|
const conditionResult = this._evaluateCondition(loop.condition, bindings);
|
|
if (conditionResult === false || conditionResult === null) {
|
|
return;
|
|
}
|
|
for (const entry of loop.entries) {
|
|
if (entry.type === 'def') {
|
|
this._instantiateMulticlassDef(entry.data, namePrefix, bindings);
|
|
} else if (entry.type === 'foreach') {
|
|
this._resolveMulticlassForeach(entry.data, namePrefix, bindings);
|
|
} else if (entry.type === 'defvar') {
|
|
const value = this._evaluateDefvar(entry.data.value, bindings);
|
|
bindings.set(entry.data.name, value);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
if (!loop.listValue || loop.listValue.length === 0) {
|
|
return;
|
|
}
|
|
for (const listItem of loop.listValue) {
|
|
const currentBindings = new Map(bindings);
|
|
if (loop.iterVarName) {
|
|
currentBindings.set(loop.iterVarName, listItem);
|
|
}
|
|
for (const entry of loop.entries) {
|
|
if (entry.type === 'def') {
|
|
this._instantiateMulticlassDef(entry.data, namePrefix, currentBindings);
|
|
} else if (entry.type === 'foreach') {
|
|
this._resolveMulticlassForeach(entry.data, namePrefix, currentBindings);
|
|
} else if (entry.type === 'defvar') {
|
|
const value = this._evaluateDefvar(entry.data.value, currentBindings);
|
|
currentBindings.set(entry.data.name, value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
_resolveTemplateValue(value, bindings) {
|
|
if (!value) {
|
|
return value;
|
|
}
|
|
if (value.type === 'string' || value.type === 'int' || value.type === 'code') {
|
|
return value;
|
|
}
|
|
if (value.type === 'def' || value.type === 'id') {
|
|
const name = typeof value.value === 'string' ? value.value : (value.value && value.value.value);
|
|
if (name && bindings.has(name)) {
|
|
return bindings.get(name);
|
|
}
|
|
return value;
|
|
}
|
|
if (value.type === 'bang') {
|
|
const resolved = this._evaluateDefvar(value, bindings);
|
|
return resolved;
|
|
}
|
|
if (value.type === 'list' && Array.isArray(value.value)) {
|
|
return new tablegen.Value('list', value.value.map((v) => this._resolveTemplateValue(v, bindings)));
|
|
}
|
|
return value;
|
|
}
|
|
|
|
_instantiateMulticlassDef(template, namePrefix, bindings) {
|
|
// Build the full name from prefix + template name
|
|
const fullNameTemplate = [...namePrefix, ...template.nameTemplate];
|
|
// Resolve name template to get the actual def name
|
|
let name = '';
|
|
for (const part of fullNameTemplate) {
|
|
if (part.type === 'concat') {
|
|
continue;
|
|
}
|
|
if (part.type === 'string') {
|
|
name += String(part.value).replace(/^"|"$/g, '');
|
|
} else if (part.type === 'number') {
|
|
name += String(part.value);
|
|
} else if (part.type === 'id') {
|
|
if (bindings.has(part.value)) {
|
|
const resolved = bindings.get(part.value);
|
|
if (resolved && resolved.type === 'string') {
|
|
name += String(resolved.value).replace(/^"|"$/g, '');
|
|
} else if (resolved && resolved.type === 'int') {
|
|
name += String(resolved.value);
|
|
} else if (typeof resolved === 'string') {
|
|
name += resolved;
|
|
} else {
|
|
name += part.value;
|
|
}
|
|
} else {
|
|
name += part.value;
|
|
}
|
|
} else if (part.type === 'field_access') {
|
|
const baseName = part.base;
|
|
if (bindings.has(baseName)) {
|
|
const baseValue = bindings.get(baseName);
|
|
if (baseValue && baseValue.type === 'record_instance' && baseValue.value && baseValue.value.fields) {
|
|
const fieldValue = baseValue.value.fields.get(part.field);
|
|
if (fieldValue && fieldValue.type === 'string') {
|
|
name += String(fieldValue.value).replace(/^"|"$/g, '');
|
|
}
|
|
}
|
|
}
|
|
} else if (part.type === 'bang') {
|
|
const evaluated = this._evaluateDefvar({ type: 'bang', value: part.value }, bindings);
|
|
if (evaluated && evaluated.type === 'string') {
|
|
name += String(evaluated.value).replace(/^"|"$/g, '');
|
|
}
|
|
}
|
|
}
|
|
// Skip if already defined (avoid duplicates)
|
|
if (this._defs.has(name)) {
|
|
return;
|
|
}
|
|
// Create the def record
|
|
const def = new tablegen.Record(name, this);
|
|
def.location = this._tokenizer ? this._tokenizer.location() : null;
|
|
// Store template bindings in the def for later resolution
|
|
def.templateBindings = new Map(bindings);
|
|
// Resolve parent classes with template bindings
|
|
const resolvedParents = [];
|
|
for (const parent of template.parents) {
|
|
const resolvedArgs = [];
|
|
if (parent.args) {
|
|
for (const arg of parent.args) {
|
|
// arg could be:
|
|
// - A tablegen.Value object: {type: '...', value: ...}
|
|
// - A named arg: {name: '...', value: <Value>}
|
|
let argValue = null;
|
|
let argName = null;
|
|
if (arg && arg.type !== undefined) {
|
|
// This is a tablegen.Value, use it directly
|
|
argValue = arg;
|
|
argName = null;
|
|
} else if (arg && arg.name && arg.value !== undefined) {
|
|
// This is a named arg, extract the value
|
|
argValue = arg.value;
|
|
argName = arg.name;
|
|
} else {
|
|
argValue = arg;
|
|
argName = null;
|
|
}
|
|
// Resolve the argument value with template bindings
|
|
const resolved = this._resolveTemplateValue(argValue, bindings);
|
|
// Push in format expected by addSubClass: named args as {name, value}, others as direct Value
|
|
if (argName) {
|
|
resolvedArgs.push({ name: argName, value: resolved });
|
|
} else {
|
|
resolvedArgs.push(resolved);
|
|
}
|
|
}
|
|
}
|
|
resolvedParents.push({ name: parent.name, args: resolvedArgs });
|
|
}
|
|
def.parents = resolvedParents;
|
|
// Copy body fields and resolve them
|
|
if (template.bodyFields) {
|
|
for (const [fieldName, field] of template.bodyFields) {
|
|
const resolvedValue = this._resolveTemplateValue(field.value, bindings);
|
|
def.fields.set(fieldName, new tablegen.RecordVal(fieldName, field.type, resolvedValue));
|
|
}
|
|
}
|
|
this.addSubClass(def);
|
|
def.resolveReferences();
|
|
if (name) {
|
|
this._defs.set(name, def);
|
|
this.defs.push(def);
|
|
}
|
|
}
|
|
|
|
_parseMulticlass() {
|
|
this._read(); // consume 'multiclass'
|
|
const name = this._expect('id');
|
|
const multiclass = {
|
|
name,
|
|
templateArgs: [],
|
|
parents: [],
|
|
entries: []
|
|
};
|
|
// Parse template parameters
|
|
if (this._match('<')) {
|
|
multiclass.templateArgs = this._parseTemplateParams();
|
|
}
|
|
// Parse parent multiclass references
|
|
if (this._match(':')) {
|
|
multiclass.parents = this._parseParentClassList();
|
|
}
|
|
// Parse multiclass body
|
|
if (this._eat('{')) {
|
|
while (!this._match('}') && !this._match('eof')) {
|
|
const token = this._tokenizer.current();
|
|
if (token.type === 'keyword') {
|
|
switch (token.value) {
|
|
case 'def':
|
|
multiclass.entries.push({ type: 'def', data: this._parseDefTemplate() });
|
|
break;
|
|
case 'defm':
|
|
multiclass.entries.push({ type: 'defm', data: this._parseDefmTemplate() });
|
|
break;
|
|
case 'foreach':
|
|
multiclass.entries.push({ type: 'foreach', data: this._parseForeachTemplate() });
|
|
break;
|
|
case 'if':
|
|
multiclass.entries.push({ type: 'foreach', data: this._parseIfAsLoop() });
|
|
break;
|
|
case 'let':
|
|
this._parseLet();
|
|
break;
|
|
case 'defvar':
|
|
multiclass.entries.push({ type: 'defvar', data: this._parseDefvarTemplate() });
|
|
break;
|
|
default:
|
|
this._read();
|
|
break;
|
|
}
|
|
} else {
|
|
this._read();
|
|
}
|
|
}
|
|
this._expect('}');
|
|
}
|
|
this.multiclasses.set(name, multiclass);
|
|
}
|
|
|
|
_parseDefmTemplate() {
|
|
this._read(); // consume 'defm'
|
|
const nameTemplate = [];
|
|
// Parse name template (similar to _parseDefTemplate but for nested defm)
|
|
while (!this._match(':') && !this._match(';') && !this._match('eof')) {
|
|
if (this._match('id')) {
|
|
const value = this._read();
|
|
if (this._eat('.')) {
|
|
const field = this._expect('id');
|
|
nameTemplate.push({ type: 'field_access', base: value, field });
|
|
} else {
|
|
nameTemplate.push({ type: 'id', value });
|
|
}
|
|
} else if (this._match('string')) {
|
|
nameTemplate.push({ type: 'string', value: this._read() });
|
|
} else if (this._match('number')) {
|
|
nameTemplate.push({ type: 'number', value: this._read() });
|
|
} else if (this._eat('#')) {
|
|
nameTemplate.push({ type: 'concat' });
|
|
} else if (this._match('!')) {
|
|
const bangValue = this._parseValue();
|
|
if (bangValue && bangValue.type === 'bang') {
|
|
nameTemplate.push({ type: 'bang', value: bangValue.value });
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
let parents = [];
|
|
if (this._match(':')) {
|
|
parents = this._parseParentClassList();
|
|
}
|
|
this._eat(';');
|
|
return { nameTemplate, parents };
|
|
}
|
|
|
|
_parseLet() {
|
|
this._read(); // consume 'let'
|
|
// Skip until we find 'in' keyword, '{', or ';'
|
|
while (!this._match('eof') && !this._match('keyword', 'in') && !this._match('{') && !this._match(';')) {
|
|
this._read();
|
|
}
|
|
if (this._match('keyword', 'in')) {
|
|
this._read(); // consume 'in'
|
|
// After 'in', we can have:
|
|
// - '{' for a block of statements
|
|
// - ';' for no body
|
|
// - A statement like 'def', 'defm', etc.
|
|
if (this._match('{')) {
|
|
this._read();
|
|
// Parse the contents of the let block - don't skip them!
|
|
// This handles 'let opDocGroup = ... in { def X, def Y, ... }'
|
|
this._parseLetBlockBody();
|
|
this._expect('}');
|
|
} else if (this._eat(';')) {
|
|
// Just a let declaration, no body
|
|
}
|
|
// Otherwise, fall through to let the main loop handle the next statement
|
|
// (e.g., 'let ... in def X' - the def will be parsed by the main loop)
|
|
} else {
|
|
this._eat('{');
|
|
this._eat(';');
|
|
}
|
|
}
|
|
|
|
_parseLetBlockBody() {
|
|
// Parse statements inside a 'let ... in { ... }' block
|
|
while (!this._match('}') && !this._match('eof')) {
|
|
const token = this._tokenizer.current();
|
|
if (token.type === 'keyword') {
|
|
switch (token.value) {
|
|
case 'def': this._parseDef(); break;
|
|
case 'defm': this._parseDefm(); break;
|
|
case 'let': this._parseLet(); break;
|
|
case 'multiclass': this._parseMulticlass(); break;
|
|
case 'defvar': this._parseDefvar(); break;
|
|
case 'defset': this._parseDefset(); break;
|
|
case 'foreach': this._parseForeach(); break;
|
|
case 'class': this._parseClass(); break;
|
|
default:
|
|
this._read();
|
|
break;
|
|
}
|
|
} else {
|
|
this._read();
|
|
}
|
|
}
|
|
}
|
|
|
|
_parseDefvar() {
|
|
this._read(); // consume 'defvar'
|
|
const name = this._expect('id'); // variable name
|
|
this._expect('=');
|
|
// Parse and store the value - needed for resolving type aliases like
|
|
// defvar Util_GlobalRefAttr = FlatSymbolRefAttr;
|
|
const value = this._parseValue();
|
|
this._expect(';');
|
|
// Store defvar for alias resolution (e.g., type constraint lookups)
|
|
if (name && value) {
|
|
this._defvars.set(name, value);
|
|
}
|
|
}
|
|
|
|
_parseDefset() {
|
|
this._read();
|
|
let depth = 0;
|
|
while (!this._match('eof')) {
|
|
if (this._eat('{')) {
|
|
depth++;
|
|
} else if (this._eat('}')) {
|
|
depth--;
|
|
if (depth === 0) {
|
|
break;
|
|
}
|
|
} else {
|
|
this._read();
|
|
}
|
|
}
|
|
}
|
|
|
|
_parseForeach() {
|
|
const location = this._tokenizer.location();
|
|
this._read();
|
|
const iterVarName = this._expect('id');
|
|
this._expect('=');
|
|
const listValue = this._parseForeachListValue();
|
|
this._expect('keyword', 'in');
|
|
const loop = { location, iterVarName, listValue, entries: [] };
|
|
if (this._match('{')) {
|
|
this._read();
|
|
this._parseForeachBody(loop);
|
|
this._expect('}');
|
|
} else {
|
|
this._parseForeachBodyStatement(loop);
|
|
}
|
|
this._resolveForeachLoop(loop, new Map());
|
|
}
|
|
|
|
_parseForeachListValue() {
|
|
const values = [];
|
|
if (this._eat('[')) {
|
|
while (!this._match(']') && !this._match('eof')) {
|
|
const value = this._parseListItem();
|
|
if (value && value.type === 'dag') {
|
|
const instantiated = this._instantiateClassTemplate(value.value);
|
|
if (instantiated) {
|
|
values.push(instantiated);
|
|
} else {
|
|
values.push(value);
|
|
}
|
|
} else {
|
|
values.push(value);
|
|
}
|
|
this._eat(',');
|
|
}
|
|
this._expect(']');
|
|
} else if (this._eat('!')) {
|
|
const op = this._expect('id');
|
|
if (op === 'range' && this._eat('(')) {
|
|
const args = [];
|
|
while (!this._match(')') && !this._match('eof')) {
|
|
args.push(this._parseValue());
|
|
this._eat(',');
|
|
}
|
|
this._expect(')');
|
|
if (args.length >= 1) {
|
|
let start = 0;
|
|
let end = 0;
|
|
if (args.length === 1) {
|
|
end = this._evaluateSimpleValue(args[0]);
|
|
} else {
|
|
start = this._evaluateSimpleValue(args[0]);
|
|
end = this._evaluateSimpleValue(args[1]);
|
|
}
|
|
for (let i = start; i < end; i++) {
|
|
values.push(new tablegen.Value('int', i));
|
|
}
|
|
}
|
|
} else {
|
|
while (!this._match('keyword', 'in') && !this._match('eof')) {
|
|
this._read();
|
|
}
|
|
}
|
|
} else if (this._eat('{')) {
|
|
const start = this._expect('number');
|
|
if (this._eat('-') || this._eat('...')) {
|
|
const end = this._expect('number');
|
|
for (let i = start; i <= end; i++) {
|
|
values.push(new tablegen.Value('int', i));
|
|
}
|
|
} else {
|
|
// Single value: {5} means just the value 5
|
|
values.push(new tablegen.Value('int', start));
|
|
}
|
|
this._expect('}');
|
|
} else {
|
|
while (!this._match('keyword', 'in') && !this._match('eof')) {
|
|
this._read();
|
|
}
|
|
}
|
|
return values;
|
|
}
|
|
|
|
_instantiateClassTemplate(dag) {
|
|
if (!dag || !dag.operator) {
|
|
return null;
|
|
}
|
|
const className = typeof dag.operator === 'string' ? dag.operator : dag.operator.value;
|
|
const classRecord = this.classes.get(className);
|
|
if (!classRecord) {
|
|
return null;
|
|
}
|
|
const fields = new Map();
|
|
const bindings = new Map();
|
|
if (classRecord.templateArgs && dag.operands) {
|
|
for (let i = 0; i < classRecord.templateArgs.length && i < dag.operands.length; i++) {
|
|
const paramName = classRecord.templateArgs[i].name;
|
|
const argValue = dag.operands[i].value;
|
|
bindings.set(paramName, argValue);
|
|
}
|
|
}
|
|
for (const [fieldName, field] of classRecord.fields) {
|
|
let resolvedValue = field.value;
|
|
if (resolvedValue && resolvedValue.type === 'def' && bindings.has(resolvedValue.value)) {
|
|
resolvedValue = bindings.get(resolvedValue.value);
|
|
} else if (resolvedValue && resolvedValue.type === 'bang') {
|
|
resolvedValue = this._evaluateBangOp(resolvedValue, bindings);
|
|
}
|
|
fields.set(fieldName, resolvedValue);
|
|
}
|
|
return new tablegen.Value('record_instance', { className, fields });
|
|
}
|
|
|
|
_evaluateBangOp(value, bindings) {
|
|
if (!value || value.type !== 'bang') {
|
|
return value;
|
|
}
|
|
const { op, args } = value.value;
|
|
if (op === 'tolower' && args && args.length === 1) {
|
|
let [arg] = args;
|
|
if (arg.type === 'def' && bindings.has(arg.value)) {
|
|
arg = bindings.get(arg.value);
|
|
}
|
|
if (arg.type === 'string') {
|
|
const str = String(arg.value).replace(/^"|"$/g, '');
|
|
return new tablegen.Value('string', str.toLowerCase());
|
|
}
|
|
}
|
|
return value;
|
|
}
|
|
|
|
_evaluateSimpleValue(value) {
|
|
if (!value) {
|
|
return 0;
|
|
}
|
|
if (value.type === 'int') {
|
|
return typeof value.value === 'number' ? value.value : parseInt(value.value, 10);
|
|
}
|
|
if (typeof value === 'number') {
|
|
return value;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
_parseForeachBody(loop) {
|
|
while (!this._match('}') && !this._match('eof')) {
|
|
this._parseForeachBodyStatement(loop);
|
|
}
|
|
}
|
|
|
|
_parseForeachBodyStatement(loop) {
|
|
const token = this._tokenizer.current();
|
|
if (token.type === 'keyword') {
|
|
switch (token.value) {
|
|
case 'def':
|
|
loop.entries.push({ type: 'def', data: this._parseDefTemplate() });
|
|
break;
|
|
case 'defm':
|
|
this._parseDefm();
|
|
break;
|
|
case 'let':
|
|
this._parseLet();
|
|
break;
|
|
case 'defvar':
|
|
loop.entries.push({ type: 'defvar', data: this._parseDefvarTemplate() });
|
|
break;
|
|
case 'foreach':
|
|
loop.entries.push({ type: 'foreach', data: this._parseForeachTemplate() });
|
|
break;
|
|
case 'if':
|
|
loop.entries.push({ type: 'foreach', data: this._parseIfAsLoop() });
|
|
break;
|
|
default:
|
|
this._read();
|
|
break;
|
|
}
|
|
} else {
|
|
this._read();
|
|
}
|
|
}
|
|
|
|
_parseIfAsLoop() {
|
|
const location = this._tokenizer.location();
|
|
this._read();
|
|
const condition = this._parseValue();
|
|
this._expect('keyword', 'then');
|
|
const loop = {
|
|
location,
|
|
iterVarName: null,
|
|
listValue: [],
|
|
entries: [],
|
|
condition,
|
|
hasDefvar: false,
|
|
isConditional: true
|
|
};
|
|
if (this._match('{')) {
|
|
this._read();
|
|
this._parseForeachBody(loop);
|
|
this._expect('}');
|
|
} else {
|
|
this._parseForeachBodyStatement(loop);
|
|
}
|
|
if (this._match('keyword', 'else')) {
|
|
this._read();
|
|
if (this._match('{')) {
|
|
this._read();
|
|
let depth = 1;
|
|
while (depth > 0 && !this._match('eof')) {
|
|
if (this._eat('{')) {
|
|
depth++;
|
|
} else if (this._eat('}')) {
|
|
depth--;
|
|
} else {
|
|
this._read();
|
|
}
|
|
}
|
|
} else {
|
|
// Single statement else: skip until semicolon
|
|
this._skipUntil([';']);
|
|
this._eat(';');
|
|
}
|
|
}
|
|
return loop;
|
|
}
|
|
|
|
_parseDefTemplate() {
|
|
this._read();
|
|
const nameTemplate = [];
|
|
while (!this._match(':') && !this._match('{') && !this._match(';') && !this._match('eof')) {
|
|
if (this._match('id')) {
|
|
const value = this._read();
|
|
if (this._eat('.')) {
|
|
const field = this._expect('id');
|
|
nameTemplate.push({ type: 'field_access', base: value, field });
|
|
} else {
|
|
nameTemplate.push({ type: 'id', value });
|
|
}
|
|
} else if (this._match('string')) {
|
|
nameTemplate.push({ type: 'string', value: this._read() });
|
|
} else if (this._match('number')) {
|
|
nameTemplate.push({ type: 'number', value: this._read() });
|
|
} else if (this._eat('#')) {
|
|
nameTemplate.push({ type: 'concat' });
|
|
} else if (this._match('!')) {
|
|
const bangValue = this._parseValue();
|
|
if (bangValue && bangValue.type === 'bang') {
|
|
nameTemplate.push({ type: 'bang', value: bangValue.value });
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
let parents = [];
|
|
if (this._match(':')) {
|
|
parents = this._parseParentClassList();
|
|
}
|
|
let bodyFields = new Map();
|
|
if (this._match('{')) {
|
|
this._read(); // consume '{'
|
|
bodyFields = this._parseRecordBodyFields();
|
|
this._expect('}');
|
|
}
|
|
this._eat(';');
|
|
return { nameTemplate, parents, bodyFields };
|
|
}
|
|
|
|
_parseForeachTemplate() {
|
|
const location = this._tokenizer.location();
|
|
this._read();
|
|
const iterVarName = this._expect('id');
|
|
this._expect('=');
|
|
const listValue = this._parseForeachListValue();
|
|
this._expect('keyword', 'in');
|
|
const loop = { location, iterVarName, listValue, entries: [] };
|
|
if (this._match('{')) {
|
|
this._read();
|
|
this._parseForeachBody(loop);
|
|
this._expect('}');
|
|
} else {
|
|
this._parseForeachBodyStatement(loop);
|
|
}
|
|
return loop;
|
|
}
|
|
|
|
_parseDefvarTemplate() {
|
|
this._read();
|
|
const name = this._expect('id');
|
|
this._expect('=');
|
|
const value = this._parseValue();
|
|
this._expect(';');
|
|
return { name, value };
|
|
}
|
|
|
|
_parseRecordBodyFields() {
|
|
const fields = new Map();
|
|
while (!this._match('}') && !this._match('eof')) {
|
|
if (this._match('keyword', 'let')) {
|
|
this._read();
|
|
const name = this._expect('id');
|
|
this._expect('=');
|
|
const value = this._parseValue();
|
|
this._eat(';');
|
|
fields.set(name, { name, type: null, value });
|
|
} else if (this._match('keyword', 'field')) {
|
|
this._read();
|
|
const type = this._parseType();
|
|
const name = this._expect('id');
|
|
let value = null;
|
|
if (this._eat('=')) {
|
|
value = this._parseValue();
|
|
}
|
|
this._eat(';');
|
|
fields.set(name, { name, type, value });
|
|
} else if (this._match('keyword', 'assert') || this._match('keyword', 'dump')) {
|
|
// Skip assert and dump statements
|
|
this._skipUntil([';']);
|
|
this._eat(';');
|
|
} else if (this._match('id') || this._match('keyword')) {
|
|
// Type followed by field name
|
|
const type = this._parseType();
|
|
const name = this._expect('id');
|
|
let value = null;
|
|
if (this._eat('=')) {
|
|
value = this._parseValue();
|
|
}
|
|
this._eat(';');
|
|
fields.set(name, { name, type, value });
|
|
} else {
|
|
this._read();
|
|
}
|
|
}
|
|
return fields;
|
|
}
|
|
|
|
_resolveForeachLoop(loop, substitutions) {
|
|
if (loop.entries.length === 0) {
|
|
return;
|
|
}
|
|
|
|
if (loop.isConditional) {
|
|
const conditionResult = this._evaluateCondition(loop.condition, substitutions);
|
|
if (conditionResult === false || conditionResult === null) {
|
|
return;
|
|
}
|
|
for (const entry of loop.entries) {
|
|
if (entry.type === 'def') {
|
|
this._instantiateDef(entry.data, substitutions);
|
|
} else if (entry.type === 'foreach') {
|
|
this._resolveForeachLoop(entry.data, substitutions);
|
|
} else if (entry.type === 'defvar') {
|
|
const value = this._evaluateDefvar(entry.data.value, substitutions);
|
|
substitutions.set(entry.data.name, value);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
if (loop.listValue.length === 0) {
|
|
return;
|
|
}
|
|
for (const listItem of loop.listValue) {
|
|
const currentSubs = new Map(substitutions);
|
|
if (loop.iterVarName) {
|
|
currentSubs.set(loop.iterVarName, listItem);
|
|
}
|
|
for (const entry of loop.entries) {
|
|
if (entry.type === 'def') {
|
|
this._instantiateDef(entry.data, currentSubs);
|
|
} else if (entry.type === 'foreach') {
|
|
this._resolveForeachLoop(entry.data, currentSubs);
|
|
} else if (entry.type === 'defvar') {
|
|
const value = this._evaluateDefvar(entry.data.value, currentSubs);
|
|
currentSubs.set(entry.data.name, value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
_evaluateCondition(condition, substitutions) {
|
|
if (!condition) {
|
|
return null;
|
|
}
|
|
if (condition.type === 'bang' && condition.value) {
|
|
const { op, args } = condition.value;
|
|
if (op === 'ne' && args.length === 2) {
|
|
const a = this._evaluateSimpleExpr(args[0], substitutions);
|
|
const b = this._evaluateSimpleExpr(args[1], substitutions);
|
|
if (a !== null && b !== null) {
|
|
return a !== b;
|
|
}
|
|
}
|
|
if (op === 'eq' && args.length === 2) {
|
|
const a = this._evaluateSimpleExpr(args[0], substitutions);
|
|
const b = this._evaluateSimpleExpr(args[1], substitutions);
|
|
if (a !== null && b !== null) {
|
|
return a === b;
|
|
}
|
|
}
|
|
}
|
|
// Can't evaluate complex conditions
|
|
return null;
|
|
}
|
|
|
|
// Evaluate a simple expression for condition evaluation
|
|
_evaluateSimpleExpr(expr, substitutions) {
|
|
if (!expr) {
|
|
return null;
|
|
}
|
|
if (expr.type === 'string') {
|
|
return String(expr.value).replace(/^"|"$/g, '');
|
|
}
|
|
if (expr.type === 'int') {
|
|
return typeof expr.value === 'number' ? expr.value : parseInt(expr.value, 10);
|
|
}
|
|
if ((expr.type === 'def' || expr.type === 'id') && typeof expr.value === 'string') {
|
|
// Handle field access like "largeT.name"
|
|
if (expr.value.includes('.')) {
|
|
const [baseName, ...fieldParts] = expr.value.split('.');
|
|
let current = substitutions.has(baseName) ? substitutions.get(baseName) : null;
|
|
if (current) {
|
|
for (const fieldName of fieldParts) {
|
|
// Handle record_instance (from _instantiateClassTemplate)
|
|
if (current && current.type === 'record_instance' && current.value && current.value.fields) {
|
|
const fieldValue = current.value.fields.get(fieldName);
|
|
if (fieldValue) {
|
|
current = fieldValue;
|
|
} else {
|
|
return null;
|
|
}
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
return this._evaluateSimpleExpr(current, substitutions);
|
|
}
|
|
} else if (substitutions.has(expr.value)) {
|
|
return this._evaluateSimpleExpr(substitutions.get(expr.value), substitutions);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
_evaluateDefvar(value, substitutions) {
|
|
if (!value) {
|
|
return new tablegen.Value('string', '');
|
|
}
|
|
if (value.type === 'string') {
|
|
return value;
|
|
}
|
|
if (value.type === 'int') {
|
|
return value;
|
|
}
|
|
if ((value.type === 'def' || value.type === 'id') && substitutions.has(value.value)) {
|
|
return substitutions.get(value.value);
|
|
}
|
|
if (value.type === 'concat') {
|
|
const parts = value.value.map((part) => this._evaluateDefvar(part, substitutions));
|
|
let result = '';
|
|
for (const part of parts) {
|
|
if (part.type === 'string') {
|
|
result += String(part.value).replace(/^"|"$/g, '');
|
|
} else if (part.type === 'int') {
|
|
result += String(part.value);
|
|
} else if (part.type === 'def' || part.type === 'id') {
|
|
result += String(part.value);
|
|
}
|
|
}
|
|
return new tablegen.Value('string', result);
|
|
}
|
|
if (value.type === 'bang' && value.value) {
|
|
const { op, args } = value.value;
|
|
if (op === 'cast' && args && args.length > 0) {
|
|
const arg = this._evaluateDefvar(args[0], substitutions);
|
|
if (arg.type === 'int') {
|
|
return new tablegen.Value('string', String(arg.value));
|
|
}
|
|
return arg;
|
|
}
|
|
if (op === 'toupper' && args && args.length > 0) {
|
|
const arg = this._evaluateDefvar(args[0], substitutions);
|
|
if (arg.type === 'string') {
|
|
return new tablegen.Value('string', String(arg.value).toUpperCase());
|
|
}
|
|
return arg;
|
|
}
|
|
if (op === 'tolower' && args && args.length > 0) {
|
|
const arg = this._evaluateDefvar(args[0], substitutions);
|
|
if (arg.type === 'string') {
|
|
return new tablegen.Value('string', String(arg.value).toLowerCase());
|
|
}
|
|
return arg;
|
|
}
|
|
if (op === 'strconcat' && args && args.length > 0) {
|
|
let result = '';
|
|
for (const arg of args) {
|
|
const evaluated = this._evaluateDefvar(arg, substitutions);
|
|
if (evaluated && evaluated.type === 'string') {
|
|
result += String(evaluated.value).replace(/^"|"$/g, '');
|
|
} else if (evaluated && evaluated.type === 'int') {
|
|
result += String(evaluated.value);
|
|
}
|
|
}
|
|
return new tablegen.Value('string', result);
|
|
}
|
|
}
|
|
return value;
|
|
}
|
|
|
|
_instantiateDef(template, substitutions) {
|
|
let name = '';
|
|
for (const part of template.nameTemplate) {
|
|
if (part.type === 'concat') {
|
|
continue;
|
|
} else if (part.type === 'field_access') {
|
|
if (substitutions.has(part.base)) {
|
|
const subValue = substitutions.get(part.base);
|
|
name += this._getFieldValue(subValue, part.field);
|
|
} else {
|
|
name += `${part.base}.${part.field}`;
|
|
}
|
|
} else if (part.type === 'id') {
|
|
if (substitutions.has(part.value)) {
|
|
const subValue = substitutions.get(part.value);
|
|
name += this._valueToString(subValue);
|
|
} else {
|
|
name += part.value;
|
|
}
|
|
} else if (part.type === 'string') {
|
|
name += part.value;
|
|
} else if (part.type === 'number') {
|
|
name += String(part.value);
|
|
} else if (part.type === 'bang') {
|
|
const evaluated = this._evaluateDefvar(new tablegen.Value('bang', part.value), substitutions);
|
|
name += this._valueToString(evaluated);
|
|
}
|
|
}
|
|
const def = new tablegen.Record(name, this);
|
|
def.location = this._tokenizer.location();
|
|
def.parents = template.parents.map((parent) => ({
|
|
name: parent.name,
|
|
args: parent.args ? parent.args.map((arg) => this._substituteValue(arg, substitutions)) : []
|
|
}));
|
|
for (const [fieldName, field] of template.bodyFields) {
|
|
const resolvedValue = field.value ? this._substituteValue(field.value, substitutions) : null;
|
|
def.fields.set(fieldName, new tablegen.RecordVal(fieldName, field.type, resolvedValue));
|
|
}
|
|
this.addSubClass(def);
|
|
def.resolveReferences();
|
|
if (name) {
|
|
this._defs.set(name, def);
|
|
this.defs.push(def);
|
|
}
|
|
}
|
|
|
|
_valueToString(value) {
|
|
if (!value) {
|
|
return '';
|
|
}
|
|
if (typeof value === 'string') {
|
|
return value;
|
|
}
|
|
if (typeof value === 'number') {
|
|
return String(value);
|
|
}
|
|
if (value.type === 'string') {
|
|
return String(value.value).replace(/^"|"$/g, '');
|
|
}
|
|
if (value.type === 'int') {
|
|
return String(value.value);
|
|
}
|
|
if (value.type === 'id' || value.type === 'def') {
|
|
return String(value.value);
|
|
}
|
|
return '';
|
|
}
|
|
|
|
_getFieldValue(value, fieldName) {
|
|
if (!value) {
|
|
return '';
|
|
}
|
|
if (value.type === 'record_instance' && value.value && value.value.fields) {
|
|
const fieldValue = value.value.fields.get(fieldName);
|
|
return this._valueToString(fieldValue);
|
|
}
|
|
return '';
|
|
}
|
|
|
|
_substituteValue(value, substitutions) {
|
|
if (!value) {
|
|
return value;
|
|
}
|
|
if (value.type === 'def' || value.type === 'id') {
|
|
const varName = value.value;
|
|
if (substitutions.has(varName)) {
|
|
return substitutions.get(varName);
|
|
}
|
|
if (typeof varName === 'string' && varName.includes('.')) {
|
|
const [base, field] = varName.split('.', 2);
|
|
if (substitutions.has(base)) {
|
|
const baseValue = substitutions.get(base);
|
|
const fieldValue = this._getFieldValue(baseValue, field);
|
|
if (fieldValue) {
|
|
return new tablegen.Value('string', fieldValue);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (value.type === 'list' && Array.isArray(value.value)) {
|
|
return {
|
|
type: 'list',
|
|
value: value.value.map((v) => this._substituteValue(v, substitutions))
|
|
};
|
|
}
|
|
if (value.type === 'dag' && value.value) {
|
|
return {
|
|
type: 'dag',
|
|
value: {
|
|
operator: value.value.operator,
|
|
operands: value.value.operands.map((op) => ({
|
|
value: this._substituteValue(op.value, substitutions),
|
|
name: op.name
|
|
}))
|
|
}
|
|
};
|
|
}
|
|
if (value.type === 'concat' && Array.isArray(value.value)) {
|
|
return {
|
|
type: 'concat',
|
|
value: value.value.map((v) => this._substituteValue(v, substitutions))
|
|
};
|
|
}
|
|
return value;
|
|
}
|
|
|
|
_parseTemplateParams() {
|
|
this._read(); // <
|
|
const params = [];
|
|
while (!this._match('>') && !this._match('eof')) {
|
|
const type = this._parseType();
|
|
const name = this._expect('id');
|
|
let defaultValue = null;
|
|
if (this._match('=')) {
|
|
this._read();
|
|
defaultValue = this._parseValue();
|
|
}
|
|
params.push({ name, type, defaultValue });
|
|
if (this._match(',')) {
|
|
this._read();
|
|
}
|
|
}
|
|
this._expect('>');
|
|
return params;
|
|
}
|
|
|
|
_parseParentClassList() {
|
|
this._read();
|
|
const parents = [];
|
|
while (!this._match('{') && !this._match(';') && !this._match('eof')) {
|
|
const parent = this._parseType();
|
|
parents.push(parent);
|
|
if (!this._eat(',')) {
|
|
break;
|
|
}
|
|
}
|
|
return parents;
|
|
}
|
|
|
|
_parseRecordBody(record) {
|
|
this._read();
|
|
while (!this._match('}') && !this._match('eof')) {
|
|
if (this._match('keyword', 'let')) {
|
|
this._read();
|
|
// Optional 'append'/'prepend' modifier concatenates with the inherited
|
|
// value instead of replacing it (TableGen field concatenation).
|
|
let concat = null;
|
|
if (this._match('id', 'append') || this._match('id', 'prepend')) {
|
|
concat = this._read();
|
|
}
|
|
const name = this._expect('id');
|
|
this._expect('=');
|
|
const value = this._parseValue();
|
|
const field = new tablegen.RecordVal(name, null, value);
|
|
if (concat) {
|
|
field.concat = concat;
|
|
}
|
|
record.fields.set(name, field);
|
|
this._eat(';');
|
|
} else if (this._match('keyword', 'defvar')) {
|
|
this._read();
|
|
const name = this._expect('id');
|
|
this._expect('=');
|
|
const value = this._parseValue();
|
|
const field = new tablegen.RecordVal(name, null, value);
|
|
record.fields.set(name, field);
|
|
this._eat(';');
|
|
} else if (this._match('keyword', 'assert')) {
|
|
this._read();
|
|
this._parseValue(); // condition
|
|
this._eat(',');
|
|
this._parseValue(); // message
|
|
this._eat(';');
|
|
} else if (this._match('keyword', 'bit') || this._match('keyword', 'bits') || this._match('keyword', 'int') ||
|
|
this._match('keyword', 'string') || this._match('keyword', 'list') || this._match('keyword', 'dag') ||
|
|
this._match('keyword', 'code') || this._match('id')) {
|
|
const type = this._parseType();
|
|
// Skip if next token is not an id (handles edge cases in complex nested structures)
|
|
if (!this._match('id')) {
|
|
if (this._match(',') || this._match('>') || this._match(')') || this._match(']')) {
|
|
continue;
|
|
}
|
|
}
|
|
const name = this._expect('id');
|
|
let value = null;
|
|
if (this._eat('=')) {
|
|
value = this._parseValue();
|
|
}
|
|
const field = new tablegen.RecordVal(name, type, value);
|
|
record.fields.set(name, field);
|
|
this._eat(';');
|
|
} else {
|
|
this._read();
|
|
}
|
|
}
|
|
this._expect('}');
|
|
}
|
|
|
|
_parseType() {
|
|
let typeName = '';
|
|
|
|
if (this._match('keyword', 'bit') || this._match('keyword', 'bits') || this._match('keyword', 'int') ||
|
|
this._match('keyword', 'string') || this._match('keyword', 'list') || this._match('keyword', 'dag') ||
|
|
this._match('keyword', 'code')) {
|
|
typeName = this._read();
|
|
} else if (this._match('id')) {
|
|
typeName = this._read();
|
|
} else if (this._match('number')) {
|
|
typeName = this._read().toString();
|
|
if (this._match('id')) {
|
|
typeName += this._read();
|
|
}
|
|
} else {
|
|
throw new tablegen.Error(`Expected type at ${this._tokenizer.location()}`);
|
|
}
|
|
const type = new tablegen.Type(typeName);
|
|
if (this._eat('<')) {
|
|
type.args = this._parseTemplateArgList();
|
|
}
|
|
return type;
|
|
}
|
|
|
|
_parseTemplateArgList() {
|
|
// Parse template arguments directly from token stream
|
|
// Supports both positional (arg1, arg2) and named (name=value) arguments
|
|
const args = [];
|
|
while (!this._match('>') && !this._match('eof')) {
|
|
// Check if this is a named argument: id = value
|
|
if (this._match('id')) {
|
|
const name = this._read();
|
|
if (this._match('=')) {
|
|
// Named argument
|
|
this._read(); // Consume '='
|
|
const value = this._parseValue();
|
|
args.push({ name, value });
|
|
} else {
|
|
// Positional argument that starts with an id
|
|
// Reconstruct the value - id might be part of concat, field access, etc.
|
|
let value = new tablegen.Value('def', name);
|
|
// Handle < > for template instantiation - manually parse with depth tracking
|
|
if (this._eat('<')) {
|
|
const nestedArgs = [];
|
|
let depth = 1;
|
|
let currentArg = [];
|
|
while (depth > 0 && !this._match('eof')) {
|
|
if (this._match('<')) {
|
|
currentArg.push(this._read());
|
|
depth++;
|
|
} else if (this._match('>')) {
|
|
if (depth === 1) {
|
|
// End of this template arg list
|
|
if (currentArg.length > 0) {
|
|
// Parse accumulated tokens properly
|
|
const argStr = currentArg.join(' ');
|
|
// Try to parse as number first
|
|
if (/^-?\d+$/.test(argStr)) {
|
|
nestedArgs.push(new tablegen.Value('int', argStr));
|
|
} else {
|
|
nestedArgs.push(new tablegen.Value('def', argStr));
|
|
}
|
|
}
|
|
this._read(); // consume the >
|
|
depth--;
|
|
} else {
|
|
currentArg.push(this._read());
|
|
depth--;
|
|
}
|
|
} else if (this._match(',') && depth === 1) {
|
|
// Argument separator at current depth
|
|
if (currentArg.length > 0) {
|
|
// Parse accumulated tokens properly
|
|
const argStr = currentArg.join(' ');
|
|
// Try to parse as number first
|
|
if (/^-?\d+$/.test(argStr)) {
|
|
nestedArgs.push(new tablegen.Value('int', argStr));
|
|
} else {
|
|
nestedArgs.push(new tablegen.Value('def', argStr));
|
|
}
|
|
currentArg = [];
|
|
}
|
|
this._read(); // consume comma
|
|
} else {
|
|
// Regular token - add to current arg
|
|
currentArg.push(this._read());
|
|
}
|
|
}
|
|
// Convert to DAG operands
|
|
const operands = nestedArgs.map((arg) => ({ value: arg, name: null }));
|
|
value = new tablegen.Value('dag', new tablegen.DAG(name, operands));
|
|
}
|
|
// Handle field access
|
|
if (this._eat('.')) {
|
|
const field = this._expect('id');
|
|
if (value.type === 'def') {
|
|
value = new tablegen.Value('def', `${name}.${field}`);
|
|
}
|
|
}
|
|
// Handle :: suffix
|
|
if (this._eat('::')) {
|
|
const suffix = this._expect('id');
|
|
if (value.type === 'def') {
|
|
value = new tablegen.Value('def', `${name}::${suffix}`);
|
|
}
|
|
}
|
|
// Handle # concatenation
|
|
if (this._match('#')) {
|
|
const values = [value];
|
|
while (this._match('#')) {
|
|
this._read();
|
|
values.push(this._parsePrimaryValue());
|
|
}
|
|
value = new tablegen.Value('concat', values);
|
|
}
|
|
args.push(value);
|
|
}
|
|
} else {
|
|
const value = this._parseValue();
|
|
args.push(value);
|
|
}
|
|
if (!this._eat(',')) {
|
|
break;
|
|
}
|
|
}
|
|
this._expect('>');
|
|
return args;
|
|
}
|
|
|
|
_parseValue() {
|
|
const values = [];
|
|
values.push(this._parsePrimaryValue());
|
|
while (this._match('#') || (values[values.length - 1] && values[values.length - 1].type === 'string' && this._match('string'))) {
|
|
if (this._match('#')) {
|
|
this._read();
|
|
// Handle trailing # before ; (malformed TableGen but seen in some files)
|
|
if (this._match(';') || this._match(',') || this._match(')') || this._match(']') || this._match('}') || this._match('eof')) {
|
|
break;
|
|
}
|
|
}
|
|
values.push(this._parsePrimaryValue());
|
|
}
|
|
if (values.length === 1) {
|
|
return values[0];
|
|
}
|
|
return new tablegen.Value('concat', values);
|
|
}
|
|
|
|
_parseListItem() {
|
|
// Handle $variable as a standalone value in list/dag context
|
|
if (this._eat('$')) {
|
|
const name = this._expect('id');
|
|
return new tablegen.Value('var', name);
|
|
}
|
|
// Special handling for dag-like constructs (id followed by <...>)
|
|
// These need to be parsed as DAGs for trait information
|
|
if (this._match('id')) {
|
|
const name = this._read();
|
|
if (this._eat('<')) {
|
|
const templateArgs = this._parseTemplateArgList();
|
|
const operands = templateArgs.map((arg) => {
|
|
if (arg && typeof arg === 'object' && arg.name && arg.value) {
|
|
return { value: arg.value, name: arg.name };
|
|
}
|
|
return { value: arg, name: null };
|
|
});
|
|
const result = new tablegen.Value('dag', new tablegen.DAG(name, operands));
|
|
if (this._eat('.')) {
|
|
result.field = this._expect('id');
|
|
}
|
|
return result;
|
|
}
|
|
// Not a template instantiation, but might be an identifier with suffixes
|
|
// Put the token back conceptually by creating a def value and checking for suffixes
|
|
let result = new tablegen.Value('def', name);
|
|
// Check for subscripts, field access, etc.
|
|
while (this._eat('[')) {
|
|
const index = this._parseValue();
|
|
this._expect(']');
|
|
result = new tablegen.Value('bang', { op: 'subscript', args: [result, index], field: null });
|
|
}
|
|
if (this._eat('.')) {
|
|
const field = this._expect('id');
|
|
if (result.type === 'def') {
|
|
result = new tablegen.Value('def', `${result.value}.${field}`);
|
|
} else {
|
|
result.value.field = field;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
return this._parseValue();
|
|
}
|
|
|
|
_parsePrimaryValue() {
|
|
if (this._match('string')) {
|
|
const value = this._read();
|
|
return new tablegen.Value('string', value);
|
|
}
|
|
if (this._match('number')) {
|
|
const value = this._read();
|
|
return new tablegen.Value('int', value);
|
|
}
|
|
if (this._match('true') || this._match('false')) {
|
|
const value = this._read();
|
|
return new tablegen.Value('bit', value);
|
|
}
|
|
if (this._match('code')) {
|
|
const value = this._read();
|
|
return new tablegen.Value('code', value);
|
|
}
|
|
if (this._eat('[')) {
|
|
const items = [];
|
|
while (!this._match(']') && !this._match('eof')) {
|
|
items.push(this._parseListItem());
|
|
this._eat(',');
|
|
}
|
|
this._expect(']');
|
|
if (this._match('<')) {
|
|
this._skip('<', '>');
|
|
}
|
|
return new tablegen.Value('list', items);
|
|
}
|
|
if (this._eat('(')) {
|
|
let operator = null;
|
|
let operatorName = null;
|
|
if (this._match('id')) {
|
|
operator = this._read();
|
|
// Handle template arguments on the operator: (NativeCodeCallVoid<...> ...)
|
|
if (this._match('<')) {
|
|
this._skip('<', '>');
|
|
}
|
|
// Handle operator binding: (OpQ:$op ...)
|
|
if (this._eat(':') && this._eat('$')) {
|
|
if (this._match('id') || this._match('keyword')) {
|
|
operatorName = this._read();
|
|
}
|
|
}
|
|
}
|
|
const operands = [];
|
|
while (!this._match(')') && !this._match('eof')) {
|
|
if (this._eat(',')) {
|
|
continue;
|
|
}
|
|
// Use _parseListItem() to handle template instantiations like Arg<TTG_MemDescType>
|
|
const value = this._parseListItem();
|
|
let name = null;
|
|
if (this._eat(':') && this._eat('$')) {
|
|
if (this._match('id') || this._match('keyword')) {
|
|
name = this._read();
|
|
}
|
|
}
|
|
const operand = { value, name };
|
|
operands.push(operand);
|
|
this._eat(',');
|
|
}
|
|
this._expect(')');
|
|
const dag = new tablegen.DAG(operator, operands);
|
|
if (operatorName) {
|
|
dag.operatorName = operatorName;
|
|
}
|
|
return new tablegen.Value('dag', dag);
|
|
}
|
|
if (this._eat('{')) {
|
|
const fields = new Map();
|
|
while (!this._match('}') && !this._match('eof')) {
|
|
const name = this._expect('id');
|
|
this._expect('=');
|
|
const value = this._parseValue();
|
|
fields.set(name, value);
|
|
this._eat(',');
|
|
}
|
|
this._expect('}');
|
|
return new tablegen.Value('record', fields);
|
|
}
|
|
if (this._eat('!')) {
|
|
let op = null;
|
|
if (this._match('id') || this._match('keyword')) {
|
|
op = this._read();
|
|
} else {
|
|
throw new tablegen.Error(`Expected operator after '!' but got '${this._tokenizer.current().type}' at ${this._tokenizer.location()}`);
|
|
}
|
|
if (this._match('<')) {
|
|
this._skip('<', '>');
|
|
}
|
|
const args = [];
|
|
if (this._eat('(')) {
|
|
if (op === 'cond') {
|
|
while (!this._match(')') && !this._match('eof')) {
|
|
const condition = this._parseValue();
|
|
this._expect(':');
|
|
const value = this._parseValue();
|
|
args.push({ condition, value });
|
|
this._eat(',');
|
|
}
|
|
} else {
|
|
while (!this._match(')') && !this._match('eof')) {
|
|
args.push(this._parseValue());
|
|
this._eat(',');
|
|
}
|
|
}
|
|
this._expect(')');
|
|
}
|
|
let field = null;
|
|
if (this._eat('.')) {
|
|
field = this._expect('id');
|
|
}
|
|
return new tablegen.Value('bang', { op, args, field });
|
|
}
|
|
if (this._match('id') || this._match('keyword')) {
|
|
const value = this._read();
|
|
let result = new tablegen.Value('def', value);
|
|
// Handle various suffixes: templates, subscripts, field access, scope resolution
|
|
while (true) {
|
|
if (this._match('<')) {
|
|
// Template arguments are skipped here - they're parsed properly in
|
|
// DAG context by _parseListItem() which creates DAG values with template args
|
|
this._skip('<', '>');
|
|
} else if (this._eat('[')) {
|
|
// Array subscripting: x[0]
|
|
const index = this._parseValue();
|
|
this._expect(']');
|
|
result = new tablegen.Value('bang', { op: 'subscript', args: [result, index], field: null });
|
|
} else if (this._eat('.')) {
|
|
// Field access: x.field or x[0].field
|
|
const field = this._expect('id');
|
|
if (result.type === 'def') {
|
|
result = new tablegen.Value('def', `${result.value}.${field}`);
|
|
} else {
|
|
// For subscript results, add field access
|
|
result.value.field = field;
|
|
}
|
|
} else if (this._eat('::')) {
|
|
// Scope resolution
|
|
const suffix = this._expect('id');
|
|
if (result.type === 'def') {
|
|
result = new tablegen.Value('def', `${result.value}::${suffix}`);
|
|
}
|
|
break;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
if (this._eat('?')) {
|
|
return new tablegen.Value('uninitialized', null);
|
|
}
|
|
if (this._eat('$')) {
|
|
const name = this._expect('id');
|
|
return new tablegen.Value('var', name);
|
|
}
|
|
throw new tablegen.Error(`Unexpected value at ${this._tokenizer.location()}`);
|
|
}
|
|
|
|
_read() {
|
|
return this._tokenizer.read().value;
|
|
}
|
|
|
|
_match(type, value) {
|
|
const token = this._tokenizer.current();
|
|
return token.type === type && (!value || token.value === value);
|
|
}
|
|
|
|
_eat(type, value) {
|
|
if (this._match(type, value)) {
|
|
this._read();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
_expect(type, value) {
|
|
if (this._match(type, value)) {
|
|
return this._read();
|
|
}
|
|
const token = this._tokenizer.current();
|
|
throw new tablegen.Error(`Expected '${type}' but got '${token.type}' at ${this._tokenizer.location()}`);
|
|
}
|
|
|
|
_skip(open, close) {
|
|
if (!this._match(open)) {
|
|
return;
|
|
}
|
|
this._read(); // consume opening token
|
|
let depth = 1;
|
|
while (depth > 0 && !this._match('eof')) {
|
|
if (this._match(open)) {
|
|
depth++;
|
|
} else if (this._match(close)) {
|
|
depth--;
|
|
}
|
|
this._read();
|
|
}
|
|
}
|
|
|
|
_skipUntil(types) {
|
|
while (!this._match('eof') && !types.includes(this._tokenizer.current().type)) {
|
|
this._read();
|
|
}
|
|
}
|
|
};
|
|
|
|
tablegen.Error = class extends Error {
|
|
|
|
constructor(message) {
|
|
super(message);
|
|
this.name = 'TableGen Error';
|
|
}
|
|
};
|
|
|
|
export const Reader = tablegen.Reader;
|