home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Turbo Toolbox
/
Turbo_Toolbox.iso
/
1990
/
08
/
praxis
/
red.pas
< prev
Wrap
Pascal/Delphi Source File
|
1990-07-05
|
17KB
|
526 lines
(* ------------------------------------------------------ *)
(* RED.PAS *)
(* File Redirection Utility *)
(* Turbo-Pascal-Versionen 4.0 und 5.x *)
(* Copyright (c) 1990 Karsten Gieselmann & TOOLBOX *)
(* ------------------------------------------------------ *)
{$M 2048, 0, 512}
{$B-,I-,R-,S-,V-} (* keine Laufzeitprüfungen! *)
PROGRAM Red;
USES
Dos
{$IFDEF Ver40}
,Dos_50 (* Kompatibilitäts-Unit aus Toolbox 12'89 *)
{$ENDIF};
CONST
Hotkey = $1C0A; (* "Erweiterungs"-Taste: <CtrlRet> *)
MaxEntries = 10; (* Max. Suchtabelleneinträge *)
RedExt : ExtStr = 'RED'; (* Standardsuffix *)
TYPE
StringPtr = ^STRING;
RedTable = ARRAY[1..MaxEntries] OF RECORD
Mask, RedPath : StringPtr;
END;
IntRegisters = RECORD CASE BYTE OF (* CPU-Register *)
1 : (BP,ES,DS,DI,SI,
DX,CX,BX,AX,IP,CS,Flags : WORD);
2 : (Dummy : ARRAY[1..5] OF WORD;
DL,DH,CL,CH,BL,BH,AL,AH : BYTE);
END;
VAR
Stack : POINTER; (* programmeigener Stapel *)
SaveInt16 : POINTER; (* alter Tastatur-Interrupt *)
SaveInt2F : POINTER; (* alter Multiplex-Interrupt *)
Enabled : BOOLEAN; (* Umleitung aktiv? *)
Installed : BOOLEAN; (* Programm schon resident? *)
StackSwapped : BOOLEAN; (* eigener Stapel aktiv? *)
Buffer : STRING; (* Universalpuffer *)
ProgName : NameStr; (* Kennung für Multiplex *)
RedName : PathStr; (* Name der aktuellen Tabelle *)
DefExt : ExtStr; (* aktuelles Standardsuffix *)
Redirection : RedTable; (* Suchtabelle *)
LastEntry : WORD; (* Index letzter Tabelleneintrag *)
(* --------------------- Utilities ---------------------- *)
PROCEDURE SwapStackAndCall( FarProc : POINTER;
Stack : POINTER;
VAR Regs : IntRegisters);
(* schaltet auf neuen Stapel um und ruft Routine auf *)
INLINE($C6/$06/StackSwapped/$01/$9C/$59/$8C/$D0/$8E/$C0/
$58/$5A/$5B/$5F/$FA/$8E/$D7/$87/$E3/$51/$9D/$9C/$06/
$53/$52/$50/$26/$FF/$1F/$FA/$58/$5A/$59/$8E/$D2/$89/
$C4/$51/$9D/$83/$C4/$04/$C6/$06/StackSwapped/$00);
PROCEDURE ChainInt(VAR Regs : IntRegisters;
OldInt : POINTER);
(* beendet aktuelle Serviceroutine und setzt die Pro-
grammausführung bei der alten Interruptroutine fort *)
INLINE($5B/$58/$5E/$1F/$87/$5C/$0E/$87/$44/$10/$8B/$54/
$16/$52/$9D/$8C/$DA/$FA/$8E/$D2/$89/$F4/$FB/$5D/
$07/$1F/$5F/$5E/$5A/$59/$CB);
FUNCTION GetKey(Call : BYTE) : WORD;
(* ruft alten Tastaturinterrupt, um eine Taste zu holen *)
INLINE($58/$86/$E0/$9C/$FF/$1E/SaveInt16);
PROCEDURE InterruptsOn;
(* erlaubt das Auslösen weiterer Interruptaufrufe *)
INLINE($FB);
FUNCTION PSP : WORD;
(* ermittelt das aktuelle Program Segment Prefix *)
INLINE($B4/$62/$CD/$21/$89/$D8);
FUNCTION InTextMode(VAR Col, Row : BYTE;
VAR VideoRAM : Pointer) : BOOLEAN;
(* prüft, ob momentan ein Textmodus aktiv ist und ermit-
telt die aktuelle Cursorposition sowie die von der
Seite abhängige Bildschirmspeicher-Adresse. Der Ein-
fachheit halber werden nur 80x25-Modi unterstützt;
eine Unterscheidung zwischen Text/Grafik bei Mono-
chrom-Systemen (Hercules) ist hier nicht möglich. *)
CONST
Segment : ARRAY[FALSE..TRUE] OF WORD = ($B800, $B000);
VAR
Regs : Registers;
Seg : WORD;
BEGIN
WITH Regs DO BEGIN
AH := $0F; (* "Read Status" *)
Intr($10, Regs);
InTextMode := (AL <= 3) OR (AL = 7);
VideoRAM := Ptr(Segment[AL=7]+$200*BH, 0);
AH := $03;
Intr($10, Regs); (* "Get Cursor" *)
Col := Succ(DL); (* Umrechnung von 0..79 auf 1..80 *)
Row := Succ(DH); (* Umrechnung von 0..24 auf 1..25 *)
END;
END;
FUNCTION HasWildCards(VAR St : STRING) : BOOLEAN;
(* prüft, ob ein String Wildcardzeichen (*,?) enthält *)
BEGIN
HasWildCards := (Pos('?', St) > 0) OR (Pos('*', St) > 0);
END;
PROCEDURE DefaultExtension(VAR FileName : PathStr;
VAR DefExt : ExtStr);
(* verbindet einen Dateinamen mit einem Standardsuffix,
falls die Namensangabe keine Erweiterung enthält. *)
VAR
Dir : DirStr;
Name : NameStr;
Ext : ExtStr;
BEGIN
FSplit(FileName, Dir, Name, Ext);
IF Ext = '' THEN
FileName := Dir + Name + '.' + DefExt;
END;
PROCEDURE UpperCase(VAR St : STRING);
(* konvertiert eine Zeichenkette in Großbuchstaben *)
VAR
i : BYTE;
BEGIN
FOR i:=1 TO Length(St) DO
St[i] := UpCase(St[i]);
END;
FUNCTION SubStringToHeap(a, b : BYTE) : POINTER;
(* legt den von führenden und folgenden Blanks befrei-
ten Teilstring Buffer[a..b] auf dem Heap ab *)
VAR
S : StringPtr;
BEGIN
WHILE (a <= b) AND (Buffer[a] <= ' ') DO Inc(a);
WHILE (b >= a) AND (Buffer[b] <= ' ') DO Dec(b);
IF a <= b THEN BEGIN
GetMem(S, b-a+2);
S^[0] := Chr(b-a+1);
Move(Buffer[a], S^[1], b-a+1);
END ELSE BEGIN
GetMem(S, 1);
S^[0] := #0;
END;
SubStringToHeap := S;
END;
(* ------------ Pattern-Matching-Algorithmus ------------ *)
FUNCTION Match(VAR Source, Pattern : STRING) : BOOLEAN;
(* prüft, ob die durch "Pattern" gegebene Zeichenkette,
die beliebig mit Wildcards "?" (ein Zeichen) und "*"
(beliebig viele Zeichen) durchsetzt sein kann, mit
"Source" (darf keine Wildcards enthalten!) überein-
stimmt. Groß- und Kleinschreibung werden dabei nicht
unterschieden.
Beispiel: "T*B?x*SP??Z*" stimmt mit
"Toolbox ist Spitze!" überein. *)
TYPE
Result = (Failed, Passed, Scanning);
VAR
PatternLen : BYTE ABSOLUTE Pattern;
SourceLen : BYTE ABSOLUTE Source;
FUNCTION MatchSubString(s : BYTE; p : BYTE) : Result;
(* rekursiver Test auf Übereinstimmung der Teilstrings
ab Pattern[p], Source[s]. Trotz Rekursion wird der
Laufzeitstapel nur minimal belastet, da lokale und
formale Parameter zusammen gerade 3 Bytes belegen! *)
VAR
State : Result;
BEGIN
IF PatternLen = 0 THEN (* triviale Übereinstimmung: *)
State := Passed (* leeres Muster! *)
ELSE BEGIN
State := Scanning;
REPEAT
IF (s > SourceLen) (* Muster und Zeichen... *)
AND (p > PatternLen) THEN (* ...abgearbeitet *)
State := Passed
ELSE IF p > PatternLen THEN
State := Failed (* Muster vorzeitig erschöpft! *)
ELSE IF Pattern[p] = '*' THEN
IF p = PatternLen THEN (* Joker entspricht.... *)
State := Passed (* ...restlichen Zeichen *)
ELSE
REPEAT (* rekursiver Restvergleich *)
State := MatchSubString(s, p+1);
Inc(s);
UNTIL (State = Passed) OR (s > SourceLen)
ELSE IF (Upcase(Pattern[p]) <> Upcase(Source[s]))
AND (Pattern[p] <> '?') THEN
State := Failed (* keine Übereinstimmung *)
ELSE BEGIN
Inc(s); (* Übereinstimmung, nächstes Zeichen *)
Inc(p);
END;
UNTIL State <> Scanning;
END;
MatchSubString := State;
END;
BEGIN
Match := (MatchSubString(1, 1) = Passed);
END;
(* ------------------- Konfiguration -------------------- *)
PROCEDURE SignOn;
(* Versions- und Copyrightmeldung *)
BEGIN
WriteLn;
WriteLn('File Redirection Utility, Version 1.00');
WriteLn('Copyright (c) 1990 K.Gieselmann & toolbox');
WriteLn;
END;
PROCEDURE GetHelp;
(* Anzeige eines Hilfsbildschirms *)
BEGIN
Write(
'Syntax: RED ?|+|- oder RED Datei [.Suffix]'^M^J,
'Parameter:'^M^J,
' ? zeigt diesen Text an'^M^J,
' + aktiviert geladene Suchtabelle'^M^J,
' - setzt Suchtabelle außer Kraft'^M^J,
' Datei lädt neue Suchtabelle aus Datei'^M^J,
' Suffix Standardsuffix für Dateinamen'^M^J);
IF NOT Installed THEN Halt;
END;
PROCEDURE ShowStatus;
(* Anzeige der momentanen Programm-Konfiguration *)
CONST
Status : ARRAY[FALSE..TRUE] OF STRING[6]
= ('passiv', 'aktiv');
BEGIN
IF RedName <> '' THEN BEGIN
Write('Tabelle ', RedName,
' geladen und ', Status[Enabled]);
END ELSE BEGIN
Write('Keine Tabelle geladen');
END;
IF DefExt = '' THEN
WriteLn(', kein Standardsuffix definiert.')
ELSE BEGIN
WriteLn(', Standardsuffix ist .', DefExt);
END;
END;
PROCEDURE LoadTable(VAR Path : PathStr);
(* lädt eine neue Suchtabelle aus "Path" *)
VAR
p : WORD;
BEGIN
Release(HeapOrg);
LastEntry := 0;
DefaultExtension(Path, RedExt);
Assign(Input, Path); Reset(Input);
IF IoResult = 0 THEN BEGIN
WHILE NOT EoF(Input)
AND (LastEntry < MaxEntries) DO BEGIN
ReadLn(Input, Buffer);
p := Pos('=', Buffer);
IF p > 0 THEN BEGIN
Inc(LastEntry);
IF MaxAvail > Length(Buffer) THEN BEGIN
WITH Redirection[LastEntry] DO BEGIN
Mask := SubStringToHeap(1, p-1);
RedPath := SubStringToHeap(p+1, Length(Buffer));
END;
END;
END;
END;
Close(Input);
Buffer := '';
Enabled := TRUE;
END ELSE BEGIN
RedName := '';
WriteLn('Datei nicht gefunden!');
END;
END;
{$F+}
PROCEDURE Configure(VAR Regs : IntRegisters);
(* wertet die per Kommandozeile gemachten Angaben aus *)
VAR
Argument : PathStr;
BEGIN
SignOn;
Argument := ParamStr(1);
IF Argument <> '' THEN BEGIN
IF Argument = '?' THEN GetHelp
ELSE IF Argument = '+' THEN Enabled := TRUE
ELSE IF Argument = '-' THEN Enabled := FALSE
ELSE BEGIN
RedName := Argument;
UpperCase(RedName);
LoadTable(RedName);
Argument := ParamStr(2);
IF Argument[1] = '.' THEN
DefExt := Copy(Argument, 2, 3)
ELSE BEGIN
DefExt := Copy(Argument, 1, 3);
END;
UpperCase(DefExt);
END;
END;
IF NOT Installed THEN
WriteLn('Programm resident installiert.')
ELSE IF (Argument <> '?') THEN BEGIN
ShowStatus;
END;
END;
{$F-}
(* -------------- Ausgedehnte Dateisuche ---------------- *)
FUNCTION Grab(VAR St : STRING) : BOOLEAN;
(* liest eine Zeichenkette vom Bildschirm rückwärts, be-
ginnend bei der momentanen Cursorposition; der Lesevor-
gang wird abgebrochen, wenn der linke Bildschirmrand
erreicht ist oder das letzte gelesene Zeichen definitiv
das Ende eines Dateinamens kennzeichnet ("Delimiters") *)
TYPE
TextScreen = ARRAY[1..25, 1..80] OF RECORD
Character, Attribute : CHAR
END;
CONST
Delimiters : SET OF CHAR =
[#0..#32, '│', '>', '<', '|', '+', '/'];
VAR
Screen : ^TextScreen;
Ch : CHAR;
Row, Col : BYTE;
Delimiter : BOOLEAN;
BEGIN
St := '';
IF InTextMode(Col, Row, Pointer(Screen)) THEN
IF Col > 1 THEN BEGIN
REPEAT
Dec(Col);
Ch := Screen^[Row, Col].Character;
Delimiter := (Ch IN Delimiters);
If NOT Delimiter THEN St := Ch + St;
UNTIL Delimiter
OR ((Length(St) > 1) AND (St[2]=':'))
OR (Col = 1);
END;
Grab := (St <> '');
END;
FUNCTION Expand(VAR FileName : PathStr) : BOOLEAN;
(* durchsucht Festplatte/Diskette entsprechend den in
der Suchtabelle spezifizierten Angaben nach der
durch "FileName" bezeichneten Datei. Im Erfolgsfall
wird der vollständige Pfadname zurückgegeben. *)
VAR
Count : WORD;
a, b, p : BYTE;
found : BOOLEAN;
Path : PathStr;
BEGIN
Expand := FALSE;
IF (FileName <> '')
AND NOT HasWildCards(FileName) THEN BEGIN
found := FALSE;
Count := 1;
DefaultExtension(FileName, DefExt);
WHILE (Count <= LastEntry) AND NOT found DO BEGIN
WITH Redirection[Count] DO BEGIN
IF Match(FileName, Mask^) THEN BEGIN
Buffer := RedPath^;
WHILE (Buffer <> '') AND NOT found DO BEGIN
p := Pos(';', Buffer);
IF p = 0 THEN p := Succ(Length(Buffer));
a := 1; b := p-1;
WHILE (a <= b) AND (Buffer[a] <= ' ') DO Inc(a);
WHILE (b >= a) AND (Buffer[b] <= ' ') DO Dec(b);
IF b >= a THEN BEGIN
Path := Copy(Buffer, a, Succ(b-a));
IF Buffer[b] <> '\' THEN Path := Path + '\';
END ELSE BEGIN
Path := '';
END;
Delete(Buffer, 1, p);
Assign(Input, Path + FileName); Reset(Input);
found := (IoResult = 0);
IF found THEN BEGIN
FileName := Path + FileName;
Expand := TRUE;
END;
END;
END;
END;
Inc(Count);
END;
END;
END;
{$F+}
PROCEDURE FetchKey(VAR Regs : IntRegisters);
(* holt den nächsten Tastencode aus dem Puffer bzw. von
der Tastatur, falls der Puffer keine Daten enthält. *)
VAR
CheckKbd : BOOLEAN;
Call, DelNum : BYTE;
FileName : PathStr;
BEGIN
WITH Regs DO BEGIN
Call := AH;
CheckKbd := (Call and $EF = 1);
REPEAT
IF Buffer <> '' THEN BEGIN
AX := Ord(Buffer[1]);
IF CheckKbd THEN
Flags := Flags AND NOT FZero
ELSE
Delete(Buffer, 1, 1);
END ELSE BEGIN
AX := GetKey(Call);
IF AX = Hotkey THEN BEGIN
IF Grab(FileName) THEN BEGIN
DelNum := Length(FileName);
IF Expand(FileName) THEN BEGIN
FillChar(Buffer[1], DelNum, ^H);
Buffer[0] := Chr(DelNum);
Buffer := Buffer + FileName + ^M
END ELSE BEGIN
Buffer := ^M;
END;
END;
END;
END;
UNTIL AX <> Hotkey;
END;
END;
{$F-}
(* --------------- Interrupt-Management ----------------- *)
PROCEDURE Int16(BP : Word); INTERRUPT;
(* neue Service-Routine für den Tastatur-Interrupt *)
VAR
Regs : IntRegisters ABSOLUTE BP;
BEGIN
InterruptsOn;
IF ((Regs.AH AND $EF = 0)
OR (Regs.AH AND $EF = 1) AND (Buffer <> ''))
AND Enabled AND NOT StackSwapped THEN
SwapStackAndCall(@FetchKey, Stack, Regs)
ELSE BEGIN
ChainInt(Regs, SaveInt16);
END;
END;
PROCEDURE Int2F(BP : Word); INTERRUPT;
(* neue Service-Routine für den Multiplex-Interrupt *)
VAR
Regs : IntRegisters ABSOLUTE BP;
BEGIN
InterruptsOn;
WITH Regs DO BEGIN
IF NOT StackSwapped (* nicht reentrant! *)
AND (AH = $AE) AND (AL <= $01) AND (DX = $FFFF)
AND (String(Ptr(DS, SI)^) = ProgName) THEN
IF AL = $00 THEN
AL := $FF (* Stufe 1: Kommando erkannt *)
ELSE BEGIN (* Stufe 2: Kommando ausführen *)
(* Standardausgabe von COMMAND.COM übernehmen: *)
Mem[PrefixSeg:$19] := Mem[PSP:$19];
(* Kommando-Parameter von COMMAND.COM kopieren: *)
Move(Mem[DS:$80],
Mem[PrefixSeg:$80], Mem[DS:$80]+1);
(* Stapel umschalten und Programm abarbeiten: *)
SwapStackAndCall(@Configure, Stack, Regs);
Mem[DS:SI] := $00; (* Flag: Kommando ausgeführt *)
END ELSE BEGIN
ChainInt(Regs, SaveInt2F); (* weiter in der Kette *)
END;
END;
END;
(* ------------------- Installation --------------------- *)
PROCEDURE Install;
(* Initialisierung der Variablen, Erstkonfiguration und
Installation der beiden Interrupt-Serviceroutinen. *)
VAR
Dummy : IntRegisters;
BEGIN
Installed := FALSE;
Enabled := TRUE;
StackSwapped := FALSE;
FSplit(ParamStr(0), Buffer, ProgName, Buffer);
Stack := Ptr(SSeg, SPtr-$64);
FileMode := 0;
LastEntry := 0;
RedName := '';
DefExt := '';
Configure(Dummy);
Buffer := '';
GetIntVec($16, SaveInt16);
GetIntVec($2F, SaveInt2F);
SetIntVec($00, SaveInt00);
{$IFNDEF Ver40}
SetIntVec($3F, SaveInt3F);
{$ENDIF}
SetIntVec($16, @Int16);
SetIntVec($2F, @Int2F);
Installed := TRUE;
Keep(0);
END;
BEGIN
Install;
END.
(* ------------------------------------------------------ *)
(* Ende von RED.PAS *)