Просмотр исходного кода

refactor Language frontends to throw an error if parsing failed instead of requiring deferred check of the `hasErrors` field

Jan Wittler 4 лет назад
Родитель
Сommit
381a18e361
40 измененных файлов с 212 добавлено и 220 удалено
  1. 15 13
      core/src/main/java/de/jplag/Submission.java
  2. 0 8
      language-api/src/main/java/de/jplag/AbstractParser.java
  3. 2 6
      language-api/src/main/java/de/jplag/Language.java
  4. 61 0
      language-api/src/main/java/de/jplag/ParsingException.java
  5. 2 6
      languages/cpp/src/main/java/de/jplag/cpp/Language.java
  6. 3 5
      languages/cpp/src/main/java/de/jplag/cpp/Scanner.java
  7. 6 5
      languages/cpp/src/main/javacc/CPP.jj
  8. 5 8
      languages/csharp/src/main/java/de/jplag/csharp/CSharpParserAdapter.java
  9. 2 6
      languages/csharp/src/main/java/de/jplag/csharp/Language.java
  10. 2 1
      languages/csharp/src/test/java/de/jplag/csharp/MinimalCSharpTest.java
  11. 2 1
      languages/emf-metamodel-dynamic/src/test/java/de/jplag/emf/dynamic/MinimalDynamicMetamodelTest.java
  12. 2 6
      languages/emf-metamodel/src/main/java/de/jplag/emf/Language.java
  13. 5 5
      languages/emf-metamodel/src/main/java/de/jplag/emf/parser/EcoreParser.java
  14. 2 1
      languages/emf-metamodel/src/test/java/de/jplag/emf/MinimalMetamodelTest.java
  15. 5 7
      languages/golang/src/main/java/de/jplag/golang/GoParserAdapter.java
  16. 2 7
      languages/golang/src/main/java/de/jplag/golang/Language.java
  17. 2 1
      languages/golang/src/test/java/de/jplag/golang/GoLanguageTest.java
  18. 18 7
      languages/java/src/main/java/de/jplag/java/JavacAdapter.java
  19. 2 7
      languages/java/src/main/java/de/jplag/java/Language.java
  20. 3 7
      languages/java/src/main/java/de/jplag/java/Parser.java
  21. 9 1
      languages/java/src/main/java/de/jplag/java/TokenGeneratingTreeScanner.java
  22. 5 7
      languages/kotlin/src/main/java/de/jplag/kotlin/KotlinParserAdapter.java
  23. 2 6
      languages/kotlin/src/main/java/de/jplag/kotlin/Language.java
  24. 2 1
      languages/kotlin/src/test/java/de/jplag/kotlin/KotlinLanguageTest.java
  25. 2 6
      languages/python-3/src/main/java/de/jplag/python3/Language.java
  26. 5 9
      languages/python-3/src/main/java/de/jplag/python3/Parser.java
  27. 2 6
      languages/rlang/src/main/java/de/jplag/rlang/Language.java
  28. 5 8
      languages/rlang/src/main/java/de/jplag/rlang/RParserAdapter.java
  29. 2 1
      languages/rlang/src/test/java/de/jplag/rlang/RLanguageTest.java
  30. 2 7
      languages/rust/src/main/java/de/jplag/rust/Language.java
  31. 5 8
      languages/rust/src/main/java/de/jplag/rust/RustParserAdapter.java
  32. 2 1
      languages/rust/src/test/java/de/jplag/rust/RustLanguageTest.java
  33. 0 3
      languages/scala/src/main/scala/de/jplag/scala/Language.scala
  34. 5 12
      languages/scala/src/main/scala/de/jplag/scala/Parser.scala
  35. 2 6
      languages/scheme/src/main/java/de/jplag/scheme/Language.java
  36. 3 5
      languages/scheme/src/main/java/de/jplag/scheme/Parser.java
  37. 6 5
      languages/scheme/src/main/javacc/Scheme.jj
  38. 2 6
      languages/text/src/main/java/de/jplag/text/Language.java
  39. 6 12
      languages/text/src/main/java/de/jplag/text/ParserAdapter.java
  40. 4 3
      languages/text/src/test/java/jplag/text/TextLanguageTest.java

+ 15 - 13
core/src/main/java/de/jplag/Submission.java

@@ -248,22 +248,24 @@ public class Submission implements Comparable<Submission> {
             return false;
         }
 
