diff options
author | Austin Adams <git@austinjadams.com> | 2019-01-11 23:33:22 -0500 |
---|---|---|
committer | Austin Adams <git@austinjadams.com> | 2019-01-11 23:33:22 -0500 |
commit | 51bc9a716531e8aaf62e86689fed182049c28cd3 (patch) | |
tree | 178f86ffde9268f3840f28ccbf33acc1cae46d3c | |
parent | c1e5dbdc1753c4784fc30daedd6442312396173a (diff) | |
download | novice-51bc9a716531e8aaf62e86689fed182049c28cd3.tar.gz novice-51bc9a716531e8aaf62e86689fed182049c28cd3.tar.xz |
Add argparse, output to object files
-rw-r--r-- | novice/assembler/assembler.ts | 9 | ||||
-rw-r--r-- | novice/assembler/configs.ts | 2 | ||||
-rw-r--r-- | novice/assembler/index.ts | 12 | ||||
-rw-r--r-- | novice/assembler/serializers/complx.ts | 33 | ||||
-rw-r--r-- | novice/assembler/serializers/index.ts | 8 | ||||
-rw-r--r-- | novice/assembler/serializers/serializer.ts | 11 | ||||
-rw-r--r-- | novice/cli.ts | 93 |
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; |