{ ********************************************************************* $Id$ Copyright (C) 1997, 1998 Gertjan Schouten This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ********************************************************************* System Utilities For Free Pascal } { NewStr creates a new PString and assigns S to it if length(s) = 0 NewStr returns Nil } function NewStr(const S: string): PString; begin result := Nil; if Length(S) <> 0 then begin New(Result); result^ := S; end ; end ; { DisposeStr frees the memory occupied by S } procedure DisposeStr(S: PString); begin if S <> Nil then begin Dispose(S); S := Nil; end ; end ; { AssignStr assigns S to P^ } procedure AssignStr(var P: PString; const S: string); begin P^ := s; end ; { AppendStr appends S to Dest } procedure AppendStr(var Dest: PString; const S: string); begin Dest^ := Dest^ + S; end ; { UpperCase returns a copy of S where all lowercase characters ( from a to z ) have been converted to uppercase } function UpperCase(const S: string): string; var i: integer; begin result := S; i := Length(S); while i <> 0 do begin if (result[i] in ['a'..'z']) then result[i] := char(byte(result[i]) - 32); Dec(i); end; end; { LowerCase returns a copy of S where all uppercase characters ( from A to Z ) have been converted to lowercase } function LowerCase(const S: string): string; var i: integer; begin result := S; i := Length(result); while i <> 0 do begin if (result[i] in ['A'..'Z']) then result[i] := char(byte(result[i]) + 32); dec(i); end; end; { CompareStr compares S1 and S2, the result is the based on substraction of the ascii values of the characters in S1 and S2 case result S1 < S2 < 0 S1 > S2 > 0 S1 = S2 = 0 } function CompareStr(const S1, S2: string): Integer; var count, count1, count2: integer; begin result := 0; Count1 := Length(S1); Count2 := Length(S2); if Count1 > Count2 then Count := Count2 else Count := Count1; result := CompareMem(Pointer(S1),Pointer(S2), Count); if (result = 0) and (Count1 <> Count2) then begin if Count1 > Count2 then result := ord(s1[Count1 + 1]) else result := -ord(s2[Count2 + 1]); end ; end ; { CompareMem returns the result of comparison of Length bytes at P1 and P2 case result P1 < P2 < 0 P1 > P2 > 0 P1 = P2 = 0 } function CompareMem(P1, P2: Pointer; Length: cardinal): integer; var i: integer; begin i := 0; result := 0; while (result = 0) and (i < length) do begin result := byte(P1^) - byte(P2^); P1 := P1 + 1; P2 := P2 + 1; i := i + 1; end ; end ; { CompareText compares S1 and S2, the result is the based on substraction of the ascii values of characters in S1 and S2 comparison is case-insensitive case result S1 < S2 < 0 S1 > S2 > 0 S1 = S2 = 0 } function CompareText(const S1, S2: string): integer; var i, count, count1, count2: integer; Chr1, Chr2: byte; begin result := 0; Count1 := Length(S1); Count2 := Length(S2); if Count1 > Count2 then Count := Count2 else Count := Count1; i := 0; while (result = 0) and (i < count) do begin i := i + 1; Chr1 := byte(s1[i]); Chr2 := byte(s2[i]); if Chr1 in [97..122] then Chr1 := Chr1 - 32; if Chr2 in [97..122] then Chr2 := Chr2 - 32; result := Chr1 - Chr2; end ; if (result = 0) and (Count1 <> Count2) then begin if Count1 > Count2 then result := byte(UpCase(s1[Count1 + 1])) else result := -byte(UpCase(s2[Count2 + 1])); end ; end ; {==============================================================================} { Ansi string functions } { these functions rely on the character set loaded by the OS } {==============================================================================} function AnsiUpperCase(const s: string): string; var len, i: integer; begin len := length(s); SetLength(result, len); for i := 1 to len do result[i] := UpperCaseTable[ord(s[i])]; end ; function AnsiLowerCase(const s: string): string; var len, i: integer; begin len := length(s); SetLength(result, len); for i := 1 to len do result[i] := LowerCaseTable[ord(s[i])]; end ; function AnsiCompareStr(const S1, S2: string): integer; begin result:=0; end ; function AnsiCompareText(const S1, S2: string): integer; begin result:=0; end ; function AnsiStrComp(S1, S2: PChar): integer; begin result:=0; end ; function AnsiStrIComp(S1, S2: PChar): integer; begin result:=0; end ; function AnsiStrLComp(S1, S2: PChar; MaxLen: cardinal): integer; begin result:=0; end ; function AnsiStrLIComp(S1, S2: PChar; MaxLen: cardinal): integer; begin result:=0; end ; function AnsiStrLower(Str: PChar): PChar; begin if Str <> Nil then begin while Str^ <> #0 do begin Str^ := LowerCaseTable[byte(Str^)]; Str := Str + 1; end ; end ; result := Str; end ; function AnsiStrUpper(Str: PChar): PChar; begin if Str <> Nil then begin while Str^ <> #0 do begin Str^ := UpperCaseTable[byte(Str^)]; Str := Str + 1; end ; end ; result := Str; end ; function AnsiLastChar(const S: string): PChar; begin result:=nil; end ; function AnsiStrLastChar(Str: PChar): PChar; begin result:=nil; end ; {==============================================================================} { End of Ansi functions } {==============================================================================} { Trim returns a copy of S with blanks characters on the left and right stripped off } function Trim(const S: string): string; var Ofs, Len: integer; begin len := Length(S); while (Len>0) and (S[Len] = ' ') do dec(Len); Ofs := 1; while (Ofs<=Len) and (S[Ofs] = ' ') do Inc(Ofs); result := Copy(S, Ofs, 1 + Len - Ofs); end ; { TrimLeft returns a copy of S with all blank characters on the left stripped off } function TrimLeft(const S: string): string; var i,l:integer; begin l := length(s); i := 1; while (i<=l) and (s[i] = ' ') do inc(i); Result := copy(s, i, l); end ; { TrimRight returns a copy of S with all blank characters on the right stripped off } function TrimRight(const S: string): string; var l:integer; begin l := length(s); while (l>0) and (s[l] = ' ') do dec(l); result := copy(s,1,l); end ; { QuotedStr returns S quoted left and right and every single quote in S replaced by two quotes } function QuotedStr(const S: string): string; begin result := AnsiQuotedStr(s, ''''); end ; { AnsiQuotedStr returns S quoted left and right by Quote, and every single occurance of Quote replaced by two } function AnsiQuotedStr(const S: string; Quote: char): string; var i, j, count: integer; begin result := '' + Quote; count := length(s); i := 0; j := 0; while i < count do begin i := i + 1; if S[i] = Quote then begin result := result + copy(S, 1 + j, i - j) + Quote; j := i; end ; end ; if i <> j then result := result + copy(S, 1 + j, i - j); result := result + Quote; end ; { AnsiExtractQuotedStr returns a copy of Src with quote characters deleted to the left and right and double occurances of Quote replaced by a single Quote } function AnsiExtractQuotedStr(var Src: PChar; Quote: Char): string; var i: integer; P, Q: PChar; begin P := Src; if Src^ = Quote then P := P + 1; Q := StrEnd(P); if PChar(Q - 1)^ = Quote then Q := Q - 1; SetLength(result, Q - P); i := 0; while P <> Q do begin i := i + 1; result[i] := P^; if (P^ = Quote) and (PChar(P + 1)^ = Quote) then P := P + 1; P := P + 1; end ; SetLength(result, i); end ; { AdjustLineBreaks returns S with all CR characters not followed by LF replaced with CR/LF } // under Linux all CR characters or CR/LF combinations should be replaced with LF function AdjustLineBreaks(const S: string): string; var i, j, count: integer; begin result := ''; i := 0; j := 0; count := Length(S); while i < count do begin i := i + 1; if (S[i] = #13) and ((i = count) or (S[i + 1] <> #10)) then begin result := result + Copy(S, 1 + j, i - j) + #10; j := i; end ; end ; if j <> i then result := result + copy(S, 1 + j, i - j); end ; { IsValidIdent returns true if the first character of Ident is in: 'A' to 'Z', 'a' to 'z' or '_' and the following characters are on of: 'A' to 'Z', 'a' to 'z', '0'..'9' or '_' } function IsValidIdent(const Ident: string): boolean; var i, len: integer; begin result := false; len := length(Ident); if len <> 0 then begin result := Ident[1] in ['A'..'Z', 'a'..'z', '_']; i := 1; while (result) and (i < len) do begin i := i + 1; result := result and (Ident[i] in ['A'..'Z', 'a'..'z', '0'..'9', '_']); end ; end ; end ; { IntToStr returns a string representing the value of Value } function IntToStr(Value: integer): string; begin System.Str(Value, result); end ; { IntToHex returns a string representing the hexadecimal value of Value } const HexDigits: array[0..15] of char = '0123456789ABCDEF'; function IntToHex(Value: integer; Digits: integer): string; var i: integer; begin SetLength(result, digits); for i := 0 to digits - 1 do begin result[digits - i] := HexDigits[value and 15]; value := value shr 4; end ; end ; { StrToInt converts the string S to an integer value, if S does not represent a valid integer value EConvertError is raised } function StrToInt(const S: string): integer; var Error: word; begin Val(S, result, Error); if Error <> 0 then raise EConvertError.createfmt(SInValidInteger,[S]); end ; { StrToIntDef converts the string S to an integer value, Default is returned in case S does not represent a valid integer value } function StrToIntDef(const S: string; Default: integer): integer; var Error: word; begin Val(S, result, Error); if Error <> 0 then result := Default; end ; { LoadStr returns the string resource Ident. } function LoadStr(Ident: integer): string; begin result:=''; end ; { FmtLoadStr returns the string resource Ident and formats it accordingly } function FmtLoadStr(Ident: integer; const Args: array of const): string; begin result:=''; end; Const feInvalidFormat = 1; feMissingArgument = 2; feInvalidArgIndex = 3; Procedure Log (Const S: String); begin {$ifdef debug} Writeln (S); {$endif} end; Procedure DoFormatError (ErrCode : Longint); Var S : String; begin //!! must be changed to contain format string... S:=''; Case ErrCode of feInvalidFormat : EConvertError.Createfmt(SInvalidFormat,[s]); feMissingArgument : EConvertError.Createfmt(SArgumentMissing,[s]); feInvalidArgIndex : EConvertError.Createfmt(SInvalidArgIndex,[s]); end; end; Function Format (Const Fmt : String; const Args : Array of const) : String; Var ChPos,OldPos,ArgPos,DoArg,Len : Longint; Hs,ToAdd : String; Index,Width,Prec : Longint; Left : Boolean; ExtVal: Extended; Fchar : char; { ReadFormat reads the format string. It returns the type character in uppercase, and sets index, Width, Prec to their correct values, or -1 if not set. It sets Left to true if left alignment was requested. In case of an error, DoFormatError is called. } Function ReadFormat : Char; Var Value : longint; Procedure ReadInteger; Var Code : Word; begin If Value<>-1 then exit; // Was already read. OldPos:=chPos; While (Chpos0) do inc(chpos); If Chpos=len then DoFormatError(feInvalidFormat); If Fmt[Chpos]='*' then begin If (Chpos>OldPos) or (ArgPos>High(Args)) or (Args[ArgPos].Vtype<>vtInteger) then DoFormatError(feInvalidFormat); Value:=Args[ArgPos].VInteger; Inc(ArgPos); Inc(chPos); end else begin If (OldPos0 then DoFormatError (feInvalidFormat); end else Value:=-1; end; end; Procedure ReadIndex; begin ReadInteger; If Fmt[ChPos]=':' then begin If Value=-1 then DoFormatError(feMissingArgument); Index:=Value; Value:=-1; Inc(Chpos); end; Log ('Read index'); end; Procedure ReadLeft; begin If Fmt[chpos]='-' then begin left:=True; Inc(chpos); end else Left:=False; Log ('Read Left'); end; Procedure ReadWidth; begin ReadInteger; If Value<>-1 then begin Width:=Value; Value:=-1; end; Log ('Read width'); end; Procedure ReadPrec; begin If Fmt[chpos]='.' then begin inc(chpos); ReadInteger; If Value=-1 then DoFormaterror(feMissingArgument); prec:=Value; end; Log ('Read precision'); end; begin Log ('Start format'); Index:=-1; Width:=-1; Prec:=-1; Value:=-1; inc(chpos); If Fmt[Chpos]='%' then exit('%'); ReadIndex; ReadLeft; ReadWidth; ReadPrec; ReadFormat:=Upcase(Fmt[ChPos]); Log ('End format'); end; Procedure DumpFormat (C : char); begin Write ('Fmt : ',fmt:10); Write (' Index : ',Index:3); Write (' Left : ',left:5); Write (' Width : ',Width:3); Write (' Prec : ',prec:3); Writeln (' Type : ',C); end; function Checkarg (AT : Longint;err:boolean):boolean; { Check if argument INDEX is of correct type (AT) If Index=-1, ArgPos is used, and argpos is augmented with 1 DoArg is set to the argument that must be used. } begin result:=false; if Index=-1 then begin DoArg:=Argpos; inc(ArgPos); end else DoArg:=Index; If (Doarg>High(Args)) or (Args[Doarg].Vtype<>AT) then begin if err then DoFormatError(feInvalidArgindex); dec(ArgPos); exit; end; result:=true; end; Const Zero = '000000000000000000000000000000000000000000000000000000000000000'; begin Result:=''; Len:=Length(Fmt)+1; Chpos:=1; OldPos:=1; ArgPos:=0; While chpos'%') do inc(chpos); If ChPos>OldPos Then Result:=Result+Copy(Fmt,OldPos,Chpos-Oldpos); If ChPos64 then Index:=64; ToAdd:=Copy(Zero,1,Index)+ToAdd; end; end; 'E' : begin CheckArg(vtExtended,true); If Prec=-1 then prec:=15; ExtVal:=Args[doarg].VExtended^; Prec:=Prec+5; // correct dot, eXXX If ExtVal<0 then Inc(Prec); // Corect for minus sign If Abs(Extval)<1 then Inc(Prec); // correct for - in E Str(Args[doarg].VExtended^:prec,ToAdd); end; 'F' : begin end; 'S' : begin if CheckArg(vtString,false) then hs:=Args[doarg].VString^ else if CheckArg(vtPChar,false) then hs:=Args[doarg].VPChar else if CheckArg(vtAnsiString,true) then hs:=ansistring(Args[doarg].VAnsiString); Index:=Length(hs); If (Prec<>-1) and (Index>Prec) then Index:=Prec; ToAdd:=Copy(hs,1,Index); end; 'P' : Begin CheckArg(vtpointer,true); ToAdd:=HexStr(Longint(Args[DoArg].VPointer),8); // Insert ':'. Is this needed in 32 bit ? No it isn't. // Insert(':',ToAdd,5); end; 'X' : begin Checkarg(vtinteger,true); If Prec>32 then ToAdd:=HexStr(Args[Doarg].VInteger,Prec) else begin // determine minimum needed number of hex digits. Index:=1; While (1 shl (Index*4))Prec then Prec:=Index; ToAdd:=HexStr(Args[DoArg].VInteger,Prec); end; end; '%': ToAdd:='%'; end; If Width<>-1 then If Length(ToAdd)Buflen then Result:=Length(S) else Result:=Buflen; Move(S[1],Buffer,Result); end; Procedure FmtStr(Var Res: String; Const Fmt : String; Const args: Array of const); begin Res:=Format(fmt,Args); end; Function StrFmt(Buffer,Fmt : PChar; Const args: Array of const) : Pchar; begin Buffer[FormatBuf(Buffer^,Maxint,Fmt^,strlen(fmt),args)]:=#0; Result:=Buffer; end; Function StrLFmt(Buffer : PCHar; Maxlen : Cardinal;Fmt : PChar; Const args: Array of const) : Pchar; begin Buffer[FormatBuf(Buffer^,MaxLen,Fmt^,strlen(fmt),args)]:=#0; Result:=Buffer; end; Function FloatToStr(Value: Extended): String; Begin Result := FloatToStrF(Value, ffGeneral, 15, 0); End; Function FloatToStrF(Value: Extended; format: TFloatFormat; Precision, Digits: Integer): String; Var P: Integer; Negative, TooSmall, TooLarge: Boolean; Begin Case format Of ffGeneral: Begin If (Precision = -1) Or (Precision > 15) Then Precision := 15; TooSmall := Abs(Value) < 0.00001; If Not TooSmall Then Begin Str(Value:0:999, Result); P := Pos('.', Result); Result[P] := DecimalSeparator; TooLarge := P > Precision + 1; End; If TooSmall Or TooLarge Then Result := FloatToStrF(Value, ffExponent, Precision, Digits); P := Length(Result); While Result[P] = '0' Do Dec(P); If Result[P] = DecimalSeparator Then Dec(P); SetLength(Result, P); End; ffExponent: Begin If (Precision = -1) Or (Precision > 15) Then Precision := 15; Str(Value:Precision + 8, Result); Result[3] := DecimalSeparator; If (Digits < 4) And (Result[Precision + 5] = '0') Then Begin system.Delete(Result, Precision + 5, 1); If (Digits < 3) And (Result[Precision + 5] = '0') Then Begin system.Delete(Result, Precision + 5, 1); If (Digits < 2) And (Result[Precision + 5] = '0') Then Begin system.Delete(Result, Precision + 5, 1); If (Digits < 1) And (Result[Precision + 5] = '0') Then system.Delete(Result, Precision + 3, 3); End; End; End; If Result[1] = ' ' Then System.Delete(Result, 1, 1); End; ffFixed: Begin If Digits = -1 Then Digits := 2 Else If Digits > 15 Then Digits := 15; Str(Value:0:Digits, Result); If Result[1] = ' ' Then System.Delete(Result, 1, 1); P := Pos('.', Result); If P <> 0 Then Result[P] := DecimalSeparator; End; ffNumber: Begin If Digits = -1 Then Digits := 2 Else If Digits > 15 Then Digits := 15; Str(Value:0:Digits, Result); If Result[1] = ' ' Then System.Delete(Result, 1, 1); P := Pos('.', Result); If P <> 0 Then Result[P] := DecimalSeparator; Dec(P, 3); While (P > 1) Do Begin If Result[P - 1] <> '-' Then Insert(ThousandSeparator, Result, P); Dec(P, 3); End; End; ffCurrency: Begin If Value < 0 Then Begin Negative := True; Value := -Value; End Else Negative := False; If Digits = -1 Then Digits := CurrencyDecimals Else If Digits > 15 Then Digits := 15; Str(Value:0:Digits, Result); If Result[1] = ' ' Then System.Delete(Result, 1, 1); P := Pos('.', Result); If P <> 0 Then Result[P] := DecimalSeparator; Dec(P, 3); While (P > 1) Do Begin Insert(ThousandSeparator, Result, P); Dec(P, 3); End; If Not Negative Then Begin Case CurrencyFormat Of 0: Result := CurrencyString + Result; 1: Result := Result + CurrencyString; 2: Result := CurrencyString + ' ' + Result; 3: Result := Result + ' ' + CurrencyString; End End Else Begin Case NegCurrFormat Of 0: Result := '(' + CurrencyString + Result + ')'; 1: Result := '-' + CurrencyString + Result; 2: Result := CurrencyString + '-' + Result; 3: Result := CurrencyString + Result + '-'; 4: Result := '(' + Result + CurrencyString + ')'; 5: Result := '-' + Result + CurrencyString; 6: Result := Result + '-' + CurrencyString; 7: Result := Result + CurrencyString + '-'; 8: Result := '-' + Result + ' ' + CurrencyString; 9: Result := '-' + CurrencyString + ' ' + Result; 10: Result := CurrencyString + ' ' + Result + '-'; End; End; End; End; End; {==============================================================================} { extra functions } {==============================================================================} { LeftStr returns Count left-most characters from S } function LeftStr(const S: string; Count: integer): string; begin result := Copy(S, 1, Count); end ; { RightStr returns Count right-most characters from S } function RightStr(const S: string; Count: integer): string; begin result := Copy(S, 1 + Length(S) - Count, Count); end; { BCDToInt converts the BCD value Value to an integer } function BCDToInt(Value: integer): integer; var i, j: integer; begin result := 0; j := 1; for i := 0 to SizeOf(Value) shr 1 - 1 do begin result := result + j * (Value and 15); j := j * 10; Value := Value shr 4; end ; end ; { Case Translation Tables Can be used in internationalization support. Although these tables can be obtained through system calls it is better to not use those, since most implementation are not 100% WARNING: before modifying a translation table make sure that the current codepage of the OS corresponds to the one you make changes to } const { upper case translation table for character set 850 } CP850UCT: array[128..255] of char = ('', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'Y', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''); { lower case translation table for character set 850 } CP850LCT: array[128..255] of char = ('', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''); { upper case translation table for character set ISO 8859/1 Latin 1 } CPISO88591UCT: array[192..255] of char = ( #192, #193, #194, #195, #196, #197, #198, #199, #200, #201, #202, #203, #204, #205, #206, #207, #208, #209, #210, #211, #212, #213, #214, #215, #216, #217, #218, #219, #220, #221, #222, #223, #192, #193, #194, #195, #196, #197, #198, #199, #200, #201, #202, #203, #204, #205, #206, #207, #208, #209, #210, #211, #212, #213, #214, #247, #216, #217, #218, #219, #220, #221, #222, #89 ); { lower case translation table for character set ISO 8859/1 Latin 1 } CPISO88591LCT: array[192..255] of char = ( #224, #225, #226, #227, #228, #229, #230, #231, #232, #233, #234, #235, #236, #237, #238, #239, #240, #241, #242, #243, #244, #245, #246, #215, #248, #249, #250, #251, #252, #253, #254, #223, #224, #225, #226, #227, #228, #229, #230, #231, #232, #233, #234, #235, #236, #237, #238, #239, #240, #241, #242, #243, #244, #245, #246, #247, #248, #249, #250, #251, #252, #253, #254, #255 ); { $Log$ Revision 1.17 1999-04-08 11:31:03 peter * removed warnings Revision 1.16 1999/04/08 10:19:41 peter * pchar support for %s Revision 1.15 1999/04/04 10:19:07 peter * format support for ansistring (from mailinglist) * fixed length checking in Trim() Revision 1.14 1999/03/01 12:40:06 michael changed delete to system.delete Revision 1.13 1999/02/28 13:17:35 michael + Added internationalization support and more format functions Revision 1.12 1999/02/24 15:56:29 michael + Small fixes. Moved getlocaltime to system-dependent files Revision 1.11 1999/02/10 22:15:12 michael + Changed to ansistrings Revision 1.10 1998/12/15 22:43:09 peter * removed temp symbols Revision 1.9 1998/11/04 10:20:52 peter * ansistring fixes Revision 1.8 1998/10/02 13:57:38 michael Format error now causes exception Revision 1.7 1998/10/02 12:17:17 michael + Made sure it compiles with official 0.99.8 Revision 1.6 1998/10/02 10:42:17 michael + Initial implementation of format Revision 1.5 1998/10/01 16:05:37 michael Added (empty) format function Revision 1.4 1998/09/17 12:39:52 michael + Further fixes from GertJan Schouten Revision 1.3 1998/09/16 14:34:37 pierre * go32v2 did not compile * wrong code in systr.inc corrected Revision 1.2 1998/09/16 08:28:42 michael Update from gertjan Schouten, plus small fix for linux Revision 1.1 1998/04/10 15:17:46 michael + Initial implementation; Donated by Gertjan Schouten His file was split into several files, to keep it a little bit structured. }