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:
Ian Flanigan 2020-11-01 17:43:48 +01:00 committed by GitHub
parent e8cd85f54a
commit c4df78cf06
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 1989 additions and 1682 deletions

View File

@ -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"
// http://kevin.vanzonneveld.net
@ -18,7 +23,7 @@ export function base64_encode (data) {
// return atob(data);
//}
var b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, ac = 0, enc='', tmp_arr = [];
if (!data) {
@ -38,7 +43,7 @@ export function base64_encode (data) {
h4 = bits & 0x3f;
// 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);
enc = tmp_arr.join('');
@ -55,7 +60,12 @@ export function base64_encode (data) {
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"
// http://kevin.vanzonneveld.net
@ -78,18 +88,17 @@ export function base64_decode(data) {
// return btoa(data);
//}
var b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, ac = 0, tmp_arr = [];
if (!data) {
return data;
return undefined;
}
do { // unpack four hexets into three octets using index points in b64
h1 = b64.indexOf(data.charAt(i++));
h2 = b64.indexOf(data.charAt(i++));
h3 = b64.indexOf(data.charAt(i++));
h4 = b64.indexOf(data.charAt(i++));
do { // unpack four hexets into three octets using index points in B64
h1 = B64.indexOf(data.charAt(i++));
h2 = B64.indexOf(data.charAt(i++));
h3 = B64.indexOf(data.charAt(i++));
h4 = B64.indexOf(data.charAt(i++));
bits = h1<<18 | h2<<12 | h3<<6 | h4;

File diff suppressed because it is too large Load Diff

1759
js/cpu6502.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@ -10,30 +10,43 @@
*/
import { base64_decode, base64_encode } from './base64';
import { byte, memory } from './types';
import { allocMemPages } from './util';
export default function RAM(sp, ep) {
var mem;
var start_page = sp;
var end_page = ep;
export interface State {
/** Start of memory region. */
start: byte;
/** 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 {
start: function() {
start: function () {
return start_page;
},
end: function() {
end: function () {
return end_page;
},
read: function(page, off) {
return mem[(page - start_page) << 8 | off];
read: function (page: byte, offset: byte) {
return mem[(page - start_page) << 8 | offset];
},
write: function(page, off, val) {
mem[(page - start_page) << 8 | off] = val;
write: function (page: byte, offset: byte, val: byte) {
mem[(page - start_page) << 8 | offset] = val;
},
getState: function() {
getState: function (): State {
return {
start: start_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;
end_page = state.end;
mem = base64_decode(state.mem);

View File

@ -1,4 +1,4 @@
var SYMBOLS = {
SYMBOLS = {
/*
0x00: 'GOWARM',
0x03: 'GOSTROUT',

27
js/types.ts Normal file
View 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,
};

View File

@ -9,27 +9,39 @@
* implied warranty.
*/
import { byte, memory, word } from "./types";
/*eslint no-console: 0*/
var hex_digits = '0123456789ABCDEF';
var bin_digits = '01';
const hex_digits = '0123456789ABCDEF';
const bin_digits = '01';
export function allocMem(size) {
function garbage() {
return (Math.random() * 0x100) & 0xff;
}
var result;
/** Returns a random byte. */
function garbage(): byte {
return (Math.random() * 0x100) & 0xff;
}
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) {
result = new Uint8Array(size);
} else {
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;
}
// 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 + 0x29] = garbage();
result[idx + 0x68] = garbage();
@ -38,23 +50,33 @@ export function allocMem(size) {
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);
}
export function bytify(ary) {
var result = ary;
/** Returns a new Uint8Array for the input array. */
export function bytify(ary: number[]): memory {
let result: number[] | Uint8Array = ary;
if (window.Uint8Array) {
result = new Uint8Array(ary);
}
return result;
}
/** Writes to the console. */
export function debug(...args: any[]): void;
export function debug() {
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) {
n = v < 256 ? 2 : 4;
}
@ -66,7 +88,11 @@ export function toHex(v, n) {
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 = '';
for (var idx = 0; idx < 8; idx++) {
result = bin_digits[v & 0x01] + result;
@ -75,29 +101,36 @@ export function toBinary(v) {
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
export function gup( name )
{
name = name.replace(/[[]/,'\\[').replace(/[\]]/,'\\]');
var regexS = '[\\?&]'+name+'=([^&#]*)';
var regex = new RegExp( regexS );
var results = regex.exec( window.location.href );
if( !results )
export function gup(name: string) {
name = name.replace(/[[]/, '\\[').replace(/[\]]/, '\\]');
var regexS = '[\\?&]' + name + '=([^&#]*)';
var regex = new RegExp(regexS);
var results = regex.exec(window.location.href);
if (!results)
return '';
else
return results[1];
}
/** Returns the URL fragment. */
export function hup() {
var regex = new RegExp('#(.*)');
var results = regex.exec(window.location.hash);
if ( !results )
if (!results)
return '';
else
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 = '';
for (let idx = 0; idx < 4; idx++) {
result += String.fromCharCode(num & 0xff);

99
test/js/util.test.ts Normal file
View 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");
});
});