aboutsummaryrefslogtreecommitdiffgithub
diff options
context:
space:
mode:
authorAustin Adams <git@austinjadams.com>2019-01-11 23:33:22 -0500
committerAustin Adams <git@austinjadams.com>2019-01-11 23:33:22 -0500
commit51bc9a716531e8aaf62e86689fed182049c28cd3 (patch)
tree178f86ffde9268f3840f28ccbf33acc1cae46d3c
parentc1e5dbdc1753c4784fc30daedd6442312396173a (diff)
downloadnovice-51bc9a716531e8aaf62e86689fed182049c28cd3.tar.gz
novice-51bc9a716531e8aaf62e86689fed182049c28cd3.tar.xz
Add argparse, output to object files
-rw-r--r--novice/assembler/assembler.ts9
-rw-r--r--novice/assembler/configs.ts2
-rw-r--r--novice/assembler/index.ts12
-rw-r--r--novice/assembler/serializers/complx.ts33
-rw-r--r--novice/assembler/serializers/index.ts8
-rw-r--r--novice/assembler/serializers/serializer.ts11
-rw-r--r--novice/cli.ts93
7 files changed, 135 insertions, 33 deletions
diff --git a/novice/assembler/assembler.ts b/novice/assembler/assembler.ts
index 221416d..67b8379 100644
--- a/novice/assembler/assembler.ts
+++ b/novice/assembler/assembler.ts
@@ -1,15 +1,17 @@
-import { Readable } from 'stream';
+import { Readable, Writable } from 'stream';
import { MachineCodeGenerator, MachineCodeSection } from './codegen';
import { Isa } from './isa';
import { PseudoOpSpec } from './opspec';
import { ParsedAssembly, Parser } from './parsers';
import { Scanner } from './scanner';
+import { Serializer } from './serializers';
interface AssemblerConfig {
parser: Parser;
generator: MachineCodeGenerator;
isa: Isa;
opSpec: PseudoOpSpec;
+ serializer: Serializer;
}
class Assembler {
@@ -32,6 +34,11 @@ class Assembler {
public async assemble(fp: Readable): Promise<MachineCodeSection[]> {
return this.codegen(await this.parse(fp));
}
+
+ public async assembleTo(inFp: Readable, outFp: Writable): Promise<void> {
+ this.cfg.serializer.serialize(
+ this.cfg.isa, await this.assemble(inFp), outFp);
+ }
}
export { Assembler, AssemblerConfig };
diff --git a/novice/assembler/configs.ts b/novice/assembler/configs.ts
index da49375..8843ae2 100644
--- a/novice/assembler/configs.ts
+++ b/novice/assembler/configs.ts
@@ -2,6 +2,7 @@ interface Config {
parser: string;
isa: string;
opSpec: string;
+ serializer: string;
}
const configs: {[s: string]: Config} = {
@@ -9,6 +10,7 @@ const configs: {[s: string]: Config} = {
parser: 'complx',
isa: 'lc3',
opSpec: 'complx',
+ serializer: 'complx',
},
};
diff --git a/novice/assembler/index.ts b/novice/assembler/index.ts
index 4f9f54f..3555dc2 100644
--- a/novice/assembler/index.ts
+++ b/novice/assembler/index.ts
@@ -4,6 +4,7 @@ import { configs } from './configs';
import { Isa, isas } from './isa';
import { opSpecs, PseudoOpSpec } from './opspec';
import { Parser, parsers } from './parsers';
+import { Serializer, serializers } from './serializers';
function getParser(parserName: string): Parser {
if (!parsers.hasOwnProperty(parserName)) {
@@ -34,6 +35,14 @@ function getOpSpec(opSpecName: string): PseudoOpSpec {
return opSpecs[opSpecName];
}
+function getSerializer(serializerName: string): Serializer {
+ if (!serializers.hasOwnProperty(serializerName)) {
+ throw new Error(`no such serializer \`${serializerName}'\n`);
+ }
+
+ return new serializers[serializerName]();
+}
+
function getConfig(configName: string): AssemblerConfig {
if (!configs.hasOwnProperty(configName)) {
throw new Error(`no such assembler config \`${configName}'\n`);
@@ -46,7 +55,8 @@ function getConfig(configName: string): AssemblerConfig {
generator: getGenerator(),
isa: getIsa(configNames.isa),
opSpec: getOpSpec(configNames.opSpec),
+ serializer: getSerializer(configNames.serializer),
};
}
-export { Assembler, getParser, getGenerator, getIsa, getOpSpec, getConfig };
+export { Assembler, getParser, getGenerator, getIsa, getOpSpec, getSerializer, getConfig };
diff --git a/novice/assembler/serializers/complx.ts b/novice/assembler/serializers/complx.ts
new file mode 100644
index 0000000..a467ccf
--- /dev/null
+++ b/novice/assembler/serializers/complx.ts
@@ -0,0 +1,33 @@
+import { Writable } from 'stream';
+import { MachineCodeSection } from '../codegen';
+import { Isa } from '../isa';
+import { Serializer } from './serializer';
+
+class ComplxObjectFileSerializer implements Serializer {
+ public fileExt(): string { return 'obj'; }
+
+ public serialize(isa: Isa, code: MachineCodeSection[], fp: Writable): void {
+ for (const section of code) {
+ this.writeWord(isa, section.startAddr, fp);
+ this.writeWord(isa, section.words.length, fp);
+
+ for (const word of section.words) {
+ this.writeWord(isa, word, fp);
+ }
+ }
+ }
+
+ // Big endian
+ private writeWord(isa: Isa, word: number, fp: Writable): void {
+ const numBytes = Math.ceil(isa.mem.word / 8);
+ const chunk = new Uint8Array(numBytes);
+
+ for (let i = 0; i < numBytes; i++) {
+ chunk[i] = word >> ((numBytes - i - 1) << 3);
+ }
+
+ fp.write(chunk);
+ }
+}
+
+export { ComplxObjectFileSerializer };
diff --git a/novice/assembler/serializers/index.ts b/novice/assembler/serializers/index.ts
new file mode 100644
index 0000000..5406ce9
--- /dev/null
+++ b/novice/assembler/serializers/index.ts
@@ -0,0 +1,8 @@
+import { ComplxObjectFileSerializer } from './complx';
+import { Serializer } from './serializer';
+
+const serializers: {[s: string]: new() => Serializer} = {
+ complx: ComplxObjectFileSerializer,
+};
+
+export { serializers, Serializer };
diff --git a/novice/assembler/serializers/serializer.ts b/novice/assembler/serializers/serializer.ts
new file mode 100644
index 0000000..799cd94
--- /dev/null
+++ b/novice/assembler/serializers/serializer.ts
@@ -0,0 +1,11 @@
+import { Writable } from 'stream';
+import { MachineCodeSection } from '../codegen';
+import { Isa } from '../isa';
+
+interface Serializer {
+ serialize(isa: Isa, code: MachineCodeSection[], fp: Writable): void;
+ // default file extension for this format. No leading dot please
+ fileExt(): string;
+}
+
+export { Serializer };
diff --git a/novice/cli.ts b/novice/cli.ts
index ccf9e6d..343695d 100644
--- a/novice/cli.ts
+++ b/novice/cli.ts
@@ -1,51 +1,75 @@
+import { ArgumentParser } from 'argparse';
import * as fs from 'fs';
import { Writable } from 'stream';
-import { Assembler, getConfig, getParser } from './assembler';
+import { Assembler, getConfig, getParser, getSerializer } from './assembler';
-async function main(args: string[], stdout: Writable,
+async function main(argv: string[], stdout: Writable,
stderr: Writable): Promise<number> {
- const subcommand = args[0];
+ const parser = new ArgumentParser({ prog: 'novice', description: 'toy assembler' });
+ const sub = parser.addSubparsers({ dest: 'subcmd', help: 'subcommand to run' });
- switch (subcommand) {
+ const asmParser = sub.addParser('asm');
+ asmParser.addArgument(['file'], { help: 'file to assemble' });
+ asmParser.addArgument(['-c', '--config'],
+ { defaultValue: 'lc3',
+ help: 'assembler configuration to use. ' +
+ 'default: %(defaultValue)s' });
+ asmParser.addArgument(['-o', '--output-file'],
+ { metavar: 'PATH',
+ defaultValue: null,
+ help: 'path to output file. default: ' +
+ 'for input file X.asm, output to X.EXT, ' +
+ 'where EXT is the default extension for ' +
+ 'the selected output format' });
+ asmParser.addArgument(['-f', '--output-format'],
+ { metavar: 'FMT',
+ defaultValue: null,
+ help: 'desired output format. default: ' +
+ 'the default output format for the ' +
+ 'the selected assembler configuration' });
+
+ const tablegenParser = sub.addParser('tablegen');
+
+ const args = parser.parseArgs(argv);
+ switch (args.subcmd) {
case 'asm':
- if (args.length >= 2 && args.length <= 3) {
- const config = (args.length < 3) ? 'lc3' : args[1];
- const path = args[args.length - 1];
- return await asm(config, path, stdout, stderr);
- } else {
- return usage(stderr);
- }
+ return await asm(args.config, args.file, args.outputFile, args.outputFormat, stdout, stderr);
case 'tablegen':
- if (args.length === 2) {
- const parser = args[1];
- return tablegen(parser, stdout, stderr);
- } else {
- return usage(stderr);
- }
+ return tablegen(args.parser, stdout, stderr);
default:
- return usage(stderr);
+ stderr.write(`novice: unsupported subcommand ${args.subcmd}\n`);
+ return 1;
}
}
-function usage(stderr: Writable): number {
- stderr.write('usage: novice asm [config] <file>\n' +
- ' novice tablegen <parser>\n');
- return 1;
-}
-
-async function asm(configName: string, path: string, stdout: Writable,
- stderr: Writable):
+async function asm(configName: string, inPath: string,
+ outPath: string|null, outFmt: string|null,
+ stdout: Writable, stderr: Writable):
Promise<number> {
try {
const cfg = getConfig(configName);
- const fp = fs.createReadStream(path);
+
+ if (outFmt) {
+ cfg.serializer = getSerializer(outFmt);
+ }
+ if (!outPath) {
+ outPath = removeExt(inPath) + '.' + cfg.serializer.fileExt();
+ }
+
+ const inFp = fs.createReadStream(inPath);
await new Promise((resolve, reject) => {
- fp.on('readable', resolve);
- fp.on('error', reject);
+ inFp.on('readable', resolve);
+ inFp.on('error', reject);
});
+ const outFp = fs.createWriteStream(outPath);
const assembler = new Assembler(cfg);
- const machineCode = await assembler.assemble(fp);
- stdout.write(JSON.stringify(machineCode));
+
+ // Buffer so we don't make 1,000,000 syscalls
+ outFp.cork();
+ await assembler.assembleTo(inFp, outFp);
+ outFp.uncork();
+ outFp.end();
+
return 0;
} catch (err) {
stderr.write(`asm error: ${err.message}\n`);
@@ -53,6 +77,13 @@ async function asm(configName: string, path: string, stdout: Writable,
}
}
+function removeExt(path: string): string {
+ const slashIdx = path.lastIndexOf('/');
+ const dotIdx = path.lastIndexOf('.');
+ const hasExt = dotIdx !== -1 && (slashIdx === -1 || slashIdx < dotIdx);
+ return hasExt ? path.substr(0, dotIdx) : path;
+}
+
function tablegen(parserName: string, stdout: Writable,
stderr: Writable): number {
let table: object;