vorax 0.4.2 → 5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +4 -29
- data/vorax.gemspec +3 -11
- metadata +4 -92
- data/.rspec +0 -1
- data/Rakefile +0 -30
- data/lib/vorax.rb +0 -60
- data/lib/vorax/base_funnel.rb +0 -30
- data/lib/vorax/output/html_convertor.rb +0 -120
- data/lib/vorax/output/html_funnel.rb +0 -79
- data/lib/vorax/output/pagezip_convertor.rb +0 -20
- data/lib/vorax/output/tablezip_convertor.rb +0 -22
- data/lib/vorax/output/vertical_convertor.rb +0 -53
- data/lib/vorax/output/zip_convertor.rb +0 -117
- data/lib/vorax/parser/argument.rb~ +0 -125
- data/lib/vorax/parser/conn_string.rb +0 -104
- data/lib/vorax/parser/grammars/alias.rb +0 -904
- data/lib/vorax/parser/grammars/alias.rl +0 -138
- data/lib/vorax/parser/grammars/column.rb +0 -454
- data/lib/vorax/parser/grammars/column.rl +0 -64
- data/lib/vorax/parser/grammars/common.rl +0 -107
- data/lib/vorax/parser/grammars/declare.rb +0 -9606
- data/lib/vorax/parser/grammars/declare.rl +0 -160
- data/lib/vorax/parser/grammars/for_block.rb +0 -440
- data/lib/vorax/parser/grammars/for_block.rl +0 -73
- data/lib/vorax/parser/grammars/plsql_def.rb +0 -539
- data/lib/vorax/parser/grammars/plsql_def.rl +0 -73
- data/lib/vorax/parser/grammars/statement.rb +0 -925
- data/lib/vorax/parser/grammars/statement.rl +0 -83
- data/lib/vorax/parser/parser.rb +0 -344
- data/lib/vorax/parser/plsql_structure.rb +0 -222
- data/lib/vorax/parser/plsql_walker.rb +0 -143
- data/lib/vorax/parser/statement_inspector.rb~ +0 -52
- data/lib/vorax/parser/stmt_inspector.rb +0 -78
- data/lib/vorax/parser/target_ref.rb +0 -110
- data/lib/vorax/sqlplus.rb +0 -273
- data/lib/vorax/version.rb +0 -7
- data/lib/vorax/vorax_io.rb +0 -70
- data/spec/column_spec.rb +0 -40
- data/spec/conn_string_spec.rb +0 -53
- data/spec/declare_spec.rb +0 -281
- data/spec/pagezip_spec.rb +0 -153
- data/spec/parser_spec.rb +0 -352
- data/spec/plsql_structure_spec.rb +0 -68
- data/spec/spec_helper.rb +0 -13
- data/spec/sql/create_objects.sql +0 -69
- data/spec/sql/dbms_crypto.spc +0 -339
- data/spec/sql/dbms_stats.spc +0 -4097
- data/spec/sql/drop_user.sql +0 -10
- data/spec/sql/muci.spc +0 -26
- data/spec/sql/setup_user.sql +0 -22
- data/spec/sql/test.fnc +0 -12
- data/spec/sql/test.pkg +0 -83
- data/spec/sqlplus_spec.rb +0 -52
- data/spec/stmt_inspector_spec.rb +0 -96
- data/spec/tablezip_spec.rb +0 -111
- data/spec/vertical_spec.rb +0 -150
@@ -1,83 +0,0 @@
|
|
1
|
-
%%{
|
2
|
-
|
3
|
-
machine statement;
|
4
|
-
|
5
|
-
action parse_start {
|
6
|
-
eof = pe
|
7
|
-
}
|
8
|
-
|
9
|
-
action parse_error {
|
10
|
-
}
|
11
|
-
|
12
|
-
action mark_as_anonymous {
|
13
|
-
stmt_type = 'ANONYMOUS'
|
14
|
-
}
|
15
|
-
|
16
|
-
action mark_as_sqlplus_command {
|
17
|
-
stmt_type = 'SQLPLUS'
|
18
|
-
}
|
19
|
-
|
20
|
-
action mark_as_sql {
|
21
|
-
stmt_type = nil
|
22
|
-
}
|
23
|
-
|
24
|
-
action mark_type {
|
25
|
-
tail = data[(0...p)]
|
26
|
-
type = tail[/\w+\Z/]
|
27
|
-
stmt_type = type.upcase if type
|
28
|
-
}
|
29
|
-
|
30
|
-
action mark_body {
|
31
|
-
stmt_type << ' BODY'
|
32
|
-
}
|
33
|
-
|
34
|
-
include common "common.rl";
|
35
|
-
|
36
|
-
# parsing rules baby
|
37
|
-
anonymous_block = ((K_BEGIN | K_DECLARE) ws+) @mark_as_anonymous;
|
38
|
-
simple_module = (K_TRIGGER | K_FUNCTION | K_PROCEDURE) %mark_type;
|
39
|
-
package_module = (K_PACKAGE %mark_type) (ws+ K_BODY %mark_body)?;
|
40
|
-
type_module = (K_TYPE %mark_type) (ws+ K_BODY %mark_body)?;
|
41
|
-
java_module = ((K_AND ws+ (K_RESOLVE | K_COMPILE) ws+ K_NOFORCE ws+) |
|
42
|
-
(K_AND ws+ (K_RESOLVE | K_COMPILE) ws+) |
|
43
|
-
(K_NOFORCE ws+))? (K_JAVA %mark_type);
|
44
|
-
plsql_module = K_CREATE ws+ (K_OR ws+ K_REPLACE ws+)?
|
45
|
-
(simple_module |
|
46
|
-
package_module |
|
47
|
-
type_module |
|
48
|
-
java_module) ws+;
|
49
|
-
set_transaction = (K_SET ws+ K_TRANSACTION ws+) @ mark_as_sql;
|
50
|
-
sqlplus_command = (((K_ACCEPT | K_ARCHIVE | K_ATTRIBUTE | K_BREAK | K_BTITLE | K_CLEAR | K_COLUMN |
|
51
|
-
K_COMPUTE | K_CONNECT | K_COPY | K_DEFINE | K_DESCRIBE | K_DISCONNECT | K_EXECUTE |
|
52
|
-
K_EXIT | K_HELP | K_HOST | K_PASSWORD | K_PAUSE | K_PRINT | K_PROMPT |
|
53
|
-
K_RECOVER | K_REMARK | K_REPFOOTER | K_REPHEADER | K_RUN | K_SAVE | K_SET | K_SHOW | K_SHUTDOWN |
|
54
|
-
K_SPOOL | K_START | K_STARTUP | K_STORE | K_TIMING | K_TITLE | K_UNDEFINE | K_VARIABLE | K_WHENEVER |
|
55
|
-
K_XQUERY) ws+) | ('@@' | '@' | '/' | '!')) @ mark_as_sqlplus_command;
|
56
|
-
|
57
|
-
main := ws* (anonymous_block | set_transaction | sqlplus_command | plsql_module) >parse_start $err(parse_error);
|
58
|
-
|
59
|
-
}%%
|
60
|
-
|
61
|
-
module Vorax
|
62
|
-
|
63
|
-
module Parser
|
64
|
-
|
65
|
-
# Gets the type of the provided statement.
|
66
|
-
#
|
67
|
-
# @param data [String] the statement
|
68
|
-
# @return [String] 'SQLPLUS' for an sqlplus statement, 'FUNCTION|PROCEDURE|PACKAGE|TYPE...' for
|
69
|
-
# a PL/SQL block, 'ANONYMOUS' for an anonymous plsql block
|
70
|
-
def self.statement_type(data)
|
71
|
-
stmt_type = nil
|
72
|
-
data << "\n"
|
73
|
-
%% write data;
|
74
|
-
%% write init;
|
75
|
-
%% write exec;
|
76
|
-
data.chop!
|
77
|
-
return stmt_type
|
78
|
-
end
|
79
|
-
|
80
|
-
end
|
81
|
-
|
82
|
-
end
|
83
|
-
|
data/lib/vorax/parser/parser.rb
DELETED
@@ -1,344 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
module Vorax
|
4
|
-
|
5
|
-
# Provides parsing utilities.
|
6
|
-
module Parser
|
7
|
-
|
8
|
-
END_LINE = /\r\n?|\n/ unless defined?(END_LINE)
|
9
|
-
SQLPLUS_TERMINATOR = END_LINE unless defined?(SQLPLUS_TERMINATOR)
|
10
|
-
SEMI_COLON_TERMINATOR = /;/ unless defined?(SEMI_COLON_TERMINATOR)
|
11
|
-
SLASH_TERMINATOR = Regexp.new('(?:' + END_LINE.to_s + '\s*\/[ \t]*' + END_LINE.to_s + ')') unless defined?(SLASH_TERMINATOR)
|
12
|
-
PLSQL_SPEC = /(?:\bpackage\b|\btype\b)/i
|
13
|
-
SUBPROG = /(?:\bfunction\b|\bprocedure\b)/i
|
14
|
-
BEGIN_MODULE = /(?:\bbegin\b)/i
|
15
|
-
END_MODULE = /(?:\bend\b)/i
|
16
|
-
|
17
|
-
# Given an expression with parenthesis, it is walking it so that to
|
18
|
-
# keep track of the open/close paren, in a balanced way.
|
19
|
-
#
|
20
|
-
# @param text [String] the string to be walked
|
21
|
-
# @return [String] the paren expression
|
22
|
-
def self.walk_balanced_paren(text)
|
23
|
-
walker = PLSQLWalker.new(text)
|
24
|
-
level = 0
|
25
|
-
start_pos = 0
|
26
|
-
end_pos = 0
|
27
|
-
walker.register_spot(/[(]/) do |scanner|
|
28
|
-
start_pos = scanner.pos - 1 if level == 0
|
29
|
-
level += 1
|
30
|
-
end
|
31
|
-
walker.register_spot(/[)]/) do |scanner|
|
32
|
-
level -= 1
|
33
|
-
if level <= 0
|
34
|
-
end_pos = scanner.pos
|
35
|
-
scanner.terminate
|
36
|
-
end
|
37
|
-
end
|
38
|
-
walker.walk
|
39
|
-
text[start_pos, end_pos]
|
40
|
-
end
|
41
|
-
|
42
|
-
# Remove all comments from the provided statement. Pay attention that every
|
43
|
-
# comment is replaced by a blank in order to cover the case where a comment is
|
44
|
-
# used as a whitespace (e.g. select * from/*comment*/dual).
|
45
|
-
#
|
46
|
-
# @param statement [String] the statement to be cleaned up of comments
|
47
|
-
# @return [String] the statement without any comment
|
48
|
-
def self.remove_all_comments(statement)
|
49
|
-
comment_areas = []
|
50
|
-
result = statement
|
51
|
-
walker = PLSQLWalker.new(statement, false)
|
52
|
-
|
53
|
-
callback = lambda do |scanner, end_pattern|
|
54
|
-
start_pos = scanner.pos - scanner.matched.length
|
55
|
-
text = scanner.scan_until(end_pattern)
|
56
|
-
if text
|
57
|
-
comment_areas << (start_pos..scanner.pos - 1)
|
58
|
-
else
|
59
|
-
scanner.terminate
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
walker.register_spot(PLSQLWalker::BEGIN_ML_COMMENT) do |scanner|
|
64
|
-
callback.call(scanner, PLSQLWalker::END_ML_COMMENT)
|
65
|
-
end
|
66
|
-
|
67
|
-
walker.register_spot(PLSQLWalker::BEGIN_SL_COMMENT) do |scanner|
|
68
|
-
callback.call(scanner, PLSQLWalker::END_SL_COMMENT)
|
69
|
-
end
|
70
|
-
|
71
|
-
walker.register_default_plsql_quoting_spot()
|
72
|
-
walker.register_default_double_quoting_spot()
|
73
|
-
walker.register_default_single_quoting_spot()
|
74
|
-
walker.walk
|
75
|
-
offset = 0
|
76
|
-
comment_areas.each do |interval|
|
77
|
-
r = (interval.min - offset .. interval.max - offset)
|
78
|
-
result[r] = " "
|
79
|
-
offset += (interval.max - interval.min)
|
80
|
-
end
|
81
|
-
result
|
82
|
-
end
|
83
|
-
|
84
|
-
# Remove the trailing comments from the provided statement.
|
85
|
-
#
|
86
|
-
# @param statement [String] the statement to be cleaned up
|
87
|
-
# @return the statement without the trailing comments.
|
88
|
-
def self.remove_trailing_comments(statement)
|
89
|
-
stmt = statement
|
90
|
-
begin
|
91
|
-
stmt.gsub!(/(?:--[^\n]*\s*\z)|(?:\/\*.*?\*\/\s*\z)/m, '')
|
92
|
-
end while !$~.nil?
|
93
|
-
stmt
|
94
|
-
end
|
95
|
-
|
96
|
-
# Get the function/procedure to which the argument on the
|
97
|
-
# provided position belongs.
|
98
|
-
#
|
99
|
-
# @param statement [String] the statement to be parsed
|
100
|
-
# @param position [int] the position index where the
|
101
|
-
# argument should be given
|
102
|
-
def self.argument_belongs_to(statement, position = nil)
|
103
|
-
position = statement.length unless position
|
104
|
-
stmt = Parser.remove_all_comments(statement[(0...position)])
|
105
|
-
stmt.reverse!
|
106
|
-
level = 0
|
107
|
-
walker = PLSQLWalker.new(stmt, false)
|
108
|
-
arg_owner = ""
|
109
|
-
|
110
|
-
squote_fallback = lambda do |scanner|
|
111
|
-
scanner.skip_until(PLSQLWalker::BEGIN_SINGLE_QUOTING)
|
112
|
-
if scanner.matched == "'"
|
113
|
-
begin
|
114
|
-
scanner.skip_until(/\'+/)
|
115
|
-
end while (scanner.matched != "'" && !scanner.eos?)
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
extract_module = lambda do |scanner|
|
120
|
-
module_name = ""
|
121
|
-
while !scanner.eos?
|
122
|
-
# consume leading whitspaces
|
123
|
-
scanner.scan(/\s*/)
|
124
|
-
if scanner.check(/"/) == '"'
|
125
|
-
# we have a quoted identifier
|
126
|
-
module_name << scanner.scan(/"/)
|
127
|
-
module_name << scanner.scan_until(/"/)
|
128
|
-
else
|
129
|
-
# unquoted identifier
|
130
|
-
module_name << scanner.scan(/\S+/)
|
131
|
-
end
|
132
|
-
# consume trailing whitespaces
|
133
|
-
scanner.scan(/\s*/)
|
134
|
-
|
135
|
-
# might be a dblink
|
136
|
-
if scanner.check(/@/) == '@'
|
137
|
-
module_name << scanner.scan(/@/)
|
138
|
-
next
|
139
|
-
end
|
140
|
-
|
141
|
-
# might be package or a schema
|
142
|
-
if scanner.check(/\./) == '.'
|
143
|
-
module_name << scanner.scan(/\./)
|
144
|
-
next
|
145
|
-
end
|
146
|
-
scanner.terminate
|
147
|
-
end
|
148
|
-
module_name.reverse!
|
149
|
-
end
|
150
|
-
|
151
|
-
walker.register_spot(/'[\]})>]/) do |scanner|
|
152
|
-
# pay attention, it's reveresed
|
153
|
-
if scanner.matched =~ /\'\]/
|
154
|
-
squote_fallback.call(scanner) unless scanner.skip_until(/\[\'q/)
|
155
|
-
elsif scanner.matched =~ /\'[}]/
|
156
|
-
squote_fallback.call(scanner) unless scanner.skip_until(/[{]\'q/)
|
157
|
-
elsif scanner.matched =~ /\'[)]/
|
158
|
-
squote_fallback.call(scanner) unless scanner.skip_until(/[(]\'q/)
|
159
|
-
elsif scanner.matched =~ /\'[>]/
|
160
|
-
squote_fallback.call(scanner) unless scanner.skip_until(/[<]\'q/)
|
161
|
-
end
|
162
|
-
end
|
163
|
-
|
164
|
-
walker.register_spot(/[)]/) do |scanner|
|
165
|
-
level += 1
|
166
|
-
end
|
167
|
-
|
168
|
-
walker.register_spot(/[(]/) do |scanner|
|
169
|
-
if level == 0
|
170
|
-
arg_owner = extract_module.call(scanner)
|
171
|
-
else
|
172
|
-
level -= 1
|
173
|
-
scanner.terminate if level < 0 #give up, it's an invalid statement
|
174
|
-
end
|
175
|
-
end
|
176
|
-
|
177
|
-
walker.walk
|
178
|
-
return arg_owner
|
179
|
-
end
|
180
|
-
|
181
|
-
# Given the html output of a script, it extracts all tables into a nice
|
182
|
-
# ruby array. This method returns a hash with the following meaning:
|
183
|
-
# :resultset => an array with resultsets from all queries which
|
184
|
-
# generated the <html> output. For example, if the
|
185
|
-
# html parameter contains the output of two valid
|
186
|
-
# queries, the :resultset will contain:
|
187
|
-
#
|
188
|
-
# [ # an array with all result sets
|
189
|
-
# [ # the resultset of the first query
|
190
|
-
# [val11, val12],
|
191
|
-
# [val21, val22],
|
192
|
-
# ...
|
193
|
-
# [valn1, valn2]
|
194
|
-
# ],
|
195
|
-
# [ # the result set of the second query
|
196
|
-
# [v11, v12, v13],
|
197
|
-
# [v21, v22, v23],
|
198
|
-
# ...
|
199
|
-
# [vn1, vn2, vn3]
|
200
|
-
# ]
|
201
|
-
# ]
|
202
|
-
# If errors are detected into the output they are extracted into the
|
203
|
-
# :errors attribute of the returining hash.
|
204
|
-
#
|
205
|
-
# @param html [String] the html to be parsed
|
206
|
-
# @return a hash with the parsed content
|
207
|
-
def self.query_result(html)
|
208
|
-
nbsp = Nokogiri::HTML(" ").text
|
209
|
-
hash = {:resultset => [], :errors => []}
|
210
|
-
doc = Nokogiri::HTML(html)
|
211
|
-
hash[:errors] = doc.xpath('/html/body/text()').map{ |n| n.text }.grep(/\nORA-[0-9]+/)
|
212
|
-
doc.xpath('//table').each do |table|
|
213
|
-
resultset = []
|
214
|
-
table.xpath('tr').each do |tr|
|
215
|
-
row = []
|
216
|
-
# replace nbsp with a plain blank in order to not confuse
|
217
|
-
# the ragel parser, in case it's used
|
218
|
-
tr.xpath('td').each { |td| row << td.text.strip.gsub(nbsp, " ") }
|
219
|
-
resultset << row unless row.empty?
|
220
|
-
end
|
221
|
-
hash[:resultset] << resultset
|
222
|
-
end
|
223
|
-
return hash
|
224
|
-
end
|
225
|
-
|
226
|
-
# Prepare the provided statement for sqlplus execution. The prepare phase consists in
|
227
|
-
# adding the right end separator according to the statement type.
|
228
|
-
#
|
229
|
-
# @param statement [String] the statement to be prepared
|
230
|
-
# @return [String] the statement with the proper end separator appended
|
231
|
-
def self.prepare_exec(statement)
|
232
|
-
stmt = Parser.remove_trailing_comments(statement)
|
233
|
-
type = Parser.statement_type(stmt)
|
234
|
-
if type == 'SQLPLUS'
|
235
|
-
# do nothing
|
236
|
-
elsif !type.nil?
|
237
|
-
# a plsql block. We need a trailing /
|
238
|
-
stmt = "#{stmt.strip}\n/\n" if stmt !~ /\n\s*\/\s*\z/
|
239
|
-
else
|
240
|
-
# normal statement. It should have a trailing ;
|
241
|
-
stmt = "#{stmt.strip};" if stmt !~ /;\s*\z/
|
242
|
-
end
|
243
|
-
return stmt
|
244
|
-
end
|
245
|
-
|
246
|
-
# Get the current statement for the provided position.
|
247
|
-
#
|
248
|
-
# @param script_content [String] the script within which the current statement must
|
249
|
-
# be detected
|
250
|
-
# @param position [int] the absolute position within the script content for which
|
251
|
-
# the current statement must be found out
|
252
|
-
# @param params [Hash] additional options. The following parameters may be
|
253
|
-
# provided:
|
254
|
-
#
|
255
|
-
# :plsql_blocks => whenever or not to consider PL/SQL blocks when the current
|
256
|
-
# statement is detected. By default is true.
|
257
|
-
# :sqlplus_commands => whenever or not to consider SQLPLUS commands when
|
258
|
-
# trying to detect the current statement
|
259
|
-
#
|
260
|
-
# @return [Hash] a hash with the following keys: :statement => the current statement
|
261
|
-
# which corresponds to the provided position, :range => the statement boundaries
|
262
|
-
# within the whole script
|
263
|
-
def self.current_statement(script_content, position=0, params = {})
|
264
|
-
opts = {
|
265
|
-
:plsql_blocks => true,
|
266
|
-
:sqlplus_commands => true
|
267
|
-
}.merge(params)
|
268
|
-
start_pos = 0
|
269
|
-
end_pos = 0
|
270
|
-
|
271
|
-
walker = PLSQLWalker.new(script_content)
|
272
|
-
|
273
|
-
walker.register_spot(Parser::SEMI_COLON_TERMINATOR) do |scanner|
|
274
|
-
type = Parser.statement_type(scanner.string[(start_pos..scanner.pos)])
|
275
|
-
if type
|
276
|
-
if opts[:plsql_blocks] && type != 'SQLPLUS'
|
277
|
-
#this is a plsql block, eat till the slash terminator
|
278
|
-
unless scanner.scan_until(Parser::SLASH_TERMINATOR)
|
279
|
-
#it's an invalid statement
|
280
|
-
scanner.terminate
|
281
|
-
end
|
282
|
-
end
|
283
|
-
end
|
284
|
-
if (start_pos..scanner.pos).include?(position)
|
285
|
-
# include the terminator
|
286
|
-
end_pos = scanner.pos
|
287
|
-
scanner.terminate
|
288
|
-
else
|
289
|
-
start_pos = scanner.pos
|
290
|
-
end
|
291
|
-
end
|
292
|
-
|
293
|
-
walker.register_spot(Parser::SLASH_TERMINATOR) do |scanner|
|
294
|
-
if (start_pos..scanner.pos).include?(position)
|
295
|
-
# include the terminator
|
296
|
-
end_pos = scanner.pos
|
297
|
-
scanner.terminate
|
298
|
-
else
|
299
|
-
start_pos = scanner.pos
|
300
|
-
end
|
301
|
-
end
|
302
|
-
|
303
|
-
if opts[:sqlplus_commands]
|
304
|
-
walker.register_spot(Parser::SQLPLUS_TERMINATOR) do |scanner|
|
305
|
-
type = Parser.statement_type(scanner.string[(start_pos..scanner.pos)])
|
306
|
-
if type
|
307
|
-
if type == 'SQLPLUS'
|
308
|
-
if (start_pos..scanner.pos-1).include?(position)
|
309
|
-
end_pos = scanner.pos - scanner.matched.length
|
310
|
-
scanner.terminate
|
311
|
-
else
|
312
|
-
start_pos = scanner.pos
|
313
|
-
end
|
314
|
-
else
|
315
|
-
if opts[:plsql_blocks]
|
316
|
-
#this is a plsql block, eat till the slash terminator
|
317
|
-
if scanner.scan_until(Parser::SLASH_TERMINATOR)
|
318
|
-
if (start_pos..scanner.pos-1).include?(position)
|
319
|
-
end_pos = scanner.pos
|
320
|
-
scanner.terminate
|
321
|
-
else
|
322
|
-
start_pos = scanner.pos
|
323
|
-
end
|
324
|
-
else
|
325
|
-
#it's an invalid statement
|
326
|
-
scanner.terminate
|
327
|
-
end
|
328
|
-
end
|
329
|
-
end
|
330
|
-
#else
|
331
|
-
#start_pos = scanner.pos
|
332
|
-
end
|
333
|
-
end
|
334
|
-
end
|
335
|
-
|
336
|
-
walker.walk
|
337
|
-
end_pos = script_content.length if end_pos == 0 #partial statement
|
338
|
-
{:statement => script_content[(start_pos...end_pos)], :range => (start_pos...end_pos)}
|
339
|
-
|
340
|
-
end
|
341
|
-
|
342
|
-
end
|
343
|
-
|
344
|
-
end
|
@@ -1,222 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
require 'tree'
|
4
|
-
|
5
|
-
module Vorax
|
6
|
-
|
7
|
-
module Parser
|
8
|
-
|
9
|
-
class Region
|
10
|
-
|
11
|
-
attr_accessor :start_pos, :end_pos, :body_start_pos, :context
|
12
|
-
attr_reader :name, :type
|
13
|
-
|
14
|
-
def initialize(name, type, start_pos = 0, end_pos = 0)
|
15
|
-
@name = name
|
16
|
-
@type = type
|
17
|
-
@start_pos = start_pos
|
18
|
-
@end_pos = end_pos
|
19
|
-
@body_start_pos = 0
|
20
|
-
@context = nil
|
21
|
-
end
|
22
|
-
|
23
|
-
def to_s
|
24
|
-
"#{@name}[#{@type}]: #{@start_pos}"
|
25
|
-
end
|
26
|
-
|
27
|
-
end
|
28
|
-
|
29
|
-
class PlsqlStructure
|
30
|
-
|
31
|
-
PLSQL_SPEC = /(?:\bpackage\b|\btype\b)/i unless defined?(PLSQL_SPEC)
|
32
|
-
SUBPROG = /(?:\bfunction\b|\bprocedure\b)/i unless defined?(SUBPROG)
|
33
|
-
BEGIN_MODULE = /(?:\bbegin\b)/i unless defined?(BEGIN_MODULE)
|
34
|
-
END_MODULE = /(?:\bend\b)/i unless defined?(END_MODULE)
|
35
|
-
FOR_STMT = /(?:\bfor\b)/i unless defined?(FOR_STMT)
|
36
|
-
LOOP_STMT = /(?:\bloop\b)/i unless defined?(LOOP_STMT)
|
37
|
-
IF_STMT = /(?:\bif\b)/i unless defined?(IF_STMT)
|
38
|
-
|
39
|
-
attr_reader :text
|
40
|
-
|
41
|
-
def initialize(text)
|
42
|
-
@text = text
|
43
|
-
@root = Tree::TreeNode.new("root", nil)
|
44
|
-
@walker = PLSQLWalker.new(text)
|
45
|
-
@level = 0
|
46
|
-
@current_parent = @root
|
47
|
-
@begin_level = 0
|
48
|
-
register_spots()
|
49
|
-
@walker.walk
|
50
|
-
rescue Exception => e
|
51
|
-
# be prepare for any nasting parse error.
|
52
|
-
# Failing here is kind of usual, having in mind
|
53
|
-
# that we often parse incomplete code.
|
54
|
-
Vorax.debug(e.to_s)
|
55
|
-
end
|
56
|
-
|
57
|
-
def tree
|
58
|
-
#@root.each { |t| t.content.end_pos = @text.length if t.content && t.content.end_pos == 0 }
|
59
|
-
@root
|
60
|
-
end
|
61
|
-
|
62
|
-
private
|
63
|
-
|
64
|
-
def assign_parent(node)
|
65
|
-
@current_parent = node
|
66
|
-
end
|
67
|
-
|
68
|
-
def register_spots
|
69
|
-
register_plsql_spec_spot()
|
70
|
-
register_slash_terminator_spot()
|
71
|
-
register_subprog_spot()
|
72
|
-
register_begin_spot()
|
73
|
-
register_for_spot()
|
74
|
-
register_loop_spot()
|
75
|
-
register_if_spot()
|
76
|
-
register_end_spot()
|
77
|
-
end
|
78
|
-
|
79
|
-
def register_plsql_spec_spot
|
80
|
-
@walker.register_spot(PLSQL_SPEC) do |scanner|
|
81
|
-
if @level == 0
|
82
|
-
meta_data = Parser.plsql_def("#{scanner.matched}#{scanner.rest}")
|
83
|
-
if meta_data[:type] == 'SPEC' || meta_data[:type] == 'BODY'
|
84
|
-
# is it a spec or a body?
|
85
|
-
region = Region.new(meta_data[:name], meta_data[:type], scanner.pos)
|
86
|
-
assign_parent(@current_parent << Tree::TreeNode.new(region.to_s, region))
|
87
|
-
@level += 1
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
def register_slash_terminator_spot
|
94
|
-
@walker.register_spot(Parser::SLASH_TERMINATOR) do |scanner|
|
95
|
-
# this should apply to the last top level node
|
96
|
-
if @root.has_children?
|
97
|
-
if @root.children.last.content
|
98
|
-
@root.children.last.content.end_pos = scanner.pos
|
99
|
-
end
|
100
|
-
assign_parent(@root)
|
101
|
-
@level = 0
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
def register_subprog_spot
|
107
|
-
@walker.register_spot(SUBPROG) do |scanner|
|
108
|
-
subprog_name = scanner.peek(32)[/(?:"[^"]+")|(?:[A-Z0-9$_#]+)/i]
|
109
|
-
if scanner.matched =~ /function/i
|
110
|
-
subprog_type = 'FUNCTION'
|
111
|
-
elsif scanner.matched =~ /procedure/i
|
112
|
-
subprog_type = 'PROCEDURE'
|
113
|
-
end
|
114
|
-
start_pos = scanner.pos - scanner.matched.length
|
115
|
-
region = Region.new(subprog_name, subprog_type, start_pos)
|
116
|
-
node = Tree::TreeNode.new(region.to_s, region)
|
117
|
-
@current_parent << node
|
118
|
-
if @current_parent && @current_parent.content && @current_parent.content.type == 'SPEC'
|
119
|
-
node.content.end_pos = node.content.start_pos
|
120
|
-
else
|
121
|
-
@level += 1
|
122
|
-
assign_parent(node)
|
123
|
-
end
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
def register_begin_spot
|
128
|
-
@walker.register_spot(BEGIN_MODULE) do |scanner|
|
129
|
-
@begin_level += 1
|
130
|
-
if @begin_level > 1
|
131
|
-
# start a new region
|
132
|
-
region = Region.new('anonymous', 'BLOCK', scanner.pos)
|
133
|
-
region.body_start_pos = scanner.pos - scanner.matched.length
|
134
|
-
@level += 1
|
135
|
-
assign_parent(@current_parent << Tree::TreeNode.new(region.to_s, region))
|
136
|
-
else
|
137
|
-
if @current_parent && @current_parent.content
|
138
|
-
@current_parent.content.body_start_pos = scanner.pos - scanner.matched.length
|
139
|
-
end
|
140
|
-
end
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
|
-
def register_for_spot
|
145
|
-
@walker.register_spot(FOR_STMT) do |scanner|
|
146
|
-
stmt = "#{scanner.matched}#{scanner.rest}"
|
147
|
-
handler = Parser.describe_for(stmt)
|
148
|
-
if handler[:end_pos] > 0
|
149
|
-
region = Region.new('for', 'FOR_BLOCK', scanner.pos - scanner.matched.length + 1)
|
150
|
-
region.body_start_pos = region.start_pos + handler[:end_pos]
|
151
|
-
region.context = handler
|
152
|
-
assign_parent(@current_parent << Tree::TreeNode.new(region.to_s, region))
|
153
|
-
scanner.pos = region.body_start_pos
|
154
|
-
@level += 1
|
155
|
-
end
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
def register_loop_spot
|
160
|
-
@walker.register_spot(LOOP_STMT) do |scanner|
|
161
|
-
stmt = "#{scanner.matched}#{scanner.rest}"
|
162
|
-
region = Region.new('loop', 'LOOP_BLOCK', scanner.pos - scanner.matched.length + 1)
|
163
|
-
region.body_start_pos = region.start_pos + 1
|
164
|
-
assign_parent(@current_parent << Tree::TreeNode.new(region.to_s, region))
|
165
|
-
@level += 1
|
166
|
-
end
|
167
|
-
end
|
168
|
-
|
169
|
-
def register_if_spot
|
170
|
-
@walker.register_spot(IF_STMT) do |scanner|
|
171
|
-
stmt = "#{scanner.matched}#{scanner.rest}"
|
172
|
-
region = Region.new('if', 'IF_BLOCK', scanner.pos - scanner.matched.length + 1)
|
173
|
-
region.body_start_pos = region.start_pos + 1
|
174
|
-
assign_parent(@current_parent << Tree::TreeNode.new(region.to_s, region))
|
175
|
-
@level += 1
|
176
|
-
end
|
177
|
-
end
|
178
|
-
|
179
|
-
def register_end_spot
|
180
|
-
@walker.register_spot(END_MODULE) do |scanner|
|
181
|
-
# we have an "end" match. first of all check if it's not part
|
182
|
-
# of an conditional compiling "$end" definition
|
183
|
-
char_behind = scanner.string[scanner.pos - scanner.matched.length - 1, 1]
|
184
|
-
if char_behind != '$'
|
185
|
-
metadata = Parser.plsql_def("#{scanner.matched}#{scanner.rest}")
|
186
|
-
if metadata[:end_def] > 0
|
187
|
-
@level -= 1 if @level > 0
|
188
|
-
if metadata[:type] == 'END'
|
189
|
-
@begin_level -= 1 if @begin_level > 0
|
190
|
-
if @current_parent.content
|
191
|
-
@current_parent.content.end_pos = (scanner.pos - 1) + (metadata[:end_def] - 1)
|
192
|
-
end
|
193
|
-
assign_parent(@current_parent.parent)
|
194
|
-
elsif metadata[:type] == 'END_LOOP'
|
195
|
-
if @current_parent.content && (@current_parent.content.type == 'FOR_BLOCK' || @current_parent.content.type == "LOOP_BLOCK")
|
196
|
-
@current_parent.content.end_pos = (scanner.pos - 1) + (metadata[:end_def] - 1)
|
197
|
-
scanner.pos = @current_parent.content.end_pos
|
198
|
-
assign_parent(@current_parent.parent)
|
199
|
-
else
|
200
|
-
# something's fishy
|
201
|
-
scanner.terminate
|
202
|
-
end
|
203
|
-
elsif metadata[:type] == 'END_IF'
|
204
|
-
if @current_parent.content && @current_parent.content.type == 'IF_BLOCK'
|
205
|
-
@current_parent.content.end_pos = (scanner.pos - 1) + (metadata[:end_def] - 1)
|
206
|
-
scanner.pos = @current_parent.content.end_pos
|
207
|
-
assign_parent(@current_parent.parent)
|
208
|
-
else
|
209
|
-
# something's fishy
|
210
|
-
scanner.terminate
|
211
|
-
end
|
212
|
-
end
|
213
|
-
end
|
214
|
-
end
|
215
|
-
end
|
216
|
-
end
|
217
|
-
|
218
|
-
end
|
219
|
-
|
220
|
-
end
|
221
|
-
|
222
|
-
end
|