lexer.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  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 {
  125. tok.Type = TokenLt
  126. tok.Literal = "<"
  127. }
  128. l.readChar()
  129. case '>':
  130. if l.peekChar() == '=' {
  131. l.readChar()
  132. tok.Type = TokenGte
  133. tok.Literal = ">="
  134. } else if l.peekChar() == '>' {
  135. l.readChar()
  136. tok.Type = TokenShiftRight
  137. tok.Literal = ">>"
  138. } else {
  139. tok.Type = TokenGt
  140. tok.Literal = ">"
  141. }
  142. l.readChar()
  143. case '!':
  144. if l.peekChar() == '=' {
  145. l.readChar()
  146. tok.Type = TokenNeq
  147. tok.Literal = "!="
  148. l.readChar()
  149. } else {
  150. tok.Type = TokenError
  151. tok.Literal = "unexpected character: !"
  152. l.readChar()
  153. }
  154. case '|':
  155. if l.peekChar() == '|' {
  156. l.readChar()
  157. tok.Type = TokenConcat
  158. tok.Literal = "||"
  159. l.readChar()
  160. } else {
  161. tok.Type = TokenBitOr
  162. tok.Literal = "|"
  163. l.readChar()
  164. }
  165. case '-':
  166. if l.peekChar() == '-' {
  167. // Line comment
  168. tok = l.readLineComment()
  169. } else if l.peekChar() == '>' {
  170. l.readChar() // consume '-'
  171. if l.peekChar() == '>' {
  172. l.readChar() // consume the first '>'
  173. tok.Type = TokenArrowText
  174. tok.Literal = "->>"
  175. } else {
  176. tok.Type = TokenArrow
  177. tok.Literal = "->"
  178. }
  179. l.readChar()
  180. } else {
  181. tok.Type = TokenMinus
  182. tok.Literal = "-"
  183. l.readChar()
  184. }
  185. case '\'':
  186. tok = l.readString()
  187. case '"':
  188. tok = l.readQuotedIdentifier('"')
  189. case '`':
  190. tok = l.readQuotedIdentifier('`')
  191. case '[':
  192. if l.afterExpression() {
  193. tok.Type = TokenLBracket
  194. tok.Literal = "["
  195. l.readChar()
  196. } else {
  197. tok = l.readBracketIdentifier()
  198. }
  199. case ']':
  200. tok.Type = TokenRBracket
  201. tok.Literal = "]"
  202. l.readChar()
  203. case ':':
  204. if l.peekChar() == ':' {
  205. l.readChar()
  206. tok.Type = TokenColonColon
  207. tok.Literal = "::"
  208. l.readChar()
  209. } else {
  210. tok.Type = TokenError
  211. tok.Literal = "unexpected character: :"
  212. l.readChar()
  213. }
  214. default:
  215. if (l.ch == 'x' || l.ch == 'X') && l.peekChar() == '\'' {
  216. tok = l.readBlob()
  217. } else if isLetter(l.ch) || l.ch == '_' {
  218. tok = l.readIdentifier()
  219. } else if isDigit(l.ch) {
  220. tok = l.readNumber()
  221. } else {
  222. tok.Type = TokenError
  223. tok.Literal = "unexpected character: " + string(l.ch)
  224. l.readChar()
  225. }
  226. }
  227. if tok.Type != TokenComment {
  228. l.prev = tok.Type
  229. }
  230. return tok
  231. }
  232. // afterExpression reports whether a bracket at this position starts a subscript
  233. // rather than a bracket-quoted identifier. Only tokens that can end an
  234. // expression qualify; everything else keeps SQLite's [identifier] quoting (a
  235. // bracket-quoted alias or name directly after a keyword is still accepted).
  236. func (l *Lexer) afterExpression() bool {
  237. switch l.prev {
  238. case TokenIdent, TokenString, TokenNumber, TokenBlob,
  239. TokenRParen, TokenRBracket, TokenTRUE, TokenFALSE, TokenNULL:
  240. return true
  241. }
  242. // Type keywords accept a trailing [] and are therefore followed by a
  243. // bracket that starts an array dimension, not an identifier.
  244. switch l.prev {
  245. case TokenINTEGER, TokenINT, TokenTINYINT, TokenSMALLINT, TokenMEDIUMINT, TokenBIGINT,
  246. TokenREAL, TokenFLOAT, TokenDOUBLE, TokenNUMERIC, TokenDECIMAL,
  247. TokenTEXT, TokenVARCHAR, TokenCHAR, TokenCHARACTER, TokenCLOB, TokenNCHAR, TokenNVARCHAR,
  248. TokenBLOB, TokenBOOLEAN, TokenDATE, TokenTIME, TokenTIMESTAMP, TokenDATETIME,
  249. TokenJSON, TokenJSONB:
  250. return true
  251. }
  252. return false
  253. }
  254. // skipWhitespace skips spaces, tabs, and newlines.
  255. func (l *Lexer) skipWhitespace() {
  256. for l.ch == ' ' || l.ch == '\t' || l.ch == '\n' || l.ch == '\r' {
  257. l.readChar()
  258. }
  259. // Also skip block comments
  260. if l.ch == '/' && l.peekChar() == '*' {
  261. l.skipBlockComment()
  262. l.skipWhitespace()
  263. }
  264. }
  265. // skipBlockComment skips /* ... */ comments.
  266. func (l *Lexer) skipBlockComment() {
  267. l.readChar() // skip /
  268. l.readChar() // skip *
  269. for {
  270. if l.ch == 0 {
  271. return // EOF in comment
  272. }
  273. if l.ch == '*' && l.peekChar() == '/' {
  274. l.readChar() // skip *
  275. l.readChar() // skip /
  276. return
  277. }
  278. l.readChar()
  279. }
  280. }
  281. // readLineComment reads a -- line comment.
  282. func (l *Lexer) readLineComment() Token {
  283. tok := Token{
  284. Type: TokenComment,
  285. Line: l.line,
  286. Column: l.column,
  287. }
  288. startPos := l.pos
  289. for l.ch != '\n' && l.ch != 0 {
  290. l.readChar()
  291. }
  292. tok.Literal = l.input[startPos:l.pos]
  293. return tok
  294. }
  295. // readString reads a 'string literal'.
  296. func (l *Lexer) readString() Token {
  297. tok := Token{
  298. Type: TokenString,
  299. Line: l.line,
  300. Column: l.column,
  301. }
  302. l.readChar() // skip opening quote
  303. var sb strings.Builder
  304. for {
  305. if l.ch == 0 {
  306. tok.Type = TokenError
  307. tok.Literal = "unterminated string"
  308. return tok
  309. }
  310. if l.ch == '\'' {
  311. if l.peekChar() == '\'' {
  312. // Escaped quote
  313. sb.WriteByte('\'')
  314. l.readChar()
  315. l.readChar()
  316. } else {
  317. // End of string
  318. l.readChar()
  319. break
  320. }
  321. } else {
  322. sb.WriteByte(l.ch)
  323. l.readChar()
  324. }
  325. }
  326. tok.Literal = sb.String()
  327. return tok
  328. }
  329. // readQuotedIdentifier reads a "quoted identifier" or `backtick identifier`.
  330. func (l *Lexer) readQuotedIdentifier(quote byte) Token {
  331. tok := Token{
  332. Type: TokenIdent,
  333. Line: l.line,
  334. Column: l.column,
  335. }
  336. l.readChar() // skip opening quote
  337. startPos := l.pos
  338. for l.ch != quote && l.ch != 0 {
  339. l.readChar()
  340. }
  341. if l.ch == 0 {
  342. tok.Type = TokenError
  343. tok.Literal = "unterminated identifier"
  344. return tok
  345. }
  346. tok.Literal = l.input[startPos:l.pos]
  347. l.readChar() // skip closing quote
  348. return tok
  349. }
  350. // readBracketIdentifier reads a [bracket identifier] (SQL Server style).
  351. func (l *Lexer) readBracketIdentifier() Token {
  352. tok := Token{
  353. Type: TokenIdent,
  354. Line: l.line,
  355. Column: l.column,
  356. Bracketed: true,
  357. }
  358. l.readChar() // skip [
  359. startPos := l.pos
  360. for l.ch != ']' && l.ch != 0 {
  361. l.readChar()
  362. }
  363. if l.ch == 0 {
  364. tok.Type = TokenError
  365. tok.Literal = "unterminated identifier"
  366. return tok
  367. }
  368. tok.Literal = l.input[startPos:l.pos]
  369. l.readChar() // skip ]
  370. return tok
  371. }
  372. // readBlob reads a X'hex' blob literal. Whitespace between hex digits is
  373. // ignored (SQLite allows it). The token literal holds the decoded raw bytes so
  374. // consumers never have to re-parse the hex form.
  375. func (l *Lexer) readBlob() Token {
  376. tok := Token{
  377. Type: TokenBlob,
  378. Line: l.line,
  379. Column: l.column,
  380. }
  381. l.readChar() // skip x/X
  382. l.readChar() // skip opening quote
  383. var sb strings.Builder
  384. for l.ch != '\'' && l.ch != 0 {
  385. if isHexDigit(l.ch) {
  386. sb.WriteByte(l.ch)
  387. } else if l.ch != ' ' && l.ch != '\t' && l.ch != '\n' && l.ch != '\r' {
  388. tok.Type = TokenError
  389. tok.Literal = "invalid character in blob literal: " + string(l.ch)
  390. return tok
  391. }
  392. l.readChar()
  393. }
  394. if l.ch == 0 {
  395. tok.Type = TokenError
  396. tok.Literal = "unterminated blob literal"
  397. return tok
  398. }
  399. l.readChar() // skip closing quote
  400. if sb.Len()%2 != 0 {
  401. tok.Type = TokenError
  402. tok.Literal = "blob literal must contain an even number of hex digits"
  403. return tok
  404. }
  405. decoded, err := hex.DecodeString(sb.String())
  406. if err != nil {
  407. tok.Type = TokenError
  408. tok.Literal = "invalid blob literal: " + err.Error()
  409. return tok
  410. }
  411. tok.Literal = string(decoded)
  412. return tok
  413. }
  414. // readIdentifier reads an identifier or keyword.
  415. func (l *Lexer) readIdentifier() Token {
  416. tok := Token{
  417. Line: l.line,
  418. Column: l.column,
  419. }
  420. startPos := l.pos
  421. for isLetter(l.ch) || isDigit(l.ch) || l.ch == '_' {
  422. l.readChar()
  423. }
  424. literal := l.input[startPos:l.pos]
  425. tok.Literal = literal
  426. tok.Type = LookupKeyword(strings.ToUpper(literal))
  427. return tok
  428. }
  429. // readNumber reads an integer or float.
  430. func (l *Lexer) readNumber() Token {
  431. tok := Token{
  432. Type: TokenNumber,
  433. Line: l.line,
  434. Column: l.column,
  435. }
  436. startPos := l.pos
  437. // Read integer part
  438. for isDigit(l.ch) {
  439. l.readChar()
  440. }
  441. // Check for decimal point
  442. if l.ch == '.' && isDigit(l.peekChar()) {
  443. l.readChar() // skip .
  444. for isDigit(l.ch) {
  445. l.readChar()
  446. }
  447. }
  448. // Check for exponent
  449. if l.ch == 'e' || l.ch == 'E' {
  450. l.readChar()
  451. if l.ch == '+' || l.ch == '-' {
  452. l.readChar()
  453. }
  454. for isDigit(l.ch) {
  455. l.readChar()
  456. }
  457. }
  458. tok.Literal = l.input[startPos:l.pos]
  459. return tok
  460. }
  461. // Tokenize returns all tokens from the input.
  462. func (l *Lexer) Tokenize() []Token {
  463. var tokens []Token
  464. for {
  465. tok := l.NextToken()
  466. tokens = append(tokens, tok)
  467. if tok.Type == TokenEOF || tok.Type == TokenError {
  468. break
  469. }
  470. }
  471. return tokens
  472. }
  473. func isLetter(ch byte) bool {
  474. return unicode.IsLetter(rune(ch))
  475. }
  476. func isDigit(ch byte) bool {
  477. return ch >= '0' && ch <= '9'
  478. }
  479. func isHexDigit(ch byte) bool {
  480. return (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')
  481. }