164 lines
5.2 KiB
JavaScript
164 lines
5.2 KiB
JavaScript
/*
|
|
* 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");
|