Split ircbotRun() into its own file
This commit is contained in:
parent
316e839804
commit
5e3f11b0fa
|
@ -20,9 +20,6 @@ Gir supports following optimizations:
|
|||
|
||||
TODO
|
||||
----
|
||||
### gir.js
|
||||
* Move `ircbotRun()` into its own file
|
||||
|
||||
### gir.html
|
||||
* Implement a UI
|
||||
|
||||
|
@ -30,4 +27,5 @@ TODO
|
|||
* Document the overall architecture
|
||||
|
||||
### General
|
||||
* Make Gir use modules
|
||||
* Get this on NPM?
|
||||
|
|
|
@ -0,0 +1,161 @@
|
|||
const programCacheSize = 16;
|
||||
// string → {compiled: [flatCommandObjects], extensions: bool}
|
||||
let programCache = new Map();
|
||||
|
||||
// (string, bool) → [flatCommandObjects]
|
||||
function cachedCompile(program, enableExtensions = true) {
|
||||
function compileToCache() {
|
||||
// Are we already in the cache? If yes, drop the old one,
|
||||
// since something must have been changed if we're called
|
||||
if(programCache.has(program)) {
|
||||
programCache.delete(program);
|
||||
}
|
||||
|
||||
let compiled = compile(program, enableExtensions);
|
||||
programCache.set(program, {compiled: compiled,
|
||||
extensions: enableExtensions});
|
||||
|
||||
// Are we over the cache size? If yes, drop the least
|
||||
// recently added
|
||||
if(programCache.size > programCacheSize) {
|
||||
console.log('delete');
|
||||
// .keys() gives them in insertion order. Since
|
||||
// there isn't a nice way to extract a thing out
|
||||
// of an iterator, use a for loop and break out
|
||||
// after first round
|
||||
for(let leastRecentlyUsed of programCache.keys()) {
|
||||
programCache.delete(leastRecentlyUsed);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(programCache.has(program)) {
|
||||
// There is a compiled version of this program
|
||||
let {compiled, extensions} = programCache.get(program);
|
||||
|
||||
// If extensions enabled state is the same as ours, we can
|
||||
// just update the useOrder and return this
|
||||
// If not, we need to compile a new program
|
||||
if(extensions == enableExtensions) {
|
||||
return compiled;
|
||||
} else {
|
||||
compileToCache();
|
||||
let {compiled} = programCache.get(program);
|
||||
return compiled;
|
||||
}
|
||||
} else {
|
||||
compileToCache();
|
||||
let {compiled} = programCache.get(program);
|
||||
return compiled;
|
||||
}
|
||||
}
|
||||
|
||||
// (string, string, int) → string
|
||||
function ircbotRun(program, input, maxCycles = 400000) {
|
||||
let compiled = cachedCompile(program);
|
||||
let vm = newVM(compiled, encodeUTF8(input));
|
||||
|
||||
let result = runVM(vm, maxCycles);
|
||||
let output = decodeUTF8(result.state.output);
|
||||
|
||||
// Replace all characters < 0x20 except for IRC formatting codes
|
||||
// with their graphical representations at U+24xx
|
||||
let formattingChars = [0x02, 0x03, 0x0f, 0x12, 0x15];
|
||||
output = Array.from(output).map(c => {
|
||||
let codePoint = c.codePointAt(0);
|
||||
if(codePoint < 0x20 && !formattingChars.includes(codePoint)) {
|
||||
return String.fromCodePoint(0x2400 + codePoint);
|
||||
} else {
|
||||
return c;
|
||||
}
|
||||
}).join('');
|
||||
|
||||
// Did we run into maxCycles?
|
||||
let executedTooLong = maxCycles != null &&
|
||||
result.cycles >= maxCycles;
|
||||
|
||||
// If there was either no output or a breakpoint triggered, dump
|
||||
// tape to output instead
|
||||
if(output.length == 0 || result.breakPointReached) {
|
||||
// If it was a breakpoint, mark it with [BP]
|
||||
if(result.breakPointReached) {
|
||||
output += '[BP]';
|
||||
}
|
||||
|
||||
// Get the tape head we should have here
|
||||
// Both the program completing succesfully and a breakpoint
|
||||
// will leave the tape head "where it should be". The cycle
|
||||
// limit can however stop a program at any point. Therefore
|
||||
// in such cases more useful is the last index that was
|
||||
// accessed
|
||||
let tapeHead = executedTooLong ? result.lastIndex : result.state.tapeHead;
|
||||
|
||||
// Find min and max of the existant array indinces, since
|
||||
// there is no good way to easily get them and we need them
|
||||
// for the output
|
||||
// Default to both being set to tapeHead, because that way
|
||||
// even if there are no indices we can get values that
|
||||
// the rest of the code can work with
|
||||
let memoryIndices = Array.from(result.state.memory.keys());
|
||||
let min = memoryIndices.reduce((x,y) => Math.min(x, y), tapeHead);
|
||||
let max = memoryIndices.reduce((x,y) => Math.max(x, y), tapeHead);
|
||||
|
||||
// Get 15 cells of context on each side of tape head
|
||||
// Exception is if max or min comes up before that, in which
|
||||
// Case move the extra to the other
|
||||
let start = tapeHead - 15;
|
||||
let end = tapeHead + 15;
|
||||
|
||||
if(start < min && end > max) {
|
||||
// Both ends fall out of bounds
|
||||
start = min;
|
||||
end = max;
|
||||
} else if(start < min && end <= max) {
|
||||
// Only start falls out of bounds
|
||||
// Add the number of cells to the part after the
|
||||
// head, but clamp that to at maximum max
|
||||
end = Math.min(end + (min - start), max);
|
||||
start = min;
|
||||
} else if(start >= min && end > max) {
|
||||
// Only end falls out of bounds
|
||||
// Do reverse of previous
|
||||
start = Math.max(start - (end - max), min);
|
||||
end = max;
|
||||
}
|
||||
|
||||
let cells = [];
|
||||
for(let i = start; i <= end; i++) {
|
||||
let cell = ''
|
||||
// 0 if cell doesn't exist
|
||||
if(result.state.memory.has(i)) {
|
||||
cell = result.state.memory.get(i).toString();
|
||||
} else {
|
||||
cell = '0';
|
||||
}
|
||||
|
||||
// Add [] around the cell if tape head is there
|
||||
if(i == tapeHead) {
|
||||
cell = `[${cell}]`;
|
||||
}
|
||||
|
||||
cells.push(cell);
|
||||
}
|
||||
|
||||
// If we don't display the start/end of the tape, add …
|
||||
output += `{${min}…${max}}(${start > min ? '… ' : ''}${cells.join(' ')}${end < max ? ' …' : ''})`
|
||||
}
|
||||
|
||||
// Add «TLE» to signify execution taking too long
|
||||
if(executedTooLong) {
|
||||
output += '«TLE»';
|
||||
}
|
||||
|
||||
// If there was a problem with parsing an int, throw an Error
|
||||
if(result.intParseFailed) {
|
||||
let context = decodeUTF8(result.state.input).slice(0, 3);
|
||||
throw new IntParseError(`';': couldn't read number (near '${context})'`);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
1
gir.html
1
gir.html
|
@ -4,6 +4,7 @@
|
|||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>Gir testbench</title>
|
||||
<script type="text/javascript" src="gir.js"></script>
|
||||
<script type="text/javascript" src="example.js"></script>
|
||||
</head>
|
||||
<html>
|
||||
<p>Open the JavaScript console</p>
|
||||
|
|
163
gir.js
163
gir.js
|
@ -949,166 +949,3 @@ function decodeUTF8(encoded) {
|
|||
function compile(program, enableExtensions = true) {
|
||||
return optimize(parse(program, enableExtensions));
|
||||
}
|
||||
|
||||
const programCacheSize = 16;
|
||||
// string → {compiled: [flatCommandObjects], extensions: bool}
|
||||
let programCache = new Map();
|
||||
|
||||
// (string, bool) → [flatCommandObjects]
|
||||
function cachedCompile(program, enableExtensions = true) {
|
||||
function compileToCache() {
|
||||
console.log('compile'); //debg
|
||||
// Are we already in the cache? If yes, drop the old one,
|
||||
// since something must have been changed if we're called
|
||||
if(programCache.has(program)) {
|
||||
programCache.delete(program);
|
||||
}
|
||||
|
||||
let compiled = compile(program, enableExtensions);
|
||||
programCache.set(program, {compiled: compiled,
|
||||
extensions: enableExtensions});
|
||||
|
||||
// Are we over the cache size? If yes, drop the least
|
||||
// recently added
|
||||
if(programCache.size > programCacheSize) {
|
||||
console.log('delete');
|
||||
// .keys() gives them in insertion order. Since
|
||||
// there isn't a nice way to extract a thing out
|
||||
// of an iterator, use a for loop and break out
|
||||
// after first round
|
||||
for(let leastRecentlyUsed of programCache.keys()) {
|
||||
programCache.delete(leastRecentlyUsed);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(programCache.has(program)) {
|
||||
// There is a compiled version of this program
|
||||
let {compiled, extensions} = programCache.get(program);
|
||||
|
||||
// If extensions enabled state is the same as ours, we can
|
||||
// just update the useOrder and return this
|
||||
// If not, we need to compile a new program
|
||||
if(extensions == enableExtensions) {
|
||||
return compiled;
|
||||
} else {
|
||||
compileToCache();
|
||||
let {compiled} = programCache.get(program);
|
||||
return compiled;
|
||||
}
|
||||
} else {
|
||||
compileToCache();
|
||||
let {compiled} = programCache.get(program);
|
||||
return compiled;
|
||||
}
|
||||
}
|
||||
|
||||
// (string, string, int) → string
|
||||
function ircbotRun(program, input, maxCycles = 400000) {
|
||||
let compiled = cachedCompile(program);
|
||||
let vm = newVM(compiled, encodeUTF8(input));
|
||||
|
||||
let result = runVM(vm, maxCycles);
|
||||
let output = decodeUTF8(result.state.output);
|
||||
|
||||
// Replace all characters < 0x20 except for IRC formatting codes
|
||||
// with their graphical representations at U+24xx
|
||||
let formattingChars = [0x02, 0x03, 0x0f, 0x12, 0x15];
|
||||
output = Array.from(output).map(c => {
|
||||
let codePoint = c.codePointAt(0);
|
||||
if(codePoint < 0x20 && !formattingChars.includes(codePoint)) {
|
||||
return String.fromCodePoint(0x2400 + codePoint);
|
||||
} else {
|
||||
return c;
|
||||
}
|
||||
}).join('');
|
||||
|
||||
// Did we run into maxCycles?
|
||||
let executedTooLong = maxCycles != null &&
|
||||
result.cycles >= maxCycles;
|
||||
|
||||
// If there was either no output or a breakpoint triggered, dump
|
||||
// tape to output instead
|
||||
if(output.length == 0 || result.breakPointReached) {
|
||||
// If it was a breakpoint, mark it with [BP]
|
||||
if(result.breakPointReached) {
|
||||
output += '[BP]';
|
||||
}
|
||||
|
||||
// Get the tape head we should have here
|
||||
// Both the program completing succesfully and a breakpoint
|
||||
// will leave the tape head "where it should be". The cycle
|
||||
// limit can however stop a program at any point. Therefore
|
||||
// in such cases more useful is the last index that was
|
||||
// accessed
|
||||
let tapeHead = executedTooLong ? result.lastIndex : result.state.tapeHead;
|
||||
|
||||
// Find min and max of the existant array indinces, since
|
||||
// there is no good way to easily get them and we need them
|
||||
// for the output
|
||||
// Default to both being set to tapeHead, because that way
|
||||
// even if there are no indices we can get values that
|
||||
// the rest of the code can work with
|
||||
let memoryIndices = Array.from(result.state.memory.keys());
|
||||
let min = memoryIndices.reduce((x,y) => Math.min(x, y), tapeHead);
|
||||
let max = memoryIndices.reduce((x,y) => Math.max(x, y), tapeHead);
|
||||
|
||||
// Get 15 cells of context on each side of tape head
|
||||
// Exception is if max or min comes up before that, in which
|
||||
// Case move the extra to the other
|
||||
let start = tapeHead - 15;
|
||||
let end = tapeHead + 15;
|
||||
|
||||
if(start < min && end > max) {
|
||||
// Both ends fall out of bounds
|
||||
start = min;
|
||||
end = max;
|
||||
} else if(start < min && end <= max) {
|
||||
// Only start falls out of bounds
|
||||
// Add the number of cells to the part after the
|
||||
// head, but clamp that to at maximum max
|
||||
end = Math.min(end + (min - start), max);
|
||||
start = min;
|
||||
} else if(start >= min && end > max) {
|
||||
// Only end falls out of bounds
|
||||
// Do reverse of previous
|
||||
start = Math.max(start - (end - max), min);
|
||||
end = max;
|
||||
}
|
||||
|
||||
let cells = [];
|
||||
for(let i = start; i <= end; i++) {
|
||||
let cell = ''
|
||||
// 0 if cell doesn't exist
|
||||
if(result.state.memory.has(i)) {
|
||||
cell = result.state.memory.get(i).toString();
|
||||
} else {
|
||||
cell = '0';
|
||||
}
|
||||
|
||||
// Add [] around the cell if tape head is there
|
||||
if(i == tapeHead) {
|
||||
cell = `[${cell}]`;
|
||||
}
|
||||
|
||||
cells.push(cell);
|
||||
}
|
||||
|
||||
// If we don't display the start/end of the tape, add …
|
||||
output += `{${min}…${max}}(${start > min ? '… ' : ''}${cells.join(' ')}${end < max ? ' …' : ''})`
|
||||
}
|
||||
|
||||
// Add «TLE» to signify execution taking too long
|
||||
if(executedTooLong) {
|
||||
output += '«TLE»';
|
||||
}
|
||||
|
||||
// If there was a problem with parsing an int, throw an Error
|
||||
if(result.intParseFailed) {
|
||||
let context = decodeUTF8(result.state.input).slice(0, 3);
|
||||
throw new IntParseError(`';': couldn't read number (near '${context})'`);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue