public determineStatementRanges()

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;
    }