Class: AnbtSql::Parser

Inherits:
Object
  • Object
show all
Includes:
StringUtil
Defined in:
lib/anbt-sql-formatter/parser.rb

Instance Method Summary collapse

Methods included from StringUtil

#char_at, #equals_ignore_case

Constructor Details

#initialize(rule) ⇒ Parser

Returns a new instance of Parser.



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/anbt-sql-formatter/parser.rb', line 13

def initialize(rule)
  @rule = rule

  # 解析前の文字列
  @before = nil

  # 解析中の位置
  @pos = nil

  # 解析中の文字。
  @char = nil

  @token_pos = nil

  # 2文字からなる記号。
  # なお、|| は文字列結合にあたります。
  @two_character_symbol = [ "<>", "<=", ">=", "||", "!=" ]
end

Instance Method Details

#concat_multiwords_keyword(tokens) ⇒ Object

2つ以上並んだキーワードは1つのキーワードとみなします。

   ["a", " ", "group", " ", "by", " ", "b"]
=> ["a", " ", "group by",         " ", "b"]


245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
# File 'lib/anbt-sql-formatter/parser.rb', line 245

def concat_multiwords_keyword(tokens)
  temp_kw_list = @rule.kw_multi_words.map{|kw| kw.split(" ") }

  # ワード数が多い順から
  temp_kw_list.sort{ |a, b|
    b.size <=> a.size
  }.each{|kw|
    index = 0
    target_tokens_size = kw.size * 2 - 1

    while index <= tokens.size - target_tokens_size
      temp_tokens = tokens[index, target_tokens_size].map {|x|
        x.string.sub(/\s+/, " ")
      }

      if equals_ignore_case(kw.join(" "), temp_tokens.join)
        tokens[index].string = temp_tokens.join
        (target_tokens_size-1).downto(1).each{|c|
          tokens.delete_at(index + c)
        }
      end

      index += 1
    end
  }
end

#digit?(c) ⇒ Boolean

Returns:

  • (Boolean)


57
58
59
# File 'lib/anbt-sql-formatter/parser.rb', line 57

def digit?(c)
  return "0" <= c && c <= '9'
end

#letter?(c) ⇒ Boolean

文字として認識して妥当かどうかを判定します。全角文字なども文字として認識を許容するものと判断します。

Returns:

  • (Boolean)


48
49
50
51
52
53
54
# File 'lib/anbt-sql-formatter/parser.rb', line 48

def letter?(c)
  return false if space?(c)
  return false if digit?(c)
  return false if symbol?(c)

  true
end

#next_sql_tokenObject

トークンを次に進めます。

  1. posを進める。

  2. sに結果を返す。

  3. typeにその種類を設定する。

不正なSQLの場合、例外が発生します。ここでは、文法チェックは行っていない点に注目してください。



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/anbt-sql-formatter/parser.rb', line 79

def next_sql_token
  $stderr.puts "next_token #{@pos} <#{@before}> #{@before.length}" if $DEBUG

  start_pos = @pos

  if @pos >= @before.length
    @pos += 1
    return nil
  end

  @char = char_at(@before, @pos)

  if space?(@char)
    work_string = ""
    loop {
      work_string += @char

      is_next_char_space = false
      if @pos + 1 < @before.size &&
        space?(char_at(@before, @pos+1))
        is_next_char_space = true
      end

      if not is_next_char_space
        @pos += 1
        return AnbtSql::Token.new(AnbtSql::TokenConstants::SPACE,
                                  work_string, start_pos)
      else
        @pos += 1
        next
      end
    }


  elsif @char == ";"
    @pos += 1
    # 2005.07.26 Tosiki Iga セミコロンは終了扱いではないようにする。
    return AnbtSql::Token.new(AnbtSql::TokenConstants::SYMBOL,
                                ";", start_pos)

  elsif digit?(@char)
    if /^(0x[0-9a-fA-F]+)/       =~ @before[@pos..-1] || # hex
       /^(\d+(\.\d+(e-?\d+)?)?)/ =~ @before[@pos..-1]    # integer, float or scientific
      num = $1
      @pos += num.length
      return AnbtSql::Token.new(AnbtSql::TokenConstants::VALUE,
                                num, start_pos)
    else
      raise "must not happen"
    end

  elsif letter?(@char)
    s = ""
    # 文字列中のドットについては、文字列と一体として考える。
    while (letter?(@char) || digit?(@char) || @char == '.')
      s += @char
      @pos += 1
      if (@pos >= @before.length())
        break
      end

      @char = char_at(@before, @pos)
    end

    if AnbtSql::Constants::SQL_RESERVED_WORDS.map{|w| w.upcase }.include?(s.upcase)
      return AnbtSql::Token.new(AnbtSql::TokenConstants::KEYWORD,
                                  s, start_pos)
    end

    return AnbtSql::Token.new(AnbtSql::TokenConstants::NAME,
                                s, start_pos)

  elsif symbol?(@char)
    s = "" + @char
    @pos += 1
    if (@pos >= @before.length())
      return AnbtSql::Token.new(AnbtSql::TokenConstants::SYMBOL,
                                s, start_pos)
    end

    # 2文字の記号かどうか調べる
    ch2 = char_at(@before, @pos)
    # for (int i = 0; i < two_character_symbol.length; i++) {
    for i in 0...@two_character_symbol.length
      if (char_at(@two_character_symbol[i], 0) == @char &&
          char_at(@two_character_symbol[i], 1) == ch2)
        @pos += 1
        s += ch2
        break
      end
    end

    if @char == "-" &&
      /^(\d+(\.\d+(e-?\d+)?)?)/ =~ @before[@pos..-1] # float or scientific
      num = $1
      @pos += num.length
      return AnbtSql::Token.new(AnbtSql::TokenConstants::VALUE,
                                s + num, start_pos)
    end

    return AnbtSql::Token.new(AnbtSql::TokenConstants::SYMBOL,
                                s, start_pos)


  else
    @pos += 1
    return AnbtSql::Token.new( AnbtSql::TokenConstants::UNKNOWN,
                                 "" + @char,
                                 start_pos )
  end
