Files
wehub-resource-sync 7254f7b4d1
Build / Build (macos-latest) (push) Has been cancelled
Build / Build (ubuntu-latest) (push) Has been cancelled
Build / Build (windows-latest) (push) Has been cancelled
Build / Analyze (javascript) (push) Has been cancelled
Build / Analyze (python) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:37:45 +08:00

663 lines
22 KiB
JavaScript

const dot = {};
dot.ModelFactory = class {
async match(context) {
const reader = await context.read('text', 0x10000);
if (reader) {
try {
for (let i = 0; i < 64; i++) {
const line = reader.read('\n');
if (line === undefined) {
break;
}
if (line.trim().startsWith('//') || line.trim().startsWith('#')) {
continue;
}
if (line.trim().match(/^(strict)?\s*digraph/)) {
return context.set('dot');
}
}
} catch {
// continue regardless of error
}
}
return null;
}
async open(context) {
const decoder = await context.read('text.decoder');
const parser = new dot.Parser(decoder);
const graph = parser.parse();
if (graph.kind !== 'digraph') {
throw new dot.Error(`Graph type '${graph.type}' is not supported.`);
}
return new dot.Model(graph);
}
};
dot.Model = class {
constructor(graph) {
this.format = 'DOT';
this.modules = [new dot.Graph(graph)];
}
};
dot.Graph = class {
constructor(graph) {
this.name = graph.name || '';
this.nodes = [];
this.inputs = [];
this.outputs = [];
const values = new Map();
values.map = (name, type, tensor, metadata) => {
if (typeof name !== 'string') {
throw new dot.Error('Invalid value name.');
}
if (!values.has(name) || tensor) {
values.set(name, new dot.Value(name, type, tensor, metadata));
}
return values.get(name);
};
const nodes = new Map();
nodes.map = (name) => {
if (typeof name !== 'string') {
throw new dot.Error('Invalid node name.');
}
if (!nodes.has(name)) {
const node = {
kind: 'node',
name: { id: name, key: name },
type: { name },
inputs: [],
outputs: [],
attributes: new Map(),
metadata: new Map()
};
nodes.set(name, node);
}
return nodes.get(name);
};
for (const node of graph.statements) {
if (node.kind === 'node') {
node.inputs = [];
node.outputs = [];
node.metadata = new Map([...node.defaults, ...node.attributes]);
node.attributes = new Map();
delete node.defaults;
const metadata = node.metadata;
if (metadata.has('label')) {
const label = metadata.get('label');
if (label.startsWith('{') && label.endsWith('}')) {
const lines = label.substring(1, label.length - 1).split('|');
if (lines.length > 1 && node.name.id === lines[0] && lines[1].startsWith('op_code=')) {
const def = lines[1].split('\\l');
const op_code = def[0].split('=').pop();
node.type = { name: op_code };
if (op_code === 'call_module') {
node.type = { name: def[1], type: 'function' };
} else if (op_code === 'call_function') {
const vals = lines[2].split('\\l');
node.type = { name: vals[0] };
} else if (op_code.startsWith('get_parameter')) {
node.attributes.set('type', op_code.substring(13, op_code.length).trim());
node.type = { name: 'get_parameter' };
}
if (lines.length > 2) {
const attributes = lines[2].split('\\l');
for (const attribute of attributes) {
const parts = attribute.split(':');
if (parts.length === 2) {
const key = parts[0].trim();
let value = parts[1].trim();
if (value.startsWith('(') && value.endsWith(')')) {
value = JSON.parse(`[${value.substring(1, value.length - 1)}]`);
}
node.attributes.set(key, value);
}
}
}
metadata.delete('label');
} else if (lines.length === 1 && lines[0].startsWith('buffer\\l')) {
const def = lines[0].split('\\l');
node.type = { name: def[0] };
if (def.length > 1) {
node.attributes.set('type', def[1]);
}
metadata.delete('label');
}
} else {
const match = label.match(/^name:\s*([A-Za-z][A-Za-z0-9_]*)\stype:\s*([A-Za-z][A-Za-z0-9_]*)$/);
if (match && node.name.id === match[1]) {
node.type = { name: match[2] };
metadata.delete('label');
}
}
}
if (!node.type) {
const lines = node.name.id.split('\\n');
const match = lines[0].match(/^([A-Z][A-Za-z0-9_]*)\/([A-Z][A-Za-z0-9_]*)\s\(op#(\d+)\)$/);
if (match) {
node.type = { name: match[2] };
} else {
const match = lines[0].match(/^([A-Z][A-Za-z0-9_]*)\s\(op#(\d+)\)$/);
if (match) {
node.type = { name: match[1] };
} else {
// debugger;
}
}
}
if (!node.type) {
node.type = { name: node.name.id };
}
nodes.set(node.name.id, node);
}
}
for (const edge of graph.statements) {
if (edge.kind === 'edge') {
edge.uses = edge.uses || [];
const to = nodes.map(edge.to.id);
to.inputs.push(edge);
edge.uses.push(to);
edge.from = nodes.map(edge.name.id);
edge.from.outputs.push(edge);
}
}
for (const [key, node] of nodes) {
const keys = new Set(['pos', 'height', 'width', 'shape', 'label']);
if (node.metadata.get('shape') === 'octagon' && node.metadata.keys().every((key) => keys.has(key)) &&
node.inputs.length === 1 && node.inputs[0].uses.length === 1 && node.inputs[0].from.outputs.length === 1 && node.inputs[0].from.outputs[0].uses.length === 1 &&
new Set(node.outputs.map((output) => output.name.id)).size === 1 && node.outputs.every((output) => output.uses.length === 1)) {
const [from] = node.inputs[0].from.outputs;
for (const e of node.outputs) {
const [n] = e.uses;
n.inputs = n.inputs.map((edge) => edge === e ? from : edge);
}
nodes.delete(key);
}
}
for (const [key, node] of nodes) {
if ((node.type.name === 'get_parameter' || node.type.name === 'buffer' || node.type.name === 'Constant') &&
node.inputs.length === 0 &&
node.outputs.length === 1 && node.outputs[0].uses.length === 1) {
node.outputs[0].initializer = node;
nodes.delete(key);
}
}
for (const [, obj] of nodes) {
const node = new dot.Node(obj, values);
this.nodes.push(node);
}
for (const edge of graph.statements) {
if (edge.kind === 'edge') {
const value = values.map(edge.name.id);
const metadata = new Map([...edge.defaults, ...edge.attributes]);
value.metadata = Array.from(metadata).map(([key, value]) => new dot.Argument(key, value));
}
}
}
};
dot.Argument = class {
constructor(name, value, type = null) {
this.name = name;
this.value = value;
this.type = type;
}
};
dot.Value = class {
constructor(name, type, initializer, metadata) {
this.name = name;
this.type = !type && initializer ? initializer.type : type;
this.initializer = initializer || null;
this.metadata = metadata;
}
};
dot.Node = class {
constructor(node, values) {
this.name = node.name.key;
this.type = node.type;
this.inputs = [];
this.outputs = [];
this.attributes = [];
this.metadata = [];
for (let i = 0; i < node.inputs.length; i++) {
const edge = node.inputs[i];
const initializer = edge.initializer ? new dot.Tensor(edge.initializer) : null;
const value = values.map(edge.name.key, null, initializer);
const argument = new dot.Argument(i.toString(), [value]);
this.inputs.push(argument);
}
for (let i = 0; i < node.outputs.length; i++) {
const edge = node.outputs[i];
const value = values.map(edge.name.key);
const argument = new dot.Argument(i.toString(), [value]);
this.outputs.push(argument);
}
for (const [name, value] of node.attributes) {
const argument = new dot.Argument(name, value, 'attribute');
this.attributes.push(argument);
}
for (const [name, value] of node.metadata) {
const argument = new dot.Argument(name, value);
this.metadata.push(argument);
}
}
};
dot.TensorType = class {
constructor(type) {
const index = type.indexOf('[');
const dtype = type.substring(0, index);
this.dataType = dtype.split('.').pop();
if (index > 0) {
const dimensions = JSON.parse(type.substring(index, type.length));
this.shape = new dot.TensorShape(dimensions);
} else {
this.shape = new dot.TensorShape([]);
}
}
toString() {
return this.dataType + this.shape.toString();
}
};
dot.TensorShape = class {
constructor(dimensions) {
this.dimensions = dimensions;
}
toString() {
if (this.dimensions && this.dimensions.length > 0) {
return `[${this.dimensions.map((dimension) => dimension.toString()).join(',')}]`;
}
return '';
}
};
dot.Tensor = class {
constructor(stmt) {
if (stmt.attributes.has('type')) {
const type = stmt.attributes.get('type');
this.type = new dot.TensorType(type);
} else {
this.type = new dot.TensorType('?');
}
}
};
dot.Parser = class {
constructor(decoder) {
// https://graphviz.org/doc/info/lang.html
this._tokenizer = new dot.Tokenizer(decoder);
this._token = this._tokenizer.read();
}
parse() {
const graph = {};
if (this._eat('id', 'strict')) {
graph.strict = true;
}
let edgeop = '';
if (this._match('id', 'graph')) {
graph.kind = this._read();
edgeop = '--';
} else if (this._match('id', 'digraph')) {
graph.kind = this._read();
edgeop = '->';
} else {
throw new dot.Error('Invalid graph type.');
}
if (this._match('id')) {
graph.name = this._read();
}
const defaults = {};
defaults.graph = new Map();
defaults.node = new Map();
defaults.edge = new Map();
graph.statements = this._parseBlock(defaults, edgeop, 0);
graph.defaults = new Map(defaults.graph);
return graph;
}
_parseBlock(defaults, edgeop) {
defaults = {
graph: new Map(defaults.graph),
node: new Map(defaults.node),
edge: new Map(defaults.edge)
};
const list = [];
this._read('{');
while (!this._match('}')) {
if (this._eat('id', 'subgraph')) {
const stmt = {};
stmt.kind = 'subgraph';
if (this._match('id')) {
stmt.name = this._read();
}
stmt.statements = this._parseBlock(defaults, edgeop);
} else if (this._match('{')) {
const stmt = {};
const statements = this._parseBlock(defaults, edgeop);
if (this._eat(edgeop)) {
if (!statements.every((stmt) => stmt.kind === 'node' && stmt.attributes.size === 0)) {
throw new dot.Error('Invalid edge group statement.');
}
const sources = statements.map((stmt) => stmt.name);
list.push(...this._parseEdges(sources, edgeop, defaults.edge));
} else {
stmt.kind = 'subgraph';
stmt.statements = statements;
}
} else if (this._match('id')) {
const name = this._parseNodeId();
if (this._eat('=')) { // attr
if (this._match('id')) {
const value = this._read();
defaults.graph.set(name, value);
} else {
throw new dot.Error('Invalid attribute value.');
}
} else if (this._eat(edgeop)) {
list.push(...this._parseEdges([name], edgeop, defaults.edge));
} else {
const attributes = this._parseAttributes();
if (name.key === 'node' || name.key === 'edge' || name.key === 'graph') {
for (const [key, value] of attributes) {
defaults[name.key].set(key, value);
}
} else {
list.push({ kind: 'node', name, attributes, defaults: new Map(defaults.node) });
}
}
}
if (this._match(';') || this._match(',')) {
this._read();
}
}
this._read('}');
return list;
}
_parseNodeIds() {
const list = [];
const open = this._eat('{');
while (!this._match('}')) {
const value = this._parseNodeId();
list.push(value);
if (this._match(',')) {
this._read();
continue;
} else if (this._match(';')) {
this._read();
if (!open) {
break;
}
} else if (!open) {
break;
}
}
if (open) {
this._read('}');
}
return list;
}
_parseNodeId() {
const name = {};
const list = [];
name.id = this._read('id');
list.push(name.id);
if (this._eat(':')) {
name.port = this._read('id');
list.push(name.port);
if (this._eat(':')) {
name.compass_pt = this._read('id');
list.push(name.compass_pt);
}
}
name.key = list.join(':');
return name;
}
_parseAttributes() {
const table = new Map();
if (this._eat('[')) {
while (this._match('id')) {
const name = this._read('id');
this._read('=');
const value = this._read('id');
table.set(name, value);
if (this._match(';') || this._match(',')) {
this._read();
}
}
this._read(']');
}
return table;
}
_parseEdges(sources, edgeop, defaults) {
const list = [];
do {
const targets = this._parseNodeIds();
for (const name of sources) {
for (const to of targets) {
list.push({ kind: 'edge', name, to });
}
}
sources = targets;
} while (this._eat(edgeop));
const attributes = this._parseAttributes();
for (const edge of list) {
edge.attributes = attributes;
edge.defaults = new Map(defaults.edge);
}
return list;
}
_match(kind, value) {
return (this._token.kind === kind && (!value || this._token.value === value));
}
_read(kind, value) {
if (kind && this._token.kind !== kind) {
throw new dot.Error(`Expected token of type '${kind}', but got '${this._token.kind}' ${this._tokenizer.location()}`);
}
if (value && this._token.value !== value) {
throw new dot.Error(`Expected token with value '${value}', but got '${this._token.value}' ${this._tokenizer.location()}`);
}
const token = this._token;
this._token = this._tokenizer.read();
return token.value;
}
_eat(kind, value) {
if (this._match(kind, value)) {
return this._read();
}
return null;
}
};
dot.Tokenizer = class {
constructor(decoder) {
this._decoder = decoder;
this._position = 0;
this._char = this._decoder.decode();
}
_read() {
if (this._char === undefined) {
this._unexpected();
}
const char = this._char;
this._position = this._decoder.position;
this._char = this._decoder.decode();
return char;
}
_peek() {
const position = this._decoder.position;
const char = this._decoder.decode();
this._decoder.position = position;
return char;
}
read() {
while (this._char) {
if (/\s/.test(this._char)) {
this._skipWhitespace();
continue;
}
if (this._char === '/' || this._char === '#') {
this._skipComment();
continue;
}
if (/[{}[\]=:;,]/.test(this._char)) {
const value = this._read();
return { kind: value, value };
} else if (this._char === '-') {
let value = this._read();
if (this._char === '>' || this._char === '-') {
value += this._read();
return { kind: value, value };
}
throw new dot.Error(`Unexpected character '${value}' ${this.location()}`);
} else if (/[a-zA-Z0-9_$"<]/.test(this._char)) {
const value = this._identifier();
return { kind: 'id', value };
} else {
throw new dot.Error(`Unexpected character '${this._char}' ${this.location()}`);
}
}
return { type: 'eof' };
}
_skipWhitespace() {
while (this._char !== undefined && /\s/.test(this._char)) {
this._read();
}
}
_skipComment() {
if (this._char === '#' || (this._char === '/' && this._peek() === '/')) {
while (this._char && this._char !== '\n') {
this._read();
}
return;
}
if (this._char === '/' && this._peek() === '*') {
while (this._char && (this._char !== '*' || this._peek() !== '/')) {
this._read();
}
this._read();
this._read();
return;
}
throw new dot.Error('Invalid comment.');
}
_identifier() {
let value = '';
if (this._char === '"') { // double quoted string
this._read();
while (this._char && this._char !== '"') {
value += this._read();
}
this._read('"');
} if (this._char === '<') { // HTML String
value += this._read();
let depth = 0;
while (depth > 0 || this._char !== '>') {
const c = this._read();
value += c;
if (c === '<') {
depth += 1;
} else if (c === '>') {
depth -= 1;
}
}
value += this._read();
} else {
while (/[a-zA-Z0-9_$.*]/.test(this._char)) {
value += this._read();
}
}
return value;
}
_unexpected() {
let c = this._char;
if (c === undefined) {
throw new dot.Error('Unexpected end of input.');
} else if (c === '"') {
c = 'string';
} else if ((c >= '0' && c <= '9') || c === '-') {
c = 'number';
} else {
if (c < ' ' || c > '\x7F') {
const name = Object.keys(this._escape).filter((key) => this._escape[key] === c);
c = (name.length === 1) ? `\\${name}` : `\\u${(`000${c.charCodeAt(0).toString(16)}`).slice(-4)}`;
}
c = `token '${c}'`;
}
this._throw(`Unexpected ${c}`);
}
_throw(message) {
message = message.replace(/\.$/, '');
throw new dot.Error(`${message} ${this._location()}`);
}
location() {
let line = 1;
let column = 1;
const position = this._decoder.position;
this._decoder.position = 0;
let c = '';
do {
if (this._decoder.position === this._position) {
this._decoder.position = position;
return `at ${line}:${column}.`;
}
c = this._decoder.decode();
if (c === '\n') {
line++;
column = 1;
} else {
column++;
}
}
while (c !== undefined);
this._decoder.position = position;
return `at ${line}:${column}.`;
}
};
dot.Error = class extends Error {
constructor(message) {
super(message);
this.name = 'Error loadig DOT graph';
}
};
export const ModelFactory = dot.ModelFactory;