1
0
mirror of https://github.com/MontFerret/ferret.git synced 2025-08-13 19:52:52 +02:00

Enhance diagnostics for unclosed string literals: improve error messages and hints for missing opening and closing quotes, and add comprehensive test cases for better clarity and coverage.

This commit is contained in:
Tim Voronov
2025-08-12 13:07:00 -04:00
parent 56acc48b9c
commit a12943633e
3 changed files with 226 additions and 22 deletions

View File

@@ -106,6 +106,22 @@ func isQuote(input string) bool {
return false
}
func isValidString(input string) bool {
if input == "" {
return false
}
if isQuote(input) {
return true
}
if isQuote(input[0:1]) && isQuote(input[len(input)-1:]) {
return true
}
return false
}
func is(node *TokenNode, expected string) bool {
if node == nil {
return false
@@ -182,3 +198,14 @@ func extractExtraneousInput(msg string) string {
return input
}
func extractMismatchedInput(msg string) string {
re := regexp.MustCompile(`mismatched input\s+(?P<input>.+?)\s+expecting`)
match := re.FindStringSubmatch(msg)
input := match[re.SubexpIndex("input")]
input = strings.TrimPrefix(input, "'")
input = strings.TrimSuffix(input, "'")
return input
}

View File

@@ -42,23 +42,41 @@ func matchCommonErrors(src *file.Source, err *CompilationError, offending *Token
input := extractNoAlternativeInputs(err.Message)
token := input[len(input)-1]
if isQuote(token) {
span := spanFromTokenSafe(offending.Token(), src)
inputRaw := extractNoAlternativeInput(err.Message)
spaces := strings.Count(inputRaw, " ") + 1
span.Start += spaces
span.End += spaces
isMissingClosingQuote := isQuote(token)
isMissingOpeningQuote := isKeyword(offending.Prev()) && isQuote(token[len(token)-1:]) && !isValidString(token)
if isMissingClosingQuote || isMissingOpeningQuote {
var span file.Span
var typeOfQuote string
var quote string
if isMissingClosingQuote {
quote = token
typeOfQuote = "closing"
span = spanFromTokenSafe(offending.Token(), src)
inputRaw := extractNoAlternativeInput(err.Message)
spaces := strings.Count(inputRaw, " ") + 1
span.Start += spaces
span.End += spaces
} else {
quote = token[len(token)-1:]
typeOfQuote = "opening"
span = spanFromTokenSafe(offending.Prev().Token(), src)
span.Start += 2
span.End += 2
}
err.Message = "Unclosed string literal"
if token == "'" {
err.Hint = fmt.Sprintf("Add a matching \"%s\" to close the string.", token)
if quote == "'" {
err.Hint = fmt.Sprintf("Add a matching \"%s\" to close the string.", quote)
err.Spans = []ErrorSpan{
NewMainErrorSpan(span, fmt.Sprintf("missing closing \"%s\"", token)),
NewMainErrorSpan(span, fmt.Sprintf("missing %s \"%s\"", typeOfQuote, quote)),
}
} else {
err.Hint = fmt.Sprintf("Add a matching '%s' to close the string.", token)
err.Hint = fmt.Sprintf("Add a matching '%s' to close the string.", quote)
err.Spans = []ErrorSpan{
NewMainErrorSpan(span, fmt.Sprintf("missing closing '%s'", token)),
NewMainErrorSpan(span, fmt.Sprintf("missing %s '%s'", typeOfQuote, quote)),
}
}
@@ -222,25 +240,42 @@ func matchCommonErrors(src *file.Source, err *CompilationError, offending *Token
return true
}
}
token := extractExtraneousInput(err.Message)
if isExtraneous(err.Message) || isMismatched(err.Message) {
var token string
if isExtraneous(err.Message) {
token = extractExtraneousInput(err.Message)
} else {
token = extractMismatchedInput(err.Message)
}
if isQuote(token) {
span := spanFromTokenSafe(offending.Token(), src)
var span file.Span
var typeOfQuote string
if isKeyword(offending) {
span = spanFromTokenSafe(offending.Token(), src)
typeOfQuote = "closing"
} else {
span = spanFromTokenSafe(offending.Prev().Token(), src)
typeOfQuote = "opening"
}
span.Start += 2
span.End += 2
err.Message = "Unclosed string literal"
if token == "'" {
err.Hint = fmt.Sprintf("Add a matching \"%s\" to close the string.", token)
err.Spans = []ErrorSpan{
NewMainErrorSpan(span, fmt.Sprintf("missing closing \"%s\"", token)),
NewMainErrorSpan(span, fmt.Sprintf("missing %s \"%s\"", typeOfQuote, token)),
}
} else {
err.Hint = fmt.Sprintf("Add a matching '%s' to close the string.", token)
err.Spans = []ErrorSpan{
NewMainErrorSpan(span, fmt.Sprintf("missing closing '%s'", token)),
NewMainErrorSpan(span, fmt.Sprintf("missing %s '%s'", typeOfQuote, token)),
}
}

View File

@@ -45,7 +45,37 @@ func TestSyntaxErrors(t *testing.T) {
Kind: compiler.SyntaxError,
Message: "Unclosed string literal",
Hint: "Add a matching '\"' to close the string.",
}, "Incomplete string"),
}, "Incomplete string (closing quote missing)"),
ErrorCase(
`
LET i = "foo bar
RETURN i
`, E{
Kind: compiler.SyntaxError,
Message: "Unclosed string literal",
Hint: "Add a matching '\"' to close the string.",
}, "Incomplete multi-string (closing quote missing)"),
ErrorCase(
`
LET i = foo"
RETURN i
`, E{
Kind: compiler.SyntaxError,
Message: "Unclosed string literal",
Hint: "Add a matching '\"' to close the string.",
}, "Incomplete string (opening quote missing)"),
ErrorCase(
`
LET i = foo bar"
RETURN i
`, E{
Kind: compiler.SyntaxError,
Message: "Unclosed string literal",
Hint: "Add a matching '\"' to close the string.",
}, "Incomplete multi-string (opening quote missing)"),
ErrorCase(
`
@@ -55,7 +85,37 @@ func TestSyntaxErrors(t *testing.T) {
Kind: compiler.SyntaxError,
Message: "Unclosed string literal",
Hint: "Add a matching \"'\" to close the string.",
}, "Incomplete string 2"),
}, "Incomplete string (closing quote missing) 2"),
ErrorCase(
`
LET i = 'foo bar
RETURN i
`, E{
Kind: compiler.SyntaxError,
Message: "Unclosed string literal",
Hint: "Add a matching \"'\" to close the string.",
}, "Incomplete multi-string (closing quote missing) 2"),
ErrorCase(
`
LET i = foo'
RETURN i
`, E{
Kind: compiler.SyntaxError,
Message: "Unclosed string literal",
Hint: "Add a matching \"'\" to close the string.",
}, "Incomplete string (opening quote missing) 2"),
ErrorCase(
`
LET i = foo bar'
RETURN i
`, E{
Kind: compiler.SyntaxError,
Message: "Unclosed string literal",
Hint: "Add a matching \"'\" to close the string.",
}, "Incomplete multi-string (opening quote missing) 2"),
ErrorCase(
"LET i = `foo "+
@@ -63,7 +123,31 @@ func TestSyntaxErrors(t *testing.T) {
Kind: compiler.SyntaxError,
Message: "Unclosed string literal",
Hint: "Add a matching '`' to close the string.",
}, "Incomplete string 3"),
}, "Incomplete string (closing quote missing) 3"),
ErrorCase(
"LET i = `foo bar"+
"RETURN i", E{
Kind: compiler.SyntaxError,
Message: "Unclosed string literal",
Hint: "Add a matching '`' to close the string.",
}, "Incomplete multi-string (closing quote missing) 3"),
ErrorCase(
"LET i = foo` "+
"RETURN i", E{
Kind: compiler.SyntaxError,
Message: "Unclosed string literal",
Hint: "Add a matching '`' to close the string.",
}, "Incomplete string (opening quote missing) 3"),
ErrorCase(
"LET i = foo bar` "+
"RETURN i", E{
Kind: compiler.SyntaxError,
Message: "Unclosed string literal",
Hint: "Add a matching '`' to close the string.",
}, "Incomplete multi-string (opening quote missing) 3"),
ErrorCase(
`
@@ -73,7 +157,37 @@ func TestSyntaxErrors(t *testing.T) {
Kind: compiler.SyntaxError,
Message: "Unclosed string literal",
Hint: "Add a matching '\"' to close the string.",
}, "Incomplete string 4"),
}, "Incomplete string (closing quote missing) 4"),
ErrorCase(
`
LET i = { "foo bar: }
RETURN i
`, E{
Kind: compiler.SyntaxError,
Message: "Unclosed string literal",
Hint: "Add a matching '\"' to close the string.",
}, "Incomplete multi-string (closing quote missing) 4"),
ErrorCase(
`
LET i = { foo": }
RETURN i
`, E{
Kind: compiler.SyntaxError,
Message: "Unclosed string literal",
Hint: "Add a matching '\"' to close the string.",
}, "Incomplete string (opening quote missing) 4"),
SkipErrorCase(
`
LET i = { foo bar": }
RETURN i
`, E{
Kind: compiler.SyntaxError,
Message: "Unclosed string literal",
Hint: "Add a matching '\"' to close the string",
}, "Incomplete multi-string (opening quote missing) 4"),
ErrorCase(
`
@@ -83,7 +197,27 @@ func TestSyntaxErrors(t *testing.T) {
Kind: compiler.SyntaxError,
Message: "Unclosed string literal",
Hint: "Add a matching \"'\" to close the string.",
}, "Incomplete string 5"),
}, "Incomplete string (closing quote missing) 5"),
ErrorCase(
`
LET i = { foo': }
RETURN i
`, E{
Kind: compiler.SyntaxError,
Message: "Unclosed string literal",
Hint: "Add a matching \"'\" to close the string.",
}, "Incomplete string (opening quote missing) 5"),
SkipErrorCase(
`
LET i = { foo bar': }
RETURN i
`, E{
Kind: compiler.SyntaxError,
Message: "Unclosed string literal",
Hint: "Add a matching \"'\" to close the string.",
}, "Incomplete multi-string (opening quote missing) 5"),
ErrorCase(
"LET i = { 'foo: }"+
@@ -91,7 +225,15 @@ func TestSyntaxErrors(t *testing.T) {
Kind: compiler.SyntaxError,
Message: "Unclosed string literal",
Hint: "Add a matching \"'\" to close the string.",
}, "Incomplete string 6"),
}, "Incomplete string (closing quote missing) 6"),
ErrorCase(
"LET i = { 'foo bar: }"+
"RETURN i", E{
Kind: compiler.SyntaxError,
Message: "Unclosed string literal",
Hint: "Add a matching \"'\" to close the string.",
}, "Incomplete multi-string (closing quote missing) 6"),
ErrorCase(
`