-        tokenList = language.parse(new HashSet<>(files));
-        if (!language.hasErrors()) {
-            if (tokenList.size() < 3) {
-                logger.error("Submission \"{}\" is too short!", name);
-                tokenList = null;
-                hasErrors = true; // invalidate submission
-                return false;
+        try {
+            tokenList = language.parse(new HashSet<>(files));
+        } catch (ParsingException e) {
+            logger.warn("Failed to parse submission {} with error {}", this, e);
+            tokenList = null;
+            hasErrors = true;
+            if (debugParser) {
+                copySubmission();
             }
-            return true;
+            return false;
         }
 
-        tokenList = null;
-        hasErrors = true; // invalidate submission
-        if (debugParser) {
-            copySubmission();
+        if (tokenList.size() < 3) {
+            logger.error("Submission \"{}\" is too short!", name);
+            tokenList = null;
+            hasErrors = true; // invalidate submission
+            return false;
         }
-        return false;
+        return true;
     }
 }

+ 0 - 8
language-api/src/main/java/de/jplag/AbstractParser.java

@@ -8,17 +8,9 @@ import org.slf4j.LoggerFactory;
  * @author Emeric Kwemou
  */
 public abstract class AbstractParser {
-    protected int errors = 0;
     public final Logger logger;
 
     protected AbstractParser() {
         this.logger = LoggerFactory.getLogger(this.getClass());
     }
-
-    /**
-     * @return true if the last parse call (which could be still ongoing) lead to one or more errors.
-     */
-    public boolean hasErrors() {
-        return errors != 0;
-    }
 }

+ 2 - 6
language-api/src/main/java/de/jplag/Language.java

@@ -33,13 +33,9 @@ public interface Language {
      * Parses a set of files.
      * @param files are the files to parse.
      * @return the list of parsed JPlag tokens.
+     * @throws ParsingException if an error during parsing the files occurred.
      */
-    List<Token> parse(Set<File> files);
-
-    /**
-     * Whether errors were found during the last {@link #parse}.
-     */
-    boolean hasErrors();
+    List<Token> parse(Set<File> files) throws ParsingException;
 
     /**
      * Determines whether a fixed-width font should be used to display that language.

+ 61 - 0
language-api/src/main/java/de/jplag/ParsingException.java

@@ -0,0 +1,61 @@
+package de.jplag;
+
+import java.io.File;
+import java.io.Serial;
+
+/**
+ * An exception to throw if any error occurred while parsing files in a language frontend.
+ */
+public class ParsingException extends Exception {
+    @Serial
+    private static final long serialVersionUID = 4385949762027596330L; // generated
+
+    /**
+     * Constructs a new exception indicating a parsing exception in the given file without a specific reason.
+     * @param file the file in which a parsing error occurred. (A null value is permitted, and indicates that the file is
+     * nonexistent or unknown.)
+     */
+    public ParsingException(File file) {
+        this(file, (String) null);
+    }
+
+    /**
+     * Constructs a new exception indicating a parsing exception in the given file with the given reason.
+     * @param file the file in which a parsing error occurred. (A null value is permitted, and indicates that the file is
+     * nonexistent or unknown.)
+     * @param reason the reason the parsing failed. A null value is permitted.)
+     */
+    public ParsingException(File file, String reason) {
+        super(constructMessage(file, reason));
+    }
+
+    /**
+     * Constructs a new exception indicating a parsing exception in the given file with the given cause.
+     * @param file the file in which a parsing error occurred. (A null value is permitted, and indicates that the file is
+     * nonexistent or unknown.)
+     * @param cause the cause. (A null value is permitted, and indicates that the cause is nonexistent or unknown.)
+     */
+    public ParsingException(File file, Throwable cause) {
+        this(file, null, cause);
+    }
+
+    /**
+     * Constructs a new exception indicating a parsing exception in the given file with the given reason and cause.
+     * @param file the file in which a parsing error occurred. (A null value is permitted, and indicates that the file is
+     * nonexistent or unknown.)
+     * @param reason the reason the parsing failed. A null value is permitted.)
+     * @param cause the cause. (A null value is permitted, and indicates that the cause is nonexistent or unknown.)
+     */
+    public ParsingException(File file, String reason, Throwable cause) {
+        super(constructMessage(file, reason), cause);
+    }
+
+    private static String constructMessage(File file, String reason) {
+        StringBuilder messageBuilder = new StringBuilder();
+        messageBuilder.append("failed to parse '%s'".formatted(file));
+        if (!reason.isEmpty() && !reason.isBlank()) {
+            messageBuilder.append(" with reason: %s".formatted(reason));
+        }
+        return messageBuilder.toString();
+    }
+}

+ 2 - 6
languages/cpp/src/main/java/de/jplag/cpp/Language.java

@@ -6,6 +6,7 @@ import java.util.Set;
 
 import org.kohsuke.MetaInfServices;
 
+import de.jplag.ParsingException;
 import de.jplag.Token;
 
 @MetaInfServices(de.jplag.Language.class)
@@ -39,12 +40,7 @@ public class Language implements de.jplag.Language {
     }
 
     @Override
-    public List<Token> parse(Set<File> files) {
+    public List<Token> parse(Set<File> files) throws ParsingException {
         return this.scanner.scan(files);
     }
-
-    @Override
-    public boolean hasErrors() {
-        return this.scanner.hasErrors();
-    }
 }

+ 3 - 5
languages/cpp/src/main/java/de/jplag/cpp/Scanner.java

@@ -6,6 +6,7 @@ import java.util.List;
 import java.util.Set;
 
 import de.jplag.AbstractParser;
+import de.jplag.ParsingException;
 import de.jplag.Token;
 
 public class Scanner extends AbstractParser {
@@ -20,15 +21,12 @@ public class Scanner extends AbstractParser {
         super();
     }
 
-    public List<Token> scan(Set<File> files) {
+    public List<Token> scan(Set<File> files) throws ParsingException {
         tokens = new ArrayList<>();
-        errors = 0;
         for (File file : files) {
             this.currentFile = file;
             logger.trace("Scanning file {}", currentFile);
-            if (!CPPScanner.scanFile(file, this)) {
-                errors++;
-            }
+            CPPScanner.scanFile(file, this);
             tokens.add(Token.fileEnd(currentFile));
         }
         return tokens;

+ 6 - 5
languages/cpp/src/main/javacc/CPP.jj

@@ -19,30 +19,31 @@ import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 
+import de.jplag.ParsingException;
+
 import static de.jplag.cpp.CPPTokenType.*;
 
 public class CPPScanner {
     private Scanner delegatingScanner;
 
-    public static boolean scanFile(File file, Scanner delegatingScanner) {
+    public static void scanFile(File file, Scanner delegatingScanner) throws ParsingException {
         CPPScanner scanner;
         try(InputStream input = new NewlineStream(new FileInputStream(file))) {
             scanner = new CPPScanner(input, "UTF-8");
             scanner.delegatingScanner = delegatingScanner;
         } catch (IOException e) {
             System.out.println("C/C++ Scanner: File " + file.getName() + " not found.");
-            return false;
+            throw new ParsingException(file, e.getMessage(), e);
         }
         try {
             scanner.scan();
         } catch (ParseException e) {
             delegatingScanner.logger.error("Parsing Error in '" + file.getName() + "': " + e.getMessage());
-            return false;
+            throw new ParsingException(file, e.getMessage(), e);
         } catch (TokenMgrException e) {
             delegatingScanner.logger.error("Scanning Error in '" + file.getName() + "': " + e.getMessage());
-            return false;
+            throw new ParsingException(file, e.getMessage(), e);
         }
-        return true;
     }
 }
 PARSER_END(CPPScanner)

+ 5 - 8
languages/csharp/src/main/java/de/jplag/csharp/CSharpParserAdapter.java

@@ -14,6 +14,7 @@ import org.antlr.v4.runtime.tree.ParseTree;
 import org.antlr.v4.runtime.tree.ParseTreeWalker;
 
 import de.jplag.AbstractParser;
+import de.jplag.ParsingException;
 import de.jplag.Token;
 import de.jplag.TokenType;
 import de.jplag.csharp.grammar.CSharpLexer;
@@ -40,19 +41,16 @@ public class CSharpParserAdapter extends AbstractParser {
      * @param files is the set of files.
      * @return the list of parsed tokens.
      */
-    public List<Token> parse(Set<File> files) {
+    public List<Token> parse(Set<File> files) throws ParsingException {
         tokens = new ArrayList<>();
-        errors = 0;
         for (File file : files) {
-            if (!parseFile(file)) {
-                errors++;
-            }
+            parseFile(file);
             tokens.add(Token.fileEnd(file));
         }
         return tokens;
     }
 
-    private boolean parseFile(File file) {
+    private void parseFile(File file) throws ParsingException {
         try (FileInputStream inputStream = new FileInputStream(file)) {
             currentFile = file;
 
@@ -72,9 +70,8 @@ public class CSharpParserAdapter extends AbstractParser {
             }
         } catch (IOException exception) {
             logger.error("Parsing Error in '" + file.getName() + "':" + File.separator + exception, exception);
-            return false;
+            throw new ParsingException(file, exception.getMessage(), exception);
         }
-        return true;
     }
 
     /* package-private */ void addToken(TokenType type, int line, int column, int length) {

+ 2 - 6
languages/csharp/src/main/java/de/jplag/csharp/Language.java

@@ -6,6 +6,7 @@ import java.util.Set;
 
 import org.kohsuke.MetaInfServices;
 
+import de.jplag.ParsingException;
 import de.jplag.Token;
 
 /**
@@ -46,12 +47,7 @@ public class Language implements de.jplag.Language {
     }
 
     @Override
-    public List<Token> parse(Set<File> files) {
+    public List<Token> parse(Set<File> files) throws ParsingException {
         return parser.parse(files);
     }
-
-    @Override
-    public boolean hasErrors() {
-        return parser.hasErrors();
-    }
 }

+ 2 - 1
languages/csharp/src/test/java/de/jplag/csharp/MinimalCSharpTest.java

@@ -33,6 +33,7 @@ import org.junit.jupiter.api.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import de.jplag.ParsingException;
 import de.jplag.SharedTokenType;
 import de.jplag.Token;
 import de.jplag.TokenPrinter;
@@ -55,7 +56,7 @@ class MinimalCSharpTest {
     }
 
     @Test
-    void testParsingTestClass() {
+    void testParsingTestClass() throws ParsingException {
         List<TokenType> expectedToken = List.of(CLASS, CLASS_BEGIN, FIELD, CONSTRUCTOR, LOCAL_VARIABLE, METHOD, METHOD_BEGIN, IF, IF_BEGIN,
                 INVOCATION, IF_END, IF_BEGIN, INVOCATION, IF_END, METHOD_END, PROPERTY, ACCESSORS_BEGIN, ACCESSOR_BEGIN, ACCESSOR_END, ACCESSOR_BEGIN,
                 ACCESSOR_END, ACCESSORS_END, FIELD, PROPERTY, ACCESSORS_BEGIN, ACCESSOR_BEGIN, RETURN, ACCESSOR_END, ACCESSOR_BEGIN, ASSIGNMENT,

+ 2 - 1
languages/emf-metamodel-dynamic/src/test/java/de/jplag/emf/dynamic/MinimalDynamicMetamodelTest.java

@@ -17,6 +17,7 @@ import org.junit.jupiter.api.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import de.jplag.ParsingException;
 import de.jplag.Token;
 import de.jplag.TokenPrinter;
 import de.jplag.TokenType;
@@ -40,7 +41,7 @@ class MinimalDynamicMetamodelTest {
     }
 
     @Test
-    void testBookstoreMetamodels() {
+    void testBookstoreMetamodels() throws ParsingException {
         List<File> testFiles = Arrays.stream(TEST_SUBJECTS).map(path -> new File(BASE_PATH.toFile(), path)).toList();
         List<Token> result = language.parse(new HashSet<>(testFiles));
         List<TokenType> tokenTypes = result.stream().map(Token::getType).toList();

+ 2 - 6
languages/emf-metamodel/src/main/java/de/jplag/emf/Language.java

@@ -7,6 +7,7 @@ import java.util.Set;
 import org.eclipse.emf.ecore.EcorePackage;
 import org.kohsuke.MetaInfServices;
 
+import de.jplag.ParsingException;
 import de.jplag.Token;
 import de.jplag.emf.parser.EcoreParser;
 
@@ -54,15 +55,10 @@ public class Language implements de.jplag.Language {
     }
 
     @Override
-    public List<Token> parse(Set<File> files) {
+    public List<Token> parse(Set<File> files) throws ParsingException {
         return parser.parse(files);
     }
 
-    @Override
-    public boolean hasErrors() {
-        return parser.hasErrors();
-    }
-
     @Override
     public boolean useViewFiles() {
         return true;

+ 5 - 5
languages/emf-metamodel/src/main/java/de/jplag/emf/parser/EcoreParser.java

@@ -8,6 +8,7 @@ import java.util.Set;
 import org.eclipse.emf.ecore.EObject;
 
 import de.jplag.AbstractParser;
+import de.jplag.ParsingException;
 import de.jplag.Token;
 import de.jplag.emf.Language;
 import de.jplag.emf.MetamodelToken;
@@ -38,11 +39,9 @@ public class EcoreParser extends AbstractParser {
      * @param files is the set of files.
      * @return the list of parsed tokens.
      */
-    public List<Token> parse(Set<File> files) {
-        errors = 0;
+    public List<Token> parse(Set<File> files) throws ParsingException {
         tokens = new ArrayList<>();
         for (File file : files) {
-            currentFile = file;
             parseModelFile(file);
         }
         return tokens;
@@ -52,11 +51,12 @@ public class EcoreParser extends AbstractParser {
      * Loads a metamodel from a file and parses it.
      * @param file is the metamodel file.
      */
-    protected void parseModelFile(File file) {
+    protected void parseModelFile(File file) throws ParsingException {
+        currentFile = file;
         treeView = new MetamodelTreeView(file);
         List<EObject> model = EMFUtil.loadModel(file);
         if (model == null) {
-            errors++;
+            throw new ParsingException(file, "failed to load model");
         } else {
             for (EObject root : model) {
                 visitor = createMetamodelVisitor();

+ 2 - 1
languages/emf-metamodel/src/test/java/de/jplag/emf/MinimalMetamodelTest.java

@@ -17,6 +17,7 @@ import org.junit.jupiter.api.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import de.jplag.ParsingException;
 import de.jplag.Token;
 import de.jplag.TokenPrinter;
 import de.jplag.TokenType;
@@ -40,7 +41,7 @@ class MinimalMetamodelTest {
     }
 
     @Test
-    void testBookstoreMetamodels() {
+    void testBookstoreMetamodels() throws ParsingException {
         List<File> testFiles = Arrays.stream(TEST_SUBJECTS).map(path -> new File(BASE_PATH.toFile(), path)).toList();
         List<Token> result = language.parse(new HashSet<>(testFiles));
 

+ 5 - 7
languages/golang/src/main/java/de/jplag/golang/GoParserAdapter.java

@@ -14,6 +14,7 @@ import org.antlr.v4.runtime.tree.ParseTree;
 import org.antlr.v4.runtime.tree.ParseTreeWalker;
 
 import de.jplag.AbstractParser;
+import de.jplag.ParsingException;
 import de.jplag.Token;
 import de.jplag.TokenType;
 import de.jplag.golang.grammar.GoLexer;
@@ -23,18 +24,16 @@ public class GoParserAdapter extends AbstractParser {
     private File currentFile;
     private List<Token> tokens;
 
-    public List<Token> parse(Set<File> files) {
+    public List<Token> parse(Set<File> files) throws ParsingException {
         tokens = new ArrayList<>();
         for (File file : files) {
-            if (!parseFile(file)) {
-                errors++;
-            }
+            parseFile(file);
             tokens.add(Token.fileEnd(file));
         }
         return tokens;
     }
 
-    private boolean parseFile(File file) {
+    private void parseFile(File file) throws ParsingException {
         try (FileInputStream inputStream = new FileInputStream(file)) {
             currentFile = file;
 
@@ -52,9 +51,8 @@ public class GoParserAdapter extends AbstractParser {
             }
         } catch (IOException exception) {
             logger.error("Parsing Error in '%s':".formatted(file.getName()), exception);
-            return false;
+            throw new ParsingException(file, exception.getMessage(), exception);
         }
-        return true;
     }
 
     public void addToken(TokenType tokenType, int line, int column, int length) {

+ 2 - 7
languages/golang/src/main/java/de/jplag/golang/Language.java

@@ -6,6 +6,7 @@ import java.util.Set;
 
 import org.kohsuke.MetaInfServices;
 
+import de.jplag.ParsingException;
 import de.jplag.Token;
 
 @MetaInfServices(de.jplag.Language.class)
@@ -42,13 +43,7 @@ public class Language implements de.jplag.Language {
     }
 
     @Override
-    public List<Token> parse(Set<File> files) {
+    public List<Token> parse(Set<File> files) throws ParsingException {
         return parserAdapter.parse(files);
     }
-
-    @Override
-    public boolean hasErrors() {
-        return parserAdapter.hasErrors();
-    }
-
 }

+ 2 - 1
languages/golang/src/test/java/de/jplag/golang/GoLanguageTest.java

@@ -21,6 +21,7 @@ import org.junit.jupiter.api.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import de.jplag.ParsingException;
 import de.jplag.SharedTokenType;
 import de.jplag.Token;
 import de.jplag.TokenPrinter;
@@ -57,7 +58,7 @@ class GoLanguageTest {
     }
 
     @Test
-    void parseTestFiles() {
+    void parseTestFiles() throws ParsingException {
         for (String fileName : testFiles) {
             List<Token> tokens = language.parse(Set.of(new File(testFileLocation, fileName)));
             String output = TokenPrinter.printTokens(tokens, testFileLocation);

+ 18 - 7
languages/java/src/main/java/de/jplag/java/JavacAdapter.java

@@ -5,12 +5,14 @@ import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.util.Collections;
 import java.util.List;
+import java.util.Locale;
 import java.util.Set;
 
 import javax.tools.Diagnostic;
 import javax.tools.DiagnosticCollector;
 import javax.tools.JavaCompiler;
 import javax.tools.JavaCompiler.CompilationTask;
+import javax.tools.JavaFileObject;
 import javax.tools.StandardJavaFileManager;
 import javax.tools.ToolProvider;
 
@@ -22,13 +24,14 @@ import com.sun.source.util.JavacTask;
 import com.sun.source.util.SourcePositions;
 import com.sun.source.util.Trees;
 
+import de.jplag.ParsingException;
 import de.jplag.Token;
 
 public class JavacAdapter {
 
     private static final JavaCompiler javac = ToolProvider.getSystemJavaCompiler();
 
-    public int parseFiles(Set<File> files, final Parser parser) {
+    public void parseFiles(Set<File> files, final Parser parser) throws ParsingException {
         var listener = new DiagnosticCollector<>();
 
         try (final StandardJavaFileManager fileManager = javac.getStandardFileManager(listener, null, StandardCharsets.UTF_8)) {
@@ -42,13 +45,18 @@ public class JavacAdapter {
             for (final CompilationUnitTree ast : executeCompilationTask(task, parser.logger)) {
                 File file = new File(ast.getSourceFile().toUri());
                 final LineMap map = ast.getLineMap();
-                ast.accept(new TokenGeneratingTreeScanner(file, parser, map, positions, ast), null);
+                var scanner = new TokenGeneratingTreeScanner(file, parser, map, positions, ast);
+                ast.accept(scanner, null);
+                if (scanner.getParsingException() != null) {
+                    throw scanner.getParsingException();
+                }
                 parser.add(Token.fileEnd(file));
             }
         } catch (IOException exception) {
             parser.logger.error(exception.getMessage(), exception);
+            throw new ParsingException(null, exception.getMessage(), exception);
         }
-        return processErrors(parser.logger, listener);
+        processErrors(parser.logger, listener);
     }
 
     private Iterable<? extends CompilationUnitTree> executeCompilationTask(final CompilationTask task, Logger logger) {
@@ -61,15 +69,18 @@ public class JavacAdapter {
         return abstractSyntaxTrees;
     }
 
-    private int processErrors(Logger logger, DiagnosticCollector<Object> listener) {
-        int errors = 0;
+    private void processErrors(Logger logger, DiagnosticCollector<Object> listener) throws ParsingException {
         for (Diagnostic<?> diagnosticItem : listener.getDiagnostics()) {
             if (diagnosticItem.getKind() == javax.tools.Diagnostic.Kind.ERROR) {
+                File file = null;
+                if (diagnosticItem.getSource() instanceof JavaFileObject) {
+                    JavaFileObject fileObject = (JavaFileObject) diagnosticItem.getSource();
+                    file = new File(fileObject.toUri());
+                }
                 logger.error("{}", diagnosticItem);
-                errors++;
+                throw new ParsingException(file, diagnosticItem.getMessage(Locale.getDefault()));
             }
         }
-        return errors;
     }
 
 }

+ 2 - 7
languages/java/src/main/java/de/jplag/java/Language.java

@@ -6,6 +6,7 @@ import java.util.Set;
 
 import org.kohsuke.MetaInfServices;
 
+import de.jplag.ParsingException;
 import de.jplag.Token;
 
 /**
@@ -42,13 +43,7 @@ public class Language implements de.jplag.Language {
     }
 
     @Override
-    public List<Token> parse(Set<File> files) {
+    public List<Token> parse(Set<File> files) throws ParsingException {
         return this.parser.parse(files);
     }
-
-    @Override
-    public boolean hasErrors() {
-        return this.parser.hasErrors();
-    }
-
 }

+ 3 - 7
languages/java/src/main/java/de/jplag/java/Parser.java

@@ -6,6 +6,7 @@ import java.util.List;
 import java.util.Set;
 
 import de.jplag.AbstractParser;
+import de.jplag.ParsingException;
 import de.jplag.Token;
 import de.jplag.TokenType;
 
@@ -19,10 +20,9 @@ public class Parser extends AbstractParser {
         super();
     }
 
-    public List<Token> parse(Set<File> files) {
+    public List<Token> parse(Set<File> files) throws ParsingException {
         tokens = new ArrayList<>();
-        errors = 0;
-        errors += new JavacAdapter().parseFiles(files, this);
+        new JavacAdapter().parseFiles(files, this);
         return tokens;
     }
 
@@ -33,8 +33,4 @@ public class Parser extends AbstractParser {
     public void add(Token token) {
         tokens.add(token);
     }
-
-    public void increaseErrors() {
-        errors++;
-    }
 }

+ 9 - 1
languages/java/src/main/java/de/jplag/java/TokenGeneratingTreeScanner.java

@@ -44,6 +44,8 @@ import com.sun.source.tree.YieldTree;
 import com.sun.source.util.SourcePositions;
 import com.sun.source.util.TreeScanner;
 
+import de.jplag.ParsingException;
+
 final class TokenGeneratingTreeScanner extends TreeScanner<Object, Object> {
     private final File file;
     private final Parser parser;
@@ -51,6 +53,8 @@ final class TokenGeneratingTreeScanner extends TreeScanner<Object, Object> {
     private final SourcePositions positions;
     private final CompilationUnitTree ast;
 
+    private ParsingException parsingException;
+
     public TokenGeneratingTreeScanner(File file, Parser parser, LineMap map, SourcePositions positions, CompilationUnitTree ast) {
         this.file = file;
         this.parser = parser;
@@ -59,6 +63,10 @@ final class TokenGeneratingTreeScanner extends TreeScanner<Object, Object> {
         this.ast = ast;
     }
 
+    public ParsingException getParsingException() {
+        return parsingException;
+    }
+
     /**
      * Convenience method that adds a specific token.
      * @param tokenType is the type of the token.
@@ -393,7 +401,7 @@ final class TokenGeneratingTreeScanner extends TreeScanner<Object, Object> {
 
     @Override
     public Object visitErroneous(ErroneousTree node, Object p) {
-        parser.increaseErrors();
+        parsingException = new ParsingException(file, "error while visiting %s".formatted(node));
         return super.visitErroneous(node, p);
     }
 

+ 5 - 7
languages/kotlin/src/main/java/de/jplag/kotlin/KotlinParserAdapter.java

@@ -14,6 +14,7 @@ import org.antlr.v4.runtime.tree.ParseTree;
 import org.antlr.v4.runtime.tree.ParseTreeWalker;
 
 import de.jplag.AbstractParser;
+import de.jplag.ParsingException;
 import de.jplag.Token;
 import de.jplag.kotlin.grammar.KotlinLexer;
 import de.jplag.kotlin.grammar.KotlinParser;
@@ -34,18 +35,16 @@ public class KotlinParserAdapter extends AbstractParser {
      * @param files the set of files.
      * @return a list containing all tokens of all files.
      */
-    public List<Token> parse(Set<File> files) {
+    public List<Token> parse(Set<File> files) throws ParsingException {
         tokens = new ArrayList<>();
         for (File file : files) {
-            if (!parseFile(file)) {
-                errors++;
-            }
+            parseFile(file);
             tokens.add(Token.fileEnd(file));
         }
         return tokens;
     }
 
-    private boolean parseFile(File file) {
+    private void parseFile(File file) throws ParsingException {
         try (FileInputStream inputStream = new FileInputStream(file)) {
             currentFile = file;
 
@@ -63,9 +62,8 @@ public class KotlinParserAdapter extends AbstractParser {
             }
         } catch (IOException exception) {
             logger.error("Parsing Error in '{}': {}{}", file.getName(), File.separator, exception);
-            return false;
+            throw new ParsingException(file, exception.getMessage(), exception);
         }
-        return true;
     }
 
     /**

+ 2 - 6
languages/kotlin/src/main/java/de/jplag/kotlin/Language.java

@@ -6,6 +6,7 @@ import java.util.Set;
 
 import org.kohsuke.MetaInfServices;
 
+import de.jplag.ParsingException;
 import de.jplag.Token;
 
 /**
@@ -45,12 +46,7 @@ public class Language implements de.jplag.Language {
     }
 
     @Override
-    public List<Token> parse(Set<File> files) {
+    public List<Token> parse(Set<File> files) throws ParsingException {
         return parserAdapter.parse(files);
     }
-
-    @Override
-    public boolean hasErrors() {
-        return parserAdapter.hasErrors();
-    }
 }

+ 2 - 1
languages/kotlin/src/test/java/de/jplag/kotlin/KotlinLanguageTest.java

@@ -19,6 +19,7 @@ import org.junit.jupiter.api.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import de.jplag.ParsingException;
 import de.jplag.SharedTokenType;
 import de.jplag.Token;
 import de.jplag.TokenPrinter;
@@ -56,7 +57,7 @@ class KotlinLanguageTest {
     }
 
     @Test
-    void parseTestFiles() {
+    void parseTestFiles() throws ParsingException {
         for (String fileName : testFiles) {
             List<Token> tokens = language.parse(Set.of(new File(testFileLocation, fileName)));
             String output = TokenPrinter.printTokens(tokens, testFileLocation);

+ 2 - 6
languages/python-3/src/main/java/de/jplag/python3/Language.java

@@ -6,6 +6,7 @@ import java.util.Set;
 
 import org.kohsuke.MetaInfServices;
 
+import de.jplag.ParsingException;
 import de.jplag.Token;
 
 @MetaInfServices(de.jplag.Language.class)
@@ -40,12 +41,7 @@ public class Language implements de.jplag.Language {
     }
 
     @Override
-    public List<Token> parse(Set<File> files) {
+    public List<Token> parse(Set<File> files) throws ParsingException {
         return this.parser.parse(files);
     }
-
-    @Override
-    public boolean hasErrors() {
-        return this.parser.hasErrors();
-    }
 }

+ 5 - 9
languages/python-3/src/main/java/de/jplag/python3/Parser.java

@@ -15,6 +15,7 @@ import org.antlr.v4.runtime.tree.ParseTree;
 import org.antlr.v4.runtime.tree.ParseTreeWalker;
 
 import de.jplag.AbstractParser;
+import de.jplag.ParsingException;
 import de.jplag.Token;
 import de.jplag.TokenType;
 import de.jplag.python3.grammar.Python3Lexer;
@@ -33,20 +34,17 @@ public class Parser extends AbstractParser {
         super();
     }
 
-    public List<Token> parse(Set<File> files) {
+    public List<Token> parse(Set<File> files) throws ParsingException {
         tokens = new ArrayList<>();
-        errors = 0;
         for (File file : files) {
             logger.trace("Parsing file {}", file.getName());
-            if (!parseFile(file)) {
-                errors++;
-            }
+            parseFile(file);
             tokens.add(Token.fileEnd(file));
         }
         return tokens;
     }
 
-    private boolean parseFile(File file) {
+    private void parseFile(File file) throws ParsingException {
         BufferedInputStream inputStream;
 
         CharStream input;
@@ -73,10 +71,8 @@ public class Parser extends AbstractParser {
 
         } catch (IOException e) {
             logger.error("Parsing Error in '" + file + "': " + e.getMessage(), e);
-            return false;
+            throw new ParsingException(file, e.getMessage(), e);
         }
-
-        return true;
     }
 
     public void add(TokenType type, org.antlr.v4.runtime.Token token) {

+ 2 - 6
languages/rlang/src/main/java/de/jplag/rlang/Language.java

@@ -6,6 +6,7 @@ import java.util.Set;
 
 import org.kohsuke.MetaInfServices;
 
+import de.jplag.ParsingException;
 import de.jplag.Token;
 
 /**
@@ -45,12 +46,7 @@ public class Language implements de.jplag.Language {
     }
 
     @Override
-    public List<Token> parse(Set<File> files) {
+    public List<Token> parse(Set<File> files) throws ParsingException {
         return parserAdapter.parse(files);
     }
-
-    @Override
-    public boolean hasErrors() {
-        return parserAdapter.hasErrors();
-    }
 }

+ 5 - 8
languages/rlang/src/main/java/de/jplag/rlang/RParserAdapter.java

@@ -14,6 +14,7 @@ import org.antlr.v4.runtime.tree.ParseTree;
 import org.antlr.v4.runtime.tree.ParseTreeWalker;
 
 import de.jplag.AbstractParser;
+import de.jplag.ParsingException;
 import de.jplag.Token;
 import de.jplag.TokenType;
 import de.jplag.rlang.grammar.RFilter;
@@ -41,19 +42,16 @@ public class RParserAdapter extends AbstractParser {
      * @param files the set of files.
      * @return a list containing all tokens of all files.
      */
-    public List<Token> parse(Set<File> files) {
+    public List<Token> parse(Set<File> files) throws ParsingException {
         tokens = new ArrayList<>();
-        errors = 0;
         for (File file : files) {
-            if (!parseFile(file)) {
-                errors++;
-            }
+            parseFile(file);
             tokens.add(Token.fileEnd(file));
         }
         return tokens;
     }
 
-    private boolean parseFile(File file) {
+    private void parseFile(File file) throws ParsingException {
         try (FileInputStream inputStream = new FileInputStream(file)) {
             currentFile = file;
 
@@ -78,9 +76,8 @@ public class RParserAdapter extends AbstractParser {
             }
         } catch (IOException exception) {
             logger.error("Parsing Error in '" + file.getName() + "': " + File.separator + exception.getMessage(), exception);
-            return false;
+            throw new ParsingException(file, exception.getMessage(), exception);
         }
-        return true;
     }
 
     /**

+ 2 - 1
languages/rlang/src/test/java/de/jplag/rlang/RLanguageTest.java

@@ -19,6 +19,7 @@ import org.junit.jupiter.api.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import de.jplag.ParsingException;
 import de.jplag.SharedTokenType;
 import de.jplag.Token;
 import de.jplag.TokenPrinter;
@@ -46,7 +47,7 @@ class RLanguageTest {
     }
 
     @Test
-    void parseTestFiles() {
+    void parseTestFiles() throws ParsingException {
         for (String fileName : testFiles) {
             List<Token> tokens = language.parse(Set.of(new File(testFileLocation, fileName)));
             String output = TokenPrinter.printTokens(tokens, testFileLocation);

+ 2 - 7
languages/rust/src/main/java/de/jplag/rust/Language.java

@@ -4,6 +4,7 @@ import java.io.File;
 import java.util.List;
 import java.util.Set;
 
+import de.jplag.ParsingException;
 import de.jplag.Token;
 
 public class Language implements de.jplag.Language {
@@ -40,13 +41,7 @@ public class Language implements de.jplag.Language {
     }
 
     @Override
-    public List<Token> parse(Set<File> files) {
+    public List<Token> parse(Set<File> files) throws ParsingException {
         return parserAdapter.parse(files);
     }
-
-    @Override
-    public boolean hasErrors() {
-        return parserAdapter.hasErrors();
-    }
-
 }

+ 5 - 8
languages/rust/src/main/java/de/jplag/rust/RustParserAdapter.java

@@ -14,6 +14,7 @@ import org.antlr.v4.runtime.tree.ParseTree;
 import org.antlr.v4.runtime.tree.ParseTreeWalker;
 
 import de.jplag.AbstractParser;
+import de.jplag.ParsingException;
 import de.jplag.Token;
 import de.jplag.rust.grammar.RustLexer;
 import de.jplag.rust.grammar.RustParser;
@@ -28,19 +29,16 @@ public class RustParserAdapter extends AbstractParser {
      * @param files the set of files.
      * @return a list containing all tokens of all files.
      */
-    public List<Token> parse(Set<File> files) {
+    public List<Token> parse(Set<File> files) throws ParsingException {
         tokens = new ArrayList<>();
-        errors = 0;
         for (File file : files) {
-            if (!parseFile(file)) {
-                errors++;
-            }
+            parseFile(file);
             tokens.add(Token.fileEnd(file));
         }
         return tokens;
     }
 
-    private boolean parseFile(File file) {
+    private void parseFile(File file) throws ParsingException {
         try (FileInputStream inputStream = new FileInputStream(file)) {
             currentFile = file;
 
@@ -61,9 +59,8 @@ public class RustParserAdapter extends AbstractParser {
             }
         } catch (IOException exception) {
             logger.error("Parsing Error in '" + file.getName() + "':" + File.separator, exception);
-            return false;
+            throw new ParsingException(file, exception.getMessage(), exception);
         }
-        return true;
     }
 
     /**

+ 2 - 1
languages/rust/src/test/java/de/jplag/rust/RustLanguageTest.java

@@ -18,6 +18,7 @@ import org.junit.jupiter.api.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import de.jplag.ParsingException;
 import de.jplag.SharedTokenType;
 import de.jplag.Token;
 import de.jplag.TokenPrinter;
@@ -50,7 +51,7 @@ class RustLanguageTest {
     }
 
     @Test
-    void parseTestFiles() {
+    void parseTestFiles() throws ParsingException {
         for (String fileName : testFiles) {
             List<Token> tokens = language.parse(Set.of(new File(testFileLocation, fileName)));
             String output = TokenPrinter.printTokens(tokens, testFileLocation);

+ 0 - 3
languages/scala/src/main/scala/de/jplag/scala/Language.scala

@@ -21,7 +21,4 @@ class Language extends de.jplag.Language {
   override def minimumTokenMatch = 8
 
   override def parse(files: util.Set[File]): java.util.List[Token] = this.parser.parse(files.asScala.toSet).asJava
-
-  override def hasErrors: Boolean = this.parser.hasErrors
-
 }

+ 5 - 12
languages/scala/src/main/scala/de/jplag/scala/Parser.scala

@@ -1,11 +1,10 @@
 package de.jplag.scala
 
 import de.jplag.scala.ScalaTokenType._
-import de.jplag.{AbstractParser, Token}
+import de.jplag.{AbstractParser, ParsingException, Token}
 
 import java.io.File
-
-import scala.collection.mutable.ListBuffer 
+import scala.collection.mutable.ListBuffer
 import scala.meta._
 
 
@@ -332,19 +331,15 @@ class Parser extends AbstractParser {
 
     def parse(files: Set[File]): List[Token] = {
         tokens = ListBuffer()
-        errors = 0
-
         for (file <- files) {
-            if (!parseFile(file)) {
-                errors += 1
-            }
+            parseFile(file)
             System.gc()
         }
 
         tokens.toList
     }
 
-    private def parseFile(file: File): Boolean = {
+    private def parseFile(file: File) = {
         currentFile = file
 
         try {
@@ -358,10 +353,8 @@ class Parser extends AbstractParser {
         } catch {
             case exception: Throwable =>
                 exception.printStackTrace()
-                return false
+                throw new ParsingException(file, exception.getMessage, exception)
         }
-
-        true
     }
 
     /**

+ 2 - 6
languages/scheme/src/main/java/de/jplag/scheme/Language.java

@@ -6,6 +6,7 @@ import java.util.Set;
 
 import org.kohsuke.MetaInfServices;
 
+import de.jplag.ParsingException;
 import de.jplag.Token;
 
 @MetaInfServices(de.jplag.Language.class)
@@ -39,12 +40,7 @@ public class Language implements de.jplag.Language {
     }
 
     @Override
-    public List<Token> parse(Set<File> files) {
+    public List<Token> parse(Set<File> files) throws ParsingException {
         return this.parser.parse(files);
     }
-
-    @Override
-    public boolean hasErrors() {
-        return this.parser.hasErrors();
-    }
 }

+ 3 - 5
languages/scheme/src/main/java/de/jplag/scheme/Parser.java

@@ -6,6 +6,7 @@ import java.util.List;
 import java.util.Set;
 
 import de.jplag.AbstractParser;
+import de.jplag.ParsingException;
 import de.jplag.Token;
 import de.jplag.TokenType;
 
@@ -21,15 +22,12 @@ public class Parser extends AbstractParser {
         super();
     }
 
-    public List<Token> parse(Set<File> files) {
+    public List<Token> parse(Set<File> files) throws ParsingException {
         tokens = new ArrayList<>();
-        errors = 0;
         for (File file : files) {
             currentFile = file;
             logger.trace("Parsing file {}", file.getName());
-            if (!SchemeParser.parseFile(file, null, this)) {
-                errors++;
-            }
+            SchemeParser.parseFile(file, null, this);
             tokens.add(Token.fileEnd(currentFile));
         }
         return tokens;

+ 6 - 5
languages/scheme/src/main/javacc/Scheme.jj

@@ -58,12 +58,14 @@ import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 
+import de.jplag.ParsingException;
+
 public class SchemeParser {
     /* used for context in the template production rule */
     private static int templateParam;
     private Parser parser2;
 
-    public static boolean parseFile(File file, SchemeParser parser, Parser parserX) {
+    public static void parseFile(File file, SchemeParser parser, Parser parserX) throws ParsingException {
         try {
             FileInputStream in = new FileInputStream(file);
             if (parser == null) {
@@ -74,18 +76,17 @@ public class SchemeParser {
             parser.parser2 = parserX;
         } catch (FileNotFoundException e) {
             System.out.println("Scheme Parser R4RS:  File " + file.getName() + " not found.");
-            return false;
+            throw new ParsingException(file, e.getMessage(), e);
         }
         try {
             parser.Program();
         } catch (ParseException e) {
             parserX.logger.error("Parsing Error in '" + file.getName() + "': " + e.getMessage());
-            return false;
+            throw new ParsingException(file, e.getMessage(), e);
         } catch (TokenMgrException e) {
             parserX.logger.error("Scanning Error in '" + file.getName() + "': " + e.getMessage());
-            return false;
+            throw new ParsingException(file, e.getMessage(), e);
         }
-        return true;
     }
 }
 PARSER_END(SchemeParser)

+ 2 - 6
languages/text/src/main/java/de/jplag/text/Language.java

@@ -6,6 +6,7 @@ import java.util.Set;
 
 import org.kohsuke.MetaInfServices;
 
+import de.jplag.ParsingException;
 import de.jplag.Token;
 
 /**
@@ -44,12 +45,7 @@ public class Language implements de.jplag.Language {
     }
 
     @Override
-    public List<Token> parse(Set<File> files) {
+    public List<Token> parse(Set<File> files) throws ParsingException {
         return parserAdapter.parse(files);
     }
-
-    @Override
-    public boolean hasErrors() {
-        return parserAdapter.hasErrors();
-    }
 }

+ 6 - 12
languages/text/src/main/java/de/jplag/text/ParserAdapter.java

@@ -9,6 +9,7 @@ import java.util.Properties;
 import java.util.Set;
 
 import de.jplag.AbstractParser;
+import de.jplag.ParsingException;
 import de.jplag.Token;
 
 import edu.stanford.nlp.ling.CoreLabel;
@@ -37,27 +38,21 @@ public class ParserAdapter extends AbstractParser {
         this.pipeline = new StanfordCoreNLP(properties);
     }
 
-    public List<Token> parse(Set<File> files) {
+    public List<Token> parse(Set<File> files) throws ParsingException {
         tokens = new ArrayList<>();
-        errors = 0;
         for (File file : files) {
             logger.trace("Parsing file {}", file);
-            if (!parseFile(file)) {
-                errors++;
-            }
+            parseFile(file);
             tokens.add(Token.fileEnd(file));
         }
         return tokens;
     }
 
-    private boolean parseFile(File file) {
+    private void parseFile(File file) throws ParsingException {
         this.currentFile = file;
         this.currentLine = 1; // lines start at 1
         this.currentLineBreakIndex = 0;
         String content = readFile(file);
-        if (content == null) {
-            return false;
-        }
         int lastTokenEnd = 0;
         CoreDocument coreDocument = pipeline.processToCoreDocument(content);
         for (CoreLabel token : coreDocument.tokens()) {
@@ -67,7 +62,6 @@ public class ParserAdapter extends AbstractParser {
                 addToken(token);
             }
         }
-        return true;
     }
 
     /**
@@ -104,12 +98,12 @@ public class ParserAdapter extends AbstractParser {
         tokens.add(new Token(new TextTokenType(text), currentFile, currentLine, column, length));
     }
 
-    private String readFile(File file) {
+    private String readFile(File file) throws ParsingException {
         try {
             return Files.readString(file.toPath());
         } catch (IOException e) {
             logger.error("Error reading from file {}", file.getName(), e);
-            return null;
+            throw new ParsingException(file, e.getMessage(), e);
         }
     }
 }

+ 4 - 3
languages/text/src/test/java/jplag/text/TextLanguageTest.java

@@ -19,6 +19,7 @@ import org.junit.jupiter.params.provider.ValueSource;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import de.jplag.ParsingException;
 import de.jplag.Token;
 import de.jplag.TokenPrinter;
 import de.jplag.TokenType;
@@ -41,7 +42,7 @@ class TextLanguageTest {
     }
 
     @Test
-    void testParsingJavaDoc() {
+    void testParsingJavaDoc() throws ParsingException {
         // Parse test input
         List<Token> result = language.parse(Set.of(new File(BASE_PATH.toFile(), TEST_SUBJECT)));
         logger.info(TokenPrinter.printTokens(result, baseDirectory));
@@ -53,7 +54,7 @@ class TextLanguageTest {
 
     @ParameterizedTest
     @ValueSource(strings = {"\n", "\r", "\r\n",})
-    void testLineBreakInputs(String input, @TempDir Path tempDir) throws IOException {
+    void testLineBreakInputs(String input, @TempDir Path tempDir) throws IOException, ParsingException {
         Path filePath = tempDir.resolve("input.txt");
         Files.writeString(filePath, input);
         List<Token> result = language.parse(Set.of(filePath.toFile()));
@@ -62,7 +63,7 @@ class TextLanguageTest {
 
     @ParameterizedTest
     @ValueSource(strings = {"\ntoken", "\rtoken", "\r\ntoken",})
-    void testTokenAfterLineBreak(String input, @TempDir Path tempDir) throws IOException {
+    void testTokenAfterLineBreak(String input, @TempDir Path tempDir) throws IOException, ParsingException {
         Path filePath = tempDir.resolve("input.txt");
         Files.writeString(filePath, input);
         List<Token> result = language.parse(Set.of(filePath.toFile()));