lexer.go 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  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. }
  16. // New creates a new Lexer for the given input.
  17. func New(input string) *Lexer {
  18. l := &Lexer{
  19. input: input,
  20. line: 1,
  21. column: 0,
  22. }
  23. l.readChar()
  24. return l
  25. }
  26. // readChar advances the lexer by one character.
  27. func (l *Lexer) readChar() {
  28. if l.readPos >= len(l.input) {
  29. l.ch = 0
  30. } else {
  31. l.ch = l.input[l.readPos]
  32. }
  33. l.pos = l.readPos
  34. l.readPos++
  35. l.column++
  36. if l.ch == '\n' {
  37. l.line++
  38. l.column = 0
  39. }
  40. }
  41. // peekChar returns the next character without advancing.
  42. func (l *Lexer) peekChar() byte {
  43. if l.readPos >= len(l.input) {
  44. return 0
  45. }
  46. return l.input[l.readPos]
  47. }
  48. // NextToken returns the next token from the input.
  49. func (l *Lexer) NextToken() Token {
  50. l.skipWhitespace()
  51. tok := Token{
  52. Line: l.line,
  53. Column: l.column,
  54. }
  55. switch l.ch {
  56. case 0:
  57. tok.Type = TokenEOF
  58. tok.Literal = ""
  59. case '+':
  60. tok.Type = TokenPlus
  61. tok.Literal = "+"
  62. l.readChar()
  63. case '*':
  64. tok.Type = TokenStar
  65. tok.Literal = "*"
  66. l.readChar()
  67. case '/':
  68. tok.Type = TokenSlash
  69. tok.Literal = "/"
  70. l.readChar()
  71. case '%':
  72. tok.Type = TokenPercent
  73. tok.Literal = "%"
  74. l.readChar()
  75. case '&':
  76. tok.Type = TokenBitAnd
  77. tok.Literal = "&"
  78. l.readChar()
  79. case '~':
  80. tok.Type = TokenBitNot
  81. tok.Literal = "~"
  82. l.readChar()
  83. case '(':
  84. tok.Type = TokenLParen
  85. tok.Literal = "("
  86. l.readChar()
  87. case ')':
  88. tok.Type = TokenRParen
  89. tok.Literal = ")"
  90. l.readChar()
  91. case ',':
  92. tok.Type = TokenComma
  93. tok.Literal = ","
  94. l.readChar()
  95. case ';':
  96. tok.Type = TokenSemicolon
  97. tok.Literal = ";"
  98. l.readChar()
  99. case '.':
  100. tok.Type = TokenDot
  101. tok.Literal = "."
  102. l.readChar()
  103. case '=':
  104. tok.Type = TokenEq
  105. tok.Literal = "="
  106. l.readChar()
  107. case '<':
  108. if l.peekChar() == '=' {
  109. l.readChar()
  110. tok.Type = TokenLte
  111. tok.Literal = "<="
  112. } else if l.peekChar() == '>' {
  113. l.readChar()
  114. tok.Type = TokenNeq
  115. tok.Literal = "<>"
  116. } else if l.peekChar() == '<' {
  117. l.readChar()
  118. tok.Type = TokenShiftLeft
  119. tok.Literal = "<<"
  120. } else {
  121. tok.Type = TokenLt
  122. tok.Literal = "<"
  123. }
  124. l.readChar()
  125. case '>':
  126. if l.peekChar() == '=' {
  127. l.readChar()
  128. tok.Type = TokenGte
  129. tok.Literal = ">="
  130. } else if l.peekChar() == '>' {
  131. l.readChar()
  132. tok.Type = TokenShiftRight
  133. tok.Literal = ">>"
  134. } else {
  135. tok.Type = TokenGt
  136. tok.Literal = ">"
  137. }
  138. l.readChar()
  139. case '!':
  140. if l.peekChar() == '=' {
  141. l.readChar()
  142. tok.Type = TokenNeq
  143. tok.Literal = "!="
  144. l.readChar()
  145. } else {
  146. tok.Type = TokenError
  147. tok.Literal = "unexpected character: !"
  148. l.readChar()
  149. }
  150. case '|':
  151. if l.peekChar() == '|' {
  152. l.readChar()
  153. tok.Type = TokenConcat
  154. tok.Literal = "||"
  155. l.readChar()
  156. } else {
  157. tok.Type = TokenBitOr
  158. tok.Literal = "|"
  159. l.readChar()
  160. }
  161. case '-':
  162. if l.peekChar() == '-' {
  163. // Line comment
  164. tok = l.readLineComment()
  165. } else {
  166. tok.Type = TokenMinus
  167. tok.Literal = "-"
  168. l.readChar()
  169. }
  170. case '\'':
  171. tok = l.readString()
  172. case '"':
  173. tok = l.readQuotedIdentifier('"')
  174. case '`':
  175. tok = l.readQuotedIdentifier('`')
  176. case '[':
  177. tok = l.readBracketIdentifier()
  178. default:
  179. if (l.ch == 'x' || l.ch == 'X') && l.peekChar() == '\'' {
  180. tok = l.readBlob()
  181. } else if isLetter(l.ch) || l.ch == '_' {
  182. tok = l.readIdentifier()
  183. } else if isDigit(l.ch) {
  184. tok = l.readNumber()
  185. } else {
  186. tok.Type = TokenError
  187. tok.Literal = "unexpected character: " + string(l.ch)
  188. l.readChar()
  189. }
  190. }
  191. return tok
  192. }
  193. // skipWhitespace skips spaces, tabs, and newlines.
  194. func (l *Lexer) skipWhitespace() {
  195. for l.ch == ' ' || l.ch == '\t' || l.ch == '\n' || l.ch == '\r' {
  196. l.readChar()
  197. }
  198. // Also skip block comments
  199. if l.ch == '/' && l.peekChar() == '*' {
  200. l.skipBlockComment()
  201. l.skipWhitespace()
  202. }
  203. }
  204. // skipBlockComment skips /* ... */ comments.
  205. func (l *Lexer) skipBlockComment() {
  206. l.readChar() // skip /
  207. l.readChar() // skip *
  208. for {
  209. if l.ch == 0 {
  210. return // EOF in comment
  211. }
  212. if l.ch == '*' && l.peekChar() == '/' {
  213. l.readChar() // skip *
  214. l.readChar() // skip /
  215. return
  216. }
  217. l.readChar()
  218. }
  219. }
  220. // readLineComment reads a -- line comment.
  221. func (l *Lexer) readLineComment() Token {
  222. tok := Token{
  223. Type: TokenComment,
  224. Line: l.line,
  225. Column: l.column,
  226. }
  227. startPos := l.pos
  228. for l.ch != '\n' && l.ch != 0 {
  229. l.readChar()
  230. }
  231. tok.Literal = l.input[startPos:l.pos]
  232. return tok
  233. }
  234. // readString reads a 'string literal'.
  235. func (l *Lexer) readString() Token {
  236. tok := Token{
  237. Type: TokenString,
  238. Line: l.line,
  239. Column: l.column,
  240. }
  241. l.readChar() // skip opening quote
  242. var sb strings.Builder
  243. for {
  244. if l.ch == 0 {
  245. tok.Type = TokenError
  246. tok.Literal = "unterminated string"
  247. return tok
  248. }
  249. if l.ch == '\'' {
  250. if l.peekChar() == '\'' {
  251. // Escaped quote
  252. sb.WriteByte('\'')
  253. l.readChar()
  254. l.readChar()
  255. } else {
  256. // End of string
  257. l.readChar()
  258. break
  259. }
  260. } else {
  261. sb.WriteByte(l.ch)
  262. l.readChar()
  263. }
  264. }
  265. tok.Literal = sb.String()
  266. return tok
  267. }
  268. // readQuotedIdentifier reads a "quoted identifier" or `backtick identifier`.
  269. func (l *Lexer) readQuotedIdentifier(quote byte) Token {
  270. tok := Token{
  271. Type: TokenIdent,
  272. Line: l.line,
  273. Column: l.column,
  274. }
  275. l.readChar() // skip opening quote
  276. startPos := l.pos
  277. for l.ch != quote && l.ch != 0 {
  278. l.readChar()
  279. }
  280. if l.ch == 0 {
  281. tok.Type = TokenError
  282. tok.Literal = "unterminated identifier"
  283. return tok
  284. }
  285. tok.Literal = l.input[startPos:l.pos]
  286. l.readChar() // skip closing quote
  287. return tok
  288. }
  289. // readBracketIdentifier reads a [bracket identifier] (SQL Server style).
  290. func (l *Lexer) readBracketIdentifier() Token {
  291. tok := Token{
  292. Type: TokenIdent,
  293. Line: l.line,
  294. Column: l.column,
  295. }
  296. l.readChar() // skip [
  297. startPos := l.pos
  298. for l.ch != ']' && l.ch != 0 {
  299. l.readChar()
  300. }
  301. if l.ch == 0 {
  302. tok.Type = TokenError
  303. tok.Literal = "unterminated identifier"
  304. return tok
  305. }
  306. tok.Literal = l.input[startPos:l.pos]
  307. l.readChar() // skip ]
  308. return tok
  309. }
  310. // readBlob reads a X'hex' blob literal. Whitespace between hex digits is
  311. // ignored (SQLite allows it). The token literal holds the decoded raw bytes so
  312. // consumers never have to re-parse the hex form.
  313. func (l *Lexer) readBlob() Token {
  314. tok := Token{
  315. Type: TokenBlob,
  316. Line: l.line,
  317. Column: l.column,
  318. }
  319. l.readChar() // skip x/X
  320. l.readChar() // skip opening quote
  321. var sb strings.Builder
  322. for l.ch != '\'' && l.ch != 0 {
  323. if isHexDigit(l.ch) {
  324. sb.WriteByte(l.ch)
  325. } else if l.ch != ' ' && l.ch != '\t' && l.ch != '\n' && l.ch != '\r' {
  326. tok.Type = TokenError
  327. tok.Literal = "invalid character in blob literal: " + string(l.ch)
  328. return tok
  329. }
  330. l.readChar()
  331. }
  332. if l.ch == 0 {
  333. tok.Type = TokenError
  334. tok.Literal = "unterminated blob literal"
  335. return tok
  336. }
  337. l.readChar() // skip closing quote
  338. if sb.Len()%2 != 0 {
  339. tok.Type = TokenError
  340. tok.Literal = "blob literal must contain an even number of hex digits"
  341. return tok
  342. }
  343. decoded, err := hex.DecodeString(sb.String())
  344. if err != nil {
  345. tok.Type = TokenError
  346. tok.Literal = "invalid blob literal: " + err.Error()
  347. return tok
  348. }
  349. tok.Literal = string(decoded)
  350. return tok
  351. }
  352. // readIdentifier reads an identifier or keyword.
  353. func (l *Lexer) readIdentifier() Token {
  354. tok := Token{
  355. Line: l.line,
  356. Column: l.column,
  357. }
  358. startPos := l.pos
  359. for isLetter(l.ch) || isDigit(l.ch) || l.ch == '_' {
  360. l.readChar()
  361. }
  362. literal := l.input[startPos:l.pos]
  363. tok.Literal = literal
  364. tok.Type = LookupKeyword(strings.ToUpper(literal))
  365. return tok
  366. }
  367. // readNumber reads an integer or float.
  368. func (l *Lexer) readNumber() Token {
  369. tok := Token{
  370. Type: TokenNumber,
  371. Line: l.line,
  372. Column: l.column,
  373. }
  374. startPos := l.pos
  375. // Read integer part
  376. for isDigit(l.ch) {
  377. l.readChar()
  378. }
  379. // Check for decimal point
  380. if l.ch == '.' && isDigit(l.peekChar()) {
  381. l.readChar() // skip .
  382. for isDigit(l.ch) {
  383. l.readChar()
  384. }
  385. }
  386. // Check for exponent
  387. if l.ch == 'e' || l.ch == 'E' {
  388. l.readChar()
  389. if l.ch == '+' || l.ch == '-' {
  390. l.readChar()
  391. }
  392. for isDigit(l.ch) {
  393. l.readChar()
  394. }
  395. }
  396. tok.Literal = l.input[startPos:l.pos]
  397. return tok
  398. }
  399. // Tokenize returns all tokens from the input.
  400. func (l *Lexer) Tokenize() []Token {
  401. var tokens []Token
  402. for {
  403. tok := l.NextToken()
  404. tokens = append(tokens, tok)
  405. if tok.Type == TokenEOF || tok.Type == TokenError {
  406. break
  407. }
  408. }
  409. return tokens
  410. }
  411. func isLetter(ch byte) bool {
  412. return unicode.IsLetter(rune(ch))
  413. }
  414. func isDigit(ch byte) bool {
  415. return ch >= '0' && ch <= '9'
  416. }
  417. func isHexDigit(ch byte) bool {
  418. return (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')
  419. }