gbasm
Version:
A GameBoy Assembler.
1,566 lines (1,242 loc) • 42.2 kB
JavaScript
// Dependencies ---------------------------------------------------------------
// ----------------------------------------------------------------------------
var Lexer = require('./Lexer'),
Expression = require('./Expression'),
Errors = require('../Errors');
// Assembly Code Parser -------------------------------------------------------
// ----------------------------------------------------------------------------
function Parser(file, stream, debug) {
this.file = file;
this.stream = stream;
this.debug = debug;
this._token = null;
this._label = null;
this._mnemonic = null;
}
Parser.prototype = {
parse: function() {
while((this._token = this.stream.next())) {
switch(this._token.type) {
case 'NAME':
this.parseName(this.stream, this._token);
break;
case 'LABEL_GLOBAL_DEF':
this.parseLabel(this.stream, this._token);
break;
case 'LABEL_LOCAL_DEF':
if (!this._label) {
this.error('local label without parent label', null, this._token.index);
}
this.file.addLabel(this._token.value, this._label, this._token.index);
break;
case 'DIRECTIVE':
this.parseDirective(this.stream, this._token);
break;
case 'INSTRUCTION':
this.parseInstruction(this.stream, this._token.value.toLowerCase());
break;
case 'MACRO':
this.parseMacro(this.stream, this._token);
break;
case 'NEWLINE':
break;
case 'EOF':
return;
case 'EXPRESSION':
// Only macro calls are allow at the top level
if (this._token.value instanceof Expression.Call) {
this.file.addMacroCall(this._token, this._token.index);
} else {
this.error(this._token.type, 'NAME, LABEL_GLOBAL_DEF, LABEL_LOCAL_DEF, DIRECTIVE, INSTRUCTION, NEWLINE or EOF', this._token.index);
}
break;
default:
this.error(this._token.type, 'NAME, LABEL_GLOBAL_DEF, LABEL_LOCAL_DEF, DIRECTIVE, INSTRUCTION, NEWLINE or EOF', this._token.index);
break;
}
}
},
parseName: function(s, name) {
var value = null;
if (s.peak('EQU')) {
s.expect('EQU');
if (s.peak('EXPRESSION')) {
value = s.get('EXPRESSION');
} else if (s.peak('NUMBER')) {
value = s.get('NUMBER');
}
if (value !== null) {
this.file.addConstant(name.value, value, name.index);
}
} else if (s.peak('EQUS')) {
s.expect('EQUS');
if (s.peak('EXPRESSION')) {
value = s.get('EXPRESSION');
} else if (s.peak('STRING')) {
value = s.get('STRING');
}
if (value !== null) {
this.file.addConstant(name.value, value, name.index);
}
} else {
this.error(name.type, null, name.index);
}
},
parseLabel: function(s, label) {
// label: DS $ff
if (s.peak('DS')) {
s.expect('DS');
var size;
if (s.peak('NUMBER')) {
size = s.get('NUMBER');
} else {
size = s.get('EXPRESSION');
}
this.file.addVariable(label.value, size, label.index);
// label: DB
} else if (s.peak('DB')) {
s.expect('DB');
this.file.addVariable(label.value, 1, label.index);
// label: DW
} else if (s.peak('DW')) {
s.expect('DW');
this.file.addVariable(label.value, 2, label.index);
// label:
} else {
this._label = this.file.addLabel(label.value, null, label.index);
}
},
parseDirective: function(s, token) {
var name = '';
switch(token.value) {
// INCLUDE "file.gb.s"
case 'INCLUDE':
name = s.get('STRING');
this.file.include(name.value, name.index);
break;
// INCBIN "file.bin"
case 'INCBIN':
name = s.get('STRING');
this.file.addBinaryInclude(name, name.index);
break;
// SECTION "Optional Name",SEGMENT,BANK[ID]
// SECTION "Optional Name",SEGMENT[Offset],BANK[ID]
case 'SECTION':
this.parseSection(s);
break;
// DS 3
// DS 16 "STRING"
case 'DS':
this.parseDS(s);
break;
// DB $ff, $ff, $ff
// DB "String"
case 'DB':
this.parseDB(s);
break;
// DW $ff, $ff, $ff
case 'DW':
this.parseDW(s);
break;
default:
this.error(this._token.value, null, this._token.index);
break;
}
},
parseSection: function(s) {
var segment, name, offset, bank;
// Optional segment name (description)
if (s.peak('STRING')) {
name = s.get('STRING');
s.expect('COMMA');
} else {
name = new Lexer.Token('STRING', 'Unnamed Section');
}
// SECTION ??,ROMX
segment = s.get('NAME');
// SECTION ??,ROMX[??]
if (s.peak('[')) {
s.expect('[');
offset = s.get('NUMBER');
s.expect(']');
// Optional Bank
if (s.peak('COMMA')) {
s.expect('COMMA');
s.expect('BANK');
s.expect('[');
bank = s.get('NUMBER');
s.expect(']');
} else {
bank = null;
}
} else {
offset = null;
if (s.peak('COMMA')) {
s.expect('COMMA');
s.expect('BANK');
s.expect('[');
bank = s.get('NUMBER');
s.expect(']');
} else {
bank = 0;
}
}
this.file.addSection(name, segment, bank, offset);
},
parseDS: function(s) {
var size = s.peak('NUMBER') ? s.expect('NUMBER') : s.expect('EXPRESSION');
if (s.peak('EXPRESSION')) {
this.file.addDataBlock([s.get('EXPRESSION')], true, size, this._token.index);
} else if (s.peak('STRING')) {
this.file.addDataBlock([s.get('STRING')], true, size, this._token.index);
} else {
this.file.addDataBlock([], true, size, this._token.index);
}
},
parseDB: function(s) {
var value, values = [];
if (s.peak('STRING')) {
while((value = s.get('STRING'))) {
var string = value.value.split('');
values.push.apply(values, string.map(function(c, i) {
return new Lexer.Token('NUMBER', c.charCodeAt(0), value.index + i);
}));
if (s.peak('COMMA')) {
s.expect('COMMA');
} else {
this.file.addDataBlock(values, true, null, this._token.index);
values = [];
break;
}
}
} else {
while((value = s.get('NUMBER_8BIT'))) {
values.push(value);
if (s.peak('COMMA')) {
s.expect('COMMA');
} else {
this.file.addDataBlock(values, true, null, this._token.index);
values = [];
break;
}
}
}
},
parseDW: function(s) {
var value, values = [];
while((value = s.get('NUMBER_16BIT'))) {
values.push(value);
if (s.peak('COMMA')) {
s.expect('COMMA');
} else {
this.file.addDataBlock(values, false, null, this._token.index);
values = [];
break;
}
}
},
// MACRO name(arg1, ...)
// ...
// ENDMACRO
parseMacro: function(s, token) {
var args = [],
tokens = [];
// Parse arguments
s.expect('LPAREN');
if (!s.peak('RPAREN')) {
while(true) {
args.push(s.get('MACRO_ARG'));
if (!s.peak('COMMA')) {
break;
} else {
s.expect('COMMA');
}
}
}
s.expect('RPAREN');
// Parse Body
if (!s.peak('ENDMACRO')) {
while(true) {
if (s.peak('ENDMACRO')) {
break;
} else {
tokens.push(s.next());
}
}
// Trim leading / trailing newlines
var start = -1,
end = -1,
length = tokens.length;
for(var i = 0; i < length; i++) {
if (start === -1 && tokens[i].type !== 'NEWLINE') {
start = i;
}
if (end === -1 && tokens[length - 1 - i].type !== 'NEWLINE') {
end = length - i;
}
}
tokens = tokens.slice(start, end);
}
tokens.push(new Lexer.Token('EOF', '', 0, this.file));
s.expect('ENDMACRO');
this.file.addMacro(token.value, args, tokens, token.index);
},
parseInstruction: function(s, mnemonic) {
var arg = null,
flag = null;
this._mnemonic = mnemonic;
switch(mnemonic) {
// 8bit / 16bit loads
case 'ld':
this.parseLdInstruction(s);
break;
case 'ldh':
// ldh [c],a
if (s.peak('[')) {
s.expect('[');
s.expect('REGISTER_C');
s.expect(']');
s.expect('COMMA');
s.expect('ACCUMULATOR');
this.instruction(8, [0xE2]);
// ldh a,?
} else if (s.peak('ACCUMULATOR')) {
s.expect('ACCUMULATOR');
s.expect('COMMA');
// ldh a,[c]
if (s.peak('[')) {
s.expect('[');
s.expect('REGISTER_C');
s.expect(']');
this.instruction(8, [0xF2]);
// ldh a,$ff
} else {
this.instruction(12, [0xF0], s.get('NUMBER_8BIT'), true);
}
// ldh $ff,a
} else {
this.instruction(12, [0xE0], s.get('NUMBER_8BIT'), true);
s.expect('COMMA');
s.expect('ACCUMULATOR');
}
break;
case 'ldhl':
// ldhl sp,$ff
s.expect('REGISTER_SP');
s.expect('COMMA');
this.instruction(12, [0xF8], s.get('NUMBER_8BIT'), true);
break;
case 'push':
// push hl|de|bc|af
this.instruction(16, [{
af: 0xF5,
bc: 0xC5,
de: 0xD5,
hl: 0xE5
}[s.get('REGISTER_STACKABLE')]]);
break;
case 'pop':
// pop hl|de|bc|af
this.instruction(12, [{
af: 0xF1,
bc: 0xC1,
de: 0xD1,
hl: 0xE1
}[s.get('REGISTER_STACKABLE')]]);
break;
// 8 Bit / 16 Bit ALU
case 'add':
case 'adc':
case 'sub':
case 'sbc':
this.parseAddSubInstruction(s, mnemonic);
break;
case 'and':
case 'or':
case 'xor':
case 'cp':
this.parseAndXorInstruction(s, mnemonic);
break;
case 'inc':
case 'dec':
// inc [hl]
// dec [hl]
if (s.peak('[')) {
s.expect('[');
s.expect('REGISTER_HL');
s.expect(']');
if (mnemonic === 'inc') {
this.instruction(12, [0x34]);
} else {
this.instruction(12, [0x35]);
}
// inc hl|de|bc
// dec hl|de|bc
} else if (s.peak('REGISTER_DOUBLE')) {
arg = s.get('REGISTER_DOUBLE');
if (mnemonic === 'inc') {
this.instruction(12, [{
bc: 0x03,
de: 0x13,
hl: 0x23
}[arg]]);
} else {
this.instruction(12, [{
bc: 0x0B,
de: 0x1B,
hl: 0x2B
}[arg]]);
}
// inc sp
// dec sp
} else if (s.peak('REGISTER_SP')) {
s.expect('REGISTER_SP');
if (mnemonic === 'inc') {
this.instruction(8, [0x33]);
} else {
this.instruction(8, [0x3B]);
}
// inc a|b|c|d|h|l
// dec a|b|c|d|h|l
} else {
arg = s.get('REGISTER_8BIT');
if (mnemonic === 'inc') {
this.instruction(4, [{
a: 0x3C,
b: 0x04,
c: 0x0C,
d: 0x14,
e: 0x1C,
h: 0x24,
l: 0x2C
}[arg]]);
} else {
this.instruction(4, [{
a: 0x3D,
b: 0x05,
c: 0x0D,
d: 0x15,
e: 0x1D,
h: 0x25,
l: 0x2D
}[arg]]);
}
}
break;
// Miscellaneous
case 'swap':
// swap [hl]
if (s.peak('[')) {
s.expect('[');
s.expect('REGISTER_HL');
s.expect(']');
this.instruction(16, [0xCB, 0x36]);
// swap a|b|c|d|h|l
} else {
this.instruction(8, [0xCB, {
a: 0x37,
b: 0x30,
c: 0x31,
d: 0x32,
e: 0x33,
h: 0x34,
l: 0x35
}[s.get('REGISTER_8BIT')]]);
}
break;
case 'daa':
this.instruction(4, [0x27]);
break;
case 'cpl':
this.instruction(4, [0x2F]);
break;
case 'ccf':
this.instruction(4, [0x3F]);
break;
case 'scf':
this.instruction(4, [0x37]);
break;
case 'nop':
this.instruction(4, [0x00]);
break;
case 'halt':
this.instruction(4, [0x76]);
break;
case 'di':
this.instruction(4, [0xF3]);
break;
case 'ei':
this.instruction(4, [0xFB]);
break;
case 'stop':
this.instruction(4, [0x10, 0x00]);
break;
// Rotates and Shifts Accumulator
case 'rlca':
case 'rla':
case 'rrca':
case 'rra':
this.instruction(4, [{
rlca: 0x07,
rla: 0x17,
rrca: 0x0F,
rra: 0x1F
}[mnemonic]]);
break;
// Rotates and Shifts Registers
case 'rlc':
case 'rl':
case 'rrc':
case 'rr':
case 'sla':
case 'sra':
case 'srl':
this.parseShiftInstruction(s, mnemonic);
break;
// Bit Op Codes
case 'bit':
case 'set':
case 'res':
this.parseBitInstruction(s, mnemonic);
break;
// Jumps
case 'jp':
// jp [hl]
if (s.peak('[')) {
s.expect('[');
s.expect('REGISTER_HL');
s.expect(']');
this.instruction(4, [0xE9]);
// jp c,label
// jp nc,label
// jp z,label
// jp nz,label
} else if (s.peak('FLAG')) {
flag = s.get('FLAG');
s.expect('COMMA');
arg = s.get('NUMBER_16BIT');
switch(flag) {
case 'c':
this.instruction(16, [0xDA], arg);
break;
case 'nc':
this.instruction(16, [0xD2], arg);
break;
case 'z':
this.instruction(16, [0xCA], arg);
break;
case 'nz':
this.instruction(16, [0xC2], arg);
break;
}
// jp label
} else {
this.instruction(16, [0xC3], s.get('NUMBER_16BIT'));
}
break;
case 'jr':
// jr c,label
// jr nc,label
// jr z,label
// jr nz,label
if (s.peak('FLAG')) {
flag = s.get('FLAG');
s.expect('COMMA');
// For offset labels we need to record the target instruction
if (s.peak('OFFSET')) {
arg = s.get('OFFSET');
} else {
arg = s.get('NUMBER_16BIT');
}
switch(flag) {
case 'c':
this.instruction(12, [0x38], arg, true, true);
break;
case 'nc':
this.instruction(12, [0x30], arg, true, true);
break;
case 'z':
this.instruction(12, [0x28], arg, true, true);
break;
case 'nz':
this.instruction(12, [0x20], arg, true, true);
break;
}
// jr label
} else {
if (s.peak('OFFSET')) {
arg = s.get('OFFSET');
} else {
arg = s.get('NUMBER_16BIT');
}
this.instruction(12, [0x18], arg, true, true);
}
break;
// Calls
case 'call':
// call c,label
// call nc,label
// call z,label
// call nz,label
if (s.peak('FLAG')) {
flag = s.get('FLAG');
s.expect('COMMA');
arg = s.get('NUMBER_16BIT');
switch(flag) {
case 'c':
this.instruction(24, [0xDC], arg);
break;
case 'nc':
this.instruction(24, [0xD4], arg);
break;
case 'z':
this.instruction(24, [0xCC], arg);
break;
case 'nz':
this.instruction(24, [0xC4], arg);
break;
}
// call label
} else {
this.instruction(24, [0xCD], s.get('NUMBER_16BIT'));
}
break;
// Restarts
case 'rst':
// rst $0048
switch(s.expect('ZERO_PAGE_LOCATION')) {
case 0x00:
this.instruction(16, [0xC7]);
break;
case 0x08:
this.instruction(16, [0xCF]);
break;
case 0x10:
this.instruction(16, [0xD7]);
break;
case 0x18:
this.instruction(16, [0xDF]);
break;
case 0x20:
this.instruction(16, [0xE7]);
break;
case 0x28:
this.instruction(16, [0xEF]);
break;
case 0x30:
this.instruction(16, [0xF7]);
break;
case 0x38:
this.instruction(16, [0xFF]);
break;
}
break;
// Returns
case 'ret':
// ret c
// ret nc
// ret z
// ret nz
if (s.peak('FLAG')) {
flag = s.get('FLAG');
switch(flag) {
case 'c':
this.instruction(20, [0xD8]);
break;
case 'nc':
this.instruction(20, [0xD0]);
break;
case 'z':
this.instruction(20, [0xC8]);
break;
case 'nz':
this.instruction(20, [0xC0]);
break;
}
} else {
this.instruction(16, [0xC9]);
}
break;
case 'reti':
this.instruction(16, [0xD9]);
break;
// Debugging
case 'msg':
this.parseDebugMessageInstruction(s);
break;
}
},
parseDebugMessageInstruction: function(s) {
var arg = s.get('STRING');
// Only generate when `-d` flag is passed to compiler
if (this.debug) {
var opcodes = [
0x52, // ld d,d
0x18, // jr @+4+LEN(message)
4 + arg.value.length,
0x64, // DW $6464
0x64,
0x00, // DW $0000
0x00
];
for(var i = 0, l = arg.value.length; i < l; i++) {
opcodes.push(arg.value.charCodeAt(i));
}
this.instruction(4 + 12, opcodes);
} else {
this.file.warning(
arg.index,
'[debug warning] Debug messages are not compiled in without the -d flag.'.yellow
);
}
},
parseLdInstruction: function(s) {
var left = null,
right = null;
// Accumulator loads
if (s.peak('ACCUMULATOR')) {
left = s.get('ACCUMULATOR');
s.expect('COMMA');
// From Memory
if (s.peak('[')) {
s.expect('[');
// ld a,[hli]
if (s.peak('REGISTER_HL_INCREMENT')) {
s.expect('REGISTER_HL_INCREMENT');
s.expect(']');
this.instruction(8, [0x2A]);
// ld a,[hld]
} else if (s.peak('REGISTER_HL_DECREMENT')) {
s.expect('REGISTER_HL_DECREMENT');
s.expect(']');
this.instruction(8, [0x3A]);
// ld(h) a,[c]
} else if (s.peak('REGISTER_C')) {
s.expect('REGISTER_C');
s.expect(']');
this.instruction(8, [0xF2]);
// ld a,[hl]
// ld a,[bc]
// ld a,[de]
} else if (s.peak('REGISTER_DOUBLE')) {
right = s.get('REGISTER_DOUBLE');
s.expect(']');
this.instruction(8, [{
bc: 0x0A,
de: 0x1A,
hl: 0x7E
}[right]]);
// ld a,[someLabel]
} else {
right = s.get('NUMBER_16BIT');
s.expect(']');
this.instruction(16, [0xFA], right);
}
// ld a,a
// ld a,b
// ld a,c
// ld a,d
// ld a,e
// ld a,h
// ld a,l
} else if (s.peak('REGISTER_8BIT')) {
this.instruction(4, [{
a: 0x7F,
b: 0x78,
c: 0x79,
d: 0x7A,
e: 0x7B,
h: 0x7C,
l: 0x7D
}[s.get('REGISTER_8BIT')]]);
// ld a,$ff
} else {
this.instruction(8, [0x3E], s.get('NUMBER_8BIT'), true);
}
// Memory stores
} else if (s.peak('[')) {
s.expect('[');
// ld [hli],a
if (s.peak('REGISTER_HL_INCREMENT')) {
s.expect('REGISTER_HL_INCREMENT');
s.expect(']');
s.expect('COMMA');
s.expect('ACCUMULATOR');
this.instruction(8, [0x22]);
// ld [hld],a
} else if (s.peak('REGISTER_HL_DECREMENT')) {
s.expect('REGISTER_HL_DECREMENT');
s.expect(']');
s.expect('COMMA');
s.expect('ACCUMULATOR');
this.instruction(8, [0x32]);
// ld [hl],a
// ld [hl],c
// ld [hl],d
// ld [hl],e
// ld [hl],h
// ld [hl],l
// ld [hl],$ff
} else if (s.peak('REGISTER_HL')) {
s.expect('REGISTER_HL');
s.expect(']');
s.expect('COMMA');
// ld [hl],register
if (s.peak('REGISTER_8BIT')) {
this.instruction(8, [{
a: 0x77,
b: 0x70,
c: 0x71,
d: 0x72,
e: 0x73,
h: 0x74,
l: 0x75
}[s.get('REGISTER_8BIT')]]);
// ld [hl],$ff
} else {
this.instruction(8, [0x36], s.get('NUMBER_8BIT'), true);
}
// ld(h) [c],a
} else if (s.peak('REGISTER_C')) {
s.expect('REGISTER_C');
s.expect(']');
s.expect('COMMA');
s.expect('ACCUMULATOR');
this.instruction(8, [0xE2]);
// ld [de],a
// ld [bc],a
} else if (s.peak('REGISTER_DOUBLE')) {
left = s.get('REGISTER_DOUBLE');
s.expect(']');
s.expect('COMMA');
s.expect('ACCUMULATOR');
this.instruction(8, [{
bc: 0x02,
de: 0x12
}[left]]);
// ld [someLabel],??
} else {
left = s.get('NUMBER_16BIT');
s.expect(']');
s.expect('COMMA');
// ld [someLabel],sp
if (s.peak('REGISTER_SP')) {
s.expect('REGISTER_SP');
this.instruction(20, [0x08], left);
// ld [someLabel],a
} else {
s.expect('ACCUMULATOR');
this.instruction(16, [0xEA], left);
}
}
// Stackpointer load
} else if (s.peak('REGISTER_SP')) {
left = s.get('REGISTER_SP');
s.expect('COMMA');
// ld sp,hl
if (s.peak('REGISTER_HL')) {
s.expect('REGISTER_HL');
this.instruction(8, [0xF9]);
// ld sp,someLabel
} else {
this.instruction(12, [0x31], s.get('NUMBER_16BIT'));
}
// 16bit register loads
} else if (s.peak('REGISTER_DOUBLE')) {
left = s.get('REGISTER_DOUBLE');
s.expect('COMMA');
// ld hl,someLabel
// ld bc,someLabel
// ld de,someLabel
// ld hl,$ffff
// ld bc,$ffff
// ld de,$ffff
this.instruction(12, [{
bc: 0x01,
de: 0x11,
hl: 0x21
}[left]], s.get('NUMBER_16BIT'));
// 8bit register loads (b, c, d, e, h, l)
// ld b,c
// ld c,[hl]
// ld d,$ff
} else {
left = s.get('REGISTER_8BIT');
s.expect('COMMA');
// ld ?,[hl]
if (s.peak('[')) {
s.expect('[');
s.expect('REGISTER_HL');
s.expect(']');
this.instruction(4, [{
b: 0x46,
c: 0x4E,
d: 0x56,
e: 0x5E,
h: 0x66,
l: 0x6E
}[left]]);
// ld ?,?
} else if (s.peak('REGISTER_8BIT')) {
this.instruction(4, [{
ba: 0x47,
bb: 0x40,
bc: 0x41,
bd: 0x42,
be: 0x43,
bh: 0x44,
bl: 0x45,
ca: 0x4F,
cb: 0x48,
cc: 0x49,
cd: 0x4A,
ce: 0x4B,
ch: 0x4C,
cl: 0x4D,
da: 0x57,
db: 0x50,
dc: 0x51,
dd: 0x52,
de: 0x53,
dh: 0x54,
dl: 0x55,
ea: 0x5F,
eb: 0x58,
ec: 0x59,
ed: 0x5A,
ee: 0x5B,
eh: 0x5C,
el: 0x5D,
ha: 0x67,
hb: 0x60,
hc: 0x61,
hd: 0x62,
he: 0x63,
hh: 0x64,
hl: 0x65,
la: 0x6F,
lb: 0x68,
lc: 0x69,
ld: 0x6A,
le: 0x6B,
lh: 0x6C,
ll: 0x6D
}[left + s.expect('REGISTER_8BIT')]]);
// ld c,$ff etc.
} else {
this.instruction(8, [{
b: 0x06,
c: 0x0E,
d: 0x16,
e: 0x1E,
h: 0x26,
l: 0x2E
}[left]], s.get('NUMBER_8BIT'), true);
}
}
},
parseAndXorInstruction: function(s, mnemonic) {
// and [hl]
// or [hl]
// xor [hl]
// cp [hl]
if (s.peak('[')) {
s.expect('[');
s.expect('REGISTER_HL');
s.expect(']');
this.instruction(8, [{
and: 0xA6,
or: 0xB6,
xor: 0xAE,
cp: 0xBE
}[mnemonic]]);
// and a|b|c|d|e|h|l
// or a|b|c|d|e|h|l
// xor a|b|c|d|e|h|l
// cp a|b|c|d|e|h|l
} else if (s.peak('REGISTER_8BIT')) {
var reg = {
a: 0x07,
b: 0x00,
c: 0x01,
d: 0x02,
e: 0x03,
h: 0x04,
l: 0x05
};
var m = {
and: 0xA0,
or: 0xB0,
xor: 0xA8,
cp: 0xB8
};
this.instruction(4, [reg[s.get('REGISTER_8BIT')] + m[mnemonic]]);
// and $ff
// or $ff
// xor $ff
// cp $ff
} else {
this.instruction(8, [{
and: 0xE6,
or: 0xF6,
xor: 0xEE,
cp: 0xFE
}[mnemonic]], s.get('NUMBER_8BIT'), true);
}
},
parseAddSubInstruction: function(s, mnemonic) {
// add hl,??
if (mnemonic === 'add' && s.peak('REGISTER_HL')) {
s.expect('REGISTER_HL');
s.expect('COMMA');
// add hl,sp
if (s.peak('REGISTER_SP')) {
s.expect('REGISTER_SP');
this.instruction(8, [0x39]);
// add hl,hl
// add hl,de
// add hl,bc
} else {
this.instruction(8, [{
bc: 0x09,
de: 0x19,
hl: 0x29
}[s.get('REGISTER_DOUBLE')]]);
}
// add SP,-2
// add SP,4
} else if (mnemonic === 'add' && s.peak('REGISTER_SP')) {
s.expect('REGISTER_SP');
s.expect('COMMA');
this.instruction(16, [0xE8], s.get('NUMBER_SIGNED_8BIT'), true, true);
// add a|b|c|d|e|h|l
// adc a|b|c|d|e|h|l
// sub a|b|c|d|e|h|l
// sbc a|b|c|d|e|h|l
} else if (s.peak('REGISTER_8BIT')) {
// add a,?
if (s.peak('ACCUMULATOR')) {
s.expect('ACCUMULATOR');
if (s.peak('COMMA')) {
s.expect('COMMA');
this.parseAddSubInstructionOperand(s, mnemonic);
} else {
// add a
// adc a
// sub a
// sbc a
this.instruction(4, [{
add: 0x87,
adc: 0x8F,
sub: 0x97,
sbc: 0x9F
}[mnemonic]]);
}
// add r8/value
} else {
this.parseAddSubInstructionOperand(s, mnemonic);
}
// add [hl]
// adc [hl]
// sub [hl]
// sbc [hl]
} else if (s.peak('[')) {
s.expect('[');
s.expect('REGISTER_HL');
s.expect(']');
this.instruction(8, [{
add: 0x86,
adc: 0x8E,
sub: 0x96,
sbc: 0x9E,
}[mnemonic]]);
// add $ff
// adc $ff
// sub $ff
// sbc $ff
} else {
this.instruction(8, [{
add: 0xC6,
adc: 0xCE,
sub: 0xD6,
sbc: 0xDE
}[mnemonic]], s.get('NUMBER_8BIT'), true);
}
},
parseAddSubInstructionOperand: function(s, mnemonic) {
// add a,r8 or add r8
// adc a,r8 or adc r8
// sub a,r8 or sub r8
// sbc a,r8 or sbc r8
if (s.peak('REGISTER_8BIT')) {
var reg = {
a: 0x07,
b: 0x00,
c: 0x01,
d: 0x02,
e: 0x03,
h: 0x04,
l: 0x05
};
var m = {
add: 0x80,
adc: 0x88,
sub: 0x90,
sbc: 0x98
};
this.instruction(4, [reg[s.get('REGISTER_8BIT')] + m[mnemonic]]);
// add a,[hl]
// adc a,[hl]
// sub a,[hl]
// sbc a,[hl]
} else if (s.peak('[')) {
s.expect('[');
s.expect('REGISTER_HL');
s.expect(']');
this.instruction(8, [{
add: 0x86,
adc: 0x8E,
sub: 0x96,
sbc: 0x9E,
}[mnemonic]]);
// add a,$ff
// adc a,$ff
// sub a,$ff
// sbc a,$ff
} else {
this.instruction(8, [{
add: 0xC6,
adc: 0xCE,
sub: 0xD6,
sbc: 0xDE
}[mnemonic]], s.get('NUMBER_8BIT'), true);
}
},
parseShiftInstruction: function(s, mnemonic) {
// rlc [hl]
// rl [hl]
// rrc [hl]
// rr [hl]
// sla [hl]
// sra [hl]
// srl [hl]
if (s.peak('[')) {
s.expect('[');
s.expect('REGISTER_HL');
s.expect(']');
this.instruction(16, [0xCB, {
rlc: 0x06,
rl: 0x16,
rrc: 0x0E,
rr: 0x1E,
sla: 0x26,
sra: 0x2E,
srl: 0x3E
}[mnemonic]]);
// rlc a|b|c|d|e|h|l
// rl a|b|c|d|e|h|l
// rrc a|b|c|d|e|h|l
// rr a|b|c|d|e|h|l
// sla a|b|c|d|e|h|l
// sra a|b|c|d|e|h|l
// srl a|b|c|d|e|h|l
} else {
// Register offset
var reg = {
a: 0x07,
b: 0x00,
c: 0x01,
d: 0x02,
e: 0x03,
h: 0x04,
l: 0x05
};
// Mnemonic Offset
var m = {
rlc: 0x00,
rl: 0x10,
rrc: 0x08,
rr: 0x18,
sla: 0x20,
sra: 0x28,
srl: 0x38
};
this.instruction(8, [0xCB, reg[s.get('REGISTER_8BIT')] + m[mnemonic]]);
}
},
parseBitInstruction: function(s, mnemonic) {
var arg = [
0, 8, 16, 24, 32, 40, 48, 56
][s.get('NUMBER_BIT_INDEX').value];
s.expect('COMMA');
// bit 0-7,[hl]
// res 0-7,[hl]
// set 0-7,[hl]
if (s.peak('[')) {
s.expect('[');
s.expect('REGISTER_HL');
s.expect(']');
if (mnemonic === 'bit') {
this.instruction(12, [0xCB, 0x46 + arg], null, false, false, true);
} else if (mnemonic === 'set') {
this.instruction(16, [0xCB, 0xC6 + arg], null, false, false, true);
} else if (mnemonic === 'res') {
this.instruction(16, [0xCB, 0x86 + arg], null, false, false, true);
}
// bit 0-7,a|b|c|d|e|h|l
// res 0-7,a|b|c|d|e|h|l
// set 0-7,a|b|c|d|e|h|l
} else {
var right = s.get('REGISTER_8BIT');
if (mnemonic === 'bit') {
this.instruction(8, [0xCB, {
a: 0x47,
b: 0x40,
c: 0x41,
d: 0x42,
e: 0x43,
h: 0x44,
l: 0x45
}[right] + arg], null, false, false, true);
} else if (mnemonic === 'set') {
this.instruction(8, [0xCB, {
a: 0xC7,
b: 0xC0,
c: 0xC1,
d: 0xC2,
e: 0xC3,
h: 0xC4,
l: 0xC5
}[right] + arg], null, false, false, true);
} else if (mnemonic === 'res') {
this.instruction(8, [0xCB, {
a: 0x87,
b: 0x80,
c: 0x81,
d: 0x82,
e: 0x83,
h: 0x84,
l: 0x85
}[right] + arg], null, false, false, true);
}
}
},
instruction: function(cycles, code, arg, isByte, isSigned, isBit) {
this.file.addInstruction(
this._mnemonic,
cycles,
code,
arg,
isByte,
isSigned,
isBit,
this._token.index
);
},
// Error Handling ---------------------------------------------------------
error: function(msg, expected, index) {
new Errors.ParseError(this.file, msg, expected, index);
}
};
// Exports --------------------------------------------------------------------
module.exports = Parser;