diff --git a/src/main/java/com/jozufozu/flywheel/backend/compile/core/Compilation.java b/src/main/java/com/jozufozu/flywheel/backend/compile/core/Compilation.java index a5d4683ce..36f7780d9 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/compile/core/Compilation.java +++ b/src/main/java/com/jozufozu/flywheel/backend/compile/core/Compilation.java @@ -61,7 +61,7 @@ public class Compilation { } GL20.glDeleteShader(handle); - return ShaderResult.failure(new FailedCompilation(shaderName, files, generatedSource.toString(), infoLog)); + return ShaderResult.failure(new FailedCompilation(shaderName, files, generatedSource.toString(), source, infoLog)); } public void enableExtension(String ext) { @@ -80,7 +80,7 @@ public class Compilation { private void appendHeader(SourceComponent component, String source) { if (component instanceof SourceFile file) { - int fileID = files.size(); + int fileID = files.size() + 1; files.add(file); diff --git a/src/main/java/com/jozufozu/flywheel/backend/compile/core/FailedCompilation.java b/src/main/java/com/jozufozu/flywheel/backend/compile/core/FailedCompilation.java index d5b91d2c8..13789f6c2 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/compile/core/FailedCompilation.java +++ b/src/main/java/com/jozufozu/flywheel/backend/compile/core/FailedCompilation.java @@ -1,6 +1,8 @@ package com.jozufozu.flywheel.backend.compile.core; import java.util.List; +import java.util.Locale; +import java.util.function.Consumer; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -13,6 +15,7 @@ import com.jozufozu.flywheel.glsl.SourceFile; import com.jozufozu.flywheel.glsl.SourceLines; import com.jozufozu.flywheel.glsl.error.ConsoleColors; import com.jozufozu.flywheel.glsl.error.ErrorBuilder; +import com.jozufozu.flywheel.glsl.error.ErrorLevel; import com.jozufozu.flywheel.glsl.span.Span; import com.jozufozu.flywheel.lib.util.StringUtil; @@ -20,16 +23,20 @@ import net.minecraft.resources.ResourceLocation; public class FailedCompilation { public static final ResourceLocation GENERATED_SOURCE_NAME = Flywheel.rl("generated_source"); - private static final Pattern ERROR_LINE = Pattern.compile("(\\d+)\\((\\d+)\\) : (.*)"); + private static final Pattern PATTERN_ONE = Pattern.compile("(\\d+)\\((\\d+)\\) : (.*)"); + private static final Pattern PATTERN_TWO = Pattern.compile("(\\w+): (\\d+):(\\d+):(?: '(.+?)' :)?(.*)"); private final List files; private final SourceLines generatedSource; private final String errorLog; private final String shaderName; + // Unused, but handy for debugging. + private final String completeSource; - public FailedCompilation(String shaderName, List files, String generatedSource, String errorLog) { + public FailedCompilation(String shaderName, List files, String generatedSource, String completeSource, String errorLog) { this.shaderName = shaderName; this.files = files; this.generatedSource = new SourceLines(GENERATED_SOURCE_NAME, generatedSource); + this.completeSource = completeSource; this.errorLog = errorLog; } @@ -45,26 +52,60 @@ public class FailedCompilation { @NotNull private Stream errorStream() { return errorLog.lines() - .map(this::interpretErrorLine); + .mapMulti(this::interpretLine); } - private ErrorBuilder interpretErrorLine(String s) { - Matcher matcher = ERROR_LINE.matcher(s); - - if (matcher.find()) { - int fileId = Integer.parseInt(matcher.group(1)); - int lineNo = Integer.parseInt(matcher.group(2)); - var msg = StringUtil.trimPrefix(matcher.group(3), "error") - .stripLeading(); - - if (fileId == 0) { - return interpretGeneratedError(lineNo, msg); - } else { - return interpretSourceError(fileId, lineNo, msg); - } + private void interpretLine(String s, Consumer out) { + if (s.isEmpty()) { + return; + } + + Matcher matcher; + + matcher = PATTERN_ONE.matcher(s); + if (matcher.find()) { + out.accept(interpretPattern1(matcher)); + return; + } + + matcher = PATTERN_TWO.matcher(s); + if (matcher.find()) { + out.accept(interpretPattern2(matcher)); + return; + } + + out.accept(ErrorBuilder.create() + .error(s)); + } + + private ErrorBuilder interpretPattern1(Matcher matcher) { + int fileId = Integer.parseInt(matcher.group(1)); + int lineNo = Integer.parseInt(matcher.group(2)); + var msg = StringUtil.trimPrefix(matcher.group(3), "error") + .stripLeading(); + + if (fileId == 0) { + return interpretGeneratedError(ErrorLevel.ERROR, lineNo, msg); + } else { + return interpretSourceError(fileId, lineNo, msg); + } + } + + private ErrorBuilder interpretPattern2(Matcher matcher) { + var errorLevel = parseErrorLevel(matcher.group(1)); + + int fileId = Integer.parseInt(matcher.group(2)); + int lineNo = Integer.parseInt(matcher.group(3)) - 1; + + String span = matcher.group(4); + + var msg = matcher.group(5).trim(); + + if (fileId == 0) { + return interpretGeneratedError(errorLevel, lineNo, msg); + } else { + return interpretWithSpan(errorLevel, fileId, lineNo, span, msg); } - return ErrorBuilder.create() - .error(s); } private ErrorBuilder interpretSourceError(int fileId, int lineNo, String msg) { @@ -77,11 +118,36 @@ public class FailedCompilation { .pointAt(span, 1); } - private ErrorBuilder interpretGeneratedError(int lineNo, String msg) { + private ErrorBuilder interpretWithSpan(ErrorLevel errorLevel, int fileId, int lineNo, String span, String msg) { + var sourceFile = files.get(fileId - 1); + + Span errorSpan; + if (span != null) { + errorSpan = sourceFile.getLineSpanMatching(lineNo, span); + } else { + errorSpan = sourceFile.getLineSpanNoWhitespace(lineNo); + } + return ErrorBuilder.create() - .error(msg) + .header(errorLevel, msg) + .pointAtFile(sourceFile) + .pointAt(errorSpan, 1); + } + + private ErrorBuilder interpretGeneratedError(ErrorLevel errorLevel, int lineNo, String msg) { + return ErrorBuilder.create() + .header(errorLevel, msg) .pointAtFile("[in generated source]") .pointAtLine(generatedSource, lineNo, 1) .note("This generally indicates a bug in Flywheel, not your shader code."); } + + @NotNull + private static ErrorLevel parseErrorLevel(String level) { + return switch (level.toLowerCase(Locale.ROOT)) { + case "error" -> ErrorLevel.ERROR; + case "warning", "warn" -> ErrorLevel.WARN; + default -> ErrorLevel.NOTE; + }; + } } diff --git a/src/main/java/com/jozufozu/flywheel/glsl/SourceFile.java b/src/main/java/com/jozufozu/flywheel/glsl/SourceFile.java index 646bcd478..7d945189f 100644 --- a/src/main/java/com/jozufozu/flywheel/glsl/SourceFile.java +++ b/src/main/java/com/jozufozu/flywheel/glsl/SourceFile.java @@ -8,6 +8,7 @@ import java.util.Optional; import java.util.Set; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -144,6 +145,24 @@ public class SourceFile implements SourceComponent { return new StringSpan(source, begin, end); } + public Span getLineSpanMatching(int line, @Nullable String match) { + if (match == null) { + return getLineSpanNoWhitespace(line); + } + + var spanBegin = source.lineString(line) + .indexOf(match); + + if (spanBegin == -1) { + return getLineSpanNoWhitespace(line); + } + + int begin = source.lineStartIndex(line) + spanBegin; + int end = begin + match.length(); + + return new StringSpan(source, begin, end); + } + /** * Search this file and recursively search all imports to find a struct definition matching the given name. *