UNPKG

dot-language-support

Version:

Parser and language service for graphviz (dot) files

1,789 lines (1,783 loc) 104 kB
import * as lst from "vscode-languageserver-types"; import { Range, TextEdit } from "vscode-languageserver-types"; //#region src/core.ts function createMapFromTemplate(template) { const map = /* @__PURE__ */ new Map(); for (const key in template) if (key in template) map.set(key, template[key]); return map; } //#endregion //#region src/types.ts const errorSource = { Scan: 1, Parse: 2, Check: 4 }; const parseError = { ExpectationFailed: 0, TrailingData: 1, FailedListParsing: 2 }; const scanError = { ExpectationFailed: 0, Unterminated: 1 }; const checkError = { InvalidEdgeOperation: 0, InvalidShapeName: 1 }; const diagnosticCategory = { Error: 1, Warning: 2, Message: 3, Suggestion: 4 }; const syntaxKind = { Unknown: 0, EndOfFileToken: 1, NewLineTrivia: 2, WhitespaceTrivia: 3, HashCommentTrivia: 4, SingleLineCommentTrivia: 5, MultiLineCommentTrivia: 6, CommaToken: 7, SemicolonToken: 8, PlusToken: 9, OpenBraceToken: 10, CloseBraceToken: 11, OpenBracketToken: 12, CloseBracketToken: 13, ColonToken: 14, EqualsToken: 15, LessThan: 16, GreaterThan: 17, CompassNorthToken: 18, CompassNorthEastToken: 19, CompassEastToken: 20, CompassSouthEastToken: 21, CompassSouthToken: 22, CompassSouthWestToken: 23, CompassWestToken: 24, CompassNorthWestToken: 25, CompassCenterToken: 26, UnderscoreToken: 27, StringLiteral: 28, HtmlIdentifier: 29, TextIdentifier: 30, QuotedTextIdentifier: 31, NumericIdentifier: 32, GraphKeyword: 33, DigraphKeyword: 34, NodeKeyword: 35, EdgeKeyword: 36, SubgraphKeyword: 37, StrictKeyword: 38, DirectedEdgeOp: 39, UndirectedEdgeOp: 40, DirectedGraph: 41, UndirectedGraph: 42, NodeStatement: 43, EdgeStatement: 44, AttributeStatement: 45, IdEqualsIdStatement: 46, SubGraph: 47, SubGraphStatement: 48, EdgeRhs: 49, AttributeContainer: 50, Assignment: 51, NormalPortDeclaration: 52, CompassPortDeclaration: 53, NodeId: 54, Count: 55, FirstNode: 41, CompassBegin: 18, CompassEnd: 27, LastKeyword: 38 }; /** reverse lookup because we cannot make syntaxKind an enum */ const syntaxKindNames = { 0: "Unknown", 1: "EndOfFileToken", 2: "NewLineTrivia", 3: "WhitespaceTrivia", 4: "HashCommentTrivia", 5: "SingleLineCommentTrivia", 6: "MultiLineCommentTrivia", 7: "CommaToken", 8: "SemicolonToken", 9: "PlusToken", 10: "OpenBraceToken", 11: "CloseBraceToken", 12: "OpenBracketToken", 13: "CloseBracketToken", 14: "ColonToken", 15: "EqualsToken", 16: "LessThan", 17: "GreaterThan", 18: "CompassNorthToken", 19: "CompassNorthEastToken", 20: "CompassEastToken", 21: "CompassSouthEastToken", 22: "CompassSouthToken", 23: "CompassSouthWestToken", 24: "CompassWestToken", 25: "CompassNorthWestToken", 26: "CompassCenterToken", 27: "UnderscoreToken", 28: "StringLiteral", 29: "HtmlIdentifier", 30: "TextIdentifier", 31: "QuotedTextIdentifier", 32: "NumericIdentifier", 33: "GraphKeyword", 34: "DigraphKeyword", 35: "NodeKeyword", 36: "EdgeKeyword", 37: "SubgraphKeyword", 38: "StrictKeyword", 39: "DirectedEdgeOp", 40: "UndirectedEdgeOp", 41: "DirectedGraph", 42: "UndirectedGraph", 43: "NodeStatement", 44: "EdgeStatement", 45: "AttributeStatement", 46: "IdEqualsIdStatement", 47: "SubGraph", 48: "SubGraphStatement", 49: "EdgeRhs", 50: "AttributeContainer", 51: "Assignment", 52: "NormalPortDeclaration", 53: "CompassPortDeclaration", 54: "NodeId", 55: "Count" }; const syntaxNodeFlags = { None: 0, ContainsErrors: 2, Synthesized: 4 }; const graphContext = { None: 0, Strict: 2, Directed: 4, Undirected: 8 }; const tokenFlags = { None: 0, Unterminated: 2, PrecedingLineBreak: 4 }; const characterCodes = { nullCharacter: 0, maxAsciiCharacter: 127, lineFeed: 10, carriageReturn: 13, lineSeparator: 8232, paragraphSeparator: 8233, nextLine: 133, space: 32, nonBreakingSpace: 160, enQuad: 8192, emQuad: 8193, enSpace: 8194, emSpace: 8195, threePerEmSpace: 8196, fourPerEmSpace: 8197, sixPerEmSpace: 8198, figureSpace: 8199, punctuationSpace: 8200, thinSpace: 8201, hairSpace: 8202, zeroWidthSpace: 8203, narrowNoBreakSpace: 8239, ideographicSpace: 12288, mathematicalSpace: 8287, ogham: 5760, _: 95, $: 36, _0: 48, _1: 49, _2: 50, _3: 51, _4: 52, _5: 53, _6: 54, _7: 55, _8: 56, _9: 57, a: 97, b: 98, c: 99, d: 100, e: 101, f: 102, g: 103, h: 104, i: 105, j: 106, k: 107, l: 108, m: 109, n: 110, o: 111, p: 112, q: 113, r: 114, s: 115, t: 116, u: 117, v: 118, w: 119, x: 120, y: 121, z: 122, A: 65, B: 66, C: 67, D: 68, E: 69, F: 70, G: 71, H: 72, I: 73, J: 74, K: 75, L: 76, M: 77, N: 78, O: 79, P: 80, Q: 81, R: 82, S: 83, T: 84, U: 85, V: 86, W: 87, X: 88, Y: 89, Z: 90, ampersand: 38, asterisk: 42, at: 64, backslash: 92, backtick: 96, bar: 124, caret: 94, closeBrace: 125, closeBracket: 93, closeParen: 41, colon: 58, comma: 44, dot: 46, doubleQuote: 34, equals: 61, exclamation: 33, greaterThan: 62, hash: 35, lessThan: 60, minus: 45, openBrace: 123, openBracket: 91, openParen: 40, percent: 37, plus: 43, question: 63, semicolon: 59, singleQuote: 39, slash: 47, tilde: 126, backspace: 8, formFeed: 12, byteOrderMark: 65279, tab: 9, verticalTab: 11 }; //#endregion //#region src/service/util.ts function getStart(sourceFile, node) { return getTokenPosOfNode(sourceFile, node); } function getTokenPosOfNode(sourceFile, node) { if (nodeIsMissing(node)) return node.pos; return skipTrivia(sourceFile.content, node.pos); } function nodeIsMissing(node) { return node === void 0 ? true : node.pos === node.end && node.pos >= 0 && node.kind !== syntaxKind.EndOfFileToken; } function syntaxNodesToRanges(doc, sourceFile, nodes) { return nodes.map((node) => syntaxNodeToRange(doc, sourceFile, node)); } function syntaxNodeToRange(doc, sourceFile, node) { const start = getStart(sourceFile, node); return { start: doc.positionAt(start), end: doc.positionAt(node.end) }; } function escapeIdentifierText(text) { if (text === "") return quote(""); if (text.includes("\"") || text.includes("\n")) return quote(text.replace(/"/, "\\\"").replace(/\n/, "\\\n")); if (!isIdentifierStart(text.charCodeAt(0)) || text.includes(" ")) return quote(text); return text; } const quote = (s) => `"${s}"`; function assertNever(v) { throw new Error(`Should not have reached this. Value: ${v ?? ""}`); } //#endregion //#region src/scanner.ts const textToToken = createMapFromTemplate({ digraph: syntaxKind.DigraphKeyword, graph: syntaxKind.GraphKeyword, edge: syntaxKind.EdgeKeyword, node: syntaxKind.NodeKeyword, strict: syntaxKind.StrictKeyword, subgraph: syntaxKind.SubgraphKeyword, n: syntaxKind.CompassNorthToken, ne: syntaxKind.CompassNorthEastToken, e: syntaxKind.CompassEastToken, se: syntaxKind.CompassSouthEastToken, s: syntaxKind.CompassSouthToken, sw: syntaxKind.CompassSouthWestToken, w: syntaxKind.CompassWestToken, nw: syntaxKind.CompassNorthWestToken, c: syntaxKind.CompassCenterToken, "+": syntaxKind.PlusToken, "=": syntaxKind.EqualsToken, "->": syntaxKind.DirectedEdgeOp, "--": syntaxKind.UndirectedEdgeOp, "{": syntaxKind.OpenBraceToken, "}": syntaxKind.CloseBraceToken, "[": syntaxKind.OpenBracketToken, "]": syntaxKind.CloseBracketToken, ";": syntaxKind.SemicolonToken, ":": syntaxKind.ColonToken, _: syntaxKind.UnderscoreToken, ",": syntaxKind.CommaToken, "<": syntaxKind.LessThan, ">": syntaxKind.GreaterThan }); function makeReverseMap(source) { const result = /* @__PURE__ */ new Map(); source.forEach((value, key) => { result.set(value, key); }); return result; } const tokenToText = makeReverseMap(textToToken); function getTokenAsText(token) { return tokenToText.get(token); } function getTextAsToken(token) { return textToToken.get(token); } var DefaultScanner = class { end; pos; startPos; tokenPos; token; tokenValue; tokenFlags; isUnterminated; text; onError; setText(newText, start = 0, length) { this.text = newText || ""; this.end = length === void 0 ? this.text.length : start + length; this.#setTextPos(start || 0); } setErrorCallback(cb) { this.onError = cb; } #setTextPos(textPos) { console.assert(textPos >= 0); this.pos = textPos; this.startPos = textPos; this.tokenPos = textPos; this.token = syntaxKind.Unknown; this.tokenValue = void 0; this.tokenFlags = tokenFlags.None; } scan(skipTrivia$1 = true) { this.startPos = this.pos; this.tokenFlags = tokenFlags.None; this.isUnterminated = false; while (true) { this.tokenPos = this.pos; if (this.pos >= this.end) return this.token = syntaxKind.EndOfFileToken; let ch = this.text.charCodeAt(this.pos); switch (ch) { case characterCodes.lineFeed: case characterCodes.carriageReturn: this.tokenFlags |= tokenFlags.PrecedingLineBreak; if (skipTrivia$1) { this.pos++; continue; } if (ch === characterCodes.carriageReturn && this.pos + 1 < this.end && this.text.charCodeAt(this.pos + 1) === characterCodes.lineFeed) this.pos += 2; else this.pos++; return this.token = syntaxKind.NewLineTrivia; case characterCodes.tab: case characterCodes.verticalTab: case characterCodes.formFeed: case characterCodes.space: if (skipTrivia$1) { this.pos++; continue; } while (this.pos < this.end && this.#isWhiteSpaceSingleLine(this.text.charCodeAt(this.pos))) this.pos++; return this.token = syntaxKind.WhitespaceTrivia; case characterCodes.hash: { const content = this.#scanHashCommentTrivia(skipTrivia$1); if (skipTrivia$1) continue; this.tokenValue = content; return this.token = syntaxKind.HashCommentTrivia; } case characterCodes.slash: if (this.pos + 1 < this.end) switch (this.text.charCodeAt(this.pos + 1)) { case characterCodes.slash: { const commentContent = this.#scanSingleLineCommentTrivia(skipTrivia$1); if (skipTrivia$1) continue; this.tokenValue = commentContent; return this.token = syntaxKind.SingleLineCommentTrivia; } case characterCodes.asterisk: { const commentContent = this.#scanMultiLineCommentTrivia(skipTrivia$1); if (skipTrivia$1) continue; this.tokenValue = commentContent; return this.token = syntaxKind.MultiLineCommentTrivia; } } this.#error("Unexpected \"/\". Did you mean to start a comment like \"/*\" or \"//\"? If you wanted to use it as an identifier, put it in double quotes.", scanError.ExpectationFailed); ++this.pos; break; case characterCodes.openBrace: this.pos++; return this.token = syntaxKind.OpenBraceToken; case characterCodes.closeBrace: this.pos++; return this.token = syntaxKind.CloseBraceToken; case characterCodes.openBracket: this.pos++; return this.token = syntaxKind.OpenBracketToken; case characterCodes.closeBracket: this.pos++; return this.token = syntaxKind.CloseBracketToken; case characterCodes.plus: this.pos++; return this.token = syntaxKind.PlusToken; case characterCodes.equals: this.pos++; return this.token = syntaxKind.EqualsToken; case characterCodes._0: case characterCodes._1: case characterCodes._2: case characterCodes._3: case characterCodes._4: case characterCodes._5: case characterCodes._6: case characterCodes._7: case characterCodes._8: case characterCodes._9: case characterCodes.dot: this.tokenValue = this.#scanNumber(); return this.token = syntaxKind.NumericIdentifier; case characterCodes.minus: switch (this.text.charCodeAt(this.pos + 1)) { case characterCodes.minus: this.pos += 2; return this.token = syntaxKind.UndirectedEdgeOp; case characterCodes.greaterThan: this.pos += 2; return this.token = syntaxKind.DirectedEdgeOp; case characterCodes._0: case characterCodes._1: case characterCodes._2: case characterCodes._3: case characterCodes._4: case characterCodes._5: case characterCodes._6: case characterCodes._7: case characterCodes._8: case characterCodes._9: case characterCodes.dot: this.tokenValue = this.#scanNumber(); return this.token = syntaxKind.NumericIdentifier; default: { const chr = this.text.charAt(this.pos + 1); this.#error(`Unexpected "${chr}". Did you mean to define an edge? Depending on the type of graph you are defining, use "->" or "--".`, scanError.ExpectationFailed); break; } } this.pos++; break; case characterCodes._: this.pos++; return this.token = syntaxKind.UnderscoreToken; case characterCodes.semicolon: this.pos++; return this.token = syntaxKind.SemicolonToken; case characterCodes.colon: this.pos++; return this.token = syntaxKind.ColonToken; case characterCodes.comma: this.pos++; return this.token = syntaxKind.CommaToken; case characterCodes.lessThan: this.tokenValue = this.#scanHtml(); return this.token = syntaxKind.HtmlIdentifier; case characterCodes.doubleQuote: this.tokenValue = this.#scanString(); return this.token = syntaxKind.StringLiteral; default: { if (isIdentifierStart(ch)) { this.pos++; while (this.pos < this.end && isIdentifierPart(ch = this.text.charCodeAt(this.pos))) this.pos++; const value = this.text.substring(this.tokenPos, this.pos); this.tokenValue = value; return this.token = this.#getIdentifierToken(value); } if (this.#isWhiteSpaceSingleLine(ch)) { this.pos++; continue; } const chr = this.text.charAt(this.pos); this.#error(`Unexpected "${chr}". Did you mean to start an identifier? Node names cannot start with "${chr}".`, scanError.ExpectationFailed); this.pos++; break; } } } } #error(message, sub, category = diagnosticCategory.Error, errPos = this.pos, length = 0) { const cb = this.onError; if (cb) { const posSave = this.pos; this.pos = errPos; cb(message, category, sub, length); this.pos = posSave; } } #isWhiteSpaceSingleLine(ch) { return ch === characterCodes.space || ch === characterCodes.tab || ch === characterCodes.verticalTab || ch === characterCodes.formFeed || ch === characterCodes.nonBreakingSpace || ch === characterCodes.nextLine || ch === characterCodes.ogham || ch >= characterCodes.enQuad && ch <= characterCodes.zeroWidthSpace || ch === characterCodes.narrowNoBreakSpace || ch === characterCodes.mathematicalSpace || ch === characterCodes.ideographicSpace || ch === characterCodes.byteOrderMark; } #isAtMultiLineCommentEnd(pos) { return pos + 1 < this.end && this.text.charCodeAt(pos) === characterCodes.asterisk && this.text.charCodeAt(pos + 1) === characterCodes.slash; } #scanHashCommentTrivia(skip) { ++this.pos; const start = this.pos; while (this.pos < this.end && !isLineBreak(this.text.charCodeAt(this.pos))) this.pos++; return skip ? void 0 : this.text.substring(start, this.pos); } #scanSingleLineCommentTrivia(skip) { this.pos += 2; const start = this.pos; while (this.pos < this.end && !isLineBreak(this.text.charCodeAt(this.pos))) this.pos++; return skip ? void 0 : this.text.substring(start, this.pos); } #scanMultiLineCommentTrivia(skip) { this.pos += 2; const start = this.pos; while (this.pos < this.end && !this.#isAtMultiLineCommentEnd(this.pos)) this.pos++; const commentEnd = this.pos; if (this.#isAtMultiLineCommentEnd(this.pos)) this.pos += 2; return skip ? void 0 : this.text.substring(start, commentEnd); } #scanHtml() { this.pos++; let result = ""; const start = this.pos; let subTagsLevel = 0; while (true) { if (this.pos >= this.end) { result += this.text.substring(start, this.pos); this.tokenFlags |= tokenFlags.Unterminated; this.isUnterminated = true; this.#error("Unterminated html literal", scanError.Unterminated); break; } const ch = this.text.charCodeAt(this.pos); if (ch === characterCodes.lessThan) { ++subTagsLevel; this.pos++; continue; } if (ch === characterCodes.greaterThan) { this.pos++; console.assert(subTagsLevel >= 0); if (subTagsLevel === 0) { result += this.text.substring(start, this.pos); break; } --subTagsLevel; continue; } this.pos++; } return result; } #scanString(allowEscapes = true) { const quote$1 = this.text.charCodeAt(this.pos); this.pos++; let result = ""; const start = this.pos; let hasBackslash = false; while (true) { if (this.pos >= this.end) { result += this.text.substring(start, this.pos); this.tokenFlags |= tokenFlags.Unterminated; this.isUnterminated = true; this.#error("Unterminated string", scanError.Unterminated); break; } const ch = this.text.charCodeAt(this.pos); if (ch === characterCodes.backslash) hasBackslash = true; else if (hasBackslash) hasBackslash = false; else { if (ch === quote$1) { result += this.text.substring(start, this.pos); this.pos++; break; } if (isLineBreak(ch)) { result += this.text.substring(start, this.pos); this.tokenFlags |= tokenFlags.Unterminated; this.isUnterminated = true; this.#error("Unterminated string", scanError.Unterminated); break; } } this.pos++; } return result.replace(/\\"/g, "\"").replace(/\\(\r?\n)/g, "$1"); } #scanNumber() { let result = ""; let hadDot = false; let hadMinus = false; const start = this.pos; while (true) { switch (this.text.charCodeAt(this.pos)) { case characterCodes._0: case characterCodes._1: case characterCodes._2: case characterCodes._3: case characterCodes._4: case characterCodes._5: case characterCodes._6: case characterCodes._7: case characterCodes._8: case characterCodes._9: break; case characterCodes.dot: if (hadDot) { result += this.text.substring(start, this.pos); return result; } hadDot = true; hadMinus = true; break; case characterCodes.minus: if (this.pos !== start || hadMinus) { result += this.text.substring(start, this.pos); return result; } hadMinus = true; break; default: result += this.text.substring(start, this.pos); return result; } ++this.pos; } } #getIdentifierToken(tokenValue) { const len = tokenValue.length; if (len >= 4 && len <= 8) { const ch = tokenValue.charCodeAt(0); if (ch >= characterCodes.a && ch <= characterCodes.z || ch >= characterCodes.A && ch <= characterCodes.Z) { const lowerCaseToken = tokenValue.toLowerCase(); const t = textToToken.get(lowerCaseToken); if (t !== void 0) { this.token = t; return t; } } } return this.token = syntaxKind.TextIdentifier; } lookAhead(callback) { return this.#speculationHelper(callback, true); } tryScan(callback) { return this.#speculationHelper(callback, false); } #speculationHelper(callback, isLookahead) { const savePos = this.pos; const saveStartPos = this.startPos; const saveTokenPos = this.tokenPos; const saveToken = this.token; const saveTokenValue = this.tokenValue; const saveTokenFlags = this.tokenFlags; const result = callback(); if (!result || isLookahead) { this.pos = savePos; this.startPos = saveStartPos; this.tokenPos = saveTokenPos; this.token = saveToken; this.tokenValue = saveTokenValue; this.tokenFlags = saveTokenFlags; } return result; } }; function isIdentifierStart(ch) { return ch >= characterCodes.A && ch <= characterCodes.Z || ch >= characterCodes.a && ch <= characterCodes.z || ch >= characterCodes._0 && ch <= characterCodes._9 || ch === characterCodes._ || ch === characterCodes.lessThan || ch === characterCodes.doubleQuote; } function isIdentifierPart(ch) { return ch >= characterCodes.A && ch <= characterCodes.Z || ch >= characterCodes.a && ch <= characterCodes.z || ch >= characterCodes._0 && ch <= characterCodes._9 || ch === characterCodes.$ || ch === characterCodes._ || ch > characterCodes.maxAsciiCharacter; } function skipTrivia(text, pos) { while (true) { switch (text.charCodeAt(pos)) { case characterCodes.carriageReturn: if (text.charCodeAt(pos + 1) === characterCodes.lineFeed) ++pos; continue; case characterCodes.lineFeed: case characterCodes.tab: case characterCodes.verticalTab: case characterCodes.formFeed: case characterCodes.space: ++pos; continue; case characterCodes.hash: ++pos; while (pos < text.length) { if (isLineBreak(text.charCodeAt(pos))) break; ++pos; } continue; case characterCodes.slash: if (pos + 1 < text.length) switch (text.charCodeAt(pos + 1)) { case characterCodes.slash: pos += 2; while (pos < text.length) { if (isLineBreak(text.charCodeAt(pos))) break; ++pos; } continue; case characterCodes.asterisk: pos += 2; while (pos < text.length) { if (text.charCodeAt(pos) === characterCodes.asterisk && text.charCodeAt(pos + 1) === characterCodes.slash) { pos += 2; break; } ++pos; } continue; } break; } return pos; } } function isLineBreak(ch) { return ch === characterCodes.lineFeed || ch === characterCodes.carriageReturn; } //#endregion //#region src/parser.ts const parsingContext = { None: 0, StatementList: 1, AttributeContainerList: 2, AssignmentList: 3, EdgeRhsList: 4, QuotedTextIdentifierConcatenation: 5, Count: 6 }; var Parser = class { currentToken = syntaxKind.Unknown; nodeCount; identifiers; identifierCount = 0; sourceText; scanner = new DefaultScanner(); currentNodeHasError; currentContext; diagnostics; constructor() { this.#resetState(); } #resetState() { this.sourceText = ""; this.scanner.setText(this.sourceText); this.scanner.setErrorCallback(this.#scanError.bind(this)); this.identifierCount = 0; this.identifiers = /* @__PURE__ */ new Set(); this.nodeCount = 0; this.diagnostics = []; this.currentNodeHasError = false; this.currentContext = parsingContext.None; } #nextToken() { this.currentToken = this.scanner.scan(true); return this.currentToken; } #token() { return this.currentToken; } #getLinesFromFile(sourceText) { return sourceText.split(/\r?\n/); } parse(sourceText) { this.sourceText = sourceText; this.scanner.setText(this.sourceText); this.#nextToken(); let graph; if (this.#token() !== syntaxKind.EndOfFileToken) { graph = this.#parseGraph(); if (this.#token() !== syntaxKind.EndOfFileToken) this.#parseErrorAtPosition(this.scanner.tokenPos, this.scanner.text.length - 1, "Content after the end of a graph declaration is invalid.", { source: errorSource.Parse, sub: parseError.TrailingData }); } const result = { content: this.sourceText, graph, identifiers: this.identifiers, diagnostics: this.diagnostics }; this.#resetState(); return result; } #parseGraph() { const strictToken = this.#parseOptionalToken(syntaxKind.StrictKeyword); const keyword = this.#parseExpectedTokenOneOf(syntaxKind.DigraphKeyword, [syntaxKind.DigraphKeyword, syntaxKind.GraphKeyword]); const kind = keyword === void 0 || keyword.kind === syntaxKind.DigraphKeyword ? syntaxKind.DirectedGraph : syntaxKind.UndirectedGraph; const graphStart = strictToken ? strictToken.pos : keyword.pos; const node = this.#createNode(kind, graphStart); node.strict = strictToken; node.keyword = keyword; node.id = this.#isIdentifier() ? this.#parseIdentifier() : void 0; this.#parseExpectedToken(syntaxKind.OpenBraceToken); node.statements = this.#parseList(parsingContext.StatementList, () => this.#parseStatement()); this.#parseExpectedToken(syntaxKind.CloseBraceToken); return this.#finishNode(node); } #parseIdentifier() { let result; const escapedIdTexts = []; switch (this.#token()) { case syntaxKind.TextIdentifier: result = this.#parseTextIdentifier(); escapedIdTexts.push(result.text); break; case syntaxKind.StringLiteral: result = this.#parseQuotedTextIdentifierConcatenation(); escapedIdTexts.push(...result.values.map((v) => v.text)); break; case syntaxKind.HtmlIdentifier: result = this.#parseHtmlIdentifier(); escapedIdTexts.push(result.htmlContent); break; case syntaxKind.NumericIdentifier: result = this.#parseNumericIdentifier(); escapedIdTexts.push(result.text); break; default: this.#reportExpectedError([syntaxKind.TextIdentifier]); result = this.#createMissingNode(syntaxKind.TextIdentifier); break; } for (const i of escapedIdTexts) this.#registerIdentifier(i); return result; } #registerIdentifier(id) { this.identifierCount++; if (!this.identifiers.has(id)) this.identifiers.add(id); } #parseTextIdentifier() { const node = this.#createNode(syntaxKind.TextIdentifier); const text = this.scanner.tokenValue; this.#nextToken(); if (text === void 0) throw "Satisfy type checker"; node.text = text; return this.#finishNode(node); } #parseQuotedTextIdentifierConcatenation() { const node = this.#createNode(syntaxKind.QuotedTextIdentifier); node.values = this.#parseList(parsingContext.QuotedTextIdentifierConcatenation, () => this.#parseQuotedTextIdentifier(), true); return this.#finishNode(node); } #parseQuotedTextIdentifier() { const node = this.#createNode(syntaxKind.StringLiteral); if (this.#token() === syntaxKind.PlusToken) this.#nextToken(); const text = this.scanner.tokenValue; this.#nextToken(); if (text === void 0) throw "Satisfy type checker"; node.text = text; return this.#finishNode(node); } #isQuotedStringFollowing() { this.#nextToken(); return this.#token() === syntaxKind.StringLiteral; } #parseHtmlIdentifier() { const node = this.#createNode(syntaxKind.HtmlIdentifier); const text = this.scanner.tokenValue; this.#nextToken(); if (text === void 0) throw "Satisfy type checker"; node.htmlContent = text; return this.#finishNode(node); } #parseNumericIdentifier() { const node = this.#createNode(syntaxKind.NumericIdentifier); const text = this.scanner.tokenValue; this.#nextToken(); if (text === void 0) throw "Satisfy type checker"; node.text = text; node.value = Number(text); return this.#finishNode(node); } #parseStatement() { switch (this.#token()) { case syntaxKind.GraphKeyword: case syntaxKind.NodeKeyword: case syntaxKind.EdgeKeyword: return this.#parseAttributeStatement(); case syntaxKind.OpenBraceToken: case syntaxKind.SubgraphKeyword: { const subgraph = this.#parseSubGraph(); if (this.#token() === syntaxKind.SemicolonToken) { const subgraphStatement$1 = this.#createNode(syntaxKind.SubGraphStatement, subgraph.pos); subgraphStatement$1.subgraph = subgraph; subgraphStatement$1.terminator = this.#parseExpectedToken(syntaxKind.SemicolonToken); return this.#finishNode(subgraphStatement$1); } if (this.#isEdgeOp()) return this.#parseEdgeStatement(subgraph); const subgraphStatement = this.#createNode(syntaxKind.SubGraphStatement, subgraph.pos); subgraphStatement.subgraph = subgraph; return this.#finishNode(subgraphStatement); } default: { if (!this.#isIdentifier) debugger; if (this.#lookAhead(() => this.#isIdEqualsIdStatement())) return this.#parseIdEqualsIdStatement(); const ns = this.#parseNodeStatement(); if (ns.terminator !== void 0 || ns.attributes.length !== 0) return ns; if (this.#isEdgeOp()) return this.#parseEdgeStatement(ns.id); return ns; } } } #parseAttributeStatement() { switch (this.#token()) { case syntaxKind.GraphKeyword: case syntaxKind.NodeKeyword: case syntaxKind.EdgeKeyword: { const node = this.#createNode(syntaxKind.AttributeStatement); node.subject = this.#parseTokenNode(); if (this.#token() === syntaxKind.OpenBracketToken) node.attributes = this.#parseList(parsingContext.AttributeContainerList, () => this.#parseAttributeContainer()); else { this.#reportExpectedError([syntaxKind.OpenBracketToken]); const missingStatement = this.#createMissingNode(syntaxKind.AttributeStatement); missingStatement.attributes = this.#createNodeArray([this.#createMissingNode(syntaxKind.AttributeContainer)], this.scanner.tokenPos, this.scanner.tokenPos); } node.terminator = this.#parseOptionalToken(syntaxKind.SemicolonToken); return this.#finishNode(node); } default: throw "This should never happen"; } } #parseAttributeContainer() { if (this.#token() !== syntaxKind.OpenBracketToken) debugger; const node = this.#createNode(syntaxKind.AttributeContainer); node.openBracket = this.#parseExpectedToken(syntaxKind.OpenBracketToken); if (this.#isIdentifier() && this.#lookAhead(() => this.#isAssignmentStart())) node.assignments = this.#parseList(parsingContext.AssignmentList, () => this.#parseAssignment()); else node.assignments = this.#createEmptyArray(); node.closeBracket = this.#parseExpectedToken(syntaxKind.CloseBracketToken); return this.#finishNode(node); } #isAssignmentStart() { if (!this.#isIdentifier) debugger; this.#nextToken(); return this.#token() === syntaxKind.EqualsToken; } #parseIdEqualsIdStatement() { if (!this.#isIdentifier) debugger; const leftIdentifier = this.#parseIdentifier(); const node = this.#createNode(syntaxKind.IdEqualsIdStatement, leftIdentifier.pos); node.leftId = leftIdentifier; if (this.#token() !== syntaxKind.EqualsToken) debugger; this.#parseExpectedToken(syntaxKind.EqualsToken); node.rightId = this.#parseIdentifier(); node.terminator = this.#parseOptionalToken(syntaxKind.SemicolonToken); return this.#finishNode(node); } #isIdEqualsIdStatement() { if (!this.#isIdentifier) debugger; this.#nextToken(); return this.#token() === syntaxKind.EqualsToken; } #parseNodeStatement() { if (!this.#isIdentifier) debugger; const node = this.#createNode(syntaxKind.NodeStatement); node.id = this.#parseNodeId(); if (this.#token() === syntaxKind.OpenBracketToken) node.attributes = this.#parseList(parsingContext.AttributeContainerList, () => this.#parseAttributeContainer()); else node.attributes = this.#createEmptyArray(); node.terminator = this.#parseOptionalToken(syntaxKind.SemicolonToken); return this.#finishNode(node); } #parseEdgeStatement(precedingItem) { console.assert(precedingItem.kind === syntaxKind.SubGraph || precedingItem.kind === syntaxKind.NodeId); console.assert(precedingItem.pos !== void 0); if (!this.#isEdgeOp()) debugger; const node = this.#createNode(syntaxKind.EdgeStatement, precedingItem.pos); node.source = precedingItem; node.rhs = this.#parseList(parsingContext.EdgeRhsList, () => this.#parseEdgeRhs()); if (this.#token() === syntaxKind.OpenBracketToken) node.attributes = this.#parseList(parsingContext.AttributeContainerList, () => this.#parseAttributeContainer()); else node.attributes = this.#createEmptyArray(); node.terminator = this.#parseOptionalToken(syntaxKind.SemicolonToken); return this.#finishNode(node); } #parseEdgeRhs() { const node = this.#createNode(syntaxKind.EdgeRhs); node.operation = this.#parseExpectedTokenOneOf(syntaxKind.DirectedEdgeOp, [syntaxKind.DirectedEdgeOp, syntaxKind.UndirectedEdgeOp]); switch (this.#token()) { case syntaxKind.SubgraphKeyword: case syntaxKind.OpenBraceToken: node.target = this.#parseSubGraph(); break; default: node.target = this.#parseNodeId(); break; } return this.#finishNode(node); } #createMissingNode(kind) { const result = this.#createNode(kind); if (isIdentifierNode(result)) switch (result.kind) { case syntaxKind.QuotedTextIdentifier: { const literal = this.#createNode(syntaxKind.StringLiteral); literal.text = ""; result.values = this.#createNodeArray([literal], result.pos, result.pos); break; } case syntaxKind.HtmlIdentifier: result.htmlContent = ""; break; case syntaxKind.TextIdentifier: case syntaxKind.NumericIdentifier: result.text = ""; break; } return this.#finishNode(result); } #parseAssignment() { if (!this.#isIdentifier) debugger; const node = this.#createNode(syntaxKind.Assignment); node.leftId = this.#parseIdentifier(); this.#parseExpectedToken(syntaxKind.EqualsToken); node.rightId = this.#parseIdentifier(); let terminator = this.#parseOptionalToken(syntaxKind.CommaToken); if (terminator === void 0) terminator = this.#parseOptionalToken(syntaxKind.SemicolonToken); node.terminator = terminator; return this.#finishNode(node); } #parseSubGraph() { console.assert(this.#token() === syntaxKind.SubgraphKeyword || this.#token() === syntaxKind.OpenBraceToken); const subGraph = this.#parseOptionalToken(syntaxKind.SubgraphKeyword); const subGraphStart = subGraph !== void 0 ? subGraph.pos : void 0; const node = this.#createNode(syntaxKind.SubGraph, subGraphStart); node.id = subGraph !== void 0 && this.#isIdentifier() ? this.#parseIdentifier() : void 0; this.#parseExpectedToken(syntaxKind.OpenBraceToken); node.statements = this.#parseList(parsingContext.StatementList, () => this.#parseStatement()); this.#parseExpectedToken(syntaxKind.CloseBraceToken); return this.#finishNode(node); } #parseNodeId() { if (!this.#isIdentifier) debugger; const node = this.#createNode(syntaxKind.NodeId); node.id = this.#parseIdentifier(); node.port = this.#token() === syntaxKind.ColonToken ? this.#parsePortDeclaration() : void 0; return this.#finishNode(node); } #parseCompassPortDeclaration() { console.assert(this.#token() === syntaxKind.ColonToken); const node = this.#createNode(syntaxKind.CompassPortDeclaration); node.colon = this.#parseTokenNode(); node.compassPt = this.#parseTokenNode(); return this.#finishNode(node); } #parseNormalPortDeclaration() { console.assert(this.#token() === syntaxKind.ColonToken); const node = this.#createNode(syntaxKind.NormalPortDeclaration); node.colon = this.#parseTokenNode(); node.id = this.#parseIdentifier(); node.compassPt = this.#token() === syntaxKind.ColonToken ? this.#parseCompassPortDeclaration() : void 0; return this.#finishNode(node); } #parsePortDeclaration() { console.assert(this.#token() === syntaxKind.ColonToken); if (this.#lookAhead(() => this.#isCompassPort())) return this.#parseCompassPortDeclaration(); return this.#parseNormalPortDeclaration(); } #isCompassPort() { console.assert(this.#token() === syntaxKind.ColonToken); if (this.#token() !== syntaxKind.ColonToken) return false; this.#nextToken(); return this.#isCompassPortKind(this.#token()); } #parseList(context, parseElement, atLeastOne = false) { const saveParsingContext = this.currentContext; this.currentContext |= 1 << context; let isListTerminated = atLeastOne ? false : this.#isListTerminator(context); const startPos = this.scanner.startPos; const elements = []; while (!isListTerminated) { if (this.#isListElement(context, false)) { const element = parseElement(); elements.push(element); isListTerminated = this.#isListTerminator(context); continue; } if (this.#abortListParsing(context)) break; } this.currentContext = saveParsingContext; return this.#createNodeArray(elements, startPos); } #getContextParseError(context) { switch (context) { case parsingContext.StatementList: return "Assignment, node definition, graph/node/edge attribute or edge definition expected."; case parsingContext.AssignmentList: return "Assignment expected."; case parsingContext.EdgeRhsList: return "Edge operation expected."; case parsingContext.QuotedTextIdentifierConcatenation: return "Quoted identifier expected"; case parsingContext.AttributeContainerList: return "Attribute marker expected."; case parsingContext.None: return "Wat, no parsing context"; case parsingContext.Count: return "Wat, 'Count' parsing context"; default: return assertNever(context); } } #isInSomeParsingContext() { for (let ctx = 0; ctx < parsingContext.Count; ctx++) if (this.currentContext & 1 << ctx) { if (this.#isListElement(ctx, true) || this.#isListTerminator(ctx)) return true; } return false; } #abortListParsing(context) { this.#parseErrorAtCurrentToken(this.#getContextParseError(context), parseError.FailedListParsing); if (this.#isInSomeParsingContext()) return true; this.#nextToken(); return false; } #isListElement(context, _inErrorRecovery) { switch (context) { case parsingContext.AssignmentList: return this.#isIdentifier(); case parsingContext.AttributeContainerList: return this.#token() === syntaxKind.OpenBracketToken; case parsingContext.EdgeRhsList: return this.#token() === syntaxKind.DirectedEdgeOp || this.#token() === syntaxKind.UndirectedEdgeOp; case parsingContext.QuotedTextIdentifierConcatenation: return this.#token() === syntaxKind.StringLiteral || this.#token() === syntaxKind.PlusToken; case parsingContext.StatementList: return this.#isIdentifier() || this.#token() === syntaxKind.SubgraphKeyword || this.#token() === syntaxKind.OpenBraceToken || this.#token() === syntaxKind.GraphKeyword || this.#token() === syntaxKind.EdgeKeyword || this.#token() === syntaxKind.NodeKeyword; default: throw "This should never happen"; } } #isListTerminator(context) { const token = this.#token(); if (token === syntaxKind.EndOfFileToken) return true; switch (context) { case parsingContext.StatementList: return token === syntaxKind.CloseBraceToken; case parsingContext.AttributeContainerList: return token !== syntaxKind.OpenBracketToken; case parsingContext.AssignmentList: return token === syntaxKind.CloseBracketToken; case parsingContext.EdgeRhsList: return token !== syntaxKind.DirectedEdgeOp && token !== syntaxKind.UndirectedEdgeOp; case parsingContext.QuotedTextIdentifierConcatenation: return token !== syntaxKind.PlusToken; default: throw "Unsupported parsing context"; } } #createEmptyArray() { const startPos = this.scanner.startPos; return this.#createNodeArray([], startPos); } #finishNode(node, end) { node.end = end === void 0 ? this.scanner.startPos : end; if (this.currentNodeHasError) { this.currentNodeHasError = false; node.flags |= syntaxNodeFlags.ContainsErrors; } return node; } #createNode(kind, pos) { this.nodeCount++; const p = pos !== void 0 && pos >= 0 ? pos : this.scanner.startPos; if (isNodeKind(kind) || kind === syntaxKind.Unknown) return newNode(kind, p, p); return isIdentifier(kind) ? newIdentifier(kind, p, p) : newToken(kind, p, p); } #createNodeArray(elements, pos, end) { const length = elements.length; const array = length >= 1 && length <= 4 ? elements.slice() : elements; array.pos = pos; array.end = end === void 0 ? this.scanner.startPos : end; return array; } #parseTokenNode() { const node = this.#createNode(this.#token()); this.#nextToken(); return this.#finishNode(node); } #getLastError(diagnostics) { return diagnostics && diagnostics.length > 0 ? diagnostics[diagnostics.length - 1] : void 0; } #parseErrorAtPosition(start, end, message, code) { const ds = this.diagnostics; const lastError = this.#getLastError(ds); if (!lastError || start !== lastError.start) ds.push({ category: diagnosticCategory.Error, start, end, message, code }); this.currentNodeHasError = true; } #parseErrorAtCurrentToken(message, sub) { const error = { source: errorSource.Parse, sub }; return this.#parseErrorAtPosition(this.scanner.tokenPos, this.scanner.pos, message, error); } #scanError(message, _category, sub, length) { const errorPos = this.scanner.pos; const err = { source: errorSource.Scan, sub }; this.#parseErrorAtPosition(errorPos, errorPos + length, message, err); } #reportExpectedError(expectedKinds) { const found = this.#isIdentifier() ? "identifier" : this.#token() === syntaxKind.EndOfFileToken ? "end of file" : `"${getTokenAsText(this.#token())}"`; const expected = expectedKinds.map((k) => { if (isIdentifier(k)) return "identifier"; if (k === syntaxKind.EndOfFileToken) return "end of file"; return `"${getTokenAsText(k)}"`; }); const lastExpected = expected.pop(); const expectedJoined = expected.join(", "); const msg = expected.length > 0 ? `Expected ${expectedJoined} or ${lastExpected} but found ${found} instead.` : `Expected ${lastExpected} but found ${found} instead.`; this.#parseErrorAtCurrentToken(msg, parseError.ExpectationFailed); } #parseExpectedOneOf(...kinds) { if (kinds.length < 2) { console.assert(false); debugger; } for (const kind of kinds) if (this.#token() === kind) { this.#nextToken(); return true; } this.#reportExpectedError(kinds); return false; } #parseExpectedTokenOneOf(fallback, kinds) { if (kinds.length < 2) { console.assert(false); debugger; } for (const kind of kinds) if (this.#token() === kind) { const node = this.#createNode(this.#token()); this.#nextToken(); return this.#finishNode(node); } this.#reportExpectedError(kinds); return this.#createMissingNode(fallback); } #parseExpectedToken(kind) { const tokenNode = this.#parseOptionalToken(kind); if (tokenNode !== void 0) return tokenNode; this.#reportExpectedError([kind]); return this.#createMissingNode(kind); } #parseExpected(kind) { const res = this.#parseOptional(kind); if (!res) this.#reportExpectedError([kind]); return res; } #parseOptionalToken(t) { if (this.#token() === t) return this.#parseTokenNode(); } #parseOptional(t) { if (this.#token() === t) { this.#nextToken(); return true; } return false; } #isEdgeOp() { switch (this.#token()) { case syntaxKind.DirectedEdgeOp: case syntaxKind.UndirectedEdgeOp: return true; default: return false; } } #isIdentifier() { switch (this.#token()) { case syntaxKind.TextIdentifier: case syntaxKind.NumericIdentifier: case syntaxKind.StringLiteral: case syntaxKind.HtmlIdentifier: return true; default: return false; } } #isCompassPortKind(kind) { return kind >= syntaxKind.CompassCenterToken && kind <= syntaxKind.CompassEnd; } #speculationHelper(callback, isLookAhead) { const saveToken = this.#token(); const saveDiagnosticsLength = this.diagnostics.length; const result = isLookAhead ? this.scanner.lookAhead(callback) : this.scanner.tryScan(callback); if (!result || isLookAhead) { this.currentToken = saveToken; this.diagnostics.length = saveDiagnosticsLength; } return result; } /** Invokes the provided callback then unconditionally restores the parser to the state it * was in immediately prior to invoking the callback. The result of invoking the callback * is returned from this function. */ #lookAhead(callback) { return this.#speculationHelper(callback, true); } /** Invokes the provided callback. If the callback returns something falsy, then it restores * the parser to the state it was in immediately prior to invoking the callback. If the * callback returns something truthy, then the parser state is not rolled back. The result * of invoking the callback is returned from this function. */ #tryParse(callback) { return this.#speculationHelper(callback, false); } }; function newNode(kind, pos, end) { return { kind, flags: syntaxNodeFlags.None, end, pos, parent: void 0 }; } const newIdentifier = newNode; const newToken = newNode; function isNodeKind(kind) { return kind >= syntaxKind.FirstNode; } function isIdentifier(kind) { return kind === syntaxKind.HtmlIdentifier || kind === syntaxKind.NumericIdentifier || kind === syntaxKind.TextIdentifier || kind === syntaxKind.QuotedTextIdentifier; } function isIdentifierNode(node) { return isIdentifier(node.kind); } //#endregion //#region src/service/languageFacts.ts const shapes = Object.freeze([ "box", "polygon", "ellipse", "oval", "circle", "point", "egg", "triangle", "plaintext", "plain", "diamond", "trapezium", "parallelogram", "house", "pentagon", "hexagon", "septagon", "octagon", "doublecircle", "doubleoctagon", "tripleoctagon", "invtriangle", "invtrapezium", "invhouse", "Mdiamond", "Msquare", "Mcircle", "record", "rect", "rectangle", "square", "star", "none", "underline", "cylinder", "note", "tab", "folder", "box3d", "component", "promoter", "cds", "terminator", "utr", "primersite", "restrictionsite", "fivepoverhang", "threepoverhang", "noverhang", "assembly", "signature", "insulator", "ribosite", "rnastab", "proteasesite", "proteinstab", "rpromoter", "rarrow", "larrow", "lpromoter" ]); const edgeAttributes = Object.freeze([ "URL", "arrowhead", "arrowsize", "arrowtail", "color", "colorscheme", "comment", "constraint", "decorate", "dir", "edgeURL", "edgehref", "edgetarget", "edgetooltip", "fillcolor", "fontcolor", "fontname", "fontsize", "headURL", "head_lp", "headclip", "headhref", "headlabel", "headport", "headtarget", "headtooltip", "href", "id", "label", "labelURL", "labelangle", "labeldistance", "labelfloat", "labelfontcolor", "labelfontname", "labelfontsize", "labelhref", "labeltarget", "labeltooltip", "layer", "len", "lhead", "lp", "ltail", "minlen", "nojustify", "penwidth", "pos", "samehead", "sametail", "showboxes", "style", "tailURL", "tail_lp", "tailclip", "tailhref", "taillabel", "tailport", "tailtarget", "tailtooltip", "target", "tooltip", "weight", "xlabel", "xlp" ]); const nodeAttributes = Object.freeze([ "URL", "area", "color", "colorscheme", "comment", "distortion", "fillcolor", "fixedsize", "fontcolor", "fontname", "fontsize", "gradientangle", "group", "height", "href", "id", "image", "imagepos", "imagescale", "label", "labelloc", "layer", "margin", "nojustify", "ordering", "orientation", "penwidth", "peripheries", "pin", "pos", "rects", "regular", "root", "samplepoints", "shape", "shapefile", "showboxes", "sides", "skew", "sortv", "style", "target", "tooltip", "vertices", "width", "xlabel", "xlp", "z" ]); const graphAttributes = Object.freeze([ "Damping", "K", "URL", "_background", "bb", "bgcolor", "center", "charset", "clusterrank", "colorscheme", "comment", "compound", "concentrate", "defaultdist", "dim", "dimen", "diredgeconstraints", "dpi", "epsilon", "esep", "fontcolor", "fontname", "fontnames", "fontpath", "fontsize", "forcelabels", "gradientangle", "href", "id", "imagepath", "inputscale", "label", "label_scheme", "labeljust", "labelloc", "landscape", "layerlistsep", "layers", "layerselect", "layersep", "layout", "levels", "levelsgap", "lheight", "lp", "lwidth", "margin", "maxiter", "mclimit", "mindist", "mode", "model", "mosek", "newrank", "nodesep", "nojustify", "normalize", "notranslate", "nslimit", "nslimit1", "ordering", "orientation", "outputorder", "overlap", "overlap_scaling", "overlap_shrink", "pack", "packmode", "pad", "page", "pagedir", "quadtree", "quantum", "rankdir", "ranksep", "ratio", "remincross", "repulsiveforce", "resolution", "root", "rotate", "rotation", "scale", "searchsize", "sep", "showboxes", "size", "smoothing", "sortv", "splines", "start", "style", "stylesheet", "target", "truecolor", "viewport", "voro_margin", "xdotversion" ]); const clusterAttributes = Object.freeze([ "K", "URL", "area", "bgcolor", "color", "colorscheme", "fillcolor", "fontcolor", "fontname", "fontsize", "gradientangle", "href", "id", "label", "labeljust", "labelloc", "layer", "lheight", "lp", "lwidth", "margin", "nojustify", "pencolor", "penwidth", "peripheries", "sortv", "style", "target", "tooltip" ]); const attributes = Array.from(new Set([ ...nodeAttributes, ...edgeAttributes, ...graphAttributes, ...clusterAttributes ])).sort(); /** * We only support the X11 color scheme */ const colors = Object.freeze({ aliceblue: "#f0f8ff", antiquewhite: "#faebd7", antiquewhite1: "#ffefdb", antiquewhite2: "#eedfcc", antiquewhite3: "#cdc0b0", antiquewhite4: "#8b8378", aqua: "#00ffff", aquamarine: "#7fffd4", aquamarine1: "#7fffd4", aquamarine2: "#76eec6", aquamarine3: "#66cdaa", aquamarine4: "#458b74", azure: "#f0ffff", azure1: "#f0ffff", azure2: "#e0eeee", azure3: "#c1cdcd", azure4: "#838b8b", beige: "#f5f5dc", bisque: "#ffe4c4", bisque1: "#ffe4c4", bisque2: "#eed5b7", bisque3: "#cdb79e", bisque4: "#8b7d6b", black: "#000000", blanchedalmond: "#ffebcd", blue: "#0000ff", blue1: "#0000ff", blue2: "#0000ee", blue3: "#0000cd", blue4: "#00008b", blueviolet: "#8a2be2", brown: "#a52a2a", brown1: "#ff4040", brown2: "#ee3b3b", brown3: "#cd3333", brown4: "#8b2323", burlywood: "#deb887", burlywood1: "#ffd39b", burlywood2: "#eec591", burlywood3: "#cdaa7d", burlywood4: "#8b7355", cadetblue: "#5f9ea0", cadetblue1: "#98f5ff", cadetblue2: "#8ee5ee", cadetblue3: "#7ac5cd", cadetblue4: "#53868b", chartreuse: "#7fff00", chartreuse1: "#7fff00", chartreuse2: "#76ee00", chartreuse3: "#66cd00", chartreuse4: "#458b00", chocolate: "#d2691e", chocolate1: "#ff7f24", chocolate2: "#ee7621", chocolate3: "#cd661d", chocolate4: "#8b4513", coral: "#ff7f50", coral1: "#ff7256", coral2: "#ee6a50", coral3: "#cd5b45", coral4: "#8b3e2f", cornflowerblue: "#6495ed", cornsilk: "#fff8dc", cornsilk1: "#fff8dc", cornsilk2: "#eee8cd", cornsilk3: "#cdc8b1", cornsilk4: "#8b8878", crimson: "#dc143c", cyan: "#00ffff", cyan1: "#00ffff", cyan2: "#00eeee", cyan3: "#00cdcd", cyan4: "#008b8b", darkblue: "#00008b", darkcyan: "#008b8b", darkgoldenrod: "#b8860b", darkgoldenrod1: "#ffb90f", darkgoldenrod2: "#eead0e", darkgoldenrod3: "#cd950c", darkgoldenrod4: "#8b6508", darkgray: "#a9a9a9