8bitworkshop/src/common/ecs/decoder.ts

204 lines
6.7 KiB
TypeScript

import { SourceLocation } from "../workertypes";
import { DataValue, ECSError } from "./ecs";
export interface DecoderResult {
properties: {[name: string] : DataValue}
}
abstract class LineDecoder {
curline: number = 0; // for debugging, zero-indexed
lines : string[][]; // array of token arrays
constructor(
text: string
) {
// split the text into lines and into tokens
this.lines = text.split('\n').map(s => s.trim()).filter(s => !!s).map(s => s.split(/\s+/));
}
decodeBits(s: string, n: number, msbfirst: boolean) {
if (s.length != n) throw new ECSError(`Expected ${n} characters`);
let b = 0;
for (let i=0; i<n; i++) {
let bit;
let ch = s.charAt(i);
if (ch == 'x' || ch == 'X' || ch == '1') bit = 1;
else if (ch == '.' || ch == '0') bit = 0;
else throw new ECSError('need x or . (or 0 or 1)');
if (bit) {
if (msbfirst) b |= 1 << (n-1-i);
else b |= 1 << i;
}
}
return b;
}
assertTokens(toks: string[], count: number) {
if (toks.length != count) throw new ECSError(`Expected ${count} tokens on line.`);
}
hex(s: string) {
let v = parseInt(s, 16);
if (isNaN(v)) throw new ECSError(`Invalid hex value: ${s}`)
return v;
}
getErrorLocation($loc: SourceLocation): SourceLocation {
// TODO: blank lines mess this up
$loc.line += this.curline + 1;
return $loc;
}
abstract parse() : DecoderResult;
}
export class VCSSpriteDecoder extends LineDecoder {
parse() {
let height = this.lines.length;
let bitmapdata = new Uint8Array(height);
let colormapdata = new Uint8Array(height);
for (let i=0; i<height; i++) {
this.curline = height - 1 - i;
let toks = this.lines[this.curline];
this.assertTokens(toks, 2);
bitmapdata[i] = this.decodeBits(toks[0], 8, true);
colormapdata[i] = this.hex(toks[1]);
}
return {
properties: {
bitmapdata, colormapdata, height: height-1
}
}
}
}
export class VCSBitmapDecoder extends LineDecoder {
parse() {
let height = this.lines.length;
let bitmapdata = new Uint8Array(height);
for (let i=0; i<height; i++) {
this.curline = height - 1 - i;
let toks = this.lines[this.curline];
this.assertTokens(toks, 1);
bitmapdata[i] = this.decodeBits(toks[0], 8, true);
}
return {
properties: {
bitmapdata, height: height-1
}
}
}
}
export class VCSPlayfieldDecoder extends LineDecoder {
parse() {
let height = this.lines.length;
let pf = new Uint32Array(height);
for (let i=0; i<height; i++) {
this.curline = height - 1 - i;
let toks = this.lines[this.curline];
this.assertTokens(toks, 1);
let pf0 = this.decodeBits(toks[0].substring(0,4), 4, false) << 4;
let pf1 = this.decodeBits(toks[0].substring(4,12), 8, true);
let pf2 = this.decodeBits(toks[0].substring(12,20), 8, false);
pf[i] = (pf0 << 0) | (pf1 << 8) | (pf2 << 16);
}
return {
properties: {
pf
}
}
}
}
export class VCSVersatilePlayfieldDecoder extends LineDecoder {
parse() {
let height = this.lines.length;
let data = new Uint8Array(height * 2);
data.fill(0x3f);
// pf0 pf1 pf2 colupf colubk ctrlpf trash
const regs = [0x0d, 0x0e, 0x0f, 0x08, 0x09, 0x0a, 0x3f];
let prev = [0,0,0,0,0,0,0];
let cur = [0,0,0,0,0,0,0];
for (let i=0; i<height; i++) {
let dataofs = height*2 - i*2;
this.curline = i;
let toks = this.lines[this.curline];
if (toks.length == 2) {
data[dataofs - 1] = this.hex(toks[0]);
data[dataofs - 2] = this.hex(toks[1]);
continue;
}
this.assertTokens(toks, 4);
cur[0] = this.decodeBits(toks[0].substring(0,4), 4, false) << 4;
cur[1] = this.decodeBits(toks[0].substring(4,12), 8, true);
cur[2] = this.decodeBits(toks[0].substring(12,20), 8, false);
if (toks[1] != '..') cur[3] = this.hex(toks[1]);
if (toks[2] != '..') cur[4] = this.hex(toks[2]);
if (toks[3] != '..') cur[5] = this.hex(toks[3]);
let changed = [];
for (let j=0; j<cur.length; j++) {
if (cur[j] != prev[j])
changed.push(j);
}
if (changed.length > 1) {
console.log(changed, cur, prev);
throw new ECSError(`More than one register change in line ${i+1}: [${changed}]`);
}
let chgidx = changed.length ? changed[0] : regs.length-1;
data[dataofs - 1] = regs[chgidx];
data[dataofs - 2] = cur[chgidx];
prev[chgidx] = cur[chgidx];
}
return {
properties: {
data
}
}
}
}
export class VCSBitmap48Decoder extends LineDecoder {
parse() {
let height = this.lines.length;
let bitmap0 = new Uint8Array(height);
let bitmap1 = new Uint8Array(height);
let bitmap2 = new Uint8Array(height);
let bitmap3 = new Uint8Array(height);
let bitmap4 = new Uint8Array(height);
let bitmap5 = new Uint8Array(height);
for (let i=0; i<height; i++) {
this.curline = height - 1 - i;
let toks = this.lines[this.curline];
this.assertTokens(toks, 1);
bitmap0[i] = this.decodeBits(toks[0].slice(0,8), 8, true);
bitmap1[i] = this.decodeBits(toks[0].slice(8,16), 8, true);
bitmap2[i] = this.decodeBits(toks[0].slice(16,24), 8, true);
bitmap3[i] = this.decodeBits(toks[0].slice(24,32), 8, true);
bitmap4[i] = this.decodeBits(toks[0].slice(32,40), 8, true);
bitmap5[i] = this.decodeBits(toks[0].slice(40,48), 8, true);
}
return {
properties: {
bitmap0, bitmap1, bitmap2, bitmap3, bitmap4, bitmap5,
height: height-1
}
}
}
}
export function newDecoder(name: string, text: string) : LineDecoder | undefined {
let cons = (DECODERS as any)[name];
if (cons) return new cons(text);
}
const DECODERS = {
'vcs_sprite': VCSSpriteDecoder,
'vcs_bitmap': VCSBitmapDecoder,
'vcs_playfield': VCSPlayfieldDecoder,
'vcs_versatile': VCSVersatilePlayfieldDecoder,
'vcs_bitmap48': VCSBitmap48Decoder,
}