anbt-sql-formatter 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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