Bighellonando qua e là, mi è capitato di andare a frugare dentro la famiglia PEEK di parole chiave di SMSQ/E. Mi sono chiesto se fosse possibile usarle per risolvere un annoso problema.
Ecco lo scenario (o meglio i due scenari):
-
Un programma per caricare una demo, o per installare un programma, necessita di verificare se determinati toolkit sono già caricati in una punter’s machine o se essa necessita di caricarseli da sé, o scegliere una diversa versione
del programma compilato da eseguire. O darsela a gambe. -
Un programma compilato necessita di verificare se la pratica utility QMenu FILE_SELECT$ sia disponibile, o se deve usarsela da sé, semplice, alternativa, per ottenere il nome di un file dall’utente.
Entrambi gli scenari potrebbero essere soddisfatti se fosse possibile verificare se cerrte parole chiave siano caricate nella macchina o no.
Il problema è facilmente risolvibile nel caso dei programmi compilati. Basta creare una piccola estensione in codice macchina per scansionare la tabella nomi, e collegarla nel solito modo al programma compilato. Ruba qualche byte extra mentre il programma è in uso, poi se ne va.
Un programma per lanciare una demo o per qualche installazione potrebbe installare un toolkit del genere per questo compito, ma dopo non potrebbe risistemare il disordine; il toolkit ormai inutile rimarrebbe fino al prossimo riavvio del sistema. Non molto entusiasmante probabilmente, ma sarebbe forse possibile verificare le parole chiave da S*BASIC senza l’uso di toolkit?
Guardando gli esempi proposti nel manuale, PEEK(\\) sembra essere “l’uomo giusto” per questo compito.
Quindi ecco il mio primo tentativo, scritto in SMSQ/E SBASIC:
FindKey 1
10 DEFine FuNction FindKey(k$) 11 LOCal n, c, o, l% 12 REMark Returns true if keyword loaded 13 REMark in this SBASIC 14 : 15 REMark Get size of Name Table 16 c = PEEK_L(\\ $1C) - PEEK_L(\\ $18) 17 FOR n = 0 TO c STEP 8 18 REMark Weed out non-keywords Types 19 IF PEEK_W(\ $18\ n) < $800: NEXT n: EXIT n 20 REMark Get offset in Name List 21 o = PEEK_W(\ $18\ n + 2) 22 REMark Skip no names 23 IF o < 0: NEXT n: EXIT n 24 REMark Get Name length 25 l% = PEEK(\ $20\ o) 26 REMark and Name 27 IF k$ == PEEK$(\ $20\ o + 1, l%): RETurn 1 28 END FOR n 29 RETurn 0 30 END DEFine FindKey
Bene, magnifico. Semplice e rapido. Problema risolto!
No.
Innanzitutto QLib non aveva niente di tutto ciò. Non mi piaceva il mio hex ed era totalmente sbilanciato a danno della versione SMSQ/E di PEEK, preferendo la sua propria versione che non capisce gli offset. L’altro problema, ben più serio, è che sebbene tale programma effettivamente funzioni nei job figlie del SBASIC, esso è inutile: mostra solo un sottogruppo di tutte le parole chiave disponibili.
Questo perché ognuno dei job figlie del SBASIC ha la sua specifica tabella nomi (giustamente), ma in tale tabella solamente le parole chiave presenti nel programma attuale e qualunque comando immediato digitato alla console lo fanno all’interno della tabella nomi locale. La tabella principale è associata unicamente al job #0, guardiano dello stub del SuperBASIC. Tale stub, un costrutto progettato per mantenere un alto grado di compatibilità con il SuperBASIC, trattiene il puntatore nella tabella nomi reale, custodita da qualche parte nel Common Heap. Come lo spazio dati del SuperBASIC, questa tabella nomi può spostarsi, ma non allo stesso modo e nemmeno per gli stessi motivi del SuperBASIC.
Può spostarsi se intasata di nuove parole chiave dato che i toolkit sono caricati nel job#0 al momento del Boot. Può spostarsi quando grosse quantità di nuovi nomi vengono aggiunti all’interpreter nel job#0. Quando necessita di ingrandirsi, una nuova, più ampia area gli viene riservata nel Common Heap, i valori correnti vengono quindi copiati sopra, e infine vengono aggiornati i puntatori nell’area variabili del SBASIC, prima che la vecchia tabella nomi sia scartata (può anche restringersi, a causa di una cancellazione di nomi dall’interpreter#0, che interesserà alcuni puntatori, quantomeno). In altre parole, si sposterà solamente in seguito a una qualche attività in corso nell’interprete del job#0.
Il modello di spostamento memoria impiegato dal SuperBasic evoca incubi atavici e strani tic nervosi in molti intrepidi programmatori amatoriali.
Ma torniamo al tema principale: potrei vedere ora che io in qualche modo ho dovuto scansionare la tabella reale, la tabella nomi del job#0. Il problema è che non c’è un modo facile per afferrare per i cojones quella tabella e immobilizzarla invocando la modalità supervisore dal SBASIC. Che fare?
Nei due scenari concepiti poc’anzi, qualunque scansione della tabella nomi, sia che si trovi in job#0, una figlia SBASIC, o un job compilato, potrebbe essere fatta per avvenire immediatamente all’inizio di yn dato programma.
E’ altamente improbabile che un grosso programma S*BASIC venga avviato esattamente nello stesso momento, e se utilizzato in un programma di Boot, niente all’infuori delle istruzioni del mio programma succederebbe nel job#0. Benché l’improbabile, com’è noto, non dovrebbe mai essere confuso con l’impossibile, almeno per quanto riguarda SMSQ/E questa sembra essere una scommessa molto sicura.
Ho pensato di andare avanti e provarci. Poi avrei fatto dei test intensivi per vedere quanto grande sarebbe stato il problema dello spostamento della memoria per il mio schema. Così ho aggiunto alcuni test triviali. Se si fosse verificato il peggiore dei casi e si fosse spostata, non avrebbe avuto conseguenze peggiori che dare una risposta sbagliata ad ogni morte di papa:
FindKey 2
10 DEFine FuNction FindKey(k$) 11 LOCal n, o, a6, c, s, l% 12 REMark Searches all keywords 13 REMark Return true on match 14 : 15 REMark Get job#0's JCB 16 s = PEEK_L(! $68! 0) 17 REMark Get job#0's a6 18 a6 = PEEK_L(s + $58) 19 : 20 REMark Some GLOBal definitions 21 sb_nmtbb = a6 + $18 22 sb_nmtbp = a6 + $1C 23 sb_nmlsb = a6 + $20 24 : 25 REMark Size of Name Table 26 c = PEEK_L(sb_nmtbp) - PEEK_L(sb_nmtbb) 27 : 28 REMark Go through list 29 FOR n = 0 TO c STEP 8 30 : 31 REMark Weed out non-keywords Types 32 IF PEEK_W(PEEK_L(sb_nmtbb) + a6 + n) < $800: NEXT n: EXIT n 33 : 34 REMark Get offset in Name List 35 o = PEEK_W(PEEK_L(sb_nmtbb) + a6 + n + 2) 36 : 37 REMark Skip no names 38 IF o <= 0: NEXT n: EXIT n 39 : 40 REMark Get Name length 41 l% = PEEK(PEEK_L(sb_nmlsb) + a6 + o) 42 IF l% > 255: NEXT n: EXIT n 43 : 44 REMark Display Name 45 IF k$ == PEEK$(PEEK_L(sb_nmlsb) + a6 + o + 1, l%): RETurn 1 46 END FOR n 47 RETurn 0 48 END DEFine ListKeys 49 : 50 PRINT FindKey("block") 51 PRINT FindKey("blox")
Così com’è, è ottimizzato per presentazione piuttosto che per velocità. L’avvio (RUN) del programma in un qualunque SBASIC dovrebbe stampare 1 e 0 sullo schermo, sia che BLOCK sia caricato o meno nell’interprete locale. In ogni caso non funzionerà in tutto ciò che non sia SBASIC, perciò sono necessarie ulteriori modifiche per renderlo universale.
Molti programmatori assumono che le variabili di sistema saranno sempre ubicate all’indirizzo $28000, come nel Qdos di un QL originale. Ma nemmeno nel QL è detto che siano fissate in quell’indirizzo, vuoi per causa del secondo schermo ed altro ancora. Ma è per questo che le cose vanno storte, e il SBASIC ha prontamente chiuso un occhio su questo fornendo le sua variabili di sistema allo stesso indirizzo.
Comunque, ciò è stato fatto principalmente per permettere agli utenti di far girare vecchi programmi che non potevano essere modificati. I nuovi programmi non dovrebbero dipendere da indirizzi non previsti. La prossima generazione di emulatori o di hardware (magari!) potrebbe, per ragioni tecniche, non essere così accomodante. Magnanimamente, in un piccolo hack rubacchiato a Minerva, la funzione VER$(-2) è disponibile anche in SMSQ/E. Il QDOS, naturalmente, non cambierà mai, perciò questo “interruttore” dovrebbe funzionare in tutti gli attuali sistemi (se siete di diverso avviso per favore fatecelo sapere!):
IF VER$ = 'JSL' OR VER$ = 'HBA' THEN SYSV = VER$(-2): REMark Minerva/SMSQ/E ELSE SYSV = 163840: REMark QDOS END IF
Mentre le variabili di sistema non possono sempre essere ubicate nella locazione “standard” (almeno da Minerva in avanti), una volta che il sistema è in piedi e in funzione, la loro ubicazione rimane fissata per tutta la sessione, cosicché necessiterà di essere verificata una volta soltanto.
Il listato finale mostra il programma completato:
FindKey 3
10 DEFine FuNction FindKey(k$) 11 LOCal i, n, o, a6, c, s 12 LOCal l%, k% 13 REMark GLOBal SYSV 14 : 15 REMark Get job#0's JCB 16 s = PEEK_L(PEEK_L(SYSV + 104)): REMark $68 17 REMark Get job#0's a6 18 a6 = PEEK_L(s + 88): REMark $58 19 : 20 REMark Some GLOBal definitions 21 sb_nmtbb = a6 + 24: REMark $18 22 sb_nmtbp = a6 + 28: REMark $1C 23 sb_nmlsb = a6 + 32: REMark $20 24 : 25 REMark Size of Name Table 26 c = PEEK_L(sb_nmtbp) - PEEK_L(sb_nmtbb) 27 : 28 k% = LEN(k$) 29 REMark Go through list 30 FOR n = 0 TO c STEP 8 31 : 32 REMark Weed out non-keywords: REMark $800 33 IF PEEK_W(PEEK_L(sb_nmtbb) + a6 + n) < 2048: NEXT n: EXIT n 34 : 35 REMark Get offset in Name List 36 o = PEEK_W(PEEK_L(sb_nmtbb) + a6 + n + 2) 37 : 38 REMark Skip no names 39 IF o < 0: NEXT n: EXIT n 40 : 41 REMark Get Name length 42 l% = PEEK(PEEK_L(sb_nmlsb) + a6 + o) 43 : 44 REMark For slow QLs 45 IF l% <> k%: NEXT n: EXIT n 46 REMark Compare strings 47 FOR i = 1 TO l% 48 IF NOT CHR$(PEEK(PEEK_L(sb_nmlsb) + a6 + o + i)) == k$(i): l% = 0: EXIT i 49 END FOR i 50 IF l% > 0: RETurn 1: REMark Bingo! 51 END FOR n 52 RETurn 0 53 END DEFine FindKey 54 : 55 REMark Get pointer to System Variables 56 v$ = VER$ 57 IF v$ = ‘JSL’ OR v$ = ‘HBA’ THEN 58 SYSV = VER$(-2): REMark Minerva/SMSQ/E 59 ELSE 60 REMark QDOS 61 SYSV = 163840: REMark $28000 62 END IF 63 : 64 PRINT FindKey("block") 65 PRINT FindKey("blox")
Come ho accennato, non esiste un modo semplice per bloccare l’area del SuperBASIC mentre questa routine è in funzione, perciò può essere usata in sicurezza solo nei programmi di Boot. Potrebbe essere usata senza problemi in maniera limitata anche in programmi compilati, se usata con parsimonia al momento dell’avvio, assumendo che è improbabile che un grosso programma S*BASIC venga caricato nello stesso istante in cui il programma compilato si inizializza:
10 REMark $$stak=800 20 : 100 REMark My little program 110 qmenu = FindKey("FILE_SELECT$") 120 sound = FindKey("SOUNDFILE") 130 : 140 REMark Rest of My little program goes here.. ... 710 IF sound: Play "tune": ELSE: Beeep ...
Disclaimer: non ho deliberatamente avuto l’intenzione di fuorviare alcuno, ma gli errori possono verificarsi. Non ci sono in circolazione molte informazioni pubblicate su questi, di conseguenza occorrerà mettere insieme i vari elementi che potranno saltar fuori, indagando attraverso il codice sorgente, facendo prove, e confrontandosi con gli altri. Per favore fateci sapere i vostri progressi, se scoprite errori, se avete esperienza di altri sistemi che sembrano non allinearsi alle informazioni qui fornite. I vostri riscontri, sia positivi che negativi, sono i benvenuti. Contattatemi tramite il servizio invio commenti che trovate qui, oppure troviamoci in QL-users o nel Forum QL!