mz700-js
Version:
MZ-700 Full JavaScript Emulator
323 lines (291 loc) • 9.51 kB
text/typescript
/* tslint:disable: class-name */
/**
* @fileoverview Z80 assembler class.
*/
import Z80LineAssembler from "./Z80-line-assembler";
import parseAddress from "../lib/parse-addr";
/**
* Z80 Assembler
* @class
*/
export default class Z80_assemble {
/**
* Assemble result lines
* @type {object[]}
*/
list:Z80LineAssembler[] = [];
/**
* mapping labels to address
* @type {object}
*/
label2value:Record<string, number> = {};
/**
* The list has ORG mnemonic, or not.
* @type {boolean}
*/
_explicitAddress:boolean;
/**
* A first address of this assembled codes.
* @type {number}
*/
minAddr:number = null;
/**
* A last address of this assembled codes.
* @type {number}
*/
maxAddr:number = null;
/**
* A binary code as assembling result.
* @type {number[]}
*/
buffer:number[] = null;
/**
* @constructor
* @param {string} asmSource The source code
* @param {boolean|undefined} assembleOnly
* Set true not to resolve the addresses and create machine code.
* @param {number} startAddr
* The starting address of assembling.
*/
constructor(asmSource:string, assembleOnly:boolean, startAddr:number) {
if(asmSource === undefined) {
return;
}
// Assemble the line by line
let address = startAddr || 0;
const sourceLines = asmSource.split(/\r{0,1}\n/);
sourceLines.forEach( line => {
const assembledCode = Z80LineAssembler.assemble(
line, address, this.label2value);
address = assembledCode.getNextAddress();
this.list.push(assembledCode);
});
this._explicitAddress = Z80_assemble.hasExplicitAddress(this.list);
if(assembleOnly) { // Estimate the code size
const range = Z80_assemble.measureCodeSize(this.list);
this.minAddr = range.minAddr;
this.maxAddr = range.minAddr;
}
if(!assembleOnly) {
this.closeAssembling(this.label2value);
}
}
/**
* @returns {boolean} true if the source includes the ORG
* pseudo mnemonic, otherwise false.
*/
isAddressExplicit():boolean {
return this._explicitAddress;
}
/**
* Check the list having ORG pseudo mnemonic.
*
* @param {Array<object>} assembleList
* An array of line assembleled information.
*
* @returns {boolean} true the list includes ORG, otherwise false.
*/
static hasExplicitAddress(assembleList:Z80LineAssembler[]):boolean {
for(const asm of assembleList) {
if(asm.mnemonic === "ORG") {
return true;
}
}
return false;
}
/**
* Resolve the relocatable addresses and create a machine code.
*
* @param {object} mapLabelToAddress
* A dictionary to get address of the labels.
*
* @returns {undefined}
*/
closeAssembling(mapLabelToAddress:Record<string, number>):void {
// Resolve address references
Z80_assemble.resolveAddress(this.list, mapLabelToAddress);
// Estimate the code size
const range = Z80_assemble.measureCodeSize(this.list);
this.minAddr = range.minAddr;
this.maxAddr = range.minAddr;
// Create machine code buffer
this.buffer = Z80_assemble.createMachineCode(
this.list, this.minAddr,
this.maxAddr - this.minAddr + 1);
}
/**
* Resolve the undetermined addresses in the assembled list.
*
* @param {Array<object>} assembleList
* An array of line assembleled information.
*
* @param {object} mapLabelToAddress
* A dictionary to get address of the labels.
*
* @returns {undefined}
*/
static resolveAddress(assembleList:Z80LineAssembler[], mapLabelToAddress:Record<string, number>):void {
assembleList.forEach( line => {
line.resolveAddress(mapLabelToAddress);
});
}
/**
* Determine the addresses of first and last line.
*
* @param {Z80LineAssembler[]} assembleList
* An array of line assembleled information.
*
* @returns {object} that has minAddr and maxAddr as keys.
*/
static measureCodeSize(assembleList:Z80LineAssembler[]):{minAddr:number, maxAddr:number} {
// address min-max
let minAddr = null;
let maxAddr = null;
assembleList.forEach(line => {
if(line.bytecode.length > 0) {
if(minAddr == null || line.address < minAddr) {
minAddr = line.address;
}
const lastAddr = line.getLastAddress();
if(maxAddr == null || lastAddr > maxAddr) {
maxAddr = lastAddr;
}
}
});
return {minAddr, maxAddr};
}
/**
* Create machine code from the assemble list.
*
* @param {Z80LineAssembler[]} assembleList
* An array of line assembleled information.
*
* @param {number} minAddr
* The address of the first line of the list.
*
* @param {number} bytesize
* The byte code size that simply determind from address.
*
* @returns {Array<number>} that contains Z80 machine code.
*/
static createMachineCode(assembleList:Z80LineAssembler[], minAddr:number, bytesize:number):number[] {
const buffer = new Array(bytesize);
assembleList.forEach(line => {
if(line.bytecode.length > 0) {
Array.prototype.splice.apply(buffer, [
line.address - minAddr,
line.bytecode.length
].concat(line.bytecode as number[]));
}
});
return buffer;
}
/**
* Assemble the sources.
*
* @param {Array<string>} sources
* Z80 assemble source
*
* @returns {object} as an assembled result.
*/
static assemble(sources:string[]):{
obj: Z80_assemble[],
label2value: Record<string, number>,
minAddr:number,
maxAddr:number,
buffer:number[],
} {
// Assemble each source
const assembled = [];
let startAddr = 0;
let minAddr = null;
let maxAddr = null;
let lastAddr = null;
sources.forEach( src => {
const asm = new Z80_assemble(src, true, startAddr);
if(minAddr == null || minAddr > asm.minAddr) {
minAddr = asm.minAddr;
}
if(maxAddr == null || maxAddr < asm.maxAddr) {
maxAddr = asm.maxAddr;
}
const lineLastAddr = asm.list.slice(-1)[0].getNextAddress() - 1;
if(lastAddr == null || lastAddr < lineLastAddr) {
lastAddr = lineLastAddr;
}
startAddr = lastAddr + 1;
assembled.push(asm);
});
// Create an integrated address map
const mapLabelToAddress = {};
assembled.forEach( asm => {
Object.keys(asm.label2value).forEach( label => {
mapLabelToAddress[label] = asm.label2value[label];
});
});
// Total code size
assembled.forEach( asm => {
asm.closeAssembling(mapLabelToAddress);
});
// Create machine code
const buffer = new Array(lastAddr - minAddr + 1);
for(let i = 0; i < buffer.length; i++) {
buffer[i] = 0;
}
assembled.forEach(asm => {
if(asm.buffer.length > 0) {
const endAddr = asm.maxAddr + asm.buffer.length - 1;
Array.prototype.splice.apply(buffer, [
asm.minAddr - minAddr,
endAddr - asm.minAddr + 1,
].concat(asm.buffer));
}
});
return {
obj: assembled,
label2value: mapLabelToAddress,
minAddr,
maxAddr,
buffer,
};
}
/**
* Translate address string to value.
* @param {string} addrToken
* The address to be converted.
* @returns {number} as the address.
*/
parseAddress(addrToken:string):number {
return parseAddress.parseAddress(addrToken, this.label2value);
}
/**
* Returns a address map list.
*
* @description
* Each element of the list is an object that has two fields 'label' and 'address'.
* The list is sorted by the address.
*
* @returns {object[]} array of address map entry
*/
getMap():{label:string, address:number}[] {
return Z80_assemble.hashMapArray(this.label2value);
}
/**
* Returns a address map list.
*
* @description
* Each element of the list is an object that has two fields 'label' and 'address'.
* The list is sorted by the address.
*
* @param {object} mapLabelToAddress
* A dictionary to get address of the labels.
*
* @returns {object[]} array of address map entry
*/
static hashMapArray(mapLabelToAddress:Record<string, number>):{label:string, address:number}[] {
return Object.keys(mapLabelToAddress).map(label => {
return { label, address: mapLabelToAddress[label] };
}).sort((a,b)=>{ return a.address - b.address; });
}
}
module.exports = Z80_assemble;