mirror of
https://github.com/hanzo-ds/sqlparser.git
synced 2026-10-30 20:53:50 +00:00
425 lines
9.3 KiB
Go
425 lines
9.3 KiB
Go
package parser
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
type Parser struct {
|
|
lexer *Lexer
|
|
}
|
|
|
|
func NewParser(buffer string) *Parser {
|
|
return &Parser{
|
|
lexer: NewLexer(buffer),
|
|
}
|
|
}
|
|
|
|
func (p *Parser) lastTokenKind() TokenKind {
|
|
if p.last() == nil {
|
|
return TokenKindEOF
|
|
}
|
|
return p.last().Kind
|
|
}
|
|
|
|
func (p *Parser) last() *Token {
|
|
return p.lexer.lastToken
|
|
}
|
|
|
|
func (p *Parser) End() Pos {
|
|
if p.last() == nil {
|
|
return Pos(p.lexer.current + 1)
|
|
}
|
|
return p.last().End
|
|
}
|
|
|
|
func (p *Parser) Pos() Pos {
|
|
last := p.last()
|
|
if last == nil {
|
|
return Pos(p.lexer.current)
|
|
}
|
|
return last.Pos
|
|
}
|
|
|
|
func (p *Parser) matchTokenKind(kind TokenKind) bool {
|
|
return p.lastTokenKind() == kind ||
|
|
(kind == TokenKindIdent && p.lastTokenKind() == TokenKindKeyword)
|
|
}
|
|
|
|
// expectTokenKind consumes the last token if it is the given kind.
|
|
func (p *Parser) expectTokenKind(kind TokenKind) error {
|
|
if lastToken := p.tryConsumeTokenKind(kind); lastToken != nil {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("expected the last token kind is: %s, but got %s", kind, p.lastTokenKind())
|
|
}
|
|
|
|
func (p *Parser) tryConsumeTokenKind(kind TokenKind) *Token {
|
|
if p.matchTokenKind(kind) {
|
|
lastToken := p.last()
|
|
_ = p.lexer.consumeToken()
|
|
return lastToken
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p *Parser) matchKeyword(keyword string) bool {
|
|
return p.matchTokenKind(TokenKindKeyword) && strings.EqualFold(p.last().String, keyword)
|
|
}
|
|
|
|
func (p *Parser) matchOneOfKeywords(keywords ...string) bool {
|
|
for _, keyword := range keywords {
|
|
if p.matchKeyword(keyword) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (p *Parser) expectKeyword(keyword string) error {
|
|
if !p.matchKeyword(keyword) {
|
|
return fmt.Errorf("expected keyword: %s, but got %s", keyword, p.lastTokenKind())
|
|
}
|
|
_ = p.lexer.consumeToken()
|
|
return nil
|
|
}
|
|
|
|
func (p *Parser) tryConsumeKeywords(keywords ...string) bool {
|
|
savedState := p.lexer.saveState()
|
|
for _, keyword := range keywords {
|
|
if !p.matchKeyword(keyword) {
|
|
p.lexer.restoreState(savedState)
|
|
return false
|
|
}
|
|
_ = p.lexer.consumeToken()
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (p *Parser) tryParseIdent() *Ident {
|
|
if p.lastTokenKind() != TokenKindIdent {
|
|
return nil
|
|
}
|
|
lastToken := p.last()
|
|
_ = p.lexer.consumeToken()
|
|
return &Ident{
|
|
NamePos: lastToken.Pos,
|
|
NameEnd: lastToken.End,
|
|
Name: lastToken.String,
|
|
QuoteType: lastToken.QuoteType,
|
|
}
|
|
}
|
|
|
|
func (p *Parser) parseIdent() (*Ident, error) {
|
|
lastToken := p.last()
|
|
if err := p.expectTokenKind(TokenKindIdent); err != nil {
|
|
return nil, err
|
|
}
|
|
ident := &Ident{
|
|
NamePos: lastToken.Pos,
|
|
NameEnd: lastToken.End,
|
|
Name: lastToken.String,
|
|
QuoteType: lastToken.QuoteType,
|
|
}
|
|
return ident, nil
|
|
}
|
|
|
|
func (p *Parser) parseIdentOrStar() (*Ident, error) {
|
|
switch {
|
|
case p.matchTokenKind(TokenKindIdent):
|
|
return p.parseIdent()
|
|
case p.matchTokenKind("*"):
|
|
lastToken := p.last()
|
|
_ = p.lexer.consumeToken()
|
|
return &Ident{
|
|
NamePos: lastToken.Pos,
|
|
NameEnd: lastToken.End,
|
|
Name: lastToken.String,
|
|
}, nil
|
|
default:
|
|
return nil, fmt.Errorf("expected <ident> or *, but got %q", p.lastTokenKind())
|
|
}
|
|
}
|
|
|
|
func (p *Parser) parseIdentOrString() (*Ident, error) {
|
|
switch {
|
|
case p.matchTokenKind(TokenKindIdent):
|
|
return p.parseIdent()
|
|
case p.matchTokenKind(TokenKindString):
|
|
lastToken := p.last()
|
|
_ = p.lexer.consumeToken()
|
|
return &Ident{
|
|
NamePos: lastToken.Pos,
|
|
NameEnd: lastToken.End,
|
|
Name: lastToken.String,
|
|
QuoteType: SingleQuote, // Treat string literals as single-quoted identifiers
|
|
}, nil
|
|
default:
|
|
return nil, fmt.Errorf("expected <ident> or <string>, but got %q", p.lastTokenKind())
|
|
}
|
|
}
|
|
|
|
func (p *Parser) tryParseDotIdent(_ Pos) (*Ident, error) {
|
|
if p.tryConsumeTokenKind(TokenKindDot) == nil {
|
|
return nil, nil // nolint
|
|
}
|
|
return p.parseIdent()
|
|
}
|
|
|
|
func (p *Parser) tryParseDotIdentOrString(_ Pos) (*Ident, error) {
|
|
if p.tryConsumeTokenKind(TokenKindDot) == nil {
|
|
return nil, nil // nolint
|
|
}
|
|
return p.parseIdentOrString()
|
|
}
|
|
|
|
func (p *Parser) parseUUID() (*UUID, error) {
|
|
if err := p.expectKeyword(KeywordUuid); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
uuidString, err := p.parseString(p.Pos())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &UUID{
|
|
Value: uuidString,
|
|
}, nil
|
|
}
|
|
|
|
func (p *Parser) tryParseUUID() (*UUID, error) {
|
|
if !p.matchKeyword(KeywordUuid) {
|
|
return nil, nil // nolint
|
|
}
|
|
return p.parseUUID()
|
|
}
|
|
|
|
func (p *Parser) tryParseComment() (*StringLiteral, error) {
|
|
if !p.tryConsumeKeywords(KeywordComment) {
|
|
return nil, nil
|
|
}
|
|
return p.parseString(p.Pos())
|
|
}
|
|
|
|
func (p *Parser) tryParseIfExists() (bool, error) {
|
|
if !p.tryConsumeKeywords(KeywordIf) {
|
|
return false, nil
|
|
}
|
|
|
|
if err := p.expectKeyword(KeywordExists); err != nil {
|
|
return false, err
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
func (p *Parser) tryParseIfNotExists() (bool, error) {
|
|
if !p.tryConsumeKeywords(KeywordIf) {
|
|
return false, nil
|
|
}
|
|
|
|
if err := p.expectKeyword(KeywordNot); err != nil {
|
|
return false, err
|
|
}
|
|
|
|
if err := p.expectKeyword(KeywordExists); err != nil {
|
|
return false, err
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
func (p *Parser) tryParseNull(pos Pos) *NullLiteral {
|
|
if !p.tryConsumeKeywords(KeywordNull) {
|
|
return nil
|
|
}
|
|
return &NullLiteral{NullPos: pos}
|
|
}
|
|
|
|
func (p *Parser) tryParseNotNull(pos Pos) (*NotNullLiteral, error) {
|
|
if !p.tryConsumeKeywords(KeywordNot) {
|
|
return nil, nil // nolint
|
|
}
|
|
notNull := &NotNullLiteral{NotPos: pos}
|
|
|
|
nullPos := p.Pos()
|
|
if err := p.expectKeyword(KeywordNull); err != nil {
|
|
return notNull, err
|
|
}
|
|
notNull.NullLiteral = &NullLiteral{NullPos: nullPos}
|
|
return notNull, nil
|
|
}
|
|
|
|
func (p *Parser) parseDecimal(pos Pos) (*NumberLiteral, error) {
|
|
number, err := p.parseNumber(pos)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if number.Base != 10 {
|
|
return nil, fmt.Errorf("invalid decimal literal: %q", number.Literal)
|
|
}
|
|
return number, nil
|
|
}
|
|
|
|
func (p *Parser) parseNumber(pos Pos) (*NumberLiteral, error) {
|
|
var err error
|
|
|
|
lastToken := p.last()
|
|
switch {
|
|
case p.matchTokenKind(TokenKindInt):
|
|
err = p.expectTokenKind(TokenKindInt)
|
|
case p.matchTokenKind(TokenKindFloat):
|
|
err = p.expectTokenKind(TokenKindFloat)
|
|
case p.matchTokenKind(TokenKindDot):
|
|
_ = p.lexer.consumeToken()
|
|
lastToken = p.last()
|
|
if err := p.expectTokenKind(TokenKindInt); err != nil {
|
|
return nil, err
|
|
}
|
|
if lastToken.Base != 10 {
|
|
return nil, fmt.Errorf("invalid decimal literal: %q", lastToken.String)
|
|
}
|
|
lastToken.String = "." + lastToken.String
|
|
lastToken.Kind = TokenKindFloat
|
|
default:
|
|
return nil, fmt.Errorf("expected <int> or <float>, but got %q", p.lastTokenKind())
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
number := &NumberLiteral{
|
|
NumPos: pos,
|
|
NumEnd: lastToken.End,
|
|
Literal: lastToken.String,
|
|
Base: lastToken.Base,
|
|
}
|
|
return number, nil
|
|
}
|
|
|
|
func (p *Parser) parseString(pos Pos) (*StringLiteral, error) {
|
|
lastToken := p.last()
|
|
if err := p.expectTokenKind(TokenKindString); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
str := &StringLiteral{
|
|
LiteralPos: pos,
|
|
LiteralEnd: lastToken.End,
|
|
Literal: lastToken.String,
|
|
}
|
|
return str, nil
|
|
}
|
|
|
|
func (p *Parser) parseLiteral(pos Pos) (Literal, error) {
|
|
switch {
|
|
case p.matchTokenKind(TokenKindInt), p.matchTokenKind(TokenKindFloat):
|
|
return p.parseNumber(pos)
|
|
case p.matchTokenKind(TokenKindString):
|
|
return p.parseString(pos)
|
|
case p.matchTokenKind(TokenKindIdent):
|
|
return p.parseIdent()
|
|
case p.matchKeyword(KeywordNull):
|
|
// accept the NULL keyword
|
|
return &NullLiteral{NullPos: pos}, nil
|
|
default:
|
|
return nil, fmt.Errorf("expected <int>, <string>, <ident> or keyword <NULL>, but got %q", p.lastTokenKind())
|
|
}
|
|
}
|
|
|
|
func (p *Parser) ParseNestedIdentifier(pos Pos) (*NestedIdentifier, error) {
|
|
ident, err := p.parseIdent()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dotIdent, err := p.tryParseDotIdent(p.Pos())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if dotIdent != nil {
|
|
return &NestedIdentifier{
|
|
Ident: ident,
|
|
DotIdent: dotIdent,
|
|
}, nil
|
|
}
|
|
return &NestedIdentifier{
|
|
Ident: ident,
|
|
}, nil
|
|
}
|
|
|
|
func (p *Parser) tryParseFormat(pos Pos) (*FormatClause, error) {
|
|
if !p.matchKeyword(KeywordFormat) {
|
|
return nil, nil // nolint
|
|
}
|
|
return p.parseFormat(pos)
|
|
}
|
|
|
|
func (p *Parser) parseFormat(pos Pos) (*FormatClause, error) {
|
|
if err := p.expectKeyword(KeywordFormat); err != nil {
|
|
return nil, err
|
|
}
|
|
formatIdent, err := p.parseIdent()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &FormatClause{
|
|
FormatPos: pos,
|
|
Format: formatIdent,
|
|
}, nil
|
|
}
|
|
|
|
func (p *Parser) wrapError(err error) error {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
|
|
lineNo := 0
|
|
column := 0
|
|
|
|
for i := 0; i < int(p.Pos()); i++ {
|
|
if p.lexer.input[i] == '\n' {
|
|
lineNo++
|
|
column = 0
|
|
} else {
|
|
column++
|
|
}
|
|
}
|
|
|
|
lines := strings.Split(p.lexer.input, "\n")
|
|
var buf strings.Builder
|
|
buf.WriteString(fmt.Sprintf("line %d:%d %s\n", lineNo, column, err.Error()))
|
|
for i, line := range lines {
|
|
if i == lineNo {
|
|
buf.WriteString(line)
|
|
buf.WriteByte('\n')
|
|
for j := 0; j < column; j++ {
|
|
buf.WriteByte(' ')
|
|
}
|
|
if p.last() != nil {
|
|
buf.WriteString(strings.Repeat("^", len(p.last().String)))
|
|
} else {
|
|
buf.WriteString("^")
|
|
}
|
|
buf.WriteByte('\n')
|
|
}
|
|
}
|
|
return errors.New(buf.String())
|
|
}
|
|
|
|
func (p *Parser) parseRatioExpr(pos Pos) (*RatioExpr, error) {
|
|
numerator, err := p.parseNumber(pos)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var denominator *NumberLiteral
|
|
if p.tryConsumeTokenKind(TokenKindDiv) != nil {
|
|
denominator, err = p.parseNumber(pos)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return &RatioExpr{
|
|
Numerator: numerator,
|
|
Denominator: denominator,
|
|
}, nil
|
|
}
|