in gui/frontend/src/parsing/SQLite/SQLiteParsingServices.ts [248:510]
public determineStatementRanges(sql: string, delimiter = ";"): IStatementSpan[] {
const result: IStatementSpan[] = [];
let start = 0;
let head = start;
let tail = head;
const end = head + sql.length;
let haveContent = false; // Set when anything else but comments were found for the current statement.
while (tail < end) {
switch (sql[tail]) {
case "/": { // Possible multi line comment or hidden (conditional) command.
if (sql[tail + 1] === "*") {
if (sql[tail + 2] === "!") { // Hidden command.
if (!haveContent) {
haveContent = true;
head = tail;
}
++tail;
}
tail += 2;
while (true) {
while (tail < end && sql[tail] !== "*") {
++tail;
}
if (tail === end) { // Unfinished multiline comment.
result.push({
delimiter,
span: { start, length: tail - start },
contentStart: haveContent ? head : start - 1,
state: StatementFinishState.OpenComment,
});
start = tail;
head = tail;
break;
} else {
if (sql[++tail] === "/") {
++tail; // Skip the slash too.
break;
}
}
}
if (!haveContent) {
head = tail; // Skip over the comment.
}
} else {
++tail;
}
break;
}
case "-": { // Possible single line comment.
const temp = tail + 2;
if (sql[tail + 1] === "-" && (sql[temp] === " " || sql[temp] === "\t" || sql[temp] === "\n")) {
// Skip everything until the end of the line.
tail += 2;
while (tail < end && sql[tail] !== "\n") {
++tail;
}
if (tail === end) { // Unfinished single line comment.
result.push({
delimiter,
span: { start, length: tail - start },
contentStart: haveContent ? head : start - 1,
state: StatementFinishState.OpenComment,
});
start = tail;
head = tail;
break;
}
if (!haveContent) {
head = tail;
}
} else {
++tail;
}
break;
}
case "#": { // MySQL single line comment.
while (tail < end && sql[tail] !== "\n") {
++tail;
}
if (tail === end) { // Unfinished single line comment.
result.push({
delimiter,
span: { start, length: tail - start },
contentStart: haveContent ? head : start - 1,
state: StatementFinishState.OpenComment,
});
start = tail;
head = tail;
break;
}
if (!haveContent) {
head = tail;
}
break;
}
case '"':
case "'":
case "`": { // Quoted string/id. Skip this in a local loop.
haveContent = true;
const quote = sql[tail++];
while (tail < end && sql[tail] !== quote) {
// Skip any escaped character too.
if (sql[tail] === "\\") {
++tail;
}
++tail;
}
if (sql[tail] === quote) {
++tail; // Skip trailing quote char if one was there.
} else { // Unfinished single string.
result.push({
delimiter,
span: { start, length: tail - start },
contentStart: haveContent ? head : start - 1,
state: StatementFinishState.OpenString,
});
start = tail;
head = tail;
}
break;
}
case "d":
case "D": {
// Possible start of the DELIMITER word.
if (!haveContent) {
haveContent = true;
head = tail;
}
haveContent = true;
if (tail + 9 >= end) {
++tail;
break; // Not enough input for that.
}
const candidate = sql.substring(tail, tail + 9);
if (candidate.match(SQLiteParsingServices.delimiterKeyword)) {
// Delimiter keyword found - get the new delimiter (everything until the end of the line).
// But first push anything we found so far and haven't pushed yet.
if (tail > start) {
result.push({
delimiter,
span: { start, length: tail - start },
contentStart: head,
state: StatementFinishState.NoDelimiter,
});
}
tail += 10; // Length of "delimiter" and a space char.
let run = tail;
while (run < end && sql[run] !== "\n") {
++run;
}
delimiter = sql.substring(tail, run).trimRight(); // Remove trailing whitespaces.
result.push({
delimiter,
span: { start, length: run - start },
contentStart: haveContent ? head : start - 1,
state: StatementFinishState.DelimiterChange,
});
tail = run;
head = tail;
start = head;
haveContent = false;
} else {
++tail;
}
break;
}
default:
if (sql[tail] === delimiter[0]) {
// Found possible start of the delimiter. Check if it really is.
if (delimiter.length === 1) {
// Most common case.
++tail;
result.push({
delimiter,
span: { start, length: tail - start },
contentStart: haveContent ? head : start - 1,
state: StatementFinishState.Complete,
});
head = tail;
start = head;
haveContent = false;
} else {
// Multi character delimiter?
const candidate = sql.substring(tail, tail + delimiter.length);
if (candidate === delimiter) {
// Multi char delimiter is complete. Tail still points to the start of the delimiter.
tail += delimiter.length;
result.push({
delimiter,
span: { start, length: tail - start },
contentStart: haveContent ? head : start - 1,
state: StatementFinishState.Complete,
});
head = tail;
start = head;
haveContent = false;
} else {
// Not a delimiter.
++tail;
if (!haveContent) {
haveContent = true;
head = tail;
}
}
}
} else {
if (!haveContent && sql[tail] > " ") {
haveContent = true;
head = tail;
}
++tail;
}
break;
}
}
// Add remaining text to the range list.
if (head < end) {
result.push({
delimiter: "",
span: { start, length: end - start },
contentStart: haveContent ? head : start - 1, // -1 to indicate no content
state: StatementFinishState.NoDelimiter,
});
}
return result;
}