/* * Copyright (C) 2017 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ CodeMirror.defineMode("regex", function() { function characterSetTokenizer(stream, state) { let context = state.currentContext(); if (context.negatedCharacterSet === undefined) { context.negatedCharacterSet = stream.eat("^"); if (context.negatedCharacterSet) return "regex-character-set-negate"; } while (stream.peek()) { if (stream.eat("\\")) return consumeEscapeSequence(stream); if (stream.eat("]")) { state.tokenize = tokenBase; return state.popContext(); } stream.next(); } return "error"; } function consumeEscapeSequence(stream) { if (stream.match(/[bBdDwWsStrnvf0]/)) return "regex-special"; if (stream.eat("x")) { if (stream.match(/[0-9a-fA-F]{2}/)) return "regex-escape"; return "error"; } if (stream.eat("u")) { if (stream.match(/[0-9a-fA-F]{4}/)) return "regex-escape-2"; return "error"; } if (stream.eat("c")) { if (stream.match(/[A-Z]/)) return "regex-escape-3"; return "error"; } if (stream.match(/[1-9]/)) return "regex-backreference"; if (stream.next()) return "regex-literal"; return "error"; } function tokenBase(stream, state) { let ch = stream.next(); if (ch === "\\") return consumeEscapeSequence(stream); // Match start of capturing/noncapturing group, or positive/negative lookaheads. if (ch === "(") { let style; if (stream.match(/\?[=!]/)) style = "regex-lookahead"; else { stream.match(/\?:/); style = "regex-group"; } return state.pushContext(stream, style, ")"); } let context = state.currentContext(); if (context && context.type === ch) return state.popContext(); // Match quantifiers: *, +, ?, {n}, {n,}, and {n,m} if (/[*+?]/.test(ch)) return "regex-quantifier"; if (ch === "{") { if (stream.match(/\d+}/)) return "regex-quantifier"; let matches = stream.match(/(\d+),(\d+)?}/); if (!matches) return "error"; let minimum = parseInt(matches[1]); let maximum = parseInt(matches[2]); if (minimum > maximum) return "error"; return "regex-quantifier"; } // Match character sets. if (ch === "[") { state.tokenize = characterSetTokenizer; return state.pushContext(stream, "regex-character-set"); } // Match miscelleneous special characters. if (/[.^$|]/.test(ch)) return "regex-special"; return null; } return { startState: function() { let contextStack = []; return { currentContext: function() { return contextStack.length ? contextStack[contextStack.length - 1] : null; }, pushContext: function(stream, data, type) { let context = {data, type}; contextStack.push(context); return data; }, popContext: function() { console.assert(contextStack.length, "Tried to pop empty context stack."); return contextStack.pop().data; }, tokenize: tokenBase, }; }, token: function(stream, state) { return state.tokenize(stream, state); } }; }); CodeMirror.defineMIME("text/x-regex", "regex");