lexer.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596
  1. package lexer
  2. import (
  3. "encoding/hex"
  4. "strings"
  5. "unicode"
  6. )
  7. // Lexer tokenizes SQL input.
  8. type Lexer struct {
  9. input string
  10. pos int // current position in input
  11. readPos int // reading position (after current char)
  12. ch byte // current char under examination
  13. line int // current line number (1-based)
  14. column int // current column number (1-based)
  15. // prev is the type of the last non-comment token, used to disambiguate a
  16. // leading bracket: after an expression it starts a subscript, otherwise it
  17. // is a [bracket-quoted identifier] as in SQLite.
  18. prev TokenType
  19. }
  20. // New creates a new Lexer for the given input.
  21. func New(input string) *Lexer {
  22. l := &Lexer{
  23. input: input,
  24. line: 1,
  25. column: 0,
  26. }
  27. l.readChar()
  28. return l
  29. }
  30. // readChar advances the lexer by one character.
  31. func (l *Lexer) readChar() {
  32. if l.readPos >= len(l.input) {
  33. l.ch = 0
  34. } else {
  35. l.ch = l.input[l.readPos]
  36. }
  37. l.pos = l.readPos
  38. l.readPos++
  39. l.column++
  40. if l.ch == '\n' {
  41. l.line++
  42. l.column = 0
  43. }
  44. }
  45. // peekChar returns the next character without advancing.
  46. func (l *Lexer) peekChar() byte {
  47. if l.readPos >= len(l.input) {
  48. return 0
  49. }
  50. return l.input[l.readPos]
  51. }
  52. // NextToken returns the next token from the input.
  53. func (l *Lexer) NextToken() Token {
  54. l.skipWhitespace()
  55. tok := Token{
  56. Line: l.line,
  57. Column: l.column,
  58. }
  59. switch l.ch {
  60. case 0:
  61. tok.Type = TokenEOF
  62. tok.Literal = ""
  63. case '+':
  64. tok.Type = TokenPlus
  65. tok.Literal = "+"
  66. l.readChar()
  67. case '*':
  68. tok.Type = TokenStar
  69. tok.Literal = "*"
  70. l.readChar()
  71. case '/':
  72. tok.Type = TokenSlash
  73. tok.Literal = "/"
  74. l.readChar()
  75. case '%':
  76. tok.Type = TokenPercent
  77. tok.Literal = "%"
  78. l.readChar()
  79. case '&':
  80. tok.Type = TokenBitAnd
  81. tok.Literal = "&"
  82. l.readChar()
  83. case '~':
  84. tok.Type = TokenBitNot
  85. tok.Literal = "~"
  86. l.readChar()
  87. case '(':
  88. tok.Type = TokenLParen
  89. tok.Literal = "("
  90. l.readChar()
  91. case ')':
  92. tok.Type = TokenRParen
  93. tok.Literal = ")"
  94. l.readChar()
  95. case ',':
  96. tok.Type = TokenComma
  97. tok.Literal = ","
  98. l.readChar()
  99. case ';':
  100. tok.Type = TokenSemicolon
  101. tok.Literal = ";"
  102. l.readChar()
  103. case '.':
  104. tok.Type = TokenDot
  105. tok.Literal = "."
  106. l.readChar()
  107. case '=':
  108. tok.Type = TokenEq
  109. tok.Literal = "="
  110. l.readChar()
  111. case '<':
  112. if l.peekChar() == '=' {
  113. l.readChar()
  114. tok.Type = TokenLte
  115. tok.Literal = "<="
  116. } else if l.peekChar() == '>' {
  117. l.readChar()
  118. tok.Type = TokenNeq
  119. tok.Literal = "<>"
  120. } else if l.peekChar() == '<' {
  121. l.readChar()
  122. tok.Type = TokenShiftLeft
  123. tok.Literal = "<<"
  124. } else if l.peekChar() == '@' {
  125. l.readChar()
  126. tok.Type = TokenLtAt
  127. tok.Literal = "<@"
  128. } else {
  129. tok.Type = TokenLt
  130. tok.Literal = "<"
  131. }
  132. l.readChar()
  133. case '>':
  134. if l.peekChar() == '=' {
  135. l.readChar()
  136. tok.Type = TokenGte
  137. tok.Literal = ">="
  138. } else if l.peekChar() == '>' {
  139. l.readChar()
  140. tok.Type = TokenShiftRight
  141. tok.Literal = ">>"
  142. } else {
  143. tok.Type = TokenGt
  144. tok.Literal = ">"
  145. }
  146. l.readChar()
  147. case '!':
  148. if l.peekChar() == '=' {
  149. l.readChar()
  150. tok.Type = TokenNeq
  151. tok.Literal = "!="
  152. l.readChar()
  153. } else {
  154. tok.Type = TokenError
  155. tok.Literal = "unexpected character: !"
  156. l.readChar()
  157. }
  158. case '|':
  159. if l.peekChar() == '|' {
  160. l.readChar()
  161. tok.Type = TokenConcat
  162. tok.Literal = "||"
  163. l.readChar()
  164. } else {
  165. tok.Type = TokenBitOr
  166. tok.Literal = "|"
  167. l.readChar()
  168. }
  169. case '#':
  170. // JSONB path and delete operators: #>, #>>, #-.
  171. if l.peekChar() == '>' {
  172. l.readChar()
  173. if l.peekChar() == '>' {
  174. l.readChar()
  175. tok.Type = TokenHashArrowText
  176. tok.Literal = "#>>"
  177. } else {
  178. tok.Type = TokenHashArrow
  179. tok.Literal = "#>"
  180. }
  181. l.readChar()
  182. } else if l.peekChar() == '-' {
  183. l.readChar()
  184. tok.Type = TokenHashMinus
  185. tok.Literal = "#-"
  186. l.readChar()
  187. } else {
  188. tok.Type = TokenError
  189. tok.Literal = "unexpected character: #"
  190. l.readChar()
  191. }
  192. case '@':
  193. // JSONB containment and jsonpath operators: @>, @@, @?.
  194. switch l.peekChar() {
  195. case '>':
  196. l.readChar()
  197. tok.Type = TokenAtArrow
  198. tok.Literal = "@>"
  199. case '@':
  200. l.readChar()
  201. tok.Type = TokenAtAt
  202. tok.Literal = "@@"
  203. case '?':
  204. l.readChar()
  205. tok.Type = TokenAtQuestion
  206. tok.Literal = "@?"
  207. default:
  208. tok.Type = TokenError
  209. tok.Literal = "unexpected character: @"
  210. }
  211. l.readChar()
  212. case '?':
  213. switch l.peekChar() {
  214. case '|':
  215. l.readChar()
  216. tok.Type = TokenQuestionPipe
  217. tok.Literal = "?|"
  218. case '&':
  219. l.readChar()
  220. tok.Type = TokenQuestionAmp
  221. tok.Literal = "?&"
  222. default:
  223. tok.Type = TokenQuestion
  224. tok.Literal = "?"
  225. }
  226. l.readChar()
  227. case '-':
  228. if l.peekChar() == '-' {
  229. // Line comment
  230. tok = l.readLineComment()
  231. } else if l.peekChar() == '>' {
  232. l.readChar() // consume '-'
  233. if l.peekChar() == '>' {
  234. l.readChar() // consume the first '>'
  235. tok.Type = TokenArrowText
  236. tok.Literal = "->>"
  237. } else {
  238. tok.Type = TokenArrow
  239. tok.Literal = "->"
  240. }
  241. l.readChar()
  242. } else {
  243. tok.Type = TokenMinus
  244. tok.Literal = "-"
  245. l.readChar()
  246. }
  247. case '\'':
  248. tok = l.readString()
  249. case '"':
  250. tok = l.readQuotedIdentifier('"')
  251. case '`':
  252. tok = l.readQuotedIdentifier('`')
  253. case '[':
  254. if l.afterExpression() {
  255. tok.Type = TokenLBracket
  256. tok.Literal = "["
  257. l.readChar()
  258. } else {
  259. tok = l.readBracketIdentifier()
  260. }
  261. case ']':
  262. tok.Type = TokenRBracket
  263. tok.Literal = "]"
  264. l.readChar()
  265. case ':':
  266. if l.peekChar() == ':' {
  267. l.readChar()
  268. tok.Type = TokenColonColon
  269. tok.Literal = "::"
  270. l.readChar()
  271. } else {
  272. tok.Type = TokenError
  273. tok.Literal = "unexpected character: :"
  274. l.readChar()
  275. }
  276. default:
  277. if (l.ch == 'x' || l.ch == 'X') && l.peekChar() == '\'' {
  278. tok = l.readBlob()
  279. } else if isLetter(l.ch) || l.ch == '_' {
  280. tok = l.readIdentifier()
  281. } else if isDigit(l.ch) {
  282. tok = l.readNumber()
  283. } else {
  284. tok.Type = TokenError
  285. tok.Literal = "unexpected character: " + string(l.ch)
  286. l.readChar()
  287. }
  288. }
  289. if tok.Type != TokenComment {
  290. l.prev = tok.Type
  291. }
  292. return tok
  293. }
  294. // afterExpression reports whether a bracket at this position starts a subscript
  295. // rather than a bracket-quoted identifier. Only tokens that can end an
  296. // expression qualify; everything else keeps SQLite's [identifier] quoting (a
  297. // bracket-quoted alias or name directly after a keyword is still accepted).
  298. func (l *Lexer) afterExpression() bool {
  299. switch l.prev {
  300. case TokenIdent, TokenString, TokenNumber, TokenBlob,
  301. TokenRParen, TokenRBracket, TokenTRUE, TokenFALSE, TokenNULL:
  302. return true
  303. }
  304. // Type keywords accept a trailing [] and are therefore followed by a
  305. // bracket that starts an array dimension, not an identifier.
  306. switch l.prev {
  307. case TokenINTEGER, TokenINT, TokenTINYINT, TokenSMALLINT, TokenMEDIUMINT, TokenBIGINT,
  308. TokenREAL, TokenFLOAT, TokenDOUBLE, TokenNUMERIC, TokenDECIMAL,
  309. TokenTEXT, TokenVARCHAR, TokenCHAR, TokenCHARACTER, TokenCLOB, TokenNCHAR, TokenNVARCHAR,
  310. TokenBLOB, TokenBOOLEAN, TokenDATE, TokenTIME, TokenTIMESTAMP, TokenDATETIME,
  311. TokenJSON, TokenJSONB:
  312. return true
  313. }
  314. return false
  315. }
  316. // skipWhitespace skips spaces, tabs, and newlines.
  317. func (l *Lexer) skipWhitespace() {
  318. for l.ch == ' ' || l.ch == '\t' || l.ch == '\n' || l.ch == '\r' {
  319. l.readChar()
  320. }
  321. // Also skip block comments
  322. if l.ch == '/' && l.peekChar() == '*' {
  323. l.skipBlockComment()
  324. l.skipWhitespace()
  325. }
  326. }
  327. // skipBlockComment skips /* ... */ comments.
  328. func (l *Lexer) skipBlockComment() {
  329. l.readChar() // skip /
  330. l.readChar() // skip *
  331. for {
  332. if l.ch == 0 {
  333. return // EOF in comment
  334. }
  335. if l.ch == '*' && l.peekChar() == '/' {
  336. l.readChar() // skip *
  337. l.readChar() // skip /
  338. return
  339. }
  340. l.readChar()
  341. }
  342. }
  343. // readLineComment reads a -- line comment.
  344. func (l *Lexer) readLineComment() Token {
  345. tok := Token{
  346. Type: TokenComment,
  347. Line: l.line,
  348. Column: l.column,
  349. }
  350. startPos := l.pos
  351. for l.ch != '\n' && l.ch != 0 {
  352. l.readChar()
  353. }
  354. tok.Literal = l.input[startPos:l.pos]
  355. return tok
  356. }
  357. // readString reads a 'string literal'.
  358. func (l *Lexer) readString() Token {
  359. tok := Token{
  360. Type: TokenString,
  361. Line: l.line,
  362. Column: l.column,
  363. }
  364. l.readChar() // skip opening quote
  365. var sb strings.Builder
  366. for {
  367. if l.ch == 0 {
  368. tok.Type = TokenError
  369. tok.Literal = "unterminated string"
  370. return tok
  371. }
  372. if l.ch == '\'' {
  373. if l.peekChar() == '\'' {
  374. // Escaped quote
  375. sb.WriteByte('\'')
  376. l.readChar()
  377. l.readChar()
  378. } else {
  379. // End of string
  380. l.readChar()
  381. break
  382. }
  383. } else {
  384. sb.WriteByte(l.ch)
  385. l.readChar()
  386. }
  387. }
  388. tok.Literal = sb.String()
  389. return tok
  390. }
  391. // readQuotedIdentifier reads a "quoted identifier" or `backtick identifier`.
  392. func (l *Lexer) readQuotedIdentifier(quote byte) Token {
  393. tok := Token{
  394. Type: TokenIdent,
  395. Line: l.line,
  396. Column: l.column,
  397. }
  398. l.readChar() // skip opening quote
  399. startPos := l.pos
  400. for l.ch != quote && l.ch != 0 {
  401. l.readChar()
  402. }
  403. if l.ch == 0 {
  404. tok.Type = TokenError
  405. tok.Literal = "unterminated identifier"
  406. return tok
  407. }
  408. tok.Literal = l.input[startPos:l.pos]
  409. l.readChar() // skip closing quote
  410. return tok
  411. }
  412. // readBracketIdentifier reads a [bracket identifier] (SQL Server style).
  413. func (l *Lexer) readBracketIdentifier() Token {
  414. tok := Token{
  415. Type: TokenIdent,
  416. Line: l.line,
  417. Column: l.column,
  418. Bracketed: true,
  419. }
  420. l.readChar() // skip [
  421. startPos := l.pos
  422. for l.ch != ']' && l.ch != 0 {
  423. l.readChar()
  424. }
  425. if l.ch == 0 {
  426. tok.Type = TokenError
  427. tok.Literal = "unterminated identifier"
  428. return tok
  429. }
  430. tok.Literal = l.input[startPos:l.pos]
  431. l.readChar() // skip ]
  432. return tok
  433. }
  434. // readBlob reads a X'hex' blob literal. Whitespace between hex digits is
  435. // ignored (SQLite allows it). The token literal holds the decoded raw bytes so
  436. // consumers never have to re-parse the hex form.
  437. func (l *Lexer) readBlob() Token {
  438. tok := Token{
  439. Type: TokenBlob,
  440. Line: l.line,
  441. Column: l.column,
  442. }
  443. l.readChar() // skip x/X
  444. l.readChar() // skip opening quote
  445. var sb strings.Builder
  446. for l.ch != '\'' && l.ch != 0 {
  447. if isHexDigit(l.ch) {
  448. sb.WriteByte(l.ch)
  449. } else if l.ch != ' ' && l.ch != '\t' && l.ch != '\n' && l.ch != '\r' {
  450. tok.Type = TokenError
  451. tok.Literal = "invalid character in blob literal: " + string(l.ch)
  452. return tok
  453. }
  454. l.readChar()
  455. }
  456. if l.ch == 0 {
  457. tok.Type = TokenError
  458. tok.Literal = "unterminated blob literal"
  459. return tok
  460. }
  461. l.readChar() // skip closing quote
  462. if sb.Len()%2 != 0 {
  463. tok.Type = TokenError
  464. tok.Literal = "blob literal must contain an even number of hex digits"
  465. return tok
  466. }
  467. decoded, err := hex.DecodeString(sb.String())
  468. if err != nil {
  469. tok.Type = TokenError
  470. tok.Literal = "invalid blob literal: " + err.Error()
  471. return tok
  472. }
  473. tok.Literal = string(decoded)
  474. return tok
  475. }
  476. // readIdentifier reads an identifier or keyword.
  477. func (l *Lexer) readIdentifier() Token {
  478. tok := Token{
  479. Line: l.line,
  480. Column: l.column,
  481. }
  482. startPos := l.pos
  483. for isLetter(l.ch) || isDigit(l.ch) || l.ch == '_' {
  484. l.readChar()
  485. }
  486. literal := l.input[startPos:l.pos]
  487. tok.Literal = literal
  488. tok.Type = LookupKeyword(strings.ToUpper(literal))
  489. return tok
  490. }
  491. // readNumber reads an integer or float.
  492. func (l *Lexer) readNumber() Token {
  493. tok := Token{
  494. Type: TokenNumber,
  495. Line: l.line,
  496. Column: l.column,
  497. }
  498. startPos := l.pos
  499. // Read integer part
  500. for isDigit(l.ch) {
  501. l.readChar()
  502. }
  503. // Check for decimal point
  504. if l.ch == '.' && isDigit(l.peekChar()) {
  505. l.readChar() // skip .
  506. for isDigit(l.ch) {
  507. l.readChar()
  508. }
  509. }
  510. // Check for exponent
  511. if l.ch == 'e' || l.ch == 'E' {
  512. l.readChar()
  513. if l.ch == '+' || l.ch == '-' {
  514. l.readChar()
  515. }
  516. for isDigit(l.ch) {
  517. l.readChar()
  518. }
  519. }
  520. tok.Literal = l.input[startPos:l.pos]
  521. return tok
  522. }
  523. // Tokenize returns all tokens from the input.
  524. func (l *Lexer) Tokenize() []Token {
  525. var tokens []Token
  526. for {
  527. tok := l.NextToken()
  528. tokens = append(tokens, tok)
  529. if tok.Type == TokenEOF || tok.Type == TokenError {
  530. break
  531. }
  532. }
  533. return tokens
  534. }
  535. func isLetter(ch byte) bool {
  536. return unicode.IsLetter(rune(ch))
  537. }
  538. func isDigit(ch byte) bool {
  539. return ch >= '0' && ch <= '9'
  540. }
  541. func isHexDigit(ch byte) bool {
  542. return (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')
  543. }