Initial working stage, but not with all tests, because IF NOT EXISTS is not implemented

This commit is contained in:
Pablu
2025-10-28 11:29:26 +01:00
commit 15f0d190dc
7 changed files with 526 additions and 0 deletions

24
sql/ast.go Normal file
View File

@@ -0,0 +1,24 @@
package sql
import "fmt"
type CreateTableStatement struct {
TableName string
Columns []Column
}
func (c *CreateTableStatement) Print() {
fmt.Printf("Name: %v\nColumns:\n", c.TableName)
for _, column := range c.Columns {
fmt.Printf("- Name: %v\n Type: %v\n Extras:\n", column.Name, column.Type)
for _, extra := range column.Extra {
fmt.Printf(" - %v\n", extra)
}
}
}
type Column struct {
Name string
Type string
Extra []string
}

143
sql/lexer.go Normal file
View File

@@ -0,0 +1,143 @@
package sql
import (
"bufio"
"errors"
"io"
"strings"
"unicode"
)
type Token int
const (
EOF Token = iota
ILLEGAL
IDENT
SEMI
LPAREN
RPAREN
COMMA
CREATE
TABLE
PRIMARY
FOREIGN
REFERENCES
KEY
NOT
TEXT
INTEGER
NULL
)
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,
}
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, ")"
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
}

208
sql/parser.go Normal file
View File

@@ -0,0 +1,208 @@
package sql
import (
"errors"
"fmt"
"io"
"slices"
)
type Parser struct {
s *Lexer
last struct {
pos Position
tok Token
lit string
}
}
func NewParser(r io.Reader) *Parser {
return &Parser{s: NewLexer(r)}
}
func (p *Parser) Parse() (*CreateTableStatement, error) {
tok, ok := p.expectOne(CREATE, EOF)
if !ok {
return nil, p.unexpectedToken()
} else if tok == EOF {
return nil, io.EOF
}
if !p.expectNext(TABLE) {
return nil, errors.New("Expect TABLE token")
}
if !p.expectNext(IDENT) {
return nil, errors.New("Expect IDENT token")
}
stmt := CreateTableStatement{
TableName: p.last.lit,
Columns: make([]Column, 0),
}
_, tok, _ = p.scan()
if tok != LPAREN {
return nil, errors.New("Expect LPAREN token")
}
for {
lastTok := p.last.tok
_, tok, _ := p.scan()
switch tok {
case IDENT:
column, err := p.parseColumn()
if err != nil {
return nil, err
}
stmt.Columns = append(stmt.Columns, column)
case RPAREN:
if !p.expectNext(SEMI) {
return nil, p.unexpectedToken()
}
return &stmt, nil
case SEMI:
if lastTok != RPAREN {
return nil, p.unexpectedToken()
}
return &stmt, nil
case FOREIGN:
if !p.expectNext(KEY) {
return nil, p.unexpectedToken()
}
if !p.expectNext(LPAREN) {
return nil, p.unexpectedToken()
}
if !p.expectNext(IDENT) {
return nil, p.unexpectedToken()
}
columnName := p.last.lit
if !p.expectNext(RPAREN) {
return nil, p.unexpectedToken()
}
if !p.expectNext(REFERENCES) {
return nil, p.unexpectedToken()
}
ref, err := p.references()
if err != nil {
return nil, err
}
column := slices.IndexFunc(stmt.Columns, func(c Column) bool {
return c.Name == columnName
})
stmt.Columns[column].Extra = append(stmt.Columns[column].Extra, ref)
default:
return nil, p.unexpectedToken()
}
}
}
func (p *Parser) parseColumn() (Column, error) {
column := Column{Name: p.last.lit, Extra: make([]string, 0)}
_, tok, lit := p.scan()
switch tok {
case TEXT:
fallthrough
case INTEGER:
column.Type = lit
default:
return Column{}, p.unexpectedToken()
}
for {
_, tok, lit := p.scan()
switch tok {
case COMMA:
fallthrough
case RPAREN:
return column, nil
case PRIMARY:
fallthrough
case NOT:
if _, ok := p.expectOne(NULL, KEY); !ok {
return Column{}, p.unexpectedToken()
}
column.Extra = append(column.Extra, fmt.Sprintf("%v_%v", lit, p.last.lit))
case REFERENCES:
ref, err := p.references()
if err != nil {
return Column{}, err
}
column.Extra = append(column.Extra, ref)
default:
return Column{}, p.unexpectedToken()
}
}
}
func (p *Parser) unexpectedToken() error {
return fmt.Errorf("Encountered unexpected token: %v lit: '%v' on pos: %v", p.last.tok, p.last.lit, p.last.pos)
}
func (p *Parser) references() (string, error) {
if !p.expectNext(IDENT) {
return "", p.unexpectedToken()
}
referenceTableName := p.last.lit
if !p.expectNext(LPAREN) {
return "", p.unexpectedToken()
}
if !p.expectNext(IDENT) {
return "", p.unexpectedToken()
}
referenceColumnName := p.last.lit
if !p.expectNext(RPAREN) {
return "", p.unexpectedToken()
}
return fmt.Sprintf("ref %v.%v", referenceTableName, referenceColumnName), nil
}
func (p *Parser) expectNext(token Token) bool {
_, tok, _ := p.scan()
return tok == token
}
func (p *Parser) expectOne(token ...Token) (Token, bool) {
_, tok, _ := p.scan()
ok := slices.ContainsFunc(token, func(t Token) bool {
return tok == t
})
return tok, ok
}
func (p *Parser) scan() (Position, Token, string) {
pos, tok, lit := p.s.Lex()
// fmt.Printf("Scanning next Token: %v | pos: %v | lit: %v\n", tok, pos, lit)
p.last = struct {
pos Position
tok Token
lit string
}{
pos,
tok,
lit,
}
return pos, tok, lit
}