end

#next_tokenObject



273
274
275
# File 'lib/anbt-sql-formatter/parser.rb', line 273

def next_token
  @tokens[@token_pos]
end

#parse(sql_str) ⇒ Object

SQL文字列をトークンの配列に変換し返します。

sql_str

変換前のSQL文



282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
# File 'lib/anbt-sql-formatter/parser.rb', line 282

def parse(sql_str)
  coarse_tokens = CoarseTokenizer.new.tokenize(sql_str)

  prepare_tokens(coarse_tokens)

  tokens = []
  count = 0
  @token_pos = 0
  loop {
    token = next_token()

    if $DEBUG
      pp "=" * 64, count, token, token.class
    end

    if token._type == AnbtSql::TokenConstants::END_OF_SQL
      break
    else
      ;
    end

    tokens.push token
    count += 1
    @token_pos += 1
  }

  concat_multiwords_keyword(tokens)

  tokens
end

#prepare_tokens(coarse_tokens) ⇒ Object



192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/anbt-sql-formatter/parser.rb', line 192

def prepare_tokens(coarse_tokens)
  @tokens = []

  pos = 0
  while pos < coarse_tokens.size
    coarse_token = coarse_tokens[pos]

    case coarse_token._type

    when :quote_single
      @tokens << AnbtSql::Token.new(AnbtSql::TokenConstants::VALUE,
                                      coarse_token.string)
    when :quote_double
      @tokens << AnbtSql::Token.new(AnbtSql::TokenConstants::NAME,
                                      coarse_token.string)
    when :comment_single
      @tokens << AnbtSql::Token.new(AnbtSql::TokenConstants::COMMENT,
                                      coarse_token.string.chomp)
    when :comment_multi
      @tokens << AnbtSql::Token.new(AnbtSql::TokenConstants::COMMENT,
                                      coarse_token.string)
    when :plain
      @before = coarse_token.string
      @pos = 0
      count = 0
      loop {
        token = next_sql_token()
        if $DEBUG
          pp "@" * 64, count, token, token.class
        end

        # if token._type == AnbtSql::TokenConstants::END_OF_SQL
        if token == nil
          break
        end

        @tokens.push token
        count += 1
      }
    end

    pos += 1
  end

  @tokens << AnbtSql::Token.new(AnbtSql::TokenConstants::END_OF_SQL,
                                  "")
end

#space?(c) ⇒ Boolean

2005.07.26

Tosiki Iga r も処理範囲に含める必要があります。

2005.08.12

Tosiki Iga 65535(もとは-1)はホワイトスペースとして扱うよう変更します。

Returns:

  • (Boolean)


36
37
38
39
40
41
42
# File 'lib/anbt-sql-formatter/parser.rb', line 36

def space?(c)
  return c == ' ' ||
    c == "\t" ||
    c == "\n" ||
    c == "\r" ||
    c == 65535
end

#symbol?(c) ⇒ Boolean

“#” は文字列の一部としますアンダースコアは記号とは扱いませんこれ以降の文字の扱いは保留

Returns:

  • (Boolean)


66
67
68
69
# File 'lib/anbt-sql-formatter/parser.rb', line 66

def symbol?(c)
  %w(" ? % & ' \( \) | * + , - . / : ; < = > !).include? c
  # "
end