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