package sql import ( "bufio" "errors" "io" "strings" "unicode" ) type Token int const ( EOF Token = iota ILLEGAL IDENT SEMI LPAREN RPAREN COMMA ASTERIKS ASSIGN BACKQUOTE QUOTE SINGLE_QUOTE // Keywords CREATE TABLE SELECT FROM WHERE AND OR ORDER TOP PRIMARY FOREIGN REFERENCES KEY NOT IF EXISTS AUTOINCREMENT CONSTRAINT TEXT INTEGER NULL REAL BLOB NUMERIC ) var keywords map[string]Token = map[string]Token{ "CREATE": CREATE, "TABLE": TABLE, "PRIMARY": PRIMARY, "FOREIGN": FOREIGN, "REFERENCES": REFERENCES, "KEY": KEY, "NOT": NOT, "TEXT": TEXT, "INTEGER": INTEGER, "NULL": NULL, "IF": IF, "EXISTS": EXISTS, "SELECT": SELECT, "FROM": FROM, "WHERE": WHERE, "AND": AND, "OR": OR, "ORDER": ORDER, "TOP": TOP, "REAL": REAL, "BLOB": BLOB, "AUTOINCREMENT": AUTOINCREMENT, "CONSTRAINT": CONSTRAINT, "NUMERIC": NUMERIC, } type Position struct { line int column int } type Lexer struct { pos Position reader *bufio.Reader } func NewLexer(reader io.Reader) *Lexer { return &Lexer{ pos: Position{line: 1, column: 0}, reader: bufio.NewReader(reader), } } func (l *Lexer) Lex() (Position, Token, string) { for { r, _, err := l.reader.ReadRune() if err != nil && errors.Is(err, io.EOF) { return l.pos, EOF, "" } else if err != nil { // Change this panic(err) } l.pos.column++ switch r { case '\n': l.resetPosition() case ';': return l.pos, SEMI, ";" case ',': return l.pos, COMMA, "," case '(': return l.pos, LPAREN, "(" case ')': return l.pos, RPAREN, ")" case '*': return l.pos, ASTERIKS, "*" case '=': return l.pos, ASSIGN, "=" case '`': return l.pos, BACKQUOTE, "`" case '"': return l.pos, QUOTE, "\"" case '\'': return l.pos, SINGLE_QUOTE, "'" default: if unicode.IsSpace(r) { continue } else if unicode.IsLetter(r) { startPos := l.pos l.backup() lit := l.lexIdent() litUpper := strings.ToUpper(lit) if token, ok := keywords[litUpper]; ok { return startPos, token, litUpper } return startPos, IDENT, lit } else { return l.pos, ILLEGAL, string(r) } } } } func (l *Lexer) lexIdent() string { var lit string for { r, _, err := l.reader.ReadRune() if err != nil && errors.Is(err, io.EOF) { return lit } else if err != nil { // Change this panic(err) } l.pos.column++ if unicode.IsLetter(r) || unicode.IsNumber(r) || r == '_' { // Change this to stringstream or something similar lit = lit + string(r) } else { l.backup() return lit } } } func (l *Lexer) backup() { if err := l.reader.UnreadRune(); err != nil { panic(err) } l.pos.column-- } func (l *Lexer) resetPosition() { l.pos.line++ l.pos.column = 0 }