aboutsummaryrefslogtreecommitdiffgithub
diff options
context:
space:
mode:
authorAustin Adams <git@austinjadams.com>2019-01-17 21:12:50 -0500
committerAustin Adams <git@austinjadams.com>2019-01-17 21:12:50 -0500
commitc65b3fb0684e54d0224fad8656f6a543c2b7f734 (patch)
treeed6095851ab97fa477ade73d809c28ea674c57b9
parente47bacace1afe3c2420d68c4cb0ee95805494fcd (diff)
downloadnovice-c65b3fb0684e54d0224fad8656f6a543c2b7f734.tar.gz
novice-c65b3fb0684e54d0224fad8656f6a543c2b7f734.tar.xz
Big refactor: Allow Grammars to configure DFAs
-rwxr-xr-xbootstrap-parse-table.sh3
-rwxr-xr-xgenerate-parse-table.sh3
-rw-r--r--novice/assembler/assembler.test.ts6
-rw-r--r--novice/assembler/assembler.ts5
-rw-r--r--novice/assembler/configs.ts2
-rw-r--r--novice/assembler/dfa/comment.test.ts12
-rw-r--r--novice/assembler/dfa/comment.ts12
-rw-r--r--novice/assembler/dfa/dfa.ts22
-rw-r--r--novice/assembler/dfa/index.ts8
-rw-r--r--novice/assembler/dfa/integer.test.ts146
-rw-r--r--novice/assembler/dfa/integer.ts52
-rw-r--r--novice/assembler/dfa/pseudoop.test.ts8
-rw-r--r--novice/assembler/dfa/pseudoop.ts16
-rw-r--r--novice/assembler/dfa/reg.ts73
-rw-r--r--novice/assembler/dfa/string.test.ts28
-rw-r--r--novice/assembler/dfa/string.ts21
-rw-r--r--novice/assembler/dfa/symbol.test.ts16
-rw-r--r--novice/assembler/dfa/symbol.ts32
-rw-r--r--novice/assembler/dfa/whitespace.test.ts12
-rw-r--r--novice/assembler/dfa/whitespace.ts6
-rw-r--r--novice/assembler/dfa/word.test.ts10
-rw-r--r--novice/assembler/dfa/word.ts16
-rw-r--r--novice/assembler/index.ts9
-rw-r--r--novice/assembler/parsers/complx.ts14
-rw-r--r--novice/assembler/parsers/grammar.ts9
-rw-r--r--novice/assembler/parsers/grammars/complx.ts34
-rw-r--r--novice/assembler/parsers/index.ts3
-rw-r--r--novice/assembler/parsers/parser.test.ts18
-rw-r--r--novice/assembler/parsers/parser.ts38
-rw-r--r--novice/assembler/parsers/tables/complx.ts5
-rw-r--r--novice/assembler/scanner.test.ts98
-rw-r--r--novice/assembler/scanner.ts23
-rw-r--r--novice/cli.test.ts7
-rw-r--r--novice/cli.ts4
-rw-r--r--novice/isa/dummy.ts18
-rw-r--r--novice/isa/index.ts6
-rw-r--r--novice/isa/isa.ts12
37 files changed, 520 insertions, 287 deletions
diff --git a/bootstrap-parse-table.sh b/bootstrap-parse-table.sh
index aa84be4..b0c599a 100755
--- a/bootstrap-parse-table.sh
+++ b/bootstrap-parse-table.sh
@@ -19,8 +19,7 @@ parser=$1
printf '/* tslint:disable */\n'
printf '// WARNING: GENERATED CODE by bootstrap-parse-table.sh\n'
printf "import { ParseTable } from '../../lr1';\n"
- printf "import { T } from '../grammar';\n"
- printf "import { NT } from '../grammars/%s';\n" "$parser"
+ printf "import { NT, T } from '../grammars/%s';\n" "$parser"
printf 'const table: ParseTable<NT, T> = {positions: {}, actionTable: [], gotoTable: []};\n'
printf 'export default table;\n'
} >"novice/assembler/parsers/tables/$parser.ts"
diff --git a/generate-parse-table.sh b/generate-parse-table.sh
index c4009a7..c60f59f 100755
--- a/generate-parse-table.sh
+++ b/generate-parse-table.sh
@@ -19,8 +19,7 @@ parser=$1
printf '/* tslint:disable */\n'
printf '// WARNING: GENERATED CODE by generate-parse-table.sh\n'
printf "import { ParseTable } from '../../lr1';\n"
- printf "import { T } from '../grammar';\n"
- printf "import { NT } from '../grammars/%s';\n" "$parser"
+ printf "import { NT, T } from '../grammars/%s';\n" "$parser"
printf 'const table: ParseTable<NT, T> = '
node novice/main.js tablegen "$parser"
printf ';\n'
diff --git a/novice/assembler/assembler.test.ts b/novice/assembler/assembler.test.ts
index 411fece..af1e9fc 100644
--- a/novice/assembler/assembler.test.ts
+++ b/novice/assembler/assembler.test.ts
@@ -63,7 +63,7 @@ describe('assembler', () => {
sections: [
{startAddr: 0x3000, instructions: [
{kind: 'instr', line: 2, op: 'lea', operands: [
- {kind: 'reg', num: 0},
+ {kind: 'reg', prefix: 'r', num: 0},
{kind: 'label', label: 'mystring'},
]},
{kind: 'instr', line: 3, op: 'puts', operands: []},
@@ -97,8 +97,8 @@ describe('assembler', () => {
]},
{startAddr: 0x4000, instructions: [
{kind: 'instr', line: 6, op: 'and', operands: [
- {kind: 'reg', num: 0},
- {kind: 'reg', num: 0},
+ {kind: 'reg', prefix: 'r', num: 0},
+ {kind: 'reg', prefix: 'r', num: 0},
{kind: 'int', val: -3},
]},
{kind: 'instr', line: 7, op: 'halt', operands: []},
diff --git a/novice/assembler/assembler.ts b/novice/assembler/assembler.ts
index 124b5e4..ce465cd 100644
--- a/novice/assembler/assembler.ts
+++ b/novice/assembler/assembler.ts
@@ -3,7 +3,6 @@ import { Isa } from '../isa';
import { MachineCodeGenerator, MachineCodeSection } from './codegen';
import { PseudoOpSpec } from './opspec';
import { ParsedAssembly, Parser } from './parsers';
-import { Scanner } from './scanner';
import { Serializer } from './serializers';
interface AssemblerConfig {
@@ -15,16 +14,14 @@ interface AssemblerConfig {
}
class Assembler {
- private scanner: Scanner;
private cfg: AssemblerConfig;
public constructor(cfg: AssemblerConfig) {
- this.scanner = new Scanner();
this.cfg = cfg;
}
public async parse(fp: Readable): Promise<ParsedAssembly> {
- return this.cfg.parser.parse(this.cfg.isa, await this.scanner.scan(fp));
+ return await this.cfg.parser.parse(fp);
}
public codegen(asm: ParsedAssembly): MachineCodeSection[] {
diff --git a/novice/assembler/configs.ts b/novice/assembler/configs.ts
index 8843ae2..917c66d 100644
--- a/novice/assembler/configs.ts
+++ b/novice/assembler/configs.ts
@@ -1,4 +1,5 @@
interface Config {
+ lexSpec: string;
parser: string;
isa: string;
opSpec: string;
@@ -7,6 +8,7 @@ interface Config {
const configs: {[s: string]: Config} = {
lc3: {
+ lexSpec: 'complx',
parser: 'complx',
isa: 'lc3',
opSpec: 'complx',
diff --git a/novice/assembler/dfa/comment.test.ts b/novice/assembler/dfa/comment.test.ts
index 6664333..d01bdfa 100644
--- a/novice/assembler/dfa/comment.test.ts
+++ b/novice/assembler/dfa/comment.test.ts
@@ -1,11 +1,13 @@
import CommentDFA from './comment';
import { feedDFA } from './helpers.test';
+type T = 'farzam'|'chen';
+
describe('comment DFA', () => {
- let dfa: CommentDFA;
+ let dfa: CommentDFA<T>;
beforeEach(() => {
- dfa = new CommentDFA();
+ dfa = new CommentDFA<T>(['#', ';']);
});
it('rejects text without ; or #', () => {
@@ -24,20 +26,20 @@ describe('comment DFA', () => {
const len = feedDFA(dfa, '; hello');
expect(dfa.getAcceptingLength()).toBe(len);
expect(dfa.isAlive()).toBe(true);
- expect(dfa.getKind()).toBe(null);
+ expect(dfa.getT()).toBe(null);
});
it('recognizes # comments', () => {
const len = feedDFA(dfa, '# end gaming today');
expect(dfa.getAcceptingLength()).toBe(len);
expect(dfa.isAlive()).toBe(true);
- expect(dfa.getKind()).toBe(null);
+ expect(dfa.getT()).toBe(null);
});
it('recognizes empty comments', () => {
const len = feedDFA(dfa, ';');
expect(dfa.getAcceptingLength()).toBe(len);
expect(dfa.isAlive()).toBe(true);
- expect(dfa.getKind()).toBe(null);
+ expect(dfa.getT()).toBe(null);
});
});
diff --git a/novice/assembler/dfa/comment.ts b/novice/assembler/dfa/comment.ts
index 809dcc4..960e2ac 100644
--- a/novice/assembler/dfa/comment.ts
+++ b/novice/assembler/dfa/comment.ts
@@ -1,18 +1,20 @@
-import { DFA, Kind } from './dfa';
+import { DFA } from './dfa';
enum State {
Start,
Comment,
}
-export default class CommentDFA extends DFA {
+export default class CommentDFA<T> extends DFA<T> {
+ private commentChars: string[];
private state!: State;
private alive!: boolean;
private length!: number;
private acceptingLength!: number;
- public constructor() {
+ public constructor(commentChars: string[]) {
super();
+ this.commentChars = commentChars;
this.reset();
}
@@ -25,7 +27,7 @@ export default class CommentDFA extends DFA {
switch (this.state) {
case State.Start:
- if (c === ';' || c === '#') {
+ if (this.commentChars.indexOf(c) !== -1) {
this.state = State.Comment;
this.acceptingLength = this.length;
} else {
@@ -54,7 +56,7 @@ export default class CommentDFA extends DFA {
this.acceptingLength = 0;
}
- public getKind(): null {
+ public getT(): null {
return null;
}
}
diff --git a/novice/assembler/dfa/dfa.ts b/novice/assembler/dfa/dfa.ts
index e366a23..faa942f 100644
--- a/novice/assembler/dfa/dfa.ts
+++ b/novice/assembler/dfa/dfa.ts
@@ -1,25 +1,9 @@
-const kindsObj = {
- 'int-decimal' : '',
- 'int-hex' : '',
- 'reg' : '',
- 'pseudoop' : '',
- 'string' : '',
- 'char' : '',
- 'word' : '',
- ':' : '',
- '(' : '',
- ')' : '',
- ',' : '',
-};
-type Kind = keyof typeof kindsObj;
-const kinds = new Set(Object.keys(kindsObj) as Kind[]);
-
-abstract class DFA {
+abstract class DFA<T> {
public abstract feed(c: string): void;
public abstract isAlive(): boolean;
public abstract getAcceptingLength(): number;
public abstract reset(): void;
- public abstract getKind(): Kind | null;
+ public abstract getT(): T | null;
}
-export { DFA, Kind, kinds };
+export { DFA };
diff --git a/novice/assembler/dfa/index.ts b/novice/assembler/dfa/index.ts
index 8a2cdc4..7ee7d20 100644
--- a/novice/assembler/dfa/index.ts
+++ b/novice/assembler/dfa/index.ts
@@ -1,12 +1,12 @@
import CommentDFA from './comment';
-import { DFA, Kind, kinds } from './dfa';
+import { DFA } from './dfa';
import IntegerDFA from './integer';
import PseudoOpDFA from './pseudoop';
+import RegDFA from './reg';
import StringDFA from './string';
import SymbolDFA from './symbol';
import WhitespaceDFA from './whitespace';
import WordDFA from './word';
-const dfas = [ WhitespaceDFA, IntegerDFA, WordDFA, PseudoOpDFA, SymbolDFA,
- StringDFA, CommentDFA ];
-export { dfas, DFA, Kind, kinds };
+export { DFA, WhitespaceDFA, IntegerDFA, WordDFA, PseudoOpDFA, RegDFA,
+ SymbolDFA, StringDFA, CommentDFA };
diff --git a/novice/assembler/dfa/integer.test.ts b/novice/assembler/dfa/integer.test.ts
index 39bd36d..45f4954 100644
--- a/novice/assembler/dfa/integer.test.ts
+++ b/novice/assembler/dfa/integer.test.ts
@@ -1,81 +1,105 @@
import IntegerDFA from './integer';
import { feedDFA } from './helpers.test';
+type T = 'int-decimal'|'int-hex';
+
describe('integer DFA', () => {
let dfa: IntegerDFA;
- beforeEach(() => {
- dfa = new IntegerDFA();
- });
+ describe('noHexZeroPrefix = true', () => {
+ beforeEach(() => {
+ dfa = new IntegerDFA<T>({hex: 'int-hex', dec: 'int-decimal'}, true);
+ });
- it('rejects raw hex digits', () => {
- const len = feedDFA(dfa, '', 'abc');
- expect(dfa.getAcceptingLength()).toBe(len);
- expect(dfa.isAlive()).toBe(false);
- });
+ it('rejects raw hex digits', () => {
+ const len = feedDFA(dfa, '', 'abc');
+ expect(dfa.getAcceptingLength()).toBe(len);
+ expect(dfa.isAlive()).toBe(false);
+ });
- it('rejects decimal numbers with hex digits', () => {
- const len = feedDFA(dfa, '123', 'adf');
- expect(dfa.getAcceptingLength()).toBe(len);
- expect(dfa.isAlive()).toBe(false);
- });
+ it('rejects decimal numbers with hex digits', () => {
+ const len = feedDFA(dfa, '123', 'adf');
+ expect(dfa.getAcceptingLength()).toBe(len);
+ expect(dfa.isAlive()).toBe(false);
+ });
- it('rejects invalid hex numbers', () => {
- const len = feedDFA(dfa, 'xbeef', 'y');
- expect(dfa.getAcceptingLength()).toBe(len);
- expect(dfa.isAlive()).toBe(false);
- });
+ it('rejects invalid hex numbers', () => {
+ const len = feedDFA(dfa, 'xbeef', 'y');
+ expect(dfa.getAcceptingLength()).toBe(len);
+ expect(dfa.isAlive()).toBe(false);
+ });
- it('rejects lonely dashes', () => {
- const len = feedDFA(dfa, '', '-');
- expect(dfa.getAcceptingLength()).toBe(len);
- expect(dfa.isAlive()).toBe(true);
- });
+ it('rejects lonely dashes', () => {
+ const len = feedDFA(dfa, '', '-');
+ expect(dfa.getAcceptingLength()).toBe(len);
+ expect(dfa.isAlive()).toBe(true);
+ });
- it('rejects signed hex', () => {
- const len = feedDFA(dfa, '', '-xbeef');
- expect(dfa.getAcceptingLength()).toBe(len);
- expect(dfa.isAlive()).toBe(false);
- });
+ it('rejects signed hex', () => {
+ const len = feedDFA(dfa, '', '-xbeef');
+ expect(dfa.getAcceptingLength()).toBe(len);
+ expect(dfa.isAlive()).toBe(false);
+ });
- it('rejects signed registers', () => {
- const len = feedDFA(dfa, '', 'r-2');
- expect(dfa.getAcceptingLength()).toBe(len);
- expect(dfa.isAlive()).toBe(false);
- });
+ it('recognizes hex literals', () => {
+ const len = feedDFA(dfa, 'xbeef');
+ expect(dfa.getAcceptingLength()).toBe(len);
+ expect(dfa.isAlive()).toBe(true);
+ expect(dfa.getT()).toEqual('int-hex');
+ });
- it('recognizes hex literals', () => {
- const len = feedDFA(dfa, 'xbeef');
- expect(dfa.getAcceptingLength()).toBe(len);
- expect(dfa.isAlive()).toBe(true);
- expect(dfa.getKind()).toEqual('int-hex');
- });
+ it('recognizes capitalized hex literals', () => {
+ const len = feedDFA(dfa, 'XBEeeeF');
+ expect(dfa.getAcceptingLength()).toBe(len);
+ expect(dfa.isAlive()).toBe(true);
+ expect(dfa.getT()).toEqual('int-hex');
+ });
- it('recognizes capitalized hex literals', () => {
- const len = feedDFA(dfa, 'XBEeeeF');
- expect(dfa.getAcceptingLength()).toBe(len);
- expect(dfa.isAlive()).toBe(true);
- expect(dfa.getKind()).toEqual('int-hex');
- });
+ it('recognizes decimal numbers', () => {
+ const len = feedDFA(dfa, '69');
+ expect(dfa.getAcceptingLength()).toBe(len);
+ expect(dfa.isAlive()).toBe(true);
+ expect(dfa.getT()).toEqual('int-decimal');
+ });
- it('recognizes decimal numbers', () => {
- const len = feedDFA(dfa, '69');
- expect(dfa.getAcceptingLength()).toBe(len);
- expect(dfa.isAlive()).toBe(true);
- expect(dfa.getKind()).toEqual('int-decimal');
+ it('recognizes negative decimal numbers', () => {
+ const len = feedDFA(dfa, '-69', 'banana');
+ expect(dfa.getAcceptingLength()).toBe(len);
+ expect(dfa.isAlive()).toBe(false);
+ expect(dfa.getT()).toEqual('int-decimal');
+ });
});
- it('recognizes negative decimal numbers', () => {
- const len = feedDFA(dfa, '-69', 'banana');
- expect(dfa.getAcceptingLength()).toBe(len);
- expect(dfa.isAlive()).toBe(false);
- expect(dfa.getKind()).toEqual('int-decimal');
- });
+ describe('noHexZeroPrefix = false', () => {
+ beforeEach(() => {
+ dfa = new IntegerDFA({hex: 'int-hex', dec: 'int-decimal'});
+ });
+
+ it('rejects hex numbers without leading 0', () => {
+ const len = feedDFA(dfa, '', 'xbeef');
+ expect(dfa.getAcceptingLength()).toBe(len);
+ expect(dfa.isAlive()).toBe(false);
+ });
+
+ it('recognizes hex literals with leading 0', () => {
+ const len = feedDFA(dfa, '0xbeef');
+ expect(dfa.getAcceptingLength()).toBe(len);
+ expect(dfa.isAlive()).toBe(true);
+ expect(dfa.getT()).toEqual('int-hex');
+ });
+
+ it('recognizes lonely 0s', () => {
+ const len = feedDFA(dfa, '0');
+ expect(dfa.getAcceptingLength()).toBe(len);
+ expect(dfa.isAlive()).toBe(true);
+ expect(dfa.getT()).toEqual('int-decimal');
+ });
- it('recognizes registers', () => {
- const len = feedDFA(dfa, 'r0');
- expect(dfa.getAcceptingLength()).toBe(len);
- expect(dfa.isAlive()).toBe(true);
- expect(dfa.getKind()).toEqual('reg');
+ it('recognizes lonely 0x as 0', () => {
+ const len = feedDFA(dfa, '0', 'x');
+ expect(dfa.getAcceptingLength()).toBe(len);
+ expect(dfa.isAlive()).toBe(true);
+ expect(dfa.getT()).toEqual('int-decimal');
+ });
});
});
diff --git a/novice/assembler/dfa/integer.ts b/novice/assembler/dfa/integer.ts
index 5fd84b0..77bc54c 100644
--- a/novice/assembler/dfa/integer.ts
+++ b/novice/assembler/dfa/integer.ts
@@ -1,21 +1,31 @@
-import { DFA, Kind } from './dfa';
+import { DFA } from './dfa';
import { isDecimalDigit, isHexDigit } from './lex';
enum State {
Start,
+ GotZero,
Decimal,
Hexadecimal,
}
-export default class IntegerDFA extends DFA {
+interface IntegerTs<T> {
+ hex: T;
+ dec: T;
+}
+
+export default class IntegerDFA<T> extends DFA<T> {
+ private Ts: IntegerTs<T>;
+ private noHexZeroPrefix: boolean;
private state!: State;
private alive!: boolean;
private length!: number;
private acceptingLength!: number;
- private kind!: Kind;
+ private T!: T;
- public constructor() {
+ public constructor(Ts: IntegerTs<T>, noHexZeroPrefix?: boolean) {
super();
+ this.Ts = Ts;
+ this.noHexZeroPrefix = !!noHexZeroPrefix;
this.reset();
}
@@ -28,25 +38,39 @@ export default class IntegerDFA extends DFA {
switch (this.state) {
case State.Start:
- if (c.toLowerCase() === 'x') {
+ if (this.noHexZeroPrefix && c.toLowerCase() === 'x') {
this.state = State.Hexadecimal;
- this.kind = 'int-hex';
} else if (isDecimalDigit(c)) {
- this.state = State.Decimal;
+ if (!this.noHexZeroPrefix && c === '0') {
+ this.state = State.GotZero;
+ } else {
+ this.state = State.Decimal;
+ }
this.acceptingLength = this.length;
- this.kind = 'int-decimal';
+ this.T = this.Ts.dec;
} else if (c === '-') {
this.state = State.Decimal;
- this.kind = 'int-decimal';
- } else if (c.toLowerCase() === 'r') {
- this.state = State.Decimal;
- this.kind = 'reg';
+ this.T = this.Ts.dec;
+ } else {
+ this.alive = false;
+ }
+ break;
+ // If we're in this state, we know !this.noHexPrefix
+ case State.GotZero:
+ if (c.toLowerCase() === 'x') {
+ // Don't bump acceptingLength because 0x is nonsense
+ // Don't set this.T in case 0 gets accepted on
+ // its own
+ this.state = State.Hexadecimal;
+ } else if (isDecimalDigit(c)) {
+ this.acceptingLength = this.length;
} else {
this.alive = false;
}
break;
case State.Hexadecimal:
if (isHexDigit(c)) {
+ this.T = this.Ts.hex;
this.acceptingLength = this.length;
} else {
this.alive = false;
@@ -77,7 +101,7 @@ export default class IntegerDFA extends DFA {
this.acceptingLength = 0;
}
- public getKind(): Kind {
- return this.kind;
+ public getT(): T {
+ return this.T;
}
}
diff --git a/novice/assembler/dfa/pseudoop.test.ts b/novice/assembler/dfa/pseudoop.test.ts
index 0566ddf..7098375 100644
--- a/novice/assembler/dfa/pseudoop.test.ts
+++ b/novice/assembler/dfa/pseudoop.test.ts
@@ -1,11 +1,13 @@
import PseudoOpDFA from './pseudoop';
import { feedDFA } from './helpers.test';
+type T = 'pseudo-op';
+
describe('pseudo-op DFA', () => {
- let dfa: PseudoOpDFA;
+ let dfa: PseudoOpDFA<T>;
beforeEach(() => {
- dfa = new PseudoOpDFA();
+ dfa = new PseudoOpDFA<T>({pseudoOp: 'pseudo-op'});
});
it('rejects nonsense', () => {
@@ -24,6 +26,6 @@ describe('pseudo-op DFA', () => {
const len = feedDFA(dfa, '.fill', ' xBEEF');
expect(dfa.getAcceptingLength()).toBe(len);
expect(dfa.isAlive()).toBe(false);
- expect(dfa.getKind()).toEqual('pseudoop');
+ expect(dfa.getT()).toEqual('pseudo-op');
});
});
diff --git a/novice/assembler/dfa/pseudoop.ts b/novice/assembler/dfa/pseudoop.ts
index f64f635..39c9191 100644
--- a/novice/assembler/dfa/pseudoop.ts
+++ b/novice/assembler/dfa/pseudoop.ts
@@ -1,4 +1,4 @@
-import { DFA, Kind } from './dfa';
+import { DFA } from './dfa';
import { isWordChar } from './lex';
enum State {
@@ -6,14 +6,20 @@ enum State {
GotDot,
}
-export default class PseudoOpDFA extends DFA {
+interface PseudoOpTs<T> {
+ pseudoOp: T;
+}
+
+export default class PseudoOpDFA<T> extends DFA<T> {
+ private Ts: PseudoOpTs<T>;
private state!: State;
private alive!: boolean;
private length!: number;
private acceptingLength!: number;
- public constructor() {
+ public constructor(Ts: PseudoOpTs<T>) {
super();
+ this.Ts = Ts;
this.reset();
}
@@ -58,7 +64,7 @@ export default class PseudoOpDFA extends DFA {
this.acceptingLength = 0;
}
- public getKind(): Kind {
- return 'pseudoop';
+ public getT(): T {
+ return this.Ts.pseudoOp;
}
}
diff --git a/novice/assembler/dfa/reg.ts b/novice/assembler/dfa/reg.ts
new file mode 100644
index 0000000..dd0f6a1
--- /dev/null
+++ b/novice/assembler/dfa/reg.ts
@@ -0,0 +1,73 @@
+import { DFA } from './dfa';
+import { isDecimalDigit, isWordChar } from './lex';
+
+enum State {
+ Start,
+ Regno,
+}
+
+interface RegTs<T> {
+ reg: T;
+}
+
+export default class RegDFA<T> extends DFA<T> {
+ private Ts: RegTs<T>;
+ private prefixes: string[];
+ private hasAliases: boolean;
+ private state!: State;
+ private alive!: boolean;
+ private length!: number;
+ private acceptingLength!: number;
+
+ public constructor(Ts: RegTs<T>, prefixes: string[], hasAliases?: boolean) {
+ super();
+ this.Ts = Ts;
+ this.prefixes = prefixes;
+ this.hasAliases = !!hasAliases;
+ this.reset();
+ }
+
+ public feed(c: string): void {
+ if (!this.alive) {
+ return;
+ }
+
+ this.length++;
+
+ switch (this.state) {
+ case State.Start:
+ if (this.prefixes.indexOf(c.toLowerCase()) !== -1) {
+ this.state = State.Regno;
+ } else {
+ this.alive = false;
+ }
+ break;
+ case State.Regno:
+ if (isDecimalDigit(c) || this.hasAliases && isWordChar(c)) {
+ this.acceptingLength = this.length;
+ } else {
+ this.alive = false;
+ }
+ break;
+ }
+ }
+
+ public isAlive(): boolean {
+ return this.alive;
+ }
+
+ public getAcceptingLength(): number {
+ return this.acceptingLength;
+ }
+
+ public reset(): void {
+ this.state = State.Start;
+ this.alive = true;
+ this.length = 0;
+ this.acceptingLength = 0;
+ }
+
+ public getT(): T {
+ return this.Ts.reg;
+ }
+}
diff --git a/novice/assembler/dfa/string.test.ts b/novice/assembler/dfa/string.test.ts
index 98fe87b..a8aad96 100644
--- a/novice/assembler/dfa/string.test.ts
+++ b/novice/assembler/dfa/string.test.ts
@@ -1,11 +1,13 @@
import StringDFA from './string';
import { feedDFA } from './helpers.test';
+type T = 'str'|'chr';
+
describe('string DFA', () => {
- let dfa: StringDFA;
+ let dfa: StringDFA<T>;
beforeEach(() => {
- dfa = new StringDFA();
+ dfa = new StringDFA<T>({string: 'str', char: 'chr'});
});
it('rejects raw text', () => {
@@ -37,56 +39,56 @@ describe('string DFA', () => {
const len = feedDFA(dfa, '""', ' ');
expect(dfa.getAcceptingLength()).toBe(len);
expect(dfa.isAlive()).toBe(false);
- expect(dfa.getKind()).toEqual('string');
+ expect(dfa.getT()).toEqual('str');
});
it('dies at closing quote', () => {
const len = feedDFA(dfa, '""');
expect(dfa.getAcceptingLength()).toBe(len);
expect(dfa.isAlive()).toBe(false);
- expect(dfa.getKind()).toEqual('string');
+ expect(dfa.getT()).toEqual('str');
});
it('recognizes nonempty string', () => {
const len = feedDFA(dfa, '"trevor"', ' ');
expect(dfa.getAcceptingLength()).toBe(len);
expect(dfa.isAlive()).toBe(false);
- expect(dfa.getKind()).toEqual('string');
+ expect(dfa.getT()).toEqual('str');
});
it('dies at closing quote of nonempty string', () => {
const len = feedDFA(dfa, '"trevor"');
expect(dfa.getAcceptingLength()).toBe(len);
expect(dfa.isAlive()).toBe(false);
- expect(dfa.getKind()).toEqual('string');
+ expect(dfa.getT()).toEqual('str');
});
it('recognizes escapes', () => {
const len = feedDFA(dfa, '"\\n"');
expect(dfa.getAcceptingLength()).toBe(len);
expect(dfa.isAlive()).toBe(false);
- expect(dfa.getKind()).toEqual('string');
+ expect(dfa.getT()).toEqual('str');
});
it('recognizes intermixed escapes', () => {
const len = feedDFA(dfa, '"hello\\ni am bob"');
expect(dfa.getAcceptingLength()).toBe(len);
expect(dfa.isAlive()).toBe(false);
- expect(dfa.getKind()).toEqual('string');
+ expect(dfa.getT()).toEqual('str');
});
it('recognizes escaped double quote', () => {
const len = feedDFA(dfa, '"hi\\"mom"', ' ');
expect(dfa.getAcceptingLength()).toBe(len);
expect(dfa.isAlive()).toBe(false);
- expect(dfa.getKind()).toEqual('string');
+ expect(dfa.getT()).toEqual('str');
});
it('recognizes lonely escaped double quote', () => {
const len = feedDFA(dfa, '"\\""');
expect(dfa.getAcceptingLength()).toBe(len);
expect(dfa.isAlive()).toBe(false);
- expect(dfa.getKind()).toEqual('string');
+ expect(dfa.getT()).toEqual('str');
});
});
@@ -125,21 +127,21 @@ describe('string DFA', () => {
const len = feedDFA(dfa, "'a'");
expect(dfa.getAcceptingLength()).toBe(len);
expect(dfa.isAlive()).toBe(false);
- expect(dfa.getKind()).toEqual('char');
+ expect(dfa.getT()).toEqual('chr');
});
it('recognizes escapes in character literals', () => {
const len = feedDFA(dfa, "'\\n'");
expect(dfa.getAcceptingLength()).toBe(len);
expect(dfa.isAlive()).toBe(false);
- expect(dfa.getKind()).toEqual('char');
+ expect(dfa.getT()).toEqual('chr');
});
it('recognizes escaped single quote in character literals', () => {
const len = feedDFA(dfa, "'\\''");
expect(dfa.getAcceptingLength()).toBe(len);
expect(dfa.isAlive()).toBe(false);
- expect(dfa.getKind()).toEqual('char');
+ expect(dfa.getT()).toEqual('chr');
});
});
});
diff --git a/novice/assembler/dfa/string.ts b/novice/assembler/dfa/string.ts
index a10af18..41d12b7 100644
--- a/novice/assembler/dfa/string.ts
+++ b/novice/assembler/dfa/string.ts
@@ -1,4 +1,4 @@
-import { DFA, Kind } from './dfa';
+import { DFA } from './dfa';
enum State {
Start,
@@ -9,15 +9,22 @@ enum State {
CharacterGotten,
}
-export default class StringDFA extends DFA {
+interface StringTs<T> {
+ string: T;
+ char: T;
+}
+
+export default class StringDFA<T> extends DFA<T> {
+ private Ts: StringTs<T>;
private state!: State;
private alive!: boolean;
private length!: number;
private acceptingLength!: number;
- private kind!: Kind;
+ private kind!: T;
- public constructor() {
+ public constructor(Ts: StringTs<T>) {
super();
+ this.Ts = Ts;
this.reset();
}
@@ -33,11 +40,11 @@ export default class StringDFA extends DFA {
switch (c) {
case '"':
this.state = State.String;
- this.kind = 'string';
+ this.kind = this.Ts.string;
break;
case "'":
this.state = State.Character;
- this.kind = 'char';
+ this.kind = this.Ts.char;
break;
default:
this.alive = false;
@@ -109,7 +116,7 @@ export default class StringDFA extends DFA {
this.acceptingLength = 0;
}
- public getKind(): Kind {
+ public getT(): T {
return this.kind;
}
}
diff --git a/novice/assembler/dfa/symbol.test.ts b/novice/assembler/dfa/symbol.test.ts
index 96e651b..3d99153 100644
--- a/novice/assembler/dfa/symbol.test.ts
+++ b/novice/assembler/dfa/symbol.test.ts
@@ -1,11 +1,13 @@
import SymbolDFA from './symbol';
import { feedDFA } from './helpers.test';
+type T = ','|'('|')'|':'|'asdfasdf';
+
describe('symbol DFA', () => {
- let dfa: SymbolDFA;
+ let dfa: SymbolDFA<T>;
beforeEach(() => {
- dfa = new SymbolDFA();
+ dfa = new SymbolDFA<T>([',', '(', ')', ':']);
});
it('rejects unknown symbols', () => {
@@ -18,34 +20,34 @@ describe('symbol DFA', () => {
const len = feedDFA(dfa, ',', 'r0');
expect(dfa.getAcceptingLength()).toBe(len);
expect(dfa.isAlive()).toBe(false);
- expect(dfa.getKind()).toEqual(',');
+ expect(dfa.getT()).toEqual(',');
});
it('dies after ,', () => {
const len = feedDFA(dfa, ',');
expect(dfa.getAcceptingLength()).toBe(len);
expect(dfa.isAlive()).toBe(false);
- expect(dfa.getKind()).toEqual(',');
+ expect(dfa.getT()).toEqual(',');
});
it('recognizes (', () => {
const len = feedDFA(dfa, '(', 'r0');
expect(dfa.getAcceptingLength()).toBe(len);
expect(dfa.isAlive()).toBe(false);
- expect(dfa.getKind()).toEqual('(');
+ expect(dfa.getT()).toEqual('(');
});
it('recognizes )', () => {
const len = feedDFA(dfa, ')');
expect(dfa.getAcceptingLength()).toBe(len);
expect(dfa.isAlive()).toBe(false);
- expect(dfa.getKind()).toEqual(')');
+ expect(dfa.getT()).toEqual(')');
});
it('recognizes :', () => {
const len = feedDFA(dfa, ':');
expect(dfa.getAcceptingLength()).toBe(len);
expect(dfa.isAlive()).toBe(false);
- expect(dfa.getKind()).toEqual(':');
+ expect(dfa.getT()).toEqual(':');
});
});
diff --git a/novice/assembler/dfa/symbol.ts b/novice/assembler/dfa/symbol.ts
index afc628a..19fda3b 100644
--- a/novice/assembler/dfa/symbol.ts
+++ b/novice/assembler/dfa/symbol.ts
@@ -1,12 +1,20 @@
-import { DFA, Kind } from './dfa';
+import { DFA } from './dfa';
-export default class SymbolDFA extends DFA {
+export default class SymbolDFA<T> extends DFA<T> {
+ private Ts: {[s: string]: T};
private alive!: boolean;
private acceptingLength!: number;
- private kind!: Kind;
+ private kind!: T;
- public constructor() {
+ public constructor(symbols: T[]) {
super();
+
+ this.Ts = {};
+ // Hack to get around the type checker
+ for (const symbol of symbols) {
+ this.Ts[symbol.toString()] = symbol;
+ }
+
this.reset();
}
@@ -15,16 +23,12 @@ export default class SymbolDFA extends DFA {
return;
}
- switch (c) {
- case '(':
- case ')':
- case ',':
- case ':':
- this.acceptingLength = 1;
- this.kind = c;
- default:
- this.alive = false;
+ if (c in this.Ts) {
+ this.acceptingLength = 1;
+ this.kind = this.Ts[c];
}
+
+ this.alive = false;
}
public isAlive(): boolean {
@@ -40,7 +44,7 @@ export default class SymbolDFA extends DFA {
this.acceptingLength = 0;
}
- public getKind(): Kind {
+ public getT(): T {
return this.kind;
}
}
diff --git a/novice/assembler/dfa/whitespace.test.ts b/novice/assembler/dfa/whitespace.test.ts
index adf9254..352d59d 100644
--- a/novice/assembler/dfa/whitespace.test.ts
+++ b/novice/assembler/dfa/whitespace.test.ts
@@ -1,11 +1,13 @@
import WhitespaceDFA from './whitespace';
import { feedDFA } from './helpers.test';
+type T = 'big'|'daddy';
+
describe('whitespace DFA', () => {
- let dfa: WhitespaceDFA;
+ let dfa: WhitespaceDFA<T>;
beforeEach(() => {
- dfa = new WhitespaceDFA();
+ dfa = new WhitespaceDFA<T>();
});
it('rejects text', () => {
@@ -18,20 +20,20 @@ describe('whitespace DFA', () => {
const len = feedDFA(dfa, ' ');
expect(dfa.getAcceptingLength()).toBe(len);
expect(dfa.isAlive()).toBe(true);
- expect(dfa.getKind()).toBe(null);
+ expect(dfa.getT()).toBe(null);
});
it('recognizes tabs', () => {
const len = feedDFA(dfa, '\t');
expect(dfa.getAcceptingLength()).toBe(len);
expect(dfa.isAlive()).toBe(true);
- expect(dfa.getKind()).toBe(null);
+ expect(dfa.getT()).toBe(null);
});
it('recognizes intermixed whitespace', () => {
const len = feedDFA(dfa, ' \t \t\t \t \t', 'halt');
expect(dfa.getAcceptingLength()).toBe(len);
expect(dfa.isAlive()).toBe(false);
- expect(dfa.getKind()).toBe(null);
+ expect(dfa.getT()).toBe(null);
});
});
diff --git a/novice/assembler/dfa/whitespace.ts b/novice/assembler/dfa/whitespace.ts
index 5662e46..6cf56de 100644
--- a/novice/assembler/dfa/whitespace.ts
+++ b/novice/assembler/dfa/whitespace.ts
@@ -1,6 +1,6 @@
-import { DFA, Kind } from './dfa';
+import { DFA } from './dfa';
-export default class WhitespaceDFA extends DFA {
+export default class WhitespaceDFA<T> extends DFA<T> {
private alive!: boolean;
private length!: number;
private acceptingLength!: number;
@@ -41,7 +41,7 @@ export default class WhitespaceDFA extends DFA {
this.acceptingLength = 0;
}
- public getKind(): null {
+ public getT(): null {
return null;
}
}
diff --git a/novice/assembler/dfa/word.test.ts b/novice/assembler/dfa/word.test.ts
index ceea316..1e1eb6c 100644
--- a/novice/assembler/dfa/word.test.ts
+++ b/novice/assembler/dfa/word.test.ts
@@ -1,11 +1,13 @@
import WordDFA from './word';
import { feedDFA } from './helpers.test';
+type T = 'word';
+
describe('word DFA', () => {
- let dfa: WordDFA;
+ let dfa: WordDFA<T>;
beforeEach(() => {
- dfa = new WordDFA();
+ dfa = new WordDFA<T>({word: 'word'});
});
it('rejects nonsense', () => {
@@ -24,13 +26,13 @@ describe('word DFA', () => {
const len = feedDFA(dfa, 'add', ' r0, r0, r1');
expect(dfa.getAcceptingLength()).toBe(len);
expect(dfa.isAlive()).toBe(false);
- expect(dfa.getKind()).toEqual('word');
+ expect(dfa.getT()).toEqual('word');
});
it('recognizes word with digits', () => {
const len = feedDFA(dfa, 'mylabel2', ': add r0, r0, r1');
expect(dfa.getAcceptingLength()).toBe(len);
expect(dfa.isAlive()).toBe(false);
- expect(dfa.getKind()).toEqual('word');
+ expect(dfa.getT()).toEqual('word');
});
});
diff --git a/novice/assembler/dfa/word.ts b/novice/assembler/dfa/word.ts
index d88d599..8f3751c 100644
--- a/novice/assembler/dfa/word.ts
+++ b/novice/assembler/dfa/word.ts
@@ -1,13 +1,19 @@
-import { DFA, Kind } from './dfa';
+import { DFA } from './dfa';
import { isWordChar } from './lex';
-export default class WordDFA extends DFA {
+interface WordTs<T> {
+ word: T;
+}
+
+export default class WordDFA<T> extends DFA<T> {
+ private Ts: WordTs<T>;
private alive!: boolean;
private length!: number;
private acceptingLength!: number;
- public constructor() {
+ public constructor(Ts: WordTs<T>) {
super();
+ this.Ts = Ts;
this.reset();
}
@@ -40,7 +46,7 @@ export default class WordDFA extends DFA {
this.acceptingLength = 0;
}
- public getKind(): Kind {
- return 'word';
+ public getT(): T {
+ return this.Ts.word;
}
}
diff --git a/novice/assembler/index.ts b/novice/assembler/index.ts
index 42dcfcf..6b9e6e9 100644
--- a/novice/assembler/index.ts
+++ b/novice/assembler/index.ts
@@ -6,12 +6,12 @@ import { opSpecs, PseudoOpSpec } from './opspec';
import { Parser, parsers } from './parsers';
import { Serializer, serializers } from './serializers';
-function getParser(parserName: string): Parser {
+function getParser(parserName: string, isa: Isa): Parser {
if (!parsers.hasOwnProperty(parserName)) {
throw new Error(`no such parser \`${parserName}'\n`);
}
- return new parsers[parserName]();
+ return new parsers[parserName](isa);
}
function getGenerator(): MachineCodeGenerator {
@@ -41,11 +41,12 @@ function getConfig(configName: string): AssemblerConfig {
}
const configNames = configs[configName];
+ const isa = getIsa(configNames.isa);
return {
- parser: getParser(configNames.parser),
+ parser: getParser(configNames.parser, isa),
generator: getGenerator(),
- isa: getIsa(configNames.isa),
+ isa,
opSpec: getOpSpec(configNames.opSpec),
serializer: getSerializer(configNames.serializer),
};
diff --git a/novice/assembler/parsers/complx.ts b/novice/assembler/parsers/complx.ts
index 27f5a7e..7023772 100644
--- a/novice/assembler/parsers/complx.ts
+++ b/novice/assembler/parsers/complx.ts
@@ -9,23 +9,22 @@ import { AbstractParser, Instruction, IntegerOperand, LabelOperand, Line,
import table from './tables/complx';
interface ParseContext {
- isa: Isa;
currentSection: Section|null;
labels: string[];
assembly: ParsedAssembly;
}
-class ComplxParser extends AbstractParser<ParseContext, NT> {
+class ComplxParser extends AbstractParser<ParseContext, NT, T> {
protected getTable(): ParseTable<NT, T> {
return table;
}
- protected getGrammar(): Grammar<NT> {
+ protected getGrammar(): Grammar<NT, T> {
return grammar;
}
- protected initCtx(isa: Isa): ParseContext {
- return {isa, currentSection: null, labels: [],
+ protected initCtx(): ParseContext {
+ return {currentSection: null, labels: [],
assembly: {sections: [], labels: {}}};
}
@@ -150,7 +149,7 @@ class ComplxParser extends AbstractParser<ParseContext, NT> {
private isInstruction(ctx: ParseContext, op: ParseTree<NT, T>) {
const wordVal = this.parseLabel(op).toLowerCase();
- return ctx.isa.instructions.some(
+ return this.isa.instructions.some(
instr => instr.op.toLowerCase() === wordVal);
}
@@ -264,7 +263,8 @@ class ComplxParser extends AbstractParser<ParseContext, NT> {
case 'string':
return {kind: 'string', contents: this.parseString(val.slice(1, -1))};
case 'reg':
- return {kind: 'reg', num: parseInt(val.substring(1), 10)};
+ return {kind: 'reg', prefix: val.charAt(0),
+ num: parseInt(val.substring(1), 10)};
case 'int-decimal':
return {kind: 'int', val: parseInt(val, 10)};
case 'int-hex':
diff --git a/novice/assembler/parsers/grammar.ts b/novice/assembler/parsers/grammar.ts
index 7aae2bd..88d099d 100644
--- a/novice/assembler/parsers/grammar.ts
+++ b/novice/assembler/parsers/grammar.ts
@@ -1,10 +1,13 @@
-import { Kind as T, kinds as Ts } from '../dfa';
+import { Isa } from '../../isa';
+import { DFA } from '../dfa';
import { Production } from '../lr1';
-interface Grammar<NT> {
+interface Grammar<NT, T> {
NTs: Set<NT>;
+ Ts: Set<T>;
productions: Production<NT, T>[];
goal: NT;
+ getDFAs(isa: Isa): DFA<T>[];
}
-export { Grammar, T, Ts };
+export { Grammar };
diff --git a/novice/assembler/parsers/grammars/complx.ts b/novice/assembler/parsers/grammars/complx.ts
index 674edea..1c63e05 100644
--- a/novice/assembler/parsers/grammars/complx.ts
+++ b/novice/assembler/parsers/grammars/complx.ts
@@ -1,5 +1,21 @@
+import { Isa, regPrefixes } from '../../../isa';
+import { CommentDFA, IntegerDFA, PseudoOpDFA, RegDFA, StringDFA, SymbolDFA,
+ WhitespaceDFA, WordDFA } from '../../dfa';
import { Production } from '../../lr1';
-import { Grammar, T } from '../grammar';
+import { Grammar } from '../grammar';
+
+const TsObj = {
+ 'int-decimal' : '',
+ 'int-hex' : '',
+ 'reg' : '',
+ 'pseudoop' : '',
+ 'string' : '',
+ 'char' : '',
+ 'word' : '',
+ ',' : '',
+};
+type T = keyof typeof TsObj;
+const Ts = new Set(Object.keys(TsObj) as T[]);
const NTsObj = {
'line' : '',
@@ -24,7 +40,6 @@ const productions: Production<NT, T>[] = [
{lhs: 'instr', rhs: ['word', 'instr-operands']},
{lhs: 'instr-operands', rhs: ['operand']},
{lhs: 'instr-operands', rhs: ['instr-operands', ',', 'operand']},
- {lhs: 'instr-operands', rhs: ['instr-operands', '(', 'operand', ')']},
{lhs: 'operand', rhs: ['word']},
{lhs: 'operand', rhs: ['int-decimal']},
{lhs: 'operand', rhs: ['int-hex']},
@@ -41,6 +56,19 @@ const productions: Production<NT, T>[] = [
];
const goal: NT = 'line';
-const grammar: Grammar<NT> = { NTs, productions, goal };
+// Make this a function so we don't create unnecessary instances in
+// memory for unused parsers
+const getDFAs = (isa: Isa) => [
+ new CommentDFA<T>(['#', ';']),
+ new IntegerDFA<T>( {hex: 'int-hex', dec: 'int-decimal'}, true),
+ new PseudoOpDFA<T>({pseudoOp: 'pseudoop'}),
+ new RegDFA<T>({reg: 'reg'}, regPrefixes(isa)),
+ new StringDFA<T>({string: 'string', char: 'char'}),
+ new SymbolDFA<T>([',']),
+ new WhitespaceDFA<T>(),
+ new WordDFA<T>({word: 'word'}),
+];
+
+const grammar: Grammar<NT, T> = { NTs, Ts, productions, goal, getDFAs };
export { NT, T, grammar };
diff --git a/novice/assembler/parsers/index.ts b/novice/assembler/parsers/index.ts
index 19a9265..93d02e0 100644
--- a/novice/assembler/parsers/index.ts
+++ b/novice/assembler/parsers/index.ts
@@ -1,7 +1,8 @@
+import { Isa } from '../../isa';
import ComplxParser from './complx';
import { Instruction, ParsedAssembly, Parser, PseudoOp, Section } from './parser';
-const parsers: {[s: string]: new() => Parser} = {
+const parsers: {[s: string]: new(isa: Isa) => Parser} = {
complx: ComplxParser,
};
diff --git a/novice/assembler/parsers/parser.test.ts b/novice/assembler/parsers/parser.test.ts
index 864ce4c..a49f52b 100644
--- a/novice/assembler/parsers/parser.test.ts
+++ b/novice/assembler/parsers/parser.test.ts
@@ -1,5 +1,5 @@
import { AbstractParser, Parser } from './parser';
-import { Grammar, T, Ts } from './grammar';
+import { Grammar } from './grammar';
import { ParseTable, ParseTree } from '../lr1';
import { Line } from '../scanner';
@@ -10,16 +10,20 @@ describe('abstract parser', () => {
describe('genTable()', () => {
// Bogus types
type NT = 'goal';
+ type T = '!';
interface Context {};
let parser: Parser;
// @ts-ignore
- const mockGrammar: Grammar<NT> = {NTs: 'aveni', productions: 'j', goal: 'timothy'};
+ const mockGrammar: Grammar<NT, T> = {NTs: 'aveni', Ts: '1332',
+ // @ts-ignore
+ productions: 'j', goal: 'timothy',
+ getDFAs: () => []};
// @ts-ignore
const mockTableGenerated: ParseTable<NT, T> = {table: 'hi dad'};
const mockGenTable = jest.fn();
- class TestParser extends AbstractParser<Context, NT> {
+ class TestParser extends AbstractParser<Context, NT, T> {
// @ts-ignore
protected getTable(): ParseTable<NT, T> { return null; }
// @ts-ignore
@@ -37,19 +41,19 @@ describe('abstract parser', () => {
});
mockGenTable.mockReturnValue(mockTableGenerated);
- parser = new TestParser();
+ parser = new TestParser(null);
});
afterEach(() => {
// @ts-ignore
- //TableGenerator.mockReset();
- //mockGenTable.mockReset();
+ TableGenerator.mockReset();
+ mockGenTable.mockReset();
});
it('generates a parse table', () => {
expect(parser.genTable()).toEqual(mockTableGenerated);
// @ts-ignore
- expect(TableGenerator.mock.calls).toEqual([['timothy', 'j', 'aveni', Ts]]);
+ expect(TableGenerator.mock.calls).toEqual([['timothy', 'j', 'aveni', '1332']]);
expect(mockGenTable.mock.calls).toEqual([[]]);
});
});
diff --git a/novice/assembler/parsers/parser.ts b/novice/assembler/parsers/parser.ts
index e67dd23..a1c3637 100644
--- a/novice/assembler/parsers/parser.ts
+++ b/novice/assembler/parsers/parser.ts
@@ -3,10 +3,11 @@ import { Isa } from '../../isa';
import { Parser as LR1Parser, ParseTable, ParseTree,
TableGenerator } from '../lr1';
import { Line, Scanner, Token } from '../scanner';
-import { Grammar, T, Ts } from './grammar';
+import { Grammar } from './grammar';
interface RegisterOperand {
kind: 'reg';
+ prefix: string;
num: number;
}
@@ -50,22 +51,37 @@ interface ParsedAssembly {
}
interface Parser {
- parse(isa: Isa, line: Line<T>[]): ParsedAssembly;
+ parse(fp: Readable): Promise<ParsedAssembly>;
// Pass back an object because higher levels of abstraction don't
// care about what exactly is in here, it's just a blob of JSON
genTable(): object;
}
// Performs pass 1 using an LR(1) parser
-abstract class AbstractParser<Ctx, NT> implements Parser {
+abstract class AbstractParser<Ctx, NT, T> implements Parser {
+ protected isa: Isa;
+ private scanner: Scanner<T>;
private parser: LR1Parser<NT, T>;
- public constructor() {
+ public constructor(isa: Isa) {
+ this.isa = isa;
+ this.scanner = new Scanner<T>(this.getGrammar().getDFAs(isa));
this.parser = new LR1Parser<NT, T>(this.getTable());
}
- public parse(isa: Isa, lines: Line<T>[]): ParsedAssembly {
- const ctx = this.initCtx(isa);
+ public async parse(fp: Readable): Promise<ParsedAssembly> {
+ return this.parseLines(await this.scanner.scan(fp));
+ }
+
+ public genTable(): ParseTable<NT, T> {
+ const grammar = this.getGrammar();
+ const tablegen = new TableGenerator(grammar.goal, grammar.productions,
+ grammar.NTs, grammar.Ts);
+ return tablegen.genTable();
+ }
+
+ protected parseLines(lines: Line<T>[]): ParsedAssembly {
+ const ctx = this.initCtx();
for (const line of lines) {
const parseTree = this.parser.parse(line);
@@ -75,15 +91,9 @@ abstract class AbstractParser<Ctx, NT> implements Parser {
return this.finish(ctx);
}
- public genTable(): ParseTable<NT, T> {
- const grammar = this.getGrammar();
- const tablegen = new TableGenerator(grammar.goal, grammar.productions, grammar.NTs, Ts);
- return tablegen.genTable();
- }
-
protected abstract getTable(): ParseTable<NT, T>;
- protected abstract getGrammar(): Grammar<NT>;
- protected abstract initCtx(isa: Isa): Ctx;
+ protected abstract getGrammar(): Grammar<NT, T>;
+ protected abstract initCtx(): Ctx;
protected abstract parseLine(ctx: Ctx, parseTree: ParseTree<NT, T>,
line: Line<T>): void;
protected abstract finish(ctx: Ctx): ParsedAssembly;
diff --git a/novice/assembler/parsers/tables/complx.ts b/novice/assembler/parsers/tables/complx.ts
index 7de34b2..f2ad75a 100644
--- a/novice/assembler/parsers/tables/complx.ts
+++ b/novice/assembler/parsers/tables/complx.ts
@@ -1,7 +1,6 @@
/* tslint:disable */
// WARNING: GENERATED CODE by generate-parse-table.sh
import { ParseTable } from '../../lr1';
-import { T } from '../grammar';
-import { NT } from '../grammars/complx';
-const table: ParseTable<NT, T> = {"positions":{"(":0,")":1,",":2,":":3,"char":4,"eof":5,"int-decimal":6,"int-hex":7,"pseudoop":8,"reg":9,"string":10,"word":11,"instr":0,"instr-line":1,"instr-operands":2,"label":3,"line":4,"operand":5,"pseudoop-call":6,"pseudoop-line":7,"pseudoop-operand":8},"actionTable":[[null,null,null,null,null,null,null,null,{"action":"shift","newState":6},null,null,{"action":"shift","newState":1}],[null,null,null,null,null,{"action":"accept","production":{"lhs":"line","rhs":["word"]}},{"action":"shift","newState":18},{"action":"shift","newState":19},{"action":"shift","newState":6},{"action":"shift","newState":20},null,{"action":"shift","newState":16}],[null,null,null,null,null,{"action":"accept","production":{"lhs":"line","rhs":["instr-line"]}},null,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,null,null,null,{"action":"reduce","production":{"lhs":"instr-line","rhs":["instr"]}},null,null,null,null,null,null],[null,null,null,null,null,{"action":"reduce","production":{"lhs":"pseudoop-line","rhs":["pseudoop-call"]}},null,null,null,null,null,null],[null,null,null,null,{"action":"shift","newState":11},{"action":"reduce","production":{"lhs":"pseudoop-call","rhs":["pseudoop"]}},{"action":"shift","newState":9},{"action":"shift","newState":10},null,null,{"action":"shift","newState":12},{"action":"shift","newState":8}],[null,null,null,null,null,{"action":"reduce","production":{"lhs":"pseudoop-call","rhs":["pseudoop","pseudoop-operand"]}},null,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,null,{"action":"reduce","production":{"lhs":"pseudoop-operand","rhs":["int-decimal"]}},null,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,null,{"action":"reduce","production":{"lhs":"pseudoop-operand","rhs":["char"]}},null,null,null,null,null,null],[null,null,null,null,null,{"action":"reduce","production":{"lhs":"pseudoop-operand","rhs":["string"]}},null,null,null,null,null,null],[null,null,null,null,null,{"action":"reduce","production":{"lhs":"instr-line","rhs":["word","instr"]}},null,null,null,null,null,null],[null,null,null,null,null,{"action":"reduce","production":{"lhs":"pseudoop-line","rhs":["word","pseudoop-call"]}},null,null,null,null,null,null],[{"action":"shift","newState":23},null,{"action":"shift","newState":22},null,null,{"action":"reduce","production":{"lhs":"instr","rhs":["word","instr-operands"]}},null,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"]}},{"action":"shift","newState":18},{"action":"shift","newState":19},null,{"action":"shift","newState":20},null,{"action":"shift","newState":21}],[{"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,null],[{"action":"reduce","production":{"lhs":"operand","rhs":["int-decimal"]}},null,{"action":"reduce","production":{"lhs":"operand","rhs":["int-decimal"]}},null,null,{"action":"reduce","production":{"lhs":"operand","rhs":["int-decimal"]}},null,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,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],[{"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,null],[null,null,null,null,null,null,{"action":"shift","newState":18},{"action":"shift","newState":19},null,{"action":"shift","newState":20},null,{"action":"shift","newState":21}],[null,null,null,null,null,null,{"action":"shift","newState":26},{"action":"shift","newState":27},null,{"action":"shift","newState":28},null,{"action":"shift","newState":25}],[null,{"action":"shift","newState":29},null,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],[null,{"action":"reduce","production":{"lhs":"operand","rhs":["int-decimal"]}},null,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],[null,{"action":"reduce","production":{"lhs":"operand","rhs":["reg"]}},null,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,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,null]],"gotoTable":[[4,2,null,null,null,null,5,3,null],[13,null,15,null,null,17,14,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,7],[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,15,null,null,17,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,30,null,null,null],[null,null,null,null,null,24,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]]};
+import { NT, T } from '../grammars/complx';
+const table: ParseTable<NT, T> = {"positions":{",":0,"char":1,"eof":2,"int-decimal":3,"int-hex":4,"pseudoop":5,"reg":6,"string":7,"word":8,"instr":0,"instr-line":1,"instr-operands":2,"label":3,"line":4,"operand":5,"pseudoop-call":6,"pseudoop-line":7,"pseudoop-operand":8},"actionTable":[[null,null,null,null,null,{"action":"shift","newState":6},null,null,{"action":"shift","newState":1}],[null,null,{"action":"accept","production":{"lhs":"line","rhs":["word"]}},{"action":"shift","newState":18},{"action":"shift","newState":19},{"action":"shift","newState":6},{"action":"shift","newState":20},null,{"action":"shift","newState":16}],[null,null,{"action":"accept","production":{"lhs":"line","rhs":["instr-line"]}},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":"reduce","production":{"lhs":"instr-line","rhs":["instr"]}},null,null,null,null,null,null],[null,null,{"action":"reduce","production":{"lhs":"pseudoop-line","rhs":["pseudoop-call"]}},null,null,null,null,null,null],[null,{"action":"shift","newState":11},{"action":"reduce","production":{"lhs":"pseudoop-call","rhs":["pseudoop"]}},{"action":"shift","newState":9},{"action":"shift","newState":10},null,null,{"action":"shift","newState":12},{"action":"shift","newState":8}],[null,null,{"action":"reduce","production":{"lhs":"pseudoop-call","rhs":["pseudoop","pseudoop-operand"]}},null,null,null,null,null,null],[null,null,{"action":"reduce","production":{"lhs":"pseudoop-operand","rhs":["word"]}},null,null,null,null,null,null],[null,null,{"action":"reduce","production":{"lhs":"pseudoop-operand","rhs":["int-decimal"]}},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,{"action":"reduce","production":{"lhs":"pseudoop-operand","rhs":["char"]}},null,null,null,null,null,null],[null,null,{"action":"reduce","production":{"lhs":"pseudoop-operand","rhs":["string"]}},null,null,null,null,null,null],[null,null,{"action":"reduce","production":{"lhs":"instr-line","rhs":["word","instr"]}},null,null,null,null,null,null],[null,null,{"action":"reduce","production":{"lhs":"pseudoop-line","rhs":["word","pseudoop-call"]}},null,null,null,null,null,null],[{"action":"shift","newState":22},null,{"action":"reduce","production":{"lhs":"instr","rhs":["word","instr-operands"]}},null,null,null,null,null,null],[{"action":"reduce","production":{"lhs":"operand","rhs":["word"]}},null,{"action":"reduce","production":{"lhs":"operand","rhs":["word"]}},{"action":"shift","newState":18},{"action":"shift","newState":19},null,{"action":"shift","newState":20},null,{"action":"shift","newState":21}],[{"action":"reduce","production":{"lhs":"instr-operands","rhs":["operand"]}},null,{"action":"reduce","production":{"lhs":"instr-operands","rhs":["operand"]}},null,null,null,null,null,null],[{"action":"reduce","production":{"lhs":"operand","rhs":["int-decimal"]}},null,{"action":"reduce","production":{"lhs":"operand","rhs":["int-decimal"]}},null,null,null,null,null,null],[{"action":"reduce","production":{"lhs":"operand","rhs":["int-hex"]}},null,{"action":"reduce","production":{"lhs":"operand","rhs":["int-hex"]}},null,null,null,null,null,null],[{"action":"reduce","production":{"lhs":"operand","rhs":["reg"]}},null,{"action":"reduce","production":{"lhs":"operand","rhs":["reg"]}},null,null,null,null,null,null],[{"action":"reduce","production":{"lhs":"operand","rhs":["word"]}},null,{"action":"reduce","production":{"lhs":"operand","rhs":["word"]}},null,null,null,null,null,null],[null,null,null,{"action":"shift","newState":18},{"action":"shift","newState":19},null,{"action":"shift","newState":20},null,{"action":"shift","newState":21}],[{"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,null]],"gotoTable":[[4,2,null,null,null,null,5,3,null],[13,null,15,null,null,17,14,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,7],[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,15,null,null,17,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,23,null,null,null],[null,null,null,null,null,null,null,null,null]]};
export default table;
diff --git a/novice/assembler/scanner.test.ts b/novice/assembler/scanner.test.ts
index 423cfa8..27619d8 100644
--- a/novice/assembler/scanner.test.ts
+++ b/novice/assembler/scanner.test.ts
@@ -1,10 +1,26 @@
import { Readable } from 'stream';
+import { CommentDFA, IntegerDFA, PseudoOpDFA, RegDFA, StringDFA, SymbolDFA,
+ WhitespaceDFA, WordDFA } from './dfa';
import { Scanner } from './scanner';
+type T = 'decimal'|'hex'|'register'|'pseudo-op'|'str'|'chr'|'word'
+ |'('|')'|','|':';
+
describe('scanner', () => {
+ let scanner: Scanner<T>;
let fp: Readable;
beforeEach(() => {
+ scanner = new Scanner<T>([
+ new CommentDFA<T>(['!']),
+ new IntegerDFA<T>({hex: 'hex', dec: 'decimal'}),
+ new PseudoOpDFA<T>({pseudoOp: 'pseudo-op'}),
+ new RegDFA<T>({reg: 'register'}, ['$']),
+ new StringDFA<T>({string: 'str', char: 'chr'}),
+ new SymbolDFA<T>(['(', ')', ',', ':']),
+ new WhitespaceDFA<T>(),
+ new WordDFA<T>({word: 'word'}),
+ ]);
fp = new Readable();
});
@@ -12,25 +28,25 @@ describe('scanner', () => {
fp.push(null);
expect.hasAssertions();
- return new Scanner().scan(fp).then(lines => {
+ return scanner.scan(fp).then(lines => {
expect(lines).toEqual([]);
});
});
it('scans instruction', () => {
- fp.push(' add r0, r0, r1 ');
+ fp.push(' add $0, $0, $1 ');
fp.push(null);
expect.hasAssertions();
- return new Scanner().scan(fp).then(lines => {
+ return scanner.scan(fp).then(lines => {
expect(lines).toEqual([
{num: 1, tokens: [
{col: 4, val: 'add', kind: 'word'},
- {col: 8, val: 'r0', kind: 'reg'},
+ {col: 8, val: '$0', kind: 'register'},
{col: 10, val: ',', kind: ','},
- {col: 12, val: 'r0', kind: 'reg'},
+ {col: 12, val: '$0', kind: 'register'},
{col: 14, val: ',', kind: ','},
- {col: 16, val: 'r1', kind: 'reg'},
+ {col: 16, val: '$1', kind: 'register'},
]},
]);
});
@@ -41,72 +57,72 @@ describe('scanner', () => {
fp.push(null);
expect.hasAssertions();
- return new Scanner().scan(fp).then(lines => {
+ return scanner.scan(fp).then(lines => {
expect(lines).toEqual([
{num: 1, tokens: [
- {col: 1, val: '.fill', kind: 'pseudoop'},
- {col: 7, val: '420', kind: 'int-decimal'},
+ {col: 1, val: '.fill', kind: 'pseudo-op'},
+ {col: 7, val: '420', kind: 'decimal'},
]},
]);
});
});
it('scans hex', () => {
- fp.push('trap x69');
+ fp.push('trap 0x69');
fp.push(null);
expect.hasAssertions();
- return new Scanner().scan(fp).then(lines => {
+ return scanner.scan(fp).then(lines => {
expect(lines).toEqual([
{num: 1, tokens: [
{col: 1, val: 'trap', kind: 'word'},
- {col: 6, val: 'x69', kind: 'int-hex'},
+ {col: 6, val: '0x69', kind: 'hex'},
]},
]);
});
});
it('ignores comment', () => {
- fp.push('; a comment\n');
- fp.push('not r0, r0 ; hi kids\n');
+ fp.push('! a comment\n');
+ fp.push('not $0, $0 ! hi kids\n');
fp.push(null);
expect.hasAssertions();
- return new Scanner().scan(fp).then(lines => {
+ return scanner.scan(fp).then(lines => {
expect(lines).toEqual([
{num: 2, tokens: [
{col: 1, val: 'not', kind: 'word'},
- {col: 5, val: 'r0', kind: 'reg'},
+ {col: 5, val: '$0', kind: 'register'},
{col: 7, val: ',', kind: ','},
- {col: 9, val: 'r0', kind: 'reg'},
+ {col: 9, val: '$0', kind: 'register'},
]},
]);
});
});
it('scans mini program', () => {
- fp.push('.orig x3000\r\n');
- fp.push('add r1, r1, r2\n');
+ fp.push('.orig 0x3000\r\n');
+ fp.push('add $1, $1, $2\n');
fp.push('.end');
fp.push(null);
expect.hasAssertions();
- return new Scanner().scan(fp).then(lines => {
+ return scanner.scan(fp).then(lines => {
expect(lines).toEqual([
{num: 1, tokens: [
- {col: 1, val: '.orig', kind: 'pseudoop'},
- {col: 7, val: 'x3000', kind: 'int-hex'},
+ {col: 1, val: '.orig', kind: 'pseudo-op'},
+ {col: 7, val: '0x3000', kind: 'hex'},
]},
{num: 2, tokens: [
- {col: 1, val: 'add', kind: 'word'},
- {col: 5, val: 'r1', kind: 'reg'},
- {col: 7, val: ',', kind: ','},
- {col: 9, val: 'r1', kind: 'reg'},
- {col: 11, val: ',', kind: ','},
- {col: 13, val: 'r2', kind: 'reg'},
+ {col: 1, val: 'add', kind: 'word'},
+ {col: 5, val: '$1', kind: 'register'},
+ {col: 7, val: ',', kind: ','},
+ {col: 9, val: '$1', kind: 'register'},
+ {col: 11, val: ',', kind: ','},
+ {col: 13, val: '$2', kind: 'register'},
]},
{num: 3, tokens: [
- {col: 1, val: '.end', kind: 'pseudoop'},
+ {col: 1, val: '.end', kind: 'pseudo-op'},
]},
]);
});
@@ -118,7 +134,7 @@ describe('scanner', () => {
fp.push(null);
expect.hasAssertions();
- return new Scanner().scan(fp).then(lines => {
+ return scanner.scan(fp).then(lines => {
expect(lines).toEqual([
{num: 1, tokens: [
{col: 1, val: 'bob', kind: 'word'},
@@ -137,11 +153,11 @@ describe('scanner', () => {
fp.push(null);
expect.hasAssertions();
- return new Scanner().scan(fp).then(lines => {
+ return scanner.scan(fp).then(lines => {
expect(lines).toEqual([
{num: 1, tokens: [
- {col: 1, val: '.stringz', kind: 'pseudoop'},
- {col: 10, val: '"hi\\npatrick"', kind: 'string'},
+ {col: 1, val: '.stringz', kind: 'pseudo-op'},
+ {col: 10, val: '"hi\\npatrick"', kind: 'str'},
]},
]);
});
@@ -152,11 +168,11 @@ describe('scanner', () => {
fp.push(null);
expect.hasAssertions();
- return new Scanner().scan(fp).then(lines => {
+ return scanner.scan(fp).then(lines => {
expect(lines).toEqual([
{num: 1, tokens: [
- {col: 1, val: '.fill', kind: 'pseudoop'},
- {col: 9, val: "'\\n'", kind: 'char'},
+ {col: 1, val: '.fill', kind: 'pseudo-op'},
+ {col: 9, val: "'\\n'", kind: 'chr'},
]},
]);
});
@@ -167,23 +183,23 @@ describe('scanner', () => {
fp.push(null);
expect.hasAssertions();
- return new Scanner().scan(fp).then(lines => {
+ return scanner.scan(fp).then(lines => {
expect(lines).toEqual([
{num: 1, tokens: [
- {col: 1, val: '.fill', kind: 'pseudoop'},
- {col: 7, val: "'\\''", kind: 'char'},
+ {col: 1, val: '.fill', kind: 'pseudo-op'},
+ {col: 7, val: "'\\''", kind: 'chr'},
]},
]);
});
});
it('handles weird chars', () => {
- fp.push("add r1, r1, r2\n");
+ fp.push("add $1, $1, $2\n");
fp.push(" ^ banana");
fp.push(null);
expect.hasAssertions();
- return new Scanner().scan(fp).catch(err => {
+ return scanner.scan(fp).catch(err => {
expect(err.message).toContain('line 2');
expect(err.message).toContain('column 5');
});
diff --git a/novice/assembler/scanner.ts b/novice/assembler/scanner.ts
index b43e6d0..8dab644 100644
--- a/novice/assembler/scanner.ts
+++ b/novice/assembler/scanner.ts
@@ -1,5 +1,5 @@
import { Readable } from 'stream';
-import { DFA, dfas, Kind, kinds } from './dfa';
+import { DFA } from './dfa';
interface Token<T> {
col: number;
@@ -12,22 +12,23 @@ interface Line<T> {
tokens: Token<T>[];
}
-class Scanner {
+class Scanner<T> {
private static readonly EOF = '';
private currentToken!: string;
- private lines!: Line<Kind>[];
- private dfas!: DFA[];
+ private lines!: Line<T>[];
+ private dfas: DFA<T>[];
private newline!: boolean;
private lineNum!: number;
private col!: number;
private lastChar!: string;
private rejectCallback!: (err: Error) => void;
- constructor() {
+ constructor(dfas: DFA<T>[]) {
+ this.dfas = dfas;
this.reset();
}
- public async scan(fp: Readable): Promise<Line<Kind>[]> {
+ public async scan(fp: Readable): Promise<Line<T>[]> {
this.reset();
const endPromise = new Promise((resolve, reject) => {
@@ -46,7 +47,7 @@ class Scanner {
private reset(): void {
this.currentToken = '';
this.lines = [];
- this.dfas = dfas.map(cls => new cls());
+ this.dfas.forEach(dfa => dfa.reset());
// Need to add a new line for the next token (at the beginning
// of the file or after a newline)
this.newline = true;
@@ -84,17 +85,17 @@ class Scanner {
if (best.getAcceptingLength() > 0) {
const tokenLen = best.getAcceptingLength();
- const tokenKind = best.getKind();
+ const tokenT = best.getT();
// Leave out stuff like whitespace (will be null in that
// case)
- if (tokenKind) {
+ if (tokenT) {
if (this.newline) {
this.newline = false;
this.lines.push({num: this.lineNum, tokens: []});
}
const newVal = this.currentToken.substring(0, tokenLen);
- const newToken = {col: this.col, val: newVal, kind: tokenKind};
+ const newToken = {col: this.col, val: newVal, kind: tokenT};
this.lines[this.lines.length - 1].tokens.push(newToken);
}
@@ -135,4 +136,4 @@ class Scanner {
}
}
-export { Line, Token, Kind, kinds, Scanner };
+export { Line, Token, Scanner };
diff --git a/novice/cli.test.ts b/novice/cli.test.ts
index 64e6e13..b5bf8da 100644
--- a/novice/cli.test.ts
+++ b/novice/cli.test.ts
@@ -1,5 +1,6 @@
import { Readable, Writable } from 'stream';
import { Buffer } from 'buffer';
+import { getIsa } from './isa';
import main from './cli';
// Mocks
@@ -150,7 +151,7 @@ describe('cli', () => {
return main(['tablegen', 'farzam'],
stdin, stdout, stderr).then(exitCode => {
// @ts-ignore
- expect(getParser.mock.calls).toEqual([['farzam']]);
+ expect(getParser.mock.calls).toEqual([['farzam', getIsa('dummy')]]);
expect(mockGenTable.mock.calls).toEqual([[]]);
expect(exitCode).toEqual(0);
@@ -166,7 +167,7 @@ describe('cli', () => {
return main(['tablegen', 'gucci'],
stdin, stdout, stderr).then(exitCode => {
// @ts-ignore
- expect(getParser.mock.calls).toEqual([['gucci']]);
+ expect(getParser.mock.calls).toEqual([['gucci', getIsa('dummy')]]);
expect(exitCode).toEqual(1);
expect(stdoutActual).toEqual('');
@@ -185,7 +186,7 @@ describe('cli', () => {
return main(['tablegen', 'guwop'],
stdin, stdout, stderr).then(exitCode => {
// @ts-ignore
- expect(getParser.mock.calls).toEqual([['guwop']]);
+ expect(getParser.mock.calls).toEqual([['guwop', getIsa('dummy')]]);
expect(exitCode).toEqual(1);
expect(stdoutActual).toEqual('');
diff --git a/novice/cli.ts b/novice/cli.ts
index 0c8db0d..5acbf1a 100644
--- a/novice/cli.ts
+++ b/novice/cli.ts
@@ -2,7 +2,7 @@ import { ArgumentParser } from 'argparse';
import * as fs from 'fs';
import { Readable, Writable } from 'stream';
import { Assembler, getConfig, getParser, getSerializer } from './assembler';
-import { StreamIO } from './isa';
+import { getIsa, StreamIO } from './isa';
import { getSimulatorConfig, Simulator } from './simulator';
async function main(argv: string[], stdin: Readable, stdout: Writable,
@@ -121,7 +121,7 @@ function tablegen(parserName: string, stdout: Writable,
stderr: Writable): number {
let table: object;
try {
- const parser = getParser(parserName);
+ const parser = getParser(parserName, getIsa('dummy'));
table = parser.genTable();
} catch (err) {
stderr.write(`error generating LR(1) table: ${err.message}\n`);
diff --git a/novice/isa/dummy.ts b/novice/isa/dummy.ts
new file mode 100644
index 0000000..9259642
--- /dev/null
+++ b/novice/isa/dummy.ts
@@ -0,0 +1,18 @@
+import { Isa } from './isa';
+
+const DummyIsa: Isa = {
+ pc: {
+ increment: 0,
+ resetVector: 0,
+ instrBits: 0,
+ },
+ mem: {
+ word: 0,
+ space: 0,
+ addressability: 0,
+ },
+ regs: [],
+ instructions: [],
+};
+
+export { DummyIsa };
diff --git a/novice/isa/index.ts b/novice/isa/index.ts
index 2dbd31f..638f5b4 100644
--- a/novice/isa/index.ts
+++ b/novice/isa/index.ts
@@ -1,10 +1,12 @@
+import { DummyIsa } from './dummy';
import { IO, StreamIO } from './io';
-import { Fields, Instruction, Isa, Reg } from './isa';
+import { Fields, Instruction, Isa, Reg, regPrefixes } from './isa';
import { Lc2200Isa } from './lc2200';
import { Lc3Isa } from './lc3';
import { MachineState, MachineStateUpdate, RegIdentifier } from './state';
const isas: {[s: string]: Isa} = {
+ dummy: DummyIsa,
lc3: Lc3Isa,
lc2200: Lc2200Isa,
};
@@ -18,4 +20,4 @@ function getIsa(isaName: string): Isa {
}
export { Isa, Instruction, isas, Lc3Isa, MachineState, MachineStateUpdate,
- RegIdentifier, Fields, Reg, getIsa, IO, StreamIO };
+ RegIdentifier, Fields, Reg, getIsa, IO, StreamIO, regPrefixes };
diff --git a/novice/isa/isa.ts b/novice/isa/isa.ts
index 373c96c..0c843a6 100644
--- a/novice/isa/isa.ts
+++ b/novice/isa/isa.ts
@@ -72,4 +72,14 @@ interface Isa {
instructions: Instruction[];
}
-export { Isa, Fields, Instruction, Reg };
+function regPrefixes(isa: Isa): string[] {
+ const result = [];
+ for (const reg of isa.regs) {
+ if (reg.kind === 'reg-range') {
+ result.push(reg.prefix);
+ }
+ }
+ return result;
+}
+
+export { Isa, Fields, Instruction, Reg, regPrefixes };