Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,27 @@

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;
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.function.Consumer;
import java.util.stream.Stream;

import static org.antlr.v4.test.tool.ToolTestUtils.testErrors;

/** */
Expand Down Expand Up @@ -145,7 +159,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",
Expand Down Expand Up @@ -214,8 +228,40 @@ public class TestAttributeChecks {
testActions("inline", inlineChecks, attributeTemplate);
}

@Test public void testDynamicInlineActions() throws RecognitionException {
testActions("inline", dynInlineChecks, attributeTemplate);
public static Stream<Arguments> testDynamicInlineActionsParams() {
List<Arguments> 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
@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 {
Expand Down Expand Up @@ -244,12 +290,21 @@ 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) {
testAction(location, template, action, expected, $ -> {});
}

private static void testAction(String location, String template, String action, String expected, Consumer<ErrorQueue> errorQueueConsumer) {
STGroup g = new STGroup('<', '>');
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}, errorQueueConsumer);
}
}
48 changes: 28 additions & 20 deletions tool-testsuite/test/org/antlr/v4/test/tool/ToolTestUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<ErrorQueue> 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<ErrorQueue> 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) {
}
}
}
Expand Down
117 changes: 117 additions & 0 deletions tool/src/org/antlr/v4/semantics/ActionErrorManager.java
Original file line number Diff line number Diff line change
@@ -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<? extends Collection<Rule>> 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);
}
}
5 changes: 3 additions & 2 deletions tool/src/org/antlr/v4/semantics/AttributeChecks.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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()) {
Expand Down
Loading