Chris@909
|
1 module CodeRay module Scanners
|
Chris@909
|
2
|
Chris@909
|
3 # by Josh Goebel
|
Chris@909
|
4 class SQL < Scanner
|
Chris@909
|
5
|
Chris@909
|
6 register_for :sql
|
Chris@909
|
7
|
Chris@909
|
8 KEYWORDS = %w(
|
Chris@909
|
9 all and any as before begin between by case check collate
|
Chris@909
|
10 each else end exists
|
Chris@909
|
11 for foreign from full group having if in inner is join
|
Chris@909
|
12 like not of on or order outer over references
|
Chris@909
|
13 then to union using values when where
|
Chris@909
|
14 left right distinct
|
Chris@909
|
15 )
|
Chris@909
|
16
|
Chris@909
|
17 OBJECTS = %w(
|
Chris@909
|
18 database databases table tables column columns fields index constraint
|
Chris@909
|
19 constraints transaction function procedure row key view trigger
|
Chris@909
|
20 )
|
Chris@909
|
21
|
Chris@909
|
22 COMMANDS = %w(
|
Chris@909
|
23 add alter comment create delete drop grant insert into select update set
|
Chris@909
|
24 show prompt begin commit rollback replace truncate
|
Chris@909
|
25 )
|
Chris@909
|
26
|
Chris@909
|
27 PREDEFINED_TYPES = %w(
|
Chris@909
|
28 char varchar varchar2 enum binary text tinytext mediumtext
|
Chris@909
|
29 longtext blob tinyblob mediumblob longblob timestamp
|
Chris@909
|
30 date time datetime year double decimal float int
|
Chris@909
|
31 integer tinyint mediumint bigint smallint unsigned bit
|
Chris@909
|
32 bool boolean hex bin oct
|
Chris@909
|
33 )
|
Chris@909
|
34
|
Chris@909
|
35 PREDEFINED_FUNCTIONS = %w( sum cast substring abs pi count min max avg now )
|
Chris@909
|
36
|
Chris@909
|
37 DIRECTIVES = %w(
|
Chris@909
|
38 auto_increment unique default charset initially deferred
|
Chris@909
|
39 deferrable cascade immediate read write asc desc after
|
Chris@909
|
40 primary foreign return engine
|
Chris@909
|
41 )
|
Chris@909
|
42
|
Chris@909
|
43 PREDEFINED_CONSTANTS = %w( null true false )
|
Chris@909
|
44
|
Chris@909
|
45 IDENT_KIND = WordList::CaseIgnoring.new(:ident).
|
Chris@909
|
46 add(KEYWORDS, :keyword).
|
Chris@909
|
47 add(OBJECTS, :type).
|
Chris@909
|
48 add(COMMANDS, :class).
|
Chris@909
|
49 add(PREDEFINED_TYPES, :predefined_type).
|
Chris@909
|
50 add(PREDEFINED_CONSTANTS, :predefined_constant).
|
Chris@909
|
51 add(PREDEFINED_FUNCTIONS, :predefined).
|
Chris@909
|
52 add(DIRECTIVES, :directive)
|
Chris@909
|
53
|
Chris@909
|
54 ESCAPE = / [rbfntv\n\\\/'"] | x[a-fA-F0-9]{1,2} | [0-7]{1,3} | . /mx
|
Chris@909
|
55 UNICODE_ESCAPE = / u[a-fA-F0-9]{4} | U[a-fA-F0-9]{8} /x
|
Chris@909
|
56
|
Chris@909
|
57 STRING_PREFIXES = /[xnb]|_\w+/i
|
Chris@909
|
58
|
Chris@909
|
59 def scan_tokens encoder, options
|
Chris@909
|
60
|
Chris@909
|
61 state = :initial
|
Chris@909
|
62 string_type = nil
|
Chris@909
|
63 string_content = ''
|
Chris@909
|
64 name_expected = false
|
Chris@909
|
65
|
Chris@909
|
66 until eos?
|
Chris@909
|
67
|
Chris@909
|
68 if state == :initial
|
Chris@909
|
69
|
Chris@909
|
70 if match = scan(/ \s+ | \\\n /x)
|
Chris@909
|
71 encoder.text_token match, :space
|
Chris@909
|
72
|
Chris@909
|
73 elsif match = scan(/(?:--\s?|#).*/)
|
Chris@909
|
74 encoder.text_token match, :comment
|
Chris@909
|
75
|
Chris@909
|
76 elsif match = scan(%r( /\* (!)? (?: .*? \*/ | .* ) )mx)
|
Chris@909
|
77 encoder.text_token match, self[1] ? :directive : :comment
|
Chris@909
|
78
|
Chris@909
|
79 elsif match = scan(/ [*\/=<>:;,!&^|()\[\]{}~%] | [-+\.](?!\d) /x)
|
Chris@909
|
80 name_expected = true if match == '.' && check(/[A-Za-z_]/)
|
Chris@909
|
81 encoder.text_token match, :operator
|
Chris@909
|
82
|
Chris@909
|
83 elsif match = scan(/(#{STRING_PREFIXES})?([`"'])/o)
|
Chris@909
|
84 prefix = self[1]
|
Chris@909
|
85 string_type = self[2]
|
Chris@909
|
86 encoder.begin_group :string
|
Chris@909
|
87 encoder.text_token prefix, :modifier if prefix
|
Chris@909
|
88 match = string_type
|
Chris@909
|
89 state = :string
|
Chris@909
|
90 encoder.text_token match, :delimiter
|
Chris@909
|
91
|
Chris@909
|
92 elsif match = scan(/ @? [A-Za-z_][A-Za-z_0-9]* /x)
|
Chris@909
|
93 encoder.text_token match, name_expected ? :ident : (match[0] == ?@ ? :variable : IDENT_KIND[match])
|
Chris@909
|
94 name_expected = false
|
Chris@909
|
95
|
Chris@909
|
96 elsif match = scan(/0[xX][0-9A-Fa-f]+/)
|
Chris@909
|
97 encoder.text_token match, :hex
|
Chris@909
|
98
|
Chris@909
|
99 elsif match = scan(/0[0-7]+(?![89.eEfF])/)
|
Chris@909
|
100 encoder.text_token match, :octal
|
Chris@909
|
101
|
Chris@909
|
102 elsif match = scan(/[-+]?(?>\d+)(?![.eEfF])/)
|
Chris@909
|
103 encoder.text_token match, :integer
|
Chris@909
|
104
|
Chris@909
|
105 elsif match = scan(/[-+]?(?:\d[fF]|\d*\.\d+(?:[eE][+-]?\d+)?|\d+[eE][+-]?\d+)/)
|
Chris@909
|
106 encoder.text_token match, :float
|
Chris@909
|
107
|
Chris@909
|
108 elsif match = scan(/\\N/)
|
Chris@909
|
109 encoder.text_token match, :predefined_constant
|
Chris@909
|
110
|
Chris@909
|
111 else
|
Chris@909
|
112 encoder.text_token getch, :error
|
Chris@909
|
113
|
Chris@909
|
114 end
|
Chris@909
|
115
|
Chris@909
|
116 elsif state == :string
|
Chris@909
|
117 if match = scan(/[^\\"'`]+/)
|
Chris@909
|
118 string_content << match
|
Chris@909
|
119 next
|
Chris@909
|
120 elsif match = scan(/["'`]/)
|
Chris@909
|
121 if string_type == match
|
Chris@909
|
122 if peek(1) == string_type # doubling means escape
|
Chris@909
|
123 string_content << string_type << getch
|
Chris@909
|
124 next
|
Chris@909
|
125 end
|
Chris@909
|
126 unless string_content.empty?
|
Chris@909
|
127 encoder.text_token string_content, :content
|
Chris@909
|
128 string_content = ''
|
Chris@909
|
129 end
|
Chris@909
|
130 encoder.text_token match, :delimiter
|
Chris@909
|
131 encoder.end_group :string
|
Chris@909
|
132 state = :initial
|
Chris@909
|
133 string_type = nil
|
Chris@909
|
134 else
|
Chris@909
|
135 string_content << match
|
Chris@909
|
136 end
|
Chris@909
|
137 elsif match = scan(/ \\ (?: #{ESCAPE} | #{UNICODE_ESCAPE} ) /mox)
|
Chris@909
|
138 unless string_content.empty?
|
Chris@909
|
139 encoder.text_token string_content, :content
|
Chris@909
|
140 string_content = ''
|
Chris@909
|
141 end
|
Chris@909
|
142 encoder.text_token match, :char
|
Chris@909
|
143 elsif match = scan(/ \\ . /mox)
|
Chris@909
|
144 string_content << match
|
Chris@909
|
145 next
|
Chris@909
|
146 elsif match = scan(/ \\ | $ /x)
|
Chris@909
|
147 unless string_content.empty?
|
Chris@909
|
148 encoder.text_token string_content, :content
|
Chris@909
|
149 string_content = ''
|
Chris@909
|
150 end
|
Chris@909
|
151 encoder.text_token match, :error
|
Chris@909
|
152 state = :initial
|
Chris@909
|
153 else
|
Chris@909
|
154 raise "else case \" reached; %p not handled." % peek(1), encoder
|
Chris@909
|
155 end
|
Chris@909
|
156
|
Chris@909
|
157 else
|
Chris@909
|
158 raise 'else-case reached', encoder
|
Chris@909
|
159
|
Chris@909
|
160 end
|
Chris@909
|
161
|
Chris@909
|
162 end
|
Chris@909
|
163
|
Chris@909
|
164 if state == :string
|
Chris@909
|
165 encoder.end_group state
|
Chris@909
|
166 end
|
Chris@909
|
167
|
Chris@909
|
168 encoder
|
Chris@909
|
169
|
Chris@909
|
170 end
|
Chris@909
|
171
|
Chris@909
|
172 end
|
Chris@909
|
173
|
Chris@909
|
174 end end |