{"id":1042,"date":"2017-04-30T18:25:28","date_gmt":"2017-04-30T16:25:28","guid":{"rendered":"http:\/\/www.hunggartorino.it\/ql\/?p=1042"},"modified":"2017-05-07T22:47:48","modified_gmt":"2017-05-07T20:47:48","slug":"findkey","status":"publish","type":"post","link":"https:\/\/www.hunggartorino.it\/ql\/findkey\/","title":{"rendered":"Find the Key"},"content":{"rendered":"<p>While fiddling around with something, I chanced to delve a bit deeper into SMSQ\/E&#8217;s PEEK family of keywords. I wandered if it would be possible to use them to solve an ancient problem.<\/p>\n<p>Heres a scenario, or rather, two:<\/p>\n<ol>\n<li>A demo program loader, or an installation program, needs to check whether certain toolkits are already loaded on a punter&#8217;s machine or whether it needs to load them itself, or choose a different version of the compiled program to execute, &#8211; or to make a dash for the hills.<\/li>\n<li>A compiled program needs to check whether the useful QMenu FILE_SELECT$ utility is available, or whether it should use its own, simple, fall-back solution, to get a file name from the user.<\/li>\n<\/ol>\n<p>Both of these scenarios could be satisfied if it were possible to check whether certain keywords are loaded in the machine or not.<\/p>\n<p>The problem is easily solved in the case of compiled programs. Just devise a small machine code extension to scan the Name Table, and link it to the compiled program in the usual way. It takes a few extra bytes while the program is in use, and then goes away. A demo program launcher or an installation program could install such a toolkit for this task, but then cannot tidy the mess afterwards; the useless toolkit remains until the next reboot. No big deal, perhaps, but would it be possible to check for keywords from S*BASIC without the use of toolkits?<\/p>\n<p>Looking at the examples given in the manual, PEEK(\\\\) looks like the man for the job.<\/p>\n<p>So here was my first attempt, written in SMSQ\/E SBASIC:<\/p>\n<h2>FindKey 1<\/h2>\n<pre>10 DEFine FuNction <a>FindKey<\/a>(k$)\r\n11 LOCal n, c, o, l%\r\n12 REMark Returns true if keyword loaded\r\n13 REMark in this SBASIC\r\n14 :\r\n15 REMark Get size of Name Table\r\n16 c = PEEK_L(\\\\ $1C) - PEEK_L(\\\\ $18)\r\n17 FOR n = 0 TO c STEP 8\r\n18  REMark Weed out non-keywords Types\r\n19  IF PEEK_W(\\ $18\\ n) &lt; $800: NEXT n: EXIT n\r\n20  REMark Get offset in Name List\r\n21  o = PEEK_W(\\ $18\\ n + 2)\r\n22  REMark Skip no names\r\n23  IF o &lt; 0: NEXT n: EXIT n\r\n24  REMark Get Name length\r\n25  l% = PEEK(\\ $20\\ o)\r\n26  REMark and Name\r\n27  IF k$ == PEEK$(\\ $20\\ o + 1, l%): RETurn 1\r\n28 END FOR n\r\n29 RETurn 0\r\n30 END DEFine FindKey\r\n<\/pre>\n<p>Well, that worked a treat. Quick and simple. Problem solved!<\/p>\n<p>Not.<\/p>\n<p>First of all QLib wasnt having any of it. It didnt like my hex and it was totally biased against SMSQ\/E&#8217;s version of PEEK, prefering its own version, which doesnt understand offsets.<\/p>\n<p>The other, more serious, problem is that although the program above does work in SBASIC daughter jobs, it is useless: It only shows a <i>subset<\/i> of all the available keywords. This is because each of the SBASIC daughter jobs gets its own Name Table (naturally), but in this table only keywords in the current program and any immediate commands typed at the console make it into the local Name Table. The main table is associated only with job #0, guardian of the SBASIC Stub. This stub, a construct devised to maintain a high degree of compatability with SuperBASIC, holds the pointers to the real Name Table, maintained somewhere in the Common Heap. Like SuperBASIC&#8217;s data space, this Name Table can move, but not in the same way nor for all the same reasons as SuperBASIC.<\/p>\n<p>It can move when engorged with new keywords as toolkits are loaded in job#0 at boot time. It can move when lots of new names are added to the interpreter in job#0. When it needs to grow, a new, larger area is reserved for it in the Common Heap, the current values are then copied over, and finally the pointers in the SBASIC Variables area are updated, before the old Name Table heap is discarded. (It can also shrink, due to a clearout of names from interpeter#0, which will affect at least some pointers.) In other words, it will only move due to some activity going on in the job#0 interpreter.<\/p>\n<p>The moving memory model employed by SuperBASIC evokes atavistic nightmares and strange nervous ticks in many a stout hobby programmer. SBASIC doesnt really have to stress so, but valiantly maintains a fascade of forgiving compatability for the benefit of ancient programs and cheeky programmers.<\/p>\n<p>So back to the issue at hand: I could see now that I somehow had to scan the real table, job #0&#8217;s Name Table. The problem is there is no easy way to grab that table by the <i>cojones<\/i> and pin it down by invoking supervisor mode from SBASIC. What to do?<\/p>\n<p>In the two scenarios I envisage above, any scanning of the Name Table, whether in job#0, a daughter SBASIC, or a compiled job, could be made to occur at the very start of a given program. It is highly unlikely that a large S*BASIC program would be started at exactly the same time, and if used in a boot program, nothing apart from my program&#8217;s own instructions would be happening in job#0. Although the unlikely, famously, should never be confused with the impossible, in SMSQ\/E at least, it seems a pretty safe bet.<\/p>\n<p>I thought Id go ahead and try it out. Then Id stress test it to see how great the issue of moving memory was to my scheme. So I added some trival tests. If the worst came to the worst and it did move, it should not have worse consequences than giving a wrong answer once in a blue moon:<\/p>\n<h2>FindKey 2<\/h2>\n<pre>10 DEFine FuNction <a>FindKey<\/a>(k$)\r\n11 LOCal n, o, a6, c, s, l%\r\n12 REMark Searches all keywords\r\n13 REMark Return true on match\r\n14 :\r\n15 REMark Get job#0's JCB\r\n16 s = PEEK_L(! $68! 0)\r\n17 REMark Get job#0's a6\r\n18 a6 = PEEK_L(s + $58)\r\n19 :\r\n20 REMark Some GLOBal definitions\r\n21 sb_nmtbb = a6 + $18\r\n22 sb_nmtbp = a6 + $1C\r\n23 sb_nmlsb = a6 + $20\r\n24 :\r\n25 REMark Size of Name Table\r\n26 c = PEEK_L(sb_nmtbp) - PEEK_L(sb_nmtbb)\r\n27 :\r\n28 REMark Go through list\r\n29 FOR n = 0 TO c STEP 8\r\n30  :\r\n31  REMark Weed out non-keywords Types\r\n32  IF PEEK_W(PEEK_L(sb_nmtbb) + a6 + n) &lt; $800: NEXT n: EXIT n\r\n33  :\r\n34  REMark Get offset in Name List\r\n35  o = PEEK_W(PEEK_L(sb_nmtbb) + a6 + n + 2)\r\n36  :\r\n37  REMark Skip no names\r\n38  IF o &lt;= 0: NEXT n: EXIT n\r\n39  :\r\n40  REMark Get Name length\r\n41  l% = PEEK(PEEK_L(sb_nmlsb) + a6 + o)\r\n42  IF l% &gt; 255: NEXT n: EXIT n\r\n43  :\r\n44  REMark Display Name\r\n45  IF k$ == PEEK$(PEEK_L(sb_nmlsb) + a6 + o + 1, l%): RETurn 1\r\n46 END FOR n\r\n47 RETurn 0\r\n48 END DEFine ListKeys\r\n49 :\r\n50 PRINT FindKey(\"block\")\r\n51 PRINT FindKey(\"blox\")\r\n<\/pre>\n<p>As it stands, its optimised for presentation rather than speed. RUNning the program in any SBASIC should print 1 and 0 to the screen, whether or no BLOCK is loaded in the local interpreter. It still wont run on anything but SBASIC, so further modification is needed to make it universal.<\/p>\n<p>A lot of programmers assume that the <i>System<\/i> Variables always will be located at address $28000, as they are under Qdos on an original QL. But even on the QL it wasnt intended to be fixed at that address, what with the second screen and all. But thats how the cookie crumbled, and SBASIC has obligingly condoned it by supplying its System Variables at the same address. However, that is mainly to allow users to run old programs that cannot be altered. New programs should not rely on unintended addresses. The next generation of emulators or hardware (you wish!) may, for technical reasons, not be so accommodating. Mercifully, in a little hack nicked from Minerva, the function VER$(-2) is available in SMSQ\/E too. QDOS, of course, will never change, so this switch should work on all current systems (Please let us know if you know otherwise!):<\/p>\n<pre>IF VER$ = 'JSL' OR VER$ = 'HBA' THEN\r\n SYSV = VER$(-2): REMark Minerva\/SMSQ\/E\r\nELSE\r\n SYSV = 163840:   REMark QDOS\r\nEND IF\r\n<\/pre>\n<p>While the System Variables may not always be located in the &#8220;standard&#8221; location (at least from Minerva and onwards), once the system is up and running, their location remains fixed throughout the session, so this only needs to be checked once.<\/p>\n<p>The final listing shows the completed program:<\/p>\n<h2>FindKey 3<\/h2>\n<pre>10 DEFine FuNction <a>FindKey<\/a>(k$)\r\n11 LOCal i, n, o, a6, c, s\r\n12 LOCal l%, k%\r\n13 REMark GLOBal SYSV\r\n14 :\r\n15 REMark Get job#0's JCB\r\n16 s = PEEK_L(PEEK_L(<em>SYSV<\/em> + 104)): REMark $68\r\n17 REMark Get job#0's a6\r\n18 a6 = PEEK_L(s + 88): REMark $58\r\n19 :\r\n20 REMark Some GLOBal definitions\r\n21 sb_nmtbb = a6 + 24: REMark $18\r\n22 sb_nmtbp = a6 + 28: REMark $1C\r\n23 sb_nmlsb = a6 + 32: REMark $20\r\n24 :\r\n25 REMark Size of Name Table\r\n26 c = PEEK_L(sb_nmtbp) - PEEK_L(sb_nmtbb)\r\n27 :\r\n28 k% = LEN(k$)\r\n29 REMark Go through list\r\n30 FOR n = 0 TO c STEP 8\r\n31  :\r\n32  REMark Weed out non-keywords: REMark   $800\r\n33  IF PEEK_W(PEEK_L(sb_nmtbb) + a6 + n) &lt; 2048: NEXT n: EXIT n\r\n34  :\r\n35  REMark Get offset in Name List\r\n36  o = PEEK_W(PEEK_L(sb_nmtbb) + a6 + n + 2)\r\n37  :\r\n38  REMark Skip no names\r\n39  IF o &lt; 0: NEXT n: EXIT n\r\n40  :\r\n41  REMark Get Name length\r\n42  l% = PEEK(PEEK_L(sb_nmlsb) + a6 + o)\r\n43  :\r\n44  REMark For slow QLs\r\n45  IF l% &lt;&gt; k%: NEXT n: EXIT n\r\n46  REMark Compare strings\r\n47  FOR i = 1 TO l%\r\n48   IF NOT CHR$(PEEK(PEEK_L(sb_nmlsb) + a6 + o + i)) == k$(i): l% = 0: EXIT i\r\n49  END FOR i\r\n50  IF l% &gt; 0: RETurn 1: REMark Bingo!\r\n51 END FOR n\r\n52 RETurn 0\r\n53 END DEFine FindKey\r\n54 :\r\n55 REMark Get pointer to System Variables\r\n56 v$ = VER$\r\n57 IF v$ = \u2018JSL\u2019 OR v$ = \u2018HBA\u2019 THEN\r\n58 SYSV = VER$(-2): REMark Minerva\/SMSQ\/E\r\n59 ELSE\r\n60 REMark QDOS\r\n61 SYSV = 163840: REMark $28000\r\n62 END IF\r\n63 :\r\n64 PRINT FindKey(\"block\")\r\n65 PRINT FindKey(\"blox\")\r\n<\/pre>\n<p>As I mentioned, theres no easy way to stop the SuperBASIC area from moving while this routine runs its course, so <u>it is only safe to use in boot programs<\/u>. It <i>may<\/i> be ok to use in a limited way in compiled programs too, if used sparingly at startup, the assumption being that its unlikely that a large S*BASIC program will be loaded at the same instance as your compiled program initialises:<\/p>\n<pre>10 REMark $$stak=800\r\n20 :\r\n100 REMark My little program\r\n110 qmenu = FindKey(\"FILE_SELECT$\")\r\n120 sound = FindKey(\"SOUNDFILE\")\r\n130 :\r\n140 REMark Rest of My little program goes here..\r\n...\r\n710 IF sound: Play \"tune\": ELSE: Beeep\r\n...\r\n<\/pre>\n<p>Disclaimer: I have not deliberately set out to mislead anyone, but errors may occur. There is not much published information on any of this out there, so it comes down to stitching together what scraps one can find, tracing through the source code, trying things out, and discussing with others. Please let us know how you get on with any of this, if you discover any errors, or if you have experience of other systems that dont seem to conform with the information given here. Good or bad, your feedback is welcome. Contact me through the comment facility here, or see you on QL-users or in the QL Forum!<\/p>\n","protected":false},"excerpt":{"rendered":"While fiddling around with something, I chanced to delve a bit deeper into SMSQ\/E&#8217;s PEEK family of keywords. I wandered if it would \n<a class=\"moretag\" href=\"https:\/\/www.hunggartorino.it\/ql\/findkey\/\"> [..more...]<\/a>","protected":false},"author":5,"featured_media":1049,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[43],"tags":[],"class_list":["post-1042","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-programming"],"_links":{"self":[{"href":"https:\/\/www.hunggartorino.it\/ql\/wp-json\/wp\/v2\/posts\/1042","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.hunggartorino.it\/ql\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.hunggartorino.it\/ql\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.hunggartorino.it\/ql\/wp-json\/wp\/v2\/users\/5"}],"replies":[{"embeddable":true,"href":"https:\/\/www.hunggartorino.it\/ql\/wp-json\/wp\/v2\/comments?post=1042"}],"version-history":[{"count":3,"href":"https:\/\/www.hunggartorino.it\/ql\/wp-json\/wp\/v2\/posts\/1042\/revisions"}],"predecessor-version":[{"id":1053,"href":"https:\/\/www.hunggartorino.it\/ql\/wp-json\/wp\/v2\/posts\/1042\/revisions\/1053"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.hunggartorino.it\/ql\/wp-json\/wp\/v2\/media\/1049"}],"wp:attachment":[{"href":"https:\/\/www.hunggartorino.it\/ql\/wp-json\/wp\/v2\/media?parent=1042"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.hunggartorino.it\/ql\/wp-json\/wp\/v2\/categories?post=1042"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.hunggartorino.it\/ql\/wp-json\/wp\/v2\/tags?post=1042"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}