diff --git a/README.md b/README.md index 7e0c708..eb2ad95 100644 --- a/README.md +++ b/README.md @@ -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? diff --git a/example.js b/example.js new file mode 100644 index 0000000..a980b7d --- /dev/null +++ b/example.js @@ -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; +} diff --git a/gir.html b/gir.html index bb28fe0..56dd7f6 100644 --- a/gir.html +++ b/gir.html @@ -4,6 +4,7 @@ Gir testbench +

Open the JavaScript console

diff --git a/gir.js b/gir.js index f0ef54b..e5f91ef 100644 --- a/gir.js +++ b/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; -}