mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2026-10-25 15:05:25 +00:00
148 lines
5.3 KiB
TypeScript
148 lines
5.3 KiB
TypeScript
import assert from "assert";
|
|
import { describe, it } from "mocha";
|
|
import { SourceFile } from "../../src/common/workertypes";
|
|
import { findListingLocation, ListingLocationContext } from "../../src/ide/search/listinglocation";
|
|
|
|
const LOOKAHEAD = 64;
|
|
|
|
function makeFile(lines: { line: number, offset: number }[]): SourceFile {
|
|
return new SourceFile(lines, '');
|
|
}
|
|
|
|
// a context where every candidate window id is "known" and file-prefix
|
|
// lookup just strips a trailing extension, like the real IDE's windows
|
|
function makeContext(overrides: Partial<ListingLocationContext> = {}): ListingLocationContext {
|
|
return {
|
|
listings: {},
|
|
filename2path: {},
|
|
isWindow: () => true,
|
|
findWindowWithFilePrefix: (fn) => fn.replace(/\.lst$/, ''),
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe('findListingLocation', function () {
|
|
|
|
it('returns null when there are no listings', function () {
|
|
const ctx = makeContext({ listings: {} });
|
|
assert.strictEqual(findListingLocation(0x100, ctx, LOOKAHEAD), null);
|
|
});
|
|
|
|
it('returns null when the PC matches no line in any listing', function () {
|
|
const ctx = makeContext({
|
|
listings: {
|
|
'game.c': { lines: [], sourcefile: makeFile([{ line: 1, offset: 0 }]) } as any,
|
|
},
|
|
});
|
|
assert.strictEqual(findListingLocation(0x9999, ctx, LOOKAHEAD), null);
|
|
});
|
|
|
|
it('resolves a sourcefile-only listing via findWindowWithFilePrefix', function () {
|
|
const ctx = makeContext({
|
|
listings: {
|
|
'game.c.lst': { lines: [], sourcefile: makeFile([{ line: 1, offset: 0x10 }]) } as any,
|
|
},
|
|
});
|
|
const loc = findListingLocation(0x10, ctx, LOOKAHEAD);
|
|
assert.ok(loc);
|
|
assert.strictEqual(loc.wndid, 'game.c'); // .lst stripped by findWindowWithFilePrefix
|
|
assert.strictEqual(loc.line, 1);
|
|
});
|
|
|
|
it('prefers the assembly listing over the source file when both are present', function () {
|
|
const ctx = makeContext({
|
|
listings: {
|
|
'game.c.lst': {
|
|
lines: [],
|
|
sourcefile: makeFile([{ line: 1, offset: 0x10 }]),
|
|
assemblyfile: makeFile([{ line: 7, offset: 0x10 }]),
|
|
} as any,
|
|
},
|
|
filename2path: { 'game.c.lst': 'game.c.lst' },
|
|
});
|
|
const loc = findListingLocation(0x10, ctx, LOOKAHEAD);
|
|
assert.ok(loc);
|
|
assert.strictEqual(loc.wndid, 'game.c.lst'); // resolved via filename2path, not findWindowWithFilePrefix
|
|
assert.strictEqual(loc.line, 7);
|
|
});
|
|
|
|
it('skips a listing whose window is not known', function () {
|
|
const ctx = makeContext({
|
|
listings: {
|
|
'unknown.c.lst': { lines: [], sourcefile: makeFile([{ line: 1, offset: 0x10 }]) } as any,
|
|
},
|
|
isWindow: () => false,
|
|
});
|
|
assert.strictEqual(findListingLocation(0x10, ctx, LOOKAHEAD), null);
|
|
});
|
|
|
|
it('picks the closest match among several listings', function () {
|
|
const ctx = makeContext({
|
|
listings: {
|
|
'far.c.lst': { lines: [], sourcefile: makeFile([{ line: 1, offset: 0x00 }]) } as any,
|
|
'near.c.lst': { lines: [], sourcefile: makeFile([{ line: 1, offset: 0x0e }]) } as any,
|
|
},
|
|
});
|
|
// pc=0x10: 'near' (offset 0x0e, score 2) beats 'far' (offset 0x00, score 16)
|
|
const loc = findListingLocation(0x10, ctx, LOOKAHEAD);
|
|
assert.ok(loc);
|
|
assert.strictEqual(loc.wndid, 'near.c');
|
|
});
|
|
|
|
it('finds the line closely preceding the PC when there is no exact match', function () {
|
|
const ctx = makeContext({
|
|
listings: {
|
|
'game.c.lst': { lines: [], sourcefile: makeFile([{ line: 5, offset: 0x100 }]) } as any,
|
|
},
|
|
});
|
|
const loc = findListingLocation(0x103, ctx, LOOKAHEAD);
|
|
assert.ok(loc);
|
|
assert.strictEqual(loc.line, 5);
|
|
});
|
|
|
|
it('does not look further behind the PC than the given lookahead', function () {
|
|
const ctx = makeContext({
|
|
listings: {
|
|
'game.c.lst': { lines: [], sourcefile: makeFile([{ line: 5, offset: 0x100 }]) } as any,
|
|
},
|
|
});
|
|
assert.strictEqual(findListingLocation(0x100 + 5, ctx, 4), null);
|
|
});
|
|
|
|
it('falls back to disassembly for library code past the end of a function', function () {
|
|
// main() ends at $865; _cgetc follows at $868 and has no source line.
|
|
// Without symbol boundaries the PC at $868 would wrongly match main's
|
|
// last line (only 3 bytes behind) instead of the disassembly.
|
|
const ctx = makeContext({
|
|
listings: {
|
|
'game.c.lst': { lines: [], sourcefile: makeFile([{ line: 15, offset: 0x865 }]) } as any,
|
|
},
|
|
symbolAddrs: [0x800, 0x840, 0x868],
|
|
});
|
|
assert.strictEqual(findListingLocation(0x868, ctx, LOOKAHEAD), null);
|
|
assert.strictEqual(findListingLocation(0x880, ctx, LOOKAHEAD), null);
|
|
// ...but the call site itself still maps to its source line
|
|
const call = findListingLocation(0x865, ctx, LOOKAHEAD);
|
|
assert.ok(call);
|
|
assert.strictEqual(call.line, 15);
|
|
});
|
|
|
|
it('maps a function prologue to that function\'s first listed line', function () {
|
|
// prev() ends at $800; main() starts at $840 with its first listed line at
|
|
// $845 (prologue unlisted). A PC in main's prologue must not show prev()'s line.
|
|
const ctx = makeContext({
|
|
listings: {
|
|
'game.c.lst': {
|
|
lines: [],
|
|
sourcefile: makeFile([{ line: 4, offset: 0x800 }, { line: 5, offset: 0x845 }]),
|
|
} as any,
|
|
},
|
|
symbolAddrs: [0x800, 0x840, 0x900],
|
|
});
|
|
const loc = findListingLocation(0x843, ctx, LOOKAHEAD);
|
|
assert.ok(loc);
|
|
assert.strictEqual(loc.line, 5);
|
|
});
|
|
|
|
});
|