apple2js/js/applesoft/heap.ts

201 lines
5.8 KiB
TypeScript

import { byte, word, Memory } from 'js/types';
import { toHex } from 'js/util';
import {
CURLINE,
ARG,
FAC,
ARYTAB,
STREND,
TXTTAB,
VARTAB
} from './zeropage';
export type ApplesoftValue = word | number | string | ApplesoftArray;
export type ApplesoftArray = Array<ApplesoftValue>;
export enum VariableType {
Float = 0,
String = 1,
Function = 2,
Integer = 3
}
export interface ApplesoftVariable {
name: string;
sizes?: number[];
type: VariableType;
value: ApplesoftValue | undefined;
}
export class ApplesoftHeap {
constructor(private mem: Memory) {}
private readByte(addr: word): byte {
const page = addr >> 8;
const off = addr & 0xff;
if (page >= 0xc0) {
throw new Error(`Address ${toHex(page)} out of range`);
}
return this.mem.read(page, off);
}
private readWord(addr: word): word {
const lsb = this.readByte(addr);
const msb = this.readByte(addr + 1);
return (msb << 8) | lsb;
}
private readInt(addr: word): word {
const msb = this.readByte(addr);
const lsb = this.readByte(addr + 1);
return (msb << 8) | lsb;
}
private readFloat(addr: word, { unpacked } = { unpacked: false }): number {
let exponent = this.readByte(addr);
if (exponent === 0) {
return 0;
}
exponent = (exponent & 0x80 ? 1 : -1) * ((exponent & 0x7F) - 1);
let msb = this.readByte(addr + 1);
const sb3 = this.readByte(addr + 2);
const sb2 = this.readByte(addr + 3);
const lsb = this.readByte(addr + 4);
let sign;
if (unpacked) {
const sb = this.readByte(addr + 5);
sign = sb & 0x80 ? -1 : 1;
} else {
sign = msb & 0x80 ? -1 : 1;
}
msb &= 0x7F;
const mantissa = (msb << 24) | (sb3 << 16) | (sb2 << 8) | lsb;
return sign * (1 + mantissa / 0x80000000) * Math.pow(2, exponent);
}
private readString(len: byte, addr: word): string {
let str = '';
for (let idx = 0; idx < len; idx++) {
str += String.fromCharCode(this.readByte(addr + idx) & 0x7F);
}
return str;
}
private readVar(addr: word) {
const firstByte = this.readByte(addr);
const lastByte = this.readByte(addr + 1);
const firstLetter = firstByte & 0x7F;
const lastLetter = lastByte & 0x7F;
const name =
String.fromCharCode(firstLetter) +
(lastLetter ? String.fromCharCode(lastLetter) : '');
const type = (lastByte & 0x80) >> 7 | (firstByte & 0x80) >> 6;
return { name, type };
}
private readArray(addr: word, type: byte, sizes: number[]): ApplesoftArray {
let strLen, strAddr;
let value;
const ary = [];
const len = sizes[0];
for (let idx = 0; idx < len; idx++) {
if (sizes.length > 1) {
value = this.readArray(addr, type, sizes.slice(1));
} else {
switch (type) {
case 0: // Real
value = this.readFloat(addr);
addr += 5;
break;
case 1: // String
strLen = this.readByte(addr);
strAddr = this.readWord(addr + 1);
value = this.readString(strLen, strAddr);
addr += 3;
break;
case 3: // Integer
default:
value = this.readInt(addr);
addr += 2;
break;
}
}
ary[idx] = value;
}
return ary;
}
dumpInternals() {
return {
txttab: this.readWord(TXTTAB),
fac: this.readFloat(FAC, { unpacked: true }),
arg: this.readFloat(ARG, { unpacked: true }),
curline: this.readWord(CURLINE),
};
}
dumpVariables() {
const simpleVariableTable = this.readWord(VARTAB);
const arrayVariableTable = this.readWord(ARYTAB);
const variableStorageEnd = this.readWord(STREND);
// var stringStorageStart = readWord(0x6F);
let addr;
const vars: ApplesoftVariable[] = [];
let value;
let strLen, strAddr;
for (addr = simpleVariableTable; addr < arrayVariableTable; addr += 7) {
const { name, type } = this.readVar(addr);
switch (type) {
case VariableType.Float:
value = this.readFloat(addr + 2);
break;
case VariableType.String:
strLen = this.readByte(addr + 2);
strAddr = this.readWord(addr + 3);
value = this.readString(strLen, strAddr);
break;
case VariableType.Function:
value = toHex(this.readWord(addr + 2));
value += ',' + toHex(this.readWord(addr + 4));
break;
case VariableType.Integer:
value = this.readInt(addr + 2);
break;
}
vars.push({ name, type, value });
}
while (addr < variableStorageEnd) {
const { name, type } = this.readVar(addr);
const off = this.readWord(addr + 2);
const dim = this.readByte(addr + 4);
const sizes = [];
for (let idx = 0; idx < dim; idx++) {
sizes[idx] = this.readInt(addr + 5 + idx * 2);
}
value = this.readArray(addr + 5 + dim * 2, type, sizes);
vars.push({ name, sizes, type, value });
if (off < 1) {
break;
}
addr += off;
}
return vars;
}
}