aboutsummaryrefslogtreecommitdiffgithub
diff options
context:
space:
mode:
authorAustin Adams <git@austinjadams.com>2019-01-19 20:29:13 -0500
committerAustin Adams <git@austinjadams.com>2019-01-19 20:29:13 -0500
commit456b297b5a80ae9c2ba5825e96493d01c88f229b (patch)
treee041f8c8d832936e12877cbb953e093bb8528ebf
parenta8a18e12910e00f956813720c990112a0292000b (diff)
downloadnovice-456b297b5a80ae9c2ba5825e96493d01c88f229b.tar.gz
novice-456b297b5a80ae9c2ba5825e96493d01c88f229b.tar.xz
Continue work on LC-2200 support
-rw-r--r--novice/assembler/assembler.test.ts1962
-rw-r--r--novice/assembler/codegen/base.ts71
-rw-r--r--novice/assembler/opspec/word.ts3
-rw-r--r--novice/assembler/parsers/complx.ts9
-rw-r--r--novice/assembler/parsers/grammars/lc2200.ts2
-rw-r--r--novice/assembler/parsers/lc2200.ts64
-rw-r--r--novice/assembler/parsers/tables/lc2200.ts2
-rw-r--r--novice/isa/dummy.ts1
-rw-r--r--novice/isa/index.ts7
-rw-r--r--novice/isa/isa.ts36
-rw-r--r--novice/isa/lc2200.ts33
-rw-r--r--novice/isa/lc3.ts46
-rw-r--r--novice/util.ts5
13 files changed, 1382 insertions, 859 deletions
diff --git a/novice/assembler/assembler.test.ts b/novice/assembler/assembler.test.ts
index af1e9fc..c38c24d 100644
--- a/novice/assembler/assembler.test.ts
+++ b/novice/assembler/assembler.test.ts
@@ -8,912 +8,1278 @@ describe('assembler', () => {
beforeEach(() => {
fp = new Readable();
- // Test the complx parser and lc3 isa for now
- assembler = new Assembler(getConfig('lc3'));
});
- describe('parse(fp)', () => {
- it('parses trivial program', () => {
- fp.push('.orig x3000\n')
- fp.push('halt\n')
- fp.push('.end\n')
- fp.push(null)
-
- return expect(assembler.parse(fp)).resolves.toEqual({
- sections: [
- {startAddr: 0x3000, instructions: [
- {kind: 'instr', line: 2, op: 'halt', operands: []},
- ]},
- ],
- labels: {},
- });
+ describe('lc-3', () => {
+ beforeEach(() => {
+ // Test the complx parser and lc3 isa for now
+ assembler = new Assembler(getConfig('lc3'));
});
- it('parses label on its own line', () => {
- fp.push('.orig x3000\n')
- fp.push('fun\n')
- fp.push('br fun\n')
- fp.push('.end\n')
- fp.push(null)
-
- return expect(assembler.parse(fp)).resolves.toEqual({
- sections: [
- {startAddr: 0x3000, instructions: [
- {kind: 'instr', line: 3, op: 'br', operands: [
- {kind: 'label', label: 'fun'}
+ describe('parse(fp)', () => {
+ it('parses trivial program', () => {
+ fp.push('.orig x3000\n')
+ fp.push('halt\n')
+ fp.push('.end\n')
+ fp.push(null)
+
+ return expect(assembler.parse(fp)).resolves.toEqual({
+ sections: [
+ {startAddr: 0x3000, instructions: [
+ {kind: 'instr', line: 2, op: 'halt', operands: []},
]},
- ]},
- ],
- labels: {'fun': [0, 0]},
+ ],
+ labels: {},
+ });
});
- });
- it('parses hello world program', () => {
- fp.push('.orig x3000\n')
- fp.push('lea r0, mystring\n')
- fp.push('puts\n')
- fp.push('halt\n')
- fp.push('\n');
- fp.push('\n');
- fp.push('mystring .stringz "hello world!"\n')
- fp.push('.end\n')
- fp.push(null)
-
- return expect(assembler.parse(fp)).resolves.toEqual({
- sections: [
- {startAddr: 0x3000, instructions: [
- {kind: 'instr', line: 2, op: 'lea', operands: [
- {kind: 'reg', prefix: 'r', num: 0},
- {kind: 'label', label: 'mystring'},
+ it('parses label on its own line', () => {
+ fp.push('.orig x3000\n')
+ fp.push('fun\n')
+ fp.push('br fun\n')
+ fp.push('.end\n')
+ fp.push(null)
+
+ return expect(assembler.parse(fp)).resolves.toEqual({
+ sections: [
+ {startAddr: 0x3000, instructions: [
+ {kind: 'instr', line: 3, op: 'br', operands: [
+ {kind: 'label', label: 'fun'}
+ ]},
]},
- {kind: 'instr', line: 3, op: 'puts', operands: []},
- {kind: 'instr', line: 4, op: 'halt', operands: []},
- {kind: 'pseudoop', line: 7, op: 'stringz', operand:
- {kind: 'string', contents: "hello world!"},
- },
- ]},
- ],
- labels: {
- 'mystring': [0, 3]
- },
+ ],
+ labels: {'fun': [0, 0]},
+ });
});
- });
- it('parses multiple sections', () => {
- fp.push('.orig x3000\n')
- fp.push('haltme halt\n')
- fp.push('.end\n')
- fp.push('\n')
- fp.push('.orig x4000\n')
- fp.push('and r0, r0, -3\n')
- fp.push('halt2 halt\n')
- fp.push('.end\n')
- fp.push(null)
-
- return expect(assembler.parse(fp)).resolves.toEqual({
- sections: [
- {startAddr: 0x3000, instructions: [
- {kind: 'instr', line: 2, op: 'halt', operands: []},
- ]},
- {startAddr: 0x4000, instructions: [
- {kind: 'instr', line: 6, op: 'and', operands: [
- {kind: 'reg', prefix: 'r', num: 0},
- {kind: 'reg', prefix: 'r', num: 0},
- {kind: 'int', val: -3},
+ it('parses hello world program', () => {
+ fp.push('.orig x3000\n')
+ fp.push('lea r0, mystring\n')
+ fp.push('puts\n')
+ fp.push('halt\n')
+ fp.push('\n');
+ fp.push('\n');
+ fp.push('mystring .stringz "hello world!"\n')
+ fp.push('.end\n')
+ fp.push(null)
+
+ return expect(assembler.parse(fp)).resolves.toEqual({
+ sections: [
+ {startAddr: 0x3000, instructions: [
+ {kind: 'instr', line: 2, op: 'lea', operands: [
+ {kind: 'reg', prefix: 'r', num: 0},
+ {kind: 'label', label: 'mystring'},
+ ]},
+ {kind: 'instr', line: 3, op: 'puts', operands: []},
+ {kind: 'instr', line: 4, op: 'halt', operands: []},
+ {kind: 'pseudoop', line: 7, op: 'stringz', operand:
+ {kind: 'string', contents: "hello world!"},
+ },
]},
- {kind: 'instr', line: 7, op: 'halt', operands: []},
- ]},
- ],
- labels: {
- 'haltme': [0, 0],
- 'halt2': [1, 1],
- },
+ ],
+ labels: {
+ 'mystring': [0, 3]
+ },
+ });
});
- });
- it('preserves label case', () => {
- fp.push('.orig x3000\n')
- fp.push('mYlAbeL halt\n')
- fp.push('another-label .blkw 1\n')
- fp.push('LOUD_LABEL .blkw 1\n')
- fp.push('.end\n')
- fp.push(null)
-
- return expect(assembler.parse(fp)).resolves.toEqual({
- sections: [
- {startAddr: 0x3000, instructions: [
- {kind: 'instr', line: 2, op: 'halt', operands: []},
- {kind: 'pseudoop', line: 3, op: 'blkw', operand:
- {kind: 'int', val: 1}},
- {kind: 'pseudoop', line: 4, op: 'blkw', operand:
- {kind: 'int', val: 1}},
- ]},
- ],
- labels: {
- 'mYlAbeL': [0, 0],
- 'another-label': [0, 1],
- 'LOUD_LABEL': [0, 2],
- },
+ it('parses multiple sections', () => {
+ fp.push('.orig x3000\n')
+ fp.push('haltme halt\n')
+ fp.push('.end\n')
+ fp.push('\n')
+ fp.push('.orig x4000\n')
+ fp.push('and r0, r0, -3\n')
+ fp.push('halt2 halt\n')
+ fp.push('.end\n')
+ fp.push(null)
+
+ return expect(assembler.parse(fp)).resolves.toEqual({
+ sections: [
+ {startAddr: 0x3000, instructions: [
+ {kind: 'instr', line: 2, op: 'halt', operands: []},
+ ]},
+ {startAddr: 0x4000, instructions: [
+ {kind: 'instr', line: 6, op: 'and', operands: [
+ {kind: 'reg', prefix: 'r', num: 0},
+ {kind: 'reg', prefix: 'r', num: 0},
+ {kind: 'int', val: -3},
+ ]},
+ {kind: 'instr', line: 7, op: 'halt', operands: []},
+ ]},
+ ],
+ labels: {
+ 'haltme': [0, 0],
+ 'halt2': [1, 1],
+ },
+ });
});
- });
- it('understands string escapes', () => {
- fp.push('.orig x3000\n')
- fp.push(".fill '\\n'\n")
- fp.push(".fill '\\r'\n")
- fp.push(".fill '\\t'\n")
- fp.push(".fill '\\\\'\n")
- fp.push(".fill '\\''\n")
- fp.push(".fill '\a'\n")
- fp.push('.end\n')
- fp.push(null)
-
- return expect(assembler.parse(fp)).resolves.toEqual({
- sections: [
- {startAddr: 0x3000, instructions: [
- {kind: 'pseudoop', line: 2, op: 'fill', operand:
- {kind: 'int', val: '\n'.charCodeAt(0)}},
- {kind: 'pseudoop', line: 3, op: 'fill', operand:
- {kind: 'int', val: '\r'.charCodeAt(0)}},
- {kind: 'pseudoop', line: 4, op: 'fill', operand:
- {kind: 'int', val: '\t'.charCodeAt(0)}},
- {kind: 'pseudoop', line: 5, op: 'fill', operand:
- {kind: 'int', val: '\\'.charCodeAt(0)}},
- {kind: 'pseudoop', line: 6, op: 'fill', operand:
- {kind: 'int', val: '\''.charCodeAt(0)}},
- {kind: 'pseudoop', line: 7, op: 'fill', operand:
- {kind: 'int', val: 'a'.charCodeAt(0)}},
- ]},
- ],
- labels: {},
+ it('preserves label case', () => {
+ fp.push('.orig x3000\n')
+ fp.push('mYlAbeL halt\n')
+ fp.push('another-label .blkw 1\n')
+ fp.push('LOUD_LABEL .blkw 1\n')
+ fp.push('.end\n')
+ fp.push(null)
+
+ return expect(assembler.parse(fp)).resolves.toEqual({
+ sections: [
+ {startAddr: 0x3000, instructions: [
+ {kind: 'instr', line: 2, op: 'halt', operands: []},
+ {kind: 'pseudoop', line: 3, op: 'blkw', operand:
+ {kind: 'int', val: 1}},
+ {kind: 'pseudoop', line: 4, op: 'blkw', operand:
+ {kind: 'int', val: 1}},
+ ]},
+ ],
+ labels: {
+ 'mYlAbeL': [0, 0],
+ 'another-label': [0, 1],
+ 'LOUD_LABEL': [0, 2],
+ },
+ });
});
- });
- it('errors on duplicate labels', () => {
- fp.push('.orig x3000\n')
- fp.push('mylabel .blkw 1\n')
- fp.push('mylabel .blkw 1\n')
- fp.push('.end\n')
- fp.push(null)
+ it('understands string escapes', () => {
+ fp.push('.orig x3000\n')
+ fp.push(".fill '\\n'\n")
+ fp.push(".fill '\\r'\n")
+ fp.push(".fill '\\t'\n")
+ fp.push(".fill '\\\\'\n")
+ fp.push(".fill '\\''\n")
+ fp.push(".fill '\a'\n")
+ fp.push('.end\n')
+ fp.push(null)
+
+ return expect(assembler.parse(fp)).resolves.toEqual({
+ sections: [
+ {startAddr: 0x3000, instructions: [
+ {kind: 'pseudoop', line: 2, op: 'fill', operand:
+ {kind: 'int', val: '\n'.charCodeAt(0)}},
+ {kind: 'pseudoop', line: 3, op: 'fill', operand:
+ {kind: 'int', val: '\r'.charCodeAt(0)}},
+ {kind: 'pseudoop', line: 4, op: 'fill', operand:
+ {kind: 'int', val: '\t'.charCodeAt(0)}},
+ {kind: 'pseudoop', line: 5, op: 'fill', operand:
+ {kind: 'int', val: '\\'.charCodeAt(0)}},
+ {kind: 'pseudoop', line: 6, op: 'fill', operand:
+ {kind: 'int', val: '\''.charCodeAt(0)}},
+ {kind: 'pseudoop', line: 7, op: 'fill', operand:
+ {kind: 'int', val: 'a'.charCodeAt(0)}},
+ ]},
+ ],
+ labels: {},
+ });
+ });
- return expect(assembler.parse(fp)).rejects.toThrow(
- 'duplicate label mylabel on line 3');
- });
+ it('errors on duplicate labels', () => {
+ fp.push('.orig x3000\n')
+ fp.push('mylabel .blkw 1\n')
+ fp.push('mylabel .blkw 1\n')
+ fp.push('.end\n')
+ fp.push(null)
- it('errors on missing .end', () => {
- fp.push('.orig x3000\n')
- fp.push('halt\n')
- fp.push(null)
+ return expect(assembler.parse(fp)).rejects.toThrow(
+ 'duplicate label mylabel on line 3');
+ });
- return expect(assembler.parse(fp)).rejects.toThrow('missing an .end');
- });
+ it('errors on missing .end', () => {
+ fp.push('.orig x3000\n')
+ fp.push('halt\n')
+ fp.push(null)
- it('errors on .end label', () => {
- fp.push('.orig x3000\n')
- fp.push('halt\n')
- fp.push('duh .end\n')
- fp.push(null)
+ return expect(assembler.parse(fp)).rejects.toThrow('missing an .end');
+ });
- return expect(assembler.parse(fp)).rejects.toThrow('should not have a label');
- });
+ it('errors on .end label', () => {
+ fp.push('.orig x3000\n')
+ fp.push('halt\n')
+ fp.push('duh .end\n')
+ fp.push(null)
- it('errors on .end with operand', () => {
- fp.push('.orig x3000\n')
- fp.push('halt\n')
- fp.push('.end "ho ho"\n')
- fp.push(null)
+ return expect(assembler.parse(fp)).rejects.toThrow('should not have a label');
+ });
- return expect(assembler.parse(fp)).rejects.toThrow('should not have an operand');
- });
+ it('errors on .end with operand', () => {
+ fp.push('.orig x3000\n')
+ fp.push('halt\n')
+ fp.push('.end "ho ho"\n')
+ fp.push(null)
- it('errors on .orig label', () => {
- fp.push('duhhhh .orig x3000\n')
- fp.push('halt\n')
- fp.push('.end\n')
- fp.push(null)
+ return expect(assembler.parse(fp)).rejects.toThrow('should not have an operand');
+ });
- return expect(assembler.parse(fp)).rejects.toThrow('should not have a label');
- });
+ it('errors on .orig label', () => {
+ fp.push('duhhhh .orig x3000\n')
+ fp.push('halt\n')
+ fp.push('.end\n')
+ fp.push(null)
- it('errors on .orig without operand', () => {
- fp.push('.orig\n')
- fp.push('halt\n')
- fp.push('.end\n')
- fp.push(null)
+ return expect(assembler.parse(fp)).rejects.toThrow('should not have a label');
+ });
- return expect(assembler.parse(fp)).rejects.toThrow('needs an address operand');
- });
+ it('errors on .orig without operand', () => {
+ fp.push('.orig\n')
+ fp.push('halt\n')
+ fp.push('.end\n')
+ fp.push(null)
- it('errors on .orig with wrong operand type', () => {
- fp.push('.orig "duhhhh"\n')
- fp.push('halt\n')
- fp.push('.end\n')
- fp.push(null)
+ return expect(assembler.parse(fp)).rejects.toThrow('needs an address operand');
+ });
- return expect(assembler.parse(fp)).rejects.toThrow('needs an address operand');
- });
+ it('errors on .orig with wrong operand type', () => {
+ fp.push('.orig "duhhhh"\n')
+ fp.push('halt\n')
+ fp.push('.end\n')
+ fp.push(null)
- it('errors on stray pseudo-op', () => {
- fp.push('.blkw 1\n')
- fp.push(null)
+ return expect(assembler.parse(fp)).rejects.toThrow('needs an address operand');
+ });
- return expect(assembler.parse(fp)).rejects.toThrow('stray assembler directive');
- });
+ it('errors on stray pseudo-op', () => {
+ fp.push('.blkw 1\n')
+ fp.push(null)
- it('errors on stray label', () => {
- fp.push('doh\n')
- fp.push(null)
+ return expect(assembler.parse(fp)).rejects.toThrow('stray assembler directive');
+ });
- return expect(assembler.parse(fp)).rejects.toThrow('stray label');
- });
+ it('errors on stray label', () => {
+ fp.push('doh\n')
+ fp.push(null)
- it('errors on stray instruction', () => {
- fp.push('trap x420\n')
- fp.push(null)
+ return expect(assembler.parse(fp)).rejects.toThrow('stray label');
+ });
- return expect(assembler.parse(fp)).rejects.toThrow('stray instruction');
- });
+ it('errors on stray instruction', () => {
+ fp.push('trap x420\n')
+ fp.push(null)
- it('errors on dangling operand', () => {
- fp.push('.orig x3000\n');
- fp.push('lea r0, mystring puts\n');
- fp.push('halt\n');
- fp.push('.end\n');
- fp.push(null);
- return expect(assembler.parse(fp)).rejects.toThrow('puts');
- });
- });
+ return expect(assembler.parse(fp)).rejects.toThrow('stray instruction');
+ });
- describe('assemble(fp)', () => {
- it('assembles trivial program', () => {
- fp.push('.orig x3000\n')
- fp.push('halt\n')
- fp.push('.end\n')
- fp.push(null)
-
- return expect(assembler.assemble(fp)).resolves.toEqual([
- {
- startAddr: 0x3000,
- words: [
- 0xf025,
- ],
- },
- ]);
+ it('errors on dangling operand', () => {
+ fp.push('.orig x3000\n');
+ fp.push('lea r0, mystring puts\n');
+ fp.push('halt\n');
+ fp.push('.end\n');
+ fp.push(null);
+ return expect(assembler.parse(fp)).rejects.toThrow('puts');
+ });
});
- it('assembles branch', () => {
- fp.push('.orig x3000\n')
- fp.push('fun\n')
- fp.push('br fun\n')
- fp.push('.end\n')
- fp.push(null)
-
- return expect(assembler.assemble(fp)).resolves.toEqual([
- {
- startAddr: 0x3000,
- words: [
- 0b0000111111111111,
- ],
- },
- ]);
- });
+ describe('assemble(fp)', () => {
+ it('assembles trivial program', () => {
+ fp.push('.orig x3000\n')
+ fp.push('halt\n')
+ fp.push('.end\n')
+ fp.push(null)
+
+ return expect(assembler.assemble(fp)).resolves.toEqual([
+ {
+ startAddr: 0x3000,
+ words: [
+ 0xf025,
+ ],
+ },
+ ]);
+ });
- it('assembles hello world program', () => {
- fp.push('.orig x3000\n')
- fp.push('lea r0, mystring\n')
- fp.push('puts\n')
- fp.push('halt\n')
- fp.push('\n');
- fp.push('\n');
- fp.push('mystring .stringz "hello world!"\n')
- fp.push('.end\n')
- fp.push(null)
-
- return expect(assembler.assemble(fp)).resolves.toEqual([
- {
- startAddr: 0x3000,
- words: [
- 0b1110000000000010,
- 0xf022,
- 0xf025,
- 'h'.charCodeAt(0),
- 'e'.charCodeAt(0),
- 'l'.charCodeAt(0),
- 'l'.charCodeAt(0),
- 'o'.charCodeAt(0),
- ' '.charCodeAt(0),
- 'w'.charCodeAt(0),
- 'o'.charCodeAt(0),
- 'r'.charCodeAt(0),
- 'l'.charCodeAt(0),
- 'd'.charCodeAt(0),
- '!'.charCodeAt(0),
- 0,
- ],
- },
- ]);
- });
+ it('assembles branch', () => {
+ fp.push('.orig x3000\n')
+ fp.push('fun\n')
+ fp.push('br fun\n')
+ fp.push('.end\n')
+ fp.push(null)
+
+ return expect(assembler.assemble(fp)).resolves.toEqual([
+ {
+ startAddr: 0x3000,
+ words: [
+ 0b0000111111111111,
+ ],
+ },
+ ]);
+ });
- it('assembles multiple sections', () => {
- fp.push('.orig x3000\n')
- fp.push('haltme halt\n')
- fp.push('.end\n')
- fp.push('\n')
- fp.push('.orig x4000\n')
- fp.push('and r1, r2, -3\n')
- fp.push('halt2 halt\n')
- fp.push('.end\n')
- fp.push(null)
-
- return expect(assembler.assemble(fp)).resolves.toEqual([
- {
- startAddr: 0x3000,
- words: [
- 0xf025,
- ],
- },
- {
- startAddr: 0x4000,
- words: [
- 0b0101001010111101,
- 0xf025,
- ],
- },
- ]);
- });
+ it('assembles hello world program', () => {
+ fp.push('.orig x3000\n')
+ fp.push('lea r0, mystring\n')
+ fp.push('puts\n')
+ fp.push('halt\n')
+ fp.push('\n');
+ fp.push('\n');
+ fp.push('mystring .stringz "hello world!"\n')
+ fp.push('.end\n')
+ fp.push(null)
+
+ return expect(assembler.assemble(fp)).resolves.toEqual([
+ {
+ startAddr: 0x3000,
+ words: [
+ 0b1110000000000010,
+ 0xf022,
+ 0xf025,
+ 'h'.charCodeAt(0),
+ 'e'.charCodeAt(0),
+ 'l'.charCodeAt(0),
+ 'l'.charCodeAt(0),
+ 'o'.charCodeAt(0),
+ ' '.charCodeAt(0),
+ 'w'.charCodeAt(0),
+ 'o'.charCodeAt(0),
+ 'r'.charCodeAt(0),
+ 'l'.charCodeAt(0),
+ 'd'.charCodeAt(0),
+ '!'.charCodeAt(0),
+ 0,
+ ],
+ },
+ ]);
+ });
- it('assembles arithmetic instructions', () => {
- fp.push('.orig x3000\n')
- fp.push('add r4, r5, r3\n')
- fp.push('add r4, r5, 3\n')
- fp.push('and r6, r3, r2\n')
- fp.push('and r6, r3, 2\n')
- fp.push('asdf not r3, r4\n')
- fp.push('.end\n')
- fp.push(null)
-
- return expect(assembler.assemble(fp)).resolves.toEqual([
- {
- startAddr: 0x3000,
- words: [
- 0b0001100101000011,
- 0b0001100101100011,
- 0b0101110011000010,
- 0b0101110011100010,
- 0b1001011100111111,
- ],
- },
- ]);
- });
+ it('assembles multiple sections', () => {
+ fp.push('.orig x3000\n')
+ fp.push('haltme halt\n')
+ fp.push('.end\n')
+ fp.push('\n')
+ fp.push('.orig x4000\n')
+ fp.push('and r1, r2, -3\n')
+ fp.push('halt2 halt\n')
+ fp.push('.end\n')
+ fp.push(null)
+
+ return expect(assembler.assemble(fp)).resolves.toEqual([
+ {
+ startAddr: 0x3000,
+ words: [
+ 0xf025,
+ ],
+ },
+ {
+ startAddr: 0x4000,
+ words: [
+ 0b0101001010111101,
+ 0xf025,
+ ],
+ },
+ ]);
+ });
- it('assembles branch instructions', () => {
- fp.push('.orig x3000\n')
- fp.push('nop\n')
- fp.push('\n')
- fp.push('asdf0 brp asdf0\n')
- fp.push('asdf1 brz asdf1\n')
- fp.push('asdf2 brzp asdf2\n')
- fp.push('asdf3 brn asdf3\n')
- fp.push('asdf4 brnp asdf4\n')
- fp.push('asdf5 brnz asdf5\n')
- fp.push('asdf6 brnzp asdf6\n')
- fp.push('asdf7 br asdf7\n')
- fp.push('\n')
- fp.push('subr jmp r3\n')
- fp.push('jsr subr\n')
- fp.push('jsrr r5\n')
- fp.push('ret\n')
- fp.push('\n')
- fp.push('trap x69\n')
- fp.push('.end\n')
- fp.push(null)
-
- return expect(assembler.assemble(fp)).resolves.toEqual([
- {
- startAddr: 0x3000,
- words: [
- // nop
- 0b0000000000000000,
-
- // br
- 0b0000001111111111,
- 0b0000010111111111,
- 0b0000011111111111,
- 0b0000100111111111,
- 0b0000101111111111,
- 0b0000110111111111,
- 0b0000111111111111,
- 0b0000111111111111,
-
- // jmp/jsr/ret
- 0b1100000011000000,
- 0b0100111111111110,
- 0b0100000101000000,
- 0b1100000111000000,
-
- // trap
- 0b1111000001101001,
- ],
- },
- ]);
- });
+ it('assembles arithmetic instructions', () => {
+ fp.push('.orig x3000\n')
+ fp.push('add r4, r5, r3\n')
+ fp.push('add r4, r5, 3\n')
+ fp.push('and r6, r3, r2\n')
+ fp.push('and r6, r3, 2\n')
+ fp.push('asdf not r3, r4\n')
+ fp.push('.end\n')
+ fp.push(null)
+
+ return expect(assembler.assemble(fp)).resolves.toEqual([
+ {
+ startAddr: 0x3000,
+ words: [
+ 0b0001100101000011,
+ 0b0001100101100011,
+ 0b0101110011000010,
+ 0b0101110011100010,
+ 0b1001011100111111,
+ ],
+ },
+ ]);
+ });
- it('assembles memory instructions', () => {
- fp.push('.orig x3000\n')
- fp.push('asdf0 ld r3, asdf0\n')
- fp.push('asdf1 ldi r4, asdf1\n')
- fp.push('asdf2 lea r2, asdf2\n')
- fp.push('ldr r1, r5, -4\n')
- fp.push('\n')
- fp.push('asdf3 st r3, asdf3\n')
- fp.push('asdf4 sti r4, asdf4\n')
- fp.push('str r1, r5, -4\n')
- fp.push('.end\n')
- fp.push(null)
-
- return expect(assembler.assemble(fp)).resolves.toEqual([
- {
- startAddr: 0x3000,
- words: [
- // loads
- 0b0010011111111111,
- 0b1010100111111111,
- 0b1110010111111111,
- 0b0110001101111100,
-
- // stores
- 0b0011011111111111,
- 0b1011100111111111,
- 0b0111001101111100,
- ],
- },
- ]);
- });
+ it('assembles branch instructions', () => {
+ fp.push('.orig x3000\n')
+ fp.push('nop\n')
+ fp.push('\n')
+ fp.push('asdf0 brp asdf0\n')
+ fp.push('asdf1 brz asdf1\n')
+ fp.push('asdf2 brzp asdf2\n')
+ fp.push('asdf3 brn asdf3\n')
+ fp.push('asdf4 brnp asdf4\n')
+ fp.push('asdf5 brnz asdf5\n')
+ fp.push('asdf6 brnzp asdf6\n')
+ fp.push('asdf7 br asdf7\n')
+ fp.push('\n')
+ fp.push('subr jmp r3\n')
+ fp.push('jsr subr\n')
+ fp.push('jsrr r5\n')
+ fp.push('ret\n')
+ fp.push('\n')
+ fp.push('trap x69\n')
+ fp.push('.end\n')
+ fp.push(null)
+
+ return expect(assembler.assemble(fp)).resolves.toEqual([
+ {
+ startAddr: 0x3000,
+ words: [
+ // nop
+ 0b0000000000000000,
+
+ // br
+ 0b0000001111111111,
+ 0b0000010111111111,
+ 0b0000011111111111,
+ 0b0000100111111111,
+ 0b0000101111111111,
+ 0b0000110111111111,
+ 0b0000111111111111,
+ 0b0000111111111111,
+
+ // jmp/jsr/ret
+ 0b1100000011000000,
+ 0b0100111111111110,
+ 0b0100000101000000,
+ 0b1100000111000000,
+
+ // trap
+ 0b1111000001101001,
+ ],
+ },
+ ]);
+ });
- it('assembles trap aliases', () => {
- fp.push('.orig x3000\n')
- fp.push('getc\n');
- fp.push('out\n');
- fp.push('puts\n');
- fp.push('in\n');
- fp.push('halt\n');
- fp.push('.end\n')
- fp.push(null)
-
- return expect(assembler.assemble(fp)).resolves.toEqual([
- {
- startAddr: 0x3000,
- words: [
- 0xf020,
- 0xf021,
- 0xf022,
- 0xf023,
- 0xf025,
- ],
- },
- ]);
- });
+ it('assembles memory instructions', () => {
+ fp.push('.orig x3000\n')
+ fp.push('asdf0 ld r3, asdf0\n')
+ fp.push('asdf1 ldi r4, asdf1\n')
+ fp.push('asdf2 lea r2, asdf2\n')
+ fp.push('ldr r1, r5, -4\n')
+ fp.push('\n')
+ fp.push('asdf3 st r3, asdf3\n')
+ fp.push('asdf4 sti r4, asdf4\n')
+ fp.push('str r1, r5, -4\n')
+ fp.push('.end\n')
+ fp.push(null)
+
+ return expect(assembler.assemble(fp)).resolves.toEqual([
+ {
+ startAddr: 0x3000,
+ words: [
+ // loads
+ 0b0010011111111111,
+ 0b1010100111111111,
+ 0b1110010111111111,
+ 0b0110001101111100,
+
+ // stores
+ 0b0011011111111111,
+ 0b1011100111111111,
+ 0b0111001101111100,
+ ],
+ },
+ ]);
+ });
- it('assembles pseudo-ops', () => {
- fp.push('.orig x5000\n')
- fp.push('.blkw 3\n');
- fp.push('.fill x1337\n');
- fp.push('.blkw 1\n');
- fp.push('.fill -2\n');
- fp.push('label .fill label\n');
- fp.push('.stringz ""\n');
- fp.push('.stringz "hi"\n');
- fp.push('.end\n')
- fp.push(null)
-
- return expect(assembler.assemble(fp)).resolves.toEqual([
- {
- startAddr: 0x5000,
- words: [
- 0x0,
- 0x0,
- 0x0,
- 0x1337,
- 0x0,
- 0xfffe,
- 0x5006,
- 0x0,
- 'h'.charCodeAt(0),
- 'i'.charCodeAt(0),
- 0x0,
- ],
- },
- ]);
- });
+ it('assembles trap aliases', () => {
+ fp.push('.orig x3000\n')
+ fp.push('getc\n');
+ fp.push('out\n');
+ fp.push('puts\n');
+ fp.push('in\n');
+ fp.push('halt\n');
+ fp.push('.end\n')
+ fp.push(null)
+
+ return expect(assembler.assemble(fp)).resolves.toEqual([
+ {
+ startAddr: 0x3000,
+ words: [
+ 0xf020,
+ 0xf021,
+ 0xf022,
+ 0xf023,
+ 0xf025,
+ ],
+ },
+ ]);
+ });
- it('errors on nonexistent label', () => {
- fp.push('.orig x3000\n');
- fp.push('lea r0, mystringputs\n');
- fp.push('halt\n');
- fp.push('.end\n');
- fp.push(null);
+ it('assembles pseudo-ops', () => {
+ fp.push('.orig x5000\n')
+ fp.push('.blkw 3\n');
+ fp.push('.fill x1337\n');
+ fp.push('.blkw 1\n');
+ fp.push('.fill -2\n');
+ fp.push('label .fill label\n');
+ fp.push('.stringz ""\n');
+ fp.push('.stringz "hi"\n');
+ fp.push('.end\n')
+ fp.push(null)
+
+ return expect(assembler.assemble(fp)).resolves.toEqual([
+ {
+ startAddr: 0x5000,
+ words: [
+ 0x0,
+ 0x0,
+ 0x0,
+ 0x1337,
+ 0x0,
+ 0xfffe,
+ 0x5006,
+ 0x0,
+ 'h'.charCodeAt(0),
+ 'i'.charCodeAt(0),
+ 0x0,
+ ],
+ },
+ ]);
+ });
- return expect(assembler.assemble(fp)).rejects.toThrow('mystringputs');
- });
+ it('errors on nonexistent label', () => {
+ fp.push('.orig x3000\n');
+ fp.push('lea r0, mystringputs\n');
+ fp.push('halt\n');
+ fp.push('.end\n');
+ fp.push(null);
- it('errors on undersized immediate', () => {
- fp.push('.orig x3000\n');
- fp.push('add r0, r0, -64\n');
- fp.push('.end\n');
- fp.push(null);
+ return expect(assembler.assemble(fp)).rejects.toThrow('mystringputs');
+ });
- return expect(assembler.assemble(fp)).rejects.toThrow('-64');
- });
+ it('errors on undersized immediate', () => {
+ fp.push('.orig x3000\n');
+ fp.push('add r0, r0, -64\n');
+ fp.push('.end\n');
+ fp.push(null);
- it('errors on barely undersized immediate', () => {
- fp.push('.orig x3000\n');
- fp.push('add r0, r0, -17\n');
- fp.push('.end\n');
- fp.push(null);
+ return expect(assembler.assemble(fp)).rejects.toThrow('-64');
+ });
- return expect(assembler.assemble(fp)).rejects.toThrow('-17');
- });
+ it('errors on barely undersized immediate', () => {
+ fp.push('.orig x3000\n');
+ fp.push('add r0, r0, -17\n');
+ fp.push('.end\n');
+ fp.push(null);
- it('assembles almost undersized immediate', () => {
- fp.push('.orig x3000\n');
- fp.push('add r0, r0, -16\n');
- fp.push('.end\n');
- fp.push(null);
-
- return expect(assembler.assemble(fp)).resolves.toEqual([
- {
- startAddr: 0x3000,
- words: [
- 0b0001000000110000,
- ],
- },
- ]);
- });
+ return expect(assembler.assemble(fp)).rejects.toThrow('-17');
+ });
- it('errors on oversized immediate', () => {
- fp.push('.orig x3000\n');
- fp.push('add r0, r0, 64\n');
- fp.push('.end\n');
- fp.push(null);
+ it('assembles almost undersized immediate', () => {
+ fp.push('.orig x3000\n');
+ fp.push('add r0, r0, -16\n');
+ fp.push('.end\n');
+ fp.push(null);
+
+ return expect(assembler.assemble(fp)).resolves.toEqual([
+ {
+ startAddr: 0x3000,
+ words: [
+ 0b0001000000110000,
+ ],
+ },
+ ]);
+ });
- return expect(assembler.assemble(fp)).rejects.toThrow('64');
- });
+ it('errors on oversized immediate', () => {
+ fp.push('.orig x3000\n');
+ fp.push('add r0, r0, 64\n');
+ fp.push('.end\n');
+ fp.push(null);
- it('errors on barely oversized immediate', () => {
- fp.push('.orig x3000\n');
- fp.push('add r0, r0, 16\n');
- fp.push('.end\n');
- fp.push(null);
+ return expect(assembler.assemble(fp)).rejects.toThrow('64');
+ });
- return expect(assembler.assemble(fp)).rejects.toThrow('16');
- });
+ it('errors on barely oversized immediate', () => {
+ fp.push('.orig x3000\n');
+ fp.push('add r0, r0, 16\n');
+ fp.push('.end\n');
+ fp.push(null);
- it('assembles almost oversized immediate', () => {
- fp.push('.orig x3000\n');
- fp.push('add r0, r0, 15\n');
- fp.push('.end\n');
- fp.push(null);
-
- return expect(assembler.assemble(fp)).resolves.toEqual([
- {
- startAddr: 0x3000,
- words: [
- 0b0001000000101111,
- ],
- },
- ]);
- });
+ return expect(assembler.assemble(fp)).rejects.toThrow('16');
+ });
- it('errors on oversized label offset', () => {
- fp.push('.orig x3000\n');
- fp.push('ld r3, faraway\n');
- fp.push('.blkw 1024\n');
- fp.push('faraway .fill 69\n');
- fp.push('.end\n');
- fp.push(null);
+ it('assembles almost oversized immediate', () => {
+ fp.push('.orig x3000\n');
+ fp.push('add r0, r0, 15\n');
+ fp.push('.end\n');
+ fp.push(null);
+
+ return expect(assembler.assemble(fp)).resolves.toEqual([
+ {
+ startAddr: 0x3000,
+ words: [
+ 0b0001000000101111,
+ ],
+ },
+ ]);
+ });
- return expect(assembler.assemble(fp)).rejects.toThrow('1024');
- });
+ it('errors on oversized label offset', () => {
+ fp.push('.orig x3000\n');
+ fp.push('ld r3, faraway\n');
+ fp.push('.blkw 1024\n');
+ fp.push('faraway .fill 69\n');
+ fp.push('.end\n');
+ fp.push(null);
- it('errors on barely oversized label offset', () => {
- fp.push('.orig x3000\n');
- fp.push('ld r3, faraway\n');
- fp.push('.blkw 256\n');
- fp.push('faraway .fill 69\n');
- fp.push('.end\n');
- fp.push(null);
+ return expect(assembler.assemble(fp)).rejects.toThrow('1024');
+ });
- return expect(assembler.assemble(fp)).rejects.toThrow('256');
- });
+ it('errors on barely oversized label offset', () => {
+ fp.push('.orig x3000\n');
+ fp.push('ld r3, faraway\n');
+ fp.push('.blkw 256\n');
+ fp.push('faraway .fill 69\n');
+ fp.push('.end\n');
+ fp.push(null);
- it('errors on undersized label offset', () => {
- fp.push('.orig x3000\n');
- fp.push('faraway .fill 69\n');
- fp.push('.blkw 1024\n');
- fp.push('ld r3, faraway\n');
- fp.push('.end\n');
- fp.push(null);
+ return expect(assembler.assemble(fp)).rejects.toThrow('256');
+ });
- return expect(assembler.assemble(fp)).rejects.toThrow('-1026');
- });
+ it('errors on undersized label offset', () => {
+ fp.push('.orig x3000\n');
+ fp.push('faraway .fill 69\n');
+ fp.push('.blkw 1024\n');
+ fp.push('ld r3, faraway\n');
+ fp.push('.end\n');
+ fp.push(null);
- it('errors on barely oversized label offset', () => {
- fp.push('.orig x3000\n');
- fp.push('faraway .fill 69\n');
- fp.push('.blkw 255\n');
- fp.push('ld r3, faraway\n');
- fp.push('.end\n');
- fp.push(null);
+ return expect(assembler.assemble(fp)).rejects.toThrow('-1026');
+ });
- return expect(assembler.assemble(fp)).rejects.toThrow('-257');
- });
+ it('errors on barely oversized label offset', () => {
+ fp.push('.orig x3000\n');
+ fp.push('faraway .fill 69\n');
+ fp.push('.blkw 255\n');
+ fp.push('ld r3, faraway\n');
+ fp.push('.end\n');
+ fp.push(null);
- it('assembles non-overlapping sections', () => {
- fp.push('.orig x4001\n');
- fp.push('.blkw 1\n');
- fp.push('.end\n');
- fp.push('.orig x4000\n');
- fp.push('.blkw 1\n');
- fp.push('.end\n');
- fp.push(null);
-
- return expect(assembler.assemble(fp)).resolves.toEqual([
- {
- startAddr: 0x4001,
- words: [
- 0x0,
- ],
- },
- {
- startAddr: 0x4000,
- words: [
- 0x0,
- ],
- },
- ]);
- });
+ return expect(assembler.assemble(fp)).rejects.toThrow('-257');
+ });
+
+ it('assembles non-overlapping sections', () => {
+ fp.push('.orig x4001\n');
+ fp.push('.blkw 1\n');
+ fp.push('.end\n');
+ fp.push('.orig x4000\n');
+ fp.push('.blkw 1\n');
+ fp.push('.end\n');
+ fp.push(null);
+
+ return expect(assembler.assemble(fp)).resolves.toEqual([
+ {
+ startAddr: 0x4001,
+ words: [
+ 0x0,
+ ],
+ },
+ {
+ startAddr: 0x4000,
+ words: [
+ 0x0,
+ ],
+ },
+ ]);
+ });
- it('errors on overlapping sections', () => {
- fp.push('.orig x4001\n');
- fp.push('.blkw 1\n');
- fp.push('.end\n');
- fp.push('.orig x4000\n');
- fp.push('.blkw 2\n');
- fp.push('.end\n');
- fp.push(null);
+ it('errors on overlapping sections', () => {
+ fp.push('.orig x4001\n');
+ fp.push('.blkw 1\n');
+ fp.push('.end\n');
+ fp.push('.orig x4000\n');
+ fp.push('.blkw 2\n');
+ fp.push('.end\n');
+ fp.push(null);
- return expect(assembler.assemble(fp)).rejects.toThrow('overlap');
- });
+ return expect(assembler.assemble(fp)).rejects.toThrow('overlap');
+ });
- it('errors on bogus pseudoops with no operands', () => {
- fp.push('.orig x3000\n');
- fp.push('.bob\n');
- fp.push('.end\n');
- fp.push(null);
+ it('errors on bogus pseudoops with no operands', () => {
+ fp.push('.orig x3000\n');
+ fp.push('.bob\n');
+ fp.push('.end\n');
+ fp.push(null);
- return expect(assembler.assemble(fp)).rejects
- .toThrow('.bob with no operand');
- });
+ return expect(assembler.assemble(fp)).rejects
+ .toThrow('.bob with no operand');
+ });
- it('errors on bogus pseudoops with operand', () => {
- fp.push('.orig x3000\n');
- fp.push('.bob "hey ya"\n');
- fp.push('.end\n');
- fp.push(null);
+ it('errors on bogus pseudoops with operand', () => {
+ fp.push('.orig x3000\n');
+ fp.push('.bob "hey ya"\n');
+ fp.push('.end\n');
+ fp.push(null);
- return expect(assembler.assemble(fp)).rejects
- .toThrow('.bob with string operand');
- });
+ return expect(assembler.assemble(fp)).rejects
+ .toThrow('.bob with string operand');
+ });
- it('errors on bogus instructions', () => {
- fp.push('.orig x3000\n');
- fp.push('steveo r1, r3, r3\n');
- fp.push('.end\n');
- fp.push(null);
+ it('errors on bogus instructions', () => {
+ fp.push('.orig x3000\n');
+ fp.push('steveo r1, r3, r3\n');
+ fp.push('.end\n');
+ fp.push(null);
- return expect(assembler.assemble(fp)).rejects
- .toThrow('steveo with operands reg, reg, reg');
- });
+ return expect(assembler.assemble(fp)).rejects
+ .toThrow('steveo with operands reg, reg, reg');
+ });
- it('errors on barely oversized regnos', () => {
- fp.push('.orig x3000\n');
- fp.push('add r0, r0, r8\n');
- fp.push('.end\n');
- fp.push(null);
+ it('errors on barely oversized regnos', () => {
+ fp.push('.orig x3000\n');
+ fp.push('add r0, r0, r8\n');
+ fp.push('.end\n');
+ fp.push(null);
- return expect(assembler.assemble(fp)).rejects
- .toThrow(/8 .* on line 2/i);
- });
+ return expect(assembler.assemble(fp)).rejects
+ .toThrow(/8 .* on line 2/i);
+ });
- it('errors on oversized regnos', () => {
- fp.push('.orig x3000\n');
- fp.push('add r0, r0, r1000\n');
- fp.push('.end\n');
- fp.push(null);
+ it('errors on oversized regnos', () => {
+ fp.push('.orig x3000\n');
+ fp.push('add r0, r0, r1000\n');
+ fp.push('.end\n');
+ fp.push(null);
- return expect(assembler.assemble(fp)).rejects
- .toThrow(/1000 .* on line 2/i);
- });
+ return expect(assembler.assemble(fp)).rejects
+ .toThrow(/1000 .* on line 2/i);
+ });
- it('errors on barely negative non-sign-extended immediates', () => {
- fp.push('.orig x3000\n');
- fp.push('trap -1\n');
- fp.push('.end\n');
- fp.push(null);
+ it('errors on barely negative non-sign-extended immediates', () => {
+ fp.push('.orig x3000\n');
+ fp.push('trap -1\n');
+ fp.push('.end\n');
+ fp.push(null);
- return expect(assembler.assemble(fp)).rejects
- .toThrow('-1');
- });
+ return expect(assembler.assemble(fp)).rejects
+ .toThrow('-1');
+ });
- it('errors on negative non-sign-extended immediates', () => {
- fp.push('.orig x3000\n');
- fp.push('trap -1000\n');
- fp.push('.end\n');
- fp.push(null);
+ it('errors on negative non-sign-extended immediates', () => {
+ fp.push('.orig x3000\n');
+ fp.push('trap -1000\n');
+ fp.push('.end\n');
+ fp.push(null);
- return expect(assembler.assemble(fp)).rejects
- .toThrow('-1000');
- });
+ return expect(assembler.assemble(fp)).rejects
+ .toThrow('-1000');
+ });
- it('errors on barely oversized non-sign-extended immediates', () => {
- fp.push('.orig x3000\n');
- fp.push('trap 256\n');
- fp.push('.end\n');
- fp.push(null);
+ it('errors on barely oversized non-sign-extended immediates', () => {
+ fp.push('.orig x3000\n');
+ fp.push('trap 256\n');
+ fp.push('.end\n');
+ fp.push(null);
- return expect(assembler.assemble(fp)).rejects
- .toThrow('256');
- });
+ return expect(assembler.assemble(fp)).rejects
+ .toThrow('256');
+ });
- it('errors on oversized non-sign-extended immediates', () => {
- fp.push('.orig x3000\n');
- fp.push('trap 1000\n');
- fp.push('.end\n');
- fp.push(null);
+ it('errors on oversized non-sign-extended immediates', () => {
+ fp.push('.orig x3000\n');
+ fp.push('trap 1000\n');
+ fp.push('.end\n');
+ fp.push(null);
- return expect(assembler.assemble(fp)).rejects
- .toThrow('1000');
- });
+ return expect(assembler.assemble(fp)).rejects
+ .toThrow('1000');
+ });
- it('errors on too many operands', () => {
- fp.push('.orig x3000\n');
- fp.push('add r0, r0, 3, r0\n');
- fp.push('.end\n');
- fp.push(null);
+ it('errors on too many operands', () => {
+ fp.push('.orig x3000\n');
+ fp.push('add r0, r0, 3, r0\n');
+ fp.push('.end\n');
+ fp.push(null);
- return expect(assembler.assemble(fp)).rejects
- .toThrow('reg, reg, int, reg');
+ return expect(assembler.assemble(fp)).rejects
+ .toThrow('reg, reg, int, reg');
+ });
+
+ it('errors on too few operands', () => {
+ fp.push('.orig x3000\n');
+ fp.push('add r0, r0\n');
+ fp.push('.end\n');
+ fp.push(null);
+
+ return expect(assembler.assemble(fp)).rejects
+ .toThrow('reg, reg');
+ });
});
- it('errors on too few operands', () => {
- fp.push('.orig x3000\n');
- fp.push('add r0, r0\n');
- fp.push('.end\n');
- fp.push(null);
+ describe('assembleTo(inFp, outFp)', () => {
+ let outFp: Writable;
+ let outActual: Buffer;
+ let outActualLen: number;
+
+ beforeEach(() => {
+ outActualLen = 0;
+ outActual = Buffer.alloc(1024);
+ outFp = new Writable({
+ write(arr, encoding, callback) {
+ for (let i = 0; i < arr.length; i++) {
+ if (outActualLen === outActual.length) {
+ throw new Error('object file too big');
+ }
+
+ outActual[outActualLen++] = arr[i];
+ }
+ callback();
+ },
+ });
+ });
+
+ it('generates object file for minimal program', () => {
+ fp.push('.orig x3000\n');
+ fp.push('halt\n');
+ fp.push('.end\n');
+ fp.push(null);
+
+ return assembler.assembleTo(fp, outFp).then(() => {
+ let exp = new Uint8Array([
+ 0x30,0x00,
+ 0x00,0x01,
+ 0xf0,0x25,
+ ]);
+ expect(outActualLen).toEqual(exp.length);
+ expect(outActual.slice(0, outActualLen).equals(exp)).toBe(true);
+ });
+ });
+
+ it('generates object file for hello world', () => {
+ fp.push('.orig x3000\n')
+ fp.push('lea r0, mystring\n')
+ fp.push('puts\n')
+ fp.push('halt\n')
+ fp.push('\n');
+ fp.push('\n');
+ fp.push('mystring .stringz "hello world!"\n')
+ fp.push('.end\n')
+ fp.push(null)
+
+ return assembler.assembleTo(fp, outFp).then(() => {
+ let exp = new Uint8Array([
+ 0x30,0x00,
+ 0x00,0x10,
+ 0xe0,0x02,
+ 0xf0,0x22,
+ 0xf0,0x25,
+ 0x00,'h'.charCodeAt(0),
+ 0x00,'e'.charCodeAt(0),
+ 0x00,'l'.charCodeAt(0),
+ 0x00,'l'.charCodeAt(0),
+ 0x00,'o'.charCodeAt(0),
+ 0x00,' '.charCodeAt(0),
+ 0x00,'w'.charCodeAt(0),
+ 0x00,'o'.charCodeAt(0),
+ 0x00,'r'.charCodeAt(0),
+ 0x00,'l'.charCodeAt(0),
+ 0x00,'d'.charCodeAt(0),
+ 0x00,'!'.charCodeAt(0),
+ 0x00,0x00,
+ ]);
+ expect(outActualLen).toEqual(exp.length);
+ expect(outActual.slice(0, outActualLen).equals(exp)).toBe(true);
+ });
+ });
- return expect(assembler.assemble(fp)).rejects
- .toThrow('reg, reg');
+ it('generates object file for multiple sections', () => {
+ fp.push('.orig x8000\n');
+ fp.push('lea r0, hi\n');
+ fp.push('puts\n');
+ fp.push('halt\n');
+ fp.push('hi .fill \'h\'\n');
+ fp.push('.fill \'i\'\n');
+ fp.push('.fill 0\n');
+ fp.push('.end\n');
+ fp.push('.orig x3000\n');
+ fp.push('halt\n');
+ fp.push('.end\n');
+ fp.push('.orig x5000\n');
+ fp.push('and r0, r0, 0\n');
+ fp.push('add r0, r0, 1\n');
+ fp.push('halt\n');
+ fp.push('.end\n');
+ fp.push(null);
+
+ return assembler.assembleTo(fp, outFp).then(() => {
+ let exp = new Uint8Array([
+ 0x80,0x00,
+ 0x00,0x06,
+ 0xe0,0x02,
+ 0xf0,0x22,
+ 0xf0,0x25,
+ 0x00,'h'.charCodeAt(0),
+ 0x00,'i'.charCodeAt(0),
+ 0x00,0x00,
+
+ 0x30,0x00,
+ 0x00,0x01,
+ 0xf0,0x25,
+
+ 0x50,0x00,
+ 0x00,0x03,
+ 0x50,0x20,
+ 0x10,0x21,
+ 0xf0,0x25,
+ ]);
+ expect(outActualLen).toEqual(exp.length);
+ expect(outActual.slice(0, outActualLen).equals(exp)).toBe(true);
+ });
+ });
});
});
- describe('assembleTo(inFp, outFp)', () => {
- let outFp: Writable;
- let outActual: Buffer;
- let outActualLen: number;
-
+ describe('lc-2200', () => {
beforeEach(() => {
- outActualLen = 0;
- outActual = Buffer.alloc(1024);
- outFp = new Writable({
- write(arr, encoding, callback) {
- for (let i = 0; i < arr.length; i++) {
- if (outActualLen === outActual.length) {
- throw new Error('object file too big');
- }
+ assembler = new Assembler(getConfig('lc2200'));
+ });
- outActual[outActualLen++] = arr[i];
- }
- callback();
- },
+ describe('parse(fp)', () => {
+ it('parses trivial program', () => {
+ fp.push('halt\n')
+ fp.push(null)
+
+ return expect(assembler.parse(fp)).resolves.toEqual({
+ sections: [
+ {startAddr: 0, instructions: [
+ {kind: 'instr', line: 1, op: 'halt', operands: []},
+ ]},
+ ],
+ labels: {},
+ });
+ });
+
+ it('parses label on its own line', () => {
+ fp.push('fun:\n')
+ fp.push('beq $zero, $zero, fun\n')
+ fp.push(null)
+
+ return expect(assembler.parse(fp)).resolves.toEqual({
+ sections: [
+ {startAddr: 0, instructions: [
+ {kind: 'instr', line: 2, op: 'beq', operands: [
+ {kind: 'reg', prefix: '$', num: 0},
+ {kind: 'reg', prefix: '$', num: 0},
+ {kind: 'label', label: 'fun'},
+ ]},
+ ]},
+ ],
+ labels: {'fun': [0, 0]},
+ });
+ });
+
+ it('preserves label case', () => {
+ fp.push('mYlAbeL: halt\n')
+ fp.push('another-label: halt\n')
+ fp.push('LOUD_LABEL: halt\n')
+ fp.push(null)
+
+ return expect(assembler.parse(fp)).resolves.toEqual({
+ sections: [
+ {startAddr: 0, instructions: [
+ {kind: 'instr', line: 1, op: 'halt', operands: []},
+ {kind: 'instr', line: 2, op: 'halt', operands: []},
+ {kind: 'instr', line: 3, op: 'halt', operands: []},
+ ]},
+ ],
+ labels: {
+ 'mYlAbeL': [0, 0],
+ 'another-label': [0, 1],
+ 'LOUD_LABEL': [0, 2],
+ },
+ });
+ });
+
+ it('errors on duplicate labels', () => {
+ fp.push('mylabel: .word 0\n')
+ fp.push('mylabel: .word 0\n')
+ fp.push(null)
+
+ return expect(assembler.parse(fp)).rejects.toThrow(
+ 'duplicate label mylabel on line 2');
+ });
+
+ it('errors on stray label', () => {
+ fp.push('doh:\n')
+ fp.push(null)
+
+ return expect(assembler.parse(fp)).rejects.toThrow('stray label');
+ });
+
+ it('errors on dangling operand', () => {
+ fp.push('addi $zero, $zero, 34 halt\n');
+ fp.push('halt\n');
+ fp.push(null);
+ return expect(assembler.parse(fp)).rejects.toThrow('halt');
});
});
- it('generates object file for minimal program', () => {
- fp.push('.orig x3000\n');
- fp.push('halt\n');
- fp.push('.end\n');
- fp.push(null);
-
- return assembler.assembleTo(fp, outFp).then(() => {
- let exp = new Uint8Array([
- 0x30,0x00,
- 0x00,0x01,
- 0xf0,0x25,
+ describe('assemble(fp)', () => {
+ it('assembles trivial program', () => {
+ fp.push('halt\n')
+ fp.push(null)
+
+ return expect(assembler.assemble(fp)).resolves.toEqual([
+ {
+ startAddr: 0x0,
+ words: [
+ 0x70000000,
+ ],
+ },
]);
- expect(outActualLen).toEqual(exp.length);
- expect(outActual.slice(0, outActualLen).equals(exp)).toBe(true);
});
- });
- it('generates object file for hello world', () => {
- fp.push('.orig x3000\n')
- fp.push('lea r0, mystring\n')
- fp.push('puts\n')
- fp.push('halt\n')
- fp.push('\n');
- fp.push('\n');
- fp.push('mystring .stringz "hello world!"\n')
- fp.push('.end\n')
- fp.push(null)
-
- return assembler.assembleTo(fp, outFp).then(() => {
- let exp = new Uint8Array([
- 0x30,0x00,
- 0x00,0x10,
- 0xe0,0x02,
- 0xf0,0x22,
- 0xf0,0x25,
- 0x00,'h'.charCodeAt(0),
- 0x00,'e'.charCodeAt(0),
- 0x00,'l'.charCodeAt(0),
- 0x00,'l'.charCodeAt(0),
- 0x00,'o'.charCodeAt(0),
- 0x00,' '.charCodeAt(0),
- 0x00,'w'.charCodeAt(0),
- 0x00,'o'.charCodeAt(0),
- 0x00,'r'.charCodeAt(0),
- 0x00,'l'.charCodeAt(0),
- 0x00,'d'.charCodeAt(0),
- 0x00,'!'.charCodeAt(0),
- 0x00,0x00,
+ it('assembles branch', () => {
+ fp.push('fun:\n')
+ fp.push('beq $zero, $zero, fun\n')
+ fp.push(null)
+
+ return expect(assembler.assemble(fp)).resolves.toEqual([
+ {
+ startAddr: 0x0,
+ words: [
+ 0x500fffff,
+ ],
+ },
]);
- expect(outActualLen).toEqual(exp.length);
- expect(outActual.slice(0, outActualLen).equals(exp)).toBe(true);
});
- });
- it('generates object file for multiple sections', () => {
- fp.push('.orig x8000\n');
- fp.push('lea r0, hi\n');
- fp.push('puts\n');
- fp.push('halt\n');
- fp.push('hi .fill \'h\'\n');
- fp.push('.fill \'i\'\n');
- fp.push('.fill 0\n');
- fp.push('.end\n');
- fp.push('.orig x3000\n');
- fp.push('halt\n');
- fp.push('.end\n');
- fp.push('.orig x5000\n');
- fp.push('and r0, r0, 0\n');
- fp.push('add r0, r0, 1\n');
- fp.push('halt\n');
- fp.push('.end\n');
- fp.push(null);
-
- return assembler.assembleTo(fp, outFp).then(() => {
- let exp = new Uint8Array([
- 0x80,0x00,
- 0x00,0x06,
- 0xe0,0x02,
- 0xf0,0x22,
- 0xf0,0x25,
- 0x00,'h'.charCodeAt(0),
- 0x00,'i'.charCodeAt(0),
- 0x00,0x00,
-
- 0x30,0x00,
- 0x00,0x01,
- 0xf0,0x25,
-
- 0x50,0x00,
- 0x00,0x03,
- 0x50,0x20,
- 0x10,0x21,
- 0xf0,0x25,
+ it('assembles arithmetic instructions', () => {
+ fp.push('nop\n')
+ fp.push('add $t0, $s0, $a0\n')
+ fp.push('addi $s2, $t1, 37\n')
+ fp.push('asdf: nand $v0, $s1, $at\n')
+ fp.push(null)
+
+ return expect(assembler.assemble(fp)).resolves.toEqual([
+ {
+ startAddr: 0x0,
+ words: [
+ 0x00000000,
+ 0x06900003,
+ 0x2b700025,
+ 0x12a00001,
+ ],
+ },
+ ]);
+ });
+
+ it('assembles branch instructions', () => {
+ fp.push('asdf0: beq $v0, $t0, asdf0\n')
+ fp.push('jalr $at, $ra\n')
+ fp.push(null)
+
+ return expect(assembler.assemble(fp)).resolves.toEqual([
+ {
+ startAddr: 0x0,
+ words: [
+ 0x526fffff,
+ 0x61f00000,
+ ],
+ },
+ ]);
+ });
+
+ it('assembles memory instructions', () => {
+ fp.push('lw $t0, 33($v0)\n')
+ fp.push('sw $s0, -5($t2)\n')
+ fp.push(null)
+
+ return expect(assembler.assemble(fp)).resolves.toEqual([
+ {
+ startAddr: 0x0,
+ words: [
+ 0x36200021,
+ 0x498ffffb,
+ ],
+ },
+ ]);
+ });
+
+ it('assembles pseudo-ops', () => {
+ fp.push('.word 0xf0006969\n')
+ fp.push('xd: .word -2\n');
+ fp.push('.word 1056\n');
+ fp.push('.word xd\n');
+ fp.push(null)
+
+ return expect(assembler.assemble(fp)).resolves.toEqual([
+ {
+ startAddr: 0x0,
+ words: [
+ // Sign extends
+ 0xf0006969 & -1,
+ 0xfffffffe & -1,
+ 0x00000420,
+ 0x00000001,
+ ],
+ },
+ ]);
+ });
+
+ it('assembles la', () => {
+ fp.push('la $t0, trevor\n');
+ fp.push('.word 0\n');
+ fp.push('.word 0\n');
+ fp.push('.word 0\n');
+ fp.push('.word 0\n');
+ fp.push('trevor: .word 0x69\n');
+ fp.push(null)
+
+ return expect(assembler.assemble(fp)).resolves.toEqual([
+ {
+ startAddr: 0x0,
+ words: [
+ 0x66600000,
+ 0x26000005,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000069,
+ ],
+ },
+ ]);
+ });
+
+ it('errors on nonexistent label', () => {
+ fp.push('beq $zero, $zero, daddy\n');
+ fp.push('halt\n');
+ fp.push(null);
+
+ return expect(assembler.assemble(fp)).rejects.toThrow('daddy');
+ });
+
+ it('errors on nonexistent label with la', () => {
+ fp.push('la $t0, daddy\n');
+ fp.push('halt\n');
+ fp.push(null);
+
+ return expect(assembler.assemble(fp)).rejects.toThrow('daddy');
+ });
+
+ it('errors on undersized immediate', () => {
+ fp.push('addi $zero, $zero, -1000000\n');
+ fp.push(null);
+
+ return expect(assembler.assemble(fp)).rejects.toThrow('-1000000');
+ });
+
+ it('errors on barely undersized immediate', () => {
+ fp.push('addi $zero, $zero, -524289\n');
+ fp.push(null);
+
+ return expect(assembler.assemble(fp)).rejects.toThrow('-524289');
+ });
+
+ it('assembles almost undersized immediate', () => {
+ fp.push('addi $zero, $zero, -524288\n');
+ fp.push(null);
+
+ return expect(assembler.assemble(fp)).resolves.toEqual([
+ {
+ startAddr: 0x0,
+ words: [
+ 0x20080000,
+ ],
+ },
+ ]);
+ });
+
+ it('errors on oversized immediate', () => {
+ fp.push('addi $zero, $zero, 1000000\n');
+ fp.push(null);
+
+ return expect(assembler.assemble(fp)).rejects.toThrow('1000000');
+ });
+
+ it('errors on barely oversized immediate', () => {
+ fp.push('addi $zero, $zero, 524288\n');
+ fp.push(null);
+
+ return expect(assembler.assemble(fp)).rejects.toThrow('524288');
+ });
+
+ it('assembles almost oversized immediate', () => {
+ fp.push('addi $zero, $zero, 524287\n');
+ fp.push(null);
+
+ return expect(assembler.assemble(fp)).resolves.toEqual([
+ {
+ startAddr: 0x0,
+ words: [
+ 0x2007ffff,
+ ],
+ },
]);
- expect(outActualLen).toEqual(exp.length);
- expect(outActual.slice(0, outActualLen).equals(exp)).toBe(true);
+ });
+
+ // TODO: figure out how to test label offset sizes without
+ // destroying jest
+
+ it('errors on bogus pseudoops with no operands', () => {
+ fp.push('.bob\n');
+ fp.push(null);
+
+ return expect(assembler.assemble(fp)).rejects
+ .toThrow('.bob with no operand');
+ });
+
+ it('errors on bogus pseudoops with operand', () => {
+ fp.push('.bob 3\n');
+ fp.push(null);
+
+ return expect(assembler.assemble(fp)).rejects
+ .toThrow('.bob with int operand');
+ });
+
+ it('errors on bogus instructions without operands', () => {
+ fp.push('water\n');
+ fp.push(null);
+
+ return expect(assembler.assemble(fp)).rejects
+ .toThrow('water with no operands');
+ });
+
+ it('errors on bogus instructions with operands', () => {
+ fp.push('steveo $t0, $t1, $t2\n');
+ fp.push(null);
+
+ return expect(assembler.assemble(fp)).rejects
+ .toThrow('steveo with operands reg, reg, reg');
+ });
+
+ it('errors on barely oversized regnos', () => {
+ fp.push('add $zero, $zero, $16\n');
+ fp.push(null);
+
+ return expect(assembler.assemble(fp)).rejects
+ .toThrow(/16 .* on line 1/i);
+ });
+
+ it('errors on oversized regnos', () => {
+ fp.push('add $zero, $zero, $1000\n');
+ fp.push(null);
+
+ return expect(assembler.assemble(fp)).rejects
+ .toThrow(/1000 .* on line 1/i);
+ });
+
+ it('errors on bogus reg aliases', () => {
+ fp.push('add $zero, $zero, $feces\n');
+ fp.push(null);
+
+ return expect(assembler.assemble(fp)).rejects.toThrow('feces');
+ });
+
+ it('errors on too many operands', () => {
+ fp.push('add $0, $0, 3, $0\n');
+ fp.push(null);
+
+ return expect(assembler.assemble(fp)).rejects
+ .toThrow('reg, reg, int, reg');
+ });
+
+ it('errors on too few operands', () => {
+ fp.push('add $0, $0\n');
+ fp.push(null);
+
+ return expect(assembler.assemble(fp)).rejects
+ .toThrow('reg, reg');
});
});
});
diff --git a/novice/assembler/codegen/base.ts b/novice/assembler/codegen/base.ts
index 74ac800..1f53f10 100644
--- a/novice/assembler/codegen/base.ts
+++ b/novice/assembler/codegen/base.ts
@@ -1,5 +1,5 @@
-import { Assembly, Instruction, InstructionSpec, Isa, PseudoOp,
- Section } from '../../isa';
+import { AliasFields, AliasSpec, Assembly, Instruction, InstructionSpec,
+ Isa, PseudoOp, Section } from '../../isa';
import { AsmContext, OpOperands, OpSpec, PseudoOpSpec } from '../opspec';
import { MachineCodeGenerator, MachineCodeSection } from './codegen';
@@ -47,6 +47,7 @@ class BaseMachineCodeGenerator implements MachineCodeGenerator {
instrIdx: j});
}
+ // TODO: unnecessary copy?
sections[i].words = sections[i].words.concat(words);
}
}
@@ -114,6 +115,33 @@ class BaseMachineCodeGenerator implements MachineCodeGenerator {
return [match.asm(ctx, operands), false];
}
} else {
+ // If it's an alias, expand that mf
+ // TODO: Figure out a more efficient way than this. just a
+ // hashmap right?
+ for (const alias of isa.aliases) {
+ if (this.aliasMatch(instr, alias)) {
+ if (!symbtable) {
+ return [new Array<number>(alias.size), true];
+ } else {
+ const instrs = alias.asm(
+ {pc, line: instr.line, symbtable},
+ this.genAliasFields(instr, alias));
+ let allWords: number[] = [];
+
+ for (const subInstr of instrs) {
+ const newPc = pc + allWords.length;
+ const [words, hasLabel] =
+ this.inflateInstr(isa, opSpec, subInstr, newPc,
+ symbtable);
+ // TODO: unnecessary copy?
+ allWords = allWords.concat(words);
+ }
+
+ return [allWords, false];
+ }
+ }
+ }
+
let match: InstructionSpec|null = null;
// TODO: Figure out a more efficient way than this. just a
@@ -168,6 +196,34 @@ class BaseMachineCodeGenerator implements MachineCodeGenerator {
return operands;
}
+ private genAliasFields(instr: Instruction, alias: AliasSpec): AliasFields {
+ const fields: AliasFields = {regs: {}, ints: {}, labels: {}};
+
+ for (let i = 0; i < instr.operands.length; i++) {
+ const operand = instr.operands[i];
+ const name = alias.operands[i].name;
+
+ switch (operand.kind) {
+ case 'reg':
+ fields.regs[name] = [operand.prefix, operand.num];
+ break;
+
+ case 'int':
+ fields.ints[name] = operand.val;
+ break;
+
+ case 'label':
+ fields.labels[name] = operand.label;
+ break;
+
+ default:
+ const _: never = operand;
+ }
+ }
+
+ return fields;
+ }
+
private genInstrBin(instr: Instruction, isaInstr: InstructionSpec,
pc: number, symbtable: SymbTable, isa: Isa): number {
let bin = 0;
@@ -316,6 +372,17 @@ class BaseMachineCodeGenerator implements MachineCodeGenerator {
ok = ok && o === instr.operands.length && f === isaInstr.fields.length;
return ok;
}
+
+ private aliasMatch(instr: Instruction, alias: AliasSpec): boolean {
+ let ok = instr.op === alias.op &&
+ instr.operands.length === alias.operands.length;
+
+ for (let i = 0; ok && i < instr.operands.length; i++) {
+ ok = ok && instr.operands[i].kind === alias.operands[i].kind;
+ }
+
+ return ok;
+ }
}
export { BaseMachineCodeGenerator };
diff --git a/novice/assembler/opspec/word.ts b/novice/assembler/opspec/word.ts
index ed05de8..4125045 100644
--- a/novice/assembler/opspec/word.ts
+++ b/novice/assembler/opspec/word.ts
@@ -1,3 +1,4 @@
+import { maskTo } from '../../util';
import { AsmContext, oneWord, OpOperands, PseudoOpSpec } from './opspec';
const wordOpSpec: PseudoOpSpec = {
@@ -6,7 +7,7 @@ const wordOpSpec: PseudoOpSpec = {
operands: [{kind: 'int', name: 'num'}],
asm: (ctx: AsmContext, operands: OpOperands) =>
// TODO: complain if too big
- [operands.ints.num & ~(-1 << ctx.isa.mem.addressability)]},
+ [maskTo(operands.ints.num, ctx.isa.mem.addressability)]},
{name: 'word',
operands: [{kind: 'label', name: 'label'}],
diff --git a/novice/assembler/parsers/complx.ts b/novice/assembler/parsers/complx.ts
index 8322a42..78b3331 100644
--- a/novice/assembler/parsers/complx.ts
+++ b/novice/assembler/parsers/complx.ts
@@ -1,7 +1,7 @@
// Parser for complx syntax
import { Assembly, Instruction, IntegerOperand, Isa,
- LabelOperand, PseudoOp, RegisterOperand, Section,
- StringOperand } from '../../isa';
+ isInstruction, LabelOperand, PseudoOp, RegisterOperand,
+ Section, StringOperand } from '../../isa';
import { ParseTable, ParseTree } from '../lr1';
import { Grammar } from './grammar';
import { grammar, NT, T } from './grammars/complx';
@@ -147,10 +147,9 @@ class ComplxParser extends AbstractParser<ParseContext, NT, T> {
return ctx.assembly;
}
- private isInstruction(ctx: ParseContext, op: ParseTree<NT, T>) {
+ private isInstruction(ctx: ParseContext, op: ParseTree<NT, T>): boolean {
const wordVal = this.parseLabel(op).toLowerCase();
- return this.isa.instructions.some(
- instr => instr.op.toLowerCase() === wordVal);
+ return isInstruction(this.isa, wordVal);
}
// Takes either an insr-line or a pseudoop-line, returns its label
diff --git a/novice/assembler/parsers/grammars/lc2200.ts b/novice/assembler/parsers/grammars/lc2200.ts
index 5e6b955..0a21da1 100644
--- a/novice/assembler/parsers/grammars/lc2200.ts
+++ b/novice/assembler/parsers/grammars/lc2200.ts
@@ -9,7 +9,6 @@ const TsObj = {
'int-hex' : '',
'reg' : '',
'pseudoop' : '',
- 'char' : '',
'word' : '',
',' : '',
'(' : '',
@@ -51,7 +50,6 @@ const productions: Production<NT, T>[] = [
{lhs: 'pseudoop-operand', rhs: ['word']},
{lhs: 'pseudoop-operand', rhs: ['int-dec']},
{lhs: 'pseudoop-operand', rhs: ['int-hex']},
- {lhs: 'pseudoop-operand', rhs: ['char']},
];
const goal: NT = 'line';
diff --git a/novice/assembler/parsers/lc2200.ts b/novice/assembler/parsers/lc2200.ts
index c72c776..b6f854b 100644
--- a/novice/assembler/parsers/lc2200.ts
+++ b/novice/assembler/parsers/lc2200.ts
@@ -1,5 +1,5 @@
// Parser for LC-2200 syntax
-import { Assembly, getAliases, Instruction, IntegerOperand, Isa, LabelOperand,
+import { Assembly, getRegAliases, Instruction, IntegerOperand, Isa, LabelOperand,
PseudoOp, RegisterOperand, Section } from '../../isa';
import { ParseTable, ParseTree } from '../lr1';
import { Grammar } from './grammar';
@@ -10,7 +10,7 @@ import table from './tables/lc2200';
interface ParseContext {
instrs: (Instruction|PseudoOp)[];
labels: {[s: string]: number};
- labelQueue: string[];
+ labelQueue: [string, number][];
}
class Lc2200Parser extends AbstractParser<ParseContext, NT, T> {
@@ -33,12 +33,13 @@ class Lc2200Parser extends AbstractParser<ParseContext, NT, T> {
switch (what.token) {
case 'label':
- ctx.labelQueue = ctx.labelQueue.concat(this.parseLabels(what));
+ ctx.labelQueue = ctx.labelQueue.concat(
+ this.parseLabels(what, line.num));
break;
case 'instr':
ctx.labelQueue = ctx.labelQueue.concat(
- this.parseLabels(what.children[0]));
+ this.parseLabels(what.children[0], line.num));
const word = what.children[1];
const op = (word.val as string).toLowerCase();
@@ -46,7 +47,7 @@ class Lc2200Parser extends AbstractParser<ParseContext, NT, T> {
let operands: (RegisterOperand|IntegerOperand|LabelOperand)[];
if (what.children.length === 3) {
const instrOperands = what.children[2];
- operands = this.parseInstrOperands(instrOperands);
+ operands = this.parseInstrOperands(instrOperands, line.num);
} else {
operands = [];
}
@@ -57,12 +58,12 @@ class Lc2200Parser extends AbstractParser<ParseContext, NT, T> {
case 'pseudoop-line':
ctx.labelQueue = ctx.labelQueue.concat(
- this.parseLabels(what.children[0]));
+ this.parseLabels(what.children[0], line.num));
const pseudoop = what.children[1];
const opName = (pseudoop.val as string).slice(1).toLowerCase();
const operand = (what.children.length === 3)
- ? (this.parseOperand(what.children[2]) as (IntegerOperand|LabelOperand))
+ ? (this.parseOperand(what.children[2], line.num) as (IntegerOperand|LabelOperand))
: undefined;
ctx.instrs.push({kind: 'pseudoop', line: line.num, op: opName,
@@ -74,8 +75,9 @@ class Lc2200Parser extends AbstractParser<ParseContext, NT, T> {
protected finish(ctx: ParseContext): Assembly {
if (ctx.labelQueue.length > 0) {
- // TODO: line numbers
- throw new Error(`stray label ${ctx.labelQueue[0]} at end of file`);
+ const [label, line] = ctx.labelQueue[0];
+ throw new Error(`stray label ${label} at end of file on ` +
+ `line ${line}`);
}
const asm: Assembly = {sections: [{startAddr: 0, instructions: ctx.instrs}],
@@ -87,41 +89,46 @@ class Lc2200Parser extends AbstractParser<ParseContext, NT, T> {
private popLabels(ctx: ParseContext): void {
while (ctx.labelQueue.length > 0) {
- const label = ctx.labelQueue.pop() as string;
- // TODO: detect dupe labels
+ const [label, line] = ctx.labelQueue.pop() as [string, number];
+
+ if (ctx.labels.hasOwnProperty(label)) {
+ throw new Error(`duplicate label ${label} on line ${line}`);
+ }
+
ctx.labels[label] = ctx.instrs.length - 1;
}
}
- private parseLabels(label: ParseTree<NT, T>): string[] {
+ private parseLabels(label: ParseTree<NT, T>, line: number):
+ [string, number][] {
if (label.children.length === 0) {
return [];
} else {
// Left recursion
- const more = this.parseLabels(label.children[0]);
- more.push(label.children[1].val as string);
+ const more = this.parseLabels(label.children[0], line);
+ more.push([label.children[1].val as string, line]);
return more;
}
}
- private parseInstrOperands(instrOperands: ParseTree<NT, T>):
+ private parseInstrOperands(instrOperands: ParseTree<NT, T>, line: number):
(RegisterOperand|IntegerOperand|LabelOperand)[] {
const operand = (instrOperands.children.length === 1)
? instrOperands.children[0]
: instrOperands.children[2];
- const parsedOperand = this.parseOperand(operand);
+ const parsedOperand = this.parseOperand(operand, line);
if (instrOperands.children.length === 1) {
return [parsedOperand];
} else {
const instrOperands2 = instrOperands.children[0];
- const operands = this.parseInstrOperands(instrOperands2);
+ const operands = this.parseInstrOperands(instrOperands2, line);
operands.push(parsedOperand);
return operands;
}
}
- private parseOperand(operand: ParseTree<NT, T>):
+ private parseOperand(operand: ParseTree<NT, T>, line: number):
RegisterOperand|IntegerOperand|LabelOperand {
const what = operand.children[0];
let val = what.val as string;
@@ -140,14 +147,21 @@ class Lc2200Parser extends AbstractParser<ParseContext, NT, T> {
val = val.toLowerCase();
const prefix = val.charAt(0);
const alias = val.slice(1);
- const regno = /^\d+$/.test(alias)
- ? parseInt(alias, 10)
- // TODO: handle invalid aliases
- : getAliases(this.isa, prefix)[alias];
- return {kind: 'reg', prefix, num: regno};
- case 'char':
- return {kind: 'int', val: val.charCodeAt(0)};
+ let regno: number;
+ if (/^\d+$/.test(alias)) {
+ regno = parseInt(alias, 10);
+ } else {
+ const aliases = getRegAliases(this.isa, prefix);
+
+ if (!aliases.hasOwnProperty(alias)) {
+ throw new Error(`unknown register alias ${alias} on ` +
+ `line ${line}`);
+ }
+
+ regno = aliases[alias];
+ }
+ return {kind: 'reg', prefix, num: regno};
default:
throw new Error(`unrecognized operand type \`${what.token}'`);
diff --git a/novice/assembler/parsers/tables/lc2200.ts b/novice/assembler/parsers/tables/lc2200.ts
index b13136d..bc0e356 100644
--- a/novice/assembler/parsers/tables/lc2200.ts
+++ b/novice/assembler/parsers/tables/lc2200.ts
@@ -2,5 +2,5 @@
// WARNING: GENERATED CODE by generate-parse-table.sh
import { ParseTable } from '../../lr1';
import { NT, T } from '../grammars/lc2200';
-const table: ParseTable<NT, T> = {"positions":{"(":0,")":1,",":2,":":3,"char":4,"eof":5,"int-dec":6,"int-hex":7,"pseudoop":8,"reg":9,"word":10,"instr":0,"instr-operands":1,"label":2,"line":3,"operand":4,"pseudoop-line":5,"pseudoop-operand":6},"actionTable":[[null,null,null,null,null,{"action":"reduce","production":{"lhs":"label","rhs":[]}},null,null,{"action":"reduce","production":{"lhs":"label","rhs":[]}},null,{"action":"reduce","production":{"lhs":"label","rhs":[]}}],[null,null,null,null,null,{"action":"accept","production":{"lhs":"line","rhs":["label"]}},null,null,{"action":"shift","newState":5},null,{"action":"shift","newState":4}],[null,null,null,null,null,{"action":"accept","production":{"lhs":"line","rhs":["instr"]}},null,null,null,null,null],[null,null,null,null,null,{"action":"accept","production":{"lhs":"line","rhs":["pseudoop-line"]}},null,null,null,null,null],[null,null,null,{"action":"shift","newState":11},null,{"action":"reduce","production":{"lhs":"instr","rhs":["label","word"]}},{"action":"shift","newState":15},{"action":"shift","newState":16},null,{"action":"shift","newState":17},{"action":"shift","newState":14}],[null,null,null,null,{"action":"shift","newState":10},{"action":"reduce","production":{"lhs":"pseudoop-line","rhs":["label","pseudoop"]}},{"action":"shift","newState":8},{"action":"shift","newState":9},null,null,{"action":"shift","newState":7}],[null,null,null,null,null,{"action":"reduce","production":{"lhs":"pseudoop-line","rhs":["label","pseudoop","pseudoop-operand"]}},null,null,null,null,null],[null,null,null,null,null,{"action":"reduce","production":{"lhs":"pseudoop-operand","rhs":["word"]}},null,null,null,null,null],[null,null,null,null,null,{"action":"reduce","production":{"lhs":"pseudoop-operand","rhs":["int-dec"]}},null,null,null,null,null],[null,null,null,null,null,{"action":"reduce","production":{"lhs":"pseudoop-operand","rhs":["int-hex"]}},null,null,null,null,null],[null,null,null,null,null,{"action":"reduce","production":{"lhs":"pseudoop-operand","rhs":["char"]}},null,null,null,null,null],[null,null,null,null,null,{"action":"reduce","production":{"lhs":"label","rhs":["label","word",":"]}},null,null,{"action":"reduce","production":{"lhs":"label","rhs":["label","word",":"]}},null,{"action":"reduce","production":{"lhs":"label","rhs":["label","word",":"]}}],[{"action":"shift","newState":19},null,{"action":"shift","newState":18},null,null,{"action":"reduce","production":{"lhs":"instr","rhs":["label","word","instr-operands"]}},null,null,null,null,null],[{"action":"reduce","production":{"lhs":"instr-operands","rhs":["operand"]}},null,{"action":"reduce","production":{"lhs":"instr-operands","rhs":["operand"]}},null,null,{"action":"reduce","production":{"lhs":"instr-operands","rhs":["operand"]}},null,null,null,null,null],[{"action":"reduce","production":{"lhs":"operand","rhs":["word"]}},null,{"action":"reduce","production":{"lhs":"operand","rhs":["word"]}},null,null,{"action":"reduce","production":{"lhs":"operand","rhs":["word"]}},null,null,null,null,null],[{"action":"reduce","production":{"lhs":"operand","rhs":["int-dec"]}},null,{"action":"reduce","production":{"lhs":"operand","rhs":["int-dec"]}},null,null,{"action":"reduce","production":{"lhs":"operand","rhs":["int-dec"]}},null,null,null,null,null],[{"action":"reduce","production":{"lhs":"operand","rhs":["int-hex"]}},null,{"action":"reduce","production":{"lhs":"operand","rhs":["int-hex"]}},null,null,{"action":"reduce","production":{"lhs":"operand","rhs":["int-hex"]}},null,null,null,null,null],[{"action":"reduce","production":{"lhs":"operand","rhs":["reg"]}},null,{"action":"reduce","production":{"lhs":"operand","rhs":["reg"]}},null,null,{"action":"reduce","production":{"lhs":"operand","rhs":["reg"]}},null,null,null,null,null],[null,null,null,null,null,null,{"action":"shift","newState":15},{"action":"shift","newState":16},null,{"action":"shift","newState":17},{"action":"shift","newState":14}],[null,null,null,null,null,null,{"action":"shift","newState":22},{"action":"shift","newState":23},null,{"action":"shift","newState":24},{"action":"shift","newState":21}],[null,{"action":"shift","newState":25},null,null,null,null,null,null,null,null,null],[null,{"action":"reduce","production":{"lhs":"operand","rhs":["word"]}},null,null,null,null,null,null,null,null,null],[null,{"action":"reduce","production":{"lhs":"operand","rhs":["int-dec"]}},null,null,null,null,null,null,null,null,null],[null,{"action":"reduce","production":{"lhs":"operand","rhs":["int-hex"]}},null,null,null,null,null,null,null,null,null],[null,{"action":"reduce","production":{"lhs":"operand","rhs":["reg"]}},null,null,null,null,null,null,null,null,null],[{"action":"reduce","production":{"lhs":"instr-operands","rhs":["instr-operands","(","operand",")"]}},null,{"action":"reduce","production":{"lhs":"instr-operands","rhs":["instr-operands","(","operand",")"]}},null,null,{"action":"reduce","production":{"lhs":"instr-operands","rhs":["instr-operands","(","operand",")"]}},null,null,null,null,null],[{"action":"reduce","production":{"lhs":"instr-operands","rhs":["instr-operands",",","operand"]}},null,{"action":"reduce","production":{"lhs":"instr-operands","rhs":["instr-operands",",","operand"]}},null,null,{"action":"reduce","production":{"lhs":"instr-operands","rhs":["instr-operands",",","operand"]}},null,null,null,null,null]],"gotoTable":[[2,null,1,null,null,3,null],[null,null,null,null,null,null,null],[null,null,null,null,null,null,null],[null,null,null,null,null,null,null],[null,12,null,null,13,null,null],[null,null,null,null,null,null,6],[null,null,null,null,null,null,null],[null,null,null,null,null,null,null],[null,null,null,null,null,null,null],[null,null,null,null,null,null,null],[null,null,null,null,null,null,null],[null,null,null,null,null,null,null],[null,null,null,null,null,null,null],[null,null,null,null,null,null,null],[null,null,null,null,null,null,null],[null,null,null,null,null,null,null],[null,null,null,null,null,null,null],[null,null,null,null,null,null,null],[null,null,null,null,26,null,null],[null,null,null,null,20,null,null],[null,null,null,null,null,null,null],[null,null,null,null,null,null,null],[null,null,null,null,null,null,null],[null,null,null,null,null,null,null],[null,null,null,null,null,null,null],[null,null,null,null,null,null,null],[null,null,null,null,null,null,null]]};
+const table: ParseTable<NT, T> = {"positions":{"(":0,")":1,",":2,":":3,"eof":4,"int-dec":5,"int-hex":6,"pseudoop":7,"reg":8,"word":9,"instr":0,"instr-operands":1,"label":2,"line":3,"operand":4,"pseudoop-line":5,"pseudoop-operand":6},"actionTable":[[null,null,null,null,{"action":"reduce","production":{"lhs":"label","rhs":[]}},null,null,{"action":"reduce","production":{"lhs":"label","rhs":[]}},null,{"action":"reduce","production":{"lhs":"label","rhs":[]}}],[null,null,null,null,{"action":"accept","production":{"lhs":"line","rhs":["label"]}},null,null,{"action":"shift","newState":5},null,{"action":"shift","newState":4}],[null,null,null,null,{"action":"accept","production":{"lhs":"line","rhs":["instr"]}},null,null,null,null,null],[null,null,null,null,{"action":"accept","production":{"lhs":"line","rhs":["pseudoop-line"]}},null,null,null,null,null],[null,null,null,{"action":"shift","newState":10},{"action":"reduce","production":{"lhs":"instr","rhs":["label","word"]}},{"action":"shift","newState":14},{"action":"shift","newState":15},null,{"action":"shift","newState":16},{"action":"shift","newState":13}],[null,null,null,null,{"action":"reduce","production":{"lhs":"pseudoop-line","rhs":["label","pseudoop"]}},{"action":"shift","newState":8},{"action":"shift","newState":9},null,null,{"action":"shift","newState":7}],[null,null,null,null,{"action":"reduce","production":{"lhs":"pseudoop-line","rhs":["label","pseudoop","pseudoop-operand"]}},null,null,null,null,null],[null,null,null,null,{"action":"reduce","production":{"lhs":"pseudoop-operand","rhs":["word"]}},null,null,null,null,null],[null,null,null,null,{"action":"reduce","production":{"lhs":"pseudoop-operand","rhs":["int-dec"]}},null,null,null,null,null],[null,null,null,null,{"action":"reduce","production":{"lhs":"pseudoop-operand","rhs":["int-hex"]}},null,null,null,null,null],[null,null,null,null,{"action":"reduce","production":{"lhs":"label","rhs":["label","word",":"]}},null,null,{"action":"reduce","production":{"lhs":"label","rhs":["label","word",":"]}},null,{"action":"reduce","production":{"lhs":"label","rhs":["label","word",":"]}}],[{"action":"shift","newState":18},null,{"action":"shift","newState":17},null,{"action":"reduce","production":{"lhs":"instr","rhs":["label","word","instr-operands"]}},null,null,null,null,null],[{"action":"reduce","production":{"lhs":"instr-operands","rhs":["operand"]}},null,{"action":"reduce","production":{"lhs":"instr-operands","rhs":["operand"]}},null,{"action":"reduce","production":{"lhs":"instr-operands","rhs":["operand"]}},null,null,null,null,null],[{"action":"reduce","production":{"lhs":"operand","rhs":["word"]}},null,{"action":"reduce","production":{"lhs":"operand","rhs":["word"]}},null,{"action":"reduce","production":{"lhs":"operand","rhs":["word"]}},null,null,null,null,null],[{"action":"reduce","production":{"lhs":"operand","rhs":["int-dec"]}},null,{"action":"reduce","production":{"lhs":"operand","rhs":["int-dec"]}},null,{"action":"reduce","production":{"lhs":"operand","rhs":["int-dec"]}},null,null,null,null,null],[{"action":"reduce","production":{"lhs":"operand","rhs":["int-hex"]}},null,{"action":"reduce","production":{"lhs":"operand","rhs":["int-hex"]}},null,{"action":"reduce","production":{"lhs":"operand","rhs":["int-hex"]}},null,null,null,null,null],[{"action":"reduce","production":{"lhs":"operand","rhs":["reg"]}},null,{"action":"reduce","production":{"lhs":"operand","rhs":["reg"]}},null,{"action":"reduce","production":{"lhs":"operand","rhs":["reg"]}},null,null,null,null,null],[null,null,null,null,null,{"action":"shift","newState":14},{"action":"shift","newState":15},null,{"action":"shift","newState":16},{"action":"shift","newState":13}],[null,null,null,null,null,{"action":"shift","newState":21},{"action":"shift","newState":22},null,{"action":"shift","newState":23},{"action":"shift","newState":20}],[null,{"action":"shift","newState":24},null,null,null,null,null,null,null,null],[null,{"action":"reduce","production":{"lhs":"operand","rhs":["word"]}},null,null,null,null,null,null,null,null],[null,{"action":"reduce","production":{"lhs":"operand","rhs":["int-dec"]}},null,null,null,null,null,null,null,null],[null,{"action":"reduce","production":{"lhs":"operand","rhs":["int-hex"]}},null,null,null,null,null,null,null,null],[null,{"action":"reduce","production":{"lhs":"operand","rhs":["reg"]}},null,null,null,null,null,null,null,null],[{"action":"reduce","production":{"lhs":"instr-operands","rhs":["instr-operands","(","operand",")"]}},null,{"action":"reduce","production":{"lhs":"instr-operands","rhs":["instr-operands","(","operand",")"]}},null,{"action":"reduce","production":{"lhs":"instr-operands","rhs":["instr-operands","(","operand",")"]}},null,null,null,null,null],[{"action":"reduce","production":{"lhs":"instr-operands","rhs":["instr-operands",",","operand"]}},null,{"action":"reduce","production":{"lhs":"instr-operands","rhs":["instr-operands",",","operand"]}},null,{"action":"reduce","production":{"lhs":"instr-operands","rhs":["instr-operands",",","operand"]}},null,null,null,null,null]],"gotoTable":[[2,null,1,null,null,3,null],[null,null,null,null,null,null,null],[null,null,null,null,null,null,null],[null,null,null,null,null,null,null],[null,11,null,null,12,null,null],[null,null,null,null,null,null,6],[null,null,null,null,null,null,null],[null,null,null,null,null,null,null],[null,null,null,null,null,null,null],[null,null,null,null,null,null,null],[null,null,null,null,null,null,null],[null,null,null,null,null,null,null],[null,null,null,null,null,null,null],[null,null,null,null,null,null,null],[null,null,null,null,null,null,null],[null,null,null,null,null,null,null],[null,null,null,null,null,null,null],[null,null,null,null,25,null,null],[null,null,null,null,19,null,null],[null,null,null,null,null,null,null],[null,null,null,null,null,null,null],[null,null,null,null,null,null,null],[null,null,null,null,null,null,null],[null,null,null,null,null,null,null],[null,null,null,null,null,null,null],[null,null,null,null,null,null,null]]};
export default table;
diff --git a/novice/isa/dummy.ts b/novice/isa/dummy.ts
index 9259642..cad93f9 100644
--- a/novice/isa/dummy.ts
+++ b/novice/isa/dummy.ts
@@ -13,6 +13,7 @@ const DummyIsa: Isa = {
},
regs: [],
instructions: [],
+ aliases: [],
};
export { DummyIsa };
diff --git a/novice/isa/index.ts b/novice/isa/index.ts
index 5113f9e..d77b1b0 100644
--- a/novice/isa/index.ts
+++ b/novice/isa/index.ts
@@ -2,8 +2,8 @@ import { Assembly, Instruction, IntegerOperand, LabelOperand, PseudoOp,
RegisterOperand, Section, StringOperand } from './assembly';
import { DummyIsa } from './dummy';
import { IO, StreamIO } from './io';
-import { Fields, getAliases, InstructionSpec, Isa, Reg,
- regPrefixes } from './isa';
+import { AliasContext, AliasFields, AliasSpec, Fields, getRegAliases,
+ InstructionSpec, Isa, isInstruction, Reg, regPrefixes } from './isa';
import { Lc2200Isa } from './lc2200';
import { Lc3Isa } from './lc3';
import { MachineState, MachineStateUpdate, RegIdentifier } from './state';
@@ -29,6 +29,7 @@ export { getIsa, isas,
// io
IO, StreamIO,
// isa
- Isa, InstructionSpec, Fields, Reg, regPrefixes, getAliases,
+ Isa, InstructionSpec, Fields, Reg, regPrefixes, getRegAliases,
+ isInstruction, AliasContext, AliasFields, AliasSpec,
// state
RegIdentifier, MachineState, MachineStateUpdate };
diff --git a/novice/isa/isa.ts b/novice/isa/isa.ts
index 7cad869..47a29b1 100644
--- a/novice/isa/isa.ts
+++ b/novice/isa/isa.ts
@@ -1,3 +1,4 @@
+import { Instruction, PseudoOp } from './assembly';
import { IO } from './io';
import { MachineState, MachineStateUpdate, RegIdentifier } from './state';
@@ -66,11 +67,36 @@ interface InstructionSpec {
sim: (state: MachineState, io: IO, ir: Fields) => MachineStateUpdate[];
}
+interface AliasOpSpec {
+ kind: 'label'|'reg'|'int';
+ name: string;
+}
+
+interface AliasFields {
+ regs: {[s: string]: [string, number]};
+ ints: {[s: string]: number};
+ labels: {[s: string]: string};
+}
+
+interface AliasContext {
+ pc: number;
+ line: number;
+ symbtable: {[s: string]: number};
+}
+
+interface AliasSpec {
+ op: string;
+ size: number;
+ operands: AliasOpSpec[];
+ asm: (ctx: AliasContext, fields: AliasFields) => (Instruction|PseudoOp)[];
+}
+
interface Isa {
pc: Pc;
mem: Mem;
regs: Reg[];
instructions: InstructionSpec[];
+ aliases: AliasSpec[];
}
function regPrefixes(isa: Isa): string[] {
@@ -83,7 +109,7 @@ function regPrefixes(isa: Isa): string[] {
return result;
}
-function getAliases(isa: Isa, prefix: string): {[s: string]: number} {
+function getRegAliases(isa: Isa, prefix: string): {[s: string]: number} {
for (const reg of isa.regs) {
if (reg.kind === 'reg-range' && reg.prefix === prefix) {
return reg.aliases || {};
@@ -93,4 +119,10 @@ function getAliases(isa: Isa, prefix: string): {[s: string]: number} {
throw new Error(`no such register prefix ${prefix}`);
}
-export { Isa, Fields, InstructionSpec, Reg, regPrefixes, getAliases };
+function isInstruction(isa: Isa, op: string): boolean {
+ return isa.instructions.some(instr => instr.op === op) ||
+ isa.aliases.some(alias => alias.op === op);
+}
+
+export { Isa, Fields, InstructionSpec, Reg, regPrefixes, getRegAliases,
+ isInstruction, AliasContext, AliasFields, AliasSpec };
diff --git a/novice/isa/lc2200.ts b/novice/isa/lc2200.ts
index e8cf841..23deb71 100644
--- a/novice/isa/lc2200.ts
+++ b/novice/isa/lc2200.ts
@@ -1,5 +1,5 @@
import { IO } from './io';
-import { Fields, Isa } from './isa';
+import { AliasContext, AliasFields, Fields, Isa } from './isa';
import { MachineState, MachineStateUpdate, RegIdentifier } from './state';
function regEquals(rx: RegIdentifier, ry: RegIdentifier): boolean {
@@ -79,9 +79,9 @@ const Lc2200Isa: Isa = {
{op: 'lw', fields: [
{kind: 'const', bits: [31, 28], val: 0b0011},
{kind: 'reg', bits: [27, 24], prefix: '$', name: 'rx'},
- {kind: 'reg', bits: [23, 20], prefix: '$', name: 'ry'},
{kind: 'imm', bits: [19, 0], sext: true, label: false,
name: 'imm20'},
+ {kind: 'reg', bits: [23, 20], prefix: '$', name: 'ry'},
],
sim: (state: MachineState, io: IO, ir: Fields) => nukeR0Writes(
[{kind: 'reg', reg: ir.regs.rx,
@@ -91,9 +91,9 @@ const Lc2200Isa: Isa = {
{op: 'sw', fields: [
{kind: 'const', bits: [31, 28], val: 0b0100},
{kind: 'reg', bits: [27, 24], prefix: '$', name: 'rx'},
- {kind: 'reg', bits: [23, 20], prefix: '$', name: 'ry'},
{kind: 'imm', bits: [19, 0], sext: true, label: false,
name: 'imm20'},
+ {kind: 'reg', bits: [23, 20], prefix: '$', name: 'ry'},
],
sim: (state: MachineState, io: IO, ir: Fields) =>
[{kind: 'mem', addr: state.reg(ir.regs.ry) + ir.imms.imm20,
@@ -141,6 +141,33 @@ const Lc2200Isa: Isa = {
sim: (state: MachineState, io: IO, ir: Fields) => [{kind: 'halt'}],
},
],
+
+ aliases: [
+ {op: 'la', operands: [
+ {kind: 'reg', name: 'rx'},
+ {kind: 'label', name: 'label'},
+ ],
+ size: 2,
+ asm: (ctx: AliasContext, fields: AliasFields) => {
+ if (!ctx.symbtable.hasOwnProperty(fields.labels.label)) {
+ throw new Error(`unknown label ${fields.labels.label} to ` +
+ `la on line ${ctx.line}`);
+ }
+
+ return [
+ {kind: 'instr', line: ctx.line, op: 'jalr', operands: [
+ {kind: 'reg', prefix: '$', num: fields.regs.rx[1]},
+ {kind: 'reg', prefix: '$', num: fields.regs.rx[1]},
+ ]},
+ {kind: 'instr', line: ctx.line, op: 'addi', operands: [
+ {kind: 'reg', prefix: '$', num: fields.regs.rx[1]},
+ {kind: 'reg', prefix: '$', num: 0},
+ // TODO: complain if does not exist
+ {kind: 'int', val: ctx.symbtable[fields.labels.label] - ctx.pc - 1},
+ ]},
+ ];
+ }},
+ ],
};
export { Lc2200Isa };
diff --git a/novice/isa/lc3.ts b/novice/isa/lc3.ts
index 4b8afe8..a49d074 100644
--- a/novice/isa/lc3.ts
+++ b/novice/isa/lc3.ts
@@ -1,5 +1,5 @@
import { IO } from './io';
-import { Fields, Isa } from './isa';
+import { AliasContext, AliasFields, Fields, Isa } from './isa';
import { MachineState, MachineStateUpdate } from './state';
function calcCc(val: number): number {
@@ -92,15 +92,6 @@ const Lc3Isa: Isa = {
sim: (state: MachineState, io: IO, ir: Fields) => [],
},
- {op: 'br', fields: [
- {kind: 'const', bits: [15, 9], val: 0b0000111},
- {kind: 'imm', bits: [ 8, 0], sext: true, label: true,
- name: 'pcoffset9'},
- ],
- sim: (state: MachineState, io: IO, ir: Fields) =>
- [{kind: 'pc', where: state.pc + 1 + ir.imms.pcoffset9}],
- },
-
{op: 'brnzp', fields: [
{kind: 'const', bits: [15, 9], val: 0b0000111},
{kind: 'imm', bits: [ 8, 0], sext: true, label: true,
@@ -185,13 +176,6 @@ const Lc3Isa: Isa = {
[{kind: 'pc', where: state.reg(ir.regs.baser)}],
},
- {op: 'ret', fields: [
- {kind: 'const', bits: [15, 0], val: 0b1100000111000000},
- ],
- sim: (state: MachineState, io: IO, ir: Fields) =>
- [{kind: 'pc', where: state.reg(['r', 7])}],
- },
-
{op: 'jsr', fields: [
{kind: 'const', bits: [15, 11], val: 0b01001},
{kind: 'imm', bits: [10, 0], sext: true, label: true,
@@ -360,6 +344,34 @@ const Lc3Isa: Isa = {
sim: (state: MachineState, io: IO, ir: Fields) => [{kind: 'halt'}],
},
],
+ aliases: [
+ {op: 'br', operands: [
+ {kind: 'label', name: 'where'},
+ ],
+ size: 1,
+ asm(ctx: AliasContext, fields: AliasFields) {
+ return [{kind: 'instr', line: ctx.line, op: 'brnzp',
+ operands: [{kind: 'label', label: fields.labels.where}]}];
+ }},
+
+ {op: 'br', operands: [
+ {kind: 'int', name: 'offset'},
+ ],
+ size: 1,
+ asm(ctx: AliasContext, fields: AliasFields) {
+ return [{kind: 'instr', line: ctx.line, op: 'brnzp',
+ operands: [{kind: 'int', val: fields.ints.offset}]}];
+ }},
+
+ {op: 'ret', operands: [],
+ size: 1,
+ asm(ctx: AliasContext, fields: AliasFields) {
+ return [{kind: 'instr', line: ctx.line, op: 'jmp',
+ operands: [{kind: 'reg', prefix: 'r', num: 7}]}];
+ }},
+
+ // TODO: move trap aliases here
+ ],
};
export { Lc3Isa };
diff --git a/novice/util.ts b/novice/util.ts
new file mode 100644
index 0000000..fce40a9
--- /dev/null
+++ b/novice/util.ts
@@ -0,0 +1,5 @@
+function maskTo(val: number, bits: number): number {
+ return val & ((bits === 32) ? -1 : ~(-1 << bits));
+}
+
+export { maskTo };