mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-10-30 13:13:00 +00:00
Improve display of runtime errors (#1320)
- Bump Cobalt to 0.6.0. We now track both line and column numbers of each bytecode instruction, allowing us to map an error to a concrete position. - `loadfile` (and similar functions) now use the full path, rather than the file name. Cobalt truncates this to 30 characters (rather than the previous 60) so this should be less noisy. - The shell, edit and Lua REPL now display the corresponding source code alongside an error. Note this is incredibly limited right now - it won't cope with errors which cross coroutine boundaries. Supporting this is on the roadmap, but requires some careful API design.
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
package dan200.computercraft.core;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntMaps;
|
||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||
import org.slf4j.Logger;
|
||||
@@ -17,19 +18,14 @@ import org.squiddev.cobalt.compiler.LuaC;
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
|
||||
class LuaCoverage {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(LuaCoverage.class);
|
||||
private static final Path ROOT = new File("src/main/resources/data/computercraft/lua").toPath();
|
||||
private static final Path BIOS = ROOT.resolve("bios.lua");
|
||||
private static final Path APIS = ROOT.resolve("rom/apis");
|
||||
private static final Path STARTUP = ROOT.resolve("rom/startup.lua");
|
||||
private static final Path SHELL = ROOT.resolve("rom/programs/shell.lua");
|
||||
private static final Path MULTISHELL = ROOT.resolve("rom/programs/advanced/multishell.lua");
|
||||
private static final Path TREASURE = ROOT.resolve("treasure");
|
||||
|
||||
private final Map<String, Int2IntMap> coverage;
|
||||
private final String blank;
|
||||
@@ -49,26 +45,20 @@ class LuaCoverage {
|
||||
}
|
||||
|
||||
void write(Writer out) throws IOException {
|
||||
Files.find(ROOT, Integer.MAX_VALUE, (path, attr) -> attr.isRegularFile() && !path.startsWith(TREASURE)).forEach(path -> {
|
||||
Files.find(ROOT, Integer.MAX_VALUE, (path, attr) -> attr.isRegularFile()).forEach(path -> {
|
||||
var relative = ROOT.relativize(path);
|
||||
var full = relative.toString().replace('\\', '/');
|
||||
if (!full.endsWith(".lua")) return;
|
||||
|
||||
var possiblePaths = Stream.of(
|
||||
coverage.remove("/" + full),
|
||||
path.equals(BIOS) ? coverage.remove("bios.lua") : null,
|
||||
path.equals(STARTUP) ? coverage.remove("startup.lua") : null,
|
||||
path.equals(SHELL) ? coverage.remove("shell.lua") : null,
|
||||
path.equals(MULTISHELL) ? coverage.remove("multishell.lua") : null,
|
||||
path.startsWith(APIS) ? coverage.remove(path.getFileName().toString()) : null
|
||||
);
|
||||
var files = possiblePaths
|
||||
.filter(Objects::nonNull)
|
||||
.flatMap(x -> x.int2IntEntrySet().stream())
|
||||
.collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue, Integer::sum));
|
||||
var possiblePaths = coverage.remove("/" + full);
|
||||
if (possiblePaths == null) possiblePaths = coverage.remove(full);
|
||||
if (possiblePaths == null) {
|
||||
possiblePaths = Int2IntMaps.EMPTY_MAP;
|
||||
LOG.warn("{} has no coverage data", full);
|
||||
}
|
||||
|
||||
try {
|
||||
writeCoverageFor(out, path, files);
|
||||
writeCoverageFor(out, path, possiblePaths);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
@@ -116,24 +106,26 @@ class LuaCoverage {
|
||||
|
||||
private static IntSet getActiveLines(File file) throws IOException {
|
||||
IntSet activeLines = new IntOpenHashSet();
|
||||
Queue<Prototype> queue = new ArrayDeque<>();
|
||||
|
||||
try (InputStream stream = new FileInputStream(file)) {
|
||||
var proto = LuaC.compile(stream, "@" + file.getPath());
|
||||
Queue<Prototype> queue = new ArrayDeque<>();
|
||||
queue.add(proto);
|
||||
|
||||
while ((proto = queue.poll()) != null) {
|
||||
var lines = proto.lineinfo;
|
||||
if (lines != null) {
|
||||
for (var line : lines) {
|
||||
activeLines.add(line);
|
||||
}
|
||||
}
|
||||
if (proto.p != null) Collections.addAll(queue, proto.p);
|
||||
}
|
||||
} catch (CompileException e) {
|
||||
throw new IllegalStateException("Cannot compile", e);
|
||||
}
|
||||
|
||||
Prototype proto;
|
||||
while ((proto = queue.poll()) != null) {
|
||||
var lines = proto.lineInfo;
|
||||
if (lines != null) {
|
||||
for (var line : lines) {
|
||||
activeLines.add(line);
|
||||
}
|
||||
}
|
||||
if (proto.children != null) Collections.addAll(queue, proto.children);
|
||||
}
|
||||
|
||||
return activeLines;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ public class ComputerTest {
|
||||
try {
|
||||
ComputerBootstrap.run("print('Hello') while true do end", ComputerBootstrap.MAX_TIME);
|
||||
} catch (AssertionError e) {
|
||||
if (e.getMessage().equals("test.lua:1: Too long without yielding")) return;
|
||||
if (e.getMessage().equals("/test.lua:1: Too long without yielding")) return;
|
||||
throw e;
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,9 @@ describe("The parallel library", function()
|
||||
end)
|
||||
|
||||
it("passes errors to the caller", function()
|
||||
expect.error(parallel.waitForAny, function() error("Test error") end):str_match("Test error$")
|
||||
local ok, err = pcall(parallel.waitForAny, function() error("Test error") end)
|
||||
if ok then fail("Expected function to error") end
|
||||
expect(tostring(err)):str_match("Test error$")
|
||||
end)
|
||||
|
||||
it("returns the number of the function that exited first", function()
|
||||
@@ -98,7 +100,9 @@ describe("The parallel library", function()
|
||||
end)
|
||||
|
||||
it("passes errors to the caller", function()
|
||||
expect.error(parallel.waitForAll, function() error("Test error") end):str_match("Test error$")
|
||||
local ok, err = pcall(parallel.waitForAll, function() error("Test error") end)
|
||||
if ok then fail("Expected function to error") end
|
||||
expect(tostring(err)):str_match("Test error$")
|
||||
end)
|
||||
|
||||
it("completes all functions before exiting", function()
|
||||
|
||||
@@ -28,8 +28,6 @@ describe("The Lua base library", function()
|
||||
end)
|
||||
|
||||
describe("loadfile", function()
|
||||
local loadfile = _G.native_loadfile or loadfile
|
||||
|
||||
local function make_file()
|
||||
local tmp = fs.open("test-files/out.lua", "w")
|
||||
tmp.write("return _ENV")
|
||||
@@ -48,7 +46,7 @@ describe("The Lua base library", function()
|
||||
|
||||
it("prefixes the filename with @", function()
|
||||
local info = debug.getinfo(loadfile("/rom/startup.lua"), "S")
|
||||
expect(info):matches { short_src = "startup.lua", source = "@startup.lua" }
|
||||
expect(info):matches { short_src = "/rom/startup.lua", source = "@/rom/startup.lua" }
|
||||
end)
|
||||
|
||||
it("loads a file with the global environment", function()
|
||||
|
||||
Reference in New Issue
Block a user