aboutsummaryrefslogtreecommitdiffgithub
diff options
context:
space:
mode:
authorAustin Adams <git@austinjadams.com>2019-01-09 20:07:36 -0700
committerAustin Adams <git@austinjadams.com>2019-01-09 20:07:36 -0700
commitc58f40467138088f033cc2ffed397178139aea48 (patch)
tree1dd7e633f96fd21b1fad2e0668a8535d07706853
parentbe9d89521567d72a1fed3866540c16d4a30d69ed (diff)
downloadnovice-c58f40467138088f033cc2ffed397178139aea48.tar.gz
novice-c58f40467138088f033cc2ffed397178139aea48.tar.xz
Support pseudo-ops
-rw-r--r--novice/assembler/assembler.ts26
-rw-r--r--novice/assembler/codegen/base.ts76
-rw-r--r--novice/assembler/codegen/codegen.ts3
-rw-r--r--novice/assembler/configs.ts15
-rw-r--r--novice/assembler/index.ts29
-rw-r--r--novice/assembler/isa/isa.ts1
-rw-r--r--novice/assembler/isa/lc3.ts1
-rw-r--r--novice/assembler/opspec/complx.ts3
-rw-r--r--novice/assembler/opspec/index.ts8
-rw-r--r--novice/assembler/opspec/opspec.ts8
-rw-r--r--novice/cli.ts24
11 files changed, 156 insertions, 38 deletions
diff --git a/novice/assembler/assembler.ts b/novice/assembler/assembler.ts
index 76b226d..221416d 100644
--- a/novice/assembler/assembler.ts
+++ b/novice/assembler/assembler.ts
@@ -1,30 +1,32 @@
import { Readable } from 'stream';
import { MachineCodeGenerator, MachineCodeSection } from './codegen';
import { Isa } from './isa';
+import { PseudoOpSpec } from './opspec';
import { ParsedAssembly, Parser } from './parsers';
import { Scanner } from './scanner';
+interface AssemblerConfig {
+ parser: Parser;
+ generator: MachineCodeGenerator;
+ isa: Isa;
+ opSpec: PseudoOpSpec;
+}
+
class Assembler {
private scanner: Scanner;
- private parser: Parser;
- private generator: MachineCodeGenerator;
- private isa: Isa;
+ private cfg: AssemblerConfig;
- public constructor(parser: Parser,
- generator: MachineCodeGenerator,
- isa: Isa) {
+ public constructor(cfg: AssemblerConfig) {
this.scanner = new Scanner();
- this.parser = parser;
- this.generator = generator;
- this.isa = isa;
+ this.cfg = cfg;
}
public async parse(fp: Readable): Promise<ParsedAssembly> {
- return this.parser.parse(this.isa, await this.scanner.scan(fp));
+ return this.cfg.parser.parse(this.cfg.isa, await this.scanner.scan(fp));
}
public codegen(asm: ParsedAssembly): MachineCodeSection[] {
- return this.generator.gen(this.isa, asm);
+ return this.cfg.generator.gen(this.cfg.isa, this.cfg.opSpec, asm);
}
public async assemble(fp: Readable): Promise<MachineCodeSection[]> {
@@ -32,4 +34,4 @@ class Assembler {
}
}
-export { Assembler };
+export { Assembler, AssemblerConfig };
diff --git a/novice/assembler/codegen/base.ts b/novice/assembler/codegen/base.ts
index 39a8146..db7af67 100644
--- a/novice/assembler/codegen/base.ts
+++ b/novice/assembler/codegen/base.ts
@@ -1,4 +1,5 @@
import { Instruction as IsaInstruction, Isa } from '../isa';
+import { AsmContext, OpOperands, OpSpec, PseudoOpSpec } from '../opspec';
import { Instruction, ParsedAssembly, PseudoOp, Section } from '../parsers';
import { MachineCodeGenerator, MachineCodeSection } from './codegen';
@@ -12,7 +13,8 @@ interface ReassembleVictim {
type SymbTable = {[s: string]: number};
class BaseMachineCodeGenerator implements MachineCodeGenerator {
- public gen(isa: Isa, asm: ParsedAssembly): MachineCodeSection[] {
+ public gen(isa: Isa, opSpec: PseudoOpSpec, asm: ParsedAssembly):
+ MachineCodeSection[] {
const sections: MachineCodeSection[] = [];
const symbtable: SymbTable = {};
const reassemble: ReassembleVictim[] = [];
@@ -34,7 +36,8 @@ class BaseMachineCodeGenerator implements MachineCodeGenerator {
}
const instr = asmSection.instructions[j];
- const [words, hasLabel] = this.inflateInstr(isa, instr, pc, null);
+ const [words, hasLabel] = this.inflateInstr(
+ isa, opSpec, instr, pc, null);
// If this instruction uses a label, we'll need to
// reassmble it
@@ -52,7 +55,8 @@ class BaseMachineCodeGenerator implements MachineCodeGenerator {
const section = asm.sections[victim.sectionIdx];
const instr = section.instructions[victim.instrIdx];
const pc = sections[victim.sectionIdx].startAddr + victim.index;
- const [words, _] = this.inflateInstr(isa, instr, pc, symbtable);
+ const [words, _] = this.inflateInstr(
+ isa, opSpec, instr, pc, symbtable);
for (let k = 0; k < words.length; k++) {
sections[victim.sectionIdx].words[victim.index + k] = words[k];
@@ -65,12 +69,39 @@ class BaseMachineCodeGenerator implements MachineCodeGenerator {
}
private inflateInstr(isa: Isa,
+ opSpec: PseudoOpSpec,
instr: Instruction|PseudoOp,
pc: number,
symbtable: SymbTable|null): [number[], boolean] {
- // TODO: actually implement pseudoops
if (instr.kind === 'pseudoop') {
- return [[0, 0, 0, 0], false];
+ let match: OpSpec|null = null;
+
+ for (const op of opSpec.ops) {
+ if (this.opMatch(instr, op)) {
+ match = op;
+ break;
+ }
+ }
+
+ if (!match) {
+ // TODO: multiple operands
+ const operand = (instr.operand ? instr.operand.kind : 'no') + ' operand';
+
+ // TODO: line numbers
+ throw new Error(`unknown assembler directive .${instr.op} ` +
+ `with ${operand}`);
+ }
+
+ if (!symbtable && instr.operand &&
+ instr.operand.kind === 'label') {
+ // TODO: make sure this is true
+ const estimator = match.size as ((isa: Isa) => number);
+ return [new Array<number>(estimator(isa)), true];
+ } else {
+ const ctx: AsmContext = {isa, symbtable: symbtable as SymbTable};
+ const operands: OpOperands = this.operandify(match, instr);
+ return [match.asm(ctx, operands), false];
+ }
} else {
let match: IsaInstruction|null = null;
@@ -98,12 +129,35 @@ class BaseMachineCodeGenerator implements MachineCodeGenerator {
const skip = !symbtable &&
instr.operands.some(op => op.kind === 'label');
const instrBin = skip ? 0 : this.genInstrBin(instr, match, pc,
- symbtable as SymbTable,
- isa);
+ symbtable as SymbTable,
+ isa);
return [[instrBin], skip];
}
}
+ private operandify(opSpec: OpSpec, pseudoOp: PseudoOp): OpOperands {
+ const operands: OpOperands = {ints: {}, labels: {}, strings: {}};
+
+ // TODO: >1 operands
+ if (pseudoOp.operand) {
+ const spec = opSpec.operands[0];
+
+ switch (pseudoOp.operand.kind) {
+ case 'string':
+ operands.strings[spec.name] = pseudoOp.operand.contents;
+ break;
+ case 'int':
+ operands.ints[spec.name] = pseudoOp.operand.val;
+ break;
+ case 'label':
+ operands.labels[spec.name] = pseudoOp.operand.label;
+ break;
+ }
+ }
+
+ return operands;
+ }
+
private genInstrBin(instr: Instruction, isaInstr: IsaInstruction,
pc: number, symbtable: SymbTable, isa: Isa): number {
let bin = 0;
@@ -146,6 +200,13 @@ class BaseMachineCodeGenerator implements MachineCodeGenerator {
return bin;
}
+ private opMatch(pseudoOp: PseudoOp, opSpec: OpSpec): boolean {
+ return pseudoOp.op === opSpec.name && (
+ !pseudoOp.operand && !opSpec.operands.length ||
+ opSpec.operands.length === 1 && !!pseudoOp.operand &&
+ pseudoOp.operand.kind === opSpec.operands[0].kind);
+ }
+
private instrMatch(instr: Instruction, isaInstr: IsaInstruction): boolean {
if (instr.op !== isaInstr.op) {
return false;
@@ -155,7 +216,6 @@ class BaseMachineCodeGenerator implements MachineCodeGenerator {
let o = 0;
let f = 0;
- // while (ok && o < instr.operands.length && f < isaInstr.fields.length) {
while (ok && f < isaInstr.fields.length) {
const field = isaInstr.fields[f];
diff --git a/novice/assembler/codegen/codegen.ts b/novice/assembler/codegen/codegen.ts
index d151c00..e4724fc 100644
--- a/novice/assembler/codegen/codegen.ts
+++ b/novice/assembler/codegen/codegen.ts
@@ -1,4 +1,5 @@
import { Isa } from '../isa';
+import { PseudoOpSpec } from '../opspec';
import { ParsedAssembly } from '../parsers';
interface MachineCodeSection {
@@ -7,7 +8,7 @@ interface MachineCodeSection {
}
interface MachineCodeGenerator {
- gen(isa: Isa, asm: ParsedAssembly): MachineCodeSection[];
+ gen(isa: Isa, opSpec: PseudoOpSpec, asm: ParsedAssembly): MachineCodeSection[];
}
export { MachineCodeSection, MachineCodeGenerator };
diff --git a/novice/assembler/configs.ts b/novice/assembler/configs.ts
new file mode 100644
index 0000000..da49375
--- /dev/null
+++ b/novice/assembler/configs.ts
@@ -0,0 +1,15 @@
+interface Config {
+ parser: string;
+ isa: string;
+ opSpec: string;
+}
+
+const configs: {[s: string]: Config} = {
+ lc3: {
+ parser: 'complx',
+ isa: 'lc3',
+ opSpec: 'complx',
+ },
+};
+
+export { configs };
diff --git a/novice/assembler/index.ts b/novice/assembler/index.ts
index f408641..4f9f54f 100644
--- a/novice/assembler/index.ts
+++ b/novice/assembler/index.ts
@@ -1,6 +1,8 @@
-import { Assembler } from './assembler';
+import { Assembler, AssemblerConfig } from './assembler';
import { BaseMachineCodeGenerator, MachineCodeGenerator } from './codegen';
+import { configs } from './configs';
import { Isa, isas } from './isa';
+import { opSpecs, PseudoOpSpec } from './opspec';
import { Parser, parsers } from './parsers';
function getParser(parserName: string): Parser {
@@ -24,4 +26,27 @@ function getIsa(isaName: string): Isa {
return isas[isaName];
}
-export { Assembler, getParser, getGenerator, getIsa };
+function getOpSpec(opSpecName: string): PseudoOpSpec {
+ if (!opSpecs.hasOwnProperty(opSpecName)) {
+ throw new Error(`no such opspec \`${opSpecName}'\n`);
+ }
+
+ return opSpecs[opSpecName];
+}
+
+function getConfig(configName: string): AssemblerConfig {
+ if (!configs.hasOwnProperty(configName)) {
+ throw new Error(`no such assembler config \`${configName}'\n`);
+ }
+
+ const configNames = configs[configName];
+
+ return {
+ parser: getParser(configNames.parser),
+ generator: getGenerator(),
+ isa: getIsa(configNames.isa),
+ opSpec: getOpSpec(configNames.opSpec),
+ };
+}
+
+export { Assembler, getParser, getGenerator, getIsa, getOpSpec, getConfig };
diff --git a/novice/assembler/isa/isa.ts b/novice/assembler/isa/isa.ts
index 450c693..f3dcbaf 100644
--- a/novice/assembler/isa/isa.ts
+++ b/novice/assembler/isa/isa.ts
@@ -8,6 +8,7 @@ interface Pc {
}
interface Mem {
+ word: number;
space: number;
addressability: number;
}
diff --git a/novice/assembler/isa/lc3.ts b/novice/assembler/isa/lc3.ts
index 9d1ab8f..6ddac38 100644
--- a/novice/assembler/isa/lc3.ts
+++ b/novice/assembler/isa/lc3.ts
@@ -26,6 +26,7 @@ const Lc3Isa: Isa = {
instrBits: 16,
},
mem: {
+ word: 16,
space: 16,
addressability: 16,
},
diff --git a/novice/assembler/opspec/complx.ts b/novice/assembler/opspec/complx.ts
index e59e752..102015d 100644
--- a/novice/assembler/opspec/complx.ts
+++ b/novice/assembler/opspec/complx.ts
@@ -1,4 +1,4 @@
-import { AsmContext, OpOperands, PseudoOpSpec } from './opspec';
+import { AsmContext, oneWord, OpOperands, PseudoOpSpec } from './opspec';
const complxOpSpec: PseudoOpSpec = {
ops: [
@@ -10,6 +10,7 @@ const complxOpSpec: PseudoOpSpec = {
{name: 'fill',
operands: [{kind: 'label', name: 'label'}],
+ size: oneWord,
asm: (ctx: AsmContext, operands: OpOperands) =>
// TODO: complain if nonexistent
[ctx.symbtable[operands.labels.label]]},
diff --git a/novice/assembler/opspec/index.ts b/novice/assembler/opspec/index.ts
new file mode 100644
index 0000000..0f4315d
--- /dev/null
+++ b/novice/assembler/opspec/index.ts
@@ -0,0 +1,8 @@
+import { complxOpSpec } from './complx';
+import { AsmContext, OpOperands, OpSpec, PseudoOpSpec } from './opspec';
+
+const opSpecs: {[s: string]: PseudoOpSpec} = {
+ complx: complxOpSpec,
+};
+
+export { AsmContext, OpOperands, OpSpec, PseudoOpSpec, complxOpSpec, opSpecs };
diff --git a/novice/assembler/opspec/opspec.ts b/novice/assembler/opspec/opspec.ts
index 16986c2..f7629ea 100644
--- a/novice/assembler/opspec/opspec.ts
+++ b/novice/assembler/opspec/opspec.ts
@@ -20,10 +20,16 @@ interface OpSpec {
name: string;
operands: OpOperandSpec[];
asm: (ctx: AsmContext, operands: OpOperands) => number[];
+ size?: (isa: Isa) => number;
}
interface PseudoOpSpec {
ops: OpSpec[];
}
-export { AsmContext, OpOperands, PseudoOpSpec };
+// Size is one word
+function oneWord(isa: Isa) {
+ return isa.mem.word / isa.mem.addressability;
+}
+
+export { AsmContext, OpOperands, PseudoOpSpec, OpSpec, oneWord };
diff --git a/novice/cli.ts b/novice/cli.ts
index 26a4665..ccf9e6d 100644
--- a/novice/cli.ts
+++ b/novice/cli.ts
@@ -1,6 +1,6 @@
import * as fs from 'fs';
import { Writable } from 'stream';
-import { Assembler, getGenerator, getIsa, getParser } from './assembler';
+import { Assembler, getConfig, getParser } from './assembler';
async function main(args: string[], stdout: Writable,
stderr: Writable): Promise<number> {
@@ -8,11 +8,10 @@ async function main(args: string[], stdout: Writable,
switch (subcommand) {
case 'asm':
- if (args.length === 4) {
- const parser = args[1];
- const isa = args[2];
- const path = args[3];
- return await asm(parser, isa, path, stdout, stderr);
+ 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);
}
@@ -29,23 +28,22 @@ async function main(args: string[], stdout: Writable,
}
function usage(stderr: Writable): number {
- stderr.write('usage: novice asm <parser> <isa> <file>\n' +
+ stderr.write('usage: novice asm [config] <file>\n' +
' novice tablegen <parser>\n');
return 1;
}
-async function asm(parserName: string, isaName: string, path: string,
- stdout: Writable, stderr: Writable): Promise<number> {
+async function asm(configName: string, path: string, stdout: Writable,
+ stderr: Writable):
+ Promise<number> {
try {
- const parser = getParser(parserName);
- const generator = getGenerator();
- const isa = getIsa(isaName);
+ const cfg = getConfig(configName);
const fp = fs.createReadStream(path);
await new Promise((resolve, reject) => {
fp.on('readable', resolve);
fp.on('error', reject);
});
- const assembler = new Assembler(parser, generator, isa);
+ const assembler = new Assembler(cfg);
const machineCode = await assembler.assemble(fp);
stdout.write(JSON.stringify(machineCode));
return 0;