USR-funktionen - vad är den bra för?

© 1999 Peter Karlsson. Svensk översättning © 2000. Publicerad i Go64/Commodore World nummer 12/1999 på engelska och tyska.

En av de mest förbisedda funktionerna i Commodores BASIC-uppsättning är USR(), den användardefinierade funktionen. Kanske är det för att den normalt sett inte gör någonting, kanske är det för att de flesta aldrig hört talas om den.

Av Peter Karlsson


Den är dock väldigt användbar för att lägga funktioner till BASICen utan att behöva ändra hela BASICen.

Ett exempel

I Arndt Dettkes andra BASIC-projekt, som började i nummer 8/1999, nämnde han avsaknaden av en modulooperator i BASIC, en funktion som beräknar resten vid en heltalsdivision. I sitt exempel klarade han sig utan den, men vad skulle hända om den verkligen var nödvändig? Självklart skulle den kunna implementeras i BASIC genom att använda DEF FN för att skapa en användardefinierad funktion, eller direkt i programkoden med INT() och några andra spännande kommandon, men det blir lätt långsamt.

För att komma till rätta med hastighetsproblemet kan vi alltid försöka skriva rutinen i maskinkod och anropa den med SYS. Problemet med denna lösning är frågan hur argument skall skickas till funktionen, och hur vi gör för att få ut resultatet. Det är här USR() kommer in - den tar nämligen automatiskt en parameter inom sina parenteser och gör den tillgänglig för rutinen, och tillåter dig dessutom att returnera ett värde till BASIC.

Sända parametrar

Ett problem kvarstår dock, och det är att modulooperatorn tar två indatavärden, täljaren och nämnaren. Nå, som jag skrev tar den automatiskt en parameter, den som skrivs mellan parenteserna i anropet, men det är inget som hindrar oss från att manuellt ta egna parametrar utanför parentesen, vi måste bara veta var i BASIC-ROMen vi skall leta för att hitta rätt rutiner.

Det automatiska argumentet skickas till USR()-rutinen i FAC1, den första av BASICens två flyttalsackumulatorer, placerad i minnet på adress 97-102 (hex 61-66), där även resultatet skall läggas.

Anropsvektorn för USR()-funktionen finns på adress 785-786 (hex 311-312), lagrad i det vanliga låg-/hög-formatet. Vid start initieras vektorn till att peka på 45640 (hex B248), vilket inte gör något annat än skriva ut felmeddelandet "ILLEGAL QUANTITY" på skärmen. För att testa det hela kan vi peka den mot en RTS-instruktion, vilket kommer få till följd att parametern som sänds in till funktionen returneras. Testa detta:

POKE 49152,96 lägg en RTS-instruktion på 49152 (hex C000)
POKE 785,0:POKE 786,192 sätt USR()-vektorn
PRINT USR(42) bör skriva ut 42 på skärmen

En väldigt trevlig sak med USR()-funktionen är att den alltid är tillgänglig för omdefinition, även om andra BASIC-utökningar används. Så genom att använda USR() när du bara behöver en funktion ger bästa möjliga kompatibilitet.

Påskdagen

Datumet för (den kristna) påskdagen bestäms av tidpunkten då fullmånen visar sig under den tidiga våren. Tack vare månens omloppsbana runt jordens regelbundenhet kan det datumet beräknas med en matematisk formel (och som av en slump bygger den mycket på modulooperatorn):

Summan av D och E ger datumet, där noll är den 22 mars, med två undantag: Om du får resultatet 26 april, eller om du får 25 april med D=28, E=6 och A större än tio, skall du dra av en vecka. Värdena på M och N beror på århundradet, enligt tabellen nedan:

ÅrhundradeMN
1583-1599222
1600-1699222
1700-1799233
1800-1899234
1900-1999245
2000-2099245
2100-2199246
2200-2299250
2300-2399261
2400-2499251
2500-2599262

Implementering

Modulooperatorn MOD(X,Y) kan implementeras genom att beräkna X-INT(X/Y)*Y, vilket är precis vad den assemblerkod vi implementerar gör. Vi använder ett antal flyttalsrutiner i BASIC ROM för att implementera detta, eftersom vi både tar emot parametrarna och lämnar tillbaka resultatet som flyttal.

För att hämta den andra parametern (nämnaren) anropar vi först MOV2F-rutinen för att flytta argumentet ut till minnet, till BASICs temporära lagringsområde på adress 87-91 (hex 57-5B). Vi anropar sedan FRMNUM-rutinen, vilken laddar nästa numeriska parameter i BASIC-källkoden och lagrar den i FAC1. Om parametern utelämnas genererar rutinen felmeddelandet SYNTAX ERROR, och om värdet inte kan tolkas som ett tal, ett uttryck eller en numerisk variabel genereras ett TYPE MISMATCH-felmeddelande, något som passar bra för vad vi försöker åstadkomma.

Det här sättet att hämta indataparametrarna resulterar i att syntaxen för MOD(X,Y)-funktionen i stället blir USR(X)Y. Ett problem med detta är den prioritet de olika operatorerna får - för att skriva MOD(X,Y)*2 måste du använda ett extra par parenteser: (USR(X)Y)*2, eftersom USR(X)Y*2 skulle tolkas som MOD(X,Y*2). Trots att detta inte ser lika bra ut som en "riktig" implementation av MOD-funktionen skulle göra kan man inte riktigt vänta sig att kunna implementera en MOD-funktion med korrekt syntax på bara 33 byte, så vi får leva med det.

Assemblerkoden medföljer månadens omslagsdisk, och så gör även en implementation av påskdagsalgoritmen. Notera att du måste starta programmet med RUN en gång, eller skriva POKE 43,1:POKE 44,16, för att se BASIC-listningen eftersom den laddar en egen teckenuppsättning på BASICs normala startadress (2048, hex 800). USR()-rutinen aktiveras genom att SYSa startadressen, i detta fall SYS 49152.

Rekommenderad läsning

Mapping the Commodore 64 från Compute! är en bra källa för att hitta information om vilka ROM-anrop som finns att tillgå, och så är även Marko Mäkeläs Commodore 64 ROM disassembly. Båda kan hämtas från Project 64-webbplatsen, http://project64.c64.org/


Denna artikel på English (engelska), Deutsch (tyska).

Åter till artikelindexet