anbt-sql-formatter 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,174 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require "pp"
4
+
5
+ =begin
6
+ エスケープ文字
7
+ =end
8
+
9
+ class CoarseToken
10
+ attr_accessor :_type, :string
11
+
12
+ def initialize(type, str)
13
+ @_type = type
14
+ @string = str
15
+ end
16
+
17
+ def to_s
18
+ %Q!<#{@_type}>%s</>! % [@string.gsub("\n", "<br>")]
19
+ end
20
+ end
21
+
22
+
23
+ class CoarseTokenizer
24
+ def initialize
25
+ @comment_single_start = /--/
26
+ @comment_multi_start = /\/\*/
27
+ @comment_multi_end = /\*\//
28
+ end
29
+
30
+ =begin rdoc
31
+ These are exclusive:
32
+ * double quote string
33
+ * single quote string
34
+ * single line comment
35
+ * multiple line comment
36
+
37
+ ソース先頭から見ていって先に現れたものが優先される。
38
+
39
+ @result <= @buf <= @str
40
+ =end
41
+
42
+ def tokenize(str)
43
+ @str = str
44
+ @str.gsub!("\r\n", "\n")
45
+ out_of_quote_single = true
46
+ out_of_quote_double = true
47
+ out_of_comment_single = true
48
+ out_of_comment_multi = true
49
+
50
+ @result = []
51
+ @buf = ""
52
+ @mode = :plain
53
+
54
+ while @str.size > 0
55
+
56
+ if /\A(")/ =~ str && out_of_quote_double &&
57
+ out_of_quote_single && out_of_comment_single && out_of_comment_multi
58
+ ## begin double quote
59
+
60
+ length = $1.size
61
+ shift_token(length, :plain, :quote_double, :start)
62
+ out_of_quote_double = false
63
+
64
+ elsif /\A(")/ =~ str && !(out_of_quote_double) &&
65
+ out_of_quote_single && out_of_comment_single && out_of_comment_multi
66
+ ## end double quote
67
+
68
+ length = $1.size
69
+ if /\A("")/ =~ str ## escaped double quote
70
+ shift_to_buf(2)
71
+ else
72
+ shift_token(length, :quote_double, :plain, :end)
73
+ out_of_quote_double = true
74
+ end
75
+
76
+ elsif /\A(')/ =~ str && out_of_quote_single &&
77
+ out_of_quote_double && out_of_comment_single && out_of_comment_multi
78
+ ## begin single quote
79
+
80
+ length = $1.size
81
+ shift_token(length, :plain, :quote_single, :start)
82
+ out_of_quote_single = false
83
+ elsif /\A(')/ =~ str && !(out_of_quote_single) &&
84
+ out_of_quote_double && out_of_comment_single && out_of_comment_multi
85
+ ## end single quote
86
+
87
+ length = $1.size
88
+ if /\A('')/ =~ @str ## escaped single quote
89
+ shift_to_buf(2)
90
+ else
91
+ shift_token(length, :quote_single, :plain, :end)
92
+ out_of_quote_single = true
93
+ end
94
+
95
+ elsif /\A(#{@comment_single_start})/ =~ str && out_of_comment_single &&
96
+ out_of_quote_single && out_of_quote_double && out_of_comment_multi
97
+ ## begin single line comment
98
+
99
+ length = $1.size
100
+ shift_token(length, :plain, :comment_single, :start)
101
+ out_of_comment_single = false
102
+
103
+ elsif /\A(\n)/ =~ str && !(out_of_comment_single) &&
104
+ out_of_quote_single && out_of_quote_double && out_of_comment_multi
105
+ ## end single line comment
106
+
107
+ length = $1.size
108
+ shift_token(length, :comment_single, :plain, :end)
109
+ out_of_comment_single = true
110
+
111
+ elsif /\A(#{@comment_multi_start})/ =~ str &&
112
+ out_of_quote_single && out_of_quote_double && out_of_comment_single && out_of_comment_multi
113
+ ## begin multi line comment
114
+
115
+ length = $1.size
116
+ shift_token(length, :plain, :comment_multi, :start)
117
+ out_of_comment_multi = false
118
+
119
+ elsif /\A(#{@comment_multi_end})/ =~ str &&
120
+ out_of_quote_single && out_of_quote_double && out_of_comment_single && !(out_of_comment_multi)
121
+ ## end multi line comment
122
+
123
+ length = $1.size
124
+ shift_token(length, :comment_multi, :plain, :end)
125
+ out_of_comment_multi = true
126
+
127
+ elsif /\A\\/ =~ str
128
+ ## escape char
129
+ shift_to_buf(2)
130
+
131
+ else
132
+ shift_to_buf(1)
133
+
134
+ end
135
+ end
136
+ @result << CoarseToken.new(@mode, @buf+@str) if (@buf+@str).size > 0
137
+
138
+ @result
139
+ end
140
+
141
+
142
+ def shift_to_buf(n)
143
+ @buf << @str[0...n]
144
+ @str[0...n] = ""
145
+ end
146
+
147
+
148
+ def shift_token(length, type, mode, flag)
149
+ case flag
150
+ when :start
151
+ @result << CoarseToken.new(type, @buf) if @buf.size > 0
152
+ @buf = @str[0..(length-1)] # <length> char from head
153
+ when :end
154
+ @result << CoarseToken.new(type, @buf+@str[0..(length-1)]) if @buf.size > 0
155
+ @buf = ""
156
+ else
157
+ raise "must not happen"
158
+ end
159
+
160
+ @str[0..(length-1)] = ""
161
+ @mode = mode
162
+ end
163
+ end
164
+
165
+
166
+ if $0 == __FILE__
167
+ tok = CoarseTokenizer.new
168
+ src = File.read(ARGV[0])
169
+ coarse_tokens = tok.tokenize(src)
170
+
171
+ coarse_tokens.each{|t|
172
+ puts t.to_s
173
+ }
174
+ end
@@ -0,0 +1,81 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ =begin rdoc
4
+ AnbtSqlFormatter: SQL整形ツール. SQL文を決められたルールに従い整形します。
5
+
6
+ フォーマットを実施するためには、入力されるSQLがSQL文として妥当であることが前提条件となります。
7
+
8
+ このクラスが準拠するSQL整形のルールについては、下記URLを参照ください。
9
+ http://homepage2.nifty.com/igat/igapyon/diary/2005/ig050613.html
10
+
11
+ このクラスには ANSI SQLの予約語一覧が蓄えられます。
12
+
13
+ @author IGA Tosiki
14
+ @author sonota
15
+ =end
16
+
17
+
18
+ class AnbtSql
19
+ class Constants
20
+ # ANSI SQL キーワード
21
+ SQL_RESERVED_WORDS =
22
+ [
23
+ # ANSI SQL89
24
+ "ALL", "AND", "ANY", "AS", "ASC", "AUTHORIZATION", "AVG", "BEGIN",
25
+ "BETWEEN", "BY", "CHAR", "CHARACTER", "CHECK", "CLOSE", "COBOL",
26
+ "COMMIT", "CONTINUE", "COUNT", "CREATE", "CURRENT", "CURSOR",
27
+ "DEC", "DECIMAL", "DECLARE", "DEFAULT", "DELETE", "DESC",
28
+ "DISTINCT", "DOUBLE", "END", "ESCAPE", "EXEC", "EXISTS", "FETCH",
29
+ "FLOAT", "FOR", "FOREIGN", "FORTRAN", "FOUND", "FROM", "GO",
30
+ "GOTO", "GRANT", "GROUP", "HAVING", "IN", "INDICATOR", "INSERT",
31
+ "INT", "INTEGER", "INTO", "IS", "KEY", "LANGUAGE", "LIKE", "MAX",
32
+ "MIN", "MODULE", "NOT", "NULL", "NUMERIC", "OF", "ON", "OPEN",
33
+ "OPTION", "OR", "ORDER", "PASCAL", "PLI", "PRECISION", "PRIMARY",
34
+ "PRIVILEGES", "PROCEDURE", "PUBLIC", "REAL", "REFERENCES",
35
+ "ROLLBACK", "SCHEMA", "SECTION", "SELECT", "SET", "SMALLINT",
36
+ "SOME", "SQL", "SQLCODE", "SQLERROR", "SUM", "TABLE", "TO",
37
+ "UNION", "UNIQUE", "UPDATE", "USER", "VALUES", "VIEW",
38
+ "WHENEVER", "WHERE", "WITH", "WORK",
39
+ # ANSI SQL92
40
+ "ABSOLUTE", "ACTION", "ADD", "ALLOCATE", "ALTER", "ARE",
41
+ "ASSERTION", "AT", "BIT", "BIT_LENGTH", "BOTH", "CASCADE",
42
+ "CASCADED", "CASE", "CAST", "CATALOG", "CHAR_LENGTH",
43
+ "CHARACTER_LENGTH", "COALESCE", "COLLATE", "COLLATION", "COLUMN",
44
+ "CONNECT", "CONNECTION", "CONSTRAINT", "CONSTRAINTS", "CONVERT",
45
+ "CORRESPONDING", "CROSS", "CURRENT_DATE", "CURRENT_TIME",
46
+ "CURRENT_TIMESTAMP", "CURRENT_USER", "DATE", "DAY", "DEALLOATE",
47
+ "DEFERRABLE", "DEFERRED", "DESCRIBE", "DESCRIPTOR", "DIAGNOSTICS",
48
+ "DISCONNECT", "DOMAIN", "DROP", "ELSE", "END-EXEC", "EXCEPT",
49
+ "EXCEPTION", "EXECUTE", "EXTERNAL", "EXTRACT", "FALSE", "FIRST",
50
+ "FULL", "GET", "GLOBAL", "HOUR", "IDENTITY", "IMMEDIATE",
51
+ "INITIALLY", "INNER", "INPUT", "INSENSITIVE", "INTERSECT",
52
+ "INTERVAL", "ISOLATION", "JOIN", "LAST", "LEADING", "LEFT",
53
+ "LEVEL", "LOCAL", "LOWER", "MATCH", "MINUTE", "MONTH", "NAMES",
54
+ "NATIONAL", "NATURAL", "NCHAR", "NEXT", "NO", "NULLIF",
55
+ "OCTET_LENGTH", "ONLY", "OUTER", "OUTPUT", "OVERLAPS", "PAD",
56
+ "PARTIAL", "POSITION", "PREPARE", "PRESERVE", "PRIOR", "READ",
57
+ "RELATIVE", "RESTRICT", "REVOKE", "RIGHT", "ROWS", "SCROLL",
58
+ "SECOND", "SESSION", "SESSION_USER", "SIZE", "SPACE", "SQLSTATE",
59
+ "SUBSTRING", "SYSTEM_USER", "TEMPORARY", "THEN", "TIME",
60
+ "TIMESTAMP", "TIMEZONE_HOUR", "TIMEZONE_MINUTE", "TRAILING",
61
+ "TRANSACTION", "TRANSLATE", "TRANSLATION", "TRIM", "TRUE",
62
+ "UNKNOWN", "UPPER", "USAGE", "USING", "VALUE", "VARCHAR",
63
+ "VARYING", "WHEN", "WRITE", "YEAR", "ZONE",
64
+ # ANSI SQL99
65
+ "ADMIN", "AFTER", "AGGREGATE", "ALIAS", "ARRAY", "BEFORE",
66
+ "BINARY", "BLOB", "BOOLEAN", "BREADTH", "CALL", "CLASS", "CLOB",
67
+ "COMPLETION", "CONDITION", "CONSTRUCTOR", "CUBE", "CURRENT_PATH",
68
+ "CURRENT_ROLE", "CYCLE", "DATA", "DEPTH", "DEREF", "DESTROY",
69
+ "DESTRUCTOR", "DETERMINISTIC", "DICTIONARY", "DO", "DYNAMIC",
70
+ "EACH", "ELSEIF", "EQUALS", "EVERY", "EXIT", "FREE", "FUNCTION",
71
+ "GENERAL", "GROUPING", "HANDLER", "HOST", "IF", "IGNORE",
72
+ "INITIALIZE", "INOUT", "ITERATE", "LARGE", "LATERAL", "LEAVE",
73
+ "LESS", "LIMIT", "LIST", "LOCALTIME", "LOCALTIMESTAMP", "LOCATOR",
74
+ "LONG", "LOOP", "MAP", "MODIFIES", "MODIFY", "NCLOB", "NEW",
75
+ "NONE", "NUMBER", "OBJECT", "OFF", "OLD", "OPERATION",
76
+ "ORDINALITY", "OUT", "PARAMETER", "PARAMETERS", "PATH", "POSTFIX",
77
+ "PREFIX", "PREORDER", "RAW", "READS", "RECURSIVE", "REDO",
78
+ # ANSI SQLではないのだが とても良く使われる構文
79
+ "TRUNCATE" ]
80
+ end
81
+ end
@@ -0,0 +1,30 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ =begin rdoc
4
+ BlancoSqlFormatterException : SQL整形ツールの例外を表します。
5
+
6
+ @author IGA Tosiki : 新規作成 at 2005.08.03
7
+ =end
8
+
9
+ =begin rdoc
10
+ * Rubyの流儀に合わせて "xxxError" とした方が良いかもしれない。
11
+
12
+ @author sonota (2009-11-xx)
13
+ =end
14
+
15
+ require "pp"
16
+
17
+
18
+ class AnbtSql
19
+ class FormatterException < IOError
20
+ def initialize(msg=nil)
21
+ super(msg)
22
+ end
23
+ end
24
+ end
25
+
26
+ class IndexOutOfBoundsException < StandardError
27
+ def initialize(msg=nil)
28
+ super(msg)
29
+ end
30
+ end
@@ -0,0 +1,409 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require "pp"
4
+
5
+ require "anbt-sql-formatter/rule"
6
+ require "anbt-sql-formatter/parser"
7
+ require "anbt-sql-formatter/exception"
8
+ require "anbt-sql-formatter/helper" # Stack
9
+
10
+
11
+ class AnbtSql
12
+ class Formatter
13
+
14
+ @rule = nil
15
+
16
+ def initialize(rule)
17
+ @rule = rule
18
+ @parser = AnbtSql::Parser.new(@rule)
19
+
20
+ # 丸カッコが関数のものかどうかを記憶
21
+ @function_bracket = Stack.new
22
+ end
23
+
24
+
25
+ def split_by_semicolon(tokens)
26
+ statements = []
27
+ buf = []
28
+ tokens.each{|token|
29
+ if token.string == ";"
30
+ statements << buf
31
+ buf = []
32
+ else
33
+ buf << token
34
+ end
35
+ }
36
+
37
+ statements << buf
38
+
39
+ statements
40
+ end
41
+
42
+
43
+ ##
44
+ # 与えられたSQLを整形した文字列を返します。
45
+ #
46
+ # 改行で終了するSQL文は、整形後も改行付きであるようにします。
47
+ # sql_str:: 整形前のSQL文
48
+ def format(sql_str)
49
+ @function_bracket.clear()
50
+ begin
51
+ isSqlEndsWithNewLine = false
52
+ if sql_str.endsWith("\n")
53
+ isSqlEndsWithNewLine = true
54
+ end
55
+
56
+ tokens = @parser.parse(sql_str)
57
+
58
+ statements = split_by_semicolon(tokens)
59
+
60
+ statements = statements.map{|tokens|
61
+ format_list(tokens)
62
+ }
63
+
64
+ # 変換結果を文字列に戻す。
65
+ after = statements.map{|tokens|
66
+ tokens.map{ |t| t.string }.join("")
67
+ }.join("\n;\n\n").sub( /\n\n\Z/, "" )
68
+
69
+ after += "\n" if isSqlEndsWithNewLine
70
+
71
+ return after
72
+ rescue => e
73
+ raise AnbtSql::FormatterException.new, e.message, e.backtrace
74
+ end
75
+ end
76
+
77
+
78
+ def modify_keyword_case(tokens)
79
+ # SQLキーワードは大文字とする。or ...
80
+ tokens.each{ |token|
81
+ next if token._type != AnbtSql::TokenConstants::KEYWORD
82
+
83
+ case @rule.keyword
84
+ when AnbtSql::Rule::KEYWORD_NONE
85
+ ;
86
+ when AnbtSql::Rule::KEYWORD_UPPER_CASE
87
+ token.string.upcase!
88
+ when AnbtSql::Rule::KEYWORD_LOWER_CASE
89
+ token.string.downcase!
90
+ end
91
+ }
92
+ end
93
+
94
+
95
+ ##
96
+ # .
97
+ # ["(", "+", ")"] => ["(+)"]
98
+ def concat_operator_for_oracle(tokens)
99
+ index = 0
100
+ # Length of tokens changes in loop!
101
+ while index < tokens.size - 2
102
+ if (tokens[index ].string == "(" &&
103
+ tokens[index + 1].string == "+" &&
104
+ tokens[index + 2].string == ")")
105
+ tokens[index].string = "(+)"
106
+ tokens.remove(index + 1)
107
+ tokens.remove(index + 1)
108
+ end
109
+ index += 1
110
+ end
111
+ end
112
+
113
+
114
+ def remove_symbol_side_space(tokens)
115
+ prevToken = nil
116
+
117
+ (tokens.size - 1).downto(1){|index|
118
+ token = tokens.get(index)
119
+ prevToken = tokens.get(index - 1)
120
+
121
+ if (token._type == AnbtSql::TokenConstants::SPACE &&
122
+ (prevToken._type == AnbtSql::TokenConstants::SYMBOL ||
123
+ prevToken._type == AnbtSql::TokenConstants::COMMENT))
124
+ tokens.remove(index)
125
+ elsif ((token._type == AnbtSql::TokenConstants::SYMBOL ||
126
+ token._type == AnbtSql::TokenConstants::COMMENT) &&
127
+ prevToken._type == AnbtSql::TokenConstants::SPACE)
128
+ tokens.remove(index - 1)
129
+ elsif (token._type == AnbtSql::TokenConstants::SPACE)
130
+ token.string = " "
131
+ end
132
+ }
133
+ end
134
+
135
+
136
+ def insert_space_between_tokens(tokens)
137
+ index = 1
138
+
139
+ # Length of tokens changes in loop!
140
+ while index < tokens.size
141
+ prev = tokens.get(index - 1)
142
+ token = tokens.get(index )
143
+
144
+ if (prev._type != AnbtSql::TokenConstants::SPACE &&
145
+ token._type != AnbtSql::TokenConstants::SPACE)
146
+ # カンマの後にはスペース入れない
147
+ if not @rule.space_after_comma
148
+ if prev.string == ","
149
+ index += 1 ; next
150
+ end
151
+ end
152
+
153
+ # 関数名の後ろにはスペースは入れない
154
+ # no space after function name
155
+ if (@rule.function?(prev.string) &&
156
+ token.string.equals("("))
157
+ index += 1 ; next
158
+ end
159
+
160
+ tokens.add(index,
161
+ AnbtSql::Token.new(AnbtSql::TokenConstants::SPACE, " ")
162
+ )
163
+ end
164
+ index += 1
165
+ end
166
+ end
167
+
168
+
169
+ def format_list_main_loop(tokens)
170
+ # インデントを整える。
171
+ indent = 0
172
+ # 丸カッコのインデント位置を覚える。
173
+ bracket_indent = Stack.new
174
+
175
+ prev = AnbtSql::Token.new(AnbtSql::TokenConstants::SPACE,
176
+ " ")
177
+
178
+ index = 0
179
+ # Length of tokens changes in loop!
180
+ while index < tokens.size
181
+ token = tokens.get(index)
182
+
183
+ if token._type == AnbtSql::TokenConstants::SYMBOL # ****
184
+
185
+ # indentを1つ増やし、'('のあとで改行。
186
+ if token.string == "("
187
+ @function_bracket.push( @rule.function?(prev.string) ? true : false )
188
+ bracket_indent.push(indent)
189
+ indent += 1
190
+ index += insert_return_and_indent(tokens, index + 1, indent)
191
+
192
+ # indentを1つ増やし、')'の前と後ろで改行。
193
+ elsif token.string == ")"
194
+ indent = (bracket_indent.pop()).to_i
195
+ index += insert_return_and_indent(tokens, index, indent)
196
+ @function_bracket.pop()
197
+
198
+ # ','の前で改行
199
+ elsif token.string == ","
200
+ index += insert_return_and_indent(tokens, index, indent, "x")
201
+
202
+ elsif token.string == ";"
203
+ # 2005.07.26 Tosiki Iga とりあえずセミコロンでSQL文がつぶれないように改良
204
+ indent = 0
205
+ index += insert_return_and_indent(tokens, index, indent)
206
+ end
207
+
208
+ elsif token._type == AnbtSql::TokenConstants::KEYWORD # ****
209
+
210
+ # indentを2つ増やし、キーワードの後ろで改行
211
+ if (token.string.equalsIgnoreCase("DELETE") ||
212
+ token.string.equalsIgnoreCase("SELECT") ||
213
+ token.string.equalsIgnoreCase("UPDATE") )
214
+ indent += 2
215
+ index += insert_return_and_indent(tokens, index + 1, indent, "+2")
216
+ end
217
+
218
+ # indentを1つ増やし、キーワードの後ろで改行
219
+ if @rule.kw_plus1_indent_x_nl.any?{ |kw| token.string.equalsIgnoreCase(kw) }
220
+ indent += 1
221
+ index += insert_return_and_indent(tokens, index + 1, indent)
222
+ end
223
+
224
+ # キーワードの前でindentを1つ減らして改行、キーワードの後ろでindentを戻して改行。
225
+ if @rule.kw_minus1_indent_nl_x_plus1_indent.any?{ |kw| token.string.equalsIgnoreCase(kw) }
226
+ index += insert_return_and_indent(tokens, index , indent - 1)
227
+ index += insert_return_and_indent(tokens, index + 1, indent )
228
+ end
229
+
230
+ # キーワードの前でindentを1つ減らして改行、キーワードの後ろでindentを戻して改行。
231
+ if (token.string.equalsIgnoreCase("VALUES"))
232
+ indent -= 1
233
+ index += insert_return_and_indent(tokens, index, indent)
234
+ end
235
+
236
+ # キーワードの前でindentを1つ減らして改行
237
+ if (token.string.equalsIgnoreCase("END"))
238
+ indent -= 1
239
+ index += insert_return_and_indent(tokens, index, indent)
240
+ end
241
+
242
+ # キーワードの前で改行
243
+ if @rule.kw_nl_x.any?{ |kw| token.string.equalsIgnoreCase(kw) }
244
+ index += insert_return_and_indent(tokens, index, indent)
245
+ end
246
+
247
+ # キーワードの前で改行, インデント+1
248
+ if @rule.kw_nl_x_plus1_indent.any?{ |kw| token.string.equalsIgnoreCase(kw) }
249
+ index += insert_return_and_indent(tokens, index, indent + 1)
250
+ end
251
+
252
+ # キーワードの前で改行。indentを強制的に0にする。
253
+ if (token.string.equalsIgnoreCase("UNION" ) ||
254
+ token.string.equalsIgnoreCase("INTERSECT") ||
255
+ token.string.equalsIgnoreCase("EXCEPT" ) )
256
+ indent -= 2
257
+ index += insert_return_and_indent(tokens, index , indent)
258
+ index += insert_return_and_indent(tokens, index + 1, indent)
259
+ end
260
+
261
+ if token.string.equalsIgnoreCase("BETWEEN")
262
+ encounterBetween = true
263
+ end
264
+
265
+ if token.string.equalsIgnoreCase("AND")
266
+ # BETWEEN のあとのANDは改行しない。
267
+ if not encounterBetween
268
+ index += insert_return_and_indent(tokens, index, indent)
269
+ end
270
+ encounterBetween = false
271
+ end
272
+
273
+ elsif (token._type == AnbtSql::TokenConstants::COMMENT) # ****
274
+
275
+ if token.string.startsWith("/*")
276
+ # マルチラインコメントの後に改行を入れる。
277
+ index += insert_return_and_indent(tokens, index + 1, indent)
278
+ elsif /^--/ =~ token.string
279
+ index += insert_return_and_indent(tokens, index + 1, indent)
280
+ end
281
+ end
282
+ prev = token
283
+
284
+ index += 1
285
+ end
286
+ end
287
+
288
+
289
+ # before: [..., "(", space, "X", space, ")", ...]
290
+ # after: [..., "(X)", ...]
291
+ # ただし、これでは "(X)" という一つの symbol トークンになってしまう。
292
+ # 整形だけが目的ならそれでも良いが、
293
+ # せっかくなので symbol/X/symbol と分けたい。
294
+ def special_treatment_for_parenthesis_with_one_element(tokens)
295
+ (tokens.size - 1).downto(4).each{|index|
296
+ next if (index >= tokens.size())
297
+
298
+ t0 = tokens.get(index )
299
+ t1 = tokens.get(index - 1)
300
+ t2 = tokens.get(index - 2)
301
+ t3 = tokens.get(index - 3)
302
+ t4 = tokens.get(index - 4)
303
+
304
+ if (t4.string. equalsIgnoreCase("(") &&
305
+ t3.string.trim.equalsIgnoreCase("" ) &&
306
+ t1.string.trim.equalsIgnoreCase("" ) &&
307
+ t0.string. equalsIgnoreCase(")") )
308
+ t4.string = t4.string + t2.string + t0.string
309
+ tokens.remove(index )
310
+ tokens.remove(index - 1)
311
+ tokens.remove(index - 2)
312
+ tokens.remove(index - 3)
313
+ end
314
+ }
315
+ end
316
+
317
+
318
+ def format_list(tokens)
319
+ return [] if tokens.empty?
320
+
321
+ # SQLの前後に空白があったら削除する。
322
+ # Delete space token at first and last of SQL tokens.
323
+
324
+ token = tokens.get(0)
325
+ if (token._type == AnbtSql::TokenConstants::SPACE)
326
+ tokens.remove(0)
327
+ end
328
+ return [] if tokens.empty?
329
+
330
+ token = tokens.get(tokens.size() - 1)
331
+ if token._type == AnbtSql::TokenConstants::SPACE
332
+ tokens.remove(tokens.size() - 1)
333
+ end
334
+ return [] if tokens.empty?
335
+
336
+ modify_keyword_case(tokens)
337
+ remove_symbol_side_space(tokens)
338
+ concat_operator_for_oracle(tokens)
339
+
340
+ encounterBetween = false
341
+
342
+ format_list_main_loop(tokens)
343
+
344
+ special_treatment_for_parenthesis_with_one_element(tokens)
345
+ insert_space_between_tokens(tokens)
346
+
347
+ return tokens
348
+ end
349
+
350
+
351
+ ##
352
+ # index の箇所のトークンの前に挿入します。
353
+ #
354
+ # 空白を置き換えた場合:: return 0
355
+ # 空白を挿入した場合:: return 1
356
+ def insert_return_and_indent(tokens, index, indent, opt=nil)
357
+ # 関数内では改行は挿入しない
358
+ # No linefeed in function.
359
+ return 0 if (@function_bracket.include?(true))
360
+
361
+ begin
362
+ # 挿入する文字列を作成する。
363
+ s = "\n"
364
+ # もし1つ前にシングルラインコメントがあるなら、改行は不要。
365
+ prevToken = tokens.get(index - 1)
366
+
367
+ if (prevToken._type == AnbtSql::TokenConstants::COMMENT &&
368
+ prevToken.string.startsWith("--"))
369
+ s = ""
370
+ end
371
+
372
+ # インデントをつける。
373
+ indent = 0 if indent < 0 ## Java版と異なる
374
+ s += @rule.indent_string * indent
375
+
376
+ # 前後にすでにスペースがあれば、それを置き換える。
377
+ token = tokens.get(index)
378
+ if token._type == AnbtSql::TokenConstants::SPACE
379
+ token.string = s
380
+ return 0
381
+ end
382
+
383
+ token = tokens.get(index - 1)
384
+ if token._type == AnbtSql::TokenConstants::SPACE
385
+ token.string = s
386
+ return 0
387
+ end
388
+
389
+ # 前後になければ、新たにスペースを追加する。
390
+ tokens.add(index,
391
+ AnbtSql::Token.new(AnbtSql::TokenConstants::SPACE, s)
392
+ )
393
+ return 1
394
+ rescue IndexOutOfBoundsException => e
395
+ if $DEBUG
396
+ $stderr.puts e.message, e.backtrace
397
+ $stderr.puts "tokens: "
398
+ tokens.each_with_index{|t,i|
399
+ $stderr.puts "index=%d: %s" % [i, t.inspect]
400
+ }
401
+ $stderr.puts "index/size: %d/%d / indent: %d / opt: %s" % [index, tokens.size, indent, opt]
402
+ end
403
+ return 0
404
+ rescue => e
405
+ raise e
406
+ end
407
+ end
408
+ end
409
+ end