import { getCodeEditorModeConfig } from "./utils"; /** * Source: https://raw.githubusercontent.com/codemirror/CodeMirror/master/mode/clike/clike.js * @author arijn Haverbeke and others * @author Lucas de Souza * @param {CodeMirror} CodeMirror */ export function CodeEditorMode (CodeMirror) { "use strict"; function Context(indented, column, type, info, align, prev) { this.indented = indented; this.column = column; this.type = type; this.info = info; this.align = align; this.prev = prev; } function pushContext(state, col, type, info) { let indent = state.indented; if (state.context && state.context.type == "statement" && type != "statement") indent = state.context.indented; return state.context = new Context(indent, col, type, info, null, state.context); } function popContext(state) { const t = state.context.type; if (t == ")" || t == "]" || t == "}") state.indented = state.context.indented; return state.context = state.context.prev; } function typeBefore(stream, state, pos) { if (state.prevToken == "variable" || state.prevToken == "type") return true; if (/\S(?:[^- ]>|[*\]])\s*$|\*$/.test(stream.string.slice(0, pos))) return true; if (state.typeAtEndOfLine && stream.column() == stream.indentation()) return true; } function isTopScope(context) { for (; ;) { if (!context || context.type == "top") return true; if (context.type == "}" && context.prev.info != "namespace") return false; context = context.prev; } } function contains(words, word) { if (typeof words === "function") { return words(word); } else { return Object.propertyIsEnumerable.call(words, word); } } function tokenComment(stream, state) { let maybeEnd = false, ch; while ((ch = stream.next())) { if (ch == "/" && maybeEnd) { state.tokenize = null; break; } maybeEnd = (ch == "*"); } return "comment"; } CodeMirror.defineMode("ivprog", function (config, parserConfig) { const indentUnit = config.indentUnit, statementIndentUnit = parserConfig.statementIndentUnit || indentUnit, dontAlignCalls = parserConfig.dontAlignCalls, keywords = parserConfig.keywords || {}, switchKeyword = parserConfig.switchKeyword, caseKeyword = parserConfig.caseKeyword, defaultKeyword = parserConfig.defaultKeyword, caseRegex = new RegExp(`^\\s*(?:${caseKeyword} .*?:|${defaultKeyword}:|\\{\\}?|\\})$`),////, types = parserConfig.types || {}, builtin = parserConfig.builtin || {}, blockKeywords = parserConfig.blockKeywords || {}, defKeywords = parserConfig.defKeywords || {}, atoms = parserConfig.atoms || {}, hooks = parserConfig.hooks || {}, multiLineStrings = parserConfig.multiLineStrings, indentStatements = false, namespaceSeparator = null, isPunctuationChar = /[[\]{}(),;:\n]/, numberStart = /[\d.]/, number = /^(?:0x[a-f\d]+|0b[01]+|(?:\d+\.?\d*|\.\d+)(?:e[-+]?\d+)?)$/i, isOperatorChar = /[+\-*%=<>!/&]/, isIdentifierChar = /[a-zA-Z0-9_]/, // An optional function that takes a {string} token and returns true if it // should be treated as a builtin. isReservedIdentifier = parserConfig.isReservedIdentifier || false; let curPunc, isDefKeyword; let tokenString = function () { /*SKIP*/}; function tokenBase(stream, state) { const ch = stream.next(); if (hooks[ch]) { const result = hooks[ch](stream, state); if (result !== false) return result; } if (ch == '"') { state.tokenize = tokenString(ch); return state.tokenize(stream, state); } if (isPunctuationChar.test(ch)) { curPunc = ch; return null; } if (numberStart.test(ch)) { stream.backUp(1) if (stream.match(number)) return "number" stream.next() } if (ch == "/") { if (stream.eat("*")) { state.tokenize = tokenComment; return tokenComment(stream, state); } if (stream.eat("/")) { stream.skipToEnd(); return "comment"; } } if (isOperatorChar.test(ch)) { while (!stream.match(/^\/[/*]/, false) && stream.eat(isOperatorChar)) { /* SKIP */} return "operator"; } stream.eatWhile(isIdentifierChar); if (namespaceSeparator) while (stream.match(namespaceSeparator)) stream.eatWhile(isIdentifierChar); const cur = stream.current(); if (contains(keywords, cur)) { if (contains(blockKeywords, cur)) curPunc = "newstatement"; if (contains(defKeywords, cur)) isDefKeyword = true; return "keyword"; } if (contains(types, cur)) return "type"; if (contains(builtin, cur) || (isReservedIdentifier && isReservedIdentifier(cur))) { if (contains(blockKeywords, cur)) curPunc = "newstatement"; return "builtin"; } if (contains(atoms, cur)) return "atom"; return "variable"; } tokenString = function (quote) { return function (stream, state) { let escaped = false, next, end = false; while ((next = stream.next()) != null) { if (next == quote && !escaped) { end = true; break; } escaped = !escaped && next == "\\"; } if (end || !(escaped || multiLineStrings)) state.tokenize = null; return "string"; }; } function maybeEOL(stream, state) { if (parserConfig.typeFirstDefinitions && stream.eol() && isTopScope(state.context)) state.typeAtEndOfLine = typeBefore(stream, state, stream.pos) } // Interface return { startState: function (basecolumn) { return { tokenize: null, context: new Context((basecolumn || 0) - indentUnit, 0, "top", null, false), indented: 0, startOfLine: true, prevToken: null }; }, token: function (stream, state) { let ctx = state.context; if (stream.sol()) { if (ctx.align == null) ctx.align = false; state.indented = stream.indentation(); state.startOfLine = true; } if (stream.eatSpace()) { maybeEOL(stream, state); return null; } curPunc = isDefKeyword = null; let style = (state.tokenize || tokenBase)(stream, state); if (style == "comment" || style == "meta") return style; if (ctx.align == null) ctx.align = true; if (curPunc == ";" || curPunc == ":" || (curPunc == "," && stream.match(/^\s*(?:\/\/.*)?$/, false))) while (state.context.type == "statement") popContext(state); else if (curPunc == "{") pushContext(state, stream.column(), "}"); else if (curPunc == "[") pushContext(state, stream.column(), "]"); else if (curPunc == "(") pushContext(state, stream.column(), ")"); else if (curPunc == "}") { while (ctx.type == "statement") ctx = popContext(state); if (ctx.type == "}") ctx = popContext(state); while (ctx.type == "statement") ctx = popContext(state); } else if (curPunc == ctx.type) popContext(state); else if (indentStatements && (((ctx.type == "}" || ctx.type == "top") && curPunc != ";") || (ctx.type == "statement" && curPunc == "newstatement"))) { pushContext(state, stream.column(), "statement", stream.current()); } if (style == "variable" && ((state.prevToken == "def" || (parserConfig.typeFirstDefinitions && typeBefore(stream, state, stream.start) && isTopScope(state.context) && stream.match(/^\s*\(/, false))))) style = "def"; if (hooks.token) { const result = hooks.token(stream, state, style); if (result !== undefined) style = result; } if (style == "def" && parserConfig.styleDefs === false) style = "variable"; state.startOfLine = false; state.prevToken = isDefKeyword ? "def" : style || curPunc; maybeEOL(stream, state); return style; }, indent: function (state, textAfter) { if (state.tokenize != tokenBase && state.tokenize != null || state.typeAtEndOfLine) return CodeMirror.Pass; let ctx = state.context; const firstChar = textAfter && textAfter.charAt(0) const closing = firstChar == ctx.type; if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev; if (parserConfig.dontIndentStatements) while (ctx.type == "statement" && parserConfig.dontIndentStatements.test(ctx.info)) ctx = ctx.prev if (hooks.indent) { const hook = hooks.indent(state, ctx, textAfter, indentUnit); if (typeof hook == "number") return hook } const switchBlock = ctx.prev && ctx.prev.info == switchKeyword; if (parserConfig.allmanIndentation && /[{(]/.test(firstChar)) { while (ctx.type != "top" && ctx.type != "}") ctx = ctx.prev return ctx.indented } if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : statementIndentUnit); if (ctx.align && (!dontAlignCalls || ctx.type != ")")) return ctx.column + (closing ? 0 : 1); if (ctx.type == ")" && !closing) return ctx.indented + statementIndentUnit; const caseTestRegex = new RegExp(`^(?:${caseKeyword}|${defaultKeyword})\b`) return ctx.indented + (closing ? 0 : indentUnit) + (!closing && switchBlock && !caseTestRegex.test(textAfter) ? indentUnit : 0); }, electricInput: caseRegex, blockCommentStart: "/*", blockCommentEnd: "*/", blockCommentContinue: " * ", lineComment: "//", fold: "brace" }; }); function words(str) { const obj = {}, words = str.split(" "); for (let i = 0; i < words.length; ++i) obj[words[i]] = true; return obj; } const codeConfig = getCodeEditorModeConfig(); const ivprogKeywords = codeConfig.keywords.join(" "); const basicTypes = words(codeConfig.types.join(" ")); function ivprogTypes(identifier) { return contains(basicTypes, identifier); } const ivprogBlockKeywords = codeConfig.blocks.join(" "); const ivprogDefKeywords = "funcao const"; function def(mimes, mode) { if (typeof mimes == "string") mimes = [mimes]; const words = []; function add(obj) { if (obj) { for (const prop in obj) if (Object.hasOwnProperty.call(obj, prop)) { words.push(prop); } } } add(mode.keywords); add(mode.types); add(mode.builtin); add(mode.atoms); if (words.length) { mode.helperType = mimes[0]; CodeMirror.registerHelper("hintWords", mimes[0], words); } for (let i = 0; i < mimes.length; ++i) CodeMirror.defineMIME(mimes[i], mode); } def(["text/x-ivprog"], { name: "ivprog", keywords: words(ivprogKeywords), types: ivprogTypes, blockKeywords: words(ivprogBlockKeywords), defKeywords: words(ivprogDefKeywords), typeFirstDefinitions: true, atoms: words(codeConfig.atoms.join(" ")), switchKeyword: codeConfig.switchString, caseKeyword: codeConfig.case_default[0], defaultKeyword: codeConfig.case_default[1], modeProps: { fold: ["brace"] } }); }