mirror of
https://gitee.com/rulego/streamsql.git
synced 2026-03-14 14:27:27 +00:00
390 lines
11 KiB
Go
390 lines
11 KiB
Go
package rsql
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// ErrorType defines error types
|
|
type ErrorType int
|
|
|
|
const (
|
|
ErrorTypeSyntax ErrorType = iota
|
|
ErrorTypeLexical
|
|
ErrorTypeSemantics
|
|
ErrorTypeUnexpectedToken
|
|
ErrorTypeMissingToken
|
|
ErrorTypeInvalidExpression
|
|
ErrorTypeUnknownKeyword
|
|
ErrorTypeInvalidNumber
|
|
ErrorTypeUnterminatedString
|
|
ErrorTypeMaxIterations
|
|
ErrorTypeUnknownFunction
|
|
)
|
|
|
|
// ParseError enhanced parsing error structure
|
|
type ParseError struct {
|
|
Type ErrorType
|
|
Message string
|
|
Position int
|
|
Line int
|
|
Column int
|
|
Token string
|
|
Expected []string
|
|
Suggestions []string
|
|
Context string
|
|
Recoverable bool
|
|
}
|
|
|
|
// Error implements the error interface
|
|
func (e *ParseError) Error() string {
|
|
var builder strings.Builder
|
|
|
|
// Basic error information
|
|
builder.WriteString(fmt.Sprintf("[%s] %s", e.getErrorTypeName(), e.Message))
|
|
|
|
// Position information
|
|
if e.Line > 0 && e.Column > 0 {
|
|
builder.WriteString(fmt.Sprintf(" at line %d, column %d", e.Line, e.Column))
|
|
} else if e.Position >= 0 {
|
|
builder.WriteString(fmt.Sprintf(" at position %d", e.Position))
|
|
}
|
|
|
|
// Current token information
|
|
if e.Token != "" {
|
|
builder.WriteString(fmt.Sprintf(" (found '%s')", e.Token))
|
|
}
|
|
|
|
// Expected token
|
|
if len(e.Expected) > 0 {
|
|
builder.WriteString(fmt.Sprintf(", expected: %s", strings.Join(e.Expected, ", ")))
|
|
}
|
|
|
|
// Context information
|
|
if e.Context != "" {
|
|
builder.WriteString(fmt.Sprintf("\nContext: %s", e.Context))
|
|
}
|
|
|
|
// Suggestions
|
|
if len(e.Suggestions) > 0 {
|
|
builder.WriteString(fmt.Sprintf("\nSuggestions: %s", strings.Join(e.Suggestions, "; ")))
|
|
}
|
|
|
|
return builder.String()
|
|
}
|
|
|
|
// getErrorTypeName gets error type name
|
|
func (e *ParseError) getErrorTypeName() string {
|
|
switch e.Type {
|
|
case ErrorTypeSyntax:
|
|
return "SYNTAX_ERROR"
|
|
case ErrorTypeLexical:
|
|
return "LEXICAL_ERROR"
|
|
case ErrorTypeSemantics:
|
|
return "SEMANTIC_ERROR"
|
|
case ErrorTypeUnexpectedToken:
|
|
return "UNEXPECTED_TOKEN"
|
|
case ErrorTypeMissingToken:
|
|
return "MISSING_TOKEN"
|
|
case ErrorTypeInvalidExpression:
|
|
return "INVALID_EXPRESSION"
|
|
case ErrorTypeUnknownKeyword:
|
|
return "UNKNOWN_KEYWORD"
|
|
case ErrorTypeInvalidNumber:
|
|
return "INVALID_NUMBER"
|
|
case ErrorTypeUnterminatedString:
|
|
return "UNTERMINATED_STRING"
|
|
case ErrorTypeMaxIterations:
|
|
return "MAX_ITERATIONS"
|
|
case ErrorTypeUnknownFunction:
|
|
return "UNKNOWN_FUNCTION"
|
|
default:
|
|
return "UNKNOWN_ERROR"
|
|
}
|
|
}
|
|
|
|
// IsRecoverable checks if error is recoverable
|
|
func (e *ParseError) IsRecoverable() bool {
|
|
return e.Recoverable
|
|
}
|
|
|
|
// ErrorRecovery error recovery strategy
|
|
type ErrorRecovery struct {
|
|
parser *Parser
|
|
errors []*ParseError
|
|
}
|
|
|
|
// NewErrorRecovery creates error recovery instance
|
|
func NewErrorRecovery(parser *Parser) *ErrorRecovery {
|
|
return &ErrorRecovery{
|
|
parser: parser,
|
|
errors: make([]*ParseError, 0),
|
|
}
|
|
}
|
|
|
|
// AddError adds an error
|
|
func (er *ErrorRecovery) AddError(err *ParseError) {
|
|
er.errors = append(er.errors, err)
|
|
}
|
|
|
|
// GetErrors gets all errors
|
|
func (er *ErrorRecovery) GetErrors() []*ParseError {
|
|
return er.errors
|
|
}
|
|
|
|
// HasErrors checks if there are errors
|
|
func (er *ErrorRecovery) HasErrors() bool {
|
|
return len(er.errors) > 0
|
|
}
|
|
|
|
// RecoverFromError recovers from error
|
|
func (er *ErrorRecovery) RecoverFromError(errorType ErrorType) bool {
|
|
switch errorType {
|
|
case ErrorTypeUnexpectedToken:
|
|
// Skip current token and continue parsing
|
|
er.parser.lexer.NextToken()
|
|
return true
|
|
case ErrorTypeMissingToken:
|
|
// Insert default token or skip
|
|
return true
|
|
case ErrorTypeInvalidExpression:
|
|
// Jump to next comma or keyword
|
|
return er.skipToNextDelimiter()
|
|
case ErrorTypeSyntax:
|
|
// Syntax errors also attempt recovery and continue parsing
|
|
return true
|
|
case ErrorTypeUnknownKeyword:
|
|
// Unknown keyword errors also attempt recovery
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// skipToNextDelimiter jumps to next delimiter
|
|
func (er *ErrorRecovery) skipToNextDelimiter() bool {
|
|
maxSkip := 10
|
|
skipped := 0
|
|
|
|
for skipped < maxSkip {
|
|
tok := er.parser.lexer.NextToken()
|
|
if tok.Type == TokenEOF {
|
|
return false
|
|
}
|
|
if tok.Type == TokenComma || tok.Type == TokenFROM ||
|
|
tok.Type == TokenWHERE || tok.Type == TokenGROUP {
|
|
return true
|
|
}
|
|
skipped++
|
|
}
|
|
return false
|
|
}
|
|
|
|
// CreateSyntaxError creates syntax error
|
|
func CreateSyntaxError(message string, position int, token string, expected []string) *ParseError {
|
|
line, column := calculateLineColumn(position)
|
|
return &ParseError{
|
|
Type: ErrorTypeSyntax,
|
|
Message: message,
|
|
Position: position,
|
|
Line: line,
|
|
Column: column,
|
|
Token: token,
|
|
Expected: expected,
|
|
Suggestions: generateSuggestions(token, expected),
|
|
Recoverable: true,
|
|
}
|
|
}
|
|
|
|
// CreateLexicalError creates lexical error
|
|
func CreateLexicalError(message string, position int, char byte) *ParseError {
|
|
line, column := calculateLineColumn(position)
|
|
return &ParseError{
|
|
Type: ErrorTypeLexical,
|
|
Message: message,
|
|
Position: position,
|
|
Line: line,
|
|
Column: column,
|
|
Token: string(char),
|
|
Suggestions: []string{"Check for invalid characters", "Ensure strings are properly closed"},
|
|
Recoverable: false,
|
|
}
|
|
}
|
|
|
|
// CreateLexicalErrorWithPosition creates lexical error with accurate position
|
|
func CreateLexicalErrorWithPosition(message string, position int, line int, column int, char byte) *ParseError {
|
|
return &ParseError{
|
|
Type: ErrorTypeLexical,
|
|
Message: message,
|
|
Position: position,
|
|
Line: line,
|
|
Column: column,
|
|
Token: string(char),
|
|
Suggestions: []string{"Check for invalid characters", "Ensure strings are properly closed"},
|
|
Recoverable: false,
|
|
}
|
|
}
|
|
|
|
// CreateUnexpectedTokenError creates unexpected token error
|
|
func CreateUnexpectedTokenError(found string, expected []string, position int) *ParseError {
|
|
line, column := calculateLineColumn(position)
|
|
return &ParseError{
|
|
Type: ErrorTypeUnexpectedToken,
|
|
Message: fmt.Sprintf("Unexpected token '%s'", found),
|
|
Position: position,
|
|
Line: line,
|
|
Column: column,
|
|
Token: found,
|
|
Expected: expected,
|
|
Suggestions: generateSuggestions(found, expected),
|
|
Recoverable: true,
|
|
}
|
|
}
|
|
|
|
// CreateMissingTokenError creates missing token error
|
|
func CreateMissingTokenError(expected string, position int) *ParseError {
|
|
line, column := calculateLineColumn(position)
|
|
return &ParseError{
|
|
Type: ErrorTypeMissingToken,
|
|
Message: fmt.Sprintf("Missing required token '%s'", expected),
|
|
Position: position,
|
|
Line: line,
|
|
Column: column,
|
|
Expected: []string{expected},
|
|
Suggestions: []string{fmt.Sprintf("Add missing '%s'", expected)},
|
|
Recoverable: true,
|
|
}
|
|
}
|
|
|
|
// CreateUnknownFunctionError creates unknown function error
|
|
func CreateUnknownFunctionError(functionName string, position int) *ParseError {
|
|
line, column := calculateLineColumn(position)
|
|
return &ParseError{
|
|
Type: ErrorTypeUnknownFunction,
|
|
Message: fmt.Sprintf("Unknown function '%s'", functionName),
|
|
Position: position,
|
|
Line: line,
|
|
Column: column,
|
|
Token: functionName,
|
|
Suggestions: generateFunctionSuggestions(functionName),
|
|
Recoverable: true,
|
|
}
|
|
}
|
|
|
|
// calculateLineColumn calculates line and column numbers
|
|
// Note: This is a simplified implementation, actual line/column should be provided by lexer
|
|
func calculateLineColumn(position int) (int, int) {
|
|
// Simplified implementation, should be calculated based on input text
|
|
// Returns estimated value based on position
|
|
line := position/50 + 1 // 假设平均每行50个字符
|
|
column := position%50 + 1
|
|
return line, column
|
|
}
|
|
|
|
// generateSuggestions generates suggestions
|
|
func generateSuggestions(found string, expected []string) []string {
|
|
suggestions := make([]string, 0)
|
|
|
|
if len(expected) > 0 {
|
|
suggestions = append(suggestions, fmt.Sprintf("Try using '%s' instead of '%s'", expected[0], found))
|
|
}
|
|
|
|
// Generate suggestions based on common error patterns
|
|
switch strings.ToUpper(found) {
|
|
case "SELCT":
|
|
suggestions = append(suggestions, "Did you mean 'SELECT'?")
|
|
case "FORM":
|
|
suggestions = append(suggestions, "Did you mean 'FROM'?")
|
|
case "WHER":
|
|
suggestions = append(suggestions, "Did you mean 'WHERE'?")
|
|
case "GROPU":
|
|
suggestions = append(suggestions, "Did you mean 'GROUP'?")
|
|
case "ODER":
|
|
suggestions = append(suggestions, "Did you mean 'ORDER'?")
|
|
}
|
|
|
|
return suggestions
|
|
}
|
|
|
|
// generateFunctionSuggestions generates function suggestions
|
|
func generateFunctionSuggestions(functionName string) []string {
|
|
suggestions := make([]string, 0)
|
|
|
|
// Generate suggestions based on common function name misspellings
|
|
funcLower := strings.ToLower(functionName)
|
|
switch {
|
|
case strings.Contains(funcLower, "coun"):
|
|
suggestions = append(suggestions, "Did you mean 'COUNT' function?")
|
|
case strings.Contains(funcLower, "su") && strings.Contains(funcLower, "m"):
|
|
suggestions = append(suggestions, "Did you mean 'SUM' function?")
|
|
case strings.Contains(funcLower, "av") && strings.Contains(funcLower, "g"):
|
|
suggestions = append(suggestions, "Did you mean 'AVG' function?")
|
|
case strings.Contains(funcLower, "ma") && strings.Contains(funcLower, "x"):
|
|
suggestions = append(suggestions, "Did you mean 'MAX' function?")
|
|
case strings.Contains(funcLower, "mi") && strings.Contains(funcLower, "n"):
|
|
suggestions = append(suggestions, "Did you mean 'MIN' function?")
|
|
case strings.Contains(funcLower, "upp"):
|
|
suggestions = append(suggestions, "Did you mean 'UPPER' function?")
|
|
case strings.Contains(funcLower, "low"):
|
|
suggestions = append(suggestions, "Did you mean 'LOWER' function?")
|
|
case strings.Contains(funcLower, "len"):
|
|
suggestions = append(suggestions, "Did you mean 'LENGTH' function?")
|
|
case strings.Contains(funcLower, "sub"):
|
|
suggestions = append(suggestions, "Did you mean 'SUBSTRING' function?")
|
|
case strings.Contains(funcLower, "con"):
|
|
suggestions = append(suggestions, "Did you mean 'CONCAT' function?")
|
|
case strings.Contains(funcLower, "abs"):
|
|
suggestions = append(suggestions, "Did you mean 'ABS' function?")
|
|
case strings.Contains(funcLower, "sqrt"):
|
|
suggestions = append(suggestions, "Did you mean 'SQRT' function?")
|
|
case strings.Contains(funcLower, "round"):
|
|
suggestions = append(suggestions, "Did you mean 'ROUND' function?")
|
|
case strings.Contains(funcLower, "floor"):
|
|
suggestions = append(suggestions, "Did you mean 'FLOOR' function?")
|
|
case strings.Contains(funcLower, "ceil"):
|
|
suggestions = append(suggestions, "Did you mean 'CEILING' function?")
|
|
}
|
|
|
|
// Generic suggestions
|
|
suggestions = append(suggestions, "Check if the function name is spelled correctly")
|
|
suggestions = append(suggestions, "Confirm that the function is registered or is a built-in function")
|
|
suggestions = append(suggestions, "View the list of available functions")
|
|
|
|
return suggestions
|
|
}
|
|
|
|
// CreateSemanticError creates semantic error
|
|
func CreateSemanticError(message string, position int) *ParseError {
|
|
line, column := calculateLineColumn(position)
|
|
return &ParseError{
|
|
Type: ErrorTypeSemantics,
|
|
Message: message,
|
|
Position: position,
|
|
Line: line,
|
|
Column: column,
|
|
Suggestions: []string{"Check semantic rules", "Verify data types and constraints"},
|
|
Recoverable: true,
|
|
}
|
|
}
|
|
|
|
// FormatErrorContext formats error context
|
|
func FormatErrorContext(input string, position int, contextLength int) string {
|
|
if position < 0 || position >= len(input) {
|
|
return ""
|
|
}
|
|
|
|
start := position - contextLength
|
|
if start < 0 {
|
|
start = 0
|
|
}
|
|
|
|
end := position + contextLength
|
|
if end > len(input) {
|
|
end = len(input)
|
|
}
|
|
|
|
context := input[start:end]
|
|
pointer := strings.Repeat(" ", position-start) + "^"
|
|
|
|
return fmt.Sprintf("%s\n%s", context, pointer)
|
|
} |