701 lines
16 KiB
HTML
701 lines
16 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<title>ilo.js :: Konilo in the Browser</title>
|
|
<meta charset="utf-8">
|
|
<script>
|
|
// LocalData, (c) 2016 Pawel; MIT Licensed
|
|
(function() {
|
|
var win = typeof window !== 'undefined' ? window : {}
|
|
var indexedDB = win.indexedDB || win.mozIndexedDB || win.webkitIndexedDB || win.msIndexedDB;
|
|
if (typeof window !== 'undefined' && !indexedDB) {
|
|
console.error('indexDB not supported');
|
|
return;
|
|
}
|
|
var db,
|
|
request = indexedDB.open('ldb', 1);
|
|
request.onsuccess = function(evt) {
|
|
db = this.result;
|
|
};
|
|
request.onerror = function(event) {
|
|
console.error('indexedDB request error');
|
|
console.log(event);
|
|
};
|
|
|
|
request.onupgradeneeded = function(event) {
|
|
db = null;
|
|
var store = event.target.result.createObjectStore('s', {
|
|
keyPath: 'k'
|
|
});
|
|
|
|
store.transaction.oncomplete = function (e){
|
|
db = e.target.db;
|
|
};
|
|
};
|
|
|
|
// if using proxy mode comment this
|
|
|
|
var ldb = {
|
|
ready: false,
|
|
get: function(key, callback) {
|
|
if (!db) {
|
|
setTimeout(function () { ldb.get(key, callback); }, 50);
|
|
return;
|
|
}
|
|
db.transaction('s').objectStore('s').get(key).onsuccess = function(event) {
|
|
var result = (event.target.result && event.target.result['v']) || null;
|
|
callback(result);
|
|
};
|
|
},
|
|
set: function(key, value, callback) {
|
|
if (!db) {
|
|
setTimeout(function () { ldb.set(key, value, callback); }, 50);
|
|
return;
|
|
}
|
|
let txn = db.transaction('s', 'readwrite');
|
|
txn.oncomplete = function(event) {
|
|
var toString$ = {}.toString;
|
|
if (toString$.call(callback).slice(8, -1) === 'Function') {
|
|
callback();
|
|
}
|
|
}
|
|
txn.objectStore('s').put({
|
|
'k': key,
|
|
'v': value,
|
|
});
|
|
txn.commit();
|
|
},
|
|
delete: function(key, callback) {
|
|
if (!db) {
|
|
setTimeout(function () { ldb.delete(key, callback); }, 50);
|
|
return;
|
|
}
|
|
db.transaction('s', 'readwrite').objectStore('s').delete(key).onsuccess = function(event) {
|
|
if (callback) callback();
|
|
};
|
|
},
|
|
list: function(callback) {
|
|
if (!db) {
|
|
setTimeout(function () { ldb.list(callback); }, 50);
|
|
return;
|
|
}
|
|
db.transaction('s').objectStore('s').getAllKeys().onsuccess = function(event) {
|
|
var result = (event.target.result) || null;
|
|
callback(result);
|
|
};
|
|
},
|
|
getAll: function(callback) {
|
|
if (!db) {
|
|
setTimeout(function () { ldb.getAll(callback); }, 50);
|
|
return;
|
|
}
|
|
db.transaction('s').objectStore('s').getAll().onsuccess = function(event) {
|
|
var result = (event.target.result) || null;
|
|
callback(result);
|
|
};
|
|
},
|
|
clear: function(callback) {
|
|
if (!db) {
|
|
setTimeout(function () { ldb.clear(callback); }, 50);
|
|
return;
|
|
}
|
|
db.transaction('s', 'readwrite').objectStore('s').clear().onsuccess = function(event) {
|
|
if (callback) callback();
|
|
};
|
|
},
|
|
}
|
|
const exported = {
|
|
'get': ldb.get,
|
|
'set': ldb.set,
|
|
'delete': ldb.delete,
|
|
'list': ldb.list,
|
|
'getAll': ldb.getAll,
|
|
'clear': ldb.clear,
|
|
};
|
|
win['ldb'] = exported
|
|
if (typeof module !== 'undefined') {
|
|
module.exports = exported;
|
|
}
|
|
})();
|
|
</script>
|
|
<script>
|
|
let m = new Int32Array(65536);
|
|
let blocks = new Int32Array(1024*1024);
|
|
var ds = new Int32Array(33);
|
|
var as = new Int32Array(257);
|
|
|
|
var ip = 0;
|
|
var sp = 0;
|
|
var rp = 0;
|
|
|
|
var input = "";
|
|
|
|
function push(v) {
|
|
ds[sp + 1] = v;
|
|
sp += 1;
|
|
}
|
|
|
|
function pop() {
|
|
sp -= 1;
|
|
return ds[sp + 1];
|
|
}
|
|
|
|
function download_blocks(int32Array, filename) {
|
|
var uint8Array = new Uint8Array(int32Array.buffer);
|
|
var blob = new Blob([uint8Array], { type: 'application/octet-stream' });
|
|
|
|
var link = document.createElement('a');
|
|
link.href = URL.createObjectURL(blob);
|
|
link.download = filename;
|
|
link.click();
|
|
URL.revokeObjectURL(link.href);
|
|
}
|
|
|
|
function get_file(f, target) {
|
|
fetch(f)
|
|
.then(response => response.arrayBuffer())
|
|
.then(buffer => {
|
|
const dataView = new DataView(buffer);
|
|
for (let i = 0; i < target.length; i++) {
|
|
target[i] = dataView.getInt32(i * 4, true);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error loading the file:', error);
|
|
});
|
|
}
|
|
|
|
function upload(file, target) {
|
|
var reader = new FileReader();
|
|
|
|
reader.onload = function(event) {
|
|
var arrayBuffer = event.target.result;
|
|
var dataView = new Int32Array(arrayBuffer);
|
|
target.set(dataView);
|
|
};
|
|
|
|
reader.readAsArrayBuffer(file);
|
|
}
|
|
|
|
function enforce32BitSignedInteger(value) {
|
|
const int32Max = Math.pow(2, 31) - 1;
|
|
const int32Min = -Math.pow(2, 31);
|
|
value = value | 0;
|
|
value = ((value - int32Min) % (int32Max - int32Min + 1)) + int32Min;
|
|
return value;
|
|
}
|
|
|
|
function guard() {
|
|
ds[sp] = enforce32BitSignedInteger(ds[sp]);
|
|
if (sp >= 2) {
|
|
ds[sp - 1] = enforce32BitSignedInteger(ds[sp - 1]);
|
|
}
|
|
}
|
|
|
|
function save_ip() {
|
|
rp += 1;
|
|
as[rp] = ip;
|
|
}
|
|
|
|
var max_lines = 256;
|
|
var cycles = 25000;
|
|
|
|
function limit_output_lines() {
|
|
const display = document.getElementById('display');
|
|
const lines = display.value.split('\n').slice(-(1+max_lines));
|
|
let k = '';
|
|
let i = '';
|
|
for (i in lines) {
|
|
k = k.concat(lines[i], '\n');
|
|
}
|
|
display.value = k.substring(0, k.length - 1);
|
|
}
|
|
|
|
async function sleep(ms) {
|
|
const display = document.getElementById("display")
|
|
display.scrollTop = display.scrollHeight;
|
|
console.log('sleep()');
|
|
limit_output_lines();
|
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
}
|
|
|
|
function no() { }
|
|
|
|
function li() {
|
|
ip += 1;
|
|
push(m[ip]);
|
|
}
|
|
|
|
function du() {
|
|
push(ds[sp]);
|
|
}
|
|
|
|
function dr() {
|
|
ds[sp] = 0;
|
|
sp -= 1;
|
|
}
|
|
|
|
function sw() {
|
|
const a = ds[sp];
|
|
ds[sp] = ds[sp - 1];
|
|
ds[sp - 1] = a;
|
|
}
|
|
|
|
function pu() {
|
|
rp += 1;
|
|
as[rp] = pop();
|
|
}
|
|
|
|
function po() {
|
|
push(as[rp]);
|
|
rp -= 1;
|
|
}
|
|
|
|
function ju() {
|
|
ip = pop() - 1;
|
|
}
|
|
|
|
function ca() {
|
|
save_ip();
|
|
ip = pop() - 1;
|
|
}
|
|
|
|
function cc() {
|
|
var a = pop();
|
|
if (pop()) {
|
|
save_ip();
|
|
ip = a - 1;
|
|
}
|
|
}
|
|
|
|
function cj() {
|
|
var a = pop();
|
|
if (pop()) {
|
|
ip = a - 1;
|
|
}
|
|
}
|
|
|
|
function re() {
|
|
ip = as[rp];
|
|
rp -= 1;
|
|
}
|
|
|
|
function eq() {
|
|
ds[sp - 1] = (ds[sp - 1] == ds[sp]) ? -1 : 0;
|
|
sp -= 1;
|
|
}
|
|
|
|
function ne() {
|
|
ds[sp - 1] = (ds[sp - 1] != ds[sp]) ? -1 : 0;
|
|
sp -= 1;
|
|
}
|
|
|
|
function lt() {
|
|
ds[sp - 1] = (ds[sp - 1] < ds[sp]) ? -1 : 0;
|
|
sp -= 1;
|
|
}
|
|
|
|
function gt() {
|
|
ds[sp - 1] = (ds[sp - 1] > ds[sp]) ? -1 : 0;
|
|
sp -= 1;
|
|
}
|
|
|
|
function fe() {
|
|
ds[sp] = m[ds[sp]];
|
|
}
|
|
|
|
function st() {
|
|
m[ds[sp]] = ds[sp - 1];
|
|
sp -= 2;
|
|
}
|
|
|
|
function ad() {
|
|
ds[sp - 1] += ds[sp];
|
|
sp -= 1;
|
|
}
|
|
|
|
function su() {
|
|
ds[sp - 1] -= ds[sp];
|
|
sp -= 1;
|
|
}
|
|
|
|
function mu() {
|
|
ds[sp - 1] *= ds[sp];
|
|
sp -= 1;
|
|
}
|
|
|
|
function di() {
|
|
const a = ds[sp];
|
|
const b = ds[sp - 1];
|
|
ds[sp] = Math.floor(b / a);
|
|
ds[sp - 1] = b % a;
|
|
if (b >= 0 && ds[sp - 1] < 0) {
|
|
ds[sp] += 1;
|
|
ds[sp - 1] -= b;
|
|
}
|
|
}
|
|
|
|
function an() {
|
|
ds[sp - 1] = ds[sp] & ds[sp - 1];
|
|
sp -= 1;
|
|
}
|
|
|
|
function or() {
|
|
ds[sp - 1] = ds[sp] | ds[sp - 1];
|
|
sp -= 1;
|
|
}
|
|
|
|
function xo() {
|
|
ds[sp - 1] = ds[sp] ^ ds[sp - 1];
|
|
sp -= 1;
|
|
}
|
|
|
|
function sl() {
|
|
ds[sp - 1] = ds[sp - 1] << ds[sp];
|
|
sp -= 1;
|
|
}
|
|
|
|
function sr() {
|
|
ds[sp - 1] = ds[sp - 1] >> ds[sp];
|
|
sp -= 1;
|
|
}
|
|
|
|
function cp() {
|
|
let l = pop();
|
|
let d = pop();
|
|
let s = ds[sp];
|
|
ds[sp] = -1;
|
|
|
|
while (l) {
|
|
if (m[d] !== m[s]) {
|
|
ds[sp] = 0;
|
|
}
|
|
l -= 1;
|
|
s += 1;
|
|
d += 1;
|
|
}
|
|
}
|
|
|
|
function cy() {
|
|
let l = pop();
|
|
let d = pop();
|
|
let s = pop();
|
|
|
|
while (l) {
|
|
m[d] = m[s];
|
|
l -= 1;
|
|
s += 1;
|
|
d += 1;
|
|
}
|
|
}
|
|
|
|
function ioa() {
|
|
const a = String.fromCharCode(pop());
|
|
document.getElementById("display").value += a;
|
|
}
|
|
|
|
function iob() {
|
|
const a = input.charCodeAt(0);
|
|
input = input.slice(1);
|
|
push(a);
|
|
}
|
|
|
|
function ioc() {
|
|
const buf = pop();
|
|
const blk = pop();
|
|
let i = 0;
|
|
while (i < 1024) {
|
|
m[buf + i] = blocks[(blk * 1024) + i];
|
|
i += 1;
|
|
}
|
|
}
|
|
|
|
function iod() {
|
|
const buf = pop();
|
|
const blk = pop();
|
|
let i = 0;
|
|
while (i < 1024) {
|
|
blocks[(blk * 1024) + i] = m[buf + i];
|
|
i += 1;
|
|
}
|
|
}
|
|
|
|
function ioe() { }
|
|
|
|
function iof() {
|
|
ip = -1; sp = 0; rp = 0;
|
|
get_file('ilo.rom', m);
|
|
}
|
|
|
|
function iog() {
|
|
ip = 65536;
|
|
input = "";
|
|
}
|
|
|
|
function ioh() {
|
|
push(sp); push(rp);
|
|
}
|
|
|
|
function io() {
|
|
var dev = pop();
|
|
switch (dev) {
|
|
case 0: ioa(); break;
|
|
case 1: iob(); break;
|
|
case 2: ioc(); break;
|
|
case 3: iod(); break;
|
|
case 4: ioe(); break;
|
|
case 5: iof(); break;
|
|
case 6: iog(); break;
|
|
case 7: ioh(); break;
|
|
default: console.log("wtf? " + dev); break;
|
|
}
|
|
}
|
|
|
|
const instructions = [
|
|
no, li, du, dr, sw,
|
|
pu, po, ju, ca, cc,
|
|
cj, re, eq, ne, lt,
|
|
gt, fe, st, ad, su,
|
|
mu, di, an, or, xo,
|
|
sl, sr, cp, cy, io
|
|
];
|
|
|
|
function process(o) {
|
|
if (o >= 0 && o < instructions.length) {
|
|
instructions[o]();
|
|
}
|
|
guard();
|
|
}
|
|
|
|
async function execute() {
|
|
input += " " + document.getElementById("input").value + " \n";
|
|
document.getElementById("input").value = "";
|
|
let ticks = 0;
|
|
while ((ip < 65536) && input.length >= 1 && !isNaN(ip)) {
|
|
var o = m[ip];
|
|
process(o & 0xff);
|
|
process((o >> 8) & 0xff);
|
|
process((o >> 16) & 0xff);
|
|
process((o >> 24) & 0xff);
|
|
ip = ip + 1;
|
|
ticks += 1;
|
|
if (ticks % cycles === 0) {
|
|
ticks = 0;
|
|
await sleep(0);
|
|
}
|
|
}
|
|
await sleep(0);
|
|
console.log("done");
|
|
document.getElementById("display").scrollTop =
|
|
document.getElementById("display").scrollHeight;
|
|
}
|
|
|
|
function use_default() {
|
|
ip = -1; sp = 0; rp = 0;
|
|
execute();
|
|
}
|
|
async function startup() {
|
|
load_prefs();
|
|
var load_rom = document.getElementById("rom-file");
|
|
load_rom.addEventListener("change", function(event) {
|
|
var file = event.target.files[0];
|
|
upload(file, m);
|
|
});
|
|
|
|
var load_blocks = document.getElementById("block-file");
|
|
load_blocks.addEventListener("change", function(event) {
|
|
var file = event.target.files[0];
|
|
upload(file, blocks);
|
|
});
|
|
|
|
var enter = document.getElementById("input").addEventListener('keypress', function(event) {
|
|
if (event.key === 'Enter') {
|
|
execute();
|
|
}
|
|
});
|
|
|
|
get_file('ilo.rom', m);
|
|
get_file('ilo.blocks', blocks);
|
|
await sleep(5000);
|
|
use_default();
|
|
}
|
|
|
|
</script>
|
|
|
|
<style>
|
|
|
|
:root {
|
|
--bg:#050505;
|
|
--fg:#ffd;
|
|
}
|
|
|
|
table {
|
|
border: 0;
|
|
width: 100%;
|
|
}
|
|
|
|
td {
|
|
width: 50%;
|
|
vertical-align: top;
|
|
}
|
|
|
|
* {
|
|
font-family: "Anonymous Pro", "Go Mono", monospace;
|
|
padding: 3px;
|
|
background: #111;
|
|
color: #ffd;
|
|
outline-width: 1px;
|
|
outline-color: #000;
|
|
outline-style: outset;
|
|
margin: 2px;
|
|
font-size: min(2vh, 2vw);
|
|
}
|
|
body {
|
|
background: #111;
|
|
color: #fec;
|
|
outline-style: none;
|
|
}
|
|
textarea {
|
|
background: var(--bg);
|
|
color: var(--fg);
|
|
display: block;
|
|
margin-left: auto;
|
|
margin-right: auto;
|
|
width: min(90vh,90vw);
|
|
height: min(55vh,55vw);
|
|
font-size: min(2vh, 2vw);
|
|
}
|
|
input, button {
|
|
background: #3b3b3b;
|
|
color: #fafada;
|
|
|
|
}
|
|
hr,br {
|
|
background: #fff;
|
|
margin-bottom: .3vh;
|
|
padding-bottom:.005vh;
|
|
}
|
|
#settings {
|
|
display: none;
|
|
}
|
|
td {
|
|
vertical-align: top;
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
|
|
function localLoad(key, defaultValue) {
|
|
const storedValue = localStorage.getItem(key);
|
|
return storedValue !== null ? storedValue : defaultValue;
|
|
}
|
|
|
|
function load_prefs() {
|
|
let a = localLoad('fg', '#ffb000');
|
|
let b = localLoad('bg', '#050505');
|
|
document.querySelector(':root').style.setProperty('--fg', a);
|
|
document.querySelector(':root').style.setProperty('--bg', b);
|
|
cycles = parseInt(localLoad('cycles', 25000));
|
|
max_lines = parseInt(localLoad('max_lines', 256));
|
|
document.getElementById('input_cycles').value = cycles;
|
|
document.getElementById('input_max_lines').value = max_lines;
|
|
}
|
|
|
|
function save_prefs() {
|
|
let a = document.querySelector(':root').style.getPropertyValue('--fg');
|
|
let b = document.querySelector(':root').style.getPropertyValue('--bg');
|
|
localStorage.setItem('fg', a);
|
|
localStorage.setItem('bg', b);
|
|
localStorage.setItem('cycles', cycles);
|
|
localStorage.setItem('max_lines', max_lines);
|
|
}
|
|
|
|
function toggle(x) {
|
|
const e = document.getElementById(x);
|
|
e.style.display = e.style.display == "block" ? "none" : "block";
|
|
}
|
|
|
|
|
|
function default_colors() {
|
|
document.querySelector(':root').style.setProperty('--fg', '#ffb000');
|
|
document.querySelector(':root').style.setProperty('--bg', '#050505');
|
|
}
|
|
function green_colors() {
|
|
document.querySelector(':root').style.setProperty('--fg', '#33ff33');
|
|
document.querySelector(':root').style.setProperty('--bg', '#050505');
|
|
}
|
|
function cyan_colors() {
|
|
document.querySelector(':root').style.setProperty('--fg', '#80ffd5');
|
|
document.querySelector(':root').style.setProperty('--bg', '#050505');
|
|
}
|
|
function plasma_colors() {
|
|
document.querySelector(':root').style.setProperty('--fg', '#fe3d14');
|
|
document.querySelector(':root').style.setProperty('--bg', '#400f15');
|
|
}
|
|
function dark_colors() {
|
|
document.querySelector(':root').style.setProperty('--fg', '#ffd');
|
|
document.querySelector(':root').style.setProperty('--bg', '#050505');
|
|
}
|
|
function light_colors() {
|
|
document.querySelector(':root').style.setProperty('--fg', '#050505');
|
|
document.querySelector(':root').style.setProperty('--bg', '#ffd');
|
|
}
|
|
|
|
</script>
|
|
</head>
|
|
<body onload="startup()"> <!-- startup -->
|
|
<button type="button" id="b_settings" onclick="toggle('settings')">Settings</button>
|
|
<button type="button" id="boot" onclick="use_default()">
|
|
Start Konilo</button> (using default image & blocks)
|
|
<hr>
|
|
|
|
<div id="settings">
|
|
<table>
|
|
<tr><td>
|
|
Use your own image & blocks.<br> <hr> <br>
|
|
ROM :::: <input type="file" id="rom-file" style="width:30vw"> <br>
|
|
Blocks : <input type="file" id="block-file" style="width:30vw"> <br>
|
|
<button type="button" id="b_boot_user" onclick="execute()">Boot</button> <hr>
|
|
<button type="button" id="getblocks" onclick="download_blocks(blocks,'ilo.blocks')">Save Blocks to Device</button>
|
|
</td><td>
|
|
--CONFIG-- <br> <hr> <br>
|
|
Max lines :::::::::::::: <input type="number" id="input_max_lines" min="1"
|
|
value="256" onchange="max_lines=parseInt(this.value)" style="width:15vw"> <br>
|
|
Cycles Between Updates : <input type="number" id="input_cycles" min="1"
|
|
value="25000" onchange="cycles=parseInt(this.value)" style="width:15vw"> <br>
|
|
<hr> COLORS
|
|
<button type="button" id="b_default" onclick="default_colors()">default</button>
|
|
<button type="button" id="b_green" onclick="green_colors()">green</button>
|
|
<button type="button" id="b_cyan" onclick="cyan_colors()">cyan</button>
|
|
<button type="button" id="b_plasma" onclick="plasma_colors()">plasma</button>
|
|
<button type="button" id="b_light" onclick="light_colors()">light</button>
|
|
<button type="button" id="b_dark" onclick="dark_colors()">dark</button>
|
|
<button type="button" id="b_save_prefs" onclick="save_prefs()">Save Prefrrences</button>
|
|
</td></tr>
|
|
</table>
|
|
<hr>
|
|
</div>
|
|
|
|
<textarea rows="24" cols="80" id="display" readonly></textarea>
|
|
<hr>
|
|
<input type="text" style="width: 80vw" id="input">
|
|
<button type="button" id="enter" onclick="execute()">Enter</button>
|
|
</body>
|
|
|
|
<!-- TODO
|
|
|
|
add custom themes
|
|
add indicator for reading/writing blocks
|
|
add indicator for processing + animation
|
|
|
|
|
|
-->
|
|
<!-- CHANGELOG
|
|
|
|
Change default cycles value.
|
|
Added ability to save config + auto loading on page load.
|
|
Amber is now default, previous default under 'dark'.
|
|
Modified cyan.
|
|
-->
|
|
</html>
|
|
|