diff --git a/example.s b/example.s new file mode 100644 index 0000000..946538f --- /dev/null +++ b/example.s @@ -0,0 +1,329 @@ +.inesprg 1 +.ineschr 1 +.inesmap 0 +.inesmir 1 + +.bank 0 +.org $C000 +RESET: +sei +cld +ldx #$40 +stx $4017 +ldx #$FF +txs +inx +stx $2000 +stx $2001 +stx $4010 +vblankwait1: +bit $2002 +bpl vblankwait1 +clrmem: +lda #$00 +sta $0000, x +sta $0100, x +sta $0200, x +sta $0400, x +sta $0500, x +sta $0600, x +sta $0700, x +lda #$FE +sta $0300, x +inx +bne clrmem +vblankwait2: +bit $2002 +bpl vblankwait2 + +lda $2002 +lda #$3F +sta $2006 +lda #$00 +sta $2006 +ldx #$00 +LoadPalettesLoop: +lda PaletteData, x +sta $2007 +inx +cpx #$20 +bne LoadPalettesLoop +LoadSprites: +ldx #$00 +LoadSpritesLoop: +lda sprites, x +sta $0200, x +inx +cpx #$10 +bne LoadSpritesLoop + +LoadBackgrounds: +lda $2002 +lda #$20 +sta $2006 +lda #$00 +sta $2006 +ldx #$00 +LoadBackground1: +lda background1, x +sta $2007 +inx +cpx #$00 +bne LoadBackground1 +LoadBackground2: +lda background2, x +sta $2007 +inx +cpx #$00 +bne LoadBackground2 +LoadBackground3: +lda background3, x +sta $2007 +inx +cpx #$00 +bne LoadBackground3 +LoadBackground4: +lda background4, x +sta $2007 +inx +cpx #$00 +bne LoadBackground4 +LoadAttribute: +lda $2002 +lda #$23 +sta $2006 +lda #$C0 +sta $2006 +ldx #$00 +LoadAttributeLoop: +lda attribute, x +sta $2007 +inx +cpx #$40 +bne LoadAttributeLoop + +lda #%10010000 +sta $2000 +lda #%00011110 +sta $2001 +Forever: +jmp Forever +NMI: + +lda #$00 +sta $2003 +lda #$02 +sta $4014 + +lda #$01 +sta $4016 +lda #$00 +sta $4016 +ReadA: +lda $4016 +and #%00000001 +beq ReadADone +ReadADone: +ReadB: +lda $4016 +and #%00000001 +beq ReadBDone +ReadBDone: +ReadSel: +lda $4016 +and #%00000001 +beq ReadSelDone +ReadSelDone: +ReadStart: +lda $4016 +and #%00000001 +beq ReadStartDone +ReadStartDone: +ReadUp: +lda $4016 +and #%00000001 +beq ReadUpDone +lda $0200 +sec +sbc #$01 +sta $0200 +lda $0204 +sec +sbc #$01 +sta $0204 +lda $0208 +sec +sbc #$01 +sta $0208 +lda $020C +sec +sbc #$01 +sta $020C +ReadUpDone: +ReadDown: +lda $4016 +and #%00000001 +beq ReadDownDone +lda $0200 +clc +adc #$01 +sta $0200 +lda $0204 +clc +adc #$01 +sta $0204 +lda $0208 +clc +adc #$01 +sta $0208 +lda $020C +clc +adc #$01 +sta $020C +ReadDownDone: +ReadLeft: +lda $4016 +and #%00000001 +beq ReadLeftDone +lda $0203 +sec +sbc #$01 +sta $0203 +lda $0207 +sec +sbc #$01 +sta $0207 +lda $020B +sec +sbc #$01 +sta $020B +lda $020F +sec +sbc #$01 +sta $020F +ReadLeftDone: +ReadRight: +lda $4016 +and #%00000001 +beq ReadRightDone +lda $0203 +clc +adc #$01 +sta $0203 +lda $0207 +clc +adc #$01 +sta $0207 +lda $020B +clc +adc #$01 +sta $020B +lda $020F +clc +adc #$01 +sta $020F +ReadRightDone: +lda #%10010000 +sta $2000 +lda #%00011110 +sta $2001 +lda #$00 +sta $2005 +sta $2005 + +rti +.bank 1 +.org $E000 + +PaletteData: +.db $22,$29,$1A,$0F,$22,$36,$17,$0F,$22,$30,$21,$0F,$22,$27,$17,$0F +.db $0F,$16,$27,$18,$22,$02,$38,$3C,$22,$1C,$15,$14,$22,$02,$38,$3C + +sprites: +.db $80, 112, $0, $80 +.db $80, 113, $0, $88 +.db $88, 114, $0, $80 +.db $88, 115, $0, $88 + +background1: +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 +.db $24,$24,$24,$24,$45,$45,$24,$24,$45,$45,$45,$45,$45,$45,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$53,$54,$24,$24 +.db $24,$24,$24,$24,$47,$47,$24,$24,$47,$47,$47,$47,$47,$47,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$55,$56,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 +background2: +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 +.db $24,$24,$24,$24,$45,$45,$24,$24,$45,$45,$45,$45,$45,$45,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$53,$54,$24,$24 +.db $24,$24,$24,$24,$47,$47,$24,$24,$47,$47,$47,$47,$47,$47,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$55,$56,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 +background3: +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 +.db $24,$24,$24,$24,$45,$45,$24,$24,$45,$45,$45,$45,$45,$45,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$53,$54,$24,$24 +.db $24,$24,$24,$24,$47,$47,$24,$24,$47,$47,$47,$47,$47,$47,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$55,$56,$24,$24 +.db 17,14,21,21,24,36,32,24,27,21,13,36,36,36,36,36 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 +background4: +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 +.db $24,$24,$24,$24,$45,$45,$24,$24,$45,$45,$45,$45,$45,$45,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$53,$54,$24,$24 +.db $24,$24,$24,$24,$47,$47,$24,$24,$47,$47,$47,$47,$47,$47,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$55,$56,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 +.db $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 +attribute: +.db %00010001, %00010001, %01010101, %00010001, %00010001, %00010001, %00010001, %01110111 +.db %00010001, %00010001, %01010101, %00010001, %00010001, %00010001, %00010001, %01110111 +.db %00010001, %00010001, %01010101, %00010001, %00010001, %00010001, %00010001, %01110111 +.db %00010001, %00010001, %01010101, %00010001, %00010001, %00010001, %00010001, %01110111 +.db %00010001, %00010001, %01010101, %00010001, %00010001, %00010001, %00010001, %01110111 +.db %00010001, %00010001, %01010101, %00010001, %00010001, %00010001, %00010001, %01110111 +.db %00010001, %00010001, %01010101, %00010001, %00010001, %00010001, %00010001, %01110111 +.db %00010001, %00010001, %01010101, %00010001, %00010001, %00010001, %00010001, %01110111 + +.org $FFFA + +.dw NMI +.dw RESET +.dw 0 +.bank 2 +.org $0000 +.incbin "mario.chr" diff --git a/index.js b/index.js new file mode 100644 index 0000000..dfc449f --- /dev/null +++ b/index.js @@ -0,0 +1,23 @@ +const mona = require('mona') + +const directive = require('./parsers/directive') +const instruction = require('./parsers/instruction') +const label = require('./parsers/label') + +function assembler (input) { + return mona.parse( + mona.collect( + mona.or( + directive(), + instruction(), + label(), + mona.eol() + ) + ), + input + ) +} + +module.exports = assembler +// const result = mona.parse(mona.collect(assembler()), c) +// console.log(result) diff --git a/package.json b/package.json new file mode 100644 index 0000000..61c84b7 --- /dev/null +++ b/package.json @@ -0,0 +1,33 @@ +{ + "name": "parser-6502", + "version": "1.0.0", + "description": "Parser for 6502 assembler.", + "main": "index.js", + "dependencies": { + "mona": "^0.9.1" + }, + "devDependencies": { + "standard": "^8.1.0", + "tap": "^7.1.2" + }, + "scripts": { + "test": "tap test/*.js --cov && standard" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/emkay/parser-6502.git" + }, + "keywords": [ + "parser", + "6502", + "nesly", + "nes", + "assembler" + ], + "author": "Michael Matuzak", + "license": "MIT", + "bugs": { + "url": "https://github.com/emkay/parser-6502/issues" + }, + "homepage": "https://github.com/emkay/parser-6502#readme" +} diff --git a/parsers/directive.js b/parsers/directive.js new file mode 100644 index 0000000..851d6fc --- /dev/null +++ b/parsers/directive.js @@ -0,0 +1,36 @@ +const mona = require('mona') +const parameters = require('./parameters') + +function directiveName () { + return mona.oneOf([ + '.inesprg', + '.ineschr', + '.inesmap', + '.inesmir', + '.bank', + '.org', + '.db', + '.byte', + '.dw', + '.word', + '.incbin', + '.rsset', + '.rs' + ]) +} + +// ::= [ ]* +function directive () { + return mona.sequence((s) => { + const d = s(directiveName()) + const args = s(mona.map((a) => (a[0]), parameters())) + // const nl = s(mona.eol()) + + return mona.value({ + directive: d, + args: args + }) + }) +} + +module.exports = directive diff --git a/parsers/instruction.js b/parsers/instruction.js new file mode 100644 index 0000000..5fc7891 --- /dev/null +++ b/parsers/instruction.js @@ -0,0 +1,37 @@ +const mona = require('mona') +const parameters = require('./parameters') + +const instructions = [ + 'adc', 'and', 'asl', + 'bcc', 'bcs', 'beq', 'bit', 'bmi', 'bne', 'bpl', 'brk', 'bvc', 'bvs', + 'clc', 'cld', 'cli', 'clv', 'cmp', 'cpx', 'cpy', + 'dec', 'dex', 'dey', + 'eor', + 'inc', 'inx', 'iny', + 'jmp', 'jsr', + 'lda', 'ldx', 'ldy', 'lsr', + 'nop', + 'ora', + 'pha', 'php', 'pla', 'plp', + 'rol', 'ror', 'rti', 'rts', + 'sbc', 'sec', 'sed', 'sei', 'sta', 'stx', 'sty', + 'tax', 'tay', 'tsx', 'txa', 'txs', 'tya' +] + +function instructionName () { + return mona.oneOf(instructions) +} + +function instruction () { + return mona.sequence((s) => { + const i = s(instructionName()) + const args = s(mona.map((a) => (a[0]), parameters())) + // const nl = s(mona.eol()) + return mona.value({ + instruction: i, + args: args + }) + }) +} + +module.exports = instruction diff --git a/parsers/label.js b/parsers/label.js new file mode 100644 index 0000000..0aadfd2 --- /dev/null +++ b/parsers/label.js @@ -0,0 +1,15 @@ +const mona = require('mona') + +function label () { + return mona.sequence((s) => { + const label = s(mona.text(mona.alphanum())) + const end = s(mona.string(':')) + const nl = s(mona.eol()) + + return mona.value({ + label: `${label}${end}` + }) + }) +} + +module.exports = label diff --git a/parsers/parameters.js b/parsers/parameters.js new file mode 100644 index 0000000..624c79f --- /dev/null +++ b/parsers/parameters.js @@ -0,0 +1,59 @@ +const mona = require('mona') + +function quotedChar () { + return mona.or(mona.noneOf('"'), + mona.and(mona.string('""'), + mona.value('"'))) +} + +// pulled out, because in the future, this might be more detailed! +// ::= + +function parameter () { + const param = () => { + return mona.or( + mona.join( + mona.string('$'), + mona.alphanum() + ), + mona.join( + mona.string('#'), + mona.string('$'), + mona.alphanum() + ), + mona.join( + mona.string('#'), + mona.string('%'), + mona.alphanum() + ), + mona.join( + mona.string('%'), + mona.alphanum() + ), + mona.between( + mona.string('"'), + mona.string('"'), + mona.text(quotedChar()) + ), + mona.alphanum() + ) + } + + return mona.text(param(), {min: 1}) +} + +function parameters () { + return mona.collect( + mona.and( + mona.spaces(), + mona.split( + parameter(), + mona.or( + mona.and(mona.string(','), mona.spaces()), + mona.string(',') + ) + ) + ) + ) +} + +module.exports = parameters diff --git a/test/assembler.js b/test/assembler.js new file mode 100644 index 0000000..5657efd --- /dev/null +++ b/test/assembler.js @@ -0,0 +1,18 @@ +const tap = require('tap') +const parser = require('..') + +tap.test('should parse the basics', (t) => { + t.plan(1) + const input = '.org $C000\nRESET:\n' + t.deepEqual(parser(input), [ + { + directive: '.org', + args: [ + '$,C000' + ] + }, + { + label: 'RESET:' + } + ]) +}) diff --git a/test/directive.js b/test/directive.js new file mode 100644 index 0000000..61be3a0 --- /dev/null +++ b/test/directive.js @@ -0,0 +1,86 @@ +const tap = require('tap') +const mona = require('mona') +const directiveParser = require('../parsers/directive') + +tap.test('will parse a directive', (t) => { + t.plan(1) + t.deepEqual(mona.parse(directiveParser(), '.inesprg 1'), { + args: [ + '1' + ], + directive: '.inesprg' + }) +}) + +tap.test('will parse a directive with direct address', (t) => { + t.plan(1) + t.deepEqual(mona.parse(directiveParser(), '.inesprg $0000'), { + args: [ + '$,0000' + ], + directive: '.inesprg' + }) +}) + +tap.test('will parse a directive with hex arg', (t) => { + t.plan(1) + t.deepEqual(mona.parse(directiveParser(), '.inesprg #$FE'), { + args: [ + '#,$,FE' + ], + directive: '.inesprg' + }) +}) + +tap.test('will parse a directive with binary arg', (t) => { + t.plan(2) + t.deepEqual(mona.parse(directiveParser(), '.db %00010001'), { + args: [ + '%,00010001' + ], + directive: '.db' + }) + + t.deepEqual(mona.parse(directiveParser(), '.db #%00010001'), { + args: [ + '#,%,00010001' + ], + directive: '.db' + }) +}) + +tap.test('will parse a directive with multiple args', (t) => { + t.plan(1) + t.deepEqual(mona.parse(directiveParser(), '.db %00010001,%00010001,%00010001,%00010001'), { + args: [ + '%,00010001', + '%,00010001', + '%,00010001', + '%,00010001' + ], + directive: '.db' + }) +}) + +tap.test('will parse a directive with multiple args with spaces between them', (t) => { + t.plan(1) + t.deepEqual(mona.parse(directiveParser(), '.db %00010001, %00010001, %00010001, %00010001'), { + args: [ + '%,00010001', + '%,00010001', + '%,00010001', + '%,00010001' + ], + directive: '.db' + }) +}) + +tap.test('will parse a directive with string arg', (t) => { + t.plan(1) + t.deepEqual(mona.parse(directiveParser(), '.incbin "mario.chr"'), { + args: [ + 'mario.chr' + ], + directive: '.incbin' + }) +}) diff --git a/test/instruction.js b/test/instruction.js new file mode 100644 index 0000000..00f504b --- /dev/null +++ b/test/instruction.js @@ -0,0 +1,27 @@ +const tap = require('tap') +const mona = require('mona') +const instructionParser = require('../parsers/instruction') + +tap.test('will parse an instruction', (t) => { + t.plan(1) + t.deepEqual(mona.parse(instructionParser(), 'sei'), { + args: null, + instruction: 'sei' + }) +}) + +tap.test('will parse an instruction with args', (t) => { + t.plan(1) + t.deepEqual(mona.parse(instructionParser(), 'stx $2000'), { + args: ['$,2000'], + instruction: 'stx' + }) +}) + +tap.test('will parse an instruction with multiple args', (t) => { + t.plan(1) + t.deepEqual(mona.parse(instructionParser(), 'lda background3, x'), { + args: ['background3', 'x'], + instruction: 'lda' + }) +}) diff --git a/test/label.js b/test/label.js new file mode 100644 index 0000000..5c00495 --- /dev/null +++ b/test/label.js @@ -0,0 +1,10 @@ +const tap = require('tap') +const mona = require('mona') +const labelParser = require('../parsers/label') + +tap.test('should parse a label', (t) => { + t.plan(2) + t.deepEqual(mona.parse(labelParser(), 'SOMETHING:'), { + label: 'SOMETHING:' + }) +})