aboutsummaryrefslogtreecommitdiffgithub
diff options
context:
space:
mode:
authorAustin Adams <git@austinjadams.com>2019-01-30 22:35:38 -0500
committerAustin Adams <git@austinjadams.com>2019-01-30 22:35:38 -0500
commit2ec61a5952821ad2d34bfe58d7d501d5f622b456 (patch)
treea2c1a9ae556f22d6c332b18767293c73321db2c1
parent38d39af46bd35174f4149a58cc4e085937975d8f (diff)
downloadnovice-2ec61a5952821ad2d34bfe58d7d501d5f622b456.tar.gz
novice-2ec61a5952821ad2d34bfe58d7d501d5f622b456.tar.xz
debugger: Load symbol tables
-rw-r--r--novice/cli.test.ts97
-rw-r--r--novice/cli.ts29
-rw-r--r--novice/simulator/debugger.ts8
-rw-r--r--novice/simulator/loaders/complx.test.ts230
-rw-r--r--novice/simulator/loaders/complx.ts114
-rw-r--r--novice/simulator/loaders/loader.ts6
6 files changed, 409 insertions, 75 deletions
diff --git a/novice/cli.test.ts b/novice/cli.test.ts
index 6c060e1..b7c9ec1 100644
--- a/novice/cli.test.ts
+++ b/novice/cli.test.ts
@@ -12,7 +12,7 @@ jest.mock('./simulator');
import { CliDebugger, getSimulatorConfig, Simulator,
SimulatorConfig } from './simulator';
jest.mock('./isa');
-import { getIsa, StreamIO } from './isa';
+import { getIsa, StreamIO, SymbTable } from './isa';
describe('cli', () => {
let stdin: Readable, stdout: Writable, stderr: Writable;
@@ -292,6 +292,8 @@ describe('cli', () => {
isa: {donkey: 'horse apple'},
loader: {
load: jest.fn(),
+ symbFileExt: () => 'lemonade',
+ loadSymb: jest.fn(),
},
};
// @ts-ignore
@@ -318,6 +320,8 @@ describe('cli', () => {
fs.createReadStream.mockReset();
// @ts-ignore
mockSimConfig.loader.load.mockReset();
+ // @ts-ignore
+ mockSimConfig.loader.loadSymb.mockReset();
});
describe('sim subcommand', () => {
@@ -361,6 +365,10 @@ describe('cli', () => {
it('simulates', () => {
return main(['sim', 'farzam.obj'],
mockStdin, stdout, stderr).then(exitCode => {
+ expect(stdoutActual).toEqual('');
+ expect(stderrActual).toEqual('');
+ expect(exitCode).toEqual(0);
+
// @ts-ignore
expect(getSimulatorConfig.mock.calls).toEqual([['lc3']]);
// @ts-ignore
@@ -371,10 +379,6 @@ describe('cli', () => {
expect(Simulator.mock.calls).toEqual([[mockSimConfig.isa, mockIo]]);
// @ts-ignore
expect(mockSim.run.mock.calls).toEqual([[]]);
-
- expect(exitCode).toEqual(0);
- expect(stdoutActual).toEqual('');
- expect(stderrActual).toEqual('');
});
});
@@ -405,11 +409,14 @@ describe('cli', () => {
describe('dbg subcommand', () => {
let mockDbg: CliDebugger;
+ // @ts-ignore
+ let mockSymbTable: SymbTable = {bob: 'larry'};
beforeAll(() => {
// @ts-ignore
mockDbg = {
run: jest.fn(),
+ getSymbTable: jest.fn(),
close: jest.fn(),
};
});
@@ -417,6 +424,8 @@ describe('cli', () => {
beforeEach(() => {
// @ts-ignore
CliDebugger.mockImplementation(() => mockDbg);
+ // @ts-ignore
+ mockDbg.getSymbTable.mockReturnValue(mockSymbTable);
});
afterEach(() => {
@@ -425,26 +434,72 @@ describe('cli', () => {
// @ts-ignore
mockDbg.run.mockReset();
// @ts-ignore
+ mockDbg.getSymbTable.mockReset();
+ // @ts-ignore
mockDbg.close.mockReset();
});
- it('launches debugger', () => {
+ it('launches debugger with debug symbols', () => {
return main(['dbg', 'brickell.obj'],
stdin, stdout, stderr).then(exitCode => {
+ expect(stdoutActual).toEqual('');
+ expect(stderrActual).toEqual('');
+ expect(exitCode).toEqual(0);
+
// @ts-ignore
expect(getSimulatorConfig.mock.calls).toEqual([['lc3']]);
// @ts-ignore
- expect(fs.createReadStream.mock.calls).toEqual([['brickell.obj']]);
+ expect(fs.createReadStream.mock.calls).toEqual([['brickell.obj'], ['brickell.lemonade']]);
+ // @ts-ignore
+ expect(mockSimConfig.loader.load.mock.calls).toEqual([[mockSimConfig.isa, mockFp, mockDbg]]);
+ // @ts-ignore
+ expect(mockSimConfig.loader.loadSymb.mock.calls).toEqual([[mockFp, mockSymbTable]]);
// @ts-ignore
expect(CliDebugger.mock.calls).toEqual([[mockSimConfig.isa, stdin, stdout]]);
// @ts-ignore
+ expect(mockDbg.getSymbTable.mock.calls).toEqual([[]]);
+ // @ts-ignore
expect(mockDbg.run.mock.calls).toEqual([[]]);
// @ts-ignore
expect(mockDbg.close.mock.calls).toEqual([[]]);
+ });
+ });
- expect(exitCode).toEqual(0);
+ it('launches debugger without debug symbols', () => {
+ // @ts-ignore
+ fs.createReadStream.mockReset();
+ // @ts-ignore
+ fs.createReadStream.mockReturnValueOnce(mockFp);
+ // @ts-ignore
+ fs.createReadStream.mockReturnValueOnce({
+ // @ts-ignore
+ on(ev, cb) {
+ if (ev === 'error') cb(new Error('ENOENT'));
+ }
+ });
+
+ return main(['dbg', 'brickell.obj'],
+ stdin, stdout, stderr).then(exitCode => {
expect(stdoutActual).toEqual('');
- expect(stderrActual).toEqual('');
+ expect(stderrActual).toMatch(/warning:.*ENOENT/);
+ expect(exitCode).toEqual(0);
+
+ // @ts-ignore
+ expect(getSimulatorConfig.mock.calls).toEqual([['lc3']]);
+ // @ts-ignore
+ expect(fs.createReadStream.mock.calls).toEqual([['brickell.obj'], ['brickell.lemonade']]);
+ // @ts-ignore
+ expect(mockSimConfig.loader.load.mock.calls).toEqual([[mockSimConfig.isa, mockFp, mockDbg]]);
+ // @ts-ignore
+ expect(mockSimConfig.loader.loadSymb.mock.calls).toEqual([]);
+ // @ts-ignore
+ expect(CliDebugger.mock.calls).toEqual([[mockSimConfig.isa, stdin, stdout]]);
+ // @ts-ignore
+ expect(mockDbg.getSymbTable.mock.calls).toEqual([]);
+ // @ts-ignore
+ expect(mockDbg.run.mock.calls).toEqual([[]]);
+ // @ts-ignore
+ expect(mockDbg.close.mock.calls).toEqual([[]]);
});
});
@@ -461,14 +516,14 @@ describe('cli', () => {
return main(['dbg', 'brickell.obj'],
stdin, stdout, stderr).then(exitCode => {
+ expect(stdoutActual).toEqual('');
+ expect(stderrActual).toMatch(/setup error.+oops i did it again/);
+ expect(exitCode).toEqual(1);
+
// @ts-ignore
expect(fs.createReadStream.mock.calls).toEqual([['brickell.obj']]);
// @ts-ignore
expect(CliDebugger.mock.calls).toEqual([]);
-
- expect(exitCode).toEqual(1);
- expect(stdoutActual).toEqual('');
- expect(stderrActual).toMatch(/setup error.+oops i did it again/);
});
});
@@ -482,20 +537,26 @@ describe('cli', () => {
return main(['dbg', 'daisy.obj'],
stdin, stdout, stderr).then(exitCode => {
+ expect(stdoutActual).toEqual('');
+ expect(stderrActual).toMatch('error: unexpected end-of-file');
+ expect(exitCode).toEqual(1);
+
// @ts-ignore
expect(getSimulatorConfig.mock.calls).toEqual([['lc3']]);
// @ts-ignore
- expect(fs.createReadStream.mock.calls).toEqual([['daisy.obj']]);
+ expect(fs.createReadStream.mock.calls).toEqual([['daisy.obj'], ['daisy.lemonade']]);
+ // @ts-ignore
+ expect(mockSimConfig.loader.load.mock.calls).toEqual([[mockSimConfig.isa, mockFp, mockDbg]]);
+ // @ts-ignore
+ expect(mockSimConfig.loader.loadSymb.mock.calls).toEqual([[mockFp, mockSymbTable]]);
// @ts-ignore
expect(CliDebugger.mock.calls).toEqual([[mockSimConfig.isa, stdin, stdout]]);
// @ts-ignore
+ expect(mockDbg.getSymbTable.mock.calls).toEqual([[]]);
+ // @ts-ignore
expect(mockDbg.run.mock.calls).toEqual([[]]);
// @ts-ignore
expect(mockDbg.close.mock.calls).toEqual([[]]);
-
- expect(exitCode).toEqual(1);
- expect(stdoutActual).toEqual('');
- expect(stderrActual).toMatch('error: unexpected end-of-file');
});
});
});
diff --git a/novice/cli.ts b/novice/cli.ts
index 2385477..6fe21b0 100644
--- a/novice/cli.ts
+++ b/novice/cli.ts
@@ -130,7 +130,7 @@ async function sim(configName: string, path: string, stdin: Readable,
));
const io = new StreamIO(stdin, stdout);
const simulator = new Simulator(cfg.isa, io);
- cfg.loader.load(cfg.isa, fp, simulator);
+ await cfg.loader.load(cfg.isa, fp, simulator);
await simulator.run();
return 0;
} catch (err) {
@@ -143,13 +143,30 @@ async function dbg(configName: string, path: string, stdin: Readable,
stdout: Writable, stderr: Writable): Promise<number> {
let cfg;
let fp: Readable;
+ let symbFp: Readable;
+ let parseSymbTable = true;
try {
cfg = getSimulatorConfig(configName);
fp = fs.createReadStream(path);
+
await new Promise((resolve, reject) => {
fp.on('readable', resolve);
fp.on('error', reject);
});
+
+ const symbPath = `${removeExt(path)}.${cfg.loader.symbFileExt()}`;
+ symbFp = fs.createReadStream(symbPath);
+
+ await new Promise((resolve, reject) => {
+ symbFp.on('readable', resolve);
+ symbFp.on('error', err => {
+ stderr.write(`warning: could not open symbol file ` +
+ `\`${symbPath}'. reason: \`${err.message}'. ` +
+ `proceeding without debug symbols...\n`);
+ parseSymbTable = false;
+ resolve();
+ });
+ });
} catch (err) {
stderr.write(`dbg: setup error: ${err.message}\n`);
return 1;
@@ -158,7 +175,15 @@ async function dbg(configName: string, path: string, stdin: Readable,
const debug = new CliDebugger(cfg.isa, stdin, stdout);
try {
- cfg.loader.load(cfg.isa, fp, debug);
+ const loadPromise = cfg.loader.load(cfg.isa, fp, debug);
+ if (parseSymbTable) {
+ await Promise.all([
+ loadPromise,
+ cfg.loader.loadSymb(symbFp, debug.getSymbTable()),
+ ]);
+ } else {
+ await loadPromise;
+ }
await debug.run();
debug.close();
return 0;
diff --git a/novice/simulator/debugger.ts b/novice/simulator/debugger.ts
index 0b0ac16..dceb4b6 100644
--- a/novice/simulator/debugger.ts
+++ b/novice/simulator/debugger.ts
@@ -1,4 +1,4 @@
-import { Fields, InstructionSpec, IO, Isa } from '../isa';
+import { Fields, InstructionSpec, IO, Isa, SymbTable } from '../isa';
import { maskTo, maxUnsignedVal, sextTo } from '../util';
import { Simulator } from './simulator';
@@ -7,6 +7,7 @@ class Debugger extends Simulator {
// Map of address -> breakpoint number
protected breakpoints: {[addr: number]: number};
protected interrupt: boolean;
+ protected symbTable: SymbTable;
public constructor(isa: Isa, io: IO) {
super(isa, io);
@@ -14,6 +15,11 @@ class Debugger extends Simulator {
this.nextBreakpoint = 0;
this.breakpoints = {};
this.interrupt = false;
+ this.symbTable = {};
+ }
+
+ public getSymbTable(): SymbTable {
+ return this.symbTable;
}
// continue
diff --git a/novice/simulator/loaders/complx.test.ts b/novice/simulator/loaders/complx.test.ts
index 55556c2..6282d01 100644
--- a/novice/simulator/loaders/complx.test.ts
+++ b/novice/simulator/loaders/complx.test.ts
@@ -1,4 +1,4 @@
-import { isas } from '../../isa';
+import { SymbTable, isas } from '../../isa';
import { Loader } from './loader';
import { ComplxObjectFileLoader } from './complx';
import { Readable } from 'stream';
@@ -9,23 +9,27 @@ interface Mem extends Memory {
}
describe('complx loader', () => {
- let mem: Mem;
let fp: Readable;
let loader: Loader;
beforeEach(() => {
- mem = {
- data: {},
- load: (addr: number) =>
- mem.data.hasOwnProperty(addr)? mem.data[addr] : 0,
- store: (addr: number, val: number) =>
- mem.data[addr] = val,
- };
- fp = new Readable();
loader = new ComplxObjectFileLoader();
});
describe('load()', () => {
+ let mem: Mem;
+
+ beforeEach(() => {
+ fp = new Readable();
+ mem = {
+ data: {},
+ load: (addr: number) =>
+ mem.data.hasOwnProperty(addr)? mem.data[addr] : 0,
+ store: (addr: number, val: number) =>
+ mem.data[addr] = val,
+ };
+ });
+
it('loads trivial object file', () => {
fp.push(new Uint8Array([
0x30,0x00,
@@ -34,10 +38,10 @@ describe('complx loader', () => {
]));
fp.push(null);
- loader.load(isas.lc3, fp, mem);
-
- expect(mem.data).toEqual({
- 0x3000: 0x1337,
+ return loader.load(isas.lc3, fp, mem).then(() => {
+ expect(mem.data).toEqual({
+ 0x3000: 0x1337,
+ });
});
});
@@ -52,13 +56,13 @@ describe('complx loader', () => {
]));
fp.push(null);
- loader.load(isas.lc3, fp, mem);
-
- expect(mem.data).toEqual({
- 0x3000: 0x1337,
- 0x3001: 0x6969,
- 0x3002: 0xdead,
- 0x3003: 0xbeef,
+ return loader.load(isas.lc3, fp, mem).then(() => {
+ expect(mem.data).toEqual({
+ 0x3000: 0x1337,
+ 0x3001: 0x6969,
+ 0x3002: 0xdead,
+ 0x3003: 0xbeef,
+ });
});
});
@@ -77,14 +81,14 @@ describe('complx loader', () => {
]));
fp.push(null);
- loader.load(isas.lc3, fp, mem);
-
- expect(mem.data).toEqual({
- 0x3000: 0x1337,
- 0x3001: 0x6969,
- 0x4000: 0x0420,
- 0x4001: 0xdead,
- 0x4002: 0xbeef,
+ return loader.load(isas.lc3, fp, mem).then(() => {
+ expect(mem.data).toEqual({
+ 0x3000: 0x1337,
+ 0x3001: 0x6969,
+ 0x4000: 0x0420,
+ 0x4001: 0xdead,
+ 0x4002: 0xbeef,
+ });
});
});
@@ -98,10 +102,10 @@ describe('complx loader', () => {
]));
fp.push(null);
- loader.load(isas.lc3, fp, mem);
-
- expect(mem.data).toEqual({
- 0x3000: 0x6969,
+ return loader.load(isas.lc3, fp, mem).then(() => {
+ expect(mem.data).toEqual({
+ 0x3000: 0x6969,
+ });
});
});
@@ -120,14 +124,14 @@ describe('complx loader', () => {
fp.push(Buffer.from([0xef]));
fp.push(null);
- loader.load(isas.lc3, fp, mem);
-
- expect(mem.data).toEqual({
- 0x3000: 0x1337,
- 0x3001: 0x6969,
- 0x3002: 0x0420,
- 0x3003: 0xdead,
- 0x3004: 0xbeef,
+ return loader.load(isas.lc3, fp, mem).then(() => {
+ expect(mem.data).toEqual({
+ 0x3000: 0x1337,
+ 0x3001: 0x6969,
+ 0x3002: 0x0420,
+ 0x3003: 0xdead,
+ 0x3004: 0xbeef,
+ });
});
});
@@ -139,9 +143,7 @@ describe('complx loader', () => {
]));
fp.push(null);
- expect(() => {
- loader.load(isas.lc3, fp, mem);
- }).toThrow('expected 1 more words');
+ return expect(loader.load(isas.lc3, fp, mem)).rejects.toThrow('expected 1 more words');
});
it('errors on odd number of bytes', () => {
@@ -153,9 +155,7 @@ describe('complx loader', () => {
]));
fp.push(null);
- expect(() => {
- loader.load(isas.lc3, fp, mem);
- }).toThrow('not divisible by 2');
+ return expect(loader.load(isas.lc3, fp, mem)).rejects.toThrow('not divisible by 2');
});
it('errors on malformed object file', () => {
@@ -164,9 +164,139 @@ describe('complx loader', () => {
]));
fp.push(null);
- expect(() => {
- loader.load(isas.lc3, fp, mem);
- }).toThrow('unexpected end-of-file');
+ return expect(loader.load(isas.lc3, fp, mem)).rejects.toThrow('unexpected end-of-file');
+ });
+ });
+
+ describe('symbFileExt()', () => {
+ it('returns the right file extension', () => {
+ expect(loader.symbFileExt()).toEqual('sym');
+ });
+ });
+
+ describe('loadSymb()', () => {
+ let symbTable: SymbTable;
+
+ beforeEach(() => {
+ fp = new Readable({
+ objectMode: true,
+ });
+ symbTable = {};
+ });
+
+ it('loads basic table', () => {
+ [
+ '69\tdais',
+ 'yy\n420\tmarley\n',
+ '3000\tstart\n34',
+ '34\tgwen\n4000\tumbrella\n',
+ ].forEach(s => fp.push(Buffer.from(s)));
+ fp.push(null);
+
+ return loader.loadSymb(fp, symbTable).then(() => {
+ expect(symbTable).toEqual({
+ daisyy: 0x69,
+ marley: 0x420,
+ start: 0x3000,
+ gwen: 0x3434,
+ umbrella: 0x4000,
+ });
+ });
+ });
+
+ it('handles two labels with same address', () => {
+ [
+ '69\tbob1\n69\tbob2',
+ ].forEach(s => fp.push(Buffer.from(s)));
+ fp.push(null);
+
+ return loader.loadSymb(fp, symbTable).then(() => {
+ expect(symbTable).toEqual({
+ bob1: 0x69,
+ bob2: 0x69,
+ });
+ });
+ });
+
+ it('handles empty table', () => {
+ fp.push(null);
+
+ return loader.loadSymb(fp, symbTable).then(() => {
+ expect(symbTable).toEqual({});
+ });
+ });
+
+ it('ignores empty lines', () => {
+ [
+ '\n21\tsavage\n\n',
+ '\n\n\n\n420\tbob',
+ ].forEach(s => fp.push(Buffer.from(s)));
+ fp.push(null);
+
+ return loader.loadSymb(fp, symbTable).then(() => {
+ expect(symbTable).toEqual({
+ savage: 0x21,
+ bob: 0x420,
+ });
+ });
+ });
+
+ it('errors on overlong string', () => {
+ [
+ 'scooby'.repeat(1024),
+ ].forEach(s => fp.push(Buffer.from(s)));
+ fp.push(null);
+
+ return expect(loader.loadSymb(fp, symbTable)).rejects.toThrow(/exceeds.*on line 1$/);
+ });
+
+ it('errors on overlong line', () => {
+ [
+ '3434\tasdfasdfasdf\n',
+ 'scooby'.repeat(1024) + '\n',
+ ].forEach(s => fp.push(Buffer.from(s)));
+ fp.push(null);
+
+ return expect(loader.loadSymb(fp, symbTable)).rejects.toThrow(/exceeds.*on line 2$/);
+ });
+
+ it('errors on nonempty line without tab', () => {
+ [
+ '3434\tasdfasdfasdf\n',
+ '3434asdfasdfasdf\n',
+ '34\tadfasdf\n',
+ ].forEach(s => fp.push(Buffer.from(s)));
+ fp.push(null);
+
+ return expect(loader.loadSymb(fp, symbTable)).rejects.toThrow(/tabs.*on line 2$/);
+ });
+
+ it('errors on negative address', () => {
+ [
+ '-4\tasdf\n',
+ ].forEach(s => fp.push(Buffer.from(s)));
+ fp.push(null);
+
+ return expect(loader.loadSymb(fp, symbTable)).rejects.toThrow(/negative.*on line 1$/);
+ });
+
+ it('errors on non-hex address', () => {
+ [
+ 'x4\tasdf\n',
+ ].forEach(s => fp.push(Buffer.from(s)));
+ fp.push(null);
+
+ return expect(loader.loadSymb(fp, symbTable)).rejects.toThrow(/`x4'.*on line 1$/);
+ });
+
+ it('errors duplicate symbol', () => {
+ [
+ '0\tgaming\n',
+ '3000\tgaming\n',
+ ].forEach(s => fp.push(Buffer.from(s)));
+ fp.push(null);
+
+ return expect(loader.loadSymb(fp, symbTable)).rejects.toThrow(/`gaming'.*on line 2$/);
});
});
});
diff --git a/novice/simulator/loaders/complx.ts b/novice/simulator/loaders/complx.ts
index 5656e58..c7110df 100644
--- a/novice/simulator/loaders/complx.ts
+++ b/novice/simulator/loaders/complx.ts
@@ -1,13 +1,13 @@
import { Buffer } from 'buffer';
import { Readable } from 'stream';
-import { Isa } from '../../isa';
+import { Isa, SymbTable } from '../../isa';
import { Memory } from '../mem';
import { Loader } from './loader';
type State = 'addr'|'len'|'words';
class ComplxObjectFileLoader implements Loader {
- public load(isa: Isa, fp: Readable, mem: Memory): void {
+ public async load(isa: Isa, fp: Readable, mem: Memory): Promise<void> {
const wordBytes = Math.ceil(isa.mem.word / 8);
const excessBuf = Buffer.alloc(wordBytes);
let excessLen = 0;
@@ -88,6 +88,116 @@ class ComplxObjectFileLoader implements Loader {
throw new Error(`expected ${wordsLeft} more words`);
}
}
+
+ public symbFileExt(): string {
+ return 'sym';
+ }
+
+ public async loadSymb(fp: Readable, symbtable: SymbTable): Promise<void> {
+ // If you have any lines this long, I'm praying for your entire
+ // family
+ const buf = Buffer.alloc(1024);
+ let size = 0;
+ let lines = 1;
+ let err: Error|null = null;
+
+ const endPromise = new Promise<void>(resolve => {
+ fp.on('end', resolve);
+ });
+
+ fp.on('data', recvBuf => {
+ if (err) {
+ return;
+ }
+
+ let start = 0;
+ for (let i = 0; i < recvBuf.length; i++) {
+ // hooray! we have a newline so this is a whole line.
+ // let's copy it to the buffer so we can decode it
+ if (recvBuf[i] === '\n'.charCodeAt(0)) {
+ const totalLen = size + i - start + 1;
+ if (totalLen > buf.length) {
+ err = new Error(`line of ${totalLen} bytes exceeds ` +
+ `maximum length of ${buf.length} on ` +
+ `line ${lines}`);
+ return;
+ }
+
+ recvBuf.copy(buf, size, start, i + 1);
+
+ try {
+ this.parseLine(buf.slice(0, totalLen).toString(),
+ lines, symbtable);
+ } catch (e) {
+ err = e;
+ return;
+ }
+
+ // Empty buffer
+ size = 0;
+ start = i + 1;
+ lines++;
+ }
+ }
+
+ // Copy excess
+ if (start < recvBuf.length) {
+ const totalLen = size + recvBuf.length - start;
+
+ if (totalLen > buf.length) {
+ err = new Error(`line of >=${totalLen} bytes exceeds ` +
+ `maximum length of ${buf.length} on ` +
+ `line ${lines}`);
+ }
+ recvBuf.copy(buf, 0, start, recvBuf.length);
+ size = totalLen;
+ }
+ });
+
+ await endPromise;
+
+ if (err) {
+ throw err;
+ }
+
+ if (size > 0) {
+ this.parseLine(buf.slice(0, size).toString(), lines, symbtable);
+ size = 0;
+ }
+ }
+
+ private parseLine(line: string, lines: number, symbtable: SymbTable) {
+ line = line.trim();
+
+ // Ignore empty lines
+ if (!line) {
+ return;
+ }
+
+ const splat = line.split('\t');
+
+ if (splat.length !== 2) {
+ throw new Error(`found ${splat.length - 1} tabs instead of 1 ` +
+ `on line ${lines}`);
+ }
+
+ const [hexAddr, sym] = splat;
+ const addr = parseInt(hexAddr, 16);
+
+ if (addr < 0) {
+ throw new Error(`negative address \`${hexAddr}' on line ${lines}`);
+ }
+
+ if (isNaN(addr)) {
+ throw new Error(`invalid address \`${hexAddr}' on line ${lines}`);
+ }
+
+ if (sym in symbtable) {
+ throw new Error(`duplicate symbol \`${sym}' on line ${lines}`);
+ }
+
+ symbtable[sym] = addr;
+ }
}
export { ComplxObjectFileLoader };
diff --git a/novice/simulator/loaders/loader.ts b/novice/simulator/loaders/loader.ts
index 208720b..99aeae5 100644
--- a/novice/simulator/loaders/loader.ts
+++ b/novice/simulator/loaders/loader.ts
@@ -1,9 +1,11 @@
import { Readable } from 'stream';
-import { Isa } from '../../isa';
+import { Isa, SymbTable } from '../../isa';
import { Memory } from '../mem';
interface Loader {
- load(isa: Isa, fp: Readable, mem: Memory): void;
+ load(isa: Isa, fp: Readable, mem: Memory): Promise<void>;
+ symbFileExt(): string;
+ loadSymb(fp: Readable, symbtable: SymbTable): Promise<void>;
}
export { Loader };