Skip to content

Commit

Permalink
Merge pull request #3700 from volodya-lombrozo/3332_for_humans
Browse files Browse the repository at this point in the history
Underline Syntax Errors
  • Loading branch information
yegor256 authored Dec 19, 2024
2 parents e059d61 + 50c2b71 commit 32d00ed
Show file tree
Hide file tree
Showing 6 changed files with 300 additions and 15 deletions.
72 changes: 57 additions & 15 deletions eo-parser/src/main/java/org/eolang/parser/ParsingErrors.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,16 @@
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.antlr.v4.runtime.ANTLRErrorListener;
import java.util.Optional;
import org.antlr.v4.runtime.BaseErrorListener;
import org.antlr.v4.runtime.NoViableAltException;
import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.Recognizer;
import org.antlr.v4.runtime.Token;
import org.cactoos.Text;
import org.cactoos.iterable.Mapped;
import org.cactoos.list.ListOf;
import org.cactoos.text.UncheckedText;
import org.xembly.Directive;
import org.xembly.Directives;

Expand All @@ -41,8 +44,7 @@
*
* @since 0.30.0
*/
final class ParsingErrors extends BaseErrorListener
implements ANTLRErrorListener, Iterable<Directive> {
final class ParsingErrors extends BaseErrorListener implements Iterable<Directive> {

/**
* Errors accumulated.
Expand Down Expand Up @@ -81,18 +83,41 @@ public void syntaxError(
final String msg,
final RecognitionException error
) {
this.errors.add(
new ParsingException(
String.format(
"[%d:%d] %s: \"%s\"",
line, position, msg,
// @checkstyle AvoidInlineConditionalsCheck (1 line)
this.lines.size() < line ? "EOF" : this.lines.get(line - 1)
),
error,
line
)
);
// @checkstyle MethodBodyCommentsCheck (20 lines)
// @todo #3332:30min Add more specific error messages.
// Currently we write just "error: no viable alternative at input" for all errors.
// It's better to use 'Recognizer<?, ?> recognizer' parameter of the current method
// to retrieve more specific error messages.
if (error instanceof NoViableAltException) {
final Token token = (Token) symbol;
this.errors.add(
new ParsingException(
String.format(
"[%d:%d] %s:%n%s",
line, position,
"error: no viable alternative at input",
new UnderlinedMessage(
this.line(line).orElse("EOF"),
position,
Math.max(token.getStopIndex() - token.getStartIndex(), 1)
).formatted()
),
error,
line
)
);
} else {
this.errors.add(
new ParsingException(
String.format(
"[%d:%d] %s: \"%s\"",
line, position, msg, this.line(line).orElse("EOF")
),
error,
line
)
);
}
}

@Override
Expand Down Expand Up @@ -121,4 +146,21 @@ public Iterator<Directive> iterator() {
public int size() {
return this.errors.size();
}

/**
* Get the line by number.
* @param number The line number.
* @return The line.
*/
private Optional<String> line(final int number) {
final Optional<String> result;
if (number < 1 || number > this.lines.size()) {
result = Optional.empty();
} else {
result = Optional.ofNullable(this.lines.get(number - 1))
.map(UncheckedText::new)
.map(UncheckedText::asString);
}
return result;
}
}
117 changes: 117 additions & 0 deletions eo-parser/src/main/java/org/eolang/parser/UnderlinedMessage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2016-2024 Objectionary.com
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.eolang.parser;

import java.util.Collections;

/**
* Underlined message.
* <p>
* For example, if you have a message "Problem is here" and you want to underline
* the word "is", you can create an instance of this class with the following
* parameters: origin="Problem is here", from=8, length=2.
* </p>
* <p>
* The result will be:
* {@code
* Problem is here
* ^^
* }
* </p>
* @since 0.50
* @todo #3332:30min Add more decorators for the error message.
* For example, {@link ParsingErrors} currently contains logic related to the message formatting.
* It's better to create a separate class for this purpose.
*/
final class UnderlinedMessage {

/**
* The message.
*/
private final String origin;

/**
* The position from which to start underlining.
*/
private final int from;

/**
* The length of the underline.
*/
private final int length;

/**
* Ctor.
* @param origin The message.
* @param from The position from which to start underlining.
* @param length The length of the underline.
*/
UnderlinedMessage(final String origin, final int from, final int length) {
this.origin = origin;
this.from = from;
this.length = length;
}

/**
* Formatted message.
* @return The formatted message.
*/
String formatted() {
return String.format(
"%s\n%s",
this.origin,
this.underline()
);
}

/**
* Underline.
* @return The underlined string.
*/
private String underline() {
final String result;
if (this.origin.isEmpty() || this.length <= 0 || this.from >= this.origin.length()) {
result = "";
} else if (this.from < 0) {
result = UnderlinedMessage.repeat("^", this.origin.length());
} else {
result = String.format(
"%s%s",
UnderlinedMessage.repeat(" ", this.from),
UnderlinedMessage.repeat("^", Math.min(this.length, this.origin.length()))
);
}
return result;
}

/**
* Repeat a symbol n times.
* @param symbol The symbol.
* @param times The number of times to repeat the symbol.
* @return The repeated symbol.
*/
private static String repeat(final String symbol, final int times) {
return String.join("", Collections.nCopies(times, symbol));
}
}
11 changes: 11 additions & 0 deletions eo-parser/src/test/java/org/eolang/parser/EoSyntaxTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,17 @@ void checksTypoPacks(final String yaml) {
Integer.parseInt(story.after().xpath("/program/errors/error[1]/@line").get(0)),
Matchers.equalTo(Integer.parseInt(story.map().get("line").toString()))
);
final String msg = "message";
if (story.map().containsKey(msg)) {
MatcherAssert.assertThat(
XhtmlMatchers.xhtml(story.after()).toString(),
story.after()
.xpath("/program/errors/error[1]/text()")
.get(0)
.replaceAll("\r", ""),
Matchers.equalTo(story.map().get(msg).toString())
);
}
}

