Nikdy jsem nějak moc nepřišel na chuť compilátorům Basicu, ale program .asm z dílny Chrise Taylora se mi celkem zamlouvá. Napsat assembler kód přímo do Basic programu, spustíte normálně z Basicu příkazem RUN a vidíte výsledek.
Nástroj se jmenuje ASM a funguje jako dot command. Stačí zkopírovat soubor asm do složky /dot/ na SD kartě a máte k dispozici assembler dostupný z každého NextBASIC programu.
Princip je elegantně jednoduchý. Do svého NextBASIC programu napíšete .asm — tím spustíte assembler. Ten pak prochází následující řádky programu a hledá ty, které začínají středníkem ;. Obsah za středníkem bere jako assemblerový zdrojový kód a kompiluje ho do paměti. Jakmile narazí na řádek bez středníku, zastaví se a vrátí kontrolu BASICu.
Zkompilovaný strojový kód pak spustíte standardním způsobem přes USR.
Jedna důležitá věc ohledně mezer: pokud kód začíná těsně za středníkem bez mezery, assembler ho bere jako návěští (label). Pro instrukce musíte mít alespoň jednu mezeru za středníkem.
Začneme tím nejjednodušším možným programem — načteme hodnotu do registru BC a vrátíme ji do BASICu přes USR:
10 CLEAR $bfff : REM Rezervujeme paměť pro kód 20 .asm : REM Spustíme assembler 30 ; org $c000 40 ; ld bc, 42 50 ; ret 60 PRINT "BC="; USR ($c000)
Spusťte program a uvidíte BC=42. Assembler zkompiloval instrukce do adresy $C000 (výchozí cíl), BASIC pak zavolal rutinu a vypsal hodnotu z registru BC. Změňte číslo na řádku 40 a spusťte znovu — okamžitá zpětná vazba.
Assembler podporuje plnohodnotná symbolická návěští. Definujete je zápisem jména těsně za středník (bez mezery). Label pak můžete použít jako cíl skoku nebo jako konstantu — a forward reference funguje, label nemusí být definován před použitím.
Tady je klasický "Hello World" — tisk řetězce přes ROM rutinu:
10 CLEAR $bfff 20 .asm 30 ; org $c000 40 ; ld a, 2 50 ; call $1601 ; Otevřeme kanál 2 (obrazovka) 60 ; ld hl, msg 70 ;nxtch ← label (bez mezery za ;) 80 ; ld a, (hl) 90 ; or a 100 ; ret z ; Konec řetězce → return 110 ; rst 16 ; Tiskni znak 120 ; inc hl 130 ; jr nxtch 140 ;msg db "Hello World",0 150 RANDOMIZE USR ($c000)
Tohle je podle mě nejsilnější funkce celého nástroje. Assembler umí číst i zapisovat celočíselné proměnné NextBASIC (%a až %z). BASIC tedy může předat parametry assembleru při kompilaci — a assembler může vrátit informace zpět do BASICu.
Proměnnou nastavenou v BASICu lze použít přímo v assemblerovém výrazu — včetně matematických operací:
5 CLEAR $bfff 10 %z = 13 : REM Nastavíme BASIC proměnnou 20 .asm 30 ; org $c000 40 ; ld bc, %z*2 ; %z je 13, takže BC = 26 50 ; ret 60 PRINT USR ($c000) : REM Vytiskne 26
Pseudo-operátor LET umožňuje assembleru uložit hodnotu (typicky adresu zkompilované rutiny) do BASIC proměnné. Nemusíte tak ručně počítat, kde co v paměti skončilo — assembler vám adresy předá sám:
5 CLEAR $bfff 10 %z = 13 20 .asm 30 ;fn1 ← label první rutiny 40 ; ld bc, %z*2 ; BC = 26 50 ; ret 60 ;fn2 ← label druhé rutiny 70 ; ld bc, 42 80 ; ret 90 ; LET %a = fn1 ; Adresa fn1 → BASIC proměnná %a 100 ; LET %b = fn2 ; Adresa fn2 → BASIC proměnná %b 110 PRINT USR (%a) : REM Zavolá fn1, vytiskne 26 120 PRINT USR (%b) : REM Zavolá fn2, vytiskne 42
ZX Spectrum Next nabízí rozšířenou paměť skrz 16KB banky. Assembler s tím pracuje nativně — pseudo-operátor BANK přesměruje výstup kompilace přímo do cílové banky. Číslo banky může pocházet přímo z BASIC proměnné:
10 BANK NEW %a : REM Alokuj novou banku, číslo → %a 20 .asm 30 ; BANK %a ; Cíl kompilace = banka číslo %a 40 ; ld bc, 42 50 ; ret 60 PRINT BANK %a USR 0 : REM Spusť kód z banky
Tady vidíme spolupráci BASICu a assembleru v plné síle. Assembler zkompiluje rekurzivní flood fill algoritmus a pomocí LET pseudo-opu předá BASICu adresy datových buněk (x, y) uvnitř zkompilovaného kódu. BASIC pak volá rutinu opakovaně — jen POKEuje souřadnice do těchto adres a spouští USR.
40 CLEAR $bfff 50 PROC assemble() 60 CIRCLE 64,110,30 70 CIRCLE 128,80,60 80 CIRCLE 192,110,30 90 PROC fill(128,80) 100 PROC fill(64,110) 110 PROC fill(192,110) 120 STOP 130 DEFPROC fill(x,y) 140 POKE %x,x : POKE %y,192-y : REM Zapíš souřadnice do rutiny 150 RANDOMIZE % USR $c000 160 ENDPROC 170 DEFPROC Assemble() 180 .asm 190 ; ld de, (coords) 200 ; call fill 210 ; ret 220 ;fill 230 ; call point 240 ; ret nz 250 ; call plot 260 ; inc e 270 ; call fill ; Rekurzivně vpravo 280 ; dec e 290 ; dec e 300 ; call fill ; Vlevo 310 ; inc e 320 ; inc d 330 ; call fill ; Dolů 340 ; dec d 350 ; dec d 360 ; call fill ; Nahoru 370 ; inc d 380 ; ret 390 ;plot 400 ; pixelad ; Z80N instrukce 410 ; setae ; Z80N instrukce 420 ; or (hl) 430 ; ld (hl), a 440 ; ret 450 ;point 460 ; pixelad 470 ; setae 480 ; and (hl) 490 ; ret 500 ;coords 510 ;x db 0 520 ;y db 0 530 ; let %x = x ; Adresa buňky X → BASIC %x 540 ; let %y = y ; Adresa buňky Y → BASIC %y 550 ENDPROC
Od verze 0.8b lze assembleru předat přímo název externího .asm souboru — inline zápis do BASICu tedy není jediná možnost. Zdrojový soubor se píše jako normální assembler: bez čísel řádků a bez středníků na začátku řádků.
Soubor lze spustit dvěma způsoby. Přímo z příkazové řádky Nextu:
.asm hello.asm
Nebo z NextBASIC programu:
10 .asm basiclib.asm
Příklad — zdrojový soubor basiclib.asm (bez středníků, bez čísel řádků):
org $c000 ; Cílová adresa
ld bc, 42
ret
A BASIC program, který ho zkompiluje a spustí:
10 CLEAR $bfff 20 .asm basiclib.asm : REM Zkompiluj externí soubor 30 PRINT USR ($c000) : REM Vytiskne 42
Tohle otevírá zajímavý workflow: kód píšete v ZED nebo jiném editoru jako čistý .asm soubor, a z BASICu ho jen zavoláte jedním řádkem. Větší assemblerové knihovny tak nemusí žít zakonzervované uvnitř BASIC programu — jsou to samostatné soubory, které lze sdílet nebo includovat z více projektů.
Assembler nabízí bohatou sadu pseudo-opů nad rámec standardních Z80 instrukcí:
| Pseudo-op | Popis |
|---|---|
| ORG | Nastaví cílovou adresu kódu. Výchozí hodnota je $C000. |
| DB / DW / DS | Definice dat — bajty, slova, rezervace bloku paměti. |
| DZ / DC | Řetězce — DZ s nulovým terminátorem, DC s nastaveným high bitem posledního znaku. |
| EQU | Definuje pojmenovanou konstantu. |
| LET | Zapíše hodnotu (např. adresu rutiny) do NextBASIC celočíselné proměnné. |
| BANK | Přesměruje výstup kompilace do konkrétní 16KB paměťové banky. |
| ALIGN | Zarovná kód nebo data na zadanou hranici bajtů. |
| ASSERT | Vyvolá chybu assemblování, pokud výraz není pravdivý. Užitečné pro sanity checks. |
| DISPLAY | Vypíše zprávu nebo hodnotu výrazu během assemblování — užitečné pro ladění. |
| IF / ELSE / END | Podmíněný překlad — různé varianty kódu podle hodnoty výrazu. |
| INCLUDE | Načte a zkompiluje externí zdrojový soubor. Větší projekty lze rozdělit do více souborů. |
| OUTPUT | Přesměruje zkompilovaný kód do souboru na SD kartě. |
| SAVENEX | Vytvoří spustitelný NEX soubor přímo z BASICu. |
| BRK / NBRK | Vloží breakpoint pro CSpect emulátor nebo hardwarový breakpoint na reálném hardwaru. |
| RELOC_START / END | Podpora přemístitelného kódu — určeno pro tvorbu NextZXOS ovladačů. |
| VAR | Proměnné návěští, jehož hodnota se může měnit mezi průchody assembleru. |
Nevím jak Vy, ale mě se tento program hodně líbí. Asi pravděpodobně nevyužiji assembler přímo v Basicu, neboť jsem Basic lama, ale kompilovat *.asm soubory s ním zkusím, textový editor ZED je celkem použitelný, tak uvidíme.