1
0
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:
Tim Voronov
2025-08-07 14:21:46 -04:00
parent d26988fb2d
commit f1dc2b12a1
5 changed files with 108 additions and 27 deletions

View File

@@ -10,6 +10,7 @@ func AnalyzeSyntaxError(src *file.Source, err *CompilationError, offending *Toke
matchers := []SyntaxErrorMatcher{ matchers := []SyntaxErrorMatcher{
matchMissingAssignmentValue, matchMissingAssignmentValue,
matchForLoopErrors, matchForLoopErrors,
matchCommonErrors,
matchMissingReturnValue, matchMissingReturnValue,
} }

View File

@@ -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
} }

View 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
}

View File

@@ -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
} }

View File

@@ -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"),
}) })
} }