Jistě si pamatujete na můj článek, jak si napsat vlastní operační systém, a jak v něm případně provozovat textově orientované aplikace (pokud je vůbec spustíte vzhledem k nutnosti emulace DOSu). Dnes se podíváme na něco, co ulehčí psaní aplikací pro Váš operační systém. Je asi jasné, že pokud Váš systém nebude kompletně emulovat DOS (nebo alespoň, co se týče spouštění aplikací, alokace paměti, ukončování, přístupu na disk, atd.), nebude možné psát aplikace (ani speciálně pro něj, i když v tomto případě by šlo o EXE) ve stávajících překladačích Pascalu, C, atd. (o Windows only ani nemluvě). Jediné řešení je tedy psát aplikace v čistém assemleru (TASM, FASM, atd. Co se týče MASM, nebyl bych tím tak moc si jistý). Jenže uznejte. Nalákat lidi na svůj OS, pro který by museli programovat v ASM není zrovna moc jednoduché.
Řešením by byl nějaký ten High Level překladač, který by třeba ani nemusel umět kompilovat instrukce do EXE souboru (resp. do COM binárky, protože EXE nechceme), ale úplně by pro začátek stačilo, aby se v něm dal napsat program ve stylu Pascalu, a on by z něj vygeneroval ASM soubor, který by přeložil třeba FASM do souboru BIN, který byste pak už mohli spustit (resp. by ho třeba ještě nějaký jiný Váš program opatřil menší hlavičkou, která by Vašemu OS sdělila, že to je skutečně spustitelný soubor pro Váš OS, a ne třeba binární ovladač DOSu, nebo vůbec jen nějaká nespustitelná data - Váš OS by pak předal řízení až za tuto hlavičku). Poslední dobou jsem trávil přes rok psaním takového Normalizátoru, Kompilátoru a Dekompilátoru pro svůj Adventure projekt pro AEditor (AEngine totiž bude používat tzv. skripty), takže vím, co to obnáší a že je možné toho dosáhnout (konec konců, co je Free Pascal?). Asi Vám toho neřeknu moc, ale snad na ty základy by to mohlo stačit. Naším cílem bude pro zjednodušení dosáhnout toho, aby Vaše IDE (které může umět i Debug, ale s tím, že buď budete muset používat externí debugger od nějakého assembleru, nebo budete muset umět emulovat všechny příkazy, což není tak jednoduché, zvláště u bloků ASM) dokázalo přeložit tento kód:
var Promena : word; Pole : array[0..5,20..65000] of longint; Zaznam : record Polozka1 : byte; Polozka2 : word; end; Mnozina : set[100..200]; Ukazatel : pointer; Soubor : file; const Cislo = 500; Retezec = "Pokusný řetězec"; begin Ukazatel := AlokujPamet(131072); Soubor := OtevriSoubor('Root/config.dat'); if Soubor <> NIL then begin Zaznam.Polozka1 := Pole[2,20]; asmmov esi,Promenna mov eax,90 int $f0end; ZavriSoubor(Soubor); end; ZrusPamet(Ukazatel); end;
Napsat aplikaci v takovém jazyce bude jistě snazší, než nutit programátory psát pro Váš OS čistě jen v assembleru. Nemůžeme využívat funkce BLOCKREAD či GETMEM, které nám nabízí např. Free Pascal, neboť ty jsou v případě DOSu vázány na přerušení INT 21h, a tedy bychom museli emulovat DOS. Výše uvedený kód je velice prostý, ale je to jen začátek, a hned jak pochopíte, jak z něj vygenerovat assembler (budeme to cílit na platformu Intel PC s použitím Intel syntaxe - té z Turbo Pascalu 7; a protože TASM je už trochu zastaralý, budeme pro překlad ASM kódu, který vygenerujeme, používat FASM). Typy, které zde používáme, si můžete nadefinovat tak, aby odpovídaly Vašemu operačnímu systému, třeba takto (pro naše účely je to celkem jedno):
type file = record Handle : longint; {0, NIL, pokud není otevřen} Jmeno : string[64]; {mít handle na offsetu 0 je výhodné!} Cesta : string; Velikost : longint; Pozice : longint; Datum : TDatum; Cas : TCas; {a další...} end; pointer = record Handle : longint; {vrátí OS poté, co naalokuje blok} Velikost : longint; {velikost alokovaného bloku} end; word = array[0..1] of byte; longint = array[0..3] of byte; {nebo znaménkový typ, jak chcete}
Nyní se tedy můžeme pokusit o překlad (resp. spíše normalizaci do jazyka symbolických adres). Snažil jsem se použít některé často využívané konstrukce jazyka Pascal, ale zase ne takové, ve kterých bychom se zamotali do velké spousty kódu (např. složité IF instrukce, které netestují jen IF A NECO B THEN, ale třeba IF (A+B) AND 5 <> (X-10) THEN, které vyžadují, abyste nejprve vypočítali levou stranu před AND, pak pravou stranu, pak teprve AND, a až poté prováděli test s třetí stranou, kterou si musíte také vypočítat). První věc, kterou totiž budeme muset vyřešit, je přečtení nadefinovaných proměnných tak, abychom s nimi mohli pracovat. Poté budeme muset postupně rozebrat všechny řádky kódu, případně z nich vytvořit skupiny instrukcí, a poté je převést do assembleru. Vaše aplikace také určitě dostane přidělený určitý prostor na staticky definované proměnné. Abychom s nimi pak mohly v assembleru pracovat, potřebujeme znát jejich adresu. Tu si např. necháme sdělit od OS. A zatím nebudeme do překladače dávat takové ty věci, jako jsou optimalizace (např. pokud konstanta nebo proměnná (u té to je malinko složitější) nebyla použita v celém kódu (bude vyžadovat asi více průchodů), nedávat ji do EXE, resp. na haldu; či schopnost vypočítat zadanou konstantu ze vzorečku, např. 128*1024), atp. (např. si pamatovat, v kterém registru ASM je uložena která hodnota, pokud nebyla změněna, a když bude potřeba s ní pracovat, neukládat ji tam znovu, ale použít daný registr, jako to např. dělá FP) nebo přetypování (např. při požadavku WORD := BYTE hodit chybu a nutit uživatele, aby si to zařídil sám přes W:=W a AND $FF, ale zrovna toto jsou základy, které by tam být měly - my to budeme dělat na oko "automaticky"), atd.
Takže, jak to bude probíhat. Pojedeme od prvního řádku k poslednímu, a každý jednotlivě zpracujeme (zatím nebudeme provádět nějaké optimalizace, atd.). Protože budeme podporovat bloky Begin, musíme si nějak pamatovat, kde jednotlivé bloky končí. Na to si vyhradíme pole (či seznam), do kterého si budeme ukládat poznámky. Na počátku překladu toto pole naplníme na nějakou později nevyužívanou hodnotu, např. -1.
const MaxVnoreni = 128; var Vnoreni : array[0..MaxVnoreni-1] of longint; AktVnoreni : word; PoslHodnota : longint; Radek,Radku : word; podm,asmb : boolean; r : string;
Průchod programem se bude dělit prakticky na dvě věci: buď budeme mezi nějakým BEGIN a END, a tedy budeme zpracovávat kód, nebo v nich nebudeme (mohou zde být i funkce), a tedy budeme případně zpracovávat definice proměnných, atd.
begin Radek := 0; PoslHodnota := 1; AktVnoreni := 0; podm := false; asmb := false; repeat ... Inc(Radek); until Radek = Radku; end.
To, že jsme v sekci pro kód poznáme tak, že odkaz v poli bude mít jinou hodnotu než -1 (resp. index větší než 0). 0 znamená, že je tam nějaký blok BEGIN, END, zatímco cokoliv jiného udává, že sekce BEGIN byla pravděpodobně zahájena podmínkou IF (zatím nebudeme řešit, pokud by za IF měl být jen jeden řádek a tedy bez BEGIN END), a tedy po prvním příkazu END bude potřeba vložit nějaký label (návěští), na který by se skákalo v případě, že by podmínka nevyšla. Pro zjednodušení také budeme předpokládat, že jsou řádky napsány přesně dle syntaxe (normalizaci, kdy zařídíte, aby např. mezi proměnnou a operátorem nebyla více než a méně než 1 mezera, aby za BEGIN nebylo už nic a před ním také ne, tu pro zjednodušení nebudeme uvádět, neboť to jsou triviálnosti zařízené přes WHILE, DO, POS, DELETE, INSERT, #32; stejně tak normalizaci na malá písmena (funkci DownString si snad napsat umíte (viz. Laacův článek), stejně jako zarovnávání operandů za symbolickými instrukcemi ASM na 8 znaků - pomocí MOD, či funkci Integer2String)).
r := ZiskejAktRadek; if r = 'begin' then begin if podm then begin Vnoreni[aktvnoreni] := PoslHodnota; inc(PoslHodnota); end else Vnoreni[aktvnoreni] := 0; inc(aktvnoreni); {překročení si snad ohlídáte} Continue; {to píši jen pro zjednodušení, že další kód už} end else {ignorujeme, ale potřebujeme INC na další řádek!} if r = 'end' begin if aktvnoreni > 0 then begin dec(aktvnoreni); if Vnoreni[aktvnoreni] > 0 then ZapisASM('label'+i2s(Vnoreni[aktvnoreni])+':'); Vnoreni[aktvnoreni] := -1; if asmb then asmb := false; end; {jinak chybí BEGIN do páru} Continue; end; if aktvnoreni > 0 then begin if r = 'asm' then begin Vnoreni[aktvnoreni] := 0; {zahájen blok ASM instrukcí} inc(aktvnoreni); asmb := True; Continue; end; if asmb then {instrukce assembleru vložíme přímo} begin {nahradíme jména proměnných za adresy, offsety, konstanty} ZapisASM(r); end else begin {zpracováváme kód Pascalu} end; end else begin {zpracováváme definice} end; podm := false;
Funkce zapisující do ASM není nic víc, než Writeln do textového souboru. Protože dobrý programovací jazyk se neobejde bez proměnných, musíme je také používat. Pro začátek jejich počet omezíme na 256 (proměnné a konstanty zvlášť). Uvedené řešení není samozřejmě jediné možné:
type TKonst = record nazev : string; {možno omezit délku a tím šetřit paměť} vel : byte; {1,2,4 = byte, word, dword; jinak řetězec} Obsah : longint; Retezec : string; Interni : boolean; {TRUE = definováno překladačem} end; TProm = record Nazev : string; Rec : string; {příslušnost k typu RECORD} Typ : byte; {1,2,4 = bwd; 3=pointer, 5=množina, 6=file; +10=array of} Adr : longint; {offset na haldě} a1,a2,a3 : longint; {dolní meze 3D pole; a1=nebo množiny} b1,b2,b3 : longint; {horní meze 3D pole; b1=nebo množiny} Vel : longint; {kolik zabírá na haldě} end; {+případně další pro FILE a POINTER} var Prom : array[0..255] of TProm; Konst : array[0..255] of TKonst; AktRec : string; {na začátku nastavit na ''} PocProm, PocKonst : word; {na začátku programu vynulovat} VelProm : longint; {také nulovat} IntKonst : longint; { := 0} i,p : longint;
A teď nám ještě zbývají dva bloky, které je potřeba dopsat na správná místa (netvrdím, že uvedený kód bude plně fungovat, ale měl by Vás přinejmenším správně nakopnout - chrlil jsem to tak, jak mě to napadalo). První třeba vyřešíme definici proměnných (jistou automatickou definici, ale konstant, budeme provozovat v bloku pro kód). Vlastně máme ještě ale tři. Ten třetí je ale krátký a jedná se o náhradu proměnných/konstant na jejich hodnoty (hlavně je to potřeba u assembleru, ale budeme ho používat i později):
if PocProm > 0 then {názvy proměnných -> adresy} for i := 0 to PocProm-1 do while Pos(Prom[i].Nazev,r) > 0 do begin p := Pos(Prom[i].Nazev,r); Delete(r,p,Length(Prom[i].Nazev)); Insert(r,p,i2s(Prom[i].Adr)); end; if PocKonst > 0 then {názvy konstant na hodnoty} for i := 0 to PocKonst-1 do if not Konst[i].Inter then while Pos(Konst[i].Nazev,r) > 0 do begin p := Pos(Konst[i].Nazev,r); Delete(r,p,Length(Konst[i].Nazev)); case Konst[i].Vel of 1,2,4 : Insert(r,p,i2s(Konst[i].Obsah)); else if Konst[i].Interni then {pro Pascal je nutno vkládat '"'} Insert(r,p,#39+'Inter_'+Konst[i].Retezec+#39) else Insert(r,p,#39+'Data_'Konst[i].Retezec+#39) end; {název bude použit jako offset v ASM pro FASM z DB} end;
Výše uvedené 2 části kódu budou potřeba později, takže se nic nezkazí tím, že se obě části přepíší jako procedury. My teď budeme potřebovat jednu funkci, která nám bude separovat údaje na řádku (šla by samozřejmě urychlit, ale jen pro přehlednost):
function Udaj(var odkud : string; DoCeho : set of char) : string; vat Prac : string; begin while (Odkud <> '') and (Odkud[1] = #32) do Delete(Odkud,1,1); if Odkud = '' then Exit(''); {prázdný výstup = řádek je prázdný} Prac := ''; while not (Odkud[1] in DoCeho) do {vrátíme řetězec do výskytu určitého znaku} begin Prac := Prac+Odkud[1]; Delete(Odkud,1,1); if Odkud = '' then Break; end if Prac <> '' then while Prac[Length(Prac)] = #32 do {smažeme případné mezery na konci} begin Delete(Prac,Length(Prac),1); if Prac = '' then Break; end; Udaj := Prac; end;
Tím máme vše, co potřebujeme, a můžeme se pustit do těch dvou zbývajících bloků. Nejprve slíbené proměnné. Zde tedy platí, že proměnná je platná pouze od své definice dolů, neboť program nejede cyklicky (jen 1 průchod - ale tato vlastnost by se dala zachovat i při více průchodech, a to tak, že by u proměnné ještě byla informace o řádku, od kterého je platná). Také zde nerozlišujeme lokální proměnné definované ve funkcích (ty pro zjednodušení zatím nezavádíme vůbec, ale nebylo by to nic těžkého: klíčové slovo FUNCTION by zahájilo blok funkce, při jehož ukončení by se smazaly všechny proměnné a konstanty definované v této funkci - měly by nějaký příznak). Nadefinujte si také nějaké další proměnné (a kontrolu syntaxe, zda je na řádku opravdu to, co tam čekáme, včetně hlášení chyb, pokud např. očekáváme číslo a je tam řetězec (např. posunutý parametr či klíčové slovo kvůli nedodržené syntaxi), nebo že dané jméno už je zabrané (definované, klíčové slovo, instrukce ASM, operátor, číslo, řídící znaky, apostrofy)):
var Par1,Par2,Par3 : string; BlokVar : byte; {0=ne, 1=VAR, 2=CONST} begin BlokVar := 0; {vhodné nulovat také při každém BEGIN} ... {a každý END by měl raději vynulovat AktRec := ''} Par1 := Udaj(r,[':',#32,'=']); if Par1 = 'var' then begin BlokVar := 1; Par1 := Udaj(r,[':']); end else if par1 = 'const' then begin BlokVar := 2; Par1 := Udaj(r,['=']); end; {teď je ve VAR1 určitě jméno} case BlokVar of 1 : begin {proměnné} Par2 := Udaj(r,[';','[',#32]); if Par2 = 'record' then begin AktRec := Par1; {je to záznam} Continue; {zkrátka odejít z CASE} end else if Par2 = 'byte' then begin {je to typ BYTE} Prom[PocProm].Nazev := Par1; {sice to rozepisuji zbytečně dlouze, ale...} Prom[PocProm].Rec := AktRec; Prom[PocProm].Typ := 1; Prom[PocProm].Adr := VelProm; Prom[PocProm].Vel := 1; end else if Par2 = 'word' then begin {je to typ WORD} Prom[PocProm].Nazev := Par1; Prom[PocProm].Rec := AktRec; Prom[PocProm].Typ := 2; if AktRec <> '' then {zarovnáváme záznam na WORD, lepší by byl DWORD} if VelmProm mod 2 <> 0 then Inc(VelProm); Prom[PocProm].Adr := VelProm; Prom[PocProm].Vel := 2; end else if Par2 = 'dword' then begin {je to typ DWORD} Prom[PocProm].Nazev := Par1; Prom[PocProm].Rec := AktRec; Prom[PocProm].Typ := 4; if AktRec <> '' then if VelmProm and 1 <> 0 then Inc(VelProm); Prom[PocProm].Adr := VelProm; Prom[PocProm].Vel := 4; end else if Par2 = 'pointer' then begin {je to typ POINTER} Prom[PocProm].Nazev := Par1; Prom[PocProm].Rec := AktRec; Prom[PocProm].Typ := 3; Prom[PocProm].Adr := VelProm; Prom[PocProm].Vel := {velikost POINTERu}; end else if Par2 = 'file' then begin {je to typ FILE} Prom[PocProm].Nazev := Par1; Prom[PocProm].Rec := AktRec; Prom[PocProm].Typ := 6; Prom[PocProm].Adr := VelProm; Prom[PocProm].Vel := {velikost FILE}; end else if Par2 = 'set' then begin {je to typ množina} Prom[PocProm].Nazev := Par1; Prom[PocProm].Rec := AktRec; Prom[PocProm].Typ := 5; Prom[PocProm].Adr := VelProm; Par1 := Udaj(r,[']']); {načteme interval} Delete(Par1,1,1); {smažeme hranaté závorky} Delete(Par1,Length(Par1),1); Par2 := Udaj(Par1,['.']); {nahrajeme první mez} Delete(Par1,1,2); {smažeme dvě tečky, teď Par2=druhá mez} Prom[PocProm].a1 := s2i(Par1); Prom[PocProm].b1 := s2i(Par2); Prom[PocProm].Vel := (Prom[PocProm].b1-Prom[PocProm].a1) shr 3; if (Prom[PocProm].b1-Prom[PocProm].a1) and 7 <> 0 then Inc(Prom[PocProm].Vel); {množina bere jen tolik bytů, kolik musí} end else begin {už nám zbylo jen pole} FillChar(Prom[PocProm],SizeOf(TProm),0); Prom[PocProm].Nazev := Par1; Prom[PocProm].Rec := AktRec; Par1 := Udaj(r,[']']); {načteme intervaly} Par2 := Udaj(r,[#32]); {přeskočíme slovo OF} Par2 := Udaj(r,[';']); {nahrajeme šířku položky} if Par2 = 'byte' then Prom[PocProm].Typ := 1 else if Par2 = 'word' then Prom[PocProm].Typ := 2 else Prom[PocProm].Typ := 4; Delete(Par1,1,1); {smažeme hranaté závorky} Delete(Par1,Length(Par1),1); {vždy nahrajeme do PAR3 interval} Par3 := Udaj(Par1,[']','.']); Par2 := Udaj(Par3,['.']); Delete(Par3,1,2); {první interval} Prom[PocProm].a3 := s2i(Par3); Prom[PocProm].b3 := s2i(Par2); if Par1 <> '' then {ještě tam je min. jeden} begin a2 := a3; b2 := b3; Delete(Par1,1,1); {smažeme oddělující čárku} Par3 := Udaj(Par1,[']','.']); Par2 := Udaj(Par3,['.']); Delete(Par3,1,2); {druhý interval} Prom[PocProm].a3 := s2i(Par3); Prom[PocProm].b3 := s2i(Par2); if Par1 <> '' then {ještě tam je jeden poslední} begin a1 := a2; b1 := b2; a2 := a3; b2 := b3; Delete(Par1,1,1); {smažeme oddělující čárku} Par3 := Udaj(Par1,[']','.']); Par2 := Udaj(Par3,['.']); Delete(Par3,1,2); {druhý interval} Prom[PocProm].a3 := s2i(Par3); Prom[PocProm].b3 := s2i(Par2); end; end; Prom[PocProm].Vel := Prom[PocProm].Typ*( (Prom[PocProm].b1-Prom[PocProm].a1+1)+ (Prom[PocProm].b2-Prom[PocProm].a2+1)* (Prom[PocProm].b3-Prom[PocProm].a3+1)+ (Prom[PocProm].b2-Prom[PocProm].a2+1)* (Prom[PocProm].b3-Prom[PocProm].a3+1)+ (Prom[PocProm].b3-Prom[PocProm].a3+1)); Inc(Prom[PocProm].Typ,10); Prom[PocProm].Adr := VelProm; end; {zarovnáme na WORD. Lepší by byl DWORD} if Prom[PocProm].Vel mod 2 <> 0 then Inc(Prom[PocProm].Vel); Inc(PocProm); Inc(VelProm,Prom[PocProm].Vel); end; 2 : begin {konstanty} Konst[PocKonst].Nazev := Par1; Par1 := Udaj(r,[#32]); if Par1[1] = '"' then begin {řetězec} Konst[PocKonst].Retezec := Copy(Par1,2,Length(Par1)-2); Konst[PocKonst].Vel := 3; {$ff} end else begin {číslo} Konst[PocKonst].Obsah := s2i(Par1); with Konst[PocKonst] do begin if (Obsah >= -128) and (Obsah < 255) then Vel := 1 else if (Obsah >= -32768) and (Obsah < 65535) then Vel := 2 else Vel := 4; end; end; Inc(PocKonst); end; end;
Tím máme definované proměnné a konstanty. Nic to nebylo, že ne? :-) A teď ten druhý, zbývající blok. V něm převedeme příkazy na assemblerovské vyjádření. Trochu to zjednoduším, abych tu nepsal druhý Free Pascal :-):
Par1 := Udaj(r,[#32]); if Par = 'if' then {je to podmínka} begin podm := true; Par1 := Udaj(r,[#32]); {operand A} Par2 := Udaj(r,[#32]); {operátor} Par3 := Udaj(r,[#32]); {operand B} {..} if Par2 = '<>' then begin {nahradit PAR1 adresou} {bereme, že šířka adresy (prvku na ní) je 4 byty} ZapisASM('mov edi,Par1'); if Par3 = 'NIL' then Par3 := '0'; if Par3_je_konstanta then begin {nahradit PAR3 konstantou} ZapisASM('cmp [edi],Par3'); {pokud za IF nemusí být BEGIN, musíme si INC zajistit!} ZapisASM('je label'+i2s(PoslHodnota)); end else begin {nahradit PAR3 adresou} ZapisASM('mov esi,Par3'); ZapisASM('mov eax,[esi]'); if sirka_adresy_byte then {ukázka přetypování nezáporných čísel} ZapisASM('and eax,$ff') else if sirka_adresy_word then ZapisASM('and eax,$ffff'); ZapisASM('cmp [edi],eax'); {příští IF už musí mít jinou PoslHODNOTu!} ZapisASM('je label'+i2s(PoslHodnota)); end; end; {...} end else if r <> '' then {je to příkaz přiřazení} begin Par2 := Udaj(r,[#32]); {operátor} Par3 := Udaj(r,[#32]); {operand B} {..} if Par2 = ':=' then {je-li PAR3 nějaká funkce, musíme zkombinovat} begin {tuto sekci s tou následující!!!} {nahradit PAR1 na adresu} if par3_je_adresa then begin {nahradit PAR3 na adresu} ZapisASM('mov esi,Par3'); {u pole jen jeho bázi, ne offset prvku} if sirka_par3 < sirka_par1 then ZapisASM('xor eax,eax'); {zajistíme přetypování} if adresa_par3_je_pole then {vypočteme offset prvku dle počtu rozměrů a jejich velikostí} case sirka_par3 of {přeneseme prvek do mezipaměti} 1 : ZapisASM('mov al,[esi+offset]'); 2 : ZapisASM('mov ax,[esi+offset]'); 4 : ZapisASM('mov eax,[esi+offset]'); end; {zdrojový operand jako adresa} end else begin {nahradit PAR3 na konstantu} {jako konstanta} ZapisASM('mov eax,Par3'); end; ZapisASM('mov edi,Par1'); {cílový operand} case sirka_par1 of 1 : ZapisASM('mov [edi],al'); 2 : ZapisASM('mov [edi],ax'); 4 : ZapisASM('mov [edi],eax'); end; end; {nám stačí jen tato varianta} {...} end else {je to nějaká integrovaná funkce} begin {pro zjednodušení nesmí být v jejím zápisu mezera} if Pos('(',Par1) > 0 then begin {má nějaké parametry} Par2 := Udaj(Par1,['(']); Delete(Par1,1,1); {pro zjednodušení mají naše funkce jen 1 parametr} Delete(Par1,Length(Par1),1); {PAR2 = jméno, PAR1 = parametr} if Par1[1] = #39 then {pro řetězec musíme nadefinovat interní konstantu} begin Konst[PocKonst].Nazev := 'String'+i2s(IntKonst); Konst[PocKonst].Retezec := Copy(Par1,2,Length(Par1)-2); Konst[PocKonst].Vel := 3; Konst[PocKonst].Interni := True; Inc(PocKonst); {hlídat si překročení meze přes PocKonst > High(Konst)} Inc(IntKonst); {dopl.kontrolu, zda tu vůbec řetězec může být} Par1 := Konst[PocKonst].Nazev; end; {převést PAR1 na konstantu/adresu. U řetězce zůstane symb.jméno jako offset} {..} if Par2 = 'ALOKUJPAMET' then {pro zjednodušení to dávám sem, ale je nutné s tím} begin {pracovat i v bloku přiřazení, tj. s adresou SOUBORu} ZapisASM('mov eax,1'); ZapisASM('mov esi,Par1'); ZapisASM('int $ff'); ZapisASM('mov edi,adresa_cile'); {zjistí se jako levý prvek u přiřazení :=} ZapisASM('mov [edi],eax'); ZapisASM('mov [edi+4],ebx'); end else if Par2 = 'OTEVRISOUBOR' then {opět musíme zjistit v přiřazení adresu cílové proměnné} begin ZapisASM('mov eax,2'); ZapisASM('mov ebx,1'); ZapisASM('mov esi,Par1'); ZapisASM('mov edi,adresa_cile'); ZapisASM('int $ff'); end else if Par2 = 'ZAVRISOUBOR' then begin ZapisASM('mov eax,4'); ZapisASM('mov esi,Par1'); ZapisASM('mov ebx,[esi]'); ZapisASM('int $ff'); end else if Par2 = 'ZRUSPAMET' then begin ZapisASM('mov eax,3'); ZapisASM('mov esi,Par1'); ZapisASM('mov ebx,[esi]'); ZapisASM('int $ff'); end else {...} end else begin {bez parametrů} {PAR1 je její jméno, můžeme rovnou provést rozklad} {v našem ukázkovém kódu ale žádnou takovou nemáme} end; end;
Určitě je vhodné některé jednodušší instrukce (interní funkce - příkazy) nadefinovat v nějakém DAT souboru, kde pomocí makrojazyka a zástupných symbolů provedete rozklad jazyka Pascal na assembler, čímž se Vám zjednoduší vlastní programování překladače. No, a to je všechno, vážení. Tedy, téměř všechno.. Na závěr programu ještě vložíme nějaké ty informace, hlavičku a inicializační kód. Nejprve je potřeba vepsat na začátek textového ASM souboru tyto řádky:
ZapisASM('jmp Zacatek'); ZapisASM('db ''MyOS Executable'',0,9,5'); if PocProm > 0 then for i := 0 to PocProm-1 do ZapisASM('; '+Prom[i].Nazev+' = '+i2s(Prom[i].Adr)); ZapisASM('Zacatek:'); ZapisASM('mov eax,0'); {chcete-li optimalizovat, použijte XOR EAX,EAX} ZapisASM('mov ebx,'+i2s(VelProm)); ZapisASM('int $ff'); ZapisASM('or eax,eax'); ZapisASM('jz Label0'); ZapisASM('mov eax,$ff'); ZapisASM('int $ff'); ZapisASM('ret'); ZapisASM('Label0:');
Následně, pokud v našem kódu byly nějaké konstanty (včetně těch, které jsme si definovali automaticky, jakožto parametry různých funkcí), vložíme jejich jména a hodnoty na konec (nesmíme také zapomenout na ukončení programu!):
ZapisASM('mov eax,$ff'); ZapisASM('int $ff'); ZapisASM('ret'); if PocKonst > 0 then for i := 0 to PocKonst-1 do begin if Konst[i].Interni then ZapisASM('Inter_'+Konst[i].Nazev+':') else ZapisASM('Data_'+Konst[i].Nazev+':'); case Konst[i].Vel of 2 : ZapisASM1('dw '); 4 : ZapisASM1('dd '); else ZapisASM1('db '); end; if Konst[i].Vel in [1,2,4] then ZapisASM(i2s(Konst[i].Obsah) else ZapisASM(#39+Konst[i].Retezec+#39+'0'); end;
A tím končíme (pokud se divíte, co znamenají některé řádky, podívejte se na konec článku - tj. vlastně na následující kód). Ve výsledku bychom měli dostat něco takového:
jmp Zacatek db 'MyOS Executable',0,9,5 ; hlavička našeho spustitelného souboru ; Prommena = 0 ; čistě informativní údaje (offsety proměnných) ; Pole = 2 ; Zaznam = 389888 ; zarovnaný WORD, tedy 4 byty celkem ; Mnozina = 389892 ; 101 prvků = 13 bytů ; Ukazatel = 389905 ; 4+4 byty ; Soubor = 389913 ; třeba 349 bytů Zacatek: mov eax,0 ; žádáme o sdělení, kde jsou naše data (do DS a ES) mov ebx,390262 ; tolik jí budeme potřebovat pro statická data int $ff ; přerušení našeho OS or eax,eax ; dostali jsme ji? jz Label0 mov eax,$ff ; konec programu při chybě int $ff ret Label0: mov eax,1 ; chceme alokovat paměť mov ebx,131072 ; 128 kB int $ff mov edi,389905 mov [edi],aex ; uschováme vrácené handle a velikost mov [edi+4],abx mov eax,2 ; otevření souboru mov ebx,1 ; jméno bude bráno jako CS:[ESI] namísto DS:[ESI] mov esi,Inter_String0 ; adresa jména jako ASCIIZ mov edi,389913 ; struktura pro uložení informací o souboru int $ff mov edi,389913 ; toto je zbytečné: optimalizace -> vyhodit řádek cmp [edi],0 ; test na NIL je Label1 mov esi,2 xor eax,eax ; nulujeme registr pro 1 B mov al,[esi+4*2*64981+4*20] ; FASM si to vypočítá při překladu mov edi,389888 mov [edi],ax ; cílová položka má šířku WORD mov esi,0 mov eax,90 ; přímé použití assembleru int $f0 mov eax,4 ; zavření souboru mov esi,389913 mov ebx,[esi] ; handle souboru int $ff Label1: mov eax,3 ; zrušení paměti mov esi,389905 mov ebx,[esi] ; handle paměti int $ff mov eax,$ff ; konec programu int $ff ret ; pokud nás OS zavolal přes CALL Data_Cislo: dw 500 Data_Retezec: db 'Pokusný řetězec',0 Inter_String0: db 'Root/config.dat',0
S tímto si už FASM poradí (netvrdím, že to půjde zkompilovat, ale teoreticky by to fungovat mohlo). Uděláte z toho BIN (něco jako COM) a máte vystaráno. Hlavně se hypotetický programátor nemusí patlat s takovýmto kódem. Offsety proměnných by samozřejmě šly definovat jako konstanty, takže by se v kódu používaly symbolická jména (pro přehlednost), což by si samozřejmě FASM převedl (samozřejmě, budete potřebovat buď naemulovat DOS tak, aby šel spustit přinejmenším FASM (z jeho zdrojáků zjistíte, které INTy a funkce využívá), nebo si napsat vlastní binární kompilátor ASM (konec konců, zdroják assembleru je dobře členěný, první sloupec bývá instrukce (nebo label) a pak následují operandy. Vše má svůj kód, kterým se to zakóduje a je to!); pokud Vám nebude stačit překládat to mimo Váš OS, např. v DOSu nebo pod Windows). A jedna poznámka na závěr: Váš překladač (normalizátor) musíte samozřejmě napsat v assembleru tak, aby šel spustit pod Vaším OS (Pascal jsem použil jen pro větší názornost). Zdroják je pro OS, který poběží v real modu. Doporučuji samozřejmě používat chráněný režim (pak vrátíte alokovaný deskriptor, resp. selektor), ale s ním to není taková legrace (alespoň než si naprogramujete základy). A to je vše...