lexer.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. package lexer
  2. import (
  3. "strings"
  4. "unicode"
  5. )
  6. // Lexer tokenizes SQL input.
  7. type Lexer struct {
  8. input string
  9. pos int // current position in input
  10. readPos int // reading position (after current char)
  11. ch byte // current char under examination
  12. line int // current line number (1-based)
  13. column int // current column number (1-based)
  14. }
  15. // New creates a new Lexer for the given input.
  16. func New(input string) *Lexer {
  17. l := &Lexer{
  18. input: input,
  19. line: 1,
  20. column: 0,
  21. }
  22. l.readChar()
  23. return l
  24. }
  25. // readChar advances the lexer by one character.
  26. func (l *Lexer) readChar() {
  27. if l.readPos >= len(l.input) {
  28. l.ch = 0
  29. } else {
  30. l.ch = l.input[l.readPos]
  31. }
  32. l.pos = l.readPos
  33. l.readPos++
  34. l.column++
  35. if l.ch == '\n' {
  36. l.line++
  37. l.column = 0
  38. }
  39. }
  40. // peekChar returns the next character without advancing.
  41. func (l *Lexer) peekChar() byte {
  42. if l.readPos >= len(l.input) {
  43. return 0
  44. }
  45. return l.input[l.readPos]
  46. }
  47. // NextToken returns the next token from the input.
  48. func (l *Lexer) NextToken() Token {
  49. l.skipWhitespace()
  50. tok := Token{
  51. Line: l.line,
  52. Column: l.column,
  53. }
  54. switch l.ch {
  55. case 0:
  56. tok.Type = TokenEOF
  57. tok.Literal = ""
  58. case '+':
  59. tok.Type = TokenPlus
  60. tok.Literal = "+"
  61. l.readChar()
  62. case '*':
  63. tok.Type = TokenStar
  64. tok.Literal = "*"
  65. l.readChar()
  66. case '/':
  67. tok.Type = TokenSlash
  68. tok.Literal = "/"
  69. l.readChar()
  70. case '%':
  71. tok.Type = TokenPercent
  72. tok.Literal = "%"
  73. l.readChar()
  74. case '(':
  75. tok.Type = TokenLParen
  76. tok.Literal = "("
  77. l.readChar()
  78. case ')':
  79. tok.Type = TokenRParen
  80. tok.Literal = ")"
  81. l.readChar()
  82. case ',':
  83. tok.Type = TokenComma
  84. tok.Literal = ","
  85. l.readChar()
  86. case ';':
  87. tok.Type = TokenSemicolon
  88. tok.Literal = ";"
  89. l.readChar()
  90. case '.':
  91. tok.Type = TokenDot
  92. tok.Literal = "."
  93. l.readChar()
  94. case '=':
  95. tok.Type = TokenEq
  96. tok.Literal = "="
  97. l.readChar()
  98. case '<':
  99. if l.peekChar() == '=' {
  100. l.readChar()
  101. tok.Type = TokenLte
  102. tok.Literal = "<="
  103. } else if l.peekChar() == '>' {
  104. l.readChar()
  105. tok.Type = TokenNeq
  106. tok.Literal = "<>"
  107. } else {
  108. tok.Type = TokenLt
  109. tok.Literal = "<"
  110. }
  111. l.readChar()
  112. case '>':
  113. if l.peekChar() == '=' {
  114. l.readChar()
  115. tok.Type = TokenGte
  116. tok.Literal = ">="
  117. } else {
  118. tok.Type = TokenGt
  119. tok.Literal = ">"
  120. }
  121. l.readChar()
  122. case '!':
  123. if l.peekChar() == '=' {
  124. l.readChar()
  125. tok.Type = TokenNeq
  126. tok.Literal = "!="
  127. l.readChar()
  128. } else {
  129. tok.Type = TokenError
  130. tok.Literal = "unexpected character: !"
  131. l.readChar()
  132. }
  133. case '|':
  134. if l.peekChar() == '|' {
  135. l.readChar()
  136. tok.Type = TokenConcat
  137. tok.Literal = "||"
  138. l.readChar()
  139. } else {
  140. tok.Type = TokenError
  141. tok.Literal = "unexpected character: |"
  142. l.readChar()
  143. }
  144. case '-':
  145. if l.peekChar() == '-' {
  146. // Line comment
  147. tok = l.readLineComment()
  148. } else {
  149. tok.Type = TokenMinus
  150. tok.Literal = "-"
  151. l.readChar()
  152. }
  153. case '\'':
  154. tok = l.readString()
  155. case '"':
  156. tok = l.readQuotedIdentifier('"')
  157. case '`':
  158. tok = l.readQuotedIdentifier('`')
  159. case '[':
  160. tok = l.readBracketIdentifier()
  161. default:
  162. if isLetter(l.ch) || l.ch == '_' {
  163. tok = l.readIdentifier()
  164. } else if isDigit(l.ch) {
  165. tok = l.readNumber()
  166. } else {
  167. tok.Type = TokenError
  168. tok.Literal = "unexpected character: " + string(l.ch)
  169. l.readChar()
  170. }
  171. }
  172. return tok
  173. }
  174. // skipWhitespace skips spaces, tabs, and newlines.
  175. func (l *Lexer) skipWhitespace() {
  176. for l.ch == ' ' || l.ch == '\t' || l.ch == '\n' || l.ch == '\r' {
  177. l.readChar()
  178. }
  179. // Also skip block comments
  180. if l.ch == '/' && l.peekChar() == '*' {
  181. l.skipBlockComment()
  182. l.skipWhitespace()
  183. }
  184. }
  185. // skipBlockComment skips /* ... */ comments.
  186. func (l *Lexer) skipBlockComment() {
  187. l.readChar() // skip /
  188. l.readChar() // skip *
  189. for {
  190. if l.ch == 0 {
  191. return // EOF in comment
  192. }
  193. if l.ch == '*' && l.peekChar() == '/' {
  194. l.readChar() // skip *
  195. l.readChar() // skip /
  196. return
  197. }
  198. l.readChar()
  199. }
  200. }
  201. // readLineComment reads a -- line comment.
  202. func (l *Lexer) readLineComment() Token {
  203. tok := Token{
  204. Type: TokenComment,
  205. Line: l.line,
  206. Column: l.column,
  207. }
  208. startPos := l.pos
  209. for l.ch != '\n' && l.ch != 0 {
  210. l.readChar()
  211. }
  212. tok.Literal = l.input[startPos:l.pos]
  213. return tok
  214. }
  215. // readString reads a 'string literal'.
  216. func (l *Lexer) readString() Token {
  217. tok := Token{
  218. Type: TokenString,
  219. Line: l.line,
  220. Column: l.column,
  221. }
  222. l.readChar() // skip opening quote
  223. var sb strings.Builder
  224. for {
  225. if l.ch == 0 {
  226. tok.Type = TokenError
  227. tok.Literal = "unterminated string"
  228. return tok
  229. }
  230. if l.ch == '\'' {
  231. if l.peekChar() == '\'' {
  232. // Escaped quote
  233. sb.WriteByte('\'')
  234. l.readChar()
  235. l.readChar()
  236. } else {
  237. // End of string
  238. l.readChar()
  239. break
  240. }
  241. } else {
  242. sb.WriteByte(l.ch)
  243. l.readChar()
  244. }
  245. }
  246. tok.Literal = sb.String()
  247. return tok
  248. }
  249. // readQuotedIdentifier reads a "quoted identifier" or `backtick identifier`.
  250. func (l *Lexer) readQuotedIdentifier(quote byte) Token {
  251. tok := Token{
  252. Type: TokenIdent,
  253. Line: l.line,
  254. Column: l.column,
  255. }
  256. l.readChar() // skip opening quote
  257. startPos := l.pos
  258. for l.ch != quote && l.ch != 0 {
  259. l.readChar()
  260. }
  261. if l.ch == 0 {
  262. tok.Type = TokenError
  263. tok.Literal = "unterminated identifier"
  264. return tok
  265. }
  266. tok.Literal = l.input[startPos:l.pos]
  267. l.readChar() // skip closing quote
  268. return tok
  269. }
  270. // readBracketIdentifier reads a [bracket identifier] (SQL Server style).
  271. func (l *Lexer) readBracketIdentifier() Token {
  272. tok := Token{
  273. Type: TokenIdent,
  274. Line: l.line,
  275. Column: l.column,
  276. }
  277. l.readChar() // skip [
  278. startPos := l.pos
  279. for l.ch != ']' && l.ch != 0 {
  280. l.readChar()
  281. }
  282. if l.ch == 0 {
  283. tok.Type = TokenError
  284. tok.Literal = "unterminated identifier"
  285. return tok
  286. }
  287. tok.Literal = l.input[startPos:l.pos]
  288. l.readChar() // skip ]
  289. return tok
  290. }
  291. // readIdentifier reads an identifier or keyword.
  292. func (l *Lexer) readIdentifier() Token {
  293. tok := Token{
  294. Line: l.line,
  295. Column: l.column,
  296. }
  297. startPos := l.pos
  298. for isLetter(l.ch) || isDigit(l.ch) || l.ch == '_' {
  299. l.readChar()
  300. }
  301. literal := l.input[startPos:l.pos]
  302. tok.Literal = literal
  303. tok.Type = LookupKeyword(strings.ToUpper(literal))
  304. return tok
  305. }
  306. // readNumber reads an integer or float.
  307. func (l *Lexer) readNumber() Token {
  308. tok := Token{
  309. Type: TokenNumber,
  310. Line: l.line,
  311. Column: l.column,
  312. }
  313. startPos := l.pos
  314. // Read integer part
  315. for isDigit(l.ch) {
  316. l.readChar()
  317. }
  318. // Check for decimal point
  319. if l.ch == '.' && isDigit(l.peekChar()) {
  320. l.readChar() // skip .
  321. for isDigit(l.ch) {
  322. l.readChar()
  323. }
  324. }
  325. // Check for exponent
  326. if l.ch == 'e' || l.ch == 'E' {
  327. l.readChar()
  328. if l.ch == '+' || l.ch == '-' {
  329. l.readChar()
  330. }
  331. for isDigit(l.ch) {
  332. l.readChar()
  333. }
  334. }
  335. tok.Literal = l.input[startPos:l.pos]
  336. return tok
  337. }
  338. // Tokenize returns all tokens from the input.
  339. func (l *Lexer) Tokenize() []Token {
  340. var tokens []Token
  341. for {
  342. tok := l.NextToken()
  343. tokens = append(tokens, tok)
  344. if tok.Type == TokenEOF || tok.Type == TokenError {
  345. break
  346. }
  347. }
  348. return tokens
  349. }
  350. func isLetter(ch byte) bool {
  351. return unicode.IsLetter(rune(ch))
  352. }
  353. func isDigit(ch byte) bool {
  354. return ch >= '0' && ch <= '9'
  355. }