diff options
author | Austin Adams <git@austinjadams.com> | 2019-01-17 21:12:50 -0500 |
---|---|---|
committer | Austin Adams <git@austinjadams.com> | 2019-01-17 21:12:50 -0500 |
commit | c65b3fb0684e54d0224fad8656f6a543c2b7f734 (patch) | |
tree | ed6095851ab97fa477ade73d809c28ea674c57b9 | |
parent | e47bacace1afe3c2420d68c4cb0ee95805494fcd (diff) | |
download | novice-c65b3fb0684e54d0224fad8656f6a543c2b7f734.tar.gz novice-c65b3fb0684e54d0224fad8656f6a543c2b7f734.tar.xz |
Big refactor: Allow Grammars to configure DFAs
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 }; |