plume 0.1.0 → 0.2.0
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.
- checksums.yaml +4 -4
- data/README.md +280 -10
- data/lib/plume/analysis/resolver.rb +574 -0
- data/lib/plume/analysis.rb +135 -0
- data/lib/plume/ast/alter_table_statement.rb +29 -0
- data/lib/plume/ast/analyze_statement.rb +15 -0
- data/lib/plume/ast/assignment.rb +18 -0
- data/lib/plume/ast/attach_statement.rb +17 -0
- data/lib/plume/ast/begin_statement.rb +17 -0
- data/lib/plume/ast/binary_expression.rb +71 -0
- data/lib/plume/ast/case_condition.rb +29 -0
- data/lib/plume/ast/case_expression.rb +22 -0
- data/lib/plume/ast/cast_expression.rb +16 -0
- data/lib/plume/ast/check_column_constraint.rb +20 -0
- data/lib/plume/ast/check_table_constraint.rb +19 -0
- data/lib/plume/ast/collate_column_constraint.rb +19 -0
- data/lib/plume/ast/collation_expression.rb +26 -0
- data/lib/plume/ast/column_constraint.rb +7 -0
- data/lib/plume/ast/column_definition.rb +16 -0
- data/lib/plume/ast/column_name.rb +22 -0
- data/lib/plume/ast/column_type.rb +25 -0
- data/lib/plume/ast/commit_statement.rb +13 -0
- data/lib/plume/ast/common_table_expression.rb +31 -0
- data/lib/plume/ast/compound_select.rb +30 -0
- data/lib/plume/ast/conflict_clause.rb +22 -0
- data/lib/plume/ast/conflict_resolution.rb +20 -0
- data/lib/plume/ast/create_index_statement.rb +29 -0
- data/lib/plume/ast/create_table_statement.rb +47 -0
- data/lib/plume/ast/create_trigger_statement.rb +43 -0
- data/lib/plume/ast/create_view_statement.rb +30 -0
- data/lib/plume/ast/create_virtual_table_statement.rb +28 -0
- data/lib/plume/ast/default_column_constraint.rb +29 -0
- data/lib/plume/ast/delete_statement.rb +28 -0
- data/lib/plume/ast/detach_statement.rb +13 -0
- data/lib/plume/ast/drop_statement.rb +21 -0
- data/lib/plume/ast/empty_function_argument.rb +6 -0
- data/lib/plume/ast/exists_expression.rb +15 -0
- data/lib/plume/ast/explain_statement.rb +15 -0
- data/lib/plume/ast/expression.rb +30 -0
- data/lib/plume/ast/foreign_key_action.rb +26 -0
- data/lib/plume/ast/foreign_key_clause.rb +50 -0
- data/lib/plume/ast/foreign_key_column_constraint.rb +16 -0
- data/lib/plume/ast/foreign_key_table_constraint.rb +20 -0
- data/lib/plume/ast/frame_boundary.rb +35 -0
- data/lib/plume/ast/frame_spec.rb +34 -0
- data/lib/plume/ast/function_arguments.rb +17 -0
- data/lib/plume/ast/function_reference.rb +29 -0
- data/lib/plume/ast/generated_as_column_constraint.rb +24 -0
- data/lib/plume/ast/indexed_column.rb +21 -0
- data/lib/plume/ast/insert_statement.rb +35 -0
- data/lib/plume/ast/join.rb +12 -0
- data/lib/plume/ast/join_clause.rb +15 -0
- data/lib/plume/ast/join_on_constraint.rb +12 -0
- data/lib/plume/ast/join_operator.rb +31 -0
- data/lib/plume/ast/join_using_constraint.rb +16 -0
- data/lib/plume/ast/like_expression.rb +19 -0
- data/lib/plume/ast/literal_expression.rb +29 -0
- data/lib/plume/ast/literal_false.rb +7 -0
- data/lib/plume/ast/literal_nil.rb +7 -0
- data/lib/plume/ast/literal_value.rb +18 -0
- data/lib/plume/ast/match_clause.rb +15 -0
- data/lib/plume/ast/node.rb +207 -0
- data/lib/plume/ast/noop_column_constraint.rb +15 -0
- data/lib/plume/ast/noop_table_constraint.rb +15 -0
- data/lib/plume/ast/not_like_expression.rb +7 -0
- data/lib/plume/ast/not_null_column_constraint.rb +17 -0
- data/lib/plume/ast/null_column_constraint.rb +17 -0
- data/lib/plume/ast/ordering_term.rb +32 -0
- data/lib/plume/ast/over_clause.rb +25 -0
- data/lib/plume/ast/parenthetical_expression.rb +37 -0
- data/lib/plume/ast/parenthetical_source.rb +19 -0
- data/lib/plume/ast/pragma_statement.rb +26 -0
- data/lib/plume/ast/primary_key_column_constraint.rb +23 -0
- data/lib/plume/ast/primary_key_table_constraint.rb +23 -0
- data/lib/plume/ast/raise_expression.rb +23 -0
- data/lib/plume/ast/reindex_statement.rb +15 -0
- data/lib/plume/ast/release_statement.rb +16 -0
- data/lib/plume/ast/result_column.rb +18 -0
- data/lib/plume/ast/returning_clause.rb +12 -0
- data/lib/plume/ast/rollback_statement.rb +19 -0
- data/lib/plume/ast/savepoint_statement.rb +16 -0
- data/lib/plume/ast/select_core.rb +27 -0
- data/lib/plume/ast/select_statement.rb +34 -0
- data/lib/plume/ast/star_function_argument.rb +6 -0
- data/lib/plume/ast/star_result_column.rb +16 -0
- data/lib/plume/ast/strict_table_option.rb +6 -0
- data/lib/plume/ast/subquery_expression.rb +14 -0
- data/lib/plume/ast/subquery_source.rb +19 -0
- data/lib/plume/ast/table_constraint.rb +7 -0
- data/lib/plume/ast/table_function.rb +28 -0
- data/lib/plume/ast/table_name.rb +28 -0
- data/lib/plume/ast/table_option.rb +7 -0
- data/lib/plume/ast/ternary_expression.rb +26 -0
- data/lib/plume/ast/token.rb +180 -0
- data/lib/plume/ast/unary_expression.rb +36 -0
- data/lib/plume/ast/unique_column_constraint.rb +17 -0
- data/lib/plume/ast/unique_table_constraint.rb +20 -0
- data/lib/plume/ast/update_statement.rb +35 -0
- data/lib/plume/ast/upsert_clause.rb +26 -0
- data/lib/plume/ast/vacuum_statement.rb +18 -0
- data/lib/plume/ast/values_clause.rb +12 -0
- data/lib/plume/ast/values_row.rb +13 -0
- data/lib/plume/ast/variable.rb +15 -0
- data/lib/plume/ast/window_definition.rb +26 -0
- data/lib/plume/ast/with_clause.rb +15 -0
- data/lib/plume/ast/without_rowid_table_option.rb +6 -0
- data/lib/plume/catalog.rb +180 -0
- data/lib/plume/lexer.rb +639 -0
- data/lib/plume/parser.rb +4015 -0
- data/lib/plume/printer.rb +471 -0
- data/lib/plume/sqlite_grammar.rb +86 -0
- data/lib/plume/union.rb +28 -0
- data/lib/plume/version.rb +1 -1
- data/lib/plume.rb +143 -3
- metadata +167 -12
data/lib/plume/lexer.rb
ADDED
|
@@ -0,0 +1,639 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Plume
|
|
4
|
+
# Map ASCII strings to an enumerator of token symbols.
|
|
5
|
+
# adapted from: https://github.com/sqlite/sqlite/blob/master/src/tokenize.c
|
|
6
|
+
class Lexer
|
|
7
|
+
# Character classes for tokenizing
|
|
8
|
+
# adapted from: https://github.com/sqlite/sqlite/blob/master/src/tokenize.c
|
|
9
|
+
TYPES = [
|
|
10
|
+
:X, # The letter 'x', or start of BLOB literal
|
|
11
|
+
:KYWD0, # First letter of a keyword
|
|
12
|
+
:KYWD, # Alphabetics or '_'. Usable in a keyword
|
|
13
|
+
:DIGIT, # Digits
|
|
14
|
+
:DOLLAR, # '$'
|
|
15
|
+
:VARALPHA, # '@', '#', ':'. Alphabetic SQL variables
|
|
16
|
+
:VARNUM, # '?'. Numeric SQL variables
|
|
17
|
+
:SPACE, # Space characters
|
|
18
|
+
:QUOTE, # '"', '\'', or '`'. String literals, quoted ids
|
|
19
|
+
:QUOTE2, # '['. [...] style quoted ids
|
|
20
|
+
:PIPE, # '|'. Bitwise OR or concatenate
|
|
21
|
+
:MINUS, # '-'. Minus or SQL-style comment
|
|
22
|
+
:LT, # '<'. Part of < or <= or <>
|
|
23
|
+
:GT, # '>'. Part of > or >=
|
|
24
|
+
:EQ, # '='. Part of = or ==
|
|
25
|
+
:BANG, # '!'. Part of !=
|
|
26
|
+
:SLASH, # '/'. / or c-style comment
|
|
27
|
+
:LP, # '('
|
|
28
|
+
:RP, # ')'
|
|
29
|
+
:SEMI, # ';'
|
|
30
|
+
:PLUS, # '+'
|
|
31
|
+
:STAR, # '*'
|
|
32
|
+
:PERCENT, # '%'
|
|
33
|
+
:COMMA, # ','
|
|
34
|
+
:AND, # '&'
|
|
35
|
+
:TILDA, # '~'
|
|
36
|
+
:DOT, # '.'
|
|
37
|
+
:ID, # unicode characters usable in IDs
|
|
38
|
+
:ILLEGAL, # Illegal character
|
|
39
|
+
:NUL, # 0x00
|
|
40
|
+
:BOM, # First byte of UTF8 BOM: 0xEF 0xBB 0xBF
|
|
41
|
+
].freeze
|
|
42
|
+
|
|
43
|
+
# All the keywords of the SQL language in SQLite's dialect
|
|
44
|
+
# adapted from: https://github.com/sqlite/sqlite/blob/master/tool/mkkeywordhash.c
|
|
45
|
+
KEYWORDS = Set[
|
|
46
|
+
:ABORT,
|
|
47
|
+
:ACTION,
|
|
48
|
+
:ADD,
|
|
49
|
+
:AFTER,
|
|
50
|
+
:ALL,
|
|
51
|
+
:ALTER,
|
|
52
|
+
:ALWAYS,
|
|
53
|
+
:ANALYZE,
|
|
54
|
+
:AND,
|
|
55
|
+
:AS,
|
|
56
|
+
:ASC,
|
|
57
|
+
:ATTACH,
|
|
58
|
+
:AUTOINCREMENT,
|
|
59
|
+
:BEFORE,
|
|
60
|
+
:BEGIN,
|
|
61
|
+
:BETWEEN,
|
|
62
|
+
:BY,
|
|
63
|
+
:CASCADE,
|
|
64
|
+
:CASE,
|
|
65
|
+
:CAST,
|
|
66
|
+
:CHECK,
|
|
67
|
+
:COLLATE,
|
|
68
|
+
:COLUMN,
|
|
69
|
+
:COMMIT,
|
|
70
|
+
:CONFLICT,
|
|
71
|
+
:CONSTRAINT,
|
|
72
|
+
:CREATE,
|
|
73
|
+
:CROSS,
|
|
74
|
+
:CURRENT,
|
|
75
|
+
:CURRENT_DATE,
|
|
76
|
+
:CURRENT_TIME,
|
|
77
|
+
:CURRENT_TIMESTAMP,
|
|
78
|
+
:DATABASE,
|
|
79
|
+
:DEFAULT,
|
|
80
|
+
:DEFERRED,
|
|
81
|
+
:DEFERRABLE,
|
|
82
|
+
:DELETE,
|
|
83
|
+
:DESC,
|
|
84
|
+
:DETACH,
|
|
85
|
+
:DISTINCT,
|
|
86
|
+
:DO,
|
|
87
|
+
:DROP,
|
|
88
|
+
:END,
|
|
89
|
+
:EACH,
|
|
90
|
+
:ELSE,
|
|
91
|
+
:ESCAPE,
|
|
92
|
+
:EXCEPT,
|
|
93
|
+
:EXCLUSIVE,
|
|
94
|
+
:EXCLUDE,
|
|
95
|
+
:EXISTS,
|
|
96
|
+
:EXPLAIN,
|
|
97
|
+
:FAIL,
|
|
98
|
+
:FILTER,
|
|
99
|
+
:FIRST,
|
|
100
|
+
:FOLLOWING,
|
|
101
|
+
:FOR,
|
|
102
|
+
:FOREIGN,
|
|
103
|
+
:FROM,
|
|
104
|
+
:FULL,
|
|
105
|
+
:GENERATED,
|
|
106
|
+
:GLOB,
|
|
107
|
+
:GROUP,
|
|
108
|
+
:GROUPS,
|
|
109
|
+
:HAVING,
|
|
110
|
+
:IF,
|
|
111
|
+
:IGNORE,
|
|
112
|
+
:IMMEDIATE,
|
|
113
|
+
:IN,
|
|
114
|
+
:INDEX,
|
|
115
|
+
:INDEXED,
|
|
116
|
+
:INITIALLY,
|
|
117
|
+
:INNER,
|
|
118
|
+
:INSERT,
|
|
119
|
+
:INSTEAD,
|
|
120
|
+
:INTERSECT,
|
|
121
|
+
:INTO,
|
|
122
|
+
:IS,
|
|
123
|
+
:ISNULL,
|
|
124
|
+
:JOIN,
|
|
125
|
+
:KEY,
|
|
126
|
+
:LAST,
|
|
127
|
+
:LEFT,
|
|
128
|
+
:LIKE,
|
|
129
|
+
:LIMIT,
|
|
130
|
+
:MATCH,
|
|
131
|
+
:MATERIALIZED,
|
|
132
|
+
:NATURAL,
|
|
133
|
+
:NO,
|
|
134
|
+
:NOT,
|
|
135
|
+
:NOTHING,
|
|
136
|
+
:NOTNULL,
|
|
137
|
+
:NULL,
|
|
138
|
+
:NULLS,
|
|
139
|
+
:OF,
|
|
140
|
+
:OFFSET,
|
|
141
|
+
:ON,
|
|
142
|
+
:OR,
|
|
143
|
+
:ORDER,
|
|
144
|
+
:OTHERS,
|
|
145
|
+
:OUTER,
|
|
146
|
+
:OVER,
|
|
147
|
+
:PARTITION,
|
|
148
|
+
:PLAN,
|
|
149
|
+
:PRAGMA,
|
|
150
|
+
:PRECEDING,
|
|
151
|
+
:PRIMARY,
|
|
152
|
+
:QUERY,
|
|
153
|
+
:RAISE,
|
|
154
|
+
:RANGE,
|
|
155
|
+
:RECURSIVE,
|
|
156
|
+
:REFERENCES,
|
|
157
|
+
:REGEXP,
|
|
158
|
+
:REINDEX,
|
|
159
|
+
:RELEASE,
|
|
160
|
+
:RENAME,
|
|
161
|
+
:REPLACE,
|
|
162
|
+
:RESTRICT,
|
|
163
|
+
:RETURNING,
|
|
164
|
+
:RIGHT,
|
|
165
|
+
:ROLLBACK,
|
|
166
|
+
:ROW,
|
|
167
|
+
:ROWID,
|
|
168
|
+
:ROWS,
|
|
169
|
+
:SAVEPOINT,
|
|
170
|
+
:SELECT,
|
|
171
|
+
:SET,
|
|
172
|
+
:STORED,
|
|
173
|
+
:STRICT,
|
|
174
|
+
:TABLE,
|
|
175
|
+
:TEMP,
|
|
176
|
+
:TEMPORARY,
|
|
177
|
+
:THEN,
|
|
178
|
+
:TIES,
|
|
179
|
+
:TO,
|
|
180
|
+
:TRANSACTION,
|
|
181
|
+
:TRIGGER,
|
|
182
|
+
:UNBOUNDED,
|
|
183
|
+
:UNION,
|
|
184
|
+
:UNIQUE,
|
|
185
|
+
:UPDATE,
|
|
186
|
+
:USING,
|
|
187
|
+
:VACUUM,
|
|
188
|
+
:VALUES,
|
|
189
|
+
:VIEW,
|
|
190
|
+
:VIRTUAL,
|
|
191
|
+
:WHEN,
|
|
192
|
+
:WHERE,
|
|
193
|
+
:WINDOW,
|
|
194
|
+
:WITH,
|
|
195
|
+
:WITHIN,
|
|
196
|
+
:WITHOUT,
|
|
197
|
+
:TRUE,
|
|
198
|
+
:FALSE,
|
|
199
|
+
].freeze
|
|
200
|
+
|
|
201
|
+
# Maps ASCII characters to their respective character types
|
|
202
|
+
# Each element's index maps to the byte value of that character
|
|
203
|
+
CHARACTER_TYPES = %i[
|
|
204
|
+
NULL ILLEGAL ILLEGAL ILLEGAL ILLEGAL ILLEGAL ILLEGAL ILLEGAL
|
|
205
|
+
ILLEGAL SPACE SPACE ILLEGAL SPACE SPACE ILLEGAL ILLEGAL
|
|
206
|
+
ILLEGAL ILLEGAL ILLEGAL ILLEGAL ILLEGAL ILLEGAL ILLEGAL ILLEGAL
|
|
207
|
+
ILLEGAL ILLEGAL ILLEGAL ILLEGAL ILLEGAL ILLEGAL ILLEGAL ILLEGAL
|
|
208
|
+
SPACE BANG QUOTE VARALPHA DOLLAR PERCENT AND QUOTE
|
|
209
|
+
LP RP STAR PLUS COMMA MINUS DOT SLASH
|
|
210
|
+
DIGIT DIGIT DIGIT DIGIT DIGIT DIGIT DIGIT DIGIT
|
|
211
|
+
DIGIT DIGIT VARALPHA SEMI LT EQ GT VARNUM
|
|
212
|
+
VARALPHA KYWD0 KYWD0 KYWD0 KYWD0 KYWD0 KYWD0 KYWD0
|
|
213
|
+
KYWD0 KYWD0 KYWD0 KYWD0 KYWD0 KYWD0 KYWD0 KYWD0
|
|
214
|
+
KYWD0 KYWD0 KYWD0 KYWD0 KYWD0 KYWD0 KYWD0 KYWD0
|
|
215
|
+
X KYWD KYWD QUOTE2 ILLEGAL ILLEGAL ILLEGAL KYWD
|
|
216
|
+
QUOTE KYWD0 KYWD0 KYWD0 KYWD0 KYWD0 KYWD0 KYWD0
|
|
217
|
+
KYWD0 KYWD0 KYWD0 KYWD0 KYWD0 KYWD0 KYWD0 KYWD0
|
|
218
|
+
KYWD0 KYWD0 KYWD0 KYWD0 KYWD0 KYWD0 KYWD0 KYWD0
|
|
219
|
+
X KYWD KYWD ILLEGAL PIPE ILLEGAL TILDA ILLEGAL
|
|
220
|
+
ID ID ID ID ID ID ID ID
|
|
221
|
+
ID ID ID ID ID ID ID ID
|
|
222
|
+
ID ID ID ID ID ID ID ID
|
|
223
|
+
ID ID ID ID ID ID ID ID
|
|
224
|
+
ID ID ID ID ID ID ID ID
|
|
225
|
+
ID ID ID ID ID ID ID ID
|
|
226
|
+
ID ID ID ID ID ID ID ID
|
|
227
|
+
ID ID ID ID ID ID ID ID
|
|
228
|
+
ID ID ID ID ID ID ID ID
|
|
229
|
+
ID ID ID ID ID ID ID ID
|
|
230
|
+
ID ID ID ID ID ID ID ID
|
|
231
|
+
ID ID ID ID ID ID ID ID
|
|
232
|
+
ID ID ID ID ID ID ID ID
|
|
233
|
+
ID ID ID ID ID ID ID BOM
|
|
234
|
+
ID ID ID ID ID ID ID ID
|
|
235
|
+
ID ID ID ID ID ID ID ID
|
|
236
|
+
].freeze
|
|
237
|
+
|
|
238
|
+
# The following 256 element lookup table encodes the characteristics
|
|
239
|
+
# of each ASCII character in a bitmask:
|
|
240
|
+
#
|
|
241
|
+
# space? 0x01
|
|
242
|
+
# alpha? 0x02
|
|
243
|
+
# digit? 0x04
|
|
244
|
+
# alnum? 0x06
|
|
245
|
+
# xdigit? 0x08
|
|
246
|
+
# to_upper 0x20
|
|
247
|
+
# id? 0x40
|
|
248
|
+
# quote? 0x80
|
|
249
|
+
#
|
|
250
|
+
# Bit 0x40 is set if the character is non-alphanumeric and can be used in an
|
|
251
|
+
# SQLite identifier. Identifiers are alphanumerics, "_", "$", and any
|
|
252
|
+
# non-ASCII UTF character. Hence the test for whether or not a character is
|
|
253
|
+
# part of an identifier is 0x46.
|
|
254
|
+
CHARACTER_FLAGS = [
|
|
255
|
+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # 00..07 ........
|
|
256
|
+
0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, # 08..0f ........
|
|
257
|
+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # 10..17 ........
|
|
258
|
+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # 18..1f ........
|
|
259
|
+
0x01, 0x00, 0x80, 0x00, 0x40, 0x00, 0x00, 0x80, # 20..27 !"#$%&'
|
|
260
|
+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # 28..2f ()*+,-./
|
|
261
|
+
0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, # 30..37 01234567
|
|
262
|
+
0x0c, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # 38..3f 89:;<=>?
|
|
263
|
+
0x00, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x02, # 40..47 @ABCDEFG
|
|
264
|
+
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # 48..4f HIJKLMNO
|
|
265
|
+
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, # 50..57 PQRSTUVW
|
|
266
|
+
0x02, 0x02, 0x02, 0x80, 0x00, 0x00, 0x00, 0x40, # 58..5f XYZ[\]^_
|
|
267
|
+
0x80, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x22, # 60..67 `abcdefg
|
|
268
|
+
0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, # 68..6f hijklmno
|
|
269
|
+
0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, # 70..77 pqrstuvw
|
|
270
|
+
0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, # 78..7f xyz{|}~.
|
|
271
|
+
0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, # 80..87 ........
|
|
272
|
+
0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, # 88..8f ........
|
|
273
|
+
0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, # 90..97 ........
|
|
274
|
+
0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, # 98..9f ........
|
|
275
|
+
0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, # a0..a7 ........
|
|
276
|
+
0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, # a8..af ........
|
|
277
|
+
0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, # b0..b7 ........
|
|
278
|
+
0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, # b8..bf ........
|
|
279
|
+
0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, # c0..c7 ........
|
|
280
|
+
0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, # c8..cf ........
|
|
281
|
+
0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, # d0..d7 ........
|
|
282
|
+
0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, # d8..df ........
|
|
283
|
+
0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, # e0..e7 ........
|
|
284
|
+
0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, # e8..ef ........
|
|
285
|
+
0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, # f0..f7 ........
|
|
286
|
+
0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40 # f8..ff ........
|
|
287
|
+
].freeze
|
|
288
|
+
|
|
289
|
+
SINGLE_CHAR_TOKENS = [].tap do |it|
|
|
290
|
+
it[0] = :ILLEGAL # \0
|
|
291
|
+
it[37] = :REM # %
|
|
292
|
+
it[38] = :BITAND # &
|
|
293
|
+
it[40] = :LP # (
|
|
294
|
+
it[41] = :RP # )
|
|
295
|
+
it[42] = :STAR # *
|
|
296
|
+
it[43] = :PLUS # +
|
|
297
|
+
it[44] = :COMMA # ,
|
|
298
|
+
it[59] = :SEMI # ;
|
|
299
|
+
it[126] = :BITNOT # ~
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
attr_reader :sql
|
|
303
|
+
attr_accessor :cursor, :anchor
|
|
304
|
+
|
|
305
|
+
def initialize(sql, skip_spaces: false)
|
|
306
|
+
# Freezing the caller's string would leave them with a frozen string they did not ask for
|
|
307
|
+
@sql =
|
|
308
|
+
case sql.encoding
|
|
309
|
+
when Encoding::UTF_8, Encoding::ASCII_8BIT then sql.frozen? ? sql : sql.dup.freeze
|
|
310
|
+
else sql.encode(Encoding::UTF_8).freeze
|
|
311
|
+
end
|
|
312
|
+
@skip_spaces = skip_spaces
|
|
313
|
+
@cursor, @anchor = 0
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def next_token
|
|
317
|
+
@anchor = @cursor
|
|
318
|
+
token = lex_next_token
|
|
319
|
+
return next_token if token == :SPACE && @skip_spaces
|
|
320
|
+
token
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def value
|
|
324
|
+
@sql.byteslice(@anchor, (@cursor - @anchor))
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
private
|
|
328
|
+
|
|
329
|
+
def lex_next_token
|
|
330
|
+
current_byte = scan
|
|
331
|
+
|
|
332
|
+
return nil if current_byte.nil?
|
|
333
|
+
if (token = SINGLE_CHAR_TOKENS[current_byte])
|
|
334
|
+
return token
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
character_type = CHARACTER_TYPES[current_byte]
|
|
338
|
+
case character_type
|
|
339
|
+
when :SPACE
|
|
340
|
+
# useless branch conditions to drive branch test coverage results
|
|
341
|
+
nil if current_byte == 32 # " "
|
|
342
|
+
nil if current_byte == 9 # "\t"
|
|
343
|
+
nil if current_byte == 10 # "\n"
|
|
344
|
+
nil if current_byte == 12 # "\f"
|
|
345
|
+
nil if current_byte == 13 # "\r"
|
|
346
|
+
|
|
347
|
+
step until (b = peek).nil? || !space?(b)
|
|
348
|
+
|
|
349
|
+
:SPACE
|
|
350
|
+
when :MINUS
|
|
351
|
+
if peek == 45 # "-"
|
|
352
|
+
step
|
|
353
|
+
step until (b = peek).nil? || b == 10 # "\n"
|
|
354
|
+
|
|
355
|
+
:SPACE # IMP: R-22934-2513
|
|
356
|
+
elsif peek == 62 # ">"
|
|
357
|
+
# differentiate between different pointers
|
|
358
|
+
if peek(1) == 62 # ">"
|
|
359
|
+
step(2)
|
|
360
|
+
:PTR2
|
|
361
|
+
else
|
|
362
|
+
step
|
|
363
|
+
:PTR1
|
|
364
|
+
end
|
|
365
|
+
else
|
|
366
|
+
:MINUS
|
|
367
|
+
end
|
|
368
|
+
when :SLASH
|
|
369
|
+
return :SLASH if peek.nil? # EOF
|
|
370
|
+
return :SLASH if peek != 42 # "*"
|
|
371
|
+
|
|
372
|
+
step # we know it's a `/*` comment so consume the "*"
|
|
373
|
+
step until (b = peek).nil? || (b == 42 && peek(1) == 47) # consume until EOF or "*/"
|
|
374
|
+
step(2) if peek == 42 && peek(1) == 47 # consume the "*/"
|
|
375
|
+
|
|
376
|
+
:SPACE # comments are encoded as SPACE tokens [IMP: R-22934-2513]
|
|
377
|
+
when :EQ
|
|
378
|
+
step if peek == 61 # "="
|
|
379
|
+
:EQ
|
|
380
|
+
when :LT
|
|
381
|
+
return step && :LE if peek == 61 # "="
|
|
382
|
+
return step && :NE if peek == 62 # ">"
|
|
383
|
+
return step && :LSHIFT if peek == 60 # "<"
|
|
384
|
+
|
|
385
|
+
:LT
|
|
386
|
+
when :GT
|
|
387
|
+
return step && :GE if peek == 61 # "="
|
|
388
|
+
return step && :RSHIFT if peek == 62 # ">"
|
|
389
|
+
|
|
390
|
+
:GT
|
|
391
|
+
when :BANG
|
|
392
|
+
return step && :NE if peek == 61 # "="
|
|
393
|
+
|
|
394
|
+
:ILLEGAL
|
|
395
|
+
when :PIPE
|
|
396
|
+
return step && :CONCAT if peek == 124 # "|"
|
|
397
|
+
|
|
398
|
+
:BITOR
|
|
399
|
+
when :QUOTE
|
|
400
|
+
# useless branch conditions to drive brach test coverage results
|
|
401
|
+
nil if current_byte == 96 # "`"
|
|
402
|
+
nil if current_byte == 39 # "'"
|
|
403
|
+
nil if current_byte == 34 # '"'
|
|
404
|
+
|
|
405
|
+
while (b = scan)
|
|
406
|
+
next unless b == current_byte
|
|
407
|
+
# you can use the same quote to escape itself
|
|
408
|
+
(peek == current_byte) ? step : break
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
return :STRING if b == 39 # "'"
|
|
412
|
+
return :ILLEGAL if b.nil?
|
|
413
|
+
|
|
414
|
+
:ID
|
|
415
|
+
when :DOT, :DIGIT
|
|
416
|
+
# If the current character is a "." and the next character is either EOF or not a digit,
|
|
417
|
+
# then this token is simply the "." character.
|
|
418
|
+
if character_type == :DOT && ((b = peek).nil? || !digit?(b))
|
|
419
|
+
return :DOT
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
# useless branch conditions to drive brach test coverage results
|
|
423
|
+
nil if current_byte == 46 # "."
|
|
424
|
+
nil if current_byte == 48 # "0"
|
|
425
|
+
nil if current_byte == 49 # "1"
|
|
426
|
+
nil if current_byte == 50 # "2"
|
|
427
|
+
nil if current_byte == 51 # "3"
|
|
428
|
+
nil if current_byte == 52 # "4"
|
|
429
|
+
nil if current_byte == 53 # "5"
|
|
430
|
+
nil if current_byte == 54 # "6"
|
|
431
|
+
nil if current_byte == 55 # "7"
|
|
432
|
+
nil if current_byte == 56 # "8"
|
|
433
|
+
nil if current_byte == 57 # "9"
|
|
434
|
+
|
|
435
|
+
token_type = (character_type == :DOT) ? :FLOAT : :INTEGER
|
|
436
|
+
if current_byte == 48 && ((b = peek) == 120 || b == 88) && xdigit?(peek(1)) # "0" and "x" or "X"
|
|
437
|
+
step(2) # consume the "x" or "X" and the first hex digit
|
|
438
|
+
while (b = peek)
|
|
439
|
+
if xdigit?(b)
|
|
440
|
+
step && next
|
|
441
|
+
elsif b == 95 # "_"
|
|
442
|
+
token_type = :QNUMBER
|
|
443
|
+
step && next
|
|
444
|
+
else
|
|
445
|
+
break
|
|
446
|
+
end
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
return token_type
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
while (b = peek)
|
|
453
|
+
if digit?(b)
|
|
454
|
+
step && next
|
|
455
|
+
elsif b == 95 # "_"
|
|
456
|
+
token_type = :QNUMBER
|
|
457
|
+
step && next
|
|
458
|
+
else
|
|
459
|
+
break
|
|
460
|
+
end
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
if peek == 46 # "."
|
|
464
|
+
step
|
|
465
|
+
token_type = :FLOAT if token_type == :INTEGER
|
|
466
|
+
|
|
467
|
+
while (b = peek)
|
|
468
|
+
if digit?(b)
|
|
469
|
+
step && next
|
|
470
|
+
elsif b == 95 # "_"
|
|
471
|
+
token_type = :QNUMBER
|
|
472
|
+
step && next
|
|
473
|
+
else
|
|
474
|
+
break
|
|
475
|
+
end
|
|
476
|
+
end
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
if (peek == 101 || peek == 69) && ( # "e" or "E"
|
|
480
|
+
digit?(peek(1)) || (
|
|
481
|
+
(peek(1) == 43 || peek(1) == 45) && digit?(peek(2)) # "+" or "-"
|
|
482
|
+
)
|
|
483
|
+
)
|
|
484
|
+
step(2)
|
|
485
|
+
token_type = :FLOAT if token_type == :INTEGER
|
|
486
|
+
|
|
487
|
+
while (b = peek)
|
|
488
|
+
if digit?(b)
|
|
489
|
+
step && next
|
|
490
|
+
elsif b == 95 # "_"
|
|
491
|
+
token_type = :QNUMBER
|
|
492
|
+
step && next
|
|
493
|
+
else
|
|
494
|
+
break
|
|
495
|
+
end
|
|
496
|
+
end
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
while id?(peek)
|
|
500
|
+
token_type = :ILLEGAL
|
|
501
|
+
step
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
token_type
|
|
505
|
+
when :QUOTE2
|
|
506
|
+
step until (b = peek).nil? || b == 93 # "]"
|
|
507
|
+
|
|
508
|
+
final = scan
|
|
509
|
+
return :ILLEGAL if final.nil?
|
|
510
|
+
|
|
511
|
+
:ID
|
|
512
|
+
when :VARNUM
|
|
513
|
+
step while digit?(peek)
|
|
514
|
+
|
|
515
|
+
:VARIABLE
|
|
516
|
+
when :DOLLAR, :VARALPHA
|
|
517
|
+
# useless branch conditions to drive brach test coverage results
|
|
518
|
+
nil if current_byte == 35 # "#"
|
|
519
|
+
nil if current_byte == 36 # "$"
|
|
520
|
+
nil if current_byte == 58 # ":"
|
|
521
|
+
nil if current_byte == 64 # "@"
|
|
522
|
+
|
|
523
|
+
n = 0
|
|
524
|
+
token_type = :VARIABLE
|
|
525
|
+
until (b = peek).nil?
|
|
526
|
+
if id?(b)
|
|
527
|
+
n += 1
|
|
528
|
+
elsif n > 0 && b == 40 # "("
|
|
529
|
+
step
|
|
530
|
+
step until (b = peek).nil? || space?(b) || b == 41 # ")"
|
|
531
|
+
|
|
532
|
+
if peek == 41 # ")"
|
|
533
|
+
step
|
|
534
|
+
else
|
|
535
|
+
token_type = :ILLEGAL
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
break
|
|
539
|
+
elsif b == 58 && peek(1) == 58 # ":"
|
|
540
|
+
step
|
|
541
|
+
else
|
|
542
|
+
break
|
|
543
|
+
end
|
|
544
|
+
step
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
if n == 0
|
|
548
|
+
token_type = :ILLEGAL
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
token_type
|
|
552
|
+
when :KYWD0
|
|
553
|
+
# If the next character is not a keyword character, consume all the id characters and return it as an id
|
|
554
|
+
kywd = TYPES.index(:KYWD)
|
|
555
|
+
if (b = peek) && (type = CHARACTER_TYPES[b]) && TYPES.index(type) > kywd
|
|
556
|
+
step while id?(peek)
|
|
557
|
+
|
|
558
|
+
return :ID
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
# Consume all the keyword characters
|
|
562
|
+
step while (b = peek) && (type = CHARACTER_TYPES[b]) && TYPES.index(type) <= kywd
|
|
563
|
+
|
|
564
|
+
# This token started out using characters that can appear in keywords,
|
|
565
|
+
# but now there is a character not allowed within keywords,
|
|
566
|
+
# so this must be an identifier instead
|
|
567
|
+
if (b = peek) && id?(b)
|
|
568
|
+
step while id?(peek)
|
|
569
|
+
|
|
570
|
+
return :ID
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
# Check if the token is a keyword
|
|
574
|
+
token = value.to_sym.upcase
|
|
575
|
+
KEYWORDS.include?(token) ? token : :ID
|
|
576
|
+
when :X
|
|
577
|
+
# useless branch conditions to drive brach test coverage results
|
|
578
|
+
nil if current_byte == 120 # "x"
|
|
579
|
+
nil if current_byte == 88 # "X"
|
|
580
|
+
|
|
581
|
+
if peek == 39 # "'"
|
|
582
|
+
token_type = :BLOB
|
|
583
|
+
step
|
|
584
|
+
|
|
585
|
+
step while xdigit?(peek)
|
|
586
|
+
|
|
587
|
+
if peek != 39 || ((@cursor - @anchor) % 2) != 0
|
|
588
|
+
token_type = :ILLEGAL
|
|
589
|
+
step until (b = peek).nil? || b == 39 # "'"
|
|
590
|
+
end
|
|
591
|
+
|
|
592
|
+
step if !peek.nil?
|
|
593
|
+
|
|
594
|
+
return token_type
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
# If it is not a BLOB literal, then it must be an ID, since no
|
|
598
|
+
# SQL keywords start with the letter 'x'.
|
|
599
|
+
step while id?(peek)
|
|
600
|
+
|
|
601
|
+
:ID
|
|
602
|
+
when :KYWD, :ID
|
|
603
|
+
step while id?(peek)
|
|
604
|
+
|
|
605
|
+
:ID
|
|
606
|
+
when :BOM
|
|
607
|
+
return step(2) && :SPACE if peek == 0xFE && peek(1) == 0xFF
|
|
608
|
+
|
|
609
|
+
step while id?(peek)
|
|
610
|
+
:ID
|
|
611
|
+
else
|
|
612
|
+
:ILLEGAL
|
|
613
|
+
end
|
|
614
|
+
end
|
|
615
|
+
|
|
616
|
+
def peek(n = 0) = @sql.getbyte(@cursor+n)
|
|
617
|
+
def step(n = 1) = @cursor += n
|
|
618
|
+
def scan(n = 0) = peek(n).tap { step(n+1) }
|
|
619
|
+
|
|
620
|
+
def space?(byte) = byte && CHARACTER_FLAGS[byte] & 0x01 != 0
|
|
621
|
+
def alnum?(byte) = byte && CHARACTER_FLAGS[byte] & 0x06 != 0
|
|
622
|
+
def alpha?(byte) = byte && CHARACTER_FLAGS[byte] & 0x02 != 0
|
|
623
|
+
def digit?(byte) = byte && CHARACTER_FLAGS[byte] & 0x04 != 0
|
|
624
|
+
def xdigit?(byte) = byte && CHARACTER_FLAGS[byte] & 0x08 != 0
|
|
625
|
+
def quote?(byte) = byte && CHARACTER_FLAGS[byte] & 0x80 != 0
|
|
626
|
+
def id?(byte) = byte && CHARACTER_FLAGS[byte] & 0x46 != 0
|
|
627
|
+
# If X is a character that can be used in an identifier then
|
|
628
|
+
# IdChar(X) will be true. Otherwise it is false.
|
|
629
|
+
#
|
|
630
|
+
# For ASCII, any character with the high-order bit set is
|
|
631
|
+
# allowed in an identifier. For 7-bit characters,
|
|
632
|
+
# sqlite3IsIdChar[X] must be 1.
|
|
633
|
+
#
|
|
634
|
+
# Ticket #1066. the SQL standard does not allow '$' in the
|
|
635
|
+
# middle of identifiers. But many SQL implementations do.
|
|
636
|
+
# SQLite will allow '$' in identifiers for compatibility.
|
|
637
|
+
# But the feature is undocumented.
|
|
638
|
+
end
|
|
639
|
+
end
|