aboutsummaryrefslogtreecommitdiffgithub
diff options
context:
space:
mode:
authorAustin Adams <git@austinjadams.com>2019-02-02 00:34:15 -0500
committerAustin Adams <git@austinjadams.com>2019-02-02 00:34:15 -0500
commit06cdd91c7d2b570c737907c9173298050ad34448 (patch)
tree0099e22dd7bd5966688223e836528d1973031ab0
parentaf65fd071280db18c828fc498227b647adb42a8c (diff)
downloadnovice-06cdd91c7d2b570c737907c9173298050ad34448.tar.gz
novice-06cdd91c7d2b570c737907c9173298050ad34448.tar.xz
sim command: auto-assemble code
-rw-r--r--novice/assembler/assembler.ts4
-rw-r--r--novice/assembler/codegen/base.ts5
-rw-r--r--novice/assembler/codegen/codegen.ts9
-rw-r--r--novice/assembler/codegen/index.ts4
-rw-r--r--novice/assembler/serializers/complx.ts3
-rw-r--r--novice/assembler/serializers/serializer.ts3
-rw-r--r--novice/cli.test.ts66
-rw-r--r--novice/cli.ts24
-rw-r--r--novice/isa/codegen.ts6
-rw-r--r--novice/isa/index.ts3
-rw-r--r--novice/simulator/simulator.ts12
11 files changed, 113 insertions, 26 deletions
diff --git a/novice/assembler/assembler.ts b/novice/assembler/assembler.ts
index 1f4c0c4..ed1803e 100644
--- a/novice/assembler/assembler.ts
+++ b/novice/assembler/assembler.ts
@@ -1,6 +1,6 @@
import { Readable, Writable } from 'stream';
-import { Assembly, Isa, SymbTable } from '../isa';
-import { MachineCodeGenerator, MachineCodeSection } from './codegen';
+import { Assembly, Isa, MachineCodeSection, SymbTable } from '../isa';
+import { MachineCodeGenerator } from './codegen';
import { PseudoOpSpec } from './opspec';
import { Parser } from './parsers';
import { Serializer } from './serializers';
diff --git a/novice/assembler/codegen/base.ts b/novice/assembler/codegen/base.ts
index 6d661f1..76b1080 100644
--- a/novice/assembler/codegen/base.ts
+++ b/novice/assembler/codegen/base.ts
@@ -1,7 +1,8 @@
import { AliasFields, AliasSpec, Assembly, Instruction, InstructionSpec,
- Isa, PseudoOp, Section, SymbTable } from '../../isa';
+ Isa, MachineCodeSection, PseudoOp, Section,
+ SymbTable } from '../../isa';
import { AsmContext, OpOperands, OpSpec, PseudoOpSpec } from '../opspec';
-import { MachineCodeGenerator, MachineCodeSection } from './codegen';
+import { MachineCodeGenerator } from './codegen';
interface ReassembleVictim {
// Index in words array in MachineCodeSection
diff --git a/novice/assembler/codegen/codegen.ts b/novice/assembler/codegen/codegen.ts
index 4723664..290a11f 100644
--- a/novice/assembler/codegen/codegen.ts
+++ b/novice/assembler/codegen/codegen.ts
@@ -1,14 +1,9 @@
-import { Assembly, Isa, SymbTable } from '../../isa';
+import { Assembly, Isa, MachineCodeSection, SymbTable } from '../../isa';
import { PseudoOpSpec } from '../opspec';
-interface MachineCodeSection {
- startAddr: number;
- words: number[];
-}
-
interface MachineCodeGenerator {
gen(isa: Isa, opSpec: PseudoOpSpec, asm: Assembly):
[SymbTable, MachineCodeSection[]];
}
-export { MachineCodeSection, MachineCodeGenerator };
+export { MachineCodeGenerator };
diff --git a/novice/assembler/codegen/index.ts b/novice/assembler/codegen/index.ts
index 991066f..01e69d8 100644
--- a/novice/assembler/codegen/index.ts
+++ b/novice/assembler/codegen/index.ts
@@ -1,4 +1,4 @@
import { BaseMachineCodeGenerator } from './base';
-import { MachineCodeGenerator, MachineCodeSection } from './codegen';
+import { MachineCodeGenerator } from './codegen';
-export { MachineCodeSection, MachineCodeGenerator, BaseMachineCodeGenerator };
+export { MachineCodeGenerator, BaseMachineCodeGenerator };
diff --git a/novice/assembler/serializers/complx.ts b/novice/assembler/serializers/complx.ts
index f22512a..d737a64 100644
--- a/novice/assembler/serializers/complx.ts
+++ b/novice/assembler/serializers/complx.ts
@@ -1,6 +1,5 @@
import { Writable } from 'stream';
-import { Isa, SymbTable } from '../../isa';
-import { MachineCodeSection } from '../codegen';
+import { Isa, MachineCodeSection, SymbTable } from '../../isa';
import { Serializer } from './serializer';
class ComplxObjectFileSerializer implements Serializer {
diff --git a/novice/assembler/serializers/serializer.ts b/novice/assembler/serializers/serializer.ts
index 4b6d715..e9bb627 100644
--- a/novice/assembler/serializers/serializer.ts
+++ b/novice/assembler/serializers/serializer.ts
@@ -1,6 +1,5 @@
import { Writable } from 'stream';
-import { Isa, SymbTable } from '../../isa';
-import { MachineCodeSection } from '../codegen';
+import { Isa, MachineCodeSection, SymbTable } from '../../isa';
interface Serializer {
serialize(isa: Isa, code: MachineCodeSection[], fp: Writable): void;
diff --git a/novice/cli.test.ts b/novice/cli.test.ts
index b7c9ec1..8415c58 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, SymbTable } from './isa';
+import { getIsa, StreamIO, SymbTable, MachineCodeSection } from './isa';
describe('cli', () => {
let stdin: Readable, stdout: Writable, stderr: Writable;
@@ -292,6 +292,7 @@ describe('cli', () => {
isa: {donkey: 'horse apple'},
loader: {
load: jest.fn(),
+ fileExt: () => 'obj',
symbFileExt: () => 'lemonade',
loadSymb: jest.fn(),
},
@@ -325,12 +326,23 @@ describe('cli', () => {
});
describe('sim subcommand', () => {
+ let mockAsm: Assembler;
let mockStdin: Readable;
let mockIo: StreamIO;
let mockSim: Simulator;
+ // @ts-ignore
+ let mockCfg: AssemblerConfig = {joe: 'biden'};
+ let mockSymbTable: SymbTable = {nice: 0x69};
+ let mockSections: MachineCodeSection[] = [
+ {startAddr: 0x420, words: [0xdead, 0xbeef]},
+ ];
beforeAll(() => {
// @ts-ignore
+ mockAsm = {
+ assemble: jest.fn(),
+ };
+ // @ts-ignore
mockStdin = {
iam: 'stdin',
// @ts-ignore
@@ -342,12 +354,20 @@ describe('cli', () => {
mockIo = {timothy: 'aveni'};
// @ts-ignore
mockSim = {
+ loadSections: jest.fn(),
run: jest.fn(),
};
});
beforeEach(() => {
// @ts-ignore
+ getConfig.mockReturnValue(mockCfg);
+ // @ts-ignore
+ Assembler.mockImplementation(() => mockAsm);
+ // @ts-ignore
+ mockAsm.assemble.mockReturnValue(
+ Promise.resolve([mockSymbTable, mockSections]));
+ // @ts-ignore
StreamIO.mockImplementation(() => mockIo);
// @ts-ignore
Simulator.mockImplementation(() => mockSim);
@@ -355,14 +375,22 @@ describe('cli', () => {
afterEach(() => {
// @ts-ignore
+ getConfig.mockReset();
+ // @ts-ignore
+ Assembler.mockReset();
+ // @ts-ignore
+ mockAsm.assemble.mockReset();
+ // @ts-ignore
StreamIO.mockReset();
// @ts-ignore
Simulator.mockReset();
// @ts-ignore
+ mockSim.loadSections.mockReset();
+ // @ts-ignore
mockSim.run.mockReset();
});
- it('simulates', () => {
+ it('simulates object file', () => {
return main(['sim', 'farzam.obj'],
mockStdin, stdout, stderr).then(exitCode => {
expect(stdoutActual).toEqual('');
@@ -374,6 +402,38 @@ describe('cli', () => {
// @ts-ignore
expect(fs.createReadStream.mock.calls).toEqual([['farzam.obj']]);
// @ts-ignore
+ expect(getConfig.mock.calls).toEqual([]);
+ // @ts-ignore
+ expect(Assembler.mock.calls).toEqual([]);
+ // @ts-ignore
+ expect(mockSim.loadSections.mock.calls).toEqual([]);
+ // @ts-ignore
+ expect(StreamIO.mock.calls).toEqual([[mockStdin, stdout]]);
+ // @ts-ignore
+ expect(Simulator.mock.calls).toEqual([[mockSimConfig.isa, mockIo]]);
+ // @ts-ignore
+ expect(mockSim.run.mock.calls).toEqual([[]]);
+ });
+ });
+
+ it('assembles and simulates assembly file', () => {
+ return main(['sim', 'marley.s'],
+ 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
+ expect(fs.createReadStream.mock.calls).toEqual([['marley.s']]);
+ // @ts-ignore
+ expect(getConfig.mock.calls).toEqual([['lc3']]);
+ // @ts-ignore
+ expect(Assembler.mock.calls).toEqual([[mockCfg]]);
+ // @ts-ignore
+ expect(mockSim.loadSections.mock.calls).toEqual([[mockSections]]);
+ // @ts-ignore
expect(StreamIO.mock.calls).toEqual([[mockStdin, stdout]]);
// @ts-ignore
expect(Simulator.mock.calls).toEqual([[mockSimConfig.isa, mockIo]]);
@@ -382,7 +442,7 @@ describe('cli', () => {
});
});
- it('handles nonexistent file', () => {
+ it('handles nonexistent object file', () => {
// @ts-ignore
fs.createReadStream.mockReset();
// @ts-ignore
diff --git a/novice/cli.ts b/novice/cli.ts
index 6fe21b0..8b85def 100644
--- a/novice/cli.ts
+++ b/novice/cli.ts
@@ -3,7 +3,7 @@ import * as fs from 'fs';
import { Readable, Writable } from 'stream';
import { Assembler, getConfig, getParser, getSerializer } from './assembler';
import { getIsa, StreamIO } from './isa';
-import { CliDebugger, getSimulatorConfig, Simulator } from './simulator';
+import { CliDebugger, getSimulatorConfig, Simulator, SimulatorConfig } from './simulator';
async function main(argv: string[], stdin: Readable, stdout: Writable,
stderr: Writable): Promise<number> {
@@ -36,8 +36,8 @@ async function main(argv: string[], stdin: Readable, stdout: Writable,
'the selected assembler configuration' });
const simParser = sub.addParser('sim', { description:
- 'simulate an object file' });
- simParser.addArgument(['file'], { help: 'object file to simulate' });
+ 'simulate an assembly/object file' });
+ simParser.addArgument(['file'], { help: 'assembly/object file to simulate' });
simParser.addArgument(['-c', '--config'],
{ defaultValue: 'lc3',
help: 'simulator configuration to use. ' +
@@ -117,8 +117,13 @@ function removeExt(path: string): string {
return hasExt ? path.substr(0, dotIdx) : path;
}
+function hasObjectFileExt(path: string, cfg: SimulatorConfig) {
+ return path.endsWith('.' + cfg.loader.fileExt());
+}
+
async function sim(configName: string, path: string, stdin: Readable,
stdout: Writable, stderr: Writable): Promise<number> {
+
try {
const cfg = getSimulatorConfig(configName);
const fp = fs.createReadStream(path);
@@ -128,9 +133,20 @@ async function sim(configName: string, path: string, stdin: Readable,
f.on('error', reject);
}),
));
+
const io = new StreamIO(stdin, stdout);
const simulator = new Simulator(cfg.isa, io);
- await cfg.loader.load(cfg.isa, fp, simulator);
+
+ const isObjectFile = hasObjectFileExt(path, cfg);
+ if (isObjectFile) {
+ await cfg.loader.load(cfg.isa, fp, simulator);
+ } else {
+ const asmCfg = getConfig(configName);
+ const assembler = new Assembler(asmCfg);
+ const [symbtable, sections] = await assembler.assemble(fp);
+ simulator.loadSections(sections);
+ }
+
await simulator.run();
return 0;
} catch (err) {
diff --git a/novice/isa/codegen.ts b/novice/isa/codegen.ts
new file mode 100644
index 0000000..4760f16
--- /dev/null
+++ b/novice/isa/codegen.ts
@@ -0,0 +1,6 @@
+interface MachineCodeSection {
+ startAddr: number;
+ words: number[];
+}
+
+export { MachineCodeSection };
diff --git a/novice/isa/index.ts b/novice/isa/index.ts
index f1c2912..e10ae2a 100644
--- a/novice/isa/index.ts
+++ b/novice/isa/index.ts
@@ -1,5 +1,6 @@
import { Assembly, Instruction, IntegerOperand, LabelOperand, PseudoOp,
RegisterOperand, Section, StringOperand } from './assembly';
+import { MachineCodeSection } from './codegen';
import { DummyIsa } from './dummy';
import { IO, StreamIO } from './io';
import { AliasContext, AliasFields, AliasSpec, Fields, getRegAliases,
@@ -30,6 +31,8 @@ export { getIsa, isas,
// assembly
Assembly, Section, Instruction, RegisterOperand, IntegerOperand,
LabelOperand, PseudoOp, StringOperand,
+ // codegen
+ MachineCodeSection,
// io
IO, StreamIO,
// isa
diff --git a/novice/simulator/simulator.ts b/novice/simulator/simulator.ts
index 9c85590..6b85499 100644
--- a/novice/simulator/simulator.ts
+++ b/novice/simulator/simulator.ts
@@ -1,5 +1,5 @@
-import { Fields, InstructionSpec, IO, Isa, MachineStateLogEntry, MachineStateUpdate,
- Reg, RegIdentifier } from '../isa';
+import { Fields, InstructionSpec, IO, Isa, MachineCodeSection, MachineStateLogEntry,
+ MachineStateUpdate, Reg, RegIdentifier } from '../isa';
import { forceUnsigned, maskTo, sextTo } from '../util';
class Simulator {
@@ -36,6 +36,14 @@ class Simulator {
// TODO: make this immutable somehow
public getRegs() { return this.regs; }
+ public loadSections(sections: MachineCodeSection[]): void {
+ for (const section of sections) {
+ for (let i = 0; i < section.words.length; i++) {
+ this.store(section.startAddr + i, section.words[i]);
+ }
+ }
+ }
+
public async step(): Promise<void> {
// If already halted, do nothing
if (this.halted) {