393 lines
7.8 KiB
Go
393 lines
7.8 KiB
Go
package db0402
|
|
|
|
import (
|
|
"errors"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
type Parser struct {
|
|
buf string
|
|
pos int
|
|
}
|
|
|
|
func NewParser(s string) Parser {
|
|
return Parser{buf: s, pos: 0}
|
|
}
|
|
|
|
type StmtSelect struct {
|
|
table string
|
|
cols []string
|
|
keys []NamedCell
|
|
}
|
|
|
|
type NamedCell struct {
|
|
column string
|
|
value Cell
|
|
}
|
|
|
|
type StmtCreatTable struct {
|
|
table string
|
|
cols []Column
|
|
pkey []string
|
|
}
|
|
|
|
type StmtInsert struct {
|
|
table string
|
|
value []Cell
|
|
}
|
|
|
|
type StmtUpdate struct {
|
|
table string
|
|
keys []NamedCell
|
|
value []NamedCell
|
|
}
|
|
|
|
type StmtDelete struct {
|
|
table string
|
|
keys []NamedCell
|
|
}
|
|
|
|
func isSpace(ch byte) bool {
|
|
switch ch {
|
|
case '\t', '\n', '\v', '\f', '\r', ' ':
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
func isAlpha(ch byte) bool {
|
|
return 'a' <= (ch|32) && (ch|32) <= 'z'
|
|
}
|
|
func isDigit(ch byte) bool {
|
|
return '0' <= ch && ch <= '9'
|
|
}
|
|
func isNameStart(ch byte) bool {
|
|
return isAlpha(ch) || ch == '_'
|
|
}
|
|
func isNameContinue(ch byte) bool {
|
|
return isAlpha(ch) || isDigit(ch) || ch == '_'
|
|
}
|
|
func isSeparator(ch byte) bool {
|
|
return ch < 128 && !isNameContinue(ch)
|
|
}
|
|
|
|
func (p *Parser) skipSpaces() {
|
|
for p.pos < len(p.buf) && isSpace(p.buf[p.pos]) {
|
|
p.pos += 1
|
|
}
|
|
}
|
|
|
|
func (p *Parser) tryKeyword(kws ...string) bool {
|
|
save := p.pos
|
|
for _, kw := range kws {
|
|
p.skipSpaces()
|
|
if !(p.pos+len(kw) <= len(p.buf) && strings.EqualFold(p.buf[p.pos:p.pos+len(kw)], kw)) {
|
|
p.pos = save
|
|
return false
|
|
}
|
|
if p.pos+len(kw) < len(p.buf) && !isSeparator(p.buf[p.pos+len(kw)]) {
|
|
p.pos = save
|
|
return false
|
|
}
|
|
p.pos += len(kw)
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (p *Parser) tryPunctuation(tok string) bool {
|
|
p.skipSpaces()
|
|
if !(p.pos+len(tok) <= len(p.buf) && p.buf[p.pos:p.pos+len(tok)] == tok) {
|
|
return false
|
|
}
|
|
p.pos += len(tok)
|
|
return true
|
|
}
|
|
|
|
func (p *Parser) tryName() (string, bool) {
|
|
p.skipSpaces()
|
|
start, cur := p.pos, p.pos
|
|
if !(cur < len(p.buf) && isNameStart(p.buf[cur])) {
|
|
return "", false
|
|
}
|
|
cur++
|
|
for cur < len(p.buf) && isNameContinue(p.buf[cur]) {
|
|
cur++
|
|
}
|
|
p.pos = cur
|
|
return p.buf[start:cur], true
|
|
}
|
|
|
|
func (p *Parser) parseValue(out *Cell) error {
|
|
p.skipSpaces()
|
|
if p.pos >= len(p.buf) {
|
|
return errors.New("expect value")
|
|
}
|
|
ch := p.buf[p.pos]
|
|
if ch == '"' || ch == '\'' {
|
|
return p.parseString(out)
|
|
} else if isDigit(ch) || ch == '-' || ch == '+' {
|
|
return p.parseInt(out)
|
|
} else {
|
|
return errors.New("expect value")
|
|
}
|
|
}
|
|
|
|
func (p *Parser) parseString(out *Cell) error {
|
|
quote := p.buf[p.pos]
|
|
cur := p.pos + 1
|
|
for cur < len(p.buf) {
|
|
ch := p.buf[cur]
|
|
if ch == '\\' {
|
|
cur++
|
|
if cur < len(p.buf) && (p.buf[cur] == '"' || p.buf[cur] == '\'') {
|
|
out.Str = append(out.Str, p.buf[cur])
|
|
cur++
|
|
} else {
|
|
return errors.New("bad escape")
|
|
}
|
|
} else if ch == quote {
|
|
out.Type = TypeStr
|
|
p.pos = cur + 1
|
|
return nil
|
|
} else {
|
|
out.Str = append(out.Str, p.buf[cur])
|
|
cur++
|
|
}
|
|
}
|
|
return errors.New("string is not terminated")
|
|
}
|
|
|
|
func (p *Parser) parseInt(out *Cell) (err error) {
|
|
start, cur := p.pos, p.pos
|
|
if p.buf[cur] == '-' || p.buf[cur] == '+' {
|
|
cur++
|
|
}
|
|
for cur < len(p.buf) && isDigit(p.buf[cur]) {
|
|
cur++
|
|
}
|
|
|
|
if out.I64, err = strconv.ParseInt(p.buf[start:cur], 10, 64); err != nil {
|
|
return err
|
|
}
|
|
out.Type = TypeI64
|
|
p.pos = cur
|
|
return nil
|
|
}
|
|
|
|
func (p *Parser) parseEqual(out *NamedCell) error {
|
|
var ok bool
|
|
out.column, ok = p.tryName()
|
|
if !ok {
|
|
return errors.New("expect column")
|
|
}
|
|
if !p.tryPunctuation("=") {
|
|
return errors.New("expect =")
|
|
}
|
|
return p.parseValue(&out.value)
|
|
}
|
|
|
|
func (p *Parser) parseSelect(out *StmtSelect) error {
|
|
for !p.tryKeyword("FROM") {
|
|
if len(out.cols) > 0 && !p.tryPunctuation(",") {
|
|
return errors.New("expect comma")
|
|
}
|
|
if name, ok := p.tryName(); ok {
|
|
out.cols = append(out.cols, name)
|
|
} else {
|
|
return errors.New("expect column")
|
|
}
|
|
}
|
|
if len(out.cols) == 0 {
|
|
return errors.New("expect column list")
|
|
}
|
|
var ok bool
|
|
if out.table, ok = p.tryName(); !ok {
|
|
return errors.New("expect table name")
|
|
}
|
|
return p.parseWhere(&out.keys)
|
|
}
|
|
|
|
func (p *Parser) parseWhere(out *[]NamedCell) error {
|
|
if !p.tryKeyword("WHERE") {
|
|
return errors.New("expect keyword")
|
|
}
|
|
for !p.tryPunctuation(";") {
|
|
expr := NamedCell{}
|
|
if len(*out) > 0 && !p.tryKeyword("AND") {
|
|
return errors.New("expect AND")
|
|
}
|
|
if err := p.parseEqual(&expr); err != nil {
|
|
return err
|
|
}
|
|
*out = append(*out, expr)
|
|
}
|
|
if len(*out) == 0 {
|
|
return errors.New("expect where clause")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p *Parser) parseCommaList(item func() error) error {
|
|
if !p.tryPunctuation("(") {
|
|
return errors.New("expect (")
|
|
}
|
|
comma := false
|
|
for !p.tryPunctuation(")") {
|
|
if comma && !p.tryPunctuation(",") {
|
|
return errors.New("expect ,")
|
|
}
|
|
comma = true
|
|
if err := item(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p *Parser) parseNameItem(out *[]string) error {
|
|
name, ok := p.tryName()
|
|
if !ok {
|
|
return errors.New("expect name")
|
|
}
|
|
*out = append(*out, name)
|
|
return nil
|
|
}
|
|
|
|
func (p *Parser) parseCreateTableItem(out *StmtCreatTable) error {
|
|
if p.tryKeyword("PRIMARY", "KEY") {
|
|
return p.parseCommaList(func() error { return p.parseNameItem(&out.pkey) })
|
|
}
|
|
|
|
var ok bool
|
|
col := Column{}
|
|
if col.Name, ok = p.tryName(); !ok {
|
|
return errors.New("expect name")
|
|
}
|
|
kind, ok := p.tryName()
|
|
if !ok {
|
|
return errors.New("expect name")
|
|
}
|
|
switch kind {
|
|
case "int64":
|
|
col.Type = TypeI64
|
|
case "string":
|
|
col.Type = TypeStr
|
|
default:
|
|
return errors.New("unknown column type")
|
|
}
|
|
out.cols = append(out.cols, col)
|
|
return nil
|
|
}
|
|
|
|
func (p *Parser) parseCreateTable(out *StmtCreatTable) error {
|
|
var ok bool
|
|
if out.table, ok = p.tryName(); !ok {
|
|
return errors.New("expect table name")
|
|
}
|
|
err := p.parseCommaList(func() error { return p.parseCreateTableItem(out) })
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !p.tryPunctuation(";") {
|
|
return errors.New("expect ;")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p *Parser) parseValueItem(out *[]Cell) error {
|
|
cell := Cell{}
|
|
if err := p.parseValue(&cell); err != nil {
|
|
return err
|
|
}
|
|
*out = append(*out, cell)
|
|
return nil
|
|
}
|
|
|
|
func (p *Parser) parseInsert(out *StmtInsert) error {
|
|
var ok bool
|
|
if out.table, ok = p.tryName(); !ok {
|
|
return errors.New("expect table name")
|
|
}
|
|
if !p.tryKeyword("VALUES") {
|
|
return errors.New("expect VALUES")
|
|
}
|
|
err := p.parseCommaList(func() error { return p.parseValueItem(&out.value) })
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !p.tryPunctuation(";") {
|
|
return errors.New("expect ;")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p *Parser) parseUpdate(out *StmtUpdate) error {
|
|
var ok bool
|
|
if out.table, ok = p.tryName(); !ok {
|
|
return errors.New("expect table name")
|
|
}
|
|
if !p.tryKeyword("SET") {
|
|
return errors.New("expect SET")
|
|
}
|
|
for !p.tryKeyword("WHERE") {
|
|
expr := NamedCell{}
|
|
if len(out.value) > 0 && !p.tryKeyword(",") {
|
|
return errors.New("expect ,")
|
|
}
|
|
if err := p.parseEqual(&expr); err != nil {
|
|
return err
|
|
}
|
|
out.value = append(out.value, expr)
|
|
}
|
|
if len(out.value) == 0 {
|
|
return errors.New("expect assignment list")
|
|
}
|
|
p.pos -= len("WHERE")
|
|
return p.parseWhere(&out.keys)
|
|
}
|
|
|
|
func (p *Parser) parseDelete(out *StmtDelete) error {
|
|
var ok bool
|
|
if out.table, ok = p.tryName(); !ok {
|
|
return errors.New("expect table name")
|
|
}
|
|
return p.parseWhere(&out.keys)
|
|
}
|
|
|
|
func (p *Parser) parseStmt() (out interface{}, err error) {
|
|
if p.tryKeyword("SELECT") {
|
|
stmt := &StmtSelect{}
|
|
err = p.parseSelect(stmt)
|
|
out = stmt
|
|
} else if p.tryKeyword("CREATE", "TABLE") {
|
|
stmt := &StmtCreatTable{}
|
|
err = p.parseCreateTable(stmt)
|
|
out = stmt
|
|
} else if p.tryKeyword("INSERT", "INTO") {
|
|
stmt := &StmtInsert{}
|
|
err = p.parseInsert(stmt)
|
|
out = stmt
|
|
} else if p.tryKeyword("UPDATE") {
|
|
stmt := &StmtUpdate{}
|
|
err = p.parseUpdate(stmt)
|
|
out = stmt
|
|
} else if p.tryKeyword("DELETE", "FROM") {
|
|
stmt := &StmtDelete{}
|
|
err = p.parseDelete(stmt)
|
|
out = stmt
|
|
} else {
|
|
err = errors.New("unknown statement")
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (p *Parser) isEnd() bool {
|
|
p.skipSpaces()
|
|
return p.pos >= len(p.buf)
|
|
}
|
|
|
|
// QzBQWVJJOUhU https://trialofcode.org/
|