thisismypassword [Lexaloffle Blog Feed]https://www.lexaloffle.com/bbs/?uid=29645 ZX Spectrum Emulator (WNIP;DPOC) <p> <table><tr><td> <a href="/bbs/?pid=135835#p"> <img src="/bbs/thumbs/pico8_zxspectrum-0.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=135835#p"> zxspectrum</a><br><br> by <a href="/bbs/?uid=29645"> thisismypassword</a> <br><br><br> <a href="/bbs/?pid=135835#p"> [Click to Play]</a> </td></tr></table> </p> <h1>How?</h1> <ul> <li>It's recommended to run it in Pico-8 with &quot;-displays_x 2 -displays_y 2&quot;.</li> <li>If you don't (say, if you run it in the BBS), you'd have to move the mouse around to see the whole window</li> <li> <p>In particular, move the mouse to the bottom-left corner to see the Spectrum prompt</p> </li> <li>You can use the keyboard to type stuff</li> <li>The symbol key is Tab.</li> <li>ZX Spectrum keyboard layout: <a href="http://slady.net/Sinclair-ZX-Spectrum-keyboard/layout/">http://slady.net/Sinclair-ZX-Spectrum-keyboard/layout/</a></li> <li>ZX Spectrum user manual: <a href="https://worldofspectrum.org/ZXBasicManual/">https://worldofspectrum.org/ZXBasicManual/</a></li> <li> <p>Non-spectrum keys like punctuation/backspace automatically press the right shift/symbol combination</p> </li> <li>To load a tape:</li> <li>First type 'j' for load.</li> <li>Then type double-quotes (&quot;) twice. On the BBS or on non-standard keyboard layouts, you're better off pressing Tab+P to get double-quotes. (On BBS, '~' might also work - looks like an emscripten bug).</li> <li>Now, press enter.</li> <li>Only after that, drag a tape (in .tap or .tzx format) to Pico-8.</li> <li> <p>Reset Pico-8 if you want to drag another tape.</p> </li> <li>By default, the arrow keys move the cursor. You can instead have them use the Kempston joystick or the Sinclair joysticks in the pause menu.</li> </ul> <h1>What?</h1> <ul> <li>It's a ZX Spectrum emulator</li> <li>It's quite slow. 2-5 times slower than the real thing.</li> <li>I don't know if sound is broken or just too slow to be ok.</li> <li> <p>Tapes with copy protection won't work.</p> </li> <li>By default, tapes load super-quick if using the loading routines in the ROM. (Can be disabled in pause menu)</li> <li> <p>By default, the CPU is underclocked to maintain responsiveness (since pico-8 can't keep up). (Can be disabled in pause menu)</p> </li> <li>The included Spectrum ROM is copyrighted by Amstrad, who have allowed its use by emulators.</li> </ul> <h1>Wherefore?</h1> <ul> <li> <p>The source code won't bite you if you read it. (But it won't lick you, either)</p> </li> <li>Some highlights from the source:</li> <li>Using the pico-8 memory for the z80 registers, to get the &quot;16 bit reg = two 8 bit regs&quot; behaviour for free(-ish).</li> <li>Using fillp + rectfill to draw each 4x4 block on the screen</li> <li>Only drawing modified (or blinking) areas of the vram.</li> <li>Pre-calculating the flag results of every single addition and add-with-carry (subtraction is easily derived from that)</li> </ul> https://www.lexaloffle.com/bbs/?tid=54562 https://www.lexaloffle.com/bbs/?tid=54562 Fri, 13 Oct 2023 11:49:48 UTC Feature Request with z8lua fork: Poke as an operator <p>Currently, there are two ways to access pico8 memory: peek/poke[2/4] and @/%/$</p> <p>The @/%/$ operators look a bit weird but are faster and better overall.<br /> However, I think their main drawback is that they don't replace the need for poke, causing code that uses them to necessarily be quite inconsistent, imbalanced and weird whenever it needs to combine peeks with pokes.</p> <p>I wanted to check how hard it would be to add the ability to poke into @/%/$, so I created the following fork of z8lua that implements it:<br /> <a href="https://github.com/thisismypassport/z8lua">https://github.com/thisismypassport/z8lua</a></p> <p>Here's how it works:</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre> -- simple poke @0x1000 = 0x1 ?@0x1000 -- outputs 1 -- use of $ as both peek and poke (copying 4 bytes of memory) $0x8000 = $0 -- multiple assignment @1,%2,$4 = 1,2,4 ?@1 -- outputs 1 ?%2 -- outputs 2 ?$4 -- outputs 4 -- compound assignment - worked automatically @1 += 0x80 ?@1 -- outputs 129 (since previously was 1) -- poke metatables meta = setmetatable({}, { __peek=function(self) return &quot;peek&quot; end, __poke=function(self, value) print(&quot;poke &quot;..value) end }) @meta ..= &quot; works&quot; -- prints: &quot;poke peek works&quot; -- reason for case added in check_conflict, for reference function f(a, b) @a, a = b, b end f(0x100, 7) ?@0x100 -- outputs 7 -- note: there is a bug in z8lua where @&quot;1&quot; doesn't work. -- Since pico8 itself doesn't have this bug, I decided not to fix it - not for peek nor for poke. </pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>Comments welcome.</p> https://www.lexaloffle.com/bbs/?tid=54297 https://www.lexaloffle.com/bbs/?tid=54297 Wed, 27 Sep 2023 03:53:12 UTC Repuzzle hint &amp; solution cart (&quot;Resolution&quot;) <p> <table><tr><td> <a href="/bbs/?pid=133720#p"> <img src="/bbs/thumbs/pico8_resolution-1.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=133720#p"> resolution</a><br><br> by <a href="/bbs/?uid=29645"> thisismypassword</a> <br><br><br> <a href="/bbs/?pid=133720#p"> [Click to Play]</a> </td></tr></table> </p> <h1>Repuzzle hint &amp; solution cart</h1> <p>(&quot;Resolution&quot;)</p> <h2>What's Repuzzle?</h2> <p>Sounds like you're here by mistake.</p> <p>Head over to <a href="https://www.lexaloffle.com/bbs/?pid=133521">Repuzzle's page in the BBS</a> and don't come back unless and until you're well and truly stuck.</p> <h2>What does this cart contain?</h2> <p>All hints and solutions for Repuzzle - revealable in gradual order.</p> <p>Whether you need a small pointer for a level or five you're stuck on, or whether you want to see all the solutions - this cart has you covered.</p> <p>Though it won't make getting to the solutions too pleasant!</p> <h2>Controls?</h2> <p>You can use the standard pico8 controls, or you can use the mouse (Mouse wheel and buttons only. The mouse wheel also works inside popups)</p> <p>You can press Ctrl+C to copy everything on-screen.</p> <h2>FAQ</h2> <h3>Why are the popups so annoying?</h3> <p>To discourage you from viewing the bigger hints &amp; solutions.</p> <h3>Why is navigation so annoying? I keep pressing the wrong buttons.</h3> <p>Same - to discourage you from having too much fun navigating the solutions.</p> <h3>Why is the cart so ugly? white on gray?!</h3> <p>Yes. Same. Discourages you from looking at the hints for too long.</p> <p>Any more questions?</p> https://www.lexaloffle.com/bbs/?tid=53923 https://www.lexaloffle.com/bbs/?tid=53923 Wed, 30 Aug 2023 04:19:00 UTC Multiple shorthands in a line spill into following line <p>I found some confusing behavior when trying to put multiple if/while shorthands on the same line, e.g.:</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>if (x1) if(x2) print('true') print('anyway')</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>This prints 'anyway' only if x1 is true - whereas I would've expected it to print 'anyway', well - anyway.</p> <p>It looks like line breaks only terminate a single if/while shorthand, causing the next line to be inside the other shorthand.</p> <p>It used to be that this code was generating a runtime error, but now it just silently does something really unexpected instead.</p> https://www.lexaloffle.com/bbs/?tid=53419 https://www.lexaloffle.com/bbs/?tid=53419 Sat, 15 Jul 2023 08:10:23 UTC Rotate-assignment operators don't work anymore <p>Hi <a href="https://www.lexaloffle.com/bbs/?uid=1"> @zep</a>, I found today that &gt;&gt;&lt;= and &lt;&lt;&gt;= don't work in 0.2.5g (a regression, as they worked in the past for sure - didn't check which version broke them)</p> <p>E.g:</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>a=3 a&lt;&lt;&gt;=5 print(a) -- 3 now</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> https://www.lexaloffle.com/bbs/?tid=52910 https://www.lexaloffle.com/bbs/?tid=52910 Mon, 29 May 2023 20:56:14 UTC x+=y now costs more than x=x+y <p>There seems to have been a change in the virtual cpu cost of +=</p> <p>Previously, in 0.2.4b, both x=x+y and x+=y cost 1 cycles.<br /> Now, in 0.2.5g, x=x+y costs 1 cycle while x+=y costs 2 cycles.<br /> (Where x and y are locals)</p> <p>The same happens with other operators that cost 1 cycle, e.g. -=/- and &amp;=/&amp;</p> <p>This feels like a bug since I wouldn't except x+=y to be costlier than x=x+y</p> <p>Below code shows the perf. difference.<br /> <div><div><input type="button" value=" Show " onClick="if (this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display != '') { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = ''; this.innerText = ''; this.value = ' Hide '; } else { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = 'none'; this.innerText = ''; this.value = ' Show '; }"></div><div><div style="display: none;"></p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>function testme_calib(name, func, calibrate_func, ...) -- based on https://www.lexaloffle.com/bbs/?pid=60198#p local n = 1024 -- calibrate flip() local unused -- i am not sure why this helps give better results, but it does, so. local x,t=stat(1),stat(2) for i=1,n do calibrate_func(...) end local y,u=stat(1),stat(2) -- measure for i=1,n do func(...) end local z,v=stat(1),stat(2) -- report local function c(t0,t1,t2) return(t0+t2-2*t1)*128/n*256/60*256*2 end -- *2 for 0.2.x local s=name..&quot; :&quot; local lc=c(x-t,y-u,z-v) if (lc != 0) s..=&quot; lua=&quot;..lc local sc=c(t,u,v) if (sc != 0) s..=&quot; sys=&quot;..sc printh(s) print(s) end function testme(name, func, ...) return testme_calib(name, func, function() end, ...) end testme(&quot;+&quot;, function(x,y) x=x+y end, 1, 2) testme(&quot;+=&quot;, function(x,y) x+=y end, 1, 2)</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p></div></div></div></p> https://www.lexaloffle.com/bbs/?tid=51993 https://www.lexaloffle.com/bbs/?tid=51993 Sat, 11 Mar 2023 20:30:12 UTC Remaining Infinite Token Exploit <p>Previously, gonengazit found some infinite token exploits <a href="https://www.lexaloffle.com/bbs/?tid=49963">here</a> and zep fixed them, but looks like they're not all gone yet - I stumbled upon one that still happens:</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>x=[[[[]] put your infinite tokens here... --]]</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>Seems like it happens because pico8 supports recursive block comments, and uses the same code to also parse long strings recursively.</p> <p>But lua then parses the long string non-recursively and executes the code that pico8 thought was part of the string.</p> <p>Pico8 probably should only parse block comments recursively, not long strings.</p> https://www.lexaloffle.com/bbs/?tid=50660 https://www.lexaloffle.com/bbs/?tid=50660 Thu, 15 Dec 2022 00:11:41 UTC Shrinko8 - Aggressive cart minification &amp; linting tool <p>I've created a cart minification &amp; linting tool in python - Shrinko8:</p> <p><strong><a href="https://github.com/thisismypassport/shrinko8">https://github.com/thisismypassport/shrinko8</a></strong></p> <p>If you don't want to download anything, or want to use a UI - you can use the webapp here:<br /> <strong><a href="https://thisismypassport.github.io/shrinko8/">https://thisismypassport.github.io/shrinko8/</a></strong></p> <p>Otherwise, see the github link for details on how to download &amp; use the tool.</p> <h1>Features</h1> <ul> <li> <p>It can do aggressive minification to reduce the token count, the character count, and the compressed size of a cart, giving meaningfully better results than other known tools like p8tool or GEM.</p> </li> <li> <p>(E.g. in one example, a 81,416 char cart went to 31,213 chars with shrinko8, 35,563 chars with p8tool (though p8tool didn't run on it without having to do some hacks), and 40,813 chars with GEM.)</p> </li> <li> <p>It can do linting, aka reporting of issues that may indicate bugs in your code, such as undefined, unused and duplicate local variables.</p> </li> <li> <p>It supports all pico8 syntax and globals, including modern ones (as of the time of writing, but I do plan to update it when needed).</p> </li> <li> <p>It also supports creating a p8.png file directly, and can actually compress carts better than Pico-8 would.</p> </li> <li>It also now supports declaring constants and replacing constant expressions with their value during minification - including even removing 'if false' blocks - see <a href="https://github.com/thisismypassport/shrinko8?tab=readme-ov-file#constants">readme</a> for more info.</li> </ul> <h1>Usage</h1> <p>Using the web-app is hopefully self-explanatory.</p> <p>Example of using the command line: (first creates p8, second creates p8.png, third minifies less but works without changes for any cart)</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>python shrinko8.py path-to-input.p8 path-to-output.p8 --lint --count --minify python shrinko8.py path-to-input.p8 path-to-output.p8.png --lint --count --minify python shrinko8.py path-to-input.p8 path-to-output.p8.png --lint --count --minify-safe-only</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>More information is in the <a href="https://github.com/thisismypassport/shrinko8?tab=readme-ov-file#shrinko8">readme</a></p> <p>I've decided to share this tool since people were having trouble using p8tool on their modern carts.</p> <p>Comments, issues, and contribution are welcome.</p> <p>Thanks to pancelor for many bug reports, suggestions, and the tool's name.</p> https://www.lexaloffle.com/bbs/?tid=48591 https://www.lexaloffle.com/bbs/?tid=48591 Fri, 22 Jul 2022 14:38:54 UTC Peek/Poke can only handle up to 0x2000 arguments <p>While pico8-lua itself can handle even 0x7fff arguments being passed to a function (easy to see via unpack, for example), peek and poke seem to only handle up to 0x2000.</p> <p>E.g. peek(0x8000, 0x2001) returns 0x2000 values instead of 0x2001<br /> And poke(0x8000, &lt;0x2001 values&gt;) only pokes addresses up to 0x9fff</p> https://www.lexaloffle.com/bbs/?tid=48328 https://www.lexaloffle.com/bbs/?tid=48328 Mon, 27 Jun 2022 06:41:51 UTC 0.2.4c BBS - crash/hang on copy/paste <p>Ever since 0.2.4c came to the BBS, copy/paste seems to cause a crash/hang:</p> <ul> <li>stat(4) when there is something in the clipboard hangs</li> <li>printh(&lt;..&gt;, &quot;@clip&quot;) hangs</li> </ul> https://www.lexaloffle.com/bbs/?tid=47280 https://www.lexaloffle.com/bbs/?tid=47280 Wed, 06 Apr 2022 21:20:09 UTC Coroutine inside coroutine still yields unexpectedly <p>There used to be an issue where coroutines would yield unexpectedly if they took to long to execute.<br /> This was fixed a while back, but I see that for coroutines that themselves run inside coroutines - this still happens.</p> <p>In the below code, in the 'coroincoro' case, nil is yielded instead of 1, showing that the yield happened sometimes before the &quot;real&quot; yield call.</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>sleep=function() for i=0,100,0.00002 do end yield(1) end function test(name, func, ...) local coro=cocreate(func, ...) local ok,result=coresume(coro) print(name..&quot;: &quot;..tostr(ok)..&quot;, &quot;..tostr(result)) end test(&quot;coroutine&quot;, sleep) local cc = cocreate(function() test(&quot;coroincoro&quot;, sleep) end) coresume(cc) </pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> https://www.lexaloffle.com/bbs/?tid=47275 https://www.lexaloffle.com/bbs/?tid=47275 Wed, 06 Apr 2022 16:46:05 UTC Token count nits <p>I noticed some nits about the way tokens are computed:</p> <ul> <li> <p>A few assignment operators ( ..= ^= &gt;&gt;&lt;= &gt;&gt;&lt;= ) seem to cost 2 tokens instead of 1 (all other assignment operators cost 1)</p> </li> <li>There seems to be logic that checks the token before '-' to determine if the '-' is a unary operator or not, but it doesn't seem to check newer operators (like &amp;, |, etc.) and thus -1 is considered 2 tokens in things like &quot;a &amp; -1&quot;. (Even though it's 1 token in &quot;a + -1&quot;)</li> </ul> https://www.lexaloffle.com/bbs/?tid=47136 https://www.lexaloffle.com/bbs/?tid=47136 Mon, 28 Mar 2022 15:40:45 UTC Repuzzle - a Pico-8 Coding Puzzle Game <p> <table><tr><td> <a href="/bbs/?pid=109128#p"> <img src="/bbs/thumbs/pico8_repuzzle-13.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=109128#p"> Repuzzle - a Pico-8 Coding Puzzle Game</a><br><br> by <a href="/bbs/?uid=29645"> thisismypassword</a> <br><br><br> <a href="/bbs/?pid=109128#p"> [Click to Play]</a> </td></tr></table> </p> <h1>Repuzzle - a Pico-8 Coding Puzzle Game</h1> <p>Insert, replace, delete, and move characters to win over 22 levels, ranging from the simple to the tricky.</p> <p>Explanation of the basic controls is provided in the game.</p> <h2>Tips:</h2> <ul> <li>The pico-8 manual and pico-8 wiki can be useful. (both for this game and in general)</li> <li>If you're stuck on one level, try another. The different &quot;sections&quot; each go roughly from easy to hard, with a lot of variance in-between.</li> <li>If you're really really stuck, the hints can help.</li> </ul> <h2>Full list of controls:</h2> <h4>Browsing:</h4> <ul> <li>Arrow keys to move between characters</li> <li>Ctrl + Arrow keys to move between words</li> <li>Page Up/Down to move between pages</li> <li>Home/End to move to start/end of line</li> <li>Ctrl + Home/End to move to start/end of code</li> <li>You can also scroll via Mouse Wheel and move to a character via left-click</li> </ul> <h4>Switching Mode:</h4> <ul> <li>Tab to select the next editing mode</li> <li>Ctrl+1/2/3 to select insert/replace/move mode directly (can also use F1/F2/F3 in BBS only)</li> <li>Clicking on a mode with the mouse will select it as well</li> </ul> <h4>Editing:</h4> <ul> <li>Type to insert characters in insert mode</li> <li>Type to replace characters in replace mode</li> <li>Backspace/delete to delete characters in insert and replace modes</li> <li>Backspace/delete to mark characters for moving in move mode</li> <li>Type to move marked characters in move mode</li> <li>Shift + Backspace/delete to undo changes (in all modes)</li> <li>You can type lowercase, uppercase (with Shift), digits/punctuation, and line breaks (Enter)</li> <li>Ctrl + V to paste code from the clipboard (in insert mode only)</li> </ul> <h4>Running:</h4> <ul> <li>Ctrl + Enter to run the code (you can also use Ctrl + B or 'run' in the pause menu)</li> <li>Escape to abort the run</li> <li>Upon error or abort, Tab (or Ctrl+1..6 or left-click) to browse the stack trace</li> <li>If the code did any drawing or printing, you'll also see a 'see output screen' option - browse to it via Tab(/etc) to see the output</li> </ul> <h4>Misc.:</h4> <ul> <li>Ctrl + C to copy all code to the clipboard</li> <li>Escape to access the pause menu (run; restart level; back to menu; see hint)</li> </ul> <h2>Uses sprites from:</h2> <ul> <li><a href="https://superdark.itch.io/8x8-pico-8-metroidvania-tileset">https://superdark.itch.io/8x8-pico-8-metroidvania-tileset</a></li> <li><a href="https://ironchestgames.itch.io/rpg-items-for-pico-8">https://ironchestgames.itch.io/rpg-items-for-pico-8</a></li> </ul> <h2>Misc notes:</h2> <ul> <li>Some non-english keyboard layouts currently don't work too well with the devkit keyboard in pico-8. Try switching to an english layout if you're having trouble with your own (or try reporting a pico8 bug)</li> <li>This uses the same Pico-8 interpreter as my Pico-8 REPL/Interpreter, available <a href="https://www.lexaloffle.com/bbs/?tid=36381"><strong><em>HERE</em></strong></a></li> </ul> <h2>Changelog</h2> <ul> <li>Tweak levels 11 &amp; 12 to avoid simple solution.</li> <li>Improve hints on levels 20 &amp; 22</li> <li>Fix holding backspace key being unreliable on large levels.</li> <li>Added mouse support &amp; paste support</li> <li>Made the deleted-due-to-move indicator pink</li> </ul> <h2>More Tips</h2> <ul> <li>If you're really really really truly <em>really</em> stuck, there is a hint &amp; solution cart <a href="https://www.lexaloffle.com/bbs/?tid=53923"><strong><em>HERE</em></strong></a>.</li> </ul> https://www.lexaloffle.com/bbs/?tid=47100 https://www.lexaloffle.com/bbs/?tid=47100 Fri, 25 Mar 2022 01:18:03 UTC BBS: All saved data deleted when switching tabs at the wrong time <p>This issue reproduces for me on multiple browsers (including chrome), and for luchak (on chrome 98).</p> <p>Warning: this bug deletes <strong>all</strong> BBS saves - not just the ones of the carts used to reproduce it.</p> <p>Steps to reproduce:</p> <ol> <li>Load some cart with a save in it, verify that the save works. I used <a href="https://www.lexaloffle.com/bbs/?pid=94408#p">https://www.lexaloffle.com/bbs/?pid=94408#p</a> (you can save/restore via menu)</li> <li>Open new tab with some other cart. I used <a href="https://www.lexaloffle.com/bbs/?pid=108109#p">https://www.lexaloffle.com/bbs/?pid=108109#p</a></li> <li>In this new tab, press play and <strong>immediately</strong> switch to the old tab</li> <li>Refresh the old tab and check if your saves are still there. They will not be.</li> <li>If you want to see your saves again, switch to the new tab and let it load. If you don't care, delete the new tab - your saves are now gone forever.</li> </ol> <p>It looks like when a game is first loading, it deletes the local storage (well, indexed db). Then, a bit later, it restores it. </p> https://www.lexaloffle.com/bbs/?tid=46942 https://www.lexaloffle.com/bbs/?tid=46942 Sat, 12 Mar 2022 04:04:32 UTC 'sub' gives incorrect results in 0.2.4 <p>It seems when the index parameters are out of bounds, sub now returns the last character in the string, instead of &quot;&quot;, which breaks string parsing code.</p> <p>As an example of incorrect result, <code>sub(&quot;a&quot;,2,2)</code> returns &quot;a&quot;, whereas it's supposed to (and used to) return &quot;&quot;.</p> https://www.lexaloffle.com/bbs/?tid=45560 https://www.lexaloffle.com/bbs/?tid=45560 Sat, 04 Dec 2021 07:51:05 UTC pack() returns a table with an &quot;n&quot; field of always 0? <p>New API pack() returns a table with an &quot;n&quot; field of always 0?</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>?pack(1,2).n -- 0 (expected: 2) ?pack(1,2,nil,3,nil).n -- 0 (expected: 5)</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>table.pack works as expected in lua.</p> https://www.lexaloffle.com/bbs/?tid=37832 https://www.lexaloffle.com/bbs/?tid=37832 Sat, 09 May 2020 15:53:52 UTC No mention of new While shorthand in manual? <p>I've just seen from the Pico 8 wiki that there's a new while shorthand:<br /> while (cond) actions</p> <p>And yeah - it works.<br /> But it's not documented anywhere in the manual - not in the shorthand section and not even in the changelog!</p> https://www.lexaloffle.com/bbs/?tid=37469 https://www.lexaloffle.com/bbs/?tid=37469 Mon, 20 Apr 2020 06:39:04 UTC Assorted (unimportant) oddities in BBS version <p>(Applies to verison 0.1.12d rcsd)</p> <ol> <li> <p>Lua's 'tostring' function is exposed now. It converts things to strings differently than tostr, including printing a table's address in memory.</p> </li> <li>After doing &quot;install_demos()&quot; or &quot;install_games()&quot;, something seems to go majorly wrong with pico-8. All nils become... nulls?!<br /> You can see this by trying to print a non-existing global/table value which would normally print [nil], but now prints [(null)].<br /> What is the type of a null? It's a null, too! (No, not the string &quot;null&quot; - but a null object itself)<br /> (It's clearly not a legal lua value)</li> </ol> <p>You can see the above oddities via my <a href="https://www.lexaloffle.com/bbs/?tid=36381">https://www.lexaloffle.com/bbs/?tid=36381</a> cart, if you'd like.</p> https://www.lexaloffle.com/bbs/?tid=36385 https://www.lexaloffle.com/bbs/?tid=36385 Sat, 28 Dec 2019 02:32:04 UTC Pico-8 REPL/Interpreter <p> <table><tr><td> <a href="/bbs/?pid=71429#p"> <img src="/bbs/thumbs/pico8_pico_repl-35.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=71429#p"> Pico Interpreter (REPL)</a><br><br> by <a href="/bbs/?uid=29645"> thisismypassword</a> <br><br><br> <a href="/bbs/?pid=71429#p"> [Click to Play]</a> </td></tr></table> </p> <h2>What's this?</h2> <p>A REPL (Read-Eval-Print Loop) for pico-8, in pico-8!</p> <p>Supports executing all lua &amp; pico-8 statements. (if statements, for loops, functions - you name it)</p> <p>While my code does its own parsing and execution of the statements, any functions you call are the real pico-8 functions.</p> <p>Code can be typed in, pasted in, or dropped in from .lua files.</p> <p>Alternatively, carts saved in .p8.rom format (<code>save mycart.p8.rom</code> in pico-8) can be dropped to the REPL to automatically run them.</p> <h2>What can I do with it?</h2> <p>Type expressions like <strong>1+3/5</strong> or <strong>sqrt(-2)</strong> to see the expected results. (Well - perhaps unexpected to some in the case of the sqrt)</p> <p>Type statements or programs like the one below to do whatever you wish. (That one prints all global functions)</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>for k, v in pairs(_env) do if (type(v) == &quot;function&quot;) ?k end</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>Computations that produce lots of results are automatically paged, computations that do nothing indefinitely can be interrupted via escape (escape works in BBS or SPLORE only)</p> <p>The special variable &quot;_&quot; is set to the value of the last expression you've executed.</p> <p>The special variable &quot;_env&quot; is set to the globals table (it's similar to lua's/pico-8's _ENV - which is also supported but requires ctrl+L to type)</p> <p>Pico-8's globals are not disturbed and are the same as in a blank Pico-8 cart.</p> <p>You can type &quot;\i=0&quot; to disable all text that &quot;interrupts&quot; the code while its executing, letting the code use up the entire screen. This also disables output paging, but pressing escape to stop execution still works.</p> <h2>What's the point of this? Pico-8 has a REPL already!</h2> <p>The main point of it is that I made it, everything else is up to you.</p> <p>But for example, you can easily take a look at whatever Pico version happens to be running in the BBS, and see how it differs from your own.</p> <h2>Can I use this to write Pico-8 carts?</h2> <p>No. All this allows you to do is to write and execute some code.</p> <p>You can't edit sprites/sfx/etc/etc, or export, or import, etc etc etc.</p> <p>Hopefully that's limited enough to avoid running afoul of any legal concerns.</p> <h2>What are the caveats?</h2> <p>As said, all evaluation other than execution of global functions is implemented by me and may have bugs (feel free to report those), or subtle inconsistencies with real pico8/lua behaviour.</p> <p>No bugs or missing features are currently known.</p> <h2>How do I copy code into the interpreter?</h2> <p>Easy - just use Ctrl+V to paste and Ctrl+X/C to cut/copy.</p> <p>Well - cut/copy is currently a bit janky on the BBS - you have to press it twice to get it on the clipboard.</p> <p>You can also drag &amp; drop a text file to the cart to copy its contents.</p> <h2>And what if I don't have any code?</h2> <p>You can take any cart, use pico-8 to convert it to .p8.rom format (e.g. <code>save mycart.p8.rom</code>) and drag &amp; drop it into the cart to run it immediately.</p> <p>The cart runs without resetting the system, so odd effects may occur if you run multiple carts without resetting (e.g. via <code>\rst</code>).</p> <p>If you press escape, you can resume the cart via <code>\cont</code> (or restart - without reset - via '\run')</p> <p>(Note: .p8 and .p8.png files are not supported - just .p8.rom)</p> <h2>Anything else of interest?</h2> <p>There are some special \-prefixed identifiers which act like special variables:</p> <ul> <li><strong>\interrupt (or \i)</strong> : set this to 0 to disable anything that might interfere with the running code (e.g. prompts while running code, override of print function, etc.) (default: 1)</li> <li><strong>\flip (or \f)</strong> : set this to 0 to disable auto-flipping. Requires \i to be 0 to have any effect. Useful for code that does its own flips but runs too slow under the REPL, resulting in flickering without this option. (default: 1)</li> <li><strong>\repl (or \r)</strong> : set this to 0 to disable REPL-specific features like automatic printing of results, '_' and '_env' (as an alias of _ENV). (default: 1)</li> <li><strong>\code (or \c)</strong> : a table of the most recently input code (item 1 is the previous code, item 2 is the one before it, and so on. item 0 is the current code). You can use this to store the code you've typed somewhere, e.g. <code>printh(\c[1], &quot;@clip&quot;)</code></li> <li><strong>\max_items (or \mi)</strong> : the maximal number of items shown when printing a table. -1 means show all. (default: 10)</li> <li><strong>\hex (or \h)</strong> : if true, numbers are printed in hex. (default: false)</li> <li><strong>\precise (or \pr)</strong> : if true, numbers are printed in hex if pico8 doesn't print them precisely in decimal. (default: false)</li> <li><strong>\colors (or \cl)</strong> : a table of the colors used, customization! color meaning: {output, errors, interrupts, prompt, input, input errors, comments, constants, keywords, punctuation, pico functions, cursor}</li> </ul> <p>As well as some special \-prefixed functions:</p> <ul> <li><strong>\exec (or \x)</strong> : a function that takes a string, and executes the string as if it were written in the repl's command line. Can take an optional second parameter - the environment to execute in. Further parameters are passed as '...' to the executed code.</li> <li><strong>\eval (or \v)</strong> : a function that takes a string, and evaluates the string as a lua expression (with the full power of the repl), returning its result. Can take an optional second parameter - the environment to execute in. Further parameters are passed as '...' to the evaluated code.</li> <li><strong>\compile (or \cm)</strong> : a function that takes a string, and compiles it as if it were written in the repl's command line. It returns a function that receives a (required) environment argument and executes the code when called. If the returned function receives further parameters, they are passed as '...' to the compiled code.</li> <li><strong>\tostr (or \ts)</strong> : a function that converts a value to a string in the same way as the repl does.</li> <li><strong>\print (or \p)</strong> : a function that prints a value in the same way as the repl does.</li> </ul> <p>And even some special \-prefixed commands (identifiers which perform an action when accessed):</p> <ul> <li><strong>\reset (or \rst)</strong> : completely resets the cart</li> <li><strong>\run</strong> : if _init/_draw/_update/etc were defined, runs them as if from the pico mainloop</li> <li><strong>\cont</strong> : continues a previous run - similar to \run, except _init is not called.</li> </ul> <p>And some shortcuts:</p> <ul> <li><strong>Ctrl+X/C/V</strong> - cut/copy/paste.</li> <li><strong>Shift+Enter or Ctrl+B</strong> - insert a line-break.</li> <li><strong>Home/End or Ctrl+A/E</strong> - move cursor to start/end of line</li> <li><strong>Ctrl+Home/End</strong> - move cursor to start/end of input</li> <li><strong>Ctrl+Up/Down</strong> - like Up/Down, but only browses through input history.</li> <li><strong>Ctrl+L</strong> - enter/exit punycase mode, where holding shift allows typing lowercase/punycase instead of symbols.</li> </ul> <p>Oh, and a puzzle game based on this interpreter can be found <a href="https://www.lexaloffle.com/bbs/?pid=109128">HERE</a>.</p> <h2>Where is the Commented Source Code?</h2> <p>Since the comments in the source code took too many characters and had to be stripped out, the original source code, with the comments, is available here:</p> <p><a href="https://pastebin.com/KVLxhiwa">Commented Source Code</a></p> <p>Just in case the link goes down, it's also available here, though unfortunately without syntax highlighting:<br /> <div><div><input type="button" value=" Show " onClick="if (this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display != '') { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = ''; this.innerText = ''; this.value = ' Hide '; } else { this.parentNode.parentNode.getElementsByTagName('div')[1].getElementsByTagName('div')[0].style.display = 'none'; this.innerText = ''; this.value = ' Show '; }"></div><div><div style="display: none;"></p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>------------------------ -- Prepare globals ------------------------ local g_ENV, my_ENV, globfuncs = _ENV, {}, {} for k,v in pairs(_ENV) do my_ENV[k] = v if (type(v) == &quot;function&quot;) globfuncs[k] = true end local _ENV = my_ENV -- with this, we segregate ourselves from the running code (all global accesses below use _ENV automagically) g_enable_repl, g_last_value = true ------------------------ -- Utils ------------------------ -- is ch inside str? (if so, returns index) function isoneof(ch, str) for i=1,#str do if (str[i] == ch) return i end end ------------------------ -- Tokenize ------------------------ -- escape sequences in strings (e.g. \n -&gt; new line) local esc_keys, esc_values = split &quot;a,b,f,n,r,t,v,\\,\&quot;,',\n,*,#,-,|,+,^&quot;, split &quot;\a,\b,\f,\n,\r,\t,\v,\\,\&quot;,',\n,\*,\#,\-,\|,\+,\^&quot; local escapes = {} for i=1,#esc_keys do escapes[esc_keys[i]] = esc_values[i] end -- is ch a digit char? function isdigit(ch) return ch and ch &gt;= '0' and ch &lt;= '9' end -- is ch a valid identifier char? function isalnum(ch) return ch and (ch &gt;= 'A' and ch &lt;= 'Z' or ch &gt;= 'a' and ch &lt;= 'z' or isoneof(ch, '_\x1e\x1f') or ch &gt;= '\x80' or isdigit(ch)) end -- extarct string value from quoted string -- returns value, end index function dequote(str, i, strlen, quote, fail) local rawstr = '' while i &lt;= strlen do local ch = str[i] if (ch == quote) break if ch == '\\' then -- handle escape sequences i += 1 local esch = str[i] ch = escapes[esch] -- handle normal escapes -- hex escape (e.g. \xff) if esch == 'x' then esch = tonum('0x'..sub(str,i+1,i+2)) if (esch) i += 2 else fail &quot;bad hex escape&quot; ch = chr(esch) -- decimal escape (e.g. \014) elseif isdigit(esch) then local start = i while isdigit(esch) and i &lt; start + 3 do i += 1; esch = str[i] end i -= 1 esch = tonum(sub(str,start,i)) if (not esch or esch &gt;= 256) fail &quot;bad decimal escape&quot; ch = chr(esch) -- ignore subsequent whitespace elseif esch == 'z' then repeat i += 1; esch = str[i] until not isoneof(esch, ' \r\t\f\v\n') if (not esch) fail() ch = '' i -= 1 elseif not esch then fail() ch='' end if (not ch) fail(&quot;bad escape: &quot; .. esch) ch='' elseif ch == '\n' then fail &quot;unterminated string&quot; break end rawstr ..= ch i += 1 end if (i &gt; strlen) fail(&quot;unterminated string&quot;, true) return rawstr, i+1 end -- extracts string value from long bracketed string (e.g. [[string]]) -- returns value, end index function delongbracket(str, i, strlen, fail) if str[i] == '[' then i += 1 local eq_start = i while (str[i] == '=') i += 1 local end_delim = ']' .. sub(str,eq_start,i-1) .. ']' local j = #end_delim if str[i] == '[' then i += 1 if (str[i] == '\n') i += 1 local start = i while (i &lt;= strlen and sub(str,i,i+j-1) != end_delim) i += 1 if (i &gt;= strlen) fail() return sub(str,start,i-1), i+j end end return nil, i end -- converts a string into tokens. -- if strict is set, errors are thrown if invalid, and comments are ignored -- returns: -- array of tokens -- array of the line each token is found at (for if/while shorthand parsing only) -- array of token start indices -- array of token end indices -- A token is: -- false for invalid -- true for comment (unless strict) -- number for numeric literal -- string for identifier, keyword, or punctuation -- table for string literal (table contains a single string at position [1]) function tokenize(str, strict) local i, line, start = 1, 1 local tokens, tlines, tstarts, tends, err = {}, {}, {}, {} local function fail(v, ok) if (strict) on_compile_fail(v, start) err = v and not ok end -- we support unindexable huge strings up to 64KB (at least as long as pico8 can handle them) -- we do this via the below hacks (though it doesn't handle huge tokens over 16KB...) local strlen = #str &gt;= 0 and #str or 0x7fff while i &lt;= strlen do if (i &gt;= 0x4001 and strlen &gt;= 0x7fff) str = sub(str, 0x4001); i -= 0x4000; strlen = #str &gt;= 0 and #str or 0x7fff start = i local ch = str[i] local ws, token -- whitespace if isoneof(ch, ' \r\t\f\v\n') then i += 1; ws = true if (ch == '\n') line += 1 -- comment elseif isoneof(ch, '-/') and str[i+1] == ch then i += 2 if (ch == '-' and str[i] == '[') token, i = delongbracket(str, i, strlen, fail) if not token then while (i &lt;= strlen and str[i] != '\n') i += 1 end if (strict) ws = true else add(tokens, true) -- number elseif isdigit(ch) or (ch == '.' and isdigit(str[i+1])) then local digits, dot = &quot;0123456789&quot;, true -- hex. number (0x...) if ch == '0' and isoneof(str[i+1], 'xX') then digits ..= &quot;AaBbCcDdEeFf&quot;; i += 2 -- binary number (0b...) elseif ch == '0' and isoneof(str[i+1], 'bB') then digits = &quot;01&quot;; i += 2 end while true do ch = str[i] if ch == '.' and dot then dot = false elseif not isoneof(ch, digits) then break end i += 1 end token = sub(str,start,i-1) if (not tonum(token)) fail &quot;bad number&quot;; token=&quot;0&quot; add(tokens, tonum(token)) -- identifier elseif isalnum(ch) then while isalnum(str[i]) do i += 1 end add(tokens, sub(str,start,i-1)) -- string elseif ch == &quot;'&quot; or ch == '&quot;' then token, i = dequote(str, i+1, strlen, ch, fail) add(tokens, {token}) -- long-bracket string elseif ch == '[' and isoneof(str[i+1], &quot;=[&quot;) then token, i = delongbracket(str, i, strlen, fail) if (not token) fail &quot;invalid long brackets&quot; add(tokens, {token}) -- punctuation else i += 1 local ch2,ch3,ch4 = unpack(split(sub(str,i,i+2),&quot;&quot;)) if ch2 == ch and ch3 == ch and isoneof(ch,'.&gt;') then i += 2 if (ch4 == &quot;=&quot; and isoneof(ch,'&gt;')) i += 1 elseif ch2 == ch and ch3 != ch and isoneof(ch,'&lt;&gt;') and isoneof(ch3,'&lt;&gt;') then i += 2 if (ch4 == &quot;=&quot;) i += 1 elseif ch2 == ch and isoneof(ch,'.:^&lt;&gt;') then i += 1 if (ch3 == &quot;=&quot; and isoneof(ch,'.^&lt;&gt;')) i += 1 elseif ch2 == '=' and isoneof(ch,'+-*/\\%^&amp;|&lt;&gt;=~!') then i += 1 elseif isoneof(ch,'+-*/\\%^&amp;|&lt;&gt;=~#(){}[];,?@$.:') then else fail(&quot;bad char: &quot; .. ch) end add(tokens, sub(str,start,i-1)) end if (not ws) add(tlines, line); add(tstarts, start); add(tends, i-1) if (err) tokens[#tokens], err = false, false end return tokens, tlines, tstarts, tends end ------------------------ -- More Utils ------------------------ -- is obj inside table? function isin(obj, tab) for i=1,#tab do if (tab[i] == obj) return i end end -- similar to unpack, except depack(pack(...)) is always ... function depack(t) return unpack(t,1,t.n) -- (unpack defaults to t,1,#t instead) end -- copy a table function copy(t) local ct = {} for k, v in next, t do ct[k] = v end return ct end ------------------------ -- Parse &amp; Eval ------------------------ -- General information: -- As we parse lua's grammar, we build nodes, which are merely -- functions that take e (an environment) as the first arg. -- Parent nodes call their children nodes, thus forming a sort of tree. -- An environment (e) is an array of scope tables -- the scope table at index 0 contains top-level upvalues like _ENV -- other scope tables contain locals defined within a local statement (*) -- Thus, upvalues and locals are accessed the same way -- Expression (expr) parsing returns a (node, setnode, tailcallnode) tuple. -- node returns the expression's value -- setnode returns a tuple of the table and key to use for the assignment (**) -- tailcallnode returns a tuple of the function and args to use for a tail-call -- setnode and/or tailcallnode are nil if assignment/call is not available -- Note that functions called from within parse_expr instead return a -- (node, is_prefix, setnode, tailcallnode) tuple, where is_prefix -- says whether the node can be used as a prefix for calls/etc. -- Statement (stmt) parsing returns a (node, is_end) tuple -- node returns either: -- nil to continue execution -- true to break from loop -- (0, label object) to goto the label object -- table to return its depack() from the function -- function to tail-call it as we return from the function -- node may also be nil for empty statements -- is_end is true if the statement must end the block -- (*) We create a table per local statement, instead of per block -- because by using goto, you can execute a local statement multiple -- times without leaving a block, each time resulting in a different -- local (that can be independently captured) -- (**) It would be much simpler for setnode to do the assignment itself, -- but it would prevent us from mimicking lua's observable left-to-right -- evaluation behaviour, where the assignment targets are evaluated -- before the assignment values. -- On that note, we generally mimic lua's observable left-to-right evaluation -- behaviour, except that we do true left-to-right evaluation, while lua -- usually evaluates locals (only!) right before the operation that uses them. -- This difference can be observed if the local is captured by a closure, -- e.g: local a=1; print(a + (function() a = 3; return 0 end)()) -- anyway: -- identifiers to treat as keywords instead local keywords = split &quot;and,break,do,else,elseif,end,false,for,function,goto,if,in,local,nil,not,or,repeat,return,then,true,until,while&quot; keyword_map = {} for kw in all(keywords) do keyword_map[kw] = true end -- is token an assign op (e.g. +=)? local function is_op_assign(token) return type(token) == &quot;string&quot; and token[-1] == '=' end -- tokens that terminate a block end_tokens = split 'end,else,elseif,until' -- parses a string, returning a function -- that receives a global environment (e.g. _ENV) and executes the code function parse(str ) -- tokenize the string first local tokens, tlines, tstarts = tokenize(str, true) -- ti: the token index we're at -- e_len: how many environments deep we are -- depth: how many blocks deep we are local ti, e_len, depth, func_e_len, loop_depth, func_depth = 1, 0, 0 , 0 local parse_expr, parse_block -- gotos: array of functions to evaluate in order to finalize gotos -- locals: maps names of locals to the environment array index where -- they're defined -- labels: maps names of labels to label objects -- -- both locals and labels use a metatable to simulate a sort-of stack -- where pushed maps inherit from all previous maps in the stack and -- can be easily popped. -- -- endcb: specifies when to stop shorthand parsing local gotos, locals, labels, endcb = {} local function fail(err) on_compile_fail(err, tstarts[ti-1] or 1) end -- return a node that returns a constant local function const_node(value) return function() return value end end -- return a node that returns the value of a variable local function var_node(name) local e_i = locals[name] if e_i then return function(e) return e[e_i][name] end -- local/upvalue else e_i = locals._ENV return function(e) return e[e_i]._ENV[name] end -- global end end -- return a node that returns the values of the vararg arguments -- of the current function. local function vararg_node() local e_i = locals['...'] if (not e_i or e_i != func_e_len) fail &quot;unexpected '...'&quot; return function(e) return depack(e[e_i][&quot;...&quot;]) end end -- return a setnode that allows assigning to the value of a variable local function assign_node(name) local e_i = locals[name] if e_i then return function(e) return e[e_i], name end -- local/upvalue else e_i = locals._ENV return function(e) return e[e_i]._ENV, name end -- global end end -- consume the next token, requiring it to be 'expect' local function require(expect) local token = tokens[ti]; ti += 1 if (token == expect) return if (token == nil) fail() fail(&quot;expected: &quot; .. expect) end -- consume the next token, requiring it to be an identifier -- returns the identifier local function require_ident(token) if (not token) token = tokens[ti]; ti += 1 if (token == nil) fail() if (type(token) == 'string' and isalnum(token[1]) and not keyword_map[token]) return token if (type(token) == 'string') fail(&quot;invalid identifier: &quot; .. token) fail &quot;identifier expected&quot; end -- if the next token is 'expect', consumes it and returns true local function accept(expect) if (tokens[ti] == expect) ti += 1; return true end -- return whether we're at the end of a statement local function at_stmt_end() return isin(tokens[ti], end_tokens) or (endcb and endcb(ti)) end -- push a new locals map to the locals 'stack' local function push_locals() locals = setmetatable({}, {__index=locals}) e_len += 1 end -- pop a locals map from the 'stack' local function pop_locals() locals = getmetatable(locals).__index e_len -= 1 end -- evaluate an array of nodes, returning a pack of results -- the last node in the array may return an arbitrary number of results, -- all of which are packed. local function eval_nodes(e, nodes) local results = {} local n = #nodes for i=1,n-1 do results[i] = nodes[i](e) end if n &gt; 0 then local values = pack(nodes[n](e)) if values.n != 1 then for i=1,values.n do results[n + i - 1] = values[i] end n += values.n - 1 else results[n] = values[1] end end results.n = n return results end -- parses a comma-separated list of elements, each parsed via 'parser' local function parse_list(parser) local list = {} add(list, (parser())) while accept ',' do add(list, (parser())) end return list end -- parse a call expression -- node : call target node -- method : method to call for method call expression (e.g. a:b()) -- arg : single argument node (e.g. for a&quot;b&quot; and a{b}) -- returns (node, is_prefix (true), setnode (nil), tailcallnode) local function parse_call(node, method, arg) -- parse the arguments local args = {} if arg then add(args, arg) elseif not accept ')' then while true do add(args, (parse_expr())) if (accept ')') break require ',' end end if method then return function(e) -- call method local obj = node(e) return obj[method](obj, depack(eval_nodes(e, args))) end, true, nil, function(e) -- return ingredients for a method tail-call local obj = node(e) return obj[method], pack(obj, depack(eval_nodes(e, args))) end else return function(e) -- call function return node(e)(depack(eval_nodes(e, args))) end, true, nil, function(e) -- return ingredients for a function tail-call return node(e), eval_nodes(e, args) end end end -- parse a table construction expression (e.g. {1,2,3}) local function parse_table() -- key/value nodes local keys, values = {}, {} -- splat_i : either #keys if the last item in the table is array-style -- (and thus may fill multiple array values), or nil otherwise local index, splat_i = 1 while not accept '}' do splat_i = nil local key, value -- e.g. [a]=b if accept '[' then key = parse_expr(); require ']'; require '='; value = parse_expr() -- e.g. a=b elseif tokens[ti+1] == '=' then key = const_node(require_ident()); require '='; value = parse_expr() -- e.g. b else key = const_node(index); value = parse_expr(); index += 1; splat_i = #keys + 1 end add(keys, key); add(values, value) if (accept '}') break if (not accept ';') require ',' end return function(e) -- constuct table -- note: exact behaviour of # may differ from natively created tables local table = {} for i=1,#keys do if i == splat_i then -- set multiple table elements (e.g. {f()}) local key, value = keys[i](e), pack(values[i](e)) for j=1,value.n do table[key + j - 1] = value[j] end else -- set table element table[keys[i](e)] = values[i](e) end end return table end end -- parse a function expression or statement -- is_stmt : true if statement -- is_local: true if local function statement local function parse_function(is_stmt, is_local) -- has_self : function has implicit self arg -- setnode : for statements, how to assign the function to a variable local name, has_self, setnode if is_stmt then if is_local then -- local function statement push_locals() name = require_ident() locals[name] = e_len setnode = assign_node(name) else -- function statement name = {require_ident()} -- function name may include multiple .-seprated parts while (accept '.') add(name, require_ident()) -- and may include a final :-separated part if (accept ':') add(name, require_ident()); has_self = true if #name == 1 then setnode = assign_node(name[1]) else local node = var_node(name[1]) for i=2,#name-1 do local node_i = node -- capture node = function(e) return node_i(e)[name[i]] end end setnode = function(e) return node(e), name[#name] end end end end -- parse function params local params, vararg = {} if (has_self) add(params, 'self') require &quot;(&quot; if not accept ')' then while true do if (accept '...') vararg = true; else add(params, require_ident()) if (accept ')') break require ',' if (vararg) fail &quot;unexpected param after '...'&quot; end end -- add function params as locals push_locals() for param in all(params) do locals[param] = e_len end if (vararg) locals['...'] = e_len -- parse function's body local old_gotos, old_depth, old_e_len = gotos, func_depth, func_e_len gotos, func_depth, func_e_len = {}, depth + 1, e_len local body = parse_block() for g in all(gotos) do g() end -- handle gotos gotos, func_depth, func_e_len = old_gotos, old_depth, old_e_len require 'end' pop_locals() return function(e) if (is_local) add(e, {}) -- create the function's environment -- note: this is a shallow copy of the environment array, -- not of the tables within. local func_e = copy(e) local expected_e_len = #func_e -- this is the actual function created local func = function(...) local args = pack(...) -- pack args -- normally, when a function exits, its environment -- ends up the same as it started, so it can be reused -- however, if the function didn't exit yet (e.g. recursion) -- we create a copy of the environment to use for this call local my_e = func_e if #my_e != expected_e_len then local new_e = {} for i=0, expected_e_len do new_e[i] = my_e[i] end my_e = new_e end -- add scope for params local scope = {} for i=1,#params do scope[params[i]] = args[i] end if (vararg) scope['...'] = pack(unpack(args, #params+1, args.n)) -- evaluate function body add(my_e, scope) local retval = body(my_e) deli(my_e) -- return function result if retval then if (type(retval) == &quot;table&quot;) return depack(retval) -- return return retval() -- tailcall end end -- assign or return the function if (is_stmt) local d,k = setnode(e); d[k] = func else return func end end -- parse a core expression, aka an expression without any suffixes -- returns (node, is_prefix, setnode, tailcallnode) local function parse_core() local token = tokens[ti]; ti += 1 local arg if (token == nil) fail() -- nil constant if (token == &quot;nil&quot;) return const_node() -- true constant if (token == &quot;true&quot;) return const_node(true) -- false constant if (token == &quot;false&quot;) return const_node(false) -- number constant if (type(token) == &quot;number&quot;) return const_node(token) -- string constant if (type(token) == &quot;table&quot;) return const_node(token[1]) -- table if (token == &quot;{&quot;) return parse_table() -- parentheses (this is NOT an no-op, unlike in most -- languages - as it forces the expression to return 1 result) if (token == &quot;(&quot;) arg = parse_expr(); require ')'; return function(e) return (arg(e)) end, true -- unary ops if (token == &quot;-&quot;) arg = parse_expr(11); return function(e) return -arg(e) end if (token == &quot;~&quot;) arg = parse_expr(11); return function(e) return ~arg(e) end if (token == &quot;not&quot;) arg = parse_expr(11); return function(e) return not arg(e) end if (token == &quot;#&quot;) arg = parse_expr(11); return function(e) return #arg(e) end if (token == &quot;@&quot;) arg = parse_expr(11); return function(e) return @arg(e) end if (token == &quot;%&quot;) arg = parse_expr(11); return function(e) return %arg(e) end if (token == &quot;$&quot;) arg = parse_expr(11); return function(e) return $arg(e) end -- function creation if (token == 'function') return parse_function() -- vararg if (token == &quot;...&quot;) return vararg_node() -- special repl-specific commands if (token == &quot;\\&quot;) arg = require_ident() return function() return cmd_exec(arg) end, true, function() return cmd_assign(arg) end -- identifiers if (require_ident(token)) return var_node(token), true, assign_node(token) fail(&quot;unexpected token: &quot; .. token) end -- parse a binary operation expression -- the extra 'v' argument is used only by op-assignment statements local function parse_binary_op(token, prec, left, right_expr) local right if (token == &quot;^&quot; and prec &lt;= 12) right = right_expr(12); return function(e,v) return left(e,v) ^ right(e) end if (token == &quot;*&quot; and prec &lt; 10) right = right_expr(10); return function(e,v) return left(e,v) * right(e) end if (token == &quot;/&quot; and prec &lt; 10) right = right_expr(10); return function(e,v) return left(e,v) / right(e) end if (token == &quot;\\&quot; and prec &lt; 10) right = right_expr(10); return function(e,v) return left(e,v) \ right(e) end if (token == &quot;%&quot; and prec &lt; 10) right = right_expr(10); return function(e,v) return left(e,v) % right(e) end if (token == &quot;+&quot; and prec &lt; 9) right = right_expr(9); return function(e,v) return left(e,v) + right(e) end if (token == &quot;-&quot; and prec &lt; 9) right = right_expr(9); return function(e,v) return left(e,v) - right(e) end if (token == &quot;..&quot; and prec &lt;= 8) right = right_expr(8); return function(e,v) return left(e,v) .. right(e) end if (token == &quot;&lt;&lt;&quot; and prec &lt; 7) right = right_expr(7); return function(e,v) return left(e,v) &lt;&lt; right(e) end if (token == &quot;&gt;&gt;&quot; and prec &lt; 7) right = right_expr(7); return function(e,v) return left(e,v) &gt;&gt; right(e) end if (token == &quot;&gt;&gt;&gt;&quot; and prec &lt; 7) right = right_expr(7); return function(e,v) return left(e,v) &gt;&gt;&gt; right(e) end if (token == &quot;&lt;&lt;&gt;&quot; and prec &lt; 7) right = right_expr(7); return function(e,v) return left(e,v) &lt;&lt;&gt; right(e) end if (token == &quot;&gt;&gt;&lt;&quot; and prec &lt; 7) right = right_expr(7); return function(e,v) return left(e,v) &gt;&gt;&lt; right(e) end if (token == &quot;&amp;&quot; and prec &lt; 6) right = right_expr(6); return function(e,v) return left(e,v) &amp; right(e) end if ((token == &quot;^^&quot; or token == &quot;~&quot;) and prec &lt; 5) right = right_expr(5); return function(e,v) return left(e,v) ^^ right(e) end if (token == &quot;|&quot; and prec &lt; 4) right = right_expr(4); return function(e,v) return left(e,v) | right(e) end if (token == &quot;&lt;&quot; and prec &lt; 3) right = right_expr(3); return function(e,v) return left(e,v) &lt; right(e) end if (token == &quot;&gt;&quot; and prec &lt; 3) right = right_expr(3); return function(e,v) return left(e,v) &gt; right(e) end if (token == &quot;&lt;=&quot; and prec &lt; 3) right = right_expr(3); return function(e,v) return left(e,v) &lt;= right(e) end if (token == &quot;&gt;=&quot; and prec &lt; 3) right = right_expr(3); return function(e,v) return left(e,v) &gt;= right(e) end if (token == &quot;==&quot; and prec &lt; 3) right = right_expr(3); return function(e,v) return left(e,v) == right(e) end if ((token == &quot;~=&quot; or token == &quot;!=&quot;) and prec &lt; 3) right = right_expr(3); return function(e,v) return left(e,v) ~= right(e) end if (token == &quot;and&quot; and prec &lt; 2) right = right_expr(2); return function(e,v) return left(e,v) and right(e) end if (token == &quot;or&quot; and prec &lt; 1) right = right_expr(1); return function(e,v) return left(e,v) or right(e) end end -- given an expression, parses a suffix for this expression, if possible -- prec : precedence to not go beyond when parsing -- isprefix : true to allow calls/etc. (lua disallows it for certain -- expression unless parentheses are used, not sure why) -- returns (node, is_prefix, setnode, tailcallnode) local function parse_expr_more(prec, left, isprefix) local token = tokens[ti]; ti += 1 local right, arg if isprefix then -- table index by name if (token == '.') right = require_ident(); return function(e) return left(e)[right] end, true, function(e) return left(e), right end -- table index if (token == '[') right = parse_expr(); require ']'; return function(e) return left(e)[right(e)] end, true, function(e) return left(e), right(e) end -- call if (token == &quot;(&quot;) return parse_call(left) -- call with table or string argument if (token == &quot;{&quot; or type(token) == &quot;table&quot;) ti -= 1; arg = parse_core(); return parse_call(left, nil, arg) -- method call if token == &quot;:&quot; then right = require_ident(); -- ... with table or string argument if (tokens[ti] == &quot;{&quot; or type(tokens[ti]) == &quot;table&quot;) arg = parse_core(); return parse_call(left, right, arg) require '('; return parse_call(left, right) end end -- binary op local node = parse_binary_op(token, prec, left, parse_expr) if (not node) ti -= 1 return node end -- parse an arbitrary expression -- prec : precedence to not go beyond when parsing -- returns (node, setnode, tailcallnode) parse_expr = function(prec) local node, isprefix, setnode, callnode = parse_core() while true do local newnode, newisprefix, newsetnode, newcallnode = parse_expr_more(prec or 0, node, isprefix) if (not newnode) break node, isprefix, setnode, callnode = newnode, newisprefix, newsetnode, newcallnode end return node, setnode, callnode end -- parse an assignment expression, returning its setnode local function parse_assign_expr() local _, assign_expr = parse_expr() if (not assign_expr) fail &quot;cannot assign to value&quot; return assign_expr end -- parse assignment statement local function parse_assign() local targets = parse_list(parse_assign_expr) require &quot;=&quot; local sources = parse_list(parse_expr) if #targets == 1 and #sources == 1 then return function(e) -- single assignment (for performance) local d,k = targets[1](e); d[k] = sources[1](e) end else return function(e) -- multiple assignment (e.g. a,b=c,d) local dests, keys = {}, {} for i=1,#targets do local d,k = targets[i](e); add(dests,d) add(keys,k) end local values = eval_nodes(e, sources) -- assign from last to first, per observable lua behaviour for i=#targets,1,-1 do dests[i][keys[i]] = values[i] end end end end -- parse op-assignment statement (e.g. +=) -- receives the setnode of the assignment target, and uses it to both get and set the value -- (this is to ensure the node is evaluated only once) local function parse_op_assign(setnode) local token = tokens[ti]; ti += 1 local op = sub(token,1,-2) local node = function(e, v) return v end -- parse_binary_op propagates the value as an extra arg to us local op_node = parse_binary_op(op, 0, node, function() return parse_expr() end) -- ignore precedence if (not op_node) fail &quot;invalid compound assignment&quot; return function(e) local d,k = setnode(e); d[k] = op_node(e, d[k]) end end -- parse local statement local function parse_local() if accept 'function' then -- local function statement return parse_function(true, true) else local targets = parse_list(require_ident) local sources = accept '=' and parse_list(parse_expr) or {} push_locals() for i=1,#targets do locals[targets[i]] = e_len end if #targets == 1 and #sources == 1 then return function(e) -- single local (for performance) add(e, {[targets[1]] = sources[1](e)}) end else return function(e) -- multiple locals local scope = {} local values = eval_nodes(e, sources) for i=1,#targets do scope[targets[i]] = values[i] end add(e, scope) end end end end -- start if/while shorthand parsing -- allows terminating the parsing of a block at the end of the line local function start_shorthand(allowed) local line = tlines[ti - 1] local prev_endcb = endcb endcb = function(i) return line != tlines[i] end if (not allowed or endcb(ti)) fail(ti &lt;= #tokens and &quot;unterminated shorthand&quot; or nil) return prev_endcb end -- end shorthand parsing, and verify we haven't exceeded the line local function end_shorthand(prev_endcb) if (endcb(ti-1)) fail(&quot;unterminated shorthand&quot;) endcb = prev_endcb end -- parse an 'if' statement local function parse_ifstmt() local short = tokens[ti] == '(' local cond = parse_expr() local then_b, else_b if accept 'then' then -- normal if statement then_b, else_b = parse_block() if accept 'else' then else_b = parse_block(); require &quot;end&quot; -- else elseif accept 'elseif' then else_b = parse_ifstmt() -- elseif else require &quot;end&quot; end else -- shorthand if local prev = start_shorthand(short) then_b = parse_block() if (not endcb(ti) and accept 'else') else_b = parse_block() -- shorhand if/else end_shorthand(prev) end return function(e) -- execute the if if cond(e) then return then_b(e) elseif else_b then return else_b(e) end end end -- parse a loop block, updating loop_depth (for break purposes) local function parse_loop_block(...) local old_depth = loop_depth loop_depth = depth + 1 local result = parse_block(...) loop_depth = old_depth return result end -- if retval denotes a break, do not propagate it further -- useful when returning from loop blocks local function handle_break(retval, label) if (retval == true) return -- break return retval, label end -- parse a 'while' block local function parse_while() local short = tokens[ti] == '(' local cond = parse_expr() local body if accept 'do' then -- normal while statement body = parse_loop_block() require 'end' else -- shorthand while statement local prev = start_shorthand(short) body = parse_loop_block() end_shorthand(prev) end return function(e) -- execute the while while cond(e) do if (stat(1)&gt;=1) yield_execute() local retval, label = body(e) if (retval) return handle_break(retval, label) end end end -- parse a repeat/until statement local function parse_repeat() -- note that the until part can reference -- locals declared inside the repeat body, thus -- we pop the locals/scopes ourselves local block_e_len = e_len local body = parse_loop_block(true) require 'until' local cond = parse_expr() while (e_len &gt; block_e_len) pop_locals() return function(e) -- execute the repeat/until repeat if (stat(1)&gt;=1) yield_execute() local retval, label = body(e) if (not retval) label = cond(e) -- reuse label as the end cond while (#e &gt; block_e_len) deli(e) -- pop scopes ourselves if (retval) return handle_break(retval, label) until label -- actually the end cond end end -- parse a 'for' statement local function parse_for() if tokens[ti + 1] == '=' then -- numeric for statement local varb = require_ident() require '=' local min = parse_expr() require ',' local max = parse_expr() local step = accept ',' and parse_expr() or const_node(1) require 'do' -- push 'for' local, and parse the body push_locals() locals[varb] = e_len local body = parse_loop_block() require 'end' pop_locals() return function(e) -- execute the numeric 'for' for i=min(e),max(e),step(e) do if (stat(1)&gt;=1) yield_execute() add(e, {[varb]=i}) local retval, label = body(e) deli(e) if (retval) return handle_break(retval, label) end end else -- generic 'for' block local targets = parse_list(require_ident) require &quot;in&quot; local sources = parse_list(parse_expr) require 'do' -- push 'for' locals, and parse the body push_locals() for target in all(targets) do locals[target] = e_len end local body = parse_loop_block() require 'end' pop_locals() return function(e) -- execute the generic 'for' -- (must synthesize it ourselves, as a generic for's -- number of vars is fixed) local exps = eval_nodes(e, sources) while true do local scope = {} local vars = {exps[1](exps[2], exps[3])} if (vars[1] == nil) break exps[3] = vars[1] for i=1,#targets do scope[targets[i]] = vars[i] end if (stat(1)&gt;=1) yield_execute() add(e, scope) local retval, label = body(e) deli(e) if (retval) return handle_break(retval, label) end end end end -- parse a break statement local function parse_break() if (not loop_depth or func_depth and loop_depth &lt; func_depth) fail &quot;break outside of loop&quot; return function() return true end end -- parse a return statement -- N.B. lua actually allows return (and vararg) outside of functions. -- this kinda completely breaks repuzzle, so the repl code in it disallows it. local function parse_return() if tokens[ti] == ';' or at_stmt_end() then -- return no values (represented by us as an empty pack) return function() return pack() end else local node, _, callnode = parse_expr() local nodes = {node} while (accept ',') add(nodes, (parse_expr())) if #nodes == 1 and callnode and func_depth then -- tail-call (aka jump into other function instead of returning) return function(e) local func, args = callnode(e); if (stat(1)&gt;=1) yield_execute() return function() return func(depack(args)) end end else -- normal return return function(e) return eval_nodes(e, nodes) end end end end -- parse label statement local function parse_label(parent) local label = require_ident() require '::' if (labels[label] and labels[label].depth == depth) fail &quot;label already defined&quot; -- store label object labels[label] = {e_len=e_len, depth=depth, block=parent, i=#parent} end -- parse goto statement local function parse_goto() local label = require_ident() local labels_c, e_len_c, value = labels, e_len -- capture labels -- the label may be defined after the goto, so process the goto -- at function end add(gotos, function () value = labels_c[label] if (not value) fail &quot;label not found&quot; if (func_depth and value.depth &lt; func_depth) fail &quot;goto outside of function&quot; -- goto cannot enter a scope -- (empty statements at the end of a scope aren't considered a -- part of the scope for this purpose) local goto_e_len = labels_c[value.depth] or e_len_c if (value.e_len &gt; goto_e_len and value.i &lt; #value.block) fail &quot;goto past local&quot; end) return function() if (stat(1)&gt;=1) yield_execute() return 0, value end end -- parse any statement local function parse_stmt(parent) local token = tokens[ti]; ti += 1 -- empty semicolon if (token == ';') return -- do-end block if (token == 'do') local node = parse_block(); require 'end'; return node -- if if (token == 'if') return parse_ifstmt() -- while loop if (token == 'while') return parse_while() -- repeat/until loop if (token == 'repeat') return parse_repeat() -- for loop if (token == 'for') return parse_for() -- break if (token == 'break') return parse_break() -- return if (token == 'return') return parse_return(), true -- local if (token == 'local') return parse_local() -- goto if (token == 'goto') return parse_goto() -- label if (token == '::') return parse_label(parent) -- function if (token == 'function' and tokens[ti] != '(') return parse_function(true) -- print shorthand if token == '?' then local print_node, nodes = var_node 'print', parse_list(parse_expr); return function (e) print_node(e)(depack(eval_nodes(e, nodes))) end end -- handle assignments and expressions ti -= 1 local start = ti -- allow reparse local node, setnode, callnode = parse_expr() -- assignment if accept ',' or accept '=' then ti = start; return parse_assign() -- op-assignment elseif is_op_assign(tokens[ti]) then return parse_op_assign(setnode) -- repl-specific print of top-level expression elseif depth &lt;= 1 and g_enable_repl then return function (e) local results = pack(node(e)) if (not (callnode and results.n == 0)) add(g_results, results) g_last_value = results[1] end -- regular expression statements (must be call) else if (not callnode) fail &quot;statement has no effect&quot; return function(e) node(e) end end end -- parse a block of statements -- keep_locals: true to let the caller exit the block themselves parse_block = function(keep_locals) -- push a new labels map in the labels 'stack' labels = setmetatable({}, {__index=labels}) labels[depth] = e_len -- increase depth depth += 1 local block_depth = depth local block_e_len = keep_locals and 0x7fff or e_len -- parse block statements local block = {} while ti &lt;= #tokens and not at_stmt_end() do local stmt, need_end = parse_stmt(block) if (stmt) add(block, stmt) if (need_end) accept ';'; break end -- pop any locals pushed inside the block while (e_len &gt; block_e_len) pop_locals() depth -= 1 labels = getmetatable(labels).__index return function (e) -- execute the block's statements local retval, label local i,n = 1,#block while i &lt;= n do retval, label = block[i](e) if retval then -- handle returns &amp; breaks if (type(retval) != &quot;number&quot;) break -- handle goto to parent block if (label.depth != block_depth) break -- handle goto to this block i = label.i while (#e &gt; label.e_len) deli(e) retval, label = nil end i += 1 end while (#e &gt; block_e_len) deli(e) return retval, label end end -- create top-level upvalues locals = g_enable_repl and {_ENV=0, _env=0, _=0} or {_ENV=0} locals['...'] = 0 -- parse top-level block local root = parse_block() if (ti &lt;= #tokens) fail &quot;unexpected end&quot; -- handle top-level gotos for g in all(gotos) do g() end return function(env, ...) -- create top-level scope local scope = g_enable_repl and {_ENV=env, _env=env, _=g_last_value} or {_ENV=env} scope['...'] = pack(...) -- execute local retval = root{[0]=scope} if (retval) return depack(retval) end end ------------------------ -- Output ------------------------ g_show_max_items, g_hex_output, g_precise_output = 10, false, false -- reverse mapping of escapes local unescapes = {[&quot;\0&quot;]=&quot;000&quot;,[&quot;\014&quot;]=&quot;014&quot;,[&quot;\015&quot;]=&quot;015&quot;} for k, v in pairs(escapes) do if (not isoneof(k, &quot;'\n&quot;)) unescapes[v] = k end -- create quoted string from a string value function requote(str) local i = 1 while i &lt;= #str do local ch = str[i] local nch = unescapes[ch] if (nch) str = sub(str,1,i-1) .. '\\' .. nch .. sub(str,i+1); i += #nch i += 1 end return '&quot;' .. str .. '&quot;' end -- is 'key' representable as an identifier? function is_identifier(key) if (type(key) != 'string') return false if (keyword_map[key]) return false if (#key == 0 or isdigit(key[1])) return false for i=1,#key do if (not isalnum(key[i])) return false end return true end -- convert value as a string -- (more featured than tostr) function value_to_str(val, depth) local ty = type(val) -- nil if (ty == 'nil') then return 'nil' -- boolean elseif (ty == 'boolean') then return val and 'true' or 'false' -- number (optionally hex) elseif (ty == 'number') then if (not g_precise_output) return tostr(val, g_hex_output) local str = tostr(val) return tonum(str) == val and str or tostr(val,1) -- string (with quotes) elseif (ty == 'string') then return requote(val) -- table contents elseif (ty == 'table' and not depth) then local res = '{' local i = 0 local prev = 0 -- avoid pairs, as it uses metamethods for k,v in next, val do if (i == g_show_max_items) res ..= ',&lt;...&gt;' break if (i &gt; 0) res ..= ',' local vstr = value_to_str(v,1) if k == prev + 1 then res ..= vstr; prev = k elseif is_identifier(k) then res ..= k .. '=' .. vstr else res ..= '[' .. value_to_str(k,1) ..']=' .. vstr end i += 1 end return res .. '}' -- other else return '&lt;' .. tostr(ty) .. '&gt;' end end -- convert more results into a string function results_to_str(str, results) if (results == nil) return str -- no new results if (not str) str = '' local count = min(21,#results) for ir=1, count do if (#str &gt; 0) str ..= '\n' local result = results[ir] if type(result) == 'table' then local line = '' for i=1,result.n do if (#line &gt; 0) line ..= ', ' line ..= value_to_str(result[i]) end str ..= line else str ..= result end end local new_results = {} for i=count+1, #results do new_results[i - count] = results[i] end return str, new_results end ------------------------ -- Console output ------------------------ poke(0x5f2d,1) -- enable keyboard cls() g_prompt = &quot;&gt; &quot; -- currently must be valid token! g_input, g_input_lines, g_input_start = &quot;&quot;, 1, 0 g_cursor_pos, g_cursor_time = 1, 20 --lint: g_str_output, g_error_output g_history, g_history_i = {''}, 1 --lint: g_interrupt, g_notice, g_notice_time g_abort = false g_num_output_lines, g_line = 0, 1 g_enable_interrupt, g_enable_autoflip = true, true g_pal = split &quot;7,4,3,5,6,8,5,12,14,7,11,5&quot; -- override print for better output g_ENV.print = function(value, ...) if (pack(...).n != 0 or not g_enable_interrupt) return print(value, ...) add(g_results, tostr(value)) end -- suppress pause (e.g. from p, etc.) function unpause() poke(0x5f30,1) end -- an iterator over pressed keys function get_keys() return function() if (stat(30)) return stat(31) end end -- walk over a string, calling a callback on its chars function walk_str(str, cb) local i = 1 local x, y = 0, 0 if (not str) return i, x, y while i &lt;= #str do local ch = str[i] local spch = ch &gt;= '\x80' if (x &gt;= (spch and 31 or 32)) y += 1; x = 0 if (cb) cb(i,ch,x,y) if ch == '\n' then y += 1; x = 0 else x += (spch and 2 or 1) end i += 1 end return i, x, y end -- given string and index, return x,y at index function str_i2xy(str, ci) local cx, cy = 0, 0 local ei, ex, ey = walk_str(str, function(i,ch,x,y) if (ci == i) cx, cy = x, y end) if (ci &gt;= ei) cx, cy = ex, ey if (ex &gt; 0) ey += 1 return cx, cy, ey end -- given string and x,y - return index at x,y function str_xy2i(str, cx, cy) local ci = 1 local found = false local ei, ex, ey = walk_str(str, function(i,ch,x,y) if (cy == y and cx == x and not found) ci = i; found = true if ((cy &lt; y or cy == y and cx &lt; x) and not found) ci = i - 1; found = true end) if (not found) ci = cy &gt;= ey and ei or ei - 1 if (ex &gt; 0) ey += 1 return ci, ey end -- print string at position, using color value or function function str_print(str, xpos, ypos, color) if type(color) == &quot;function&quot; then walk_str(str, function(i,ch,x,y) print(ch, xpos + x*4, ypos + y*6, color(i)) end) else print(str and &quot;\^rw&quot; .. str, xpos, ypos, color) end end -- print code, using syntax highlighting function str_print_input(input, xpos, ypos) local tokens, _, tstarts, tends = tokenize(input) -- tlines not reliable! local ti = 1 str_print(input, xpos, ypos, function(i) while ti &lt;= #tends and tends[ti] &lt; i do ti += 1 end local token if (ti &lt;= #tends and tstarts[ti] &lt;= i) token = tokens[ti] local c = g_pal[5] if token == false then c = g_pal[6] -- error elseif token == true then c = g_pal[7] -- comment elseif type(token) != 'string' or isin(token, {&quot;nil&quot;,&quot;true&quot;,&quot;false&quot;}) then c = g_pal[8] elseif keyword_map[token] then c = g_pal[9] elseif not isalnum(token[1]) then c = g_pal[10] elseif globfuncs[token] then c = g_pal[11] end return c end) end -- draw (messy...) function _draw() local old_color = peek(0x5f25) local old_camx, old_camy = peek2(0x5f28), peek2(0x5f2a) camera() local function scroll(count) cursor(0,127) for _=1,count do rectfill(0,g_line*6,127,(g_line+1)*6-1,0) if g_line &lt; 21 then g_line += 1 else print &quot;&quot; end end end local function unscroll(count, minline) for _=1,count do if (g_line &gt; minline) g_line -= 1 rectfill(0,g_line*6,127,(g_line+1)*6-1,0) end end local function draw_cursor(x, y) for i=0,2 do local c = pget(x+i,y+5) pset(x+i,y+5,c==0 and g_pal[12] or 0) end end local function draw_input(cursor) local input = g_prompt .. g_input .. ' ' local cx, cy, ilines = str_i2xy(input, #g_prompt + g_cursor_pos) -- ' ' is cursor placeholder if ilines &gt; g_input_lines then scroll(ilines - g_input_lines) elseif ilines &lt; g_input_lines then unscroll(g_input_lines - ilines, ilines) end g_input_lines = ilines g_input_start = mid(g_input_start, 0, max(g_input_lines - 21, 0)) ::again:: local sy = g_line - g_input_lines + g_input_start if (sy+cy &lt; 0) g_input_start += 1; goto again if (sy+cy &gt;= 21) g_input_start -= 1; goto again local y = sy*6 rectfill(0,y,127,y+g_input_lines*6-1,0) if (g_input_lines&gt;21) rectfill(0,126,127,127,0) -- clear partial line str_print_input(input,0,y) print(g_prompt,0,y,g_pal[4]) if (g_cursor_time &gt;= 10 and cursor != false and not g_interrupt) draw_cursor(cx*4, y + cy*6) end -- require pressing enter to view more results local function page_interrupt(page_olines) scroll(1) g_line -= 1 print(&quot;[enter] ('esc' to abort)&quot;,0,g_line*6,g_pal[3]) while true do flip(); unpause() for key in get_keys() do if (key == '\x1b') g_abort = true; g_str_output = ''; g_results = {}; return false if (key == '\r' or key == '\n') g_num_output_lines += page_olines; return true end end end ::restart:: local ostart, olines if g_results or g_str_output then ostart, olines = str_xy2i(g_str_output, 0, g_num_output_lines) if olines - g_num_output_lines &lt;= 20 and g_results then -- add more output g_str_output, g_results = results_to_str(g_str_output, g_results) ostart, olines = str_xy2i(g_str_output, 0, g_num_output_lines) if (#g_results == 0 and not g_interrupt) g_results = nil end end if (not g_interrupt) camera() if (g_num_output_lines == 0 and not g_interrupt) draw_input(not g_str_output) if g_str_output then local output = sub(g_str_output, ostart) local page_olines = min(olines - g_num_output_lines, 20) scroll(page_olines) str_print(output,0,(g_line - page_olines)*6,g_pal[1]) if page_olines &lt; olines - g_num_output_lines then if (page_interrupt(page_olines)) goto restart else local _, _, elines = str_i2xy(g_error_output, 0) scroll(elines) str_print(g_error_output,0,(g_line - elines)*6,g_pal[2]) if g_interrupt then g_num_output_lines += page_olines else g_input, g_input_lines, g_input_start, g_cursor_pos, g_num_output_lines, g_str_output, g_error_output = '', 0, 0, 1, 0 draw_input() end end end if g_interrupt then scroll(1) g_line -= 1 print(g_interrupt,0,g_line*6,g_pal[3]) end if g_notice then scroll(1) g_line -= 1 print(g_notice,0,g_line*6,g_pal[3]) g_notice = nil end if g_notice_time then g_notice_time -= 1 if (g_notice_time == 0) g_notice, g_notice_time = '' end g_cursor_time -= 1 if (g_cursor_time == 0) g_cursor_time = 20 color(old_color) camera(old_camx, old_camy) if (g_line &lt;= 20) cursor(0, g_line * 6) end ------------------------ --- Execution loop ------------------------ g_in_execute_yield, g_in_mainloop, g_from_flip = false, false, false g_pending_keys = {} --lint: g_results, g_error, g_error_idx -- report compilation error -- an error of nil means code is likely incomplete function on_compile_fail(err, idx) g_error, g_error_idx = err, idx assert(false, err) end -- execute code function execute_raw(line, env, ...) return parse(line)(env or g_ENV, ...) end -- evaluate code function eval_raw(expr, env, ...) return execute_raw(&quot;return &quot; .. expr, env, ...) end -- try parse code function try_parse(line) local cc = cocreate(parse) ::_:: local ok, result = coresume(cc, line) if (ok and not result) goto _ -- this shouldn't happen anymore, but does (pico bug?) if (not ok) result, g_error = g_error, false return ok, result end function pos_to_str(line, idx) local x, y = str_i2xy(line, idx) return &quot;line &quot; .. y+1 .. &quot; col &quot; .. x+1 end -- execute code function execute(line, complete) g_results, g_abort, g_error = {}, false, false g_in_execute_yield, g_in_mainloop, g_from_flip = false, false, false -- create a coroutine to allow the code to yield to us periodically local coro = cocreate(function () local results = pack(execute_raw(line)) if (results.n != 0) add(g_results, results) end) local _ok, error while true do _ok, error = coresume(coro) if (costatus(coro) == 'dead') break -- handle yields (due to yield/flip or periodic) if g_enable_interrupt and not g_in_mainloop then g_interrupt = &quot;running, press 'esc' to abort&quot; _draw(); flip() g_interrupt = nil else if (g_enable_autoflip and not g_in_mainloop and not g_from_flip) flip() if (not g_enable_autoflip and holdframe) holdframe() g_from_flip = false end for key in get_keys() do if key == '\x1b' then g_abort = true else add(g_pending_keys, key) end end -- abort execution if needed if (g_abort) error = 'computation aborted'; break end if g_error == nil then -- code is incomplete if (complete) error = &quot;unexpected end of code&quot; else error, g_results = nil end if (g_error) error, g_error = g_error .. &quot;\nat &quot; .. pos_to_str(line, g_error_idx) g_error_output = error g_pending_keys = {} return not error end -- called periodically during execution yield_execute = function () -- yield all the way back to us g_in_execute_yield = true yield() g_in_execute_yield = false end -- override flip to force a yield_execute g_ENV.flip = function(...) local results = pack(flip(...)) g_from_flip = true yield_execute() return depack(results) end -- override coresume to handle yield_execute in coroutines g_ENV.coresume = function(co, ...) local results = pack(coresume(co, ...)) -- propagate yields from yield_execute while g_in_execute_yield do yield() results = pack(coresume(co)) -- and resume end g_error = false -- discard inner compilation errors (via \x) return depack(results) end -- override stat so we can handle keys ourselves g_ENV.stat = function(i, ...) if i == 30 then return #g_pending_keys &gt; 0 or stat(i, ...) elseif i == 31 then if #g_pending_keys &gt; 0 then return deli(g_pending_keys, 1) else local key = stat(i, ...) if (key == '\x1b') g_abort = true return key end else return stat(i, ...) end end -- simulate a mainloop. -- NOTE: -- real mainloop disables time/btnp updates, and also can't be recursed into/quit legally. -- the below doesn't disable time/btnp updates at all - but that's not important enough for us. function do_mainloop(env, continue) if not continue then if (_set_fps) _set_fps(env._update60 and 60 or 30) if (env._init) env._init() end g_in_mainloop = true while env._draw or env._update or env._update60 do -- if (_update_buttons) _update_buttons() -- this breaks btnp in its current form if (holdframe) holdframe() if env._update60 then env._update60() elseif env._update then env._update() end if (env._draw) env._draw() flip() g_from_flip = true yield_execute() end g_in_mainloop = false end ------------------------ -- Cart decompression ------------------------ k_old_code_table = &quot;\n 0123456789abcdefghijklmnopqrstuvwxyz!#%(){}[]&lt;&gt;+=/*:;.,~_&quot; -- Old code compression scheme - encodes offset+count for repeated code function uncompress_code_old(comp) local code, i = &quot;&quot;, 9 while true do local ch = ord(comp, i); i += 1 if ch == 0 then -- any pico8 char local ch2 = comp[i]; i += 1 if (ch2 == '\0') break -- end code ..= ch2 elseif ch &lt;= 0x3b then -- quick char from table code ..= k_old_code_table[ch] else -- copy previous code local ch2 = ord(comp, i); i += 1 local count = (ch2 &gt;&gt; 4) + 2 local offset = ((ch - 0x3c) &lt;&lt; 4) + (ch2 &amp; 0xf) for _=1,count do code ..= code[-offset] end end end return code end -- New code compression scheme - also uses move-to-front (mtf) and bit reading function uncompress_code_new(comp) local code, i, shift, mtf = &quot;&quot;, 9, 0, {} for idx=0,0xff do mtf[idx] = chr(idx) end local function getbit() local bit = (ord(comp, i) &gt;&gt; shift) &amp; 1 shift += 1 if (shift == 8) i += 1; shift = 0 return bit == 1 end local function getbits(n) local value = 0 for bit=0,n-1 do -- NOT fast value |= tonum(getbit()) &lt;&lt; bit end return value end while true do if getbit() then -- literal char local nbits, idx = 4, 0 while (getbit()) idx |= 1 &lt;&lt; nbits; nbits += 1 idx += getbits(nbits) local ch = mtf[idx] code ..= ch -- update mtf for j=idx,1,-1 do mtf[j] = mtf[j-1] end mtf[0] = ch else -- copy previous code (usually) local obits = getbit() and (getbit() and 5 or 10) or 15 local offset = getbits(obits) + 1 if offset == 1 and obits == 15 then break -- not an official way to recognize end, but works elseif offset == 1 and obits == 10 then -- raw block while true do local ch = getbits(8) if (ch == 0) break else code ..= chr(ch) end else local count = 3 repeat local part = getbits(3) count += part until part != 7 for _=1,count do -- we assume 0x8000 isn't a valid offset (pico8 doesn't produce it) code ..= code[-offset] end end end end return code end ------------------------ -- Console input ------------------------ --lint: g_ideal_x, g_key_code g_prev_paste = stat(4) g_key_time, g_lower = 0, false poke(0x5f5c,10,2) -- faster btnp -- return if keyboard key is pressed, using btnp-like logic function keyp(code) if stat(28,code) then if (code != g_key_code) g_key_code, g_key_time = code, 0 return g_key_time == 0 or (g_key_time &gt;= 10 and g_key_time % 2 == 0) elseif g_key_code == code then g_key_code = nil end end -- update console input function _update() local input = false local function go_line(dy) local cx, cy, h = str_i2xy(g_prompt .. g_input, #g_prompt + g_cursor_pos) if (g_ideal_x) cx = g_ideal_x cy += dy if (not (cy &gt;= 0 and cy &lt; h)) return false g_cursor_pos = max(str_xy2i(g_prompt .. g_input, cx, cy) - #g_prompt, 1) g_ideal_x = cx g_cursor_time = 20 -- setting input clears ideal x return true end local function go_edge(dx) local cx, cy = str_i2xy(g_prompt .. g_input, #g_prompt + g_cursor_pos) cx = dx &gt; 0 and 100 or 0 g_cursor_pos = max(str_xy2i(g_prompt .. g_input, cx, cy) - #g_prompt, 1) input = true end local function go_history(di) g_history[g_history_i] = g_input g_history_i += di g_input = g_history[g_history_i] if di &lt; 0 then g_cursor_pos = #g_input + 1 else g_cursor_pos = max(str_xy2i(g_prompt .. g_input, 32, 0) - #g_prompt, 1) -- end of first line local ch = g_input[g_cursor_pos] if (ch and ch != '\n') g_cursor_pos -= 1 end input = true end local function push_history() if #g_input &gt; 0 then if (#g_history &gt; 50) del(g_history, g_history[1]) g_history[#g_history] = g_input add(g_history, '') g_history_i = #g_history input = true end end local function delchar(offset) if (g_cursor_pos+offset &gt; 0) then g_input = sub(g_input,1,g_cursor_pos+offset-1) .. sub(g_input,g_cursor_pos+offset+1) g_cursor_pos += offset input = true end end local function inschar(key) g_input = sub(g_input,1,g_cursor_pos-1) .. key .. sub(g_input,g_cursor_pos) g_cursor_pos += #key input = true end local ctrl = stat(28,224) or stat(28,228) local shift = stat(28,225) or stat(28,229) local keycode = -1 if keyp(80) then -- left if (g_cursor_pos &gt; 1) g_cursor_pos -= 1; input = true elseif keyp(79) then -- right if (g_cursor_pos &lt;= #g_input) g_cursor_pos += 1; input = true elseif keyp(82) then -- up if ((ctrl or not go_line(-1)) and g_history_i &gt; 1) go_history(-1) elseif keyp(81) then -- down if ((ctrl or not go_line(1)) and g_history_i &lt; #g_history) go_history(1) else local key = stat(31) keycode = ord(key) if key == '\x1b' then -- escape if #g_input == 0 then extcmd &quot;pause&quot; else g_results, g_error_output = {}; push_history() end elseif key == '\r' or key == '\n' then -- enter if shift then inschar '\n' else execute(g_input) -- sets g_results/g_error_output if (not g_results) inschar '\n' else push_history() end elseif ctrl and keyp(40) then -- ctrl+enter execute(g_input, true); push_history() elseif key != '' and keycode &gt;= 0x20 and keycode &lt; 0x9a then -- ignore ctrl-junk if (g_lower and keycode &gt;= 0x80) key = chr(keycode - 63) inschar(key) elseif keycode == 193 then -- ctrl+b inschar '\n' elseif keycode == 192 then -- ctrl+a go_edge(-1) elseif keycode == 196 then -- ctrl+e go_edge(1) elseif keycode == 203 then -- ctrl+l g_lower = not g_lower g_notice, g_notice_time = &quot;shift now selects &quot; .. (g_lower and &quot;punycase&quot; or &quot;symbols&quot;), 40 elseif keyp(74) then -- home if (ctrl) g_cursor_pos = 1; input = true else go_edge(-1); elseif keyp(77) then -- end if (ctrl) g_cursor_pos = #g_input + 1; input = true else go_edge(1); elseif keyp(42) then delchar(-1) -- backspace elseif keyp(76) then delchar(0) -- del end end local paste = stat(4) if (paste != g_prev_paste or keycode == 213) inschar(paste); g_prev_paste = paste -- ctrl+v if keycode == 194 or keycode == 215 then -- ctrl+x/c if g_input != '' and g_input != g_prev_paste then g_prev_paste = g_input; printh(g_input, &quot;@clip&quot;); if (keycode == 215) g_input = ''; g_cursor_pos = 1; g_notice = &quot;press again to put in clipboard&quot; else g_notice = '' end end if stat(120) then -- file drop local str, count = &quot;&quot; repeat count = serial(0x800,0x5f80,0x80) str ..= chr(peek(0x5f80,count)) until count == 0 if (not load_cart(str)) inschar(str) end if (input) g_cursor_time, g_ideal_x = 20 g_key_time += 1 unpause() end ------------------------ -- Main ------------------------ -- my own crummy mainloop, since time() does not seem to update if the regular mainloop goes &quot;rogue&quot; and flips. function toplevel_main() while true do if (holdframe) holdframe() _update() _draw() flip() end end -- Self-test -- (so I can more easily see if something got regressed in the future (esp. due to pico8 changes)) function selftest(i, cb) local ok, error = coresume(cocreate(cb)) if not ok then printh(&quot;error #&quot; .. i .. &quot;: &quot; .. error) print(&quot;error #&quot; .. i .. &quot;\npico8 broke something again,\nthis cart may not work.\npress any button to ignore&quot;) while (btnp() == 0) flip() cls() end end selftest(1, function() assert(pack(eval_raw &quot;(function (...) return ... end)(1,2,nil,nil)&quot; ).n == 4) end) selftest(2, function() assert(eval_raw &quot;function() local temp, temp2 = {max(1,3)}, -20;return temp[1] + temp2; end&quot; () == -17) end) ------------------------------------------------------- -- We're running out of tokens! -- What to do? Well, we already have an interpreter above, -- so we might as well as interpret the rest of our code! -- -- But looking at code inside strings isn't fun, so I'm automatically moving -- all the below code (after the count::stop) into the $$BELOW$$ string -- when creating the cart. ------------------------------------------------------- _ENV.g_ENV = g_ENV -- make g_ENV a global, so it can be accessed by below code execute_raw(&quot;$$BELOW$$&quot;, _ENV) --lint: count::stop ------------------------ -- Special \-commands ------------------------ -- execute a repl-specific command function cmd_exec(name) if isin(name, {&quot;i&quot;,&quot;interrupt&quot;}) then return g_enable_interrupt elseif isin(name, {&quot;f&quot;,&quot;flip&quot;}) then return g_enable_autoflip elseif isin(name, {&quot;r&quot;,&quot;repl&quot;}) then return g_enable_repl elseif isin(name, {&quot;mi&quot;,&quot;max_items&quot;}) then return g_show_max_items elseif isin(name, {&quot;h&quot;,&quot;hex&quot;}) then return g_hex_output elseif isin(name, {&quot;pr&quot;,&quot;precise&quot;}) then return g_precise_output elseif isin(name, {&quot;cl&quot;,&quot;colors&quot;}) then return g_pal elseif isin(name, {&quot;c&quot;,&quot;code&quot;}) then local code = {[0]=g_input} for i=1,#g_history-1 do code[i] = g_history[#g_history-i] end return code elseif isin(name, {&quot;cm&quot;,&quot;compile&quot;}) then return function(str) return try_parse(str) end elseif isin(name, {&quot;x&quot;,&quot;exec&quot;}) then return function(str, env, ...) execute_raw(str, env, ...) end elseif isin(name, {&quot;v&quot;,&quot;eval&quot;}) then return function(str, env, ...) return eval_raw(str, env, ...) end elseif isin(name, {&quot;p&quot;,&quot;print&quot;}) then return function(str, ...) g_ENV.print(value_to_str(str), ...) end elseif isin(name, {&quot;ts&quot;,&quot;tostr&quot;}) then return function(str) return value_to_str(str) end elseif isin(name, {&quot;rst&quot;,&quot;reset&quot;}) then run() -- full pico8 reset elseif isin(name, {&quot;run&quot;}) then do_mainloop(g_ENV) elseif isin(name, {&quot;cont&quot;}) then do_mainloop(g_ENV, true) else assert(false, &quot;unknown \\-command&quot;) end end -- assign to a repl-specific command function cmd_assign(name) local function trueish(t) return (t and t != 0) and true or false end local func if isin(name, {&quot;i&quot;,&quot;interrupt&quot;}) then func = function(v) g_enable_interrupt = trueish(v) end elseif isin(name, {&quot;f&quot;,&quot;flip&quot;}) then func = function(v) g_enable_autoflip = trueish(v) end elseif isin(name, {&quot;r&quot;,&quot;repl&quot;}) then func = function(v) g_enable_repl = trueish(v) end elseif isin(name, {&quot;mi&quot;,&quot;max_items&quot;}) then func = function(v) g_show_max_items = tonum(v) or -1 end elseif isin(name, {&quot;h&quot;,&quot;hex&quot;}) then func = function(v) g_hex_output = trueish(v) end elseif isin(name, {&quot;pr&quot;,&quot;precise&quot;}) then func = function(v) g_precise_output = trueish(v) end elseif isin(name, {&quot;cl&quot;,&quot;colors&quot;}) then func = function(v) g_pal = v end else assert(false, &quot;unknown \\-command assign&quot;) end -- do some trickery to allow calling func upon assignment -- (as we're expected to return the assignment target) local obj = { __newindex=function(t,k,v) func(v) end, __index=function() return cmd_exec(name) end, -- op-assign needs this } return setmetatable(obj, obj), 0 end ------------------------ -- Misc. ------------------------ function load_cart(str) -- is this a full rom? (I'm assuming nobody will drop exactly-32kb text files here!) local code, full = sub(str, 0x4301) if #code == 0x3d00 then full = true poke(0, ord(str, 1, 0x4300)) -- load rom else code = str -- else, either tiny-rom or plaintext end local header = sub(code, 1, 4) if header == &quot;:c:\0&quot; then code = uncompress_code_old(code) elseif header == &quot;\0pxa&quot; then code = uncompress_code_new(code) elseif full then code = split(code, '\0')[1] else -- either plaintext or a tiny/uncompressed tiny-rom (indistinguishable) return end -- run in ideal execution environment g_enable_interrupt, g_enable_repl = false, false local ok = execute(code, true) g_enable_repl = true if (ok) execute(&quot;\\run&quot;) -- we need to call do_mainloop from within execute, this is the easiest way return true end toplevel_main()</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p></div></div></div></p> <h2>Changelog</h2> <p>That's not a question.</p> <p>Uh - I mean:</p> <h4>v35:</h4> <ul> <li>Updated subtleties of identifiers to match those of Pico8 (well, allow ゛ and ゜)</li> <li>Remove support for nested comments (as Pico-8 did)</li> </ul> <h4>v34:</h4> <ul> <li>Updated subtleties of shorthands to match those of the latest Pico version</li> <li>Support nested comments (Pico-8 oddity)</li> </ul> <h4>v33:</h4> <ul> <li>Updated due to breaking change in Pico v0.2.5d</li> </ul> <h4>v32:</h4> <ul> <li>Support drag &amp; drop of carts in .p8.rom format.</li> <li>Added \cont to continue a previous \run</li> <li>Fix btnp under \run</li> <li>Support '//' comments (Pico-8 oddity)</li> </ul> <h4>v31:</h4> <ul> <li>Support new Pico v0.2.5 '~' alias for '^^'</li> <li>Added \pr to enable precise number printing</li> <li>Allow return and ... at top-level</li> <li>Allow setting ... when calling \x/\v/etc.</li> </ul> <h4>v30:</h4> <ul> <li>Further commented source code (result was too large, so it was placed in this post)</li> <li>Added \ts and \p functions</li> <li>Avoid calling flip twice per frame (Used to make some sense in an earlier pico version)</li> </ul> <h4>v29:</h4> <ul> <li>Syntax checking is now stricter, like in lua/pico-8</li> <li>Fixed scope of repeat-until block to include the 'until' expression</li> <li>Fixed code that combines a method call (':') with an unparenthesized argument</li> <li>Fixed subtle issues with goto</li> <li>Lots of other minor syntax fix-ups</li> <li>Added Ctrl+L for typing punycase, and added _ENV</li> <li>Changed \r=0 to also disable _ and _env (but not _ENV, which is a real language feature)</li> <li>Assigning to _env no longer changes the environment - you need to assign to _ENV instead</li> <li>Added \cm and optional env arg to \x and \v</li> <li>Print compilation error positions</li> </ul> <h4>v28:</h4> <ul> <li>Minor bugfixies</li> <li>Added &quot;\r=0&quot; to disable automatic printing of results</li> </ul> <h4>v27:</h4> <ul> <li>Support for delete, [ctrl+]home/end, shift+enter, and ctrl+up/down keys</li> <li>Support for dropping text files to cart</li> <li>Support printing of P8SCII strings</li> </ul> <h4>v26:</h4> <ul> <li>Hackfix for regression introduced in Pico v0.2.4 (enabled if needed)</li> <li>Support for P8SCII escape codes</li> </ul> <h4>v25:</h4> <ul> <li>Proper support for _env reassignment/redeclaration, just for completion's sake. (_ENV reassignment since v29)</li> </ul> <h4>v24:</h4> <ul> <li>Fixed keyboard input. (stat(30) and stat(31))</li> <li>Fixed output for code that prints something and then hangs for a while.</li> <li>Added &quot;\f=0&quot; to disable auto-flip (also requires &quot;\i=0&quot;)</li> </ul> <h4>v23:</h4> <ul> <li>Faster trailing-nil handling using new Pico8 APIs.</li> <li>Self-test on startup. (Will give an error if there's a regression again)</li> <li>Fixed \eval for tail calls</li> </ul> <h4>v22:</h4> <ul> <li>Fixed for regression introduced in Pico v0.2.1 (enabled if needed)</li> </ul> <h4>v21:</h4> <ul> <li>Updated for changed Pico v0.2.0f opcodes. (Now requires v0.2.0f and up)</li> </ul> <h4>v20:</h4> <ul> <li>Fixed for Pico v0.2.0e (no longer relying on pico8's coroutine bug)</li> </ul> <h4>v19:</h4> <ul> <li>Added new Pico v0.2.0d opcodes. (Now requires v0.2.0d and up)</li> <li>Added v0.2.0 while shorthand</li> </ul> <h4>v18:</h4> <ul> <li>Added new Pico v0.2.0 opcodes. (Now requires Pico8 v0.2.0 and up)</li> <li>Changed all @-prefixed identifiers/commands to be \-prefixed instead, now that pico8 stole @ from me.</li> <li>Added \hex</li> </ul> <h4>v17:</h4> <ul> <li>Fixed editing of large inputs</li> </ul> <h4>v16:</h4> <ul> <li>Added \exec &amp; \eval</li> </ul> <h4>v15:</h4> <ul> <li>Fully working trailing-nil handling. (Thanks to JWinslow23 &amp; sparr for ideas in the discord)</li> </ul> <h4>v14:</h4> <ul> <li>Ctrl+A/E as a poor linuxman's home/end</li> </ul> <h4>v13:</h4> <ul> <li>More trailing-nil handling fixes.</li> </ul> <h4>v12:</h4> <ul> <li>Ctrl+B inserts a line break.</li> <li>Added \reset &amp; \run</li> <li>Minor syntax fixies. (Around pico-8's questionably-parsed syntax)</li> </ul> <h4>v11:</h4> <ul> <li>Fix time()/t() not working correctly.</li> </ul> <h4>v10:</h4> <ul> <li>Supported copying/pasting in BBS. (Now that it's supported by pico)</li> <li>Fixed syntax highlighting in long lines.</li> <li>Fixed some... &quot;security holes&quot; in the repl. (__pairs, global overrides)</li> </ul> <h4>v9:</h4> <ul> <li>Syntax highlighting! (Customizable, too - see \cl)</li> </ul> <h4>v8:</h4> <ul> <li>Added \c to allow programmatically accessing code.</li> <li>Added table printing ({1,2,3} instead of &lt;table&gt;), \mi</li> <li>Some minor syntax fixies</li> </ul> <h4>v7:</h4> <ul> <li>Allow escape to bring up the pause menu when not used to cancel input.</li> </ul> <h4>v6:</h4> <ul> <li>Added paste support</li> <li>2 minor syntax bugfixes</li> </ul> <h4>v5:</h4> <ul> <li>Support goto and tailcalls</li> <li>Fix recursion.</li> </ul> <h4>v4:</h4> <ul> <li>Fix expressions like 'type(nil)'</li> </ul> <h4>v3:</h4> <ul> <li>Support comments, bracket-string literals and all string literal escapes</li> <li>Support dots/colons in function name</li> </ul> <h4>v2:</h4> <ul> <li>Added &quot;\i=0&quot; command to disable &quot;interruptions&quot; while executing code.</li> <li>Fixed nasty glitch that would cause code to sometimes appear to be failing by spamming errors.</li> <li>Fixed paging of 20*k+1 output lines</li> </ul> <h4>v1:</h4> <ul> <li>Original version, support for almost all pico-8/lua syntax.</li> </ul> https://www.lexaloffle.com/bbs/?tid=36381 https://www.lexaloffle.com/bbs/?tid=36381 Thu, 26 Dec 2019 23:09:43 UTC Fairchild Channel F Emulator <p> <table><tr><td> <a href="/bbs/?pid=64568#p"> <img src="/bbs/thumbs/pico8_fairchild001-11.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=64568#p"> Fairchild Channel F Emulator</a><br><br> by <a href="/bbs/?uid=29645"> thisismypassword</a> <br><br><br> <a href="/bbs/?pid=64568#p"> [Click to Play]</a> </td></tr></table> </p> <p><strong>Update 1</strong> - full speed &amp; better sound.<br /> <strong>Update 2</strong> - fix missing mouse cursor.<br /> <strong>Update 3</strong> - fix speed decrease in Pico8 0.2.0<br /> <strong>Update 4</strong> - support cart/bios drag&amp;drop and function keys.</p> <h2>What is this?</h2> <p>An emulator for the Fairchild Channel F, the first console to use programmable ROMs and a microprocessor.<br /> The console was released in 1975, so don't expect much in the way of graphics, gameplay, or usability.<br /> See <a href="https://en.wikipedia.org/wiki/Fairchild_Channel_F">Wikipedia</a> for more info.</p> <h2>And what's that game running?</h2> <p>That's a homebrewed (i.e. - NOT original) game made by Blackbird and e5frog. You can find it <a href="http://channelf.se/veswiki/index.php?title=Homebrew:Pac-Man">here</a>.<br /> Any sound glitches are due to the emulator, not the game, by the way. (See below)</p> <p>It pushes the capabilities of the console to its limits, so its quality is considerably higher than that of the other games for the console.<br /> It's also anachronistic - the original Pac-Man wasn't released until 1980.</p> <h2>And how do I play other games?</h2> <p>In order to play other games, you first need the console's two BIOS files and the ROM for the other game you want to play. (Having these is up to you and they cannot be shared)</p> <p>If you have those, simply drag &amp; drop them one-by-one into pico-8. (E.g. drop each of the bios files, then drop the cart).<br /> If you want to run the game included in the bios, drop only the 2 bios files, then press the reset button.</p> <p>By the way, a BIOS is needed since the &quot;BIOS&quot; included with the emulator is a homebrewed one which is only good enough for running a few games which don't use it much.</p> <p>If you wish to copy the BIOS and ROM to the cart yourself - e.g. via cstore, they should be copied sequentially: BIOS SL31253 or SL90025 to location 0x0, BIOS SL31254 to location 0x400, and the cart to location 0x800. (Just like the Fairchild's own memory layout)</p> <h2>What are the controls?</h2> <p>In &quot;1P&quot; mode (default), the controls are:</p> <ul> <li>arrow keys = move</li> <li>Z = push</li> <li>S/F = rotate left/right</li> <li>E/D = push/pull</li> <li>left shift = hold to control player 2 instead of player 1</li> </ul> <p>In &quot;2P&quot; mode, the controls are:</p> <ul> <li>arrow keys = move</li> <li>Z + arrow keys = rotate/push/pull</li> <li>S/F/E/D = player 2 move</li> <li>left shift + S/F/E/D = player 2 rotate/push/pull</li> </ul> <p>In both modes, additional controls are:</p> <ul> <li>X + left/down/up/right = press console key 1/2/3/4. (can also use the mouse, or F1 through F4)</li> <li>X + Z = switch between 1P/2P modes. (can also use the mouse or F5; only affects the controls)</li> <li>(to reset, can use the mouse or F12)</li> </ul> <h2>What's working well?</h2> <p>All functionality is working and most likely bug-free, so all original and homebrew games work well, including the carts with extra memory.<br /> (Exception: no support for the homebrewed multicart, which is too large for pico8 anyway)</p> <p>Thanks to some optimizations, games now usually run at full speed.</p> <h2>What's not working well?</h2> <p>Sound has occasional glitches (either due to pico8's sound limitations or my implementation).</p> <p>Keys sometime need to be pressed for a few moments to work in some games. This might be due to the original console's controls being slow to press.</p> <h2>What's the point of this?</h2> <p>Proving that emulators for non-trivial systems are possible to do in pico-8.<br /> And just for the fun of creating and/or playing an emulator!</p> https://www.lexaloffle.com/bbs/?tid=34262 https://www.lexaloffle.com/bbs/?tid=34262 Sat, 18 May 2019 21:25:44 UTC