mirror of
https://github.com/whscullin/apple2js.git
synced 2024-01-12 14:14:38 +00:00
Typescript conversion of several files, including js/cpu6502 (#38)
* Convert `js/util.js` to Typescript and add tests Besides converting `js/util.js` to Typescript, this change also adds `js/types.ts` that defines common types used in apple2js. Some of these types, like `byte` and `word` are for information only. * Convert `js/base64.js` to Typescript This also adds a new type, `memory`, that is either an array of numbers, or a Uint8Array. * Convert `js/ram.js` to Typescript This change does not convert `RAM` to a class; it just introduces types. * Basic typing of cpu6502 This is a really rough first pass. There are some problems that can't be fixed until this is turned into a real class, but at least all of the function arguments are now typed. This caught a few cases where extra arguments were being passed in. * Convert `js/cpu6502` to a class In theory, idiomatic classes should be better than the previous closure-based classes. However, this conversion shows that the instruction table does not fit well with idiomatic classes as method referenced in the table need to be called with the correct `this` everywhere. This should, at best, be considered a first attempt.
This commit is contained in:
parent
e8cd85f54a
commit
c4df78cf06
@ -1,4 +1,9 @@
|
|||||||
export function base64_encode (data) {
|
import { byte, memory } from "./types";
|
||||||
|
|
||||||
|
const B64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
|
||||||
|
|
||||||
|
/** Encode an array of bytes in base64. */
|
||||||
|
export function base64_encode(data: memory) {
|
||||||
// Twacked by Will Scullin to handle arrays of "bytes"
|
// Twacked by Will Scullin to handle arrays of "bytes"
|
||||||
|
|
||||||
// http://kevin.vanzonneveld.net
|
// http://kevin.vanzonneveld.net
|
||||||
@ -18,7 +23,7 @@ export function base64_encode (data) {
|
|||||||
// return atob(data);
|
// return atob(data);
|
||||||
//}
|
//}
|
||||||
|
|
||||||
var b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
|
|
||||||
var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, ac = 0, enc='', tmp_arr = [];
|
var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, ac = 0, enc='', tmp_arr = [];
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
@ -38,7 +43,7 @@ export function base64_encode (data) {
|
|||||||
h4 = bits & 0x3f;
|
h4 = bits & 0x3f;
|
||||||
|
|
||||||
// use hexets to index into b64, and append result to encoded string
|
// use hexets to index into b64, and append result to encoded string
|
||||||
tmp_arr[ac++] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4);
|
tmp_arr[ac++] = B64.charAt(h1) + B64.charAt(h2) + B64.charAt(h3) + B64.charAt(h4);
|
||||||
} while (i < data.length);
|
} while (i < data.length);
|
||||||
|
|
||||||
enc = tmp_arr.join('');
|
enc = tmp_arr.join('');
|
||||||
@ -55,7 +60,12 @@ export function base64_encode (data) {
|
|||||||
return enc;
|
return enc;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function base64_decode(data) {
|
/** Returns undefined if the input is null or undefined. */
|
||||||
|
export function base64_decode(data: null | undefined): undefined;
|
||||||
|
/** Returns an array of bytes from the given base64-encoded string. */
|
||||||
|
export function base64_decode(data: string): memory;
|
||||||
|
/** Returns an array of bytes from the given base64-encoded string. */
|
||||||
|
export function base64_decode(data: string | null | undefined): memory | undefined {
|
||||||
// Twacked by Will Scullin to handle arrays of "bytes"
|
// Twacked by Will Scullin to handle arrays of "bytes"
|
||||||
|
|
||||||
// http://kevin.vanzonneveld.net
|
// http://kevin.vanzonneveld.net
|
||||||
@ -78,18 +88,17 @@ export function base64_decode(data) {
|
|||||||
// return btoa(data);
|
// return btoa(data);
|
||||||
//}
|
//}
|
||||||
|
|
||||||
var b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
|
|
||||||
var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, ac = 0, tmp_arr = [];
|
var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, ac = 0, tmp_arr = [];
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return data;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
do { // unpack four hexets into three octets using index points in b64
|
do { // unpack four hexets into three octets using index points in B64
|
||||||
h1 = b64.indexOf(data.charAt(i++));
|
h1 = B64.indexOf(data.charAt(i++));
|
||||||
h2 = b64.indexOf(data.charAt(i++));
|
h2 = B64.indexOf(data.charAt(i++));
|
||||||
h3 = b64.indexOf(data.charAt(i++));
|
h3 = B64.indexOf(data.charAt(i++));
|
||||||
h4 = b64.indexOf(data.charAt(i++));
|
h4 = B64.indexOf(data.charAt(i++));
|
||||||
|
|
||||||
bits = h1<<18 | h2<<12 | h3<<6 | h4;
|
bits = h1<<18 | h2<<12 | h3<<6 | h4;
|
||||||
|
|
1633
js/cpu6502.js
1633
js/cpu6502.js
File diff suppressed because it is too large
Load Diff
1759
js/cpu6502.ts
Normal file
1759
js/cpu6502.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -10,30 +10,43 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { base64_decode, base64_encode } from './base64';
|
import { base64_decode, base64_encode } from './base64';
|
||||||
|
import { byte, memory } from './types';
|
||||||
import { allocMemPages } from './util';
|
import { allocMemPages } from './util';
|
||||||
|
|
||||||
export default function RAM(sp, ep) {
|
export interface State {
|
||||||
var mem;
|
/** Start of memory region. */
|
||||||
var start_page = sp;
|
start: byte;
|
||||||
var end_page = ep;
|
/** End of memory region. */
|
||||||
|
end: byte;
|
||||||
|
/** Base64-encoded contents. */
|
||||||
|
mem: string;
|
||||||
|
};
|
||||||
|
|
||||||
mem = allocMemPages(ep - sp + 1);
|
/**
|
||||||
|
* Represents RAM from the start page `sp` to end page `ep`. The memory
|
||||||
|
* is addressed by `page` and `offset`.
|
||||||
|
*/
|
||||||
|
export default function RAM(sp: byte, ep: byte) {
|
||||||
|
let start_page = sp;
|
||||||
|
let end_page = ep;
|
||||||
|
|
||||||
|
let mem = allocMemPages(ep - sp + 1);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
start: function() {
|
start: function () {
|
||||||
return start_page;
|
return start_page;
|
||||||
},
|
},
|
||||||
end: function() {
|
end: function () {
|
||||||
return end_page;
|
return end_page;
|
||||||
},
|
},
|
||||||
read: function(page, off) {
|
read: function (page: byte, offset: byte) {
|
||||||
return mem[(page - start_page) << 8 | off];
|
return mem[(page - start_page) << 8 | offset];
|
||||||
},
|
},
|
||||||
write: function(page, off, val) {
|
write: function (page: byte, offset: byte, val: byte) {
|
||||||
mem[(page - start_page) << 8 | off] = val;
|
mem[(page - start_page) << 8 | offset] = val;
|
||||||
},
|
},
|
||||||
|
|
||||||
getState: function() {
|
getState: function (): State {
|
||||||
return {
|
return {
|
||||||
start: start_page,
|
start: start_page,
|
||||||
end: end_page,
|
end: end_page,
|
||||||
@ -41,7 +54,7 @@ export default function RAM(sp, ep) {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
setState: function(state) {
|
setState: function (state: State) {
|
||||||
start_page = state.start;
|
start_page = state.start;
|
||||||
end_page = state.end;
|
end_page = state.end;
|
||||||
mem = base64_decode(state.mem);
|
mem = base64_decode(state.mem);
|
@ -1,4 +1,4 @@
|
|||||||
var SYMBOLS = {
|
SYMBOLS = {
|
||||||
/*
|
/*
|
||||||
0x00: 'GOWARM',
|
0x00: 'GOWARM',
|
||||||
0x03: 'GOSTROUT',
|
0x03: 'GOSTROUT',
|
||||||
|
27
js/types.ts
Normal file
27
js/types.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
|
||||||
|
/** A byte (0..255). This is not enforced by the compiler. */
|
||||||
|
export type byte = number;
|
||||||
|
|
||||||
|
/** A word (0..65535). This is not enforced by the compiler. */
|
||||||
|
export type word = number;
|
||||||
|
|
||||||
|
/** A region of memory. */
|
||||||
|
export type memory = number[] | Uint8Array;
|
||||||
|
|
||||||
|
export type DiskFormat = '2mg' | 'd13' | 'do' | 'dsk' | 'hdv' | 'po' | 'nib' | 'woz';
|
||||||
|
|
||||||
|
export interface Drive {
|
||||||
|
format: DiskFormat,
|
||||||
|
volume: number,
|
||||||
|
tracks: Array<byte[] | Uint8Array>,
|
||||||
|
trackMap: unknown,
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface DiskIIDrive extends Drive {
|
||||||
|
rawTracks: unknown,
|
||||||
|
track: number,
|
||||||
|
head: number,
|
||||||
|
phase: number,
|
||||||
|
readOnly: boolean,
|
||||||
|
dirty: boolean,
|
||||||
|
};
|
@ -9,27 +9,39 @@
|
|||||||
* implied warranty.
|
* implied warranty.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { byte, memory, word } from "./types";
|
||||||
|
|
||||||
/*eslint no-console: 0*/
|
/*eslint no-console: 0*/
|
||||||
|
|
||||||
var hex_digits = '0123456789ABCDEF';
|
const hex_digits = '0123456789ABCDEF';
|
||||||
var bin_digits = '01';
|
const bin_digits = '01';
|
||||||
|
|
||||||
export function allocMem(size) {
|
/** Returns a random byte. */
|
||||||
function garbage() {
|
function garbage(): byte {
|
||||||
return (Math.random() * 0x100) & 0xff;
|
return (Math.random() * 0x100) & 0xff;
|
||||||
}
|
}
|
||||||
var result;
|
|
||||||
|
export const testables = {
|
||||||
|
garbage
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array or Uint8Array of `size` bytes filled as if the computer
|
||||||
|
* was just powered on.
|
||||||
|
*/
|
||||||
|
export function allocMem(size: number) {
|
||||||
|
let result: number[] | Uint8Array;
|
||||||
if (window.Uint8Array) {
|
if (window.Uint8Array) {
|
||||||
result = new Uint8Array(size);
|
result = new Uint8Array(size);
|
||||||
} else {
|
} else {
|
||||||
result = new Array(size);
|
result = new Array(size);
|
||||||
}
|
}
|
||||||
var idx;
|
|
||||||
for (idx = 0; idx < size; idx++) {
|
for (let idx = 0; idx < size; idx++) {
|
||||||
result[idx] = (idx & 0x02) ? 0x00 : 0xff;
|
result[idx] = (idx & 0x02) ? 0x00 : 0xff;
|
||||||
}
|
}
|
||||||
// Borrowed from AppleWin (https://github.com/AppleWin/AppleWin)
|
// Borrowed from AppleWin (https://github.com/AppleWin/AppleWin)
|
||||||
for(idx = 0; idx < size; idx += 0x200 ) {
|
for (let idx = 0; idx < size; idx += 0x200) {
|
||||||
result[idx + 0x28] = garbage();
|
result[idx + 0x28] = garbage();
|
||||||
result[idx + 0x29] = garbage();
|
result[idx + 0x29] = garbage();
|
||||||
result[idx + 0x68] = garbage();
|
result[idx + 0x68] = garbage();
|
||||||
@ -38,23 +50,33 @@ export function allocMem(size) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function allocMemPages(pages) {
|
/** Returns an array or Uint8Array of 256 * `pages` bytes. */
|
||||||
|
export function allocMemPages(pages: number): memory {
|
||||||
return allocMem(pages << 8);
|
return allocMem(pages << 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function bytify(ary) {
|
/** Returns a new Uint8Array for the input array. */
|
||||||
var result = ary;
|
export function bytify(ary: number[]): memory {
|
||||||
|
let result: number[] | Uint8Array = ary;
|
||||||
if (window.Uint8Array) {
|
if (window.Uint8Array) {
|
||||||
result = new Uint8Array(ary);
|
result = new Uint8Array(ary);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Writes to the console. */
|
||||||
|
export function debug(...args: any[]): void;
|
||||||
export function debug() {
|
export function debug() {
|
||||||
console.log.apply(console, arguments);
|
console.log.apply(console, arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toHex(v, n) {
|
/**
|
||||||
|
* Returns a string of hex digits (all caps).
|
||||||
|
* @param v the value to encode
|
||||||
|
* @param n the number of nibbles. If `n` is missing, it is guessed from the value
|
||||||
|
* of `v`. If `v` < 256, it is assumed to be 2 nibbles, otherwise 4.
|
||||||
|
*/
|
||||||
|
export function toHex(v: byte | word | number, n?: number) {
|
||||||
if (!n) {
|
if (!n) {
|
||||||
n = v < 256 ? 2 : 4;
|
n = v < 256 ? 2 : 4;
|
||||||
}
|
}
|
||||||
@ -66,7 +88,11 @@ export function toHex(v, n) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toBinary(v) {
|
/**
|
||||||
|
* Returns a string of 8 binary digits.
|
||||||
|
* @param v the value to encode
|
||||||
|
*/
|
||||||
|
export function toBinary(v: byte) {
|
||||||
var result = '';
|
var result = '';
|
||||||
for (var idx = 0; idx < 8; idx++) {
|
for (var idx = 0; idx < 8; idx++) {
|
||||||
result = bin_digits[v & 0x01] + result;
|
result = bin_digits[v & 0x01] + result;
|
||||||
@ -75,29 +101,36 @@ export function toBinary(v) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value of a query parameter or the empty string if it does not
|
||||||
|
* exist.
|
||||||
|
* @param name the parameter name. Note that `name` must not have any RegExp
|
||||||
|
* meta-characters except '[' and ']' or it will fail.
|
||||||
|
*/
|
||||||
// From http://www.netlobo.com/url_query_string_javascript.html
|
// From http://www.netlobo.com/url_query_string_javascript.html
|
||||||
export function gup( name )
|
export function gup(name: string) {
|
||||||
{
|
name = name.replace(/[[]/, '\\[').replace(/[\]]/, '\\]');
|
||||||
name = name.replace(/[[]/,'\\[').replace(/[\]]/,'\\]');
|
var regexS = '[\\?&]' + name + '=([^&#]*)';
|
||||||
var regexS = '[\\?&]'+name+'=([^&#]*)';
|
var regex = new RegExp(regexS);
|
||||||
var regex = new RegExp( regexS );
|
var results = regex.exec(window.location.href);
|
||||||
var results = regex.exec( window.location.href );
|
if (!results)
|
||||||
if( !results )
|
|
||||||
return '';
|
return '';
|
||||||
else
|
else
|
||||||
return results[1];
|
return results[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns the URL fragment. */
|
||||||
export function hup() {
|
export function hup() {
|
||||||
var regex = new RegExp('#(.*)');
|
var regex = new RegExp('#(.*)');
|
||||||
var results = regex.exec(window.location.hash);
|
var results = regex.exec(window.location.hash);
|
||||||
if ( !results )
|
if (!results)
|
||||||
return '';
|
return '';
|
||||||
else
|
else
|
||||||
return results[1];
|
return results[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function numToString(num) {
|
/** Packs a 32-bit integer into a string in little-endian order. */
|
||||||
|
export function numToString(num: number) {
|
||||||
let result = '';
|
let result = '';
|
||||||
for (let idx = 0; idx < 4; idx++) {
|
for (let idx = 0; idx < 4; idx++) {
|
||||||
result += String.fromCharCode(num & 0xff);
|
result += String.fromCharCode(num & 0xff);
|
99
test/js/util.test.ts
Normal file
99
test/js/util.test.ts
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
/** @fileoverview Test for utils.ts. */
|
||||||
|
|
||||||
|
import { allocMem, allocMemPages, numToString, testables, toBinary, toHex } from "../../js/util";
|
||||||
|
|
||||||
|
describe('garbage', () => {
|
||||||
|
it('returns 0 <= x <= 255', () => {
|
||||||
|
for (let i = 0; i < 1024; i++) {
|
||||||
|
expect(testables.garbage()).toBeGreaterThanOrEqual(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('allocMem', () => {
|
||||||
|
it('returns an array of the correct size', () => {
|
||||||
|
expect(allocMem(2048).length).toBe(2048);
|
||||||
|
|
||||||
|
});
|
||||||
|
it('has 0xff and 0x00 patterns', () => {
|
||||||
|
let memory = allocMem(2048);
|
||||||
|
expect(memory[0]).toBe(0xff);
|
||||||
|
expect(memory[1]).toBe(0xff);
|
||||||
|
expect(memory[2]).toBe(0x00);
|
||||||
|
expect(memory[3]).toBe(0x00);
|
||||||
|
expect(memory[4]).toBe(0xff);
|
||||||
|
});
|
||||||
|
it('has garbage in the right places', () => {
|
||||||
|
let memory = allocMem(0x800);
|
||||||
|
for (let i = 0; i < 0x800; i += 0x200) {
|
||||||
|
let passed = memory[i + 0x28] != 0xff
|
||||||
|
&& memory[i + 0x29] != 0xff
|
||||||
|
&& memory[i + 0x68] != 0xff
|
||||||
|
&& memory[i + 0x69] != 0xff;
|
||||||
|
if (passed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fail('garbage not found');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('allocMemPages', () => {
|
||||||
|
it('allocates 256 * the size', () => {
|
||||||
|
expect(allocMemPages(5).length).toBe(5 * 256);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('toHex', () => {
|
||||||
|
it('converts an odd number of characters', () => {
|
||||||
|
expect(toHex(0xfedcb, 5)).toEqual("FEDCB");
|
||||||
|
});
|
||||||
|
it('correctly guesses byte values', () => {
|
||||||
|
expect(toHex(0xa5)).toEqual("A5");
|
||||||
|
});
|
||||||
|
it('correctly guesses word values', () => {
|
||||||
|
expect(toHex(0x1abc)).toEqual("1ABC");
|
||||||
|
});
|
||||||
|
it('only uses the bottom work of larger values', () => {
|
||||||
|
expect(toHex(0xabcdef)).toEqual("CDEF");
|
||||||
|
});
|
||||||
|
it('correctly prepends zeros', () => {
|
||||||
|
expect(toHex(0xa5, 4)).toEqual("00A5");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('toBinary', () => {
|
||||||
|
it('has 8 digits for zero', () => {
|
||||||
|
expect(toBinary(0x00)).toEqual("00000000");
|
||||||
|
});
|
||||||
|
it('correctly sets bits', () => {
|
||||||
|
expect(toBinary(0xa5)).toEqual("10100101");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('gup', () => {
|
||||||
|
// untestable due to direct reference to window.location
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('hup', () => {
|
||||||
|
// untestable due to direct reference to window.location
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('numToString', () => {
|
||||||
|
it('packs a zero byte into a string of all zeros', () => {
|
||||||
|
expect(numToString(0x00)).toEqual("\0\0\0\0");
|
||||||
|
});
|
||||||
|
it('packs a byte in the printable ASCII range into a zero-padded string',
|
||||||
|
() => {
|
||||||
|
expect(numToString(0x41)).toEqual("A\0\0\0");
|
||||||
|
});
|
||||||
|
it('packs a word into a string', () => {
|
||||||
|
expect(numToString(0x4142)).toEqual("BA\0\0");
|
||||||
|
});
|
||||||
|
it('packs a 32-bit value into a string', () => {
|
||||||
|
expect(numToString(0x41424344)).toEqual("DCBA");
|
||||||
|
});
|
||||||
|
it('ignores more than 32 bits', () => {
|
||||||
|
expect(numToString(0x4142434445)).toEqual("EDCB");
|
||||||
|
});
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user