From 45ee4ec05510b371f37c97c42c3325b106a714aa Mon Sep 17 00:00:00 2001 From: Wojtek K <5840392+wojciszek@users.noreply.github.com> Date: Sun, 29 Jun 2025 19:47:13 +0200 Subject: [PATCH 1/2] Set @ParametrizedTest for testDynamicInlineActions Signed-off-by: Wojtek K <5840392+wojciszek@users.noreply.github.com> --- .../v4/test/tool/TestAttributeChecks.java | 39 ++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/tool-testsuite/test/org/antlr/v4/test/tool/TestAttributeChecks.java b/tool-testsuite/test/org/antlr/v4/test/tool/TestAttributeChecks.java index 15397c5cc4..508094ce20 100644 --- a/tool-testsuite/test/org/antlr/v4/test/tool/TestAttributeChecks.java +++ b/tool-testsuite/test/org/antlr/v4/test/tool/TestAttributeChecks.java @@ -9,10 +9,17 @@ import org.antlr.runtime.RecognitionException; import org.antlr.v4.tool.ErrorType; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.stringtemplate.v4.ST; import org.stringtemplate.v4.STGroup; import org.stringtemplate.v4.misc.ErrorBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + import static org.antlr.v4.test.tool.ToolTestUtils.testErrors; /** */ @@ -145,7 +152,7 @@ public class TestAttributeChecks { "$S::j = $S::k;", "error(" + ErrorType.UNDEFINED_RULE_IN_NONLOCAL_REF.code + "): A.g4:5:8: reference to undefined rule S in non-local ref $S::j = $S::k;\n", }; - String[] dynInlineChecks = { + static String[] dynInlineChecks = { "$a", "error(" + ErrorType.ISOLATED_RULE_REF.code + "): A.g4:7:4: missing attribute access on rule reference a in $a\n", "$b", "error(" + ErrorType.ISOLATED_RULE_REF.code + "): A.g4:7:4: missing attribute access on rule reference b in $b\n", "$lab", "error(" + ErrorType.ISOLATED_RULE_REF.code + "): A.g4:7:4: missing attribute access on rule reference lab in $lab\n", @@ -214,8 +221,18 @@ public class TestAttributeChecks { testActions("inline", inlineChecks, attributeTemplate); } - @Test public void testDynamicInlineActions() throws RecognitionException { - testActions("inline", dynInlineChecks, attributeTemplate); + public static Stream testDynamicInlineActionsParams() { + List arguments = new ArrayList<>(); + for (int i = 0; i < dynInlineChecks.length; i+=2) { + arguments.add(Arguments.of(dynInlineChecks[i], dynInlineChecks[i+1])); + } + return arguments.stream(); + } + + @ParameterizedTest + @MethodSource("testDynamicInlineActionsParams") + public void testDynamicInlineActions(String input, String expected) { + testAction("inline", attributeTemplate, input, expected); } @Test public void testBadInlineActions() throws RecognitionException { @@ -244,12 +261,16 @@ private static void testActions(String location, String[] pairs, String template for (int i = 0; i < pairs.length; i += 2) { String action = pairs[i]; String expected = pairs[i + 1]; - STGroup g = new STGroup('<', '>'); - g.setListener(new ErrorBuffer()); // hush warnings - ST st = new ST(g, template); - st.add(location, action); - String grammar = st.render(); - testErrors(new String[]{grammar, expected}, false); + testAction(location, template, action, expected); } } + + private static void testAction(String location, String template, String action, String expected) { + STGroup g = new STGroup('<', '>'); + g.setListener(new ErrorBuffer()); // hush warnings + ST st = new ST(g, template); + st.add(location, action); + String grammar = st.render(); + testErrors(new String[]{grammar, expected}, false); + } } From bee7fd8e6d670ce6bc7cd08eeee3c261b03cfdb0 Mon Sep 17 00:00:00 2001 From: Wojtek K <5840392+wojciszek@users.noreply.github.com> Date: Sun, 29 Jun 2025 22:35:49 +0200 Subject: [PATCH 2/2] Calculate absolute position for action offending token Signed-off-by: Wojtek K <5840392+wojciszek@users.noreply.github.com> --- .../v4/test/tool/TestAttributeChecks.java | 38 +++++- .../org/antlr/v4/test/tool/ToolTestUtils.java | 48 ++++--- .../v4/semantics/ActionErrorManager.java | 117 ++++++++++++++++++ .../antlr/v4/semantics/AttributeChecks.java | 5 +- 4 files changed, 184 insertions(+), 24 deletions(-) create mode 100644 tool/src/org/antlr/v4/semantics/ActionErrorManager.java diff --git a/tool-testsuite/test/org/antlr/v4/test/tool/TestAttributeChecks.java b/tool-testsuite/test/org/antlr/v4/test/tool/TestAttributeChecks.java index 508094ce20..8bd3d7a3f4 100644 --- a/tool-testsuite/test/org/antlr/v4/test/tool/TestAttributeChecks.java +++ b/tool-testsuite/test/org/antlr/v4/test/tool/TestAttributeChecks.java @@ -6,9 +6,15 @@ package org.antlr.v4.test.tool; +import org.antlr.runtime.CommonToken; import org.antlr.runtime.RecognitionException; +import org.antlr.v4.test.runtime.ErrorQueue; +import org.antlr.v4.tool.ANTLRMessage; import org.antlr.v4.tool.ErrorType; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledOnOs; +import org.junit.jupiter.api.condition.OS; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -18,6 +24,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; import java.util.stream.Stream; import static org.antlr.v4.test.tool.ToolTestUtils.testErrors; @@ -235,6 +242,28 @@ public void testDynamicInlineActions(String input, String expected) { testAction("inline", attributeTemplate, input, expected); } + @Test + @EnabledOnOs({OS.WINDOWS}) + public void shouldDetectOffendingTokenAbsolutePositionInWindows(){ + String expectedError = "error(" + ErrorType.ISOLATED_RULE_REF.code + "): A.g4:7:4: missing attribute access on rule reference lab in $lab\n"; + testAction("inline", attributeTemplate, "$lab", expectedError, q -> assertOffendingToken(q, 131, 133)); + } + + @Test + @EnabledOnOs({OS.LINUX, OS.MAC}) + public void shouldDetectOffendingTokenAbsolutePositionInLinux(){ + String expectedError = "error(" + ErrorType.ISOLATED_RULE_REF.code + "): A.g4:7:4: missing attribute access on rule reference lab in $lab\n"; + testAction("inline", attributeTemplate, "$lab", expectedError, q -> assertOffendingToken(q, 125, 127)); + } + + private static void assertOffendingToken(ErrorQueue q, int expected, int expected1) { + Assertions.assertEquals(1, q.errors.size()); + ANTLRMessage antlrMessage = q.errors.get(0); + CommonToken token = (CommonToken) antlrMessage.offendingToken; + Assertions.assertEquals(expected, token.getStartIndex(), "Offending Token start index assertion"); + Assertions.assertEquals(expected1, token.getStopIndex(), "Offending Token stop index assertion"); + } + @Test public void testBadInlineActions() throws RecognitionException { testActions("inline", bad_inlineChecks, attributeTemplate); } @@ -266,11 +295,16 @@ private static void testActions(String location, String[] pairs, String template } private static void testAction(String location, String template, String action, String expected) { + testAction(location, template, action, expected, $ -> {}); + } + + private static void testAction(String location, String template, String action, String expected, Consumer errorQueueConsumer) { STGroup g = new STGroup('<', '>'); - g.setListener(new ErrorBuffer()); // hush warnings + ErrorBuffer listener = new ErrorBuffer(); + g.setListener(listener); // hush warnings ST st = new ST(g, template); st.add(location, action); String grammar = st.render(); - testErrors(new String[]{grammar, expected}, false); + testErrors(new String[]{grammar, expected}, errorQueueConsumer); } } diff --git a/tool-testsuite/test/org/antlr/v4/test/tool/ToolTestUtils.java b/tool-testsuite/test/org/antlr/v4/test/tool/ToolTestUtils.java index 6add050bd4..f5b8fcb85c 100644 --- a/tool-testsuite/test/org/antlr/v4/test/tool/ToolTestUtils.java +++ b/tool-testsuite/test/org/antlr/v4/test/tool/ToolTestUtils.java @@ -29,6 +29,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; +import java.util.function.Consumer; import static org.antlr.v4.test.runtime.FileUtils.deleteDirectory; import static org.antlr.v4.test.runtime.Generator.antlrOnString; @@ -91,33 +92,40 @@ public static RunOptions createOptionsForJavaToolTests( } public static void testErrors(String[] pairs, boolean printTree) { + testErrors(pairs, $ -> {}); + } + + public static void testErrors(String[] pairs, Consumer errorQueueAssertionsConsumer) { for (int i = 0; i < pairs.length; i += 2) { String grammarStr = pairs[i]; String expect = pairs[i + 1]; + testErrors(grammarStr, expect, errorQueueAssertionsConsumer); + } + } - String[] lines = grammarStr.split("\n"); - String fileName = getFilenameFromFirstLineOfGrammar(lines[0]); - - String tempDirName = "AntlrTestErrors-" + Thread.currentThread().getName() + "-" + System.currentTimeMillis(); - String tempTestDir = Paths.get(TempDirectory, tempDirName).toString(); + private static void testErrors(String grammarStr, String expect, Consumer additionalAssertions) { + String[] lines = grammarStr.split("\n"); + String fileName = getFilenameFromFirstLineOfGrammar(lines[0]); - try { - ErrorQueue equeue = antlrOnString(tempTestDir, null, fileName, grammarStr, false); + String tempDirName = "AntlrTestErrors-" + Thread.currentThread().getName() + "-" + System.currentTimeMillis(); + String tempTestDir = Paths.get(TempDirectory, tempDirName).toString(); - String actual = equeue.toString(true); - actual = actual.replace(tempTestDir + File.separator, ""); - String msg = grammarStr; - msg = msg.replace("\n", "\\n"); - msg = msg.replace("\r", "\\r"); - msg = msg.replace("\t", "\\t"); + try { + ErrorQueue equeue = antlrOnString(tempTestDir, null, fileName, grammarStr, false); + String actual = equeue.toString(true); + actual = actual.replace(tempTestDir + File.separator, ""); + String msg = grammarStr; + msg = msg.replace("\n", "\\n"); + msg = msg.replace("\r", "\\r"); + msg = msg.replace("\t", "\\t"); - assertEquals(expect, actual, "error in: " + msg); - } - finally { - try { - deleteDirectory(new File(tempTestDir)); - } catch (IOException ignored) { - } + assertEquals(expect, actual, "error in: " + msg); + additionalAssertions.accept(equeue); + } + finally { + try { + deleteDirectory(new File(tempTestDir)); + } catch (IOException ignored) { } } } diff --git a/tool/src/org/antlr/v4/semantics/ActionErrorManager.java b/tool/src/org/antlr/v4/semantics/ActionErrorManager.java new file mode 100644 index 0000000000..4abe4bdf8d --- /dev/null +++ b/tool/src/org/antlr/v4/semantics/ActionErrorManager.java @@ -0,0 +1,117 @@ +package org.antlr.v4.semantics; + +import org.antlr.runtime.CommonToken; +import org.antlr.runtime.RecognitionException; +import org.antlr.runtime.Token; +import org.antlr.v4.tool.*; +import org.stringtemplate.v4.ST; + +import java.util.Collection; +import java.util.Objects; + +public class ActionErrorManager extends ErrorManager { + private final Token actionToken; + private final ErrorManager delegate; + + public ActionErrorManager(Token actionToken, ErrorManager delegate) { + super(delegate.tool); + this.delegate = Objects.requireNonNull(delegate, "ErrorManager delegate cannot be null"); + this.actionToken = Objects.requireNonNull(actionToken, "Token actionToken cannot be null"); + } + + @Override + public void grammarError(ErrorType etype, String fileName, Token token, Object... args) { + delegate.grammarError(etype, fileName, resolveTokenWithGrammarAbsolutePosition(token), args); + } + + private Token resolveTokenWithGrammarAbsolutePosition(Token token) { + if (token instanceof CommonToken && actionToken instanceof CommonToken) { + CommonToken absolutePositionToken = new CommonToken(token); + CommonToken ruleToken = (CommonToken) actionToken; + absolutePositionToken.setStartIndex(ruleToken.getStartIndex() + absolutePositionToken.getStartIndex()); + absolutePositionToken.setStopIndex(ruleToken.getStartIndex() + absolutePositionToken.getStopIndex()); + return absolutePositionToken; + } + return token; + } + + @Override + public void resetErrorState() { + delegate.resetErrorState(); + } + + @Override + public ST getMessageTemplate(ANTLRMessage msg) { + return delegate.getMessageTemplate(msg); + } + + @Override + public ST getLocationFormat() { + return delegate.getLocationFormat(); + } + + @Override + public ST getReportFormat(ErrorSeverity severity) { + return delegate.getReportFormat(severity); + } + + @Override + public ST getMessageFormat() { + return delegate.getMessageFormat(); + } + + @Override + public boolean formatWantsSingleLineMessage() { + return delegate.formatWantsSingleLineMessage(); + } + + @Override + public void info(String msg) { + delegate.info(msg); + } + + @Override + public void syntaxError(ErrorType etype, String fileName, Token token, RecognitionException antlrException, Object... args) { + delegate.syntaxError(etype, fileName, token, antlrException, args); + } + + @Override + public void toolError(ErrorType errorType, Object... args) { + delegate.toolError(errorType, args); + } + + @Override + public void toolError(ErrorType errorType, Throwable e, Object... args) { + delegate.toolError(errorType, e, args); + } + + @Override + public void leftRecursionCycles(String fileName, Collection> cycles) { + delegate.leftRecursionCycles(fileName, cycles); + } + + @Override + public int getNumErrors() { + return delegate.getNumErrors(); + } + + @Override + public void emit(ErrorType etype, ANTLRMessage msg) { + delegate.emit(etype, msg); + } + + @Override + public void setFormat(String formatName) { + delegate.setFormat(formatName); + } + + @Override + protected boolean verifyFormat() { + return super.verifyFormat(); + } + + @Override + public void panic(ErrorType errorType, Object... args) { + delegate.panic(errorType, args); + } +} diff --git a/tool/src/org/antlr/v4/semantics/AttributeChecks.java b/tool/src/org/antlr/v4/semantics/AttributeChecks.java index 84bbd4187e..84d76db284 100644 --- a/tool/src/org/antlr/v4/semantics/AttributeChecks.java +++ b/tool/src/org/antlr/v4/semantics/AttributeChecks.java @@ -7,6 +7,7 @@ package org.antlr.v4.semantics; import org.antlr.runtime.ANTLRStringStream; +import org.antlr.runtime.CommonToken; import org.antlr.runtime.Token; import org.antlr.v4.parse.ActionSplitter; import org.antlr.v4.parse.ActionSplitterListener; @@ -39,8 +40,8 @@ public AttributeChecks(Grammar g, Rule r, Alternative alt, ActionAST node, Token this.alt = alt; this.node = node; this.actionToken = actionToken; - this.errMgr = g.tool.errMgr; - } + this.errMgr = new ActionErrorManager(actionToken, g.tool.errMgr); + } public static void checkAllAttributeExpressions(Grammar g) { for (ActionAST act : g.namedActions.values()) {