201 lines
3.1 KiB
Go
201 lines
3.1 KiB
Go
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
|
|
|
|
INSERT
|
|
INTO
|
|
VALUES
|
|
RETURNING
|
|
|
|
SELECT
|
|
FROM
|
|
WHERE
|
|
AND
|
|
OR
|
|
ORDER
|
|
TOP
|
|
|
|
DELETE
|
|
|
|
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,
|
|
"INSERT": INSERT,
|
|
"INTO": INTO,
|
|
"VALUES": VALUES,
|
|
"RETURNING": RETURNING,
|
|
"DELETE": DELETE,
|
|
}
|
|
|
|
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
|
|
}
|