diff --git a/pkg/compiler/internal/diagnostics/error_analyzer.go b/pkg/compiler/internal/diagnostics/error_analyzer.go
index afe4527b..0c9b402a 100644
--- a/pkg/compiler/internal/diagnostics/error_analyzer.go
+++ b/pkg/compiler/internal/diagnostics/error_analyzer.go
@@ -10,6 +10,7 @@ func AnalyzeSyntaxError(src *file.Source, err *CompilationError, offending *Toke
matchers := []SyntaxErrorMatcher{
matchMissingAssignmentValue,
matchForLoopErrors,
+ matchCommonErrors,
matchMissingReturnValue,
}
diff --git a/pkg/compiler/internal/diagnostics/helpers.go b/pkg/compiler/internal/diagnostics/helpers.go
index 56d7c38f..21e15c6e 100644
--- a/pkg/compiler/internal/diagnostics/helpers.go
+++ b/pkg/compiler/internal/diagnostics/helpers.go
@@ -114,33 +114,24 @@ func has(msg string, substr string) bool {
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.+)`)
+ match := re.FindStringSubmatch(msg)
+
+ return strings.Trim(match[re.SubexpIndex("input")], "'")
+}
+
func isExtraneous(msg string) bool {
return has(msg, "extraneous input")
}
-func parseExtraneousInput(msg string) string {
+func extractExtraneousInput(msg string) string {
re := regexp.MustCompile(`extraneous input\s+(?P.+?)\s+expecting`)
match := re.FindStringSubmatch(msg)
- return match[re.SubexpIndex("input")]
-}
-
-func parseExtraneousInputAll(msg string) (string, []string) {
- rx := regexp.MustCompile(`extraneous input\s+(?P.+?)\s+expecting\s+\{(?P.+?)\}`)
- 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
+
+ return strings.Trim(match[re.SubexpIndex("input")], "'")
}
diff --git a/pkg/compiler/internal/diagnostics/match_common_errors.go b/pkg/compiler/internal/diagnostics/match_common_errors.go
new file mode 100644
index 00000000..316592c5
--- /dev/null
+++ b/pkg/compiler/internal/diagnostics/match_common_errors.go
@@ -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
+}
diff --git a/pkg/compiler/internal/diagnostics/match_for_loop_errors.go b/pkg/compiler/internal/diagnostics/match_for_loop_errors.go
index 6a4bc917..ff8fef06 100644
--- a/pkg/compiler/internal/diagnostics/match_for_loop_errors.go
+++ b/pkg/compiler/internal/diagnostics/match_for_loop_errors.go
@@ -1,6 +1,8 @@
package diagnostics
import (
+ "strings"
+
"github.com/MontFerret/ferret/pkg/file"
)
@@ -93,9 +95,9 @@ func matchForLoopErrors(src *file.Source, err *CompilationError, offending *Toke
}
if isExtraneous(err.Message) {
- input := parseExtraneousInput(err.Message)
+ input := extractExtraneousInput(err.Message)
- if input != "','" {
+ if input != "," {
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
}
diff --git a/test/integration/compiler/compiler_errors_syntax_test.go b/test/integration/compiler/compiler_errors_syntax_test.go
index 848b220f..60ecdca2 100644
--- a/test/integration/compiler/compiler_errors_syntax_test.go
+++ b/test/integration/compiler/compiler_errors_syntax_test.go
@@ -157,8 +157,30 @@ func TestSyntaxErrors(t *testing.T) {
RETURN x
`, E{
Kind: compiler.SyntaxError,
- Message: "---",
- Hint: "FILTER requires a boolean expression.",
+ Message: "Dangling comma in LIMIT clause",
+ Hint: "LIMIT accepts one or two arguments. Did you forget to add a value?",
}, "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"),
})
}