Procedury
Mad-Pascal pozwala na przekazanie do procedury maksymalnie 8 parametrów. Dostępne są trzy sposoby przekazywania parametrów: przez wartość, stałą CONST
i referencję VAR
.
Parametry procedur odczytywane są i wartościowane od prawej do lewej strony tests\tests-medium\function_valuation_of_arguments.pas
Możliwe jest zagnieżdżanie procedur.
Możliwa jest rekurencja procedur, pod warunkiem że parametry procedury będą przekazywane przez wartość, będą typu prostego - porządkowego. Typ rekordowy, wskaźnikowy nie będzie właściwie alokowany w pamięci.
Jeśli jest to możliwe kompilator przekazuje parametry do procedury poprzez zmienne z pominięciem stosu programowego.
Funkcje
Mad-Pascal pozwala na przekazanie do funkcji maksymalnie 8 parametrów. Dostępne są trzy sposoby przekazywania parametrów: przez wartość, stałą CONST
i referencję VAR
.
Wynik funkcji zwracamy przypisując go do nazwy funkcji lub korzystając z automatycznie deklarowanej zmiennej RESULT
, np.:
function add(a,b: word): cardinal;
begin
Result := a+b;
end;
function mul(a,b: word): cardinal;
begin
mul := a*b;
end;
- Używanie modyfikatora
INTERRUPT
nie jest zalecane.
Parametry funkcji odczytywane są i wartościowane od prawej do lewej strony tests\tests-medium\function_valuation_of_arguments.pas
Możliwe jest zagnieżdżanie funkcji.
Możliwa jest rekurencja funkcji, pod warunkiem, że parametry funkcji będą przekazywane przez wartość, będą typu prostego - porządkowego. Typ rekordowy, wskaźnikowy nie będzie właściwie alokowany w pamięci.
Jeśli jest to możliwe kompilator przekazuje parametry do funkcji poprzez zmienne z pominięciem stosu programowego.
Przekazywanie parametrów
Można przekazywać do 8 parametrów do funkcji lub procedury. Oddziela się je średnikami. Każdy z parametrów ma własny typ.
Zmienne podawane zazwyczaj w parametrach są kopiowane w inny obszar pamięci i właśnie do tego obszaru ma dostęp programista.
Kompilator Mad Pascala
wykonuje kopię parametrów, nie należy przekazywać zbyt dużej ilości danych, by nie spowolnić działania programu. Zaleca się przekazywać do czterech bajtów.
Przekazywanie przez wartość
Przekazywanie parametrów przez wartość jest sposobem najprostszym z możliwych. Deklaracja procedury nie musi zawierać żadnych dodatkowych słów kluczowych - wystarczy poniższa konstrukcja nagłówka procedury bądź funkcji:
procedure Foo(S : String);
Przekazanie parametru przez wartość wiąże się utworzeniem jego kopii lokalnej do wykorzystania jedynie przez procedurę lub funkcję. Oryginalna wartość zmiennej przekazanej do procedury nie zostaje w żaden sposób naruszona.
Przekazywanie przez stałą
Umieszczenie w deklaracji parametrów słowa kluczowego CONST
spowoduje przekazywanie parametrów jako stałych.
procedure Show(const Message : String);
begin
Writeln(Message);
end;
Procedura nie może w żaden sposób wpływać na zawartość parametru. Próba nadania przez procedurę jakiejś wartości spowoduje komunikat o błędzie:
Can't assign values to const variable
, nie można więc zapisać tego w ten sposób:
procedure Show(const Message : String);
begin
Message := 'Nowa wartość';
Writeln(Message);
end;
Obecnie kompilator MP
odkłada takie parametry w pamięci podobnie jak w przypadku przekazywania przez wartość, docelowo powinien tylko przez wskaźnik.
Przekazywanie przez referencję
Przekazywanie parametrów przez referencję polega na umieszczeniu przed parametrami słowa kluczowego VAR
. Dzięki temu kod znajdujący się wewnątrz procedury może zmienić wartość parametru.
Sposób przekazywania danych przez referencję jest optymalny, gdyż nie jest tworzona kopia zmiennej. W związku z tym jest możliwe przekazywanie danych z procedury na zewnątrz.
uses crt;
procedure SetValue(var a,b,c,d: byte);
begin
{ próba nadania nowej wartości dla parametru }
a := a + 2;
b := a + 3;
c := b + 4;
d := c + 5;
end;
var a,b,c,d: byte;
begin
a:=3;
SetValue(a,b,c,d);
writeln(a); // 5
writeln(b); // 8
writeln(c); // 12
writeln(d); // 17
repeat until keypressed;
end.
Parametry bez typu
MP
pozwala deklarować parametry procedur i funkcji, które nie posiadają typu. W takim wypadku, nazwy parametrów muszą być poprzedzone słowem kluczowym VAR
.
Dostęp do wartości takich parametrów musi odbywać się poprzez rzutowanie.
var w: cardinal;
procedure doit(var d);
begin
Writeln('As integer: ',PInteger(@D)^);
Writeln('As Byte : ',PByte(@D)^);
end;
begin
w:=31411;
doit(w);
end.
>> 31411
>> 179
Przekazywanie parametru przez wskaźnik, zależnie od typu takiego wskaźnika:
var pn: pointer;
tb: array [0..0] of byte;
procedure doit(var a); overload;
begin
writeln('VAR',',',cardinal(@a));
end;
procedure doit(var a: pointer); overload;
begin
writeln('VAR POINTER',',',cardinal(a));
end;
begin
pn:=@tb;
doit(tb); // VAR
doit(pn); // VAR POINTER
end.
Odczyt pliku pod adres wskazany przez wskaźnik:
procedure BlockRead(var f: file; var Buf; count: word; var Result: word);
var pn: pointer;
f: file;
begin
InitGraph(15+16);
pn:=pointer(dpeek(88));
assign(f, 'FILENAME'); reset(f, 1);
blockread(f, pn^, 192*40);
close(f);
end.
Odczyt pliku pod adres wskazany przez tablicę:
var bf: array [0..0] of byte;
f: file;
begin
InitGraph(15+16);
bf:=pointer(dpeek(88));
assign(f, 'FILENAME'); reset(f, 1);
blockread(f, bf, 192*40);
close(f);
end.
Tablice otwarte
Obecnie kompilator MP
nie wspiera tablic otwartych
ani nie pozwala na przekazywanie tablic otwartych
jako parametru.
function Foo(const A : array of const) : String;
ShowMessage(Foo(['test', 10, ' ', 'mama', 1000]));
function Foo(const A : array of const) : String;
var
i : Integer;
begin
for I := 0 to High(A) do
begin
case A[i].VType of
vtInteger: Result := Result + IntToStr(A[i].VInteger);
vtChar: Result := Result + A[i].VChar;
vtString: Result := Result + A[i].VString^;
vtAnsiString: Result := Result + String(A[i].VAnsiString);
vtVariant: Result := Result + String(A[i].VVariant^);
{ itd. }
end;
end;
end;
Procedury zagnieżdżone
Nic nie stoi na przeszkodzie, aby daną procedurę lub funkcję umieścić w innej procedurze lub funkcji.
procedure A;
procedure B;
begin
end;
begin
end;
Z powyższego zapisu wynika, że procedura lub funkcja zagnieżdżona (w tym wypadku procedura B
) musi zostać umieszczona przed blokiem BEGIN
.
W takim przypadku nadal obowiązują zasady o zmiennych lokalnych. Oznacza to, że zmienna umieszczona w procedurze B
nie będzie dostępna dla procedury A
.
Przy zastosowaniu procedur zagnieżdżonych obowiązują również inne zasady. Procedura zewnętrzna do A
nie ma dostępu do procedury B
, procedura B
ma dostęp do parametrów wywołania procedury A
.
program nested;
function E(x: byte): byte;
function F(y: byte): byte;
begin
F := x + y
end;
begin
Result := F(3) + F(2)
end;
begin
writeln(E(1));
while true do;
end.
W powyższym przykładzie, zmienne X
, Y
są dostępne w funkcji F
, natomiast dla funkcji E
dostępna jest tylko zmienna X
.
Procedury i funkcje zagnieżdżone mogą okazać się przydatne, gdy do realizacji jednej funkcji przydatna lub wymagana jest inna procedura lub funkcja, która, z punktu widzenia innych procedur lub funkcji danego modułu, jest zbędna. Jednocześnie warto przypomnieć, że zagnieżdżenie pozwala na wywoływanie bez przekazywania parametrów z funkcji nadrzędnej.
Modyfikatory
assembler
Procedury/Funkcje oznaczona przez ASSEMBLER
mogą składać się tylko z bloku ASM. Kompilator nie dokonuje analizy składni takich bloków, traktuje je jak komentarz, ewentualne błędy zostaną wychwycone dopiero podczas asemblacji.
UWAGA:
Wymagane jest aby zachować stan rejestruX
CPU6502
, który używany jest do obsługi stosu programowego Mad-Pascal.
Kompilator dopuszcza dwie składnie bloku ASM
, z klamrami { } jak dla komentarza i standardową bez klamer.
ASM
lda #10
sta 712
END;
ASM
{ lda #10
sta 712
};
procedure name; assembler;
asm
lda #10
sta 712
end;
procedure name; assembler;
asm
{
lda #10
sta 712
};
end;
overload
Procedury/Funkcje przeciążone rozpoznawane są na podstawie listy parametrów.
procedure suma(var i: integer; a,b: integer); overload;
begin
i := a+b;
end;
procedure suma(var i: integer; a,b,c: integer); overload;
begin
i := a+b+c;
end;
function fsuma(a,b: word): cardinal; assembler; overload;
asm
{
adw a b result
};
end;
function fsuma(a,b: real): real; overload;
begin
Result := a+b;
end;
Przy przekazywaniu parametrów do procedury/funkcji należy pamiętać że Mad Pascal rozszerza typ obliczanych wyrażeń, dlatego jeśli zależy nam na określonym typie powinniśmy dokonać rzutowania.
procedure tst(a,b: shortint); overload;
begin
writeln('shortint');
end;
procedure tst(a,b: smallint); overload;
begin
writeln('smallint');
end;
procedure tst(a,b: integer); overload;
begin
writeln('integer');
end;
dla a,b = shortint
tst(a,b) -> wybierze shortint
tst(a+1,b) -> wybierze integer
tst(smallint(a+1),b) -> wybierze smallint
forward
Jeżeli chcemy aby procedura/funkcja była zadeklarowana za miejscem jej pierwszego wywołania, należy użyć modyfikator FORWARD
.
procedure nazwa [(lista-parametrów-formalnych)]; forward;
...
...
...
procedure nazwa;
begin
end;
register
Użycie modyfikatora REGISTER
spowoduje, że trzy pierwsze parametry formalne procedury/funkcji będą umieszczone na stronie zerowej, w 32-bitowych rejestrach programowych, odpowiednio EDX
, ECX
, EAX
.
procedure nazwa (a,b,c: cardinal); register;
// a = edx
// b = ecx
// c = eax
W przypadku funkcji, zmienna RESULT przechowująca wartość funkcji alokowana jest na stronie zerowej pod adresem :STACKORIGIN-4
który odpowiada bezpośrednio zmiennej :TMP
function nazwa (a,b,c: cardinal): cardinal; register;
// a = edx
// b = ecx
// c = eax
// Result = :STACKORIGIN-4 (:TMP)
Jeśli w ciele procedury/funkcji występują operacje mnożenia/dzielenia, albo operacje na tablicach dwu-wymiarowych wówczas rejestry EDX
, ECX
, EAX
mogą ulec zniszczeniu co spowoduje "niespodziewane" rezultaty.
interrupt
Procedury/Funkcje oznaczone przez INTERRUPT
kompilator będzie kończył rozkazem RTI
(standardowo RTS
).
Niezależnie czy w programie wystąpi wywołanie takiej procedury/funkcji kompilator zawsze wygeneruje dla niej kod.
Na wejściu procedury/funkcji oznaczonej przez INTERRUPT
programista musi zadbać o zachowanie rejestrów CPU A
X
Y
, na wyjściu o przywrócenie stanu takich rejestrów, kompilator ogranicza się tylko do wstawienia końcowego rozkazu RTI
.
Kompilator zgłosi błąd jeśli w takiej procedurze/funkcji wystąpią rozkazy assemblera odwołujące się do zmiennych :BP
, :BP2
lub :STACKORIGIN
.
procedure dli; interrupt; assembler;
asm
pha
lda #$c8
sta wsync
sta $d01a
pla
end; // rozkaz RTI zostanie wstawiony automatycznie
keep
Użycie modyfikator KEEP
spowoduje że tak oznaczona procedura/funkcja zostanie zawsze skompilowana niezależnie czy wystąpiło, czy nie wystąpiło odwołanie w programie do takiej procedury/funkcji.
pascal
Użycie modyfikatora PASCAL
spowoduje, że procedura/funkcja będzie traktowana jako rekurencyjna. Standardowo kompilator automatycznie wykrywa rekurencję, ale mogą zdarzyć się sytuacje dla których będzie to niemożliwe.
Przykład samples/math/evaluate.pas
stdcall
Użycie modyfikatora STDCALL
spowoduje wymuszenie przekazywania parametrów do procedury/funkcji poprzez stos programowy. Domyślnie kompilator stara się przekazywać parametry przez zmienne, bez udziału stosu programowego.
inline
Procedura, funkcja zostaje zamieniona na makro Mad Assemblera, pozbywamy się wywołań z udziałem rozkazu JSR
.
Nie ma możliwości używania rekurencji dla takich procedur/funkcji.
external
Modyfikator EXTERNAL
informuje kompilator że procedura/funkcja zostanie dolinkowana
{$LINK filename}
na etapie assemblacji.
Przykład kilku procedur, które zostaną dolinkowane do programu w Pascalu (procedura z 1 argumentem typu BYTE jest wyjątkiem, taki argument przekazywany jest przez rejestr akumulatora)
.public proc1, proc2, proc3
.reloc
.proc proc1 (.byte a) .reg
rts
.endp
.proc proc2 (.word tmp) .var
rts
tmp dta a(0)
.endp
.proc proc3 (.byte x,y,z) .var
rts
x brk
y brk
z brk
.endp
Przykład wykorzystania w Pascalu
procedure proc1 (a: byte); external;
procedure proc2 (a: word); external;
procedure proc3 (a,b,c: byte); external;
{$link filename.obx}