dot.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662
  1. const dot = {};
  2. dot.ModelFactory = class {
  3. async match(context) {
  4. const reader = await context.read('text', 0x10000);
  5. if (reader) {
  6. try {
  7. for (let i = 0; i < 64; i++) {
  8. const line = reader.read('\n');
  9. if (line === undefined) {
  10. break;
  11. }
  12. if (line.trim().startsWith('//') || line.trim().startsWith('#')) {
  13. continue;
  14. }
  15. if (line.trim().match(/^(strict)?\s*digraph/)) {
  16. return context.set('dot');
  17. }
  18. }
  19. } catch {
  20. // continue regardless of error
  21. }
  22. }
  23. return null;
  24. }
  25. async open(context) {
  26. const decoder = await context.read('text.decoder');
  27. const parser = new dot.Parser(decoder);
  28. const graph = parser.parse();
  29. if (graph.kind !== 'digraph') {
  30. throw new dot.Error(`Graph type '${graph.type}' is not supported.`);
  31. }
  32. return new dot.Model(graph);
  33. }
  34. };
  35. dot.Model = class {
  36. constructor(graph) {
  37. this.format = 'DOT';
  38. this.modules = [new dot.Graph(graph)];
  39. }
  40. };
  41. dot.Graph = class {
  42. constructor(graph) {
  43. this.name = graph.name || '';
  44. this.nodes = [];
  45. this.inputs = [];
  46. this.outputs = [];
  47. const values = new Map();
  48. values.map = (name, type, tensor, metadata) => {
  49. if (typeof name !== 'string') {
  50. throw new dot.Error('Invalid value name.');
  51. }
  52. if (!values.has(name) || tensor) {
  53. values.set(name, new dot.Value(name, type, tensor, metadata));
  54. }
  55. return values.get(name);
  56. };
  57. const nodes = new Map();
  58. nodes.map = (name) => {
  59. if (typeof name !== 'string') {
  60. throw new dot.Error('Invalid node name.');
  61. }
  62. if (!nodes.has(name)) {
  63. const node = {
  64. kind: 'node',
  65. name: { id: name, key: name },
  66. type: { name },
  67. inputs: [],
  68. outputs: [],
  69. attributes: new Map(),
  70. metadata: new Map()
  71. };
  72. nodes.set(name, node);
  73. }
  74. return nodes.get(name);
  75. };
  76. for (const node of graph.statements) {
  77. if (node.kind === 'node') {
  78. node.inputs = [];
  79. node.outputs = [];
  80. node.metadata = new Map([...node.defaults, ...node.attributes]);
  81. node.attributes = new Map();
  82. delete node.defaults;
  83. const metadata = node.metadata;
  84. if (metadata.has('label')) {
  85. const label = metadata.get('label');
  86. if (label.startsWith('{') && label.endsWith('}')) {
  87. const lines = label.substring(1, label.length - 1).split('|');
  88. if (lines.length > 1 && node.name.id === lines[0] && lines[1].startsWith('op_code=')) {
  89. const def = lines[1].split('\\l');
  90. const op_code = def[0].split('=').pop();
  91. node.type = { name: op_code };
  92. if (op_code === 'call_module') {
  93. node.type = { name: def[1], type: 'function' };
  94. } else if (op_code === 'call_function') {
  95. const vals = lines[2].split('\\l');
  96. node.type = { name: vals[0] };
  97. } else if (op_code.startsWith('get_parameter')) {
  98. node.attributes.set('type', op_code.substring(13, op_code.length).trim());
  99. node.type = { name: 'get_parameter' };
  100. }
  101. if (lines.length > 2) {
  102. const attributes = lines[2].split('\\l');
  103. for (const attribute of attributes) {
  104. const parts = attribute.split(':');
  105. if (parts.length === 2) {
  106. const key = parts[0].trim();
  107. let value = parts[1].trim();
  108. if (value.startsWith('(') && value.endsWith(')')) {
  109. value = JSON.parse(`[${value.substring(1, value.length - 1)}]`);
  110. }
  111. node.attributes.set(key, value);
  112. }
  113. }
  114. }
  115. metadata.delete('label');
  116. } else if (lines.length === 1 && lines[0].startsWith('buffer\\l')) {
  117. const def = lines[0].split('\\l');
  118. node.type = { name: def[0] };
  119. if (def.length > 1) {
  120. node.attributes.set('type', def[1]);
  121. }
  122. metadata.delete('label');
  123. }
  124. } else {
  125. const match = label.match(/^name:\s*([A-Za-z][A-Za-z0-9_]*)\stype:\s*([A-Za-z][A-Za-z0-9_]*)$/);
  126. if (match && node.name.id === match[1]) {
  127. node.type = { name: match[2] };
  128. metadata.delete('label');
  129. }
  130. }
  131. }
  132. if (!node.type) {
  133. const lines = node.name.id.split('\\n');
  134. const match = lines[0].match(/^([A-Z][A-Za-z0-9_]*)\/([A-Z][A-Za-z0-9_]*)\s\(op#(\d+)\)$/);
  135. if (match) {
  136. node.type = { name: match[2] };
  137. } else {
  138. const match = lines[0].match(/^([A-Z][A-Za-z0-9_]*)\s\(op#(\d+)\)$/);
  139. if (match) {
  140. node.type = { name: match[1] };
  141. } else {
  142. // debugger;
  143. }
  144. }
  145. }
  146. if (!node.type) {
  147. node.type = { name: node.name.id };
  148. }
  149. nodes.set(node.name.id, node);
  150. }
  151. }
  152. for (const edge of graph.statements) {
  153. if (edge.kind === 'edge') {
  154. edge.uses = edge.uses || [];
  155. const to = nodes.map(edge.to.id);
  156. to.inputs.push(edge);
  157. edge.uses.push(to);
  158. edge.from = nodes.map(edge.name.id);
  159. edge.from.outputs.push(edge);
  160. }
  161. }
  162. for (const [key, node] of nodes) {
  163. const keys = new Set(['pos', 'height', 'width', 'shape', 'label']);
  164. if (node.metadata.get('shape') === 'octagon' && node.metadata.keys().every((key) => keys.has(key)) &&
  165. 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 &&
  166. new Set(node.outputs.map((output) => output.name.id)).size === 1 && node.outputs.every((output) => output.uses.length === 1)) {
  167. const [from] = node.inputs[0].from.outputs;
  168. for (const e of node.outputs) {
  169. const [n] = e.uses;
  170. n.inputs = n.inputs.map((edge) => edge === e ? from : edge);
  171. }
  172. nodes.delete(key);
  173. }
  174. }
  175. for (const [key, node] of nodes) {
  176. if ((node.type.name === 'get_parameter' || node.type.name === 'buffer' || node.type.name === 'Constant') &&
  177. node.inputs.length === 0 &&
  178. node.outputs.length === 1 && node.outputs[0].uses.length === 1) {
  179. node.outputs[0].initializer = node;
  180. nodes.delete(key);
  181. }
  182. }
  183. for (const [, obj] of nodes) {
  184. const node = new dot.Node(obj, values);
  185. this.nodes.push(node);
  186. }
  187. for (const edge of graph.statements) {
  188. if (edge.kind === 'edge') {
  189. const value = values.map(edge.name.id);
  190. const metadata = new Map([...edge.defaults, ...edge.attributes]);
  191. value.metadata = Array.from(metadata).map(([key, value]) => new dot.Argument(key, value));
  192. }
  193. }
  194. }
  195. };
  196. dot.Argument = class {
  197. constructor(name, value, type = null) {
  198. this.name = name;
  199. this.value = value;
  200. this.type = type;
  201. }
  202. };
  203. dot.Value = class {
  204. constructor(name, type, initializer, metadata) {
  205. this.name = name;
  206. this.type = !type && initializer ? initializer.type : type;
  207. this.initializer = initializer || null;
  208. this.metadata = metadata;
  209. }
  210. };
  211. dot.Node = class {
  212. constructor(node, values) {
  213. this.name = node.name.key;
  214. this.type = node.type;
  215. this.inputs = [];
  216. this.outputs = [];
  217. this.attributes = [];
  218. this.metadata = [];
  219. for (let i = 0; i < node.inputs.length; i++) {
  220. const edge = node.inputs[i];
  221. const initializer = edge.initializer ? new dot.Tensor(edge.initializer) : null;
  222. const value = values.map(edge.name.key, null, initializer);
  223. const argument = new dot.Argument(i.toString(), [value]);
  224. this.inputs.push(argument);
  225. }
  226. for (let i = 0; i < node.outputs.length; i++) {
  227. const edge = node.outputs[i];
  228. const value = values.map(edge.name.key);
  229. const argument = new dot.Argument(i.toString(), [value]);
  230. this.outputs.push(argument);
  231. }
  232. for (const [name, value] of node.attributes) {
  233. const argument = new dot.Argument(name, value, 'attribute');
  234. this.attributes.push(argument);
  235. }
  236. for (const [name, value] of node.metadata) {
  237. const argument = new dot.Argument(name, value);
  238. this.metadata.push(argument);
  239. }
  240. }
  241. };
  242. dot.TensorType = class {
  243. constructor(type) {
  244. const index = type.indexOf('[');
  245. const dtype = type.substring(0, index);
  246. this.dataType = dtype.split('.').pop();
  247. if (index > 0) {
  248. const dimensions = JSON.parse(type.substring(index, type.length));
  249. this.shape = new dot.TensorShape(dimensions);
  250. } else {
  251. this.shape = new dot.TensorShape([]);
  252. }
  253. }
  254. toString() {
  255. return this.dataType + this.shape.toString();
  256. }
  257. };
  258. dot.TensorShape = class {
  259. constructor(dimensions) {
  260. this.dimensions = dimensions;
  261. }
  262. toString() {
  263. if (this.dimensions && this.dimensions.length > 0) {
  264. return `[${this.dimensions.map((dimension) => dimension.toString()).join(',')}]`;
  265. }
  266. return '';
  267. }
  268. };
  269. dot.Tensor = class {
  270. constructor(stmt) {
  271. if (stmt.attributes.has('type')) {
  272. const type = stmt.attributes.get('type');
  273. this.type = new dot.TensorType(type);
  274. } else {
  275. this.type = new dot.TensorType('?');
  276. }
  277. }
  278. };
  279. dot.Parser = class {
  280. constructor(decoder) {
  281. // https://graphviz.org/doc/info/lang.html
  282. this._tokenizer = new dot.Tokenizer(decoder);
  283. this._token = this._tokenizer.read();
  284. }
  285. parse() {
  286. const graph = {};
  287. if (this._eat('id', 'strict')) {
  288. graph.strict = true;
  289. }
  290. let edgeop = '';
  291. if (this._match('id', 'graph')) {
  292. graph.kind = this._read();
  293. edgeop = '--';
  294. } else if (this._match('id', 'digraph')) {
  295. graph.kind = this._read();
  296. edgeop = '->';
  297. } else {
  298. throw new dot.Error('Invalid graph type.');
  299. }
  300. if (this._match('id')) {
  301. graph.name = this._read();
  302. }
  303. const defaults = {};
  304. defaults.graph = new Map();
  305. defaults.node = new Map();
  306. defaults.edge = new Map();
  307. graph.statements = this._parseBlock(defaults, edgeop, 0);
  308. graph.defaults = new Map(defaults.graph);
  309. return graph;
  310. }
  311. _parseBlock(defaults, edgeop) {
  312. defaults = {
  313. graph: new Map(defaults.graph),
  314. node: new Map(defaults.node),
  315. edge: new Map(defaults.edge)
  316. };
  317. const list = [];
  318. this._read('{');
  319. while (!this._match('}')) {
  320. if (this._eat('id', 'subgraph')) {
  321. const stmt = {};
  322. stmt.kind = 'subgraph';
  323. if (this._match('id')) {
  324. stmt.name = this._read();
  325. }
  326. stmt.statements = this._parseBlock(defaults, edgeop);
  327. } else if (this._match('{')) {
  328. const stmt = {};
  329. const statements = this._parseBlock(defaults, edgeop);
  330. if (this._eat(edgeop)) {
  331. if (!statements.every((stmt) => stmt.kind === 'node' && stmt.attributes.size === 0)) {
  332. throw new dot.Error('Invalid edge group statement.');
  333. }
  334. const sources = statements.map((stmt) => stmt.name);
  335. list.push(...this._parseEdges(sources, edgeop, defaults.edge));
  336. } else {
  337. stmt.kind = 'subgraph';
  338. stmt.statements = statements;
  339. }
  340. } else if (this._match('id')) {
  341. const name = this._parseNodeId();
  342. if (this._eat('=')) { // attr
  343. if (this._match('id')) {
  344. const value = this._read();
  345. defaults.graph.set(name, value);
  346. } else {
  347. throw new dot.Error('Invalid attribute value.');
  348. }
  349. } else if (this._eat(edgeop)) {
  350. list.push(...this._parseEdges([name], edgeop, defaults.edge));
  351. } else {
  352. const attributes = this._parseAttributes();
  353. if (name.key === 'node' || name.key === 'edge' || name.key === 'graph') {
  354. for (const [key, value] of attributes) {
  355. defaults[name.key].set(key, value);
  356. }
  357. } else {
  358. list.push({ kind: 'node', name, attributes, defaults: new Map(defaults.node) });
  359. }
  360. }
  361. }
  362. if (this._match(';') || this._match(',')) {
  363. this._read();
  364. }
  365. }
  366. this._read('}');
  367. return list;
  368. }
  369. _parseNodeIds() {
  370. const list = [];
  371. const open = this._eat('{');
  372. while (!this._match('}')) {
  373. const value = this._parseNodeId();
  374. list.push(value);
  375. if (this._match(',')) {
  376. this._read();
  377. continue;
  378. } else if (this._match(';')) {
  379. this._read();
  380. if (!open) {
  381. break;
  382. }
  383. } else if (!open) {
  384. break;
  385. }
  386. }
  387. if (open) {
  388. this._read('}');
  389. }
  390. return list;
  391. }
  392. _parseNodeId() {
  393. const name = {};
  394. const list = [];
  395. name.id = this._read('id');
  396. list.push(name.id);
  397. if (this._eat(':')) {
  398. name.port = this._read('id');
  399. list.push(name.port);
  400. if (this._eat(':')) {
  401. name.compass_pt = this._read('id');
  402. list.push(name.compass_pt);
  403. }
  404. }
  405. name.key = list.join(':');
  406. return name;
  407. }
  408. _parseAttributes() {
  409. const table = new Map();
  410. if (this._eat('[')) {
  411. while (this._match('id')) {
  412. const name = this._read('id');
  413. this._read('=');
  414. const value = this._read('id');
  415. table.set(name, value);
  416. if (this._match(';') || this._match(',')) {
  417. this._read();
  418. }
  419. }
  420. this._read(']');
  421. }
  422. return table;
  423. }
  424. _parseEdges(sources, edgeop, defaults) {
  425. const list = [];
  426. do {
  427. const targets = this._parseNodeIds();
  428. for (const name of sources) {
  429. for (const to of targets) {
  430. list.push({ kind: 'edge', name, to });
  431. }
  432. }
  433. sources = targets;
  434. } while (this._eat(edgeop));
  435. const attributes = this._parseAttributes();
  436. for (const edge of list) {
  437. edge.attributes = attributes;
  438. edge.defaults = new Map(defaults.edge);
  439. }
  440. return list;
  441. }
  442. _match(kind, value) {
  443. return (this._token.kind === kind && (!value || this._token.value === value));
  444. }
  445. _read(kind, value) {
  446. if (kind && this._token.kind !== kind) {
  447. throw new dot.Error(`Expected token of type '${kind}', but got '${this._token.kind}' ${this._tokenizer.location()}`);
  448. }
  449. if (value && this._token.value !== value) {
  450. throw new dot.Error(`Expected token with value '${value}', but got '${this._token.value}' ${this._tokenizer.location()}`);
  451. }
  452. const token = this._token;
  453. this._token = this._tokenizer.read();
  454. return token.value;
  455. }
  456. _eat(kind, value) {
  457. if (this._match(kind, value)) {
  458. return this._read();
  459. }
  460. return null;
  461. }
  462. };
  463. dot.Tokenizer = class {
  464. constructor(decoder) {
  465. this._decoder = decoder;
  466. this._position = 0;
  467. this._char = this._decoder.decode();
  468. }
  469. _read() {
  470. if (this._char === undefined) {
  471. this._unexpected();
  472. }
  473. const char = this._char;
  474. this._position = this._decoder.position;
  475. this._char = this._decoder.decode();
  476. return char;
  477. }
  478. _peek() {
  479. const position = this._decoder.position;
  480. const char = this._decoder.decode();
  481. this._decoder.position = position;
  482. return char;
  483. }
  484. read() {
  485. while (this._char) {
  486. if (/\s/.test(this._char)) {
  487. this._skipWhitespace();
  488. continue;
  489. }
  490. if (this._char === '/' || this._char === '#') {
  491. this._skipComment();
  492. continue;
  493. }
  494. if (/[{}[\]=:;,]/.test(this._char)) {
  495. const value = this._read();
  496. return { kind: value, value };
  497. } else if (this._char === '-') {
  498. let value = this._read();
  499. if (this._char === '>' || this._char === '-') {
  500. value += this._read();
  501. return { kind: value, value };
  502. }
  503. throw new dot.Error(`Unexpected character '${value}' ${this.location()}`);
  504. } else if (/[a-zA-Z0-9_$"<]/.test(this._char)) {
  505. const value = this._identifier();
  506. return { kind: 'id', value };
  507. } else {
  508. throw new dot.Error(`Unexpected character '${this._char}' ${this.location()}`);
  509. }
  510. }
  511. return { type: 'eof' };
  512. }
  513. _skipWhitespace() {
  514. while (this._char !== undefined && /\s/.test(this._char)) {
  515. this._read();
  516. }
  517. }
  518. _skipComment() {
  519. if (this._char === '#' || (this._char === '/' && this._peek() === '/')) {
  520. while (this._char && this._char !== '\n') {
  521. this._read();
  522. }
  523. return;
  524. }
  525. if (this._char === '/' && this._peek() === '*') {
  526. while (this._char && (this._char !== '*' || this._peek() !== '/')) {
  527. this._read();
  528. }
  529. this._read();
  530. this._read();
  531. return;
  532. }
  533. throw new dot.Error('Invalid comment.');
  534. }
  535. _identifier() {
  536. let value = '';
  537. if (this._char === '"') { // double quoted string
  538. this._read();
  539. while (this._char && this._char !== '"') {
  540. value += this._read();
  541. }
  542. this._read('"');
  543. } if (this._char === '<') { // HTML String
  544. value += this._read();
  545. let depth = 0;
  546. while (depth > 0 || this._char !== '>') {
  547. const c = this._read();
  548. value += c;
  549. if (c === '<') {
  550. depth += 1;
  551. } else if (c === '>') {
  552. depth -= 1;
  553. }
  554. }
  555. value += this._read();
  556. } else {
  557. while (/[a-zA-Z0-9_$.*]/.test(this._char)) {
  558. value += this._read();
  559. }
  560. }
  561. return value;
  562. }
  563. _unexpected() {
  564. let c = this._char;
  565. if (c === undefined) {
  566. throw new dot.Error('Unexpected end of input.');
  567. } else if (c === '"') {
  568. c = 'string';
  569. } else if ((c >= '0' && c <= '9') || c === '-') {
  570. c = 'number';
  571. } else {
  572. if (c < ' ' || c > '\x7F') {
  573. const name = Object.keys(this._escape).filter((key) => this._escape[key] === c);
  574. c = (name.length === 1) ? `\\${name}` : `\\u${(`000${c.charCodeAt(0).toString(16)}`).slice(-4)}`;
  575. }
  576. c = `token '${c}'`;
  577. }
  578. this._throw(`Unexpected ${c}`);
  579. }
  580. _throw(message) {
  581. message = message.replace(/\.$/, '');
  582. throw new dot.Error(`${message} ${this._location()}`);
  583. }
  584. location() {
  585. let line = 1;
  586. let column = 1;
  587. const position = this._decoder.position;
  588. this._decoder.position = 0;
  589. let c = '';
  590. do {
  591. if (this._decoder.position === this._position) {
  592. this._decoder.position = position;
  593. return `at ${line}:${column}.`;
  594. }
  595. c = this._decoder.decode();
  596. if (c === '\n') {
  597. line++;
  598. column = 1;
  599. } else {
  600. column++;
  601. }
  602. }
  603. while (c !== undefined);
  604. this._decoder.position = position;
  605. return `at ${line}:${column}.`;
  606. }
  607. };
  608. dot.Error = class extends Error {
  609. constructor(message) {
  610. super(message);
  611. this.name = 'Error loadig DOT graph';
  612. }
  613. };
  614. export const ModelFactory = dot.ModelFactory;