// Simplified DOT grammar // // Not supported (yet): // // * HTML IDs { function merge(a, b, key) { function x(a) { a.forEach(function (b) { if (!(b[key] in obj)) { obj[b[key]] = obj[b[key]] || {}; array.push(obj[b[key]]); } Object.keys(b).forEach(function (k) { obj[b[key]][k] = b[k]; }); }); } var array = [], obj = {}; x(a); x(b); return array; } var directed; } start = graphStmt+ graphStmt = _* strict:(strict _)? type:graphType _* id:(id)? _* '{' _* stmts:stmtList? _* '}' _* { return {type: type, id: id, strict: strict !== null, stmts: stmts}; } stmtList = first:stmt _* ';'? rest:(_* inner:stmt _* ';'?)* { var result = [first]; for (var i = 0; i < rest.length; ++i) { result.push(rest[i][1]); } return result; } stmt = attrStmt / edgeStmt / subgraphStmt / inlineAttrStmt / nodeStmt attrStmt = type:(graph / node /edge) _* attrs:attrList { return { type: "attr", attrType: type, attrs: attrs || {}}; } inlineAttrStmt = k:id _* '=' _* v:id { var attrs = {}; attrs[k] = v; return { type: "inlineAttr", attrs: attrs }; } nodeStmt = id:nodeId _* attrs:attrList? { return {type: "node", id: id, attrs: attrs || {}}; } edgeStmt = lhs:(nodeIdOrSubgraph) _* rhs:edgeRHS _* attrs:attrList? { var elems = [lhs]; for (var i = 0; i < rhs.length; ++i) { elems.push(rhs[i]); } return { type: "edge", elems: elems, attrs: attrs || {} }; } subgraphStmt = id:(subgraph _* (id _*)?)? '{' _* stmts:stmtList? _* '}' { id = (id && id[2]) || []; return { type: "subgraph", id: id[0], stmts: stmts }; } attrList = first:attrListBlock rest:(_* attrListBlock)* { var result = first; for (var i = 0; i < rest.length; ++i) { merge(result, rest[i][1]); } return result; } attrListBlock = '[' _* aList:aList? _* ']' { return aList; } aList = first:idDef rest:(_* ','? _* idDef)* { var result = first; for (var i = 0; i < rest.length; ++i) { _.merge(result, rest[i][3]); } return result; } edgeRHS = ("--" !{ return directed; } / "->" &{ return directed; }) _* rhs:(nodeIdOrSubgraph) _* rest:edgeRHS? { var result = [rhs]; if (rest) { for (var i = 0; i < rest.length; ++i) { result.push(rest[i]); } } return result; } idDef = k:id v:(_* '=' _* id)? { var result = {}; result[k] = v[3]; return result; } nodeIdOrSubgraph = subgraphStmt / id:nodeId { return { type: "node", id: id, attrs: {} }; } nodeId = id:id _* port? { return id; } port = ':' _* id _* (':' _* compassPt)? compassPt = "ne" / "se" / "sw" / "nw" / "n" / "e" / "s" / "w" / "c" / "_" id "identifier" = fst:[a-zA-Z\u0200-\u0377_] rest:[a-zA-Z\u0200-\u0377_0-9]* { return fst + rest.join(""); } / sign:'-'? dot:'.' after:[0-9]+ { return (sign || "") + dot + after.join(""); } / sign:'-'? before:[0-9]+ after:('.' [0-9]*)? { return (sign || "") + before.join("") + (after ? after[0] : "") + (after ? after[1].join("") : ""); } / '"' id:("\\\"" { return '"'; } / "\\" ch:[^"] { return "\\" + ch; } / [^"])* '"' { return id.join(""); } node = k:"node"i { return k.toLowerCase(); } edge = k:"edge"i { return k.toLowerCase(); } graph = k:"graph"i { return k.toLowerCase(); } digraph = k:"digraph"i { return k.toLowerCase(); } subgraph = k:"subgraph"i { return k.toLowerCase(); } strict = k:"strict"i { return k.toLowerCase(); } graphType = graph:graph / graph:digraph { directed = graph === "digraph"; return graph; } whitespace "whitespace" = [ \t\r\n]+ comment "comment" = "//" ([^\n])* / "/*" (!"*/" .)* "*/" _ = whitespace / comment