mirror of
https://github.com/MontFerret/ferret.git
synced 2025-08-15 20:02:56 +02:00
Refactor error handling for LIMIT clause: enhance diagnostics for dangling commas, improve error messages and hints, and introduce common error matching logic for better clarity and maintainability.
This commit is contained in:
@@ -10,6 +10,7 @@ func AnalyzeSyntaxError(src *file.Source, err *CompilationError, offending *Toke
|
|||||||
matchers := []SyntaxErrorMatcher{
|
matchers := []SyntaxErrorMatcher{
|
||||||
matchMissingAssignmentValue,
|
matchMissingAssignmentValue,
|
||||||
matchForLoopErrors,
|
matchForLoopErrors,
|
||||||
|
matchCommonErrors,
|
||||||
matchMissingReturnValue,
|
matchMissingReturnValue,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -114,33 +114,24 @@ func has(msg string, substr string) bool {
|
|||||||
return strings.Contains(strings.ToLower(msg), strings.ToLower(substr))
|
return strings.Contains(strings.ToLower(msg), strings.ToLower(substr))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isNoAlternative(msg string) bool {
|
||||||
|
return has(msg, "no viable alternative at input")
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractNoAlternativeInput(msg string) string {
|
||||||
|
re := regexp.MustCompile(`no viable alternative at input\s+(?P<input>.+)`)
|
||||||
|
match := re.FindStringSubmatch(msg)
|
||||||
|
|
||||||
|
return strings.Trim(match[re.SubexpIndex("input")], "'")
|
||||||
|
}
|
||||||
|
|
||||||
func isExtraneous(msg string) bool {
|
func isExtraneous(msg string) bool {
|
||||||
return has(msg, "extraneous input")
|
return has(msg, "extraneous input")
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseExtraneousInput(msg string) string {
|
func extractExtraneousInput(msg string) string {
|
||||||
re := regexp.MustCompile(`extraneous input\s+(?P<input>.+?)\s+expecting`)
|
re := regexp.MustCompile(`extraneous input\s+(?P<input>.+?)\s+expecting`)
|
||||||
match := re.FindStringSubmatch(msg)
|
match := re.FindStringSubmatch(msg)
|
||||||
return match[re.SubexpIndex("input")]
|
|
||||||
}
|
return strings.Trim(match[re.SubexpIndex("input")], "'")
|
||||||
|
|
||||||
func parseExtraneousInputAll(msg string) (string, []string) {
|
|
||||||
rx := regexp.MustCompile(`extraneous input\s+(?P<input>.+?)\s+expecting\s+\{(?P<expected>.+?)\}`)
|
|
||||||
matches := rx.FindStringSubmatch(msg)
|
|
||||||
|
|
||||||
if len(matches) != 3 {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
input := strings.TrimSpace(matches[1])
|
|
||||||
expectedRaw := strings.TrimSpace(matches[2])
|
|
||||||
var expected []string
|
|
||||||
|
|
||||||
for _, part := range strings.Split(expectedRaw, ",") {
|
|
||||||
part = strings.TrimSpace(part)
|
|
||||||
part = strings.Trim(part, "'")
|
|
||||||
expected = append(expected, part)
|
|
||||||
}
|
|
||||||
|
|
||||||
return input, expected
|
|
||||||
}
|
}
|
||||||
|
23
pkg/compiler/internal/diagnostics/match_common_errors.go
Normal file
23
pkg/compiler/internal/diagnostics/match_common_errors.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package diagnostics
|
||||||
|
|
||||||
|
import "github.com/MontFerret/ferret/pkg/file"
|
||||||
|
|
||||||
|
func matchCommonErrors(src *file.Source, err *CompilationError, offending *TokenNode) bool {
|
||||||
|
if isNoAlternative(err.Message) {
|
||||||
|
if is(offending.Prev(), ",") {
|
||||||
|
span := spanFromTokenSafe(offending.Prev().Token(), src)
|
||||||
|
span.Start++
|
||||||
|
span.End++
|
||||||
|
|
||||||
|
err.Message = "Expected expression after ','"
|
||||||
|
err.Hint = "Did you forget to provide a value?"
|
||||||
|
err.Spans = []ErrorSpan{
|
||||||
|
NewMainErrorSpan(span, "missing value"),
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
@@ -1,6 +1,8 @@
|
|||||||
package diagnostics
|
package diagnostics
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/MontFerret/ferret/pkg/file"
|
"github.com/MontFerret/ferret/pkg/file"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -93,9 +95,9 @@ func matchForLoopErrors(src *file.Source, err *CompilationError, offending *Toke
|
|||||||
}
|
}
|
||||||
|
|
||||||
if isExtraneous(err.Message) {
|
if isExtraneous(err.Message) {
|
||||||
input := parseExtraneousInput(err.Message)
|
input := extractExtraneousInput(err.Message)
|
||||||
|
|
||||||
if input != "','" {
|
if input != "," {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,5 +124,47 @@ func matchForLoopErrors(src *file.Source, err *CompilationError, offending *Toke
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isNoAlternative(err.Message) {
|
||||||
|
if is(prev, ",") {
|
||||||
|
var steps int
|
||||||
|
|
||||||
|
// We walk back two tokens to find if the keyword is LIMIT.
|
||||||
|
for ; steps < 2 && !is(prev, "LIMIT"); steps++ {
|
||||||
|
prev = prev.Prev()
|
||||||
|
}
|
||||||
|
|
||||||
|
if is(prev, "LIMIT") {
|
||||||
|
span := spanFromTokenSafe(offending.Prev().Token(), src)
|
||||||
|
span.Start++
|
||||||
|
span.End++
|
||||||
|
|
||||||
|
err.Message = "Dangling comma in LIMIT clause"
|
||||||
|
err.Hint = "LIMIT accepts one or two arguments. Did you forget to add a value?"
|
||||||
|
err.Spans = []ErrorSpan{
|
||||||
|
NewMainErrorSpan(span, "missing value"),
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else if is(offending, "LIMIT") {
|
||||||
|
input := extractNoAlternativeInput(err.Message)
|
||||||
|
tokens := strings.Fields(input)
|
||||||
|
|
||||||
|
if len(tokens) > 0 && has(tokens[len(tokens)-1], ",") {
|
||||||
|
span := spanFromTokenSafe(offending.Token(), src)
|
||||||
|
span.Start = span.End
|
||||||
|
span.End = span.Start + 1
|
||||||
|
|
||||||
|
err.Message = "Dangling comma in LIMIT clause"
|
||||||
|
err.Hint = "LIMIT accepts one or two arguments. Did you forget to add a value?"
|
||||||
|
err.Spans = []ErrorSpan{
|
||||||
|
NewMainErrorSpan(span, "missing value"),
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@@ -157,8 +157,30 @@ func TestSyntaxErrors(t *testing.T) {
|
|||||||
RETURN x
|
RETURN x
|
||||||
`, E{
|
`, E{
|
||||||
Kind: compiler.SyntaxError,
|
Kind: compiler.SyntaxError,
|
||||||
Message: "---",
|
Message: "Dangling comma in LIMIT clause",
|
||||||
Hint: "FILTER requires a boolean expression.",
|
Hint: "LIMIT accepts one or two arguments. Did you forget to add a value?",
|
||||||
}, "LIMIT unexpected comma 2"),
|
}, "LIMIT unexpected comma 2"),
|
||||||
|
ErrorCase(
|
||||||
|
`
|
||||||
|
LET users = []
|
||||||
|
FOR x IN users
|
||||||
|
LIMIT ,
|
||||||
|
RETURN x
|
||||||
|
`, E{
|
||||||
|
Kind: compiler.SyntaxError,
|
||||||
|
Message: "Dangling comma in LIMIT clause",
|
||||||
|
Hint: "LIMIT accepts one or two arguments. Did you forget to add a value?",
|
||||||
|
}, "LIMIT unexpected comma 3"),
|
||||||
|
ErrorCase(
|
||||||
|
`
|
||||||
|
LET users = []
|
||||||
|
FOR x IN users
|
||||||
|
LIMIT,
|
||||||
|
RETURN x
|
||||||
|
`, E{
|
||||||
|
Kind: compiler.SyntaxError,
|
||||||
|
Message: "Dangling comma in LIMIT clause",
|
||||||
|
Hint: "LIMIT accepts one or two arguments. Did you forget to add a value?",
|
||||||
|
}, "LIMIT unexpected comma 4"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user