@ParameterizedTest
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2016-2024 Objectionary.com
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.eolang.parser;

import java.util.stream.Stream;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

/**
* Test case for {@link UnderlinedMessage}.
* @since 0.50
* @checkstyle ParameterNumberCheck (500 lines)
*/
@SuppressWarnings("PMD.AvoidDuplicateLiterals")
final class UnderlinedMessageTest {

@ParameterizedTest
@MethodSource("examples")
void addsUndeline(final String input, final int from, final int length, final String expected) {
MatcherAssert.assertThat(
"We expect the message to be highlighted with underline characters",
new UnderlinedMessage(input, from, length).formatted(),
Matchers.equalTo(expected)
);
}

/**
* Test cases for {@link UnderlinedMessageTest#addsUndeline}.
* ANTLR {@link org.antlr.v4.runtime.BaseErrorListener} returns strange line numbers
* and positions like -1. Here I hide this problem intentionally to make all the rest
* tests pass.
* @return Test cases.
*/
private static Stream<Arguments> examples() {
final String issue = "Problem is here";
return Stream.of(
Arguments.of(issue, 0, 7, "Problem is here\n^^^^^^^"),
Arguments.of(issue, 8, 2, "Problem is here\n ^^"),
Arguments.of(issue, 0, 0, "Problem is here\n"),
Arguments.of(issue, 0, 1, "Problem is here\n^"),
Arguments.of(issue, 14, 1, "Problem is here\n ^"),
Arguments.of(issue, 0, 15, "Problem is here\n^^^^^^^^^^^^^^^"),
Arguments.of(issue, -1, 0, "Problem is here\n"),
Arguments.of(issue, 0, -1, "Problem is here\n"),
Arguments.of(issue, 0, 100, "Problem is here\n^^^^^^^^^^^^^^^"),
Arguments.of(issue, 100, 0, "Problem is here\n"),
Arguments.of(issue, 100, 100, "Problem is here\n"),
Arguments.of("", 1, 10, "\n")
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
# SOFTWARE.
---
line: 2
message: >-
[2:4] error: no viable alternative at input:
y:^
^
input: |
x
y:^
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# The MIT License (MIT)
#
# Copyright (c) 2016-2024 Objectionary.com
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
---
line: 5
message: >-
[5:2] error: no viable alternative at input:
*
^
input: |
# This is a code snippet from the following issue:
# https://github.com/objectionary/eo/issues/3332
[args] > app
seq > @
*
"a" > a
QQ.io.stdout
a

2 comments on commit 32d00ed

@0pdd
Copy link

@0pdd 0pdd commented on 32d00ed Dec 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Puzzle 3332-e91c74c1 discovered in eo-parser/src/main/java/org/eolang/parser/ParsingErrors.java) and submitted as #3706. Please, remember that the puzzle was not necessarily added in this particular commit. Maybe it was added earlier, but we discovered it only now.

@0pdd
Copy link

@0pdd 0pdd commented on 32d00ed Dec 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Puzzle 3332-fa5220a9 discovered in eo-parser/src/main/java/org/eolang/parser/UnderlinedMessage.java) and submitted as #3707. Please, remember that the puzzle was not necessarily added in this particular commit. Maybe it was added earlier, but we discovered it only now.

Please sign in to comment.