SqlParser.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SQLFormatter
{
    class SqlParser
    {
        /**
         * 解析前の文字列
         */
        private String fBefore;

        /**
         * 解析中の文字。
         */
        private char fChar;

        /**
         * 解析中の位置
         */
        private int fPos;

        /**
         * 2文字からなる記号。
         * 
         * なお、|| は文字列結合にあたります。
         */
        private static String[] twoCharacterSymbol = { "<>", "<=", ">=", "||" };

        /**
         * パーサのインスタンスを作成します。
         */
        public SqlParser() {
        }

        /**
         * 与えられた文字が、ホワイトスペース文字かどうかを判定します。
         * 
         * @param argChar
         * @return
         */
        public static bool isSpace(char argChar) {
            // 2005.07.26 Tosiki Iga \r も処理範囲に含める必要があります。
            // 2005.08.12 Tosiki Iga 65535(もとは-1)はホワイトスペースとして扱うよう変更します。
            return argChar == ' ' || argChar == '\t' || argChar == '\n'
                    || argChar == '\r' || argChar == 65535;
        }

        /**
         * 文字として認識して妥当かどうかを判定します。
         * 
         * 全角文字なども文字として認識を許容するものと判断します<br>
         * ※このメソッドはBlancoSqlEditorPluginから参照されます。
         * 
         * @param argChar
         * @return
         */
        public static bool isLetter(char argChar) {
            // SQLにおいて アンダースコアは英字の仲間です.
            // blanco において # は英字の仲間です.
            // ここに日本語も含めなくてはならない。
            // return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z')
            // || (c == '_' || c == '#');
            if (isSpace(argChar)) {
                return false;
            }
            if (isDigit(argChar)) {
                return false;
            }
            if (isSymbol(argChar)) {
                return false;
            }
            return true;
        }

        /**
         * 数字かどうかを判定します。
         * 
         * @param argChar
         * @return
         */
        public static bool isDigit(char argChar) {
            return '0' <= argChar && argChar <= '9';
        }

        /**
         * 記号かどうかを判定します。
         * 
         * @param argChar
         * @return
         */
        public static bool isSymbol(char argChar) {
            switch (argChar) {
            case '"': // double quote
            case '?': // question mark
            case '%': // percent
            case '&': // ampersand
            case '\'': // quote
            case '(': // left paren
            case ')': // right paren
            case '|': // vertical bar
            case '*': // asterisk
            case '+': // plus sign
            case ',': // comma
            case '-': // minus sign
            case '.': // period
            case '/': // solidus
            case ':': // colon
            case ';': // semicolon
            case '<': // less than operator
            case '=': // equals operator
            case '>': // greater than operator

                // blancoでは # は文字列の一部です case '#':
                // アンダースコアは記号とは扱いません case '_': //underscore
                // これ以降の文字の扱いは保留
                // case '!':
                // case '$':
                // case '[':
                // case '\\':
                // case ']':
                // case '^':
                // case '{':
                // case '}':
                // case '~':
                return true;
            default:
                return false;
            }
        }

        /**
         * トークンを次に進めます。
         * 
         * posを進める。sに結果を返す。typeにその種類を設定する。
         * 
         * 不正なSQLの場合、例外が発生します。 ここでは、文法チェックは行っていない点に注目してください。
         * 
         * @return トークンを返す.
         */
        SqlToken nextToken() {
            int start_pos = fPos;
            if (fPos >= fBefore.Length) {
                fPos++;
                return new SqlToken(SqlTokenConstants.END, "",
                        start_pos);
            }

            fChar = fBefore[fPos];

            if (isSpace(fChar)) {
                String workString = "";
                for (;;) {
                    workString += fChar;
                    fChar = fBefore[fPos];
                    if (!isSpace(fChar)) {
                        return new SqlToken(SqlTokenConstants.SPACE,
                                workString, start_pos);
                    }
                    fPos++;
                    if (fPos >= fBefore.Length) {
                        return new SqlToken(SqlTokenConstants.SPACE,
                                workString, start_pos);
                    }
                }
            } else if (fChar == ';') {
                fPos++;
                // 2005.07.26 Tosiki Iga セミコロンは終了扱いではないようにする。
                return new SqlToken(SqlTokenConstants.SYMBOL, ";",
                        start_pos);
            } else if (isDigit(fChar)) {
                String s = "";
                while (isDigit(fChar) || fChar == '.') {
                    // if (ch == '.') type = Token.REAL;
                    s += fChar;
                    fPos++;

                    if (fPos >= fBefore.Length) {
                        // 長さを超えている場合には処理中断します。
                        break;
                    }

                    fChar = fBefore[fPos];
                }
                return new SqlToken(SqlTokenConstants.VALUE, s,
                        start_pos);
            } else if (isLetter(fChar)) {
                String s = "";
                // 文字列中のドットについては、文字列と一体として考える。
                while (isLetter(fChar) || isDigit(fChar) || fChar == '.') {
                    s += fChar;
                    fPos++;
                    if (fPos >= fBefore.Length) {
                        break;
                    }

                    fChar = fBefore[fPos];
                }
                for (int i = 0; i < SqlConstants.SQL_RESERVED_WORDS.Length; i++) {
                    if(string.Compare(s, SqlConstants.SQL_RESERVED_WORDS[i], true) == 0){
                        return new SqlToken(SqlTokenConstants.KEYWORD, s, start_pos);
                    }
                }
                return new SqlToken(SqlTokenConstants.NAME, s, start_pos);
            }
            // single line comment
            else if (fChar == '-') {
                fPos++;
                char ch2 = fBefore[fPos];
                // -- じゃなかったとき
                if (ch2 != '-') {
                    return new SqlToken(SqlTokenConstants.SYMBOL, "-", start_pos);
                }
                fPos++;
                String s = "--";
                for (;;) {
                    fChar = fBefore[fPos];
                    s += fChar;
                    fPos++;
                    if (fChar == '\n' || fPos >= fBefore.Length) {
                        return new SqlToken(SqlTokenConstants.COMMENT, s, start_pos);
                    }
                }
            }
            // マルチラインコメント
            else if (fChar == '/') {
                fPos++;
                char ch2 = fBefore[fPos];
                // /* じゃなかったとき
                if (ch2 != '*') {
                    return new SqlToken(SqlTokenConstants.SYMBOL, "/",
                            start_pos);
                }

                String s = "/*";
                fPos++;
                int ch0 = -1;
                for (;;) {
                    ch0 = fChar;
                    fChar = fBefore[fPos];
                    s += fChar;
                    fPos++;
                    if (ch0 == '*' && fChar == '/') {
                        return new SqlToken(SqlTokenConstants.COMMENT,
                                s, start_pos);
                    }
                }
            } else if (fChar == '\'') {
                fPos++;
                String s = "'";
                for (;;) {
                    fChar = fBefore[fPos];
                    s += fChar;
                    fPos++;
                    if (fChar == '\'') {
                        return new SqlToken(SqlTokenConstants.VALUE, s,
                                start_pos);
                    }
                }
            } else if (fChar == '\"') {
                fPos++;
                String s = "\"";
                for (;;) {
                    fChar = fBefore[fPos];
                    s += fChar;
                    fPos++;
                    if (fChar == '\"') {
                        return new SqlToken(SqlTokenConstants.NAME, s,
                                start_pos);
                    }
                }
            }

            else if (isSymbol(fChar)) {
                // 記号
                String s = "" + fChar;
                fPos++;
                if (fPos >= fBefore.Length) {
                    return new SqlToken(SqlTokenConstants.SYMBOL, s,
                            start_pos);
                }
                // 2文字の記号かどうか調べる
                char ch2 = fBefore[fPos];
                for (int i = 0; i < twoCharacterSymbol.Length; i++) {
                    if (twoCharacterSymbol[i][0] == fChar
                            && twoCharacterSymbol[i][1] == ch2) {
                        fPos++;
                        s += ch2;
                        break;
                    }
                }
                return new SqlToken(SqlTokenConstants.SYMBOL, s,
                        start_pos);
            } else {
                fPos++;
                return new SqlToken(SqlTokenConstants.UNKNOWN, ""
                        + fChar, start_pos);
            }
        }

        /**
         * SQL文字列をトークンの配列に変換します。
         * 
         * @param argSql
         *            変換前のSQL文
         * @return Tokenの配列
         */
        public List<SqlToken> parse(String argSql)
        {
            fPos = 0;
            fBefore = argSql;

            List<SqlToken> list = new List<SqlToken>();
            for (; ; )
            {
                SqlToken token = nextToken();
                if (token.getType() == SqlTokenConstants.END)
                {
                    break;
                }

                list.Add(token);
            }
            return list;
        }
        //public ArrayList parse(String argSql) {
        //    fPos = 0;
        //    fBefore = argSql;

        //    ArrayList list = new ArrayList();
        //    for (;;) {
        //        SqlToken token = nextToken();
        //        if (token.getType() == SqlTokenConstants.END) {
        //            break;
        //        }

        //        list.Add(token);
        //    }
        //    return list;
        //}
    }
}