Files
kolmck/System/D2005/SysUtils.pas
dkolmck 254b27740e read.txt
git-svn-id: https://svn.code.sf.net/p/kolmck/code@17 91bb2d04-0c0c-4d2d-88a5-bbb6f4c1fa07
2009-08-19 09:26:45 +00:00

16720 lines
482 KiB
ObjectPascal

{ *********************************************************************** }
{ }
{ Delphi / Kylix Cross-Platform Runtime Library }
{ System Utilities Unit }
{ }
{ Copyright (c) 1995-2004 Borland Software Corporation }
{ }
{ Copyright and license exceptions noted in source }
{ }
{ *********************************************************************** }
unit SysUtils;
{$H+}
{$WARN SYMBOL_PLATFORM OFF}
{$WARN UNSAFE_TYPE OFF}
interface
uses
{$IFDEF MSWINDOWS}
Windows, kol,
{$ENDIF}
{$IFDEF LINUX}
Types,
Libc,
{$ENDIF}
SysConst;
const
{ File open modes }
{$IFDEF LINUX}
fmOpenRead = O_RDONLY;
fmOpenWrite = O_WRONLY;
fmOpenReadWrite = O_RDWR;
// fmShareCompat not supported
fmShareExclusive = $0010;
fmShareDenyWrite = $0020;
// fmShareDenyRead not supported
fmShareDenyNone = $0030;
{$ENDIF}
{$IFDEF MSWINDOWS}
fmOpenRead = $0000;
fmOpenWrite = $0001;
fmOpenReadWrite = $0002;
fmShareCompat = $0000 platform; // DOS compatibility mode is not portable
fmShareExclusive = $0010;
fmShareDenyWrite = $0020;
fmShareDenyRead = $0030 platform; // write-only not supported on all platforms
fmShareDenyNone = $0040;
{$ENDIF}
{ File attribute constants }
faReadOnly = $00000001 platform;
faHidden = $00000002 platform;
faSysFile = $00000004 platform;
faVolumeID = $00000008 platform deprecated; // not used in Win32
faDirectory = $00000010;
faArchive = $00000020 platform;
faSymLink = $00000040 platform;
faAnyFile = $0000003F;
{ Units of time }
HoursPerDay = 24;
MinsPerHour = 60;
SecsPerMin = 60;
MSecsPerSec = 1000;
MinsPerDay = HoursPerDay * MinsPerHour;
SecsPerDay = MinsPerDay * SecsPerMin;
MSecsPerDay = SecsPerDay * MSecsPerSec;
{ Days between 1/1/0001 and 12/31/1899 }
DateDelta = 693594;
{ Days between TDateTime basis (12/31/1899) and Unix time_t basis (1/1/1970) }
UnixDateDelta = 25569;
type
{ Standard Character set type }
TSysCharSet = set of Char;
{ Set access to an integer }
TIntegerSet = set of 0..SizeOf(Integer) * 8 - 1;
{ Type conversion records }
WordRec = packed record
case Integer of
0: (Lo, Hi: Byte);
1: (Bytes: array [0..1] of Byte);
end;
LongRec = packed record
case Integer of
0: (Lo, Hi: Word);
1: (Words: array [0..1] of Word);
2: (Bytes: array [0..3] of Byte);
end;
Int64Rec = packed record
case Integer of
0: (Lo, Hi: Cardinal);
1: (Cardinals: array [0..1] of Cardinal);
2: (Words: array [0..3] of Word);
3: (Bytes: array [0..7] of Byte);
end;
{ General arrays }
PByteArray = ^TByteArray;
TByteArray = array[0..32767] of Byte;
PWordArray = ^TWordArray;
TWordArray = array[0..16383] of Word;
{ Generic procedure pointer }
TProcedure = procedure;
{ Generic filename type }
TFileName = type string;
{ Search record used by FindFirst, FindNext, and FindClose }
TSearchRec = record
Time: Integer;
Size: Integer;
Attr: Integer;
Name: TFileName;
ExcludeAttr: Integer;
{$IFDEF MSWINDOWS}
FindHandle: THandle platform;
FindData: TWin32FindData platform;
{$ENDIF}
{$IFDEF LINUX}
Mode: mode_t platform;
FindHandle: Pointer platform;
PathOnly: String platform;
Pattern: String platform;
{$ENDIF}
end;
{ FloatToText, FloatToTextFmt, TextToFloat, and FloatToDecimal type codes }
TFloatValue = (fvExtended, fvCurrency);
{ FloatToText format codes }
TFloatFormat = (ffGeneral, ffExponent, ffFixed, ffNumber, ffCurrency);
{ FloatToDecimal result record }
TFloatRec = packed record
Exponent: Smallint;
Negative: Boolean;
Digits: array[0..20] of Char;
end;
{ Date and time record }
TTimeStamp = record
Time: Integer; { Number of milliseconds since midnight }
Date: Integer; { One plus number of days since 1/1/0001 }
end;
{ MultiByte Character Set (MBCS) byte type }
TMbcsByteType = (mbSingleByte, mbLeadByte, mbTrailByte);
{ System Locale information record }
TSysLocale = packed record
DefaultLCID: Integer;
PriLangID: Integer;
SubLangID: Integer;
FarEast: Boolean;
MiddleEast: Boolean;
end;
{$IFDEF MSWINDOWS}
{ This is used by TLanguages }
TLangRec = packed record
FName: string;
FLCID: LCID;
FExt: string;
end;
{ This stores the languages that the system supports }
TLanguages = class
private
FSysLangs: array of TLangRec;
function LocalesCallback(LocaleID: PChar): Integer; stdcall;
function GetExt(Index: Integer): string;
function GetID(Index: Integer): string;
function GetLCID(Index: Integer): LCID;
function GetName(Index: Integer): string;
function GetNameFromLocaleID(ID: LCID): string;
function GetNameFromLCID(const ID: string): string;
function GetCount: integer;
public
constructor Create;
function IndexOf(ID: LCID): Integer;
property Count: Integer read GetCount;
property Name[Index: Integer]: string read GetName;
property NameFromLocaleID[ID: LCID]: string read GetNameFromLocaleID;
property NameFromLCID[const ID: string]: string read GetNameFromLCID;
property ID[Index: Integer]: string read GetID;
property LocaleID[Index: Integer]: LCID read GetLCID;
property Ext[Index: Integer]: string read GetExt;
end platform;
{$ENDIF}
{$IFDEF LINUX}
TEraRange = record
StartDate : Integer; // whole days since 12/31/1899 (TDateTime basis)
EndDate : Integer; // whole days since 12/31/1899 (TDateTime basis)
// Direction : Char;
end;
{$ENDIF}
{ Exceptions }
Exception = class(TObject)
private
FMessage: string;
FHelpContext: Integer;
public
constructor Create(const Msg: string);
constructor CreateFmt(const Msg: string; const Args: array of const);
constructor CreateRes(Ident: Integer); overload;
constructor CreateRes(const ResStringRec: string); overload;
constructor CreateResFmt(Ident: Integer; const Args: array of const); overload;
constructor CreateResFmt(const ResStringRec: string; const Args: array of const); overload;
constructor CreateHelp(const Msg: string; AHelpContext: Integer);
constructor CreateFmtHelp(const Msg: string; const Args: array of const;
AHelpContext: Integer);
constructor CreateResHelp(Ident: Integer; AHelpContext: Integer); overload;
constructor CreateResHelp(ResStringRec: PResStringRec; AHelpContext: Integer); overload;
constructor CreateResFmtHelp(ResStringRec: PResStringRec; const Args: array of const;
AHelpContext: Integer); overload;
constructor CreateResFmtHelp(Ident: Integer; const Args: array of const;
AHelpContext: Integer); overload;
property HelpContext: Integer read FHelpContext write FHelpContext;
property Message: string read FMessage write FMessage;
end;
ExceptClass = class of Exception;
EAbort = class(Exception);
EHeapException = class(Exception)
private
AllowFree: Boolean;
public
procedure FreeInstance; override;
end;
EOutOfMemory = class(EHeapException);
EInOutError = class(Exception)
public
ErrorCode: Integer;
end;
{$IFDEF MSWINDOWS}
PExceptionRecord = ^TExceptionRecord;
TExceptionRecord = record
ExceptionCode: Cardinal;
ExceptionFlags: Cardinal;
ExceptionRecord: PExceptionRecord;
ExceptionAddress: Pointer;
NumberParameters: Cardinal;
ExceptionInformation: array[0..14] of Cardinal;
end;
{$ENDIF}
EExternal = class(Exception)
public
{$IFDEF MSWINDOWS}
ExceptionRecord: PExceptionRecord platform;
{$ENDIF}
{$IFDEF LINUX}
ExceptionAddress: LongWord platform;
AccessAddress: LongWord platform;
SignalNumber: Integer platform;
{$ENDIF}
end;
EExternalException = class(EExternal);
EIntError = class(EExternal);
EDivByZero = class(EIntError);
ERangeError = class(EIntError);
EIntOverflow = class(EIntError);
EMathError = class(EExternal);
EInvalidOp = class(EMathError);
EZeroDivide = class(EMathError);
EOverflow = class(EMathError);
EUnderflow = class(EMathError);
EInvalidPointer = class(EHeapException);
EInvalidCast = class(Exception);
EConvertError = class(Exception);
EAccessViolation = class(EExternal);
EPrivilege = class(EExternal);
EStackOverflow = class(EExternal)
end deprecated;
EControlC = class(EExternal);
{$IFDEF LINUX}
EQuit = class(EExternal) end platform;
{$ENDIF}
{$IFDEF LINUX}
ECodesetConversion = class(Exception) end platform;
{$ENDIF}
EVariantError = class(Exception);
EPropReadOnly = class(Exception);
EPropWriteOnly = class(Exception);
EAssertionFailed = class(Exception);
{$IFNDEF PC_MAPPED_EXCEPTIONS}
EAbstractError = class(Exception) end platform;
{$ENDIF}
EIntfCastError = class(Exception);
EInvalidContainer = class(Exception);
EInvalidInsert = class(Exception);
EPackageError = class(Exception);
EOSError = class(Exception)
public
ErrorCode: DWORD;
end;
{$IFDEF MSWINDOWS}
EWin32Error = class(EOSError)
end deprecated;
{$ENDIF}
ESafecallException = class(Exception);
{$IFDEF LINUX}
{
Signals
External exceptions, or signals, are, by default, converted to language
exceptions by the Delphi RTL. Under Linux, a Delphi application installs
signal handlers to trap the raw signals, and convert them. Delphi libraries
do not install handlers by default. So if you are implementing a standalone
library, such as an Apache DSO, and you want to have signals converted to
language exceptions that you can catch, you must install signal hooks
manually, using the interfaces that the Delphi RTL provides.
For most libraries, installing signal handlers is pretty
straightforward. Call HookSignal(RTL_SIGDEFAULT) at initialization time,
and UnhookSignal(RTL_SIGNALDEFAULT) at shutdown. This will install handlers
for a set of signals that the RTL normally hooks for Delphi applications.
There are some cases where the above initialization will not work properly:
The proper behaviour for setting up signal handlers is to set
a signal handler, and then later restore the signal handler to its previous
state when you clean up. If you have two libraries lib1 and lib2, and lib1
installs a signal handler, and then lib2 installs a signal handler, those
libraries have to uninstall in the proper order if they restore signal
handlers, or the signal handlers can be left in an inconsistent and
potentially fatal state. Not all libraries behave well with respect to
installing signal handlers. To hedge against this possibility, and allow
you to manage signal handlers better in the face of whatever behaviour
you may find in external libraries, we provide a set of four interfaces to
allow you to tailor the Delphi signal handler hooking/unhooking in the
event of an emergency. These are:
InquireSignal
AbandonSignalHandler
HookSignal
UnhookSignal
InquireSignal allows you to look at the state of a signal handler, so
that you can find out if someone grabbed it out from under you.
AbandonSignalHandler tells the RTL never to unhook a particular
signal handler. This can be used if you find a case where it would
be unsafe to return to the previous state of signal handling. For
example, if the previous signal handler was installed by a library
which has since been unloaded.
HookSignal/UnhookSignal setup signal handlers that map certain signals
into language exceptions.
See additional notes at InquireSignal, et al, below.
}
const
RTL_SIGINT = 0; // User interrupt (SIGINT)
RTL_SIGFPE = 1; // Floating point exception (SIGFPE)
RTL_SIGSEGV = 2; // Segmentation violation (SIGSEGV)
RTL_SIGILL = 3; // Illegal instruction (SIGILL)
RTL_SIGBUS = 4; // Bus error (SIGBUS)
RTL_SIGQUIT = 5; // User interrupt (SIGQUIT)
RTL_SIGLAST = RTL_SIGQUIT; // Used internally. Don't use this.
RTL_SIGDEFAULT = -1; // Means all of a set of signals that the we capture
// normally. This is currently all of the preceding
// signals. You cannot pass this to InquireSignal.
type
{ TSignalState is the state of a given signal handler, as returned by
InquireSignal. See InquireSignal, below.
}
TSignalState = (ssNotHooked, ssHooked, ssOverridden);
var
{
If DeferUserInterrupts is set, we do not raise either SIGINT or SIGQUIT as
an exception, instead, we set SIGINTIssued or SIGQUITIssued when the
signal arrives, and swallow the signal where the OS issued it. This gives
GUI applications the chance to defer the actual handling of the signal
until a time when it is safe to do so.
}
DeferUserInterrupts: Boolean;
SIGINTIssued: Boolean;
SIGQUITIssued: Boolean;
{$ENDIF}
{$IFDEF LINUX}
const
MAX_PATH = 4095; // From /usr/include/linux/limits.h PATH_MAX
{$ENDIF}
var
{ Empty string and null string pointer. These constants are provided for
backwards compatibility only. }
EmptyStr: string = '';
NullStr: PString = @EmptyStr;
EmptyWideStr: WideString = '';
NullWideStr: PWideString = @EmptyWideStr;
{$IFDEF MSWINDOWS}
{ Win32 platform identifier. This will be one of the following values:
VER_PLATFORM_WIN32s
VER_PLATFORM_WIN32_WINDOWS
VER_PLATFORM_WIN32_NT
See WINDOWS.PAS for the numerical values. }
Win32Platform: Integer = 0;
{ Win32 OS version information -
see TOSVersionInfo.dwMajorVersion/dwMinorVersion/dwBuildNumber }
Win32MajorVersion: Integer = 0;
Win32MinorVersion: Integer = 0;
Win32BuildNumber: Integer = 0;
{ Win32 OS extra version info string -
see TOSVersionInfo.szCSDVersion }
Win32CSDVersion: string = '';
{ Win32 OS version tester }
function CheckWin32Version(AMajor: Integer; AMinor: Integer = 0): Boolean;
{ GetFileVersion returns the most significant 32 bits of a file's binary
version number. Typically, this includes the major and minor version placed
together in one 32-bit integer. It generally does not include the release
or build numbers. It returns Cardinal(-1) if it failed. }
function GetFileVersion(const AFileName: string): Cardinal;
{$ENDIF}
{ Currency and date/time formatting options
The initial values of these variables are fetched from the system registry
using the GetLocaleInfo function in the Win32 API. The description of each
variable specifies the LOCALE_XXXX constant used to fetch the initial
value.
CurrencyString - Defines the currency symbol used in floating-point to
decimal conversions. The initial value is fetched from LOCALE_SCURRENCY.
CurrencyFormat - Defines the currency symbol placement and separation
used in floating-point to decimal conversions. Possible values are:
0 = '$1'
1 = '1$'
2 = '$ 1'
3 = '1 $'
The initial value is fetched from LOCALE_ICURRENCY.
NegCurrFormat - Defines the currency format for used in floating-point to
decimal conversions of negative numbers. Possible values are:
0 = '($1)' 4 = '(1$)' 8 = '-1 $' 12 = '$ -1'
1 = '-$1' 5 = '-1$' 9 = '-$ 1' 13 = '1- $'
2 = '$-1' 6 = '1-$' 10 = '1 $-' 14 = '($ 1)'
3 = '$1-' 7 = '1$-' 11 = '$ 1-' 15 = '(1 $)'
The initial value is fetched from LOCALE_INEGCURR.
ThousandSeparator - The character used to separate thousands in numbers
with more than three digits to the left of the decimal separator. The
initial value is fetched from LOCALE_STHOUSAND. A value of #0 indicates
no thousand separator character should be output even if the format string
specifies thousand separators.
DecimalSeparator - The character used to separate the integer part from
the fractional part of a number. The initial value is fetched from
LOCALE_SDECIMAL. DecimalSeparator must be a non-zero value.
CurrencyDecimals - The number of digits to the right of the decimal point
in a currency amount. The initial value is fetched from LOCALE_ICURRDIGITS.
DateSeparator - The character used to separate the year, month, and day
parts of a date value. The initial value is fetched from LOCATE_SDATE.
ShortDateFormat - The format string used to convert a date value to a
short string suitable for editing. For a complete description of date and
time format strings, refer to the documentation for the FormatDate
function. The short date format should only use the date separator
character and the m, mm, d, dd, yy, and yyyy format specifiers. The
initial value is fetched from LOCALE_SSHORTDATE.
LongDateFormat - The format string used to convert a date value to a long
string suitable for display but not for editing. For a complete description
of date and time format strings, refer to the documentation for the
FormatDate function. The initial value is fetched from LOCALE_SLONGDATE.
TimeSeparator - The character used to separate the hour, minute, and
second parts of a time value. The initial value is fetched from
LOCALE_STIME.
TimeAMString - The suffix string used for time values between 00:00 and
11:59 in 12-hour clock format. The initial value is fetched from
LOCALE_S1159.
TimePMString - The suffix string used for time values between 12:00 and
23:59 in 12-hour clock format. The initial value is fetched from
LOCALE_S2359.
ShortTimeFormat - The format string used to convert a time value to a
short string with only hours and minutes. The default value is computed
from LOCALE_ITIME and LOCALE_ITLZERO.
LongTimeFormat - The format string used to convert a time value to a long
string with hours, minutes, and seconds. The default value is computed
from LOCALE_ITIME and LOCALE_ITLZERO.
ShortMonthNames - Array of strings containing short month names. The mmm
format specifier in a format string passed to FormatDate causes a short
month name to be substituted. The default values are fecthed from the
LOCALE_SABBREVMONTHNAME system locale entries.
LongMonthNames - Array of strings containing long month names. The mmmm
format specifier in a format string passed to FormatDate causes a long
month name to be substituted. The default values are fecthed from the
LOCALE_SMONTHNAME system locale entries.
ShortDayNames - Array of strings containing short day names. The ddd
format specifier in a format string passed to FormatDate causes a short
day name to be substituted. The default values are fecthed from the
LOCALE_SABBREVDAYNAME system locale entries.
LongDayNames - Array of strings containing long day names. The dddd
format specifier in a format string passed to FormatDate causes a long
day name to be substituted. The default values are fecthed from the
LOCALE_SDAYNAME system locale entries.
ListSeparator - The character used to separate items in a list. The
initial value is fetched from LOCALE_SLIST.
TwoDigitYearCenturyWindow - Determines what century is added to two
digit years when converting string dates to numeric dates. This value
is subtracted from the current year before extracting the century.
This can be used to extend the lifetime of existing applications that
are inextricably tied to 2 digit year data entry. The best solution
to Year 2000 (Y2k) issues is not to accept 2 digit years at all - require
4 digit years in data entry to eliminate century ambiguities.
Examples:
Current TwoDigitCenturyWindow Century StrToDate() of:
Year Value Pivot '01/01/03' '01/01/68' '01/01/50'
-------------------------------------------------------------------------
1998 0 1900 1903 1968 1950
2002 0 2000 2003 2068 2050
1998 50 (default) 1948 2003 1968 1950
2002 50 (default) 1952 2003 1968 2050
2020 50 (default) 1970 2003 2068 2050
}
var
CurrencyString: string;
CurrencyFormat: Byte;
NegCurrFormat: Byte;
ThousandSeparator: Char;
DecimalSeparator: Char;
CurrencyDecimals: Byte;
DateSeparator: Char;
ShortDateFormat: string;
LongDateFormat: string;
TimeSeparator: Char;
TimeAMString: string;
TimePMString: string;
ShortTimeFormat: string;
LongTimeFormat: string;
ShortMonthNames: array[1..12] of string;
LongMonthNames: array[1..12] of string;
ShortDayNames: array[1..7] of string;
LongDayNames: array[1..7] of string;
SysLocale: TSysLocale;
TwoDigitYearCenturyWindow: Word = 50;
ListSeparator: Char;
{ Thread safe currency and date/time formatting
The TFormatSettings record is designed to allow thread safe formatting,
equivalent to the gloabal variables described above. Each of the
formatting routines that use the gloabal variables have overloaded
equivalents, requiring an additional parameter of type TFormatSettings.
A TFormatSettings record must be populated before use. This can be done
using the GetLocaleFormatSettings function, which will populate the
record with values based on the given locale (using the Win32 API
function GetLocaleInfo). Note that some format specifiers still require
specific thread locale settings (such as period/era names).
}
type
TFormatSettings = record
CurrencyFormat: Byte;
NegCurrFormat: Byte;
ThousandSeparator: Char;
DecimalSeparator: Char;
CurrencyDecimals: Byte;
DateSeparator: Char;
TimeSeparator: Char;
ListSeparator: Char;
CurrencyString: string;
ShortDateFormat: string;
LongDateFormat: string;
TimeAMString: string;
TimePMString: string;
ShortTimeFormat: string;
LongTimeFormat: string;
ShortMonthNames: array[1..12] of string;
LongMonthNames: array[1..12] of string;
ShortDayNames: array[1..7] of string;
LongDayNames: array[1..7] of string;
TwoDigitYearCenturyWindow: Word;
end;
TLocaleOptions = (loInvariantLocale, loUserLocale);
const
MaxEraCount = 7;
var
EraNames: array [1..MaxEraCount] of string;
EraYearOffsets: array [1..MaxEraCount] of Integer;
{$IFDEF LINUX}
EraRanges : array [1..MaxEraCount] of TEraRange platform;
EraYearFormats: array [1..MaxEraCount] of string platform;
EraCount: Byte platform;
{$ENDIF}
const
PathDelim = {$IFDEF MSWINDOWS} '\'; {$ELSE} '/'; {$ENDIF}
DriveDelim = {$IFDEF MSWINDOWS} ':'; {$ELSE} ''; {$ENDIF}
PathSep = {$IFDEF MSWINDOWS} ';'; {$ELSE} ':'; {$ENDIF}
{$IFDEF MSWINDOWS}
function Languages: TLanguages;
{$ENDIF}
{ Memory management routines }
{ AllocMem allocates a block of the given size on the heap. Each byte in
the allocated buffer is set to zero. To dispose the buffer, use the
FreeMem standard procedure. }
function AllocMem(Size: Cardinal): Pointer;
{ Exit procedure handling }
{ AddExitProc adds the given procedure to the run-time library's exit
procedure list. When an application terminates, its exit procedures are
executed in reverse order of definition, i.e. the last procedure passed
to AddExitProc is the first one to get executed upon termination. }
procedure AddExitProc(Proc: TProcedure);
{ String handling routines }
{ NewStr allocates a string on the heap. NewStr is provided for backwards
compatibility only. }
function NewStr(const S: string): PString; deprecated;
{ DisposeStr disposes a string pointer that was previously allocated using
NewStr. DisposeStr is provided for backwards compatibility only. }
procedure DisposeStr(P: PString); deprecated;
{ AssignStr assigns a new dynamically allocated string to the given string
pointer. AssignStr is provided for backwards compatibility only. }
procedure AssignStr(var P: PString; const S: string); deprecated;
{ AppendStr appends S to the end of Dest. AppendStr is provided for
backwards compatibility only. Use "Dest := Dest + S" instead. }
procedure AppendStr(var Dest: string; const S: string); deprecated;
{ UpperCase converts all ASCII characters in the given string to upper case.
The conversion affects only 7-bit ASCII characters between 'a' and 'z'. To
convert 8-bit international characters, use AnsiUpperCase. }
function UpperCase(const S: string): string; overload;
function UpperCase(const S: string; LocaleOptions: TLocaleOptions): string; overload; inline;
{ LowerCase converts all ASCII characters in the given string to lower case.
The conversion affects only 7-bit ASCII characters between 'A' and 'Z'. To
convert 8-bit international characters, use AnsiLowerCase. }
function LowerCase(const S: string): string; overload;
function LowerCase(const S: string; LocaleOptions: TLocaleOptions): string; overload; inline;
{ CompareStr compares S1 to S2, with case-sensitivity. The return value is
less than 0 if S1 < S2, 0 if S1 = S2, or greater than 0 if S1 > S2. The
compare operation is based on the 8-bit ordinal value of each character
and is not affected by the current user locale. }
function CompareStr(const S1, S2: string): Integer; overload;
function CompareStr(const S1, S2: string; LocaleOptions: TLocaleOptions): Integer; overload;
{ SameStr compares S1 to S2, with case-sensitivity. Returns true if
S1 and S2 are the equal, that is, if CompareStr would return 0. }
function SameStr(const S1, S2: string): Boolean; overload;
function SameStr(const S1, S2: string; LocaleOptions: TLocaleOptions): Boolean; overload;
{ CompareMem performs a binary compare of Length bytes of memory referenced
by P1 to that of P2. CompareMem returns True if the memory referenced by
P1 is identical to that of P2. }
function CompareMem(P1, P2: Pointer; Length: Integer): Boolean; assembler;
{ CompareText compares S1 to S2, without case-sensitivity. The return value
is the same as for CompareStr. The compare operation is based on the 8-bit
ordinal value of each character, after converting 'a'..'z' to 'A'..'Z',
and is not affected by the current user locale. }
function CompareText(const S1, S2: string): Integer; overload;
function CompareText(const S1, S2: string; LocaleOptions: TLocaleOptions): Integer; overload;
{ SameText compares S1 to S2, without case-sensitivity. Returns true if
S1 and S2 are the equal, that is, if CompareText would return 0. SameText
has the same 8-bit limitations as CompareText }
function SameText(const S1, S2: string): Boolean; overload;
function SameText(const S1, S2: string; LocaleOptions: TLocaleOptions): Boolean; overload;
{ AnsiUpperCase converts all characters in the given string to upper case.
The conversion uses the current user locale. }
function AnsiUpperCase(const S: string): string;
{ AnsiLowerCase converts all characters in the given string to lower case.
The conversion uses the current user locale. }
function AnsiLowerCase(const S: string): string;
{ AnsiCompareStr compares S1 to S2, with case-sensitivity. The compare
operation is controlled by the current user locale. The return value
is the same as for CompareStr. }
function AnsiCompareStr(const S1, S2: string): Integer; inline;
{ AnsiSameStr compares S1 to S2, with case-sensitivity. The compare
operation is controlled by the current user locale. The return value
is True if AnsiCompareStr would have returned 0. }
function AnsiSameStr(const S1, S2: string): Boolean; inline;
{ AnsiCompareText compares S1 to S2, without case-sensitivity. The compare
operation is controlled by the current user locale. The return value
is the same as for CompareStr. }
function AnsiCompareText(const S1, S2: string): Integer; inline;
{ AnsiSameText compares S1 to S2, without case-sensitivity. The compare
operation is controlled by the current user locale. The return value
is True if AnsiCompareText would have returned 0. }
function AnsiSameText(const S1, S2: string): Boolean; inline;
{ AnsiStrComp compares S1 to S2, with case-sensitivity. The compare
operation is controlled by the current user locale. The return value
is the same as for CompareStr. }
function AnsiStrComp(S1, S2: PChar): Integer; inline;
{ AnsiStrIComp compares S1 to S2, without case-sensitivity. The compare
operation is controlled by the current user locale. The return value
is the same as for CompareStr. }
function AnsiStrIComp(S1, S2: PChar): Integer; inline;
{ AnsiStrLComp compares S1 to S2, with case-sensitivity, up to a maximum
length of MaxLen bytes. The compare operation is controlled by the
current user locale. The return value is the same as for CompareStr. }
function AnsiStrLComp(S1, S2: PChar; MaxLen: Cardinal): Integer;
{ AnsiStrLIComp compares S1 to S2, without case-sensitivity, up to a maximum
length of MaxLen bytes. The compare operation is controlled by the
current user locale. The return value is the same as for CompareStr. }
function AnsiStrLIComp(S1, S2: PChar; MaxLen: Cardinal): Integer;
{ AnsiStrLower converts all characters in the given string to lower case.
The conversion uses the current user locale. }
function AnsiStrLower(Str: PChar): PChar;
{ AnsiStrUpper converts all characters in the given string to upper case.
The conversion uses the current user locale. }
function AnsiStrUpper(Str: PChar): PChar;
{ AnsiLastChar returns a pointer to the last full character in the string.
This function supports multibyte characters }
function AnsiLastChar(const S: string): PChar;
{ AnsiStrLastChar returns a pointer to the last full character in the string.
This function supports multibyte characters. }
function AnsiStrLastChar(P: PChar): PChar;
{ WideUpperCase converts all characters in the given string to upper case. }
function WideUpperCase(const S: WideString): WideString;
{ WideLowerCase converts all characters in the given string to lower case. }
function WideLowerCase(const S: WideString): WideString;
{ WideCompareStr compares S1 to S2, with case-sensitivity. The return value
is the same as for CompareStr. }
function WideCompareStr(const S1, S2: WideString): Integer;
{ WideSameStr compares S1 to S2, with case-sensitivity. The return value
is True if WideCompareStr would have returned 0. }
function WideSameStr(const S1, S2: WideString): Boolean; inline;
{ WideCompareText compares S1 to S2, without case-sensitivity. The return value
is the same as for CompareStr. }
function WideCompareText(const S1, S2: WideString): Integer;
{ WideSameText compares S1 to S2, without case-sensitivity. The return value
is True if WideCompareText would have returned 0. }
function WideSameText(const S1, S2: WideString): Boolean; inline;
{ Trim trims leading and trailing spaces and control characters from the
given string. }
function Trim(const S: string): string; overload;
function Trim(const S: WideString): WideString; overload;
{ TrimLeft trims leading spaces and control characters from the given
string. }
function TrimLeft(const S: string): string; overload;
function TrimLeft(const S: WideString): WideString; overload;
{ TrimRight trims trailing spaces and control characters from the given
string. }
function TrimRight(const S: string): string; overload;
function TrimRight(const S: WideString): WideString; overload;
{ QuotedStr returns the given string as a quoted string. A single quote
character is inserted at the beginning and the end of the string, and
for each single quote character in the string, another one is added. }
function QuotedStr(const S: string): string;
{ AnsiQuotedStr returns the given string as a quoted string, using the
provided Quote character. A Quote character is inserted at the beginning
and end of the string, and each Quote character in the string is doubled.
This function supports multibyte character strings (MBCS). }
function AnsiQuotedStr(const S: string; Quote: Char): string;
{ AnsiExtractQuotedStr removes the Quote characters from the beginning and end
of a quoted string, and reduces pairs of Quote characters within the quoted
string to a single character. If the first character in Src is not the Quote
character, the function returns an empty string. The function copies
characters from the Src to the result string until the second solitary
Quote character or the first null character in Src. The Src parameter is
updated to point to the first character following the quoted string. If
the Src string does not contain a matching end Quote character, the Src
parameter is updated to point to the terminating null character in Src.
This function supports multibyte character strings (MBCS). }
function AnsiExtractQuotedStr(var Src: PChar; Quote: Char): string;
{ AnsiDequotedStr is a simplified version of AnsiExtractQuotedStr }
function AnsiDequotedStr(const S: string; AQuote: Char): string;
{ AdjustLineBreaks adjusts all line breaks in the given string to the
indicated style.
When Style is tlbsCRLF, the function changes all
CR characters not followed by LF and all LF characters not preceded
by a CR into CR/LF pairs.
When Style is tlbsLF, the function changes all CR/LF pairs and CR characters
not followed by LF to LF characters. }
function AdjustLineBreaks(const S: string; Style: TTextLineBreakStyle =
{$IFDEF LINUX} tlbsLF {$ENDIF}
{$IFDEF MSWINDOWS} tlbsCRLF {$ENDIF}): string;
{ IsValidIdent returns true if the given string is a valid identifier. An
identifier is defined as a character from the set ['A'..'Z', 'a'..'z', '_']
followed by zero or more characters from the set ['A'..'Z', 'a'..'z',
'0..'9', '_']. With DotNet code we need to allow dots in the names.}
function IsValidIdent(const Ident: string; AllowDots: Boolean = False): Boolean;
{ IntToStr converts the given value to its decimal string representation. }
function IntToStr(Value: Integer): string; overload;
function IntToStr(Value: Int64): string; overload;
{ IntToHex converts the given value to a hexadecimal string representation
with the minimum number of digits specified. }
function IntToHex(Value: Integer; Digits: Integer): string; overload;
function IntToHex(Value: Int64; Digits: Integer): string; overload;
{ StrToInt converts the given string to an integer value. If the string
doesn't contain a valid value, an EConvertError exception is raised. }
function StrToInt(const S: string): Integer;
function StrToIntDef(const S: string; Default: Integer): Integer;
function TryStrToInt(const S: string; out Value: Integer): Boolean;
{ Similar to the above functions but for Int64 instead }
function StrToInt64(const S: string): Int64;
function StrToInt64Def(const S: string; const Default: Int64): Int64;
function TryStrToInt64(const S: string; out Value: Int64): Boolean;
{ StrToBool converts the given string to a boolean value. If the string
doesn't contain a valid value, an EConvertError exception is raised.
BoolToStr converts boolean to a string value that in turn can be converted
back into a boolean. BoolToStr will always pick the first element of
the TrueStrs/FalseStrs arrays. }
var
TrueBoolStrs: array of String;
FalseBoolStrs: array of String;
const
DefaultTrueBoolStr = 'True'; // DO NOT LOCALIZE
DefaultFalseBoolStr = 'False'; // DO NOT LOCALIZE
function StrToBool(const S: string): Boolean;
function StrToBoolDef(const S: string; const Default: Boolean): Boolean;
function TryStrToBool(const S: string; out Value: Boolean): Boolean;
function BoolToStr(B: Boolean; UseBoolStrs: Boolean = False): string;
{ LoadStr loads the string resource given by Ident from the application's
executable file or associated resource module. If the string resource
does not exist, LoadStr returns an empty string. }
function LoadStr(Ident: Integer): string;
{ FmtLoadStr loads the string resource given by Ident from the application's
executable file or associated resource module, and uses it as the format
string in a call to the Format function with the given arguments. }
function FmtLoadStr(Ident: Integer; const Args: array of const): string;
{ File management routines }
{ FileOpen opens the specified file using the specified access mode. The
access mode value is constructed by OR-ing one of the fmOpenXXXX constants
with one of the fmShareXXXX constants. If the return value is positive,
the function was successful and the value is the file handle of the opened
file. A return value of -1 indicates that an error occurred. }
function FileOpen(const FileName: string; Mode: LongWord): Integer;
{ FileCreate creates a new file by the specified name. If the return value
is positive, the function was successful and the value is the file handle
of the new file. A return value of -1 indicates that an error occurred.
On Linux, this calls FileCreate(FileName, DEFFILEMODE) to create
the file with read and write access for the current user only. }
function FileCreate(const FileName: string): Integer; overload; inline;
{ This second version of FileCreate lets you specify the access rights to put on the newly
created file. The access rights parameter is ignored on Win32 }
function FileCreate(const FileName: string; Rights: Integer): Integer; overload; inline;
{ FileRead reads Count bytes from the file given by Handle into the buffer
specified by Buffer. The return value is the number of bytes actually
read; it is less than Count if the end of the file was reached. The return
value is -1 if an error occurred. }
function FileRead(Handle: Integer; var Buffer; Count: LongWord): Integer;
{ FileWrite writes Count bytes to the file given by Handle from the buffer
specified by Buffer. The return value is the number of bytes actually
written, or -1 if an error occurred. }
function FileWrite(Handle: Integer; const Buffer; Count: LongWord): Integer;
{ FileSeek changes the current position of the file given by Handle to be
Offset bytes relative to the point given by Origin. Origin = 0 means that
Offset is relative to the beginning of the file, Origin = 1 means that
Offset is relative to the current position, and Origin = 2 means that
Offset is relative to the end of the file. The return value is the new
current position, relative to the beginning of the file, or -1 if an error
occurred. }
function FileSeek(Handle, Offset, Origin: Integer): Integer; overload;
function FileSeek(Handle: Integer; const Offset: Int64; Origin: Integer): Int64; overload;
{ FileClose closes the specified file. }
procedure FileClose(Handle: Integer); inline;
{ FileAge returns the date-and-time stamp of the specified file. The return
value can be converted to a TDateTime value using the FileDateToDateTime
function. The return value is -1 if the file does not exist. }
function FileAge(const FileName: string): Integer;
{ FileExists returns a boolean value that indicates whether the specified
file exists. }
function FileExists(const FileName: string): Boolean; inline;
{ DirectoryExists returns a boolean value that indicates whether the
specified directory exists (and is actually a directory) }
function DirectoryExists(const Directory: string): Boolean;
{ ForceDirectories ensures that all the directories in a specific path exist.
Any portion that does not already exist will be created. Function result
indicates success of the operation. The function can fail if the current
user does not have sufficient file access rights to create directories in
the given path. }
function ForceDirectories(Dir: string): Boolean;
{ FindFirst searches the directory given by Path for the first entry that
matches the filename given by Path and the attributes given by Attr. The
result is returned in the search record given by SearchRec. The return
value is zero if the function was successful. Otherwise the return value
is a system error code. After calling FindFirst, always call FindClose.
FindFirst is typically used with FindNext and FindClose as follows:
Result := FindFirst(Path, Attr, SearchRec);
while Result = 0 do
begin
ProcessSearchRec(SearchRec);
Result := FindNext(SearchRec);
end;
FindClose(SearchRec);
where ProcessSearchRec represents user-defined code that processes the
information in a search record. }
function FindFirst(const Path: string; Attr: Integer;
var F: TSearchRec): Integer;
{ FindNext returs the next entry that matches the name and attributes
specified in a previous call to FindFirst. The search record must be one
that was passed to FindFirst. The return value is zero if the function was
successful. Otherwise the return value is a system error code. }
function FindNext(var F: TSearchRec): Integer;
{ FindClose terminates a FindFirst/FindNext sequence and frees memory and system
resources allocated by FindFirst.
Every FindFirst/FindNext must end with a call to FindClose. }
procedure FindClose(var F: TSearchRec);
{ FileGetDate returns the OS date-and-time stamp of the file given by
Handle. The return value is -1 if the handle is invalid. The
FileDateToDateTime function can be used to convert the returned value to
a TDateTime value. }
function FileGetDate(Handle: Integer): Integer;
{ FileSetDate sets the OS date-and-time stamp of the file given by FileName
to the value given by Age. The DateTimeToFileDate function can be used to
convert a TDateTime value to an OS date-and-time stamp. The return value
is zero if the function was successful. Otherwise the return value is a
system error code. }
function FileSetDate(const FileName: string; Age: Integer): Integer; overload;
{$IFDEF MSWINDOWS}
{ FileSetDate by handle is not available on Unix platforms because there
is no standard way to set a file's modification time using only a file
handle, and no standard way to obtain the file name of an open
file handle. }
function FileSetDate(Handle: Integer; Age: Integer): Integer; overload; platform;
{ FileGetAttr returns the file attributes of the file given by FileName. The
attributes can be examined by AND-ing with the faXXXX constants defined
above. A return value of -1 indicates that an error occurred. }
function FileGetAttr(const FileName: string): Integer; platform;
{ FileSetAttr sets the file attributes of the file given by FileName to the
value given by Attr. The attribute value is formed by OR-ing the
appropriate faXXXX constants. The return value is zero if the function was
successful. Otherwise the return value is a system error code. }
function FileSetAttr(const FileName: string; Attr: Integer): Integer; platform;
{$ENDIF}
{ FileIsReadOnly tests whether a given file is read-only for the current
process and effective user id. If the file does not exist, the
function returns False. (Check FileExists before calling FileIsReadOnly)
This function is platform portable. }
function FileIsReadOnly(const FileName: string): Boolean; inline;
{ FileSetReadOnly sets the read only state of a file. The file must
exist and the current effective user id must be the owner of the file.
On Unix systems, FileSetReadOnly attempts to set or remove
all three (user, group, and other) write permissions on the file.
If you want to grant partial permissions (writeable for owner but not
for others), use platform specific functions such as chmod.
The function returns True if the file was successfully modified,
False if there was an error. This function is platform portable. }
function FileSetReadOnly(const FileName: string; ReadOnly: Boolean): Boolean;
{ DeleteFile deletes the file given by FileName. The return value is True if
the file was successfully deleted, or False if an error occurred. }
function DeleteFile(const FileName: string): Boolean; inline;
{ RenameFile renames the file given by OldName to the name given by NewName.
The return value is True if the file was successfully renamed, or False if
an error occurred. }
function RenameFile(const OldName, NewName: string): Boolean; inline;
{ ChangeFileExt changes the extension of a filename. FileName specifies a
filename with or without an extension, and Extension specifies the new
extension for the filename. The new extension can be a an empty string or
a period followed by up to three characters. }
function ChangeFileExt(const FileName, Extension: string): string;
{ ExtractFilePath extracts the drive and directory parts of the given
filename. The resulting string is the leftmost characters of FileName,
up to and including the colon or backslash that separates the path
information from the name and extension. The resulting string is empty
if FileName contains no drive and directory parts. }
function ExtractFilePath(const FileName: string): string;
{ ExtractFileDir extracts the drive and directory parts of the given
filename. The resulting string is a directory name suitable for passing
to SetCurrentDir, CreateDir, etc. The resulting string is empty if
FileName contains no drive and directory parts. }
function ExtractFileDir(const FileName: string): string;
{ ExtractFileDrive extracts the drive part of the given filename. For
filenames with drive letters, the resulting string is '<drive>:'.
For filenames with a UNC path, the resulting string is in the form
'\\<servername>\<sharename>'. If the given path contains neither
style of filename, the result is an empty string. }
function ExtractFileDrive(const FileName: string): string;
{ ExtractFileName extracts the name and extension parts of the given
filename. The resulting string is the leftmost characters of FileName,
starting with the first character after the colon or backslash that
separates the path information from the name and extension. The resulting
string is equal to FileName if FileName contains no drive and directory
parts. }
function ExtractFileName(const FileName: string): string;
{ ExtractFileExt extracts the extension part of the given filename. The
resulting string includes the period character that separates the name
and extension parts. The resulting string is empty if the given filename
has no extension. }
function ExtractFileExt(const FileName: string): string;
{ ExpandFileName expands the given filename to a fully qualified filename.
The resulting string consists of a drive letter, a colon, a root relative
directory path, and a filename. Embedded '.' and '..' directory references
are removed. }
function ExpandFileName(const FileName: string): string;
{ ExpandFilenameCase returns a fully qualified filename like ExpandFilename,
but performs a case-insensitive filename search looking for a close match
in the actual file system, differing only in uppercase versus lowercase of
the letters. This is useful to convert lazy user input into useable file
names, or to convert filename data created on a case-insensitive file
system (Win32) to something useable on a case-sensitive file system (Linux).
The MatchFound out parameter indicates what kind of match was found in the
file system, and what the function result is based upon:
( in order of increasing difficulty or complexity )
mkExactMatch: Case-sensitive match. Result := ExpandFileName(FileName).
mkSingleMatch: Exactly one file in the given directory path matches the
given filename on a case-insensitive basis.
Result := ExpandFileName(FileName as found in file system).
mkAmbiguous: More than one file in the given directory path matches the
given filename case-insensitively.
In many cases, this should be considered an error.
Result := ExpandFileName(First matching filename found).
mkNone: File not found at all. Result := ExpandFileName(FileName).
Note that because this function has to search the file system it may be
much slower than ExpandFileName, particularly when the given filename is
ambiguous or does not exist. Use ExpandFilenameCase only when you have
a filename of dubious orgin - such as from user input - and you want
to make a best guess before failing. }
type
TFilenameCaseMatch = (mkNone, mkExactMatch, mkSingleMatch, mkAmbiguous);
function ExpandFileNameCase(const FileName: string;
out MatchFound: TFilenameCaseMatch): string;
{ ExpandUNCFileName expands the given filename to a fully qualified filename.
This function is the same as ExpandFileName except that it will return the
drive portion of the filename in the format '\\<servername>\<sharename> if
that drive is actually a network resource instead of a local resource.
Like ExpandFileName, embedded '.' and '..' directory references are
removed. }
function ExpandUNCFileName(const FileName: string): string;
{ ExtractRelativePath will return a file path name relative to the given
BaseName. It strips the common path dirs and adds '..\' on Windows,
and '../' on Linux for each level up from the BaseName path. }
function ExtractRelativePath(const BaseName, DestName: string): string;
{$IFDEF MSWINDOWS}
{ ExtractShortPathName will convert the given filename to the short form
by calling the GetShortPathName API. Will return an empty string if
the file or directory specified does not exist }
function ExtractShortPathName(const FileName: string): string;
{$ENDIF}
{ FileSearch searches for the file given by Name in the list of directories
given by DirList. The directory paths in DirList must be separated by
PathSep chars. The search always starts with the current directory of the
current drive. The returned value is a concatenation of one of the
directory paths and the filename, or an empty string if the file could not
be located. }
function FileSearch(const Name, DirList: string): string;
{$IFDEF MSWINDOWS}
{ DiskFree returns the number of free bytes on the specified drive number,
where 0 = Current, 1 = A, 2 = B, etc. DiskFree returns -1 if the drive
number is invalid. }
function DiskFree(Drive: Byte): Int64;
{ DiskSize returns the size in bytes of the specified drive number, where
0 = Current, 1 = A, 2 = B, etc. DiskSize returns -1 if the drive number
is invalid. }
function DiskSize(Drive: Byte): Int64;
{$ENDIF}
{ FileDateToDateTime converts an OS date-and-time value to a TDateTime
value. The FileAge, FileGetDate, and FileSetDate routines operate on OS
date-and-time values, and the Time field of a TSearchRec used by the
FindFirst and FindNext functions contains an OS date-and-time value. }
function FileDateToDateTime(FileDate: Integer): TDateTime;
{ DateTimeToFileDate converts a TDateTime value to an OS date-and-time
value. The FileAge, FileGetDate, and FileSetDate routines operate on OS
date-and-time values, and the Time field of a TSearchRec used by the
FindFirst and FindNext functions contains an OS date-and-time value. }
function DateTimeToFileDate(DateTime: TDateTime): Integer;
{ GetCurrentDir returns the current directory. }
function GetCurrentDir: string;
{ SetCurrentDir sets the current directory. The return value is True if
the current directory was successfully changed, or False if an error
occurred. }
function SetCurrentDir(const Dir: string): Boolean;
{ CreateDir creates a new directory. The return value is True if a new
directory was successfully created, or False if an error occurred. }
function CreateDir(const Dir: string): Boolean;
{ RemoveDir deletes an existing empty directory. The return value is
True if the directory was successfully deleted, or False if an error
occurred. }
function RemoveDir(const Dir: string): Boolean;
{ PChar routines }
{ const params help simplify C++ code. No effect on pascal code }
{ StrLen returns the number of characters in Str, not counting the null
terminator. }
function StrLen(const Str: PChar): Cardinal;
{ StrEnd returns a pointer to the null character that terminates Str. }
function StrEnd(const Str: PChar): PChar;
{ StrMove copies exactly Count characters from Source to Dest and returns
Dest. Source and Dest may overlap. }
function StrMove(Dest: PChar; const Source: PChar; Count: Cardinal): PChar;
{ StrCopy copies Source to Dest and returns Dest. }
function StrCopy(Dest: PChar; const Source: PChar): PChar;
{ StrECopy copies Source to Dest and returns StrEnd(Dest). }
function StrECopy(Dest:PChar; const Source: PChar): PChar;
{ StrLCopy copies at most MaxLen characters from Source to Dest and
returns Dest. }
function StrLCopy(Dest: PChar; const Source: PChar; MaxLen: Cardinal): PChar;
{ StrPCopy copies the Pascal style string Source into Dest and
returns Dest. }
function StrPCopy(Dest: PChar; const Source: string): PChar;
{ StrPLCopy copies at most MaxLen characters from the Pascal style string
Source into Dest and returns Dest. }
function StrPLCopy(Dest: PChar; const Source: string;
MaxLen: Cardinal): PChar;
{ StrCat appends a copy of Source to the end of Dest and returns Dest. }
function StrCat(Dest: PChar; const Source: PChar): PChar;
{ StrLCat appends at most MaxLen - StrLen(Dest) characters from Source to
the end of Dest, and returns Dest. }
function StrLCat(Dest: PChar; const Source: PChar; MaxLen: Cardinal): PChar;
{ StrComp compares Str1 to Str2. The return value is less than 0 if
Str1 < Str2, 0 if Str1 = Str2, or greater than 0 if Str1 > Str2. }
function StrComp(const Str1, Str2: PChar): Integer;
{ StrIComp compares Str1 to Str2, without case sensitivity. The return
value is the same as StrComp. }
function StrIComp(const Str1, Str2: PChar): Integer;
{ StrLComp compares Str1 to Str2, for a maximum length of MaxLen
characters. The return value is the same as StrComp. }
function StrLComp(const Str1, Str2: PChar; MaxLen: Cardinal): Integer;
{ StrLIComp compares Str1 to Str2, for a maximum length of MaxLen
characters, without case sensitivity. The return value is the same
as StrComp. }
function StrLIComp(const Str1, Str2: PChar; MaxLen: Cardinal): Integer;
{ StrScan returns a pointer to the first occurrence of Chr in Str. If Chr
does not occur in Str, StrScan returns NIL. The null terminator is
considered to be part of the string. }
function StrScan(const Str: PChar; Chr: Char): PChar;
{ StrRScan returns a pointer to the last occurrence of Chr in Str. If Chr
does not occur in Str, StrRScan returns NIL. The null terminator is
considered to be part of the string. }
function StrRScan(const Str: PChar; Chr: Char): PChar;
{ StrPos returns a pointer to the first occurrence of Str2 in Str1. If
Str2 does not occur in Str1, StrPos returns NIL. }
function StrPos(const Str1, Str2: PChar): PChar;
{ StrUpper converts Str to upper case and returns Str. }
function StrUpper(Str: PChar): PChar;
{ StrLower converts Str to lower case and returns Str. }
function StrLower(Str: PChar): PChar;
{ StrPas converts Str to a Pascal style string. This function is provided
for backwards compatibility only. To convert a null terminated string to
a Pascal style string, use a string type cast or an assignment. }
function StrPas(const Str: PChar): string;
{ StrAlloc allocates a buffer of the given size on the heap. The size of
the allocated buffer is encoded in a four byte header that immediately
preceeds the buffer. To dispose the buffer, use StrDispose. }
function StrAlloc(Size: Cardinal): PChar;
{ StrBufSize returns the allocated size of the given buffer, not including
the two byte header. }
function StrBufSize(const Str: PChar): Cardinal;
{ StrNew allocates a copy of Str on the heap. If Str is NIL, StrNew returns
NIL and doesn't allocate any heap space. Otherwise, StrNew makes a
duplicate of Str, obtaining space with a call to the StrAlloc function,
and returns a pointer to the duplicated string. To dispose the string,
use StrDispose. }
function StrNew(const Str: PChar): PChar;
{ StrDispose disposes a string that was previously allocated with StrAlloc
or StrNew. If Str is NIL, StrDispose does nothing. }
procedure StrDispose(Str: PChar);
{ String formatting routines }
{ The Format routine formats the argument list given by the Args parameter
using the format string given by the Format parameter.
Format strings contain two types of objects--plain characters and format
specifiers. Plain characters are copied verbatim to the resulting string.
Format specifiers fetch arguments from the argument list and apply
formatting to them.
Format specifiers have the following form:
"%" [index ":"] ["-"] [width] ["." prec] type
A format specifier begins with a % character. After the % come the
following, in this order:
- an optional argument index specifier, [index ":"]
- an optional left-justification indicator, ["-"]
- an optional width specifier, [width]
- an optional precision specifier, ["." prec]
- the conversion type character, type
The following conversion characters are supported:
d Decimal. The argument must be an integer value. The value is converted
to a string of decimal digits. If the format string contains a precision
specifier, it indicates that the resulting string must contain at least
the specified number of digits; if the value has less digits, the
resulting string is left-padded with zeros.
u Unsigned decimal. Similar to 'd' but no sign is output.
e Scientific. The argument must be a floating-point value. The value is
converted to a string of the form "-d.ddd...E+ddd". The resulting
string starts with a minus sign if the number is negative, and one digit
always precedes the decimal point. The total number of digits in the
resulting string (including the one before the decimal point) is given
by the precision specifer in the format string--a default precision of
15 is assumed if no precision specifer is present. The "E" exponent
character in the resulting string is always followed by a plus or minus
sign and at least three digits.
f Fixed. The argument must be a floating-point value. The value is
converted to a string of the form "-ddd.ddd...". The resulting string
starts with a minus sign if the number is negative. The number of digits
after the decimal point is given by the precision specifier in the
format string--a default of 2 decimal digits is assumed if no precision
specifier is present.
g General. The argument must be a floating-point value. The value is
converted to the shortest possible decimal string using fixed or
scientific format. The number of significant digits in the resulting
string is given by the precision specifier in the format string--a
default precision of 15 is assumed if no precision specifier is present.
Trailing zeros are removed from the resulting string, and a decimal
point appears only if necessary. The resulting string uses fixed point
format if the number of digits to the left of the decimal point in the
value is less than or equal to the specified precision, and if the
value is greater than or equal to 0.00001. Otherwise the resulting
string uses scientific format.
n Number. The argument must be a floating-point value. The value is
converted to a string of the form "-d,ddd,ddd.ddd...". The "n" format
corresponds to the "f" format, except that the resulting string
contains thousand separators.
m Money. The argument must be a floating-point value. The value is
converted to a string that represents a currency amount. The conversion
is controlled by the CurrencyString, CurrencyFormat, NegCurrFormat,
ThousandSeparator, DecimalSeparator, and CurrencyDecimals global
variables, all of which are initialized from locale settings provided
by the operating system. For example, Currency Format preferences can be
set in the International section of the Windows Control Panel. If the format
string contains a precision specifier, it overrides the value given
by the CurrencyDecimals global variable.
p Pointer. The argument must be a pointer value. The value is converted
to a string of the form "XXXX:YYYY" where XXXX and YYYY are the
segment and offset parts of the pointer expressed as four hexadecimal
digits.
s String. The argument must be a character, a string, or a PChar value.
The string or character is inserted in place of the format specifier.
The precision specifier, if present in the format string, specifies the
maximum length of the resulting string. If the argument is a string
that is longer than this maximum, the string is truncated.
x Hexadecimal. The argument must be an integer value. The value is
converted to a string of hexadecimal digits. If the format string
contains a precision specifier, it indicates that the resulting string
must contain at least the specified number of digits; if the value has
less digits, the resulting string is left-padded with zeros.
Conversion characters may be specified in upper case as well as in lower
case--both produce the same results.
For all floating-point formats, the actual characters used as decimal and
thousand separators are obtained from the DecimalSeparator and
ThousandSeparator global variables.
Index, width, and precision specifiers can be specified directly using
decimal digit string (for example "%10d"), or indirectly using an asterisk
charcater (for example "%*.*f"). When using an asterisk, the next argument
in the argument list (which must be an integer value) becomes the value
that is actually used. For example "Format('%*.*f', [8, 2, 123.456])" is
the same as "Format('%8.2f', [123.456])".
A width specifier sets the minimum field width for a conversion. If the
resulting string is shorter than the minimum field width, it is padded
with blanks to increase the field width. The default is to right-justify
the result by adding blanks in front of the value, but if the format
specifier contains a left-justification indicator (a "-" character
preceding the width specifier), the result is left-justified by adding
blanks after the value.
An index specifier sets the current argument list index to the specified
value. The index of the first argument in the argument list is 0. Using
index specifiers, it is possible to format the same argument multiple
times. For example "Format('%d %d %0:d %d', [10, 20])" produces the string
'10 20 10 20'.
The Format function can be combined with other formatting functions. For
example
S := Format('Your total was %s on %s', [
FormatFloat('$#,##0.00;;zero', Total),
FormatDateTime('mm/dd/yy', Date)]);
which uses the FormatFloat and FormatDateTime functions to customize the
format beyond what is possible with Format.
Each of the string formatting routines that uses global variables for
formatting (separators, decimals, date/time formats etc.), has an
overloaded equivalent requiring a parameter of type TFormatSettings. This
additional parameter provides the formatting information rather than the
global variables. For more information see the notes at TFormatSettings. }
function Format(const Format: string;
const Args: array of const): string; overload;
function Format(const Format: string; const Args: array of const;
const FormatSettings: TFormatSettings): string; overload;
{ FmtStr formats the argument list given by Args using the format string
given by Format into the string variable given by Result. For further
details, see the description of the Format function. }
procedure FmtStr(var Result: string; const Format: string;
const Args: array of const); overload;
procedure FmtStr(var Result: string; const Format: string;
const Args: array of const; const FormatSettings: TFormatSettings); overload;
{ StrFmt formats the argument list given by Args using the format string
given by Format into the buffer given by Buffer. It is up to the caller to
ensure that Buffer is large enough for the resulting string. The returned
value is Buffer. For further details, see the description of the Format
function. }
function StrFmt(Buffer, Format: PChar;
const Args: array of const): PChar; overload;
function StrFmt(Buffer, Format: PChar; const Args: array of const;
const FormatSettings: TFormatSettings): PChar; overload;
{ StrLFmt formats the argument list given by Args using the format string
given by Format into the buffer given by Buffer. The resulting string will
contain no more than MaxBufLen characters, not including the null terminator.
The returned value is Buffer. For further details, see the description of
the Format function. }
function StrLFmt(Buffer: PChar; MaxBufLen: Cardinal; Format: PChar;
const Args: array of const): PChar; overload;
function StrLFmt(Buffer: PChar; MaxBufLen: Cardinal; Format: PChar;
const Args: array of const;
const FormatSettings: TFormatSettings): PChar; overload;
{ FormatBuf formats the argument list given by Args using the format string
given by Format and FmtLen into the buffer given by Buffer and BufLen.
The Format parameter is a reference to a buffer containing FmtLen
characters, and the Buffer parameter is a reference to a buffer of BufLen
characters. The returned value is the number of characters actually stored
in Buffer. The returned value is always less than or equal to BufLen. For
further details, see the description of the Format function. }
function FormatBuf(var Buffer; BufLen: Cardinal; const Format;
FmtLen: Cardinal; const Args: array of const): Cardinal; overload;
function FormatBuf(var Buffer; BufLen: Cardinal; const Format;
FmtLen: Cardinal; const Args: array of const;
const FormatSettings: TFormatSettings): Cardinal; overload;
{ The WideFormat routine formats the argument list given by the Args parameter
using the format WideString given by the Format parameter. This routine is
the WideString equivalent of Format. For further details, see the description
of the Format function. }
function WideFormat(const Format: WideString;
const Args: array of const): WideString; overload;
function WideFormat(const Format: WideString;
const Args: array of const;
const FormatSettings: TFormatSettings): WideString; overload;
{ WideFmtStr formats the argument list given by Args using the format WideString
given by Format into the WideString variable given by Result. For further
details, see the description of the Format function. }
procedure WideFmtStr(var Result: WideString; const Format: WideString;
const Args: array of const); overload;
procedure WideFmtStr(var Result: WideString; const Format: WideString;
const Args: array of const; const FormatSettings: TFormatSettings); overload;
{ WideFormatBuf formats the argument list given by Args using the format string
given by Format and FmtLen into the buffer given by Buffer and BufLen.
The Format parameter is a reference to a buffer containing FmtLen
UNICODE characters (WideChar), and the Buffer parameter is a reference to a
buffer of BufLen UNICODE characters (WideChar). The return value is the number
of UNICODE characters actually stored in Buffer. The return value is always
less than or equal to BufLen. For further details, see the description of the
Format function.
Important: BufLen, FmtLen and the return result are always the number of
UNICODE characters, *not* the number of bytes. To calculate the number of bytes
multiply them by SizeOf(WideChar). }
function WideFormatBuf(var Buffer; BufLen: Cardinal; const Format;
FmtLen: Cardinal; const Args: array of const): Cardinal; overload;
function WideFormatBuf(var Buffer; BufLen: Cardinal; const Format;
FmtLen: Cardinal; const Args: array of const;
const FormatSettings: TFormatSettings): Cardinal; overload;
{ Floating point conversion routines }
{ Each of the floating point conversion routines that uses global variables
for formatting (separators, decimals, etc.), has an overloaded equivalent
requiring a parameter of type TFormatSettings. This additional parameter
provides the formatting information rather than the global variables. For
more information see the notes at TFormatSettings. }
{ FloatToStr converts the floating-point value given by Value to its string
representation. The conversion uses general number format with 15
significant digits. For further details, see the description of the
FloatToStrF function. }
function FloatToStr(Value: Extended): string; overload;
function FloatToStr(Value: Extended;
const FormatSettings: TFormatSettings): string; overload;
{ CurrToStr converts the currency value given by Value to its string
representation. The conversion uses general number format. For further
details, see the description of the CurrToStrF function. }
function CurrToStr(Value: Currency): string; overload;
function CurrToStr(Value: Currency;
const FormatSettings: TFormatSettings): string; overload;
{ FloatToCurr will range validate a value to make sure it falls
within the acceptable currency range }
const
MinCurrency: Currency = -922337203685477.5807 {$IFDEF LINUX} + 1 {$ENDIF}; //!! overflow?
MaxCurrency: Currency = 922337203685477.5807 {$IFDEF LINUX} - 1 {$ENDIF}; //!! overflow?
function FloatToCurr(const Value: Extended): Currency;
function TryFloatToCurr(const Value: Extended; out AResult: Currency): Boolean;
{ FloatToStrF converts the floating-point value given by Value to its string
representation. The Format parameter controls the format of the resulting
string. The Precision parameter specifies the precision of the given value.
It should be 7 or less for values of type Single, 15 or less for values of
type Double, and 18 or less for values of type Extended. The meaning of the
Digits parameter depends on the particular format selected.
The possible values of the Format parameter, and the meaning of each, are
described below.
ffGeneral - General number format. The value is converted to the shortest
possible decimal string using fixed or scientific format. Trailing zeros
are removed from the resulting string, and a decimal point appears only
if necessary. The resulting string uses fixed point format if the number
of digits to the left of the decimal point in the value is less than or
equal to the specified precision, and if the value is greater than or
equal to 0.00001. Otherwise the resulting string uses scientific format,
and the Digits parameter specifies the minimum number of digits in the
exponent (between 0 and 4).
ffExponent - Scientific format. The value is converted to a string of the
form "-d.ddd...E+dddd". The resulting string starts with a minus sign if
the number is negative, and one digit always precedes the decimal point.
The total number of digits in the resulting string (including the one
before the decimal point) is given by the Precision parameter. The "E"
exponent character in the resulting string is always followed by a plus
or minus sign and up to four digits. The Digits parameter specifies the
minimum number of digits in the exponent (between 0 and 4).
ffFixed - Fixed point format. The value is converted to a string of the
form "-ddd.ddd...". The resulting string starts with a minus sign if the
number is negative, and at least one digit always precedes the decimal
point. The number of digits after the decimal point is given by the Digits
parameter--it must be between 0 and 18. If the number of digits to the
left of the decimal point is greater than the specified precision, the
resulting value will use scientific format.
ffNumber - Number format. The value is converted to a string of the form
"-d,ddd,ddd.ddd...". The ffNumber format corresponds to the ffFixed format,
except that the resulting string contains thousand separators.
ffCurrency - Currency format. The value is converted to a string that
represents a currency amount. The conversion is controlled by the
CurrencyString, CurrencyFormat, NegCurrFormat, ThousandSeparator, and
DecimalSeparator global variables, all of which are initialized from
locale settings provided by the operating system. For example,
Currency Format preferences can be set in the International section
of the Windows Control Panel.
The number of digits after the decimal point is given by the Digits
parameter--it must be between 0 and 18.
For all formats, the actual characters used as decimal and thousand
separators are obtained from the DecimalSeparator and ThousandSeparator
global variables.
If the given value is a NAN (not-a-number), the resulting string is 'NAN'.
If the given value is positive infinity, the resulting string is 'INF'. If
the given value is negative infinity, the resulting string is '-INF'. }
function FloatToStrF(Value: Extended; Format: TFloatFormat;
Precision, Digits: Integer): string; overload;
function FloatToStrF(Value: Extended; Format: TFloatFormat;
Precision, Digits: Integer;
const FormatSettings: TFormatSettings): string; overload;
{ CurrToStrF converts the currency value given by Value to its string
representation. A call to CurrToStrF corresponds to a call to
FloatToStrF with an implied precision of 19 digits. }
function CurrToStrF(Value: Currency; Format: TFloatFormat;
Digits: Integer): string; overload;
function CurrToStrF(Value: Currency; Format: TFloatFormat;
Digits: Integer; const FormatSettings: TFormatSettings): string; overload;
{ FloatToText converts the given floating-point value to its decimal
representation using the specified format, precision, and digits. The
Value parameter must be a variable of type Extended or Currency, as
indicated by the ValueType parameter. The resulting string of characters
is stored in the given buffer, and the returned value is the number of
characters stored. The resulting string is not null-terminated. For
further details, see the description of the FloatToStrF function. }
function FloatToText(BufferArg: PChar; const Value; ValueType: TFloatValue;
Format: TFloatFormat; Precision, Digits: Integer): Integer; overload;
function FloatToText(BufferArg: PChar; const Value; ValueType: TFloatValue;
Format: TFloatFormat; Precision, Digits: Integer;
const FormatSettings: TFormatSettings): Integer; overload;
{ FormatFloat formats the floating-point value given by Value using the
format string given by Format. The following format specifiers are
supported in the format string:
0 Digit placeholder. If the value being formatted has a digit in the
position where the '0' appears in the format string, then that digit
is copied to the output string. Otherwise, a '0' is stored in that
position in the output string.
# Digit placeholder. If the value being formatted has a digit in the
position where the '#' appears in the format string, then that digit
is copied to the output string. Otherwise, nothing is stored in that
position in the output string.
. Decimal point. The first '.' character in the format string
determines the location of the decimal separator in the formatted
value; any additional '.' characters are ignored. The actual
character used as a the decimal separator in the output string is
determined by the DecimalSeparator global variable, which is initialized
from locale settings obtained from the operating system.
, Thousand separator. If the format string contains one or more ','
characters, the output will have thousand separators inserted between
each group of three digits to the left of the decimal point. The
placement and number of ',' characters in the format string does not
affect the output, except to indicate that thousand separators are
wanted. The actual character used as a the thousand separator in the
output is determined by the ThousandSeparator global variable, which
is initialized from locale settings obtained from the operating system.
E+ Scientific notation. If any of the strings 'E+', 'E-', 'e+', or 'e-'
E- are contained in the format string, the number is formatted using
e+ scientific notation. A group of up to four '0' characters can
e- immediately follow the 'E+', 'E-', 'e+', or 'e-' to determine the
minimum number of digits in the exponent. The 'E+' and 'e+' formats
cause a plus sign to be output for positive exponents and a minus
sign to be output for negative exponents. The 'E-' and 'e-' formats
output a sign character only for negative exponents.
'xx' Characters enclosed in single or double quotes are output as-is, and
"xx" do not affect formatting.
; Separates sections for positive, negative, and zero numbers in the
format string.
The locations of the leftmost '0' before the decimal point in the format
string and the rightmost '0' after the decimal point in the format string
determine the range of digits that are always present in the output string.
The number being formatted is always rounded to as many decimal places as
there are digit placeholders ('0' or '#') to the right of the decimal
point. If the format string contains no decimal point, the value being
formatted is rounded to the nearest whole number.
If the number being formatted has more digits to the left of the decimal
separator than there are digit placeholders to the left of the '.'
character in the format string, the extra digits are output before the
first digit placeholder.
To allow different formats for positive, negative, and zero values, the
format string can contain between one and three sections separated by
semicolons.
One section - The format string applies to all values.
Two sections - The first section applies to positive values and zeros, and
the second section applies to negative values.
Three sections - The first section applies to positive values, the second
applies to negative values, and the third applies to zeros.
If the section for negative values or the section for zero values is empty,
that is if there is nothing between the semicolons that delimit the
section, the section for positive values is used instead.
If the section for positive values is empty, or if the entire format string
is empty, the value is formatted using general floating-point formatting
with 15 significant digits, corresponding to a call to FloatToStrF with
the ffGeneral format. General floating-point formatting is also used if
the value has more than 18 digits to the left of the decimal point and
the format string does not specify scientific notation.
The table below shows some sample formats and the results produced when
the formats are applied to different values:
Format string 1234 -1234 0.5 0
-----------------------------------------------------------------------
1234 -1234 0.5 0
0 1234 -1234 1 0
0.00 1234.00 -1234.00 0.50 0.00
#.## 1234 -1234 .5
#,##0.00 1,234.00 -1,234.00 0.50 0.00
#,##0.00;(#,##0.00) 1,234.00 (1,234.00) 0.50 0.00
#,##0.00;;Zero 1,234.00 -1,234.00 0.50 Zero
0.000E+00 1.234E+03 -1.234E+03 5.000E-01 0.000E+00
#.###E-0 1.234E3 -1.234E3 5E-1 0E0
----------------------------------------------------------------------- }
function FormatFloat(const Format: string; Value: Extended): string; overload;
function FormatFloat(const Format: string; Value: Extended;
const FormatSettings: TFormatSettings): string; overload;
{ FormatCurr formats the currency value given by Value using the format
string given by Format. For further details, see the description of the
FormatFloat function. }
function FormatCurr(const Format: string; Value: Currency): string; overload;
function FormatCurr(const Format: string; Value: Currency;
const FormatSettings: TFormatSettings): string; overload;
{ FloatToTextFmt converts the given floating-point value to its decimal
representation using the specified format. The Value parameter must be a
variable of type Extended or Currency, as indicated by the ValueType
parameter. The resulting string of characters is stored in the given
buffer, and the returned value is the number of characters stored. The
resulting string is not null-terminated. For further details, see the
description of the FormatFloat function. }
function FloatToTextFmt(Buf: PChar; const Value; ValueType: TFloatValue;
Format: PChar): Integer; overload;
function FloatToTextFmt(Buf: PChar; const Value; ValueType: TFloatValue;
Format: PChar; const FormatSettings: TFormatSettings): Integer; overload;
{ StrToFloat converts the given string to a floating-point value. The string
must consist of an optional sign (+ or -), a string of digits with an
optional decimal point, and an optional 'E' or 'e' followed by a signed
integer. Leading and trailing blanks in the string are ignored. The
DecimalSeparator global variable defines the character that must be used
as a decimal point. Thousand separators and currency symbols are not
allowed in the string. If the string doesn't contain a valid value, an
EConvertError exception is raised. }
function StrToFloat(const S: string): Extended; overload;
function StrToFloat(const S: string;
const FormatSettings: TFormatSettings): Extended; overload;
function StrToFloatDef(const S: string;
const Default: Extended): Extended; overload;
function StrToFloatDef(const S: string; const Default: Extended;
const FormatSettings: TFormatSettings): Extended; overload;
function TryStrToFloat(const S: string; out Value: Extended): Boolean; overload;
function TryStrToFloat(const S: string; out Value: Extended;
const FormatSettings: TFormatSettings): Boolean; overload;
function TryStrToFloat(const S: string; out Value: Double): Boolean; overload;
function TryStrToFloat(const S: string; out Value: Double;
const FormatSettings: TFormatSettings): Boolean; overload;
function TryStrToFloat(const S: string; out Value: Single): Boolean; overload;
function TryStrToFloat(const S: string; out Value: Single;
const FormatSettings: TFormatSettings): Boolean; overload;
{ StrToCurr converts the given string to a currency value. For further
details, see the description of the StrToFloat function. }
function StrToCurr(const S: string): Currency; overload;
function StrToCurr(const S: string;
const FormatSettings: TFormatSettings): Currency; overload;
function StrToCurrDef(const S: string;
const Default: Currency): Currency; overload;
function StrToCurrDef(const S: string; const Default: Currency;
const FormatSettings: TFormatSettings): Currency; overload;
function TryStrToCurr(const S: string; out Value: Currency): Boolean; overload;
function TryStrToCurr(const S: string; out Value: Currency;
const FormatSettings: TFormatSettings): Boolean; overload;
{ TextToFloat converts the null-terminated string given by Buffer to a
floating-point value which is returned in the variable given by Value.
The Value parameter must be a variable of type Extended or Currency, as
indicated by the ValueType parameter. The return value is True if the
conversion was successful, or False if the string is not a valid
floating-point value. For further details, see the description of the
StrToFloat function. }
function TextToFloat(Buffer: PChar; var Value;
ValueType: TFloatValue): Boolean; overload;
function TextToFloat(Buffer: PChar; var Value; ValueType: TFloatValue;
const FormatSettings: TFormatSettings): Boolean; overload;
{ FloatToDecimal converts a floating-point value to a decimal representation
that is suited for further formatting. The Value parameter must be a
variable of type Extended or Currency, as indicated by the ValueType
parameter. For values of type Extended, the Precision parameter specifies
the requested number of significant digits in the result--the allowed range
is 1..18. For values of type Currency, the Precision parameter is ignored,
and the implied precision of the conversion is 19 digits. The Decimals
parameter specifies the requested maximum number of digits to the left of
the decimal point in the result. Precision and Decimals together control
how the result is rounded. To produce a result that always has a given
number of significant digits regardless of the magnitude of the number,
specify 9999 for the Decimals parameter. The result of the conversion is
stored in the specified TFloatRec record as follows:
Exponent - Contains the magnitude of the number, i.e. the number of
significant digits to the right of the decimal point. The Exponent field
is negative if the absolute value of the number is less than one. If the
number is a NAN (not-a-number), Exponent is set to -32768. If the number
is INF or -INF (positive or negative infinity), Exponent is set to 32767.
Negative - True if the number is negative, False if the number is zero
or positive.
Digits - Contains up to 18 (for type Extended) or 19 (for type Currency)
significant digits followed by a null terminator. The implied decimal
point (if any) is not stored in Digits. Trailing zeros are removed, and
if the resulting number is zero, NAN, or INF, Digits contains nothing but
the null terminator. }
procedure FloatToDecimal(var Result: TFloatRec; const Value;
ValueType: TFloatValue; Precision, Decimals: Integer);
{ Date/time support routines }
function DateTimeToTimeStamp(DateTime: TDateTime): TTimeStamp;
function TimeStampToDateTime(const TimeStamp: TTimeStamp): TDateTime;
function MSecsToTimeStamp(MSecs: Comp): TTimeStamp;
function TimeStampToMSecs(const TimeStamp: TTimeStamp): Comp;
{ EncodeDate encodes the given year, month, and day into a TDateTime value.
The year must be between 1 and 9999, the month must be between 1 and 12,
and the day must be between 1 and N, where N is the number of days in the
specified month. If the specified values are not within range, an
EConvertError exception is raised. The resulting value is the number of
days between 12/30/1899 and the given date. }
function EncodeDate(Year, Month, Day: Word): TDateTime;
{ EncodeTime encodes the given hour, minute, second, and millisecond into a
TDateTime value. The hour must be between 0 and 23, the minute must be
between 0 and 59, the second must be between 0 and 59, and the millisecond
must be between 0 and 999. If the specified values are not within range, an
EConvertError exception is raised. The resulting value is a number between
0 (inclusive) and 1 (not inclusive) that indicates the fractional part of
a day given by the specified time. The value 0 corresponds to midnight,
0.5 corresponds to noon, 0.75 corresponds to 6:00 pm, etc. }
function EncodeTime(Hour, Min, Sec, MSec: Word): TDateTime;
{ Instead of generating errors the following variations of EncodeDate and
EncodeTime simply return False if the parameters given are not valid.
Other than that, these functions are functionally the same as the above
functions. }
function TryEncodeDate(Year, Month, Day: Word; out Date: TDateTime): Boolean;
function TryEncodeTime(Hour, Min, Sec, MSec: Word; out Time: TDateTime): Boolean;
{ DecodeDate decodes the integral (date) part of the given TDateTime value
into its corresponding year, month, and day. If the given TDateTime value
is less than or equal to zero, the year, month, and day return parameters
are all set to zero. }
procedure DecodeDate(const DateTime: TDateTime; var Year, Month, Day: Word);
{ This variation of DecodeDate works similarly to the above function but
returns more information. The result value of this function indicates
whether the year decoded is a leap year or not. }
function DecodeDateFully(const DateTime: TDateTime; var Year, Month, Day,
DOW: Word): Boolean;
{$IFDEF LINUX}
function InternalDecodeDate(const DateTime: TDateTime; var Year, Month, Day, DOW: Word): Boolean;
{$ENDIF}
{ DecodeTime decodes the fractional (time) part of the given TDateTime value
into its corresponding hour, minute, second, and millisecond. }
procedure DecodeTime(const DateTime: TDateTime; var Hour, Min, Sec, MSec: Word);
{$IFDEF MSWINDOWS}
{ DateTimeToSystemTime converts a date and time from Delphi's TDateTime
format into the Win32 API's TSystemTime format. }
procedure DateTimeToSystemTime(const DateTime: TDateTime; var SystemTime: TSystemTime);
{ SystemTimeToDateTime converts a date and time from the Win32 API's
TSystemTime format into Delphi's TDateTime format. }
function SystemTimeToDateTime(const SystemTime: TSystemTime): TDateTime;
{$ENDIF}
{ DayOfWeek returns the day of the week of the given date. The result is an
integer between 1 and 7, corresponding to Sunday through Saturday.
This function is not ISO 8601 compliant, for that see the DateUtils unit. }
function DayOfWeek(const DateTime: TDateTime): Word;
{ Date returns the current date. }
function Date: TDateTime;
{ Time returns the current time. }
function Time: TDateTime;
{$IFDEF LINUX}
{ clashes with Time in <X11/Xlib.h>, use GetTime instead }
{$EXTERNALSYM Time}
{$ENDIF}
function GetTime: TDateTime;
{ Now returns the current date and time, corresponding to Date + Time. }
function Now: TDateTime;
{ Current year returns the year portion of the date returned by Now }
function CurrentYear: Word;
{ IncMonth returns Date shifted by the specified number of months.
NumberOfMonths parameter can be negative, to return a date N months ago.
If the input day of month is greater than the last day of the resulting
month, the day is set to the last day of the resulting month.
Input time of day is copied to the DateTime result. }
function IncMonth(const DateTime: TDateTime; NumberOfMonths: Integer = 1): TDateTime;
{ Optimized version of IncMonth that works with years, months and days
directly. See above comments for more detail as to what happens to the day
when incrementing months }
procedure IncAMonth(var Year, Month, Day: Word; NumberOfMonths: Integer = 1);
{ ReplaceTime replaces the time portion of the DateTime parameter with the given
time value, adjusting the signs as needed if the date is prior to 1900
(Date value less than zero) }
procedure ReplaceTime(var DateTime: TDateTime; const NewTime: TDateTime);
{ ReplaceDate replaces the date portion of the DateTime parameter with the given
date value, adjusting as needed for negative dates }
procedure ReplaceDate(var DateTime: TDateTime; const NewDate: TDateTime);
{ IsLeapYear determines whether the given year is a leap year. }
function IsLeapYear(Year: Word): Boolean;
type
PDayTable = ^TDayTable;
TDayTable = array[1..12] of Word;
{ The MonthDays array can be used to quickly find the number of
days in a month: MonthDays[IsLeapYear(Y), M] }
const
MonthDays: array [Boolean] of TDayTable =
((31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31),
(31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31));
{ Each of the date/time formatting routines that uses global variables
for formatting (separators, decimals, etc.), has an overloaded equivalent
requiring a parameter of type TFormatSettings. This additional parameter
provides the formatting information rather than the global variables. For
more information see the note at TFormatSettings. }
{ DateToStr converts the date part of the given TDateTime value to a string.
The conversion uses the format specified by the ShortDateFormat global
variable. }
function DateToStr(const DateTime: TDateTime): string; overload; inline;
function DateToStr(const DateTime: TDateTime;
const FormatSettings: TFormatSettings): string; overload; inline;
{ TimeToStr converts the time part of the given TDateTime value to a string.
The conversion uses the format specified by the LongTimeFormat global
variable. }
function TimeToStr(const DateTime: TDateTime): string; overload; inline;
function TimeToStr(const DateTime: TDateTime;
const FormatSettings: TFormatSettings): string; overload; inline;
{ DateTimeToStr converts the given date and time to a string. The resulting
string consists of a date and time formatted using the ShortDateFormat and
LongTimeFormat global variables. Time information is included in the
resulting string only if the fractional part of the given date and time
value is non-zero. }
function DateTimeToStr(const DateTime: TDateTime): string; overload; inline;
function DateTimeToStr(const DateTime: TDateTime;
const FormatSettings: TFormatSettings): string; overload; inline;
{ StrToDate converts the given string to a date value. The string must
consist of two or three numbers, separated by the character defined by
the DateSeparator global variable. The order for month, day, and year is
determined by the ShortDateFormat global variable--possible combinations
are m/d/y, d/m/y, and y/m/d. If the string contains only two numbers, it
is interpreted as a date (m/d or d/m) in the current year. Year values
between 0 and 99 are assumed to be in the current century. If the given
string does not contain a valid date, an EConvertError exception is
raised. }
function StrToDate(const S: string): TDateTime; overload;
function StrToDate(const S: string;
const FormatSettings: TFormatSettings): TDateTime; overload;
function StrToDateDef(const S: string;
const Default: TDateTime): TDateTime; overload;
function StrToDateDef(const S: string; const Default: TDateTime;
const FormatSettings: TFormatSettings): TDateTime; overload;
function TryStrToDate(const S: string; out Value: TDateTime): Boolean; overload;
function TryStrToDate(const S: string; out Value: TDateTime;
const FormatSettings: TFormatSettings): Boolean; overload;
{ StrToTime converts the given string to a time value. The string must
consist of two or three numbers, separated by the character defined by
the TimeSeparator global variable, optionally followed by an AM or PM
indicator. The numbers represent hour, minute, and (optionally) second,
in that order. If the time is followed by AM or PM, it is assumed to be
in 12-hour clock format. If no AM or PM indicator is included, the time
is assumed to be in 24-hour clock format. If the given string does not
contain a valid time, an EConvertError exception is raised. }
function StrToTime(const S: string): TDateTime; overload;
function StrToTime(const S: string;
const FormatSettings: TFormatSettings): TDateTime; overload;
function StrToTimeDef(const S: string;
const Default: TDateTime): TDateTime; overload;
function StrToTimeDef(const S: string; const Default: TDateTime;
const FormatSettings: TFormatSettings): TDateTime; overload;
function TryStrToTime(const S: string; out Value: TDateTime): Boolean; overload;
function TryStrToTime(const S: string; out Value: TDateTime;
const FormatSettings: TFormatSettings): Boolean; overload;
{ StrToDateTime converts the given string to a date and time value. The
string must contain a date optionally followed by a time. The date and
time parts of the string must follow the formats described for the
StrToDate and StrToTime functions. }
function StrToDateTime(const S: string): TDateTime; overload;
function StrToDateTime(const S: string;
const FormatSettings: TFormatSettings): TDateTime; overload;
function StrToDateTimeDef(const S: string;
const Default: TDateTime): TDateTime; overload;
function StrToDateTimeDef(const S: string; const Default: TDateTime;
const FormatSettings: TFormatSettings): TDateTime; overload;
function TryStrToDateTime(const S: string;
out Value: TDateTime): Boolean; overload;
function TryStrToDateTime(const S: string; out Value: TDateTime;
const FormatSettings: TFormatSettings): Boolean; overload;
{ FormatDateTime formats the date-and-time value given by DateTime using the
format given by Format. The following format specifiers are supported:
c Displays the date using the format given by the ShortDateFormat
global variable, followed by the time using the format given by
the LongTimeFormat global variable. The time is not displayed if
the fractional part of the DateTime value is zero.
d Displays the day as a number without a leading zero (1-31).
dd Displays the day as a number with a leading zero (01-31).
ddd Displays the day as an abbreviation (Sun-Sat) using the strings
given by the ShortDayNames global variable.
dddd Displays the day as a full name (Sunday-Saturday) using the strings
given by the LongDayNames global variable.
ddddd Displays the date using the format given by the ShortDateFormat
global variable.
dddddd Displays the date using the format given by the LongDateFormat
global variable.
g Displays the period/era as an abbreviation (Japanese and
Taiwanese locales only).
gg Displays the period/era as a full name.
e Displays the year in the current period/era as a number without
a leading zero (Japanese, Korean and Taiwanese locales only).
ee Displays the year in the current period/era as a number with
a leading zero (Japanese, Korean and Taiwanese locales only).
m Displays the month as a number without a leading zero (1-12). If
the m specifier immediately follows an h or hh specifier, the
minute rather than the month is displayed.
mm Displays the month as a number with a leading zero (01-12). If
the mm specifier immediately follows an h or hh specifier, the
minute rather than the month is displayed.
mmm Displays the month as an abbreviation (Jan-Dec) using the strings
given by the ShortMonthNames global variable.
mmmm Displays the month as a full name (January-December) using the
strings given by the LongMonthNames global variable.
yy Displays the year as a two-digit number (00-99).
yyyy Displays the year as a four-digit number (0000-9999).
h Displays the hour without a leading zero (0-23).
hh Displays the hour with a leading zero (00-23).
n Displays the minute without a leading zero (0-59).
nn Displays the minute with a leading zero (00-59).
s Displays the second without a leading zero (0-59).
ss Displays the second with a leading zero (00-59).
z Displays the millisecond without a leading zero (0-999).
zzz Displays the millisecond with a leading zero (000-999).
t Displays the time using the format given by the ShortTimeFormat
global variable.
tt Displays the time using the format given by the LongTimeFormat
global variable.
am/pm Uses the 12-hour clock for the preceding h or hh specifier, and
displays 'am' for any hour before noon, and 'pm' for any hour
after noon. The am/pm specifier can use lower, upper, or mixed
case, and the result is displayed accordingly.
a/p Uses the 12-hour clock for the preceding h or hh specifier, and
displays 'a' for any hour before noon, and 'p' for any hour after
noon. The a/p specifier can use lower, upper, or mixed case, and
the result is displayed accordingly.
ampm Uses the 12-hour clock for the preceding h or hh specifier, and
displays the contents of the TimeAMString global variable for any
hour before noon, and the contents of the TimePMString global
variable for any hour after noon.
/ Displays the date separator character given by the DateSeparator
global variable.
: Displays the time separator character given by the TimeSeparator
global variable.
'xx' Characters enclosed in single or double quotes are displayed as-is,
"xx" and do not affect formatting.
Format specifiers may be written in upper case as well as in lower case
letters--both produce the same result.
If the string given by the Format parameter is empty, the date and time
value is formatted as if a 'c' format specifier had been given.
The following example:
S := FormatDateTime('"The meeting is on" dddd, mmmm d, yyyy, ' +
'"at" hh:mm AM/PM', StrToDateTime('2/15/95 10:30am'));
assigns 'The meeting is on Wednesday, February 15, 1995 at 10:30 AM' to
the string variable S. }
function FormatDateTime(const Format: string;
DateTime: TDateTime): string; overload; inline;
function FormatDateTime(const Format: string; DateTime: TDateTime;
const FormatSettings: TFormatSettings): string; overload;
{ DateTimeToString converts the date and time value given by DateTime using
the format string given by Format into the string variable given by Result.
For further details, see the description of the FormatDateTime function. }
procedure DateTimeToString(var Result: string; const Format: string;
DateTime: TDateTime); overload;
procedure DateTimeToString(var Result: string; const Format: string;
DateTime: TDateTime; const FormatSettings: TFormatSettings); overload;
{ FloatToDateTime will range validate a value to make sure it falls
within the acceptable date range }
const
MinDateTime: TDateTime = -657434.0; { 01/01/0100 12:00:00.000 AM }
MaxDateTime: TDateTime = 2958465.99999; { 12/31/9999 11:59:59.999 PM }
function FloatToDateTime(const Value: Extended): TDateTime;
function TryFloatToDateTime(const Value: Extended; out AResult: TDateTime): Boolean;
{ System error messages }
function SysErrorMessage(ErrorCode: Integer): string;
{ Initialization file support }
function GetLocaleStr(Locale, LocaleType: Integer; const Default: string): string; platform;
function GetLocaleChar(Locale, LocaleType: Integer; Default: Char): Char; platform;
{ GetFormatSettings resets all locale-specific variables (date, time, number,
currency formats, system locale) to the values provided by the operating system. }
procedure GetFormatSettings;
{ GetLocaleFormatSettings loads locale-specific variables (date, time, number,
currency formats) with values provided by the operating system for the
specified locale (LCID). The values are stored in the FormatSettings record. }
{$IFDEF MSWINDOWS}
procedure GetLocaleFormatSettings(LCID: Integer;
var FormatSettings: TFormatSettings);
{$ENDIF}
{ Exception handling routines }
{$IFDEF LINUX}
{ InquireSignal is used to determine the state of an OS signal handler.
Pass it one of the RTL_SIG* constants, and it will return a TSignalState
which will tell you if the signal has been hooked, not hooked, or overriden
by some other module. You can use this function to determine if some other
module has hijacked your signal handlers, should you wish to reinstall your
own. This is a risky proposition under Linux, and is only recommended as a
last resort. Do not pass RTL_SIGDEFAULT to this function.
}
function InquireSignal(RtlSigNum: Integer): TSignalState;
{ AbandonSignalHandler tells the RTL to leave a signal handler
in place, even if we believe that we hooked it at startup time.
Once you have called AbandonSignalHandler with a specific signal number,
neither UnhookSignal nor the RTL will restore any previous signal handler
under any condition.
}
procedure AbandonSignalHandler(RtlSigNum: Integer);
{ HookSignal is used to hook individual signals, or an RTL-defined default
set of signals. It does not test whether a signal has already been
hooked, so it should be used in conjunction with InquireSignal. It is
exposed to enable users to hook signals in standalone libraries, or in the
event that an external module hijacks the RTL installed signal handlers.
Pass RTL_SIGDEFAULT if you want to hook all the signals that the RTL
normally hooks at startup time.
}
procedure HookSignal(RtlSigNum: Integer);
{ UnhookSignal is used to remove signal handlers installed by HookSignal.
It can remove individual signal handlers, or the RTL-defined default set
of signals. If OnlyIfHooked is True, then we will only unhook the signal
if the signal handler has been hooked, and has not since been overriden by
some foreign handler.
}
procedure UnhookSignal(RtlSigNum: Integer; OnlyIfHooked: Boolean = True);
{ HookOSExceptions is used internally by thread support. DON'T call this
function yourself. }
procedure HookOSExceptions;
{ MapSignal is used internally as well. It maps a signal and associated
context to an internal value that represents the type of Exception
class to raise. }
function MapSignal(SigNum: Integer; Context: PSigContext): LongWord;
{ SignalConverter is used internally to properly reinit the FPU and properly
raise an external OS exception object. DON'T call this function yourself. }
procedure SignalConverter(ExceptionEIP: LongWord; FaultAddr: LongWord; ErrorCode: LongWord);
{
See the comment at the threadvar declarations for these below. The access
to these has been implemented through getter/setter functions because you
cannot use threadvars across packages.
}
procedure SetSafeCallExceptionMsg(const Msg: String);
procedure SetSafeCallExceptionAddr(Addr: Pointer);
function GetSafeCallExceptionMsg: String;
function GetSafeCallExceptionAddr: Pointer;
{ HookOSExceptionsProc is used internally and cannot be used in a conventional
manner. DON'T ever set this variable. }
var
HookOSExceptionsProc: procedure = nil platform deprecated;
{ LoadLibrary / FreeLibrary are defined here only for convenience. On Linux,
they map directly to dlopen / dlclose. Note that module loading semantics
on Linux are not identical to Windows. }
function LoadLibrary(ModuleName: PChar): HMODULE;
function FreeLibrary(Module: HMODULE): LongBool;
{ GetProcAddress does what it implies. It performs the same function as the like
named function under Windows. dlsym does not quite have the same sematics as
GetProcAddress as it will return the address of a symbol in another module if
it was not found in the given HMODULE. This function will verify that the 'Proc'
is actually found within the 'Module', and if not returns nil }
function GetProcAddress(Module: HMODULE; Proc: PChar): Pointer;
{ Given a module name, this function will return the module handle. There is no
direct equivalent in Linux so this function provides that capability. Also
note, this function is specific to glibc. }
function GetModuleHandle(ModuleName: PChar): HMODULE;
{ This function works just like GetModuleHandle, except it will look for a module
that matches the given base package name. For example, given the base package
name 'package', the actual module name is, by default, 'bplpackage.so'. This
function will search for the string 'package' within the module name. }
function GetPackageModuleHandle(PackageName: PChar): HMODULE;
{$ENDIF}
{ In Linux, the parameter to sleep() is in whole seconds. In Windows, the
parameter is in milliseconds. To ease headaches, we implement a version
of sleep here for Linux that takes milliseconds and calls a Linux system
function with sub-second resolution. This maps directly to the Windows
API on Windows. }
procedure Sleep(milliseconds: Cardinal);{$IFDEF MSWINDOWS} stdcall; {$ENDIF}
{$IFDEF MSWINDOWS}
(*$EXTERNALSYM Sleep*)
{$ENDIF}
function GetModuleName(Module: HMODULE): string;
function ExceptionErrorMessage(ExceptObject: TObject; ExceptAddr: Pointer;
Buffer: PChar; Size: Integer): Integer;
procedure ShowException(ExceptObject: TObject; ExceptAddr: Pointer);
procedure Abort;
procedure OutOfMemoryError;
procedure Beep;
{ MBCS functions }
{ LeadBytes is a char set that indicates which char values are lead bytes
in multibyte character sets (Japanese, Chinese, etc).
This set is always empty for western locales. }
var
LeadBytes: set of Char = [];
(*$EXTERNALSYM LeadBytes*)
(*$HPPEMIT 'namespace Sysutils {'*)
(*$HPPEMIT 'extern PACKAGE System::Set<Byte, 0, 255> LeadBytes;'*)
(*$HPPEMIT '} // namespace Sysutils'*)
{ ByteType indicates what kind of byte exists at the Index'th byte in S.
Western locales always return mbSingleByte. Far East multibyte locales
may also return mbLeadByte, indicating the byte is the first in a multibyte
character sequence, and mbTrailByte, indicating that the byte is one of
a sequence of bytes following a lead byte. One or more trail bytes can
follow a lead byte, depending on locale charset encoding and OS platform.
Parameters are assumed to be valid. }
function ByteType(const S: string; Index: Integer): TMbcsByteType;
{ StrByteType works the same as ByteType, but on null-terminated PChar strings }
function StrByteType(Str: PChar; Index: Cardinal): TMbcsByteType;
{ ByteToCharLen returns the character length of a MBCS string, scanning the
string for up to MaxLen bytes. In multibyte character sets, the number of
characters in a string may be less than the number of bytes. }
function ByteToCharLen(const S: string; MaxLen: Integer): Integer;
{ CharToByteLen returns the byte length of a MBCS string, scanning the string
for up to MaxLen characters. }
function CharToByteLen(const S: string; MaxLen: Integer): Integer;
{ ByteToCharIndex returns the 1-based character index of the Index'th byte in
a MBCS string. Returns zero if Index is out of range:
(Index <= 0) or (Index > Length(S)) }
function ByteToCharIndex(const S: string; Index: Integer): Integer;
{ CharToByteIndex returns the 1-based byte index of the Index'th character
in a MBCS string. Returns zero if Index or Result are out of range:
(Index <= 0) or (Index > Length(S)) or (Result would be > Length(S)) }
function CharToByteIndex(const S: string; Index: Integer): Integer;
{ StrCharLength returns the number of bytes required by the first character
in Str. In Windows, multibyte characters can be up to two bytes in length.
In Linux, multibyte characters can be up to six bytes in length (UTF-8). }
function StrCharLength(const Str: PChar): Integer;
{ StrNextChar returns a pointer to the first byte of the character following
the character pointed to by Str. }
function StrNextChar(const Str: PChar): PChar;
{ CharLength returns the number of bytes required by the character starting
at bytes S[Index]. }
function CharLength(const S: String; Index: Integer): Integer;
{ NextCharIndex returns the byte index of the first byte of the character
following the character starting at S[Index]. }
function NextCharIndex(const S: String; Index: Integer): Integer;
{ IsPathDelimiter returns True if the character at byte S[Index]
is a PathDelimiter ('\' or '/'), and it is not a MBCS lead or trail byte. }
function IsPathDelimiter(const S: string; Index: Integer): Boolean;
{ IsDelimiter returns True if the character at byte S[Index] matches any
character in the Delimiters string, and the character is not a MBCS lead or
trail byte. S may contain multibyte characters; Delimiters must contain
only single byte characters. }
function IsDelimiter(const Delimiters, S: string; Index: Integer): Boolean;
{ IncludeTrailingPathDelimiter returns the path with a PathDelimiter
('/' or '\') at the end. This function is MBCS enabled. }
function IncludeTrailingPathDelimiter(const S: string): string;
{ IncludeTrailingBackslash is the old name for IncludeTrailingPathDelimiter. }
function IncludeTrailingBackslash(const S: string): string; platform; inline;
{ ExcludeTrailingPathDelimiter returns the path without a PathDelimiter
('\' or '/') at the end. This function is MBCS enabled. }
function ExcludeTrailingPathDelimiter(const S: string): string;
{ ExcludeTrailingBackslash is the old name for ExcludeTrailingPathDelimiter. }
function ExcludeTrailingBackslash(const S: string): string; platform; inline;
{ LastDelimiter returns the byte index in S of the rightmost whole
character that matches any character in Delimiters (except null (#0)).
S may contain multibyte characters; Delimiters must contain only single
byte non-null characters.
Example: LastDelimiter('\.:', 'c:\filename.ext') returns 12. }
function LastDelimiter(const Delimiters, S: string): Integer;
{ AnsiCompareFileName supports DOS file name comparison idiosyncracies
in Far East locales (Zenkaku) on Windows.
In non-MBCS locales on Windows, AnsiCompareFileName is identical to
AnsiCompareText (case insensitive).
On Linux, AnsiCompareFileName is identical to AnsiCompareStr (case sensitive).
For general purpose file name comparisions, you should use this function
instead of AnsiCompareText. }
function AnsiCompareFileName(const S1, S2: string): Integer; inline;
function SameFileName(const S1, S2: string): Boolean; inline;
{ AnsiLowerCaseFileName supports lowercase conversion idiosyncracies of
DOS file names in Far East locales (Zenkaku). In non-MBCS locales,
AnsiLowerCaseFileName is identical to AnsiLowerCase. }
function AnsiLowerCaseFileName(const S: string): string;
{ AnsiUpperCaseFileName supports uppercase conversion idiosyncracies of
DOS file names in Far East locales (Zenkaku). In non-MBCS locales,
AnsiUpperCaseFileName is identical to AnsiUpperCase. }
function AnsiUpperCaseFileName(const S: string): string;
{ AnsiPos: Same as Pos but supports MBCS strings }
function AnsiPos(const Substr, S: string): Integer;
{ AnsiStrPos: Same as StrPos but supports MBCS strings }
function AnsiStrPos(Str, SubStr: PChar): PChar;
{ AnsiStrRScan: Same as StrRScan but supports MBCS strings }
function AnsiStrRScan(Str: PChar; Chr: Char): PChar;
{ AnsiStrScan: Same as StrScan but supports MBCS strings }
function AnsiStrScan(Str: PChar; Chr: Char): PChar;
{ StringReplace replaces occurances of <oldpattern> with <newpattern> in a
given string. Assumes the string may contain Multibyte characters }
type
TReplaceFlags = set of (rfReplaceAll, rfIgnoreCase);
function StringReplace(const S, OldPattern, NewPattern: string;
Flags: TReplaceFlags): string;
{ WrapText will scan a string for BreakChars and insert the BreakStr at the
last BreakChar position before MaxCol. Will not insert a break into an
embedded quoted string (both ''' and '"' supported) }
function WrapText(const Line, BreakStr: string; const BreakChars: TSysCharSet;
MaxCol: Integer): string; overload;
function WrapText(const Line: string; MaxCol: Integer = 45): string; overload;
{ FindCmdLineSwitch determines whether the string in the Switch parameter
was passed as a command line argument to the application. SwitchChars
identifies valid argument-delimiter characters (i.e., "-" and "/" are
common delimiters). The IgnoreCase paramter controls whether a
case-sensistive or case-insensitive search is performed. }
const
SwitchChars = {$IFDEF MSWINDOWS} ['/','-']; {$ENDIF}
{$IFDEF LINUX} ['-']; {$ENDIF}
function FindCmdLineSwitch(const Switch: string; const Chars: TSysCharSet;
IgnoreCase: Boolean): Boolean; overload;
{ These versions of FindCmdLineSwitch are convenient for writing portable
code. The characters that are valid to indicate command line switches vary
on different platforms. For example, '/' cannot be used as a switch char
on Linux because '/' is the path delimiter. }
{ This version uses SwitchChars defined above, and IgnoreCase False. }
function FindCmdLineSwitch(const Switch: string): Boolean; overload;
{ This version uses SwitchChars defined above. }
function FindCmdLineSwitch(const Switch: string; IgnoreCase: Boolean): Boolean; overload;
{ FreeAndNil frees the given TObject instance and sets the variable reference
to nil. Be careful to only pass TObjects to this routine. }
procedure FreeAndNil(var Obj);
{ Interface support routines }
function Supports(const Instance: IInterface; const IID: TGUID; out Intf): Boolean; overload;
function Supports(const Instance: TObject; const IID: TGUID; out Intf): Boolean; overload;
function Supports(const Instance: IInterface; const IID: TGUID): Boolean; overload;
function Supports(const Instance: TObject; const IID: TGUID): Boolean; overload;
function Supports(const AClass: TClass; const IID: TGUID): Boolean; overload;
function CreateGUID(out Guid: TGUID): HResult;
{$IFDEF MSWINDOWS}
stdcall;
{$ENDIF}
function StringToGUID(const S: string): TGUID;
function GUIDToString(const GUID: TGUID): string;
function IsEqualGUID(const guid1, guid2: TGUID): Boolean;
{$IFDEF MSWINDOWS}
stdcall; {$EXTERNALSYM IsEqualGUID}
{$ENDIF}
{ Package support routines }
{ Package Info flags }
const
pfNeverBuild = $00000001;
pfDesignOnly = $00000002;
pfRunOnly = $00000004;
pfIgnoreDupUnits = $00000008;
pfModuleTypeMask = $C0000000;
pfExeModule = $00000000;
pfPackageModule = $40000000;
pfProducerMask = $0C000000;
pfV3Produced = $00000000;
pfProducerUndefined = $04000000;
pfBCB4Produced = $08000000;
pfDelphi4Produced = $0C000000;
pfLibraryModule = $80000000;
{ Unit info flags }
const
ufMainUnit = $01;
ufPackageUnit = $02;
ufWeakUnit = $04;
ufOrgWeakUnit = $08;
ufImplicitUnit = $10;
ufWeakPackageUnit = ufPackageUnit or ufWeakUnit;
{$IFDEF LINUX}
var
PkgLoadingMode: Integer = RTLD_LAZY;
{$ENDIF}
{ Procedure type of the callback given to GetPackageInfo. Name is the actual
name of the package element. If IsUnit is True then Name is the name of
a contained unit; a required package if False. Param is the value passed
to GetPackageInfo }
type
TNameType = (ntContainsUnit, ntRequiresPackage, ntDcpBpiName);
TPackageInfoProc = procedure (const Name: string; NameType: TNameType; Flags: Byte; Param: Pointer);
{ LoadPackage loads a given package DLL, checks for duplicate units and
calls the initialization blocks of all the contained units }
function LoadPackage(const Name: string): HMODULE;
{ UnloadPackage does the opposite of LoadPackage by calling the finalization
blocks of all contained units, then unloading the package DLL }
procedure UnloadPackage(Module: HMODULE);
{ GetPackageInfo accesses the given package's info table and enumerates
all the contained units and required packages }
procedure GetPackageInfo(Module: HMODULE; Param: Pointer; var Flags: Integer;
InfoProc: TPackageInfoProc);
{ GetPackageDescription loads the description resource from the package
library. If the description resource does not exist,
an empty string is returned. }
function GetPackageDescription(ModuleName: PChar): string;
{ InitializePackage validates and initializes the given package DLL }
procedure InitializePackage(Module: HMODULE);
{ FinalizePackage finalizes the given package DLL }
procedure FinalizePackage(Module: HMODULE);
{ RaiseLastOSError calls GetLastError to retrieve the code for
the last occuring error in a call to an OS or system library function.
If GetLastError returns an error code, RaiseLastOSError raises
an EOSError exception with the error code and a system-provided
message associated with with error. }
procedure RaiseLastOSError; overload;
procedure RaiseLastOSError(LastError: Integer); overload;
{$IFDEF MSWINDOWS}
procedure RaiseLastWin32Error; deprecated; // use RaiseLastOSError
{ Win32Check is used to check the return value of a Win32 API function }
{ which returns a BOOL to indicate success. If the Win32 API function }
{ returns False (indicating failure), Win32Check calls RaiseLastOSError }
{ to raise an exception. If the Win32 API function returns True, }
{ Win32Check returns True. }
function Win32Check(RetVal: BOOL): BOOL; platform;
{$ENDIF}
{ Termination procedure support }
type
TTerminateProc = function: Boolean;
{ Call AddTerminateProc to add a terminate procedure to the system list of }
{ termination procedures. Delphi will call all of the function in the }
{ termination procedure list before an application terminates. The user- }
{ defined TermProc function should return True if the application can }
{ safely terminate or False if the application cannot safely terminate. }
{ If one of the functions in the termination procedure list returns False, }
{ the application will not terminate. }
procedure AddTerminateProc(TermProc: TTerminateProc);
{ CallTerminateProcs is called by VCL when an application is about to }
{ terminate. It returns True only if all of the functions in the }
{ system's terminate procedure list return True. This function is }
{ intended only to be called by Delphi, and it should not be called }
{ directly. }
function CallTerminateProcs: Boolean;
function GDAL: LongWord;
procedure RCS;
procedure RPR;
{ HexDisplayPrefix contains the prefix to display on hexadecimal
values - '$' for Pascal syntax, '0x' for C++ syntax. This is
for display only - this does not affect the string-to-integer
conversion routines. }
var
HexDisplayPrefix: string = '$';
{$IFDEF MSWINDOWS}
{ The GetDiskFreeSpace Win32 API does not support partitions larger than 2GB
under Win95. A new Win32 function, GetDiskFreeSpaceEx, supports partitions
larger than 2GB but only exists on Win NT 4.0 and Win95 OSR2.
The GetDiskFreeSpaceEx function pointer variable below will be initialized
at startup to point to either the actual OS API function if it exists on
the system, or to an internal Delphi function if it does not. When running
on Win95 pre-OSR2, the output of this function will still be limited to
the 2GB range reported by Win95, but at least you don't have to worry
about which API function to call in code you write. }
var
GetDiskFreeSpaceEx: function (Directory: PChar; var FreeAvailable,
TotalSpace: TLargeInteger; TotalFree: PLargeInteger): Bool stdcall = nil;
{ SafeLoadLibrary calls LoadLibrary, disabling normal Win32 error message
popup dialogs if the requested file can't be loaded. SafeLoadLibrary also
preserves the current FPU control word (precision, exception masks) across
the LoadLibrary call (in case the DLL you're loading hammers the FPU control
word in its initialization, as many MS DLLs do)}
function SafeLoadLibrary(const FileName: string;
ErrorMode: UINT = SEM_NOOPENFILEERRORBOX): HMODULE;
{$ENDIF}
{$IFDEF LINUX}
{ SafeLoadLibrary calls LoadLibrary preserves the current FPU control
word (precision, exception masks) across the LoadLibrary call (in
case the shared object you're loading hammers the FPU control
word in its initialization, as many MS DLLs do) }
function SafeLoadLibrary(const FileName: string;
Dummy: LongWord = 0): HMODULE;
{$ENDIF}
{ Thread synchronization }
{ IReadWriteSync is an abstract interface for general read/write synchronization.
Some implementations may allow simultaneous readers, but writers always have
exclusive locks.
Worst case is that this class behaves identical to a TRTLCriticalSection -
that is, read and write locks block all other threads. }
type
IReadWriteSync = interface
['{7B108C52-1D8F-4CDB-9CDF-57E071193D3F}']
procedure BeginRead;
procedure EndRead;
function BeginWrite: Boolean;
procedure EndWrite;
end;
TSimpleRWSync = class(TInterfacedObject, IReadWriteSync)
private
FLock: TRTLCriticalSection;
public
constructor Create;
destructor Destroy; override;
procedure BeginRead;
procedure EndRead;
function BeginWrite: Boolean;
procedure EndWrite;
end;
{ TThreadLocalCounter
This class implements a lightweight non-blocking thread local storage
mechanism specifically built for tracking per-thread recursion counts
in TMultiReadExclusiveWriteSynchronizer. This class is intended for
Delphi RTL internal use only. In the future it may be generalized
and "hardened" for general application use, but until then leave it alone.
Rules of Use:
The tls object must be opened to gain access to the thread-specific data
structure. If a threadinfo block does not exist for the current thread,
Open will allocate one. Every call to Open must be matched with a call
to Close. The pointer returned by Open is invalid after the matching call
to Close (or Delete).
The thread info structure is unique to each thread. Once you have it, it's
yours. You don't need to guard against concurrent access to the thread
data by multiple threads - your thread is the only thread that will ever
have access to the structure that Open returns. The thread info structure
is allocated and owned by the tls object. If you put allocated pointers
in the thread info make sure you free them before you delete the threadinfo
node.
When thread data is no longer needed, call the Delete method on the pointer.
This must be done between calls to Open and Close. You should not use the
thread data after calling Delete.
Important: Do not keep the tls object open for long periods of time.
In particular, be careful not to wait on a thread synchronization event or
critical section while you have the tls object open. It's much better to
open and close the tls object before and after the blocking event than to
leave the tls object open while waiting.
Implementation Notes:
The main purpose of this storage class is to provide thread-local storage
without using limited / problematic OS tls slots and without requiring
expensive blocking thread synchronization. This class performs no
blocking waits or spin loops! (except for memory allocation)
Thread info is kept in linked lists to facilitate non-blocking threading
techniques. A hash table indexed by a hash of the current thread ID
reduces linear search times.
When a node is deleted, its thread ID is stripped and its Active field is
set to zero, meaning it is available to be recycled for other threads.
Nodes are never removed from the live list or freed while the class is in
use. All nodes are freed when the class is destroyed.
Nodes are only inserted at the front of the list (each list in the hash table).
The linked list management relies heavily on InterlockedExchange to perform
atomic node pointer replacements. There are brief windows of time where
the linked list may be circular while a two-step insertion takes place.
During that brief window, other threads traversing the lists may see
the same node more than once more than once. (pun!) This is fine for what this
implementation needs. Don't do anything silly like try to count the
nodes during a traversal.
}
type
PThreadInfo = ^TThreadInfo;
TThreadInfo = record
Next: PThreadInfo;
ThreadID: Cardinal;
Active: Integer;
RecursionCount: Cardinal;
end;
TThreadLocalCounter = class
private
FHashTable: array [0..15] of PThreadInfo;
function HashIndex: Byte;
function Recycle: PThreadInfo;
public
destructor Destroy; override;
procedure Open(var Thread: PThreadInfo);
procedure Delete(var Thread: PThreadInfo);
procedure Close(var Thread: PThreadInfo);
end;
{$IFDEF MSWINDOWS}
{ TMultiReadExclusiveWriteSynchronizer minimizes thread serialization to gain
read access to a resource shared among threads while still providing complete
exclusivity to callers needing write access to the shared resource.
(multithread shared reads, single thread exclusive write)
Read locks are allowed while owning a write lock.
Read locks can be promoted to write locks within the same thread.
(BeginRead, BeginWrite, EndWrite, EndRead)
Note: Other threads have an opportunity to modify the protected resource
when you call BeginWrite before you are granted the write lock, even
if you already have a read lock open. Best policy is not to retain
any info about the protected resource (such as count or size) across a
write lock. Always reacquire samples of the protected resource after
acquiring or releasing a write lock.
The function result of BeginWrite indicates whether another thread got
the write lock while the current thread was waiting for the write lock.
Return value of True means that the write lock was acquired without
any intervening modifications by other threads. Return value of False
means another thread got the write lock while you were waiting, so the
resource protected by the MREWS object should be considered modified.
Any samples of the protected resource should be discarded.
In general, it's better to just always reacquire samples of the protected
resource after obtaining a write lock. The boolean result of BeginWrite
and the RevisionLevel property help cases where reacquiring the samples
is computationally expensive or time consuming.
RevisionLevel changes each time a write lock is granted. You can test
RevisionLevel for equality with a previously sampled value of the property
to determine if a write lock has been granted, implying that the protected
resource may be changed from its state when the original RevisionLevel
value was sampled. Do not rely on the sequentiality of the current
RevisionLevel implementation (it will wrap around to zero when it tops out).
Do not perform greater than / less than comparisons on RevisionLevel values.
RevisionLevel indicates only the stability of the protected resource since
your original sample. It should not be used to calculate how many
revisions have been made.
}
type
TMultiReadExclusiveWriteSynchronizer = class(TInterfacedObject, IReadWriteSync)
private
FSentinel: Integer;
FReadSignal: THandle;
FWriteSignal: THandle;
FWaitRecycle: Cardinal;
FWriteRecursionCount: Cardinal;
tls: TThreadLocalCounter;
FWriterID: Cardinal;
FRevisionLevel: Cardinal;
procedure BlockReaders;
procedure UnblockReaders;
procedure UnblockOneWriter;
procedure WaitForReadSignal;
procedure WaitForWriteSignal;
{$IFDEF DEBUG_MREWS}
procedure Debug(const Msg: string);
{$ENDIF}
public
constructor Create;
destructor Destroy; override;
procedure BeginRead;
procedure EndRead;
function BeginWrite: Boolean;
procedure EndWrite;
property RevisionLevel: Cardinal read FRevisionLevel;
end;
{$ELSE}
type
TMultiReadExclusiveWriteSynchronizer = TSimpleRWSync;
{$ENDIF}
type
TMREWSync = TMultiReadExclusiveWriteSynchronizer; // short form
function GetEnvironmentVariable(const Name: string): string; overload;
{$IFDEF LINUX}
function InterlockedIncrement(var I: Integer): Integer;
function InterlockedDecrement(var I: Integer): Integer;
function InterlockedExchange(var A: Integer; B: Integer): Integer;
function InterlockedExchangeAdd(var A: Integer; B: Integer): Integer;
{$ENDIF}
implementation
{$IFDEF LINUX}
{
Exceptions raised in methods that are safecall will be filtered
through the virtual method SafeCallException on the class. The
implementation of this method under Linux has the option of setting
the following thread vars: SafeCallExceptionMsg, SafeCallExceptionAddr.
If these are set, then the implementation of SafeCallError here will
reraise a generic exception based on these. One might consider actually
having the SafeCallException implementation store off the exception
object itself, but this raises the issue that the exception object
might have to live a long time (if an external application calls a
Delphi safecall method). Since an arbitrary exception object could
be holding large resources hostage, we hold only the string and
address as a hedge.
}
threadvar
SafeCallExceptionMsg: String;
SafeCallExceptionAddr: Pointer;
procedure SetSafeCallExceptionMsg(const Msg: String);
begin
SafeCallExceptionMsg := Msg;
end;
procedure SetSafeCallExceptionAddr(Addr: Pointer);
begin
SafeCallExceptionAddr := Addr;
end;
function GetSafeCallExceptionMsg: String;
begin
Result := SafeCallExceptionMsg;
end;
function GetSafeCallExceptionAddr: Pointer;
begin
Result := SafeCallExceptionAddr;
end;
{$ENDIF}
{ Utility routines }
procedure DivMod(Dividend: Integer; Divisor: Word;
var Result, Remainder: Word);
asm
PUSH EBX
MOV EBX,EDX
MOV EDX,EAX
SHR EDX,16
DIV BX
MOV EBX,Remainder
MOV [ECX],AX
MOV [EBX],DX
POP EBX
end;
{$IFDEF PIC}
function GetGOT: Pointer; export;
begin
asm
MOV Result,EBX
end;
end;
{$ENDIF}
procedure ConvertError(const ResString: string); local;
begin
raise EConvertError.Create(ResString);
end;
procedure ConvertErrorFmt(const ResString: string; const Args: array of const); local;
begin
raise EConvertError.CreateFmt(ResString, Args);
end;
{$IFDEF MSWINDOWS}
{$EXTERNALSYM CoCreateGuid}
function CoCreateGuid(out guid: TGUID): HResult; stdcall; external 'ole32.dll' name 'CoCreateGuid';
function CreateGUID(out Guid: TGUID): HResult;
begin
Result := CoCreateGuid(Guid);
end;
//function CreateGUID; external 'ole32.dll' name 'CoCreateGuid';
{$ENDIF}
{$IFDEF LINUX}
{ CreateGUID }
{ libuuid.so implements the tricky code to create GUIDs using the
MAC address of the network adapter plus other flavor bits.
libuuid.so is currently distributed with the ext2 file system
package, but does not depend upon the ext2 file system libraries.
Ideally, libuuid.so should be distributed separately.
If you do not have libuuid.so.1 on your Linux distribution, you
can extract the library from the e2fsprogs RPM.
Note: Do not use the generic uuid_generate function in libuuid.so.
In the current implementation (e2fsprogs-1.19), uuid_generate
gives preference to generating guids entirely from random number
streams over generating guids based on the NIC MAC address.
No matter how "random" a random number generator is, it will
never produce guids that can be guaranteed unique across all
systems on the planet. MAC-address based guids are guaranteed
unique because the MAC address of the NIC is guaranteed unique
by the manufacturer.
For this reason, we call uuid_generate_time instead of the
generic uuid_generate. uuid_generate_time constructs the guid
using the MAC address, and falls back to randomness if no NIC
can be found. }
var
libuuidHandle: Pointer;
uuid_generate_time: procedure (out Guid: TGUID) cdecl;
function CreateGUID(out Guid: TGUID): HResult;
const
E_NOTIMPL = HRESULT($80004001);
begin
Result := E_NOTIMPL;
if libuuidHandle = nil then
begin
libuuidHandle := dlopen('libuuid.so.1', RTLD_LAZY);
if libuuidHandle = nil then Exit;
uuid_generate_time := dlsym(libuuidHandle, 'uuid_generate_time');
if @uuid_generate_time = nil then Exit;
end;
uuid_generate_time(Guid);
Result := 0;
end;
{$ENDIF}
{$IFDEF MSWINDOWS}
function StringFromCLSID(const clsid: TGUID; out psz: PWideChar): HResult; stdcall;
external 'ole32.dll' name 'StringFromCLSID';
procedure CoTaskMemFree(pv: Pointer); stdcall;
external 'ole32.dll' name 'CoTaskMemFree';
function CLSIDFromString(psz: PWideChar; out clsid: TGUID): HResult; stdcall;
external 'ole32.dll' name 'CLSIDFromString';
{$ENDIF MSWINDOWS}
function StringToGUID(const S: string): TGUID;
{$IFDEF MSWINDOWS}
begin
if not Succeeded(CLSIDFromString(PWideChar(WideString(S)), Result)) then
ConvertErrorFmt(SInvalidGUID, [s]);
end;
{$ENDIF}
{$IFDEF LINUX}
procedure InvalidGUID;
begin
ConvertErrorFmt(@SInvalidGUID, [s]);
end;
function HexChar(c: Char): Byte;
begin
case c of
'0'..'9': Result := Byte(c) - Byte('0');
'a'..'f': Result := (Byte(c) - Byte('a')) + 10;
'A'..'F': Result := (Byte(c) - Byte('A')) + 10;
else
InvalidGUID;
Result := 0;
end;
end;
function HexByte(p: PChar): Char;
begin
Result := Char((HexChar(p[0]) shl 4) + HexChar(p[1]));
end;
var
i: Integer;
src, dest: PChar;
begin
if ((Length(S) <> 38) or (s[1] <> '{')) then InvalidGUID;
dest := @Result;
src := PChar(s);
Inc(src);
for i := 0 to 3 do
dest[i] := HexByte(src+(3-i)*2);
Inc(src, 8);
Inc(dest, 4);
if src[0] <> '-' then InvalidGUID;
Inc(src);
for i := 0 to 1 do
begin
dest^ := HexByte(src+2);
Inc(dest);
dest^ := HexByte(src);
Inc(dest);
Inc(src, 4);
if src[0] <> '-' then InvalidGUID;
inc(src);
end;
dest^ := HexByte(src);
Inc(dest);
Inc(src, 2);
dest^ := HexByte(src);
Inc(dest);
Inc(src, 2);
if src[0] <> '-' then InvalidGUID;
Inc(src);
for i := 0 to 5 do
begin
dest^ := HexByte(src);
Inc(dest);
Inc(src, 2);
end;
end;
{$ENDIF LINUX}
{$IFDEF MSWINDOWS}
function GUIDToString(const GUID: TGUID): string;
var
P: PWideChar;
begin
if not Succeeded(StringFromCLSID(GUID, P)) then
ConvertError(SInvalidGUID);
Result := P;
CoTaskMemFree(P);
end;
{$ENDIF}
{$IFDEF LINUX}
function GUIDToString(const GUID: TGUID): string;
begin
SetLength(Result, 38);
StrLFmt(PChar(Result), 38,'{%.8x-%.4x-%.4x-%.2x%.2x-%.2x%.2x%.2x%.2x%.2x%.2x}', // do not localize
[GUID.D1, GUID.D2, GUID.D3, GUID.D4[0], GUID.D4[1], GUID.D4[2], GUID.D4[3],
GUID.D4[4], GUID.D4[5], GUID.D4[6], GUID.D4[7]]);
end;
{$ENDIF}
{$IFDEF MSWINDOWS}
function IsEqualGUID; external 'ole32.dll' name 'IsEqualGUID';
{$ENDIF MSWINDOWS}
{$IFDEF LINUX}
function IsEqualGUID(const guid1, guid2: TGUID): Boolean;
var
a, b: PIntegerArray;
begin
a := PIntegerArray(@guid1);
b := PIntegerArray(@guid2);
Result := (a^[0] = b^[0]) and (a^[1] = b^[1]) and (a^[2] = b^[2]) and (a^[3] = b^[3]);
end;
{$ENDIF LINUX}
{ Memory management routines }
function AllocMem(Size: Cardinal): Pointer;
begin
GetMem(Result, Size);
FillChar(Result^, Size, 0);
end;
{ Exit procedure handling }
type
PExitProcInfo = ^TExitProcInfo;
TExitProcInfo = record
Next: PExitProcInfo;
SaveExit: Pointer;
Proc: TProcedure;
end;
var
ExitProcList: PExitProcInfo = nil;
procedure DoExitProc;
var
P: PExitProcInfo;
Proc: TProcedure;
begin
P := ExitProcList;
ExitProcList := P^.Next;
ExitProc := P^.SaveExit;
Proc := P^.Proc;
Dispose(P);
Proc;
end;
procedure AddExitProc(Proc: TProcedure);
var
P: PExitProcInfo;
begin
New(P);
P^.Next := ExitProcList;
P^.SaveExit := ExitProc;
P^.Proc := Proc;
ExitProcList := P;
ExitProc := @DoExitProc;
end;
{ String handling routines }
function NewStr(const S: string): PString;
begin
if S = '' then Result := NullStr else
begin
New(Result);
Result^ := S;
end;
end;
procedure DisposeStr(P: PString);
begin
if (P <> nil) and (P^ <> '') then Dispose(P);
end;
procedure AssignStr(var P: PString; const S: string);
var
Temp: PString;
begin
Temp := P;
P := NewStr(S);
DisposeStr(Temp);
end;
procedure AppendStr(var Dest: string; const S: string);
begin
Dest := Dest + S;
end;
function UpperCase(const S: string): string;
var
Ch: Char;
L: Integer;
Source, Dest: PChar;
begin
L := Length(S);
SetLength(Result, L);
Source := Pointer(S);
Dest := Pointer(Result);
while L <> 0 do
begin
Ch := Source^;
if (Ch >= 'a') and (Ch <= 'z') then Dec(Ch, 32);
Dest^ := Ch;
Inc(Source);
Inc(Dest);
Dec(L);
end;
end;
function UpperCase(const S: string; LocaleOptions: TLocaleOptions): string;
begin
if LocaleOptions = loUserLocale then
Result := AnsiUpperCase(S)
else
Result := UpperCase(S);
end;
function LowerCase(const S: string): string;
var
Ch: Char;
L: Integer;
Source, Dest: PChar;
begin
L := Length(S);
SetLength(Result, L);
Source := Pointer(S);
Dest := Pointer(Result);
while L <> 0 do
begin
Ch := Source^;
if (Ch >= 'A') and (Ch <= 'Z') then Inc(Ch, 32);
Dest^ := Ch;
Inc(Source);
Inc(Dest);
Dec(L);
end;
end;
function LowerCase(const S: string; LocaleOptions: TLocaleOptions): string;
begin
if LocaleOptions = loUserLocale then
Result := AnsiLowerCase(S)
else
Result := LowerCase(S);
end;
function CompareStr(const S1, S2: string): Integer; assembler;
asm
PUSH ESI
PUSH EDI
MOV ESI,EAX
MOV EDI,EDX
OR EAX,EAX
JE @@1
MOV EAX,[EAX-4]
@@1: OR EDX,EDX
JE @@2
MOV EDX,[EDX-4]
@@2: MOV ECX,EAX
CMP ECX,EDX
JBE @@3
MOV ECX,EDX
@@3: CMP ECX,ECX
REPE CMPSB
JE @@4
MOVZX EAX,BYTE PTR [ESI-1]
MOVZX EDX,BYTE PTR [EDI-1]
@@4: SUB EAX,EDX
POP EDI
POP ESI
end;
function CompareStr(const S1, S2: string; LocaleOptions: TLocaleOptions): Integer;
begin
if LocaleOptions = loUserLocale then
Result := AnsiCompareStr(S1, S2)
else
Result := CompareStr(S1, S2);
end;
function SameStr(const S1, S2: string): Boolean;
asm
CMP EAX,EDX
JZ @1
OR EAX,EAX
JZ @2
OR EDX,EDX
JZ @3
MOV ECX,[EAX-4]
CMP ECX,[EDX-4]
JNE @3
CALL CompareStr
TEST EAX,EAX
JNZ @3
@1: MOV AL,1
@2: RET
@3: XOR EAX,EAX
end;
function SameStr(const S1, S2: string; LocaleOptions: TLocaleOptions): Boolean;
begin
if LocaleOptions = loUserLocale then
Result := AnsiSameStr(S1, S2)
else
Result := SameStr(S1, S2);
end;
function CompareMem(P1, P2: Pointer; Length: Integer): Boolean; assembler;
asm
PUSH ESI
PUSH EDI
MOV ESI,P1
MOV EDI,P2
MOV EDX,ECX
XOR EAX,EAX
AND EDX,3
SAR ECX,2
JS @@1 // Negative Length implies identity.
REPE CMPSD
JNE @@2
MOV ECX,EDX
REPE CMPSB
JNE @@2
@@1: INC EAX
@@2: POP EDI
POP ESI
end;
(* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1
*
* The implementation of function CompareText is subject to the
* Mozilla Public License Version 1.1 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Fastcode
*
* The Initial Developer of the Original Code is
* Fastcode
*
* Portions created by the Initial Developer are Copyright (C) 2002-2004
* the Initial Developer. All Rights Reserved.
*
* Contributor(s): John O'Harrow
*
* ***** END LICENSE BLOCK ***** *)
function CompareText(const S1, S2: string): Integer;
asm
TEST EAX, EAX
JNZ @@CheckS2
TEST EDX, EDX
JZ @@Ret
MOV EAX, [EDX-4]
NEG EAX
@@Ret:
RET
@@CheckS2:
TEST EDX, EDX
JNZ @@Compare
MOV EAX, [EAX-4]
RET
@@Compare:
PUSH EBX
PUSH EBP
PUSH ESI
MOV EBP, [EAX-4] // length(S1)
MOV EBX, [EDX-4] // length(S2)
SUB EBP, EBX // Result if All Compared Characters Match
SBB ECX, ECX
AND ECX, EBP
ADD ECX, EBX // min(length(S1),length(S2)) = Compare Length
LEA ESI, [EAX+ECX] // Last Compare Position in S1
ADD EDX, ECX // Last Compare Position in S2
NEG ECX
JZ @@SetResult // Exit if Smallest Length = 0
@@Loop: // Load Next 2 Chars from S1 and S2
// May Include Null Terminator}
MOVZX EAX, WORD PTR [ESI+ECX]
MOVZX EBX, WORD PTR [EDX+ECX]
CMP EAX, EBX
JE @@Next // Next 2 Chars Match
CMP AL, BL
JE @@SecondPair // First Char Matches
MOV AH, 0
MOV BH, 0
CMP AL, 'a'
JL @@UC1
CMP AL, 'z'
JG @@UC1
SUB EAX, 'a'-'A'
@@UC1:
CMP BL, 'a'
JL @@UC2
CMP BL, 'z'
JG @@UC2
SUB EBX, 'a'-'A'
@@UC2:
SUB EAX, EBX // Compare Both Uppercase Chars
JNE @@Done // Exit with Result in EAX if Not Equal
MOVZX EAX, WORD PTR [ESI+ECX] // Reload Same 2 Chars from S1
MOVZX EBX, WORD PTR [EDX+ECX] // Reload Same 2 Chars from S2
CMP AH, BH
JE @@Next // Second Char Matches
@@SecondPair:
SHR EAX, 8
SHR EBX, 8
CMP AL, 'a'
JL @@UC3
CMP AL, 'z'
JG @@UC3
SUB EAX, 'a'-'A'
@@UC3:
CMP BL, 'a'
JL @@UC4
CMP BL, 'z'
JG @@UC4
SUB EBX, 'a'-'A'
@@UC4:
SUB EAX, EBX // Compare Both Uppercase Chars
JNE @@Done // Exit with Result in EAX if Not Equal
@@Next:
ADD ECX, 2
JL @@Loop // Loop until All required Chars Compared
@@SetResult:
MOV EAX, EBP // All Matched, Set Result from Lengths
@@Done:
POP ESI
POP EBP
POP EBX
end;
function CompareText(const S1, S2: string; LocaleOptions: TLocaleOptions): Integer;
begin
if LocaleOptions = loUserLocale then
Result := AnsiCompareText(S1, S2)
else
Result := CompareText(S1, S2);
end;
function SameText(const S1, S2: string): Boolean; assembler;
asm
CMP EAX,EDX
JZ @1
OR EAX,EAX
JZ @2
OR EDX,EDX
JZ @3
MOV ECX,[EAX-4]
CMP ECX,[EDX-4]
JNE @3
CALL CompareText
TEST EAX,EAX
JNZ @3
@1: MOV AL,1
@2: RET
@3: XOR EAX,EAX
end;
function SameText(const S1, S2: string; LocaleOptions: TLocaleOptions): Boolean;
begin
if LocaleOptions = loUserLocale then
Result := AnsiSameText(S1, S2)
else
Result := SameText(S1, S2);
end;
function AnsiUpperCase(const S: string): string;
{$IFDEF MSWINDOWS}
var
Len: Integer;
begin
Len := Length(S);
SetString(Result, PChar(S), Len);
if Len > 0 then CharUpperBuff(Pointer(Result), Len);
end;
{$ENDIF}
{$IFDEF LINUX}
begin
Result := WideUpperCase(S);
end;
{$ENDIF}
function AnsiLowerCase(const S: string): string;
{$IFDEF MSWINDOWS}
var
Len: Integer;
begin
Len := Length(S);
SetString(Result, PChar(S), Len);
if Len > 0 then CharLowerBuff(Pointer(Result), Len);
end;
{$ENDIF}
{$IFDEF LINUX}
begin
Result := WideLowerCase(S);
end;
{$ENDIF}
function AnsiCompareStr(const S1, S2: string): Integer;
begin
{$IFDEF MSWINDOWS}
Result := CompareString(LOCALE_USER_DEFAULT, 0, PChar(S1), Length(S1),
PChar(S2), Length(S2)) - 2;
{$ENDIF}
{$IFDEF LINUX}
// glibc 2.1.2 / 2.1.3 implementations of strcoll() and strxfrm()
// have severe capacity limits. Comparing two 100k strings may
// exhaust the stack and kill the process.
// Fixed in glibc 2.1.91 and later.
Result := strcoll(PChar(S1), PChar(S2));
{$ENDIF}
end;
function AnsiSameStr(const S1, S2: string): Boolean;
begin
Result := AnsiCompareStr(S1, S2) = 0;
end;
function AnsiCompareText(const S1, S2: string): Integer;
begin
{$IFDEF MSWINDOWS}
Result := CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE, PChar(S1),
Length(S1), PChar(S2), Length(S2)) - 2;
{$ENDIF}
{$IFDEF LINUX}
Result := WideCompareText(S1, S2);
{$ENDIF}
end;
function AnsiSameText(const S1, S2: string): Boolean;
begin
Result := AnsiCompareText(S1, S2) = 0;
end;
function AnsiStrComp(S1, S2: PChar): Integer;
begin
{$IFDEF MSWINDOWS}
Result := CompareString(LOCALE_USER_DEFAULT, 0, S1, -1, S2, -1) - 2;
{$ENDIF}
{$IFDEF LINUX}
Result := strcoll(S1, S2);
{$ENDIF}
end;
function AnsiStrIComp(S1, S2: PChar): Integer;
begin
{$IFDEF MSWINDOWS}
Result := CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE, S1, -1,
S2, -1) - 2;
{$ENDIF}
{$IFDEF LINUX}
Result := AnsiCompareText(S1, S2);
{$ENDIF}
end;
// StrLenLimit: Scan Src for a null terminator up to MaxLen bytes
function StrLenLimit(Src: PChar; MaxLen: Cardinal): Cardinal;
begin
if Src = nil then
begin
Result := 0;
Exit;
end;
Result := MaxLen;
while (Src^ <> #0) and (Result > 0) do
begin
Inc(Src);
Dec(Result);
end;
Result := MaxLen - Result;
end;
{ StrBufLimit: Return a pointer to a buffer that contains no more than MaxLen
bytes of Src, avoiding heap allocation if possible.
If clipped Src length is less than MaxLen, return Src. Allocated = False.
If clipped Src length is less than StaticBufLen, return StaticBuf with a
copy of Src. Allocated = False.
Otherwise, return a heap allocated buffer with a copy of Src. Allocated = True.
}
function StrBufLimit(Src: PChar; MaxLen: Cardinal; StaticBuf: PChar;
StaticBufLen: Cardinal; var Allocated: Boolean): PChar;
var
Len: Cardinal;
begin
Len := StrLenLimit(Src, MaxLen);
Allocated := False;
if Len < MaxLen then
Result := Src
else
begin
if Len < StaticBufLen then
Result := StaticBuf
else
begin
GetMem(Result, Len+1);
Allocated := True;
end;
Move(Src^, Result^, Len);
Result[Len] := #0;
end;
end;
function InternalAnsiStrLComp(S1, S2: PChar; MaxLen: Cardinal; CaseSensitive: Boolean): Integer;
var
Buf1, Buf2: array [0..4095] of Char;
P1, P2: PChar;
Allocated1, Allocated2: Boolean;
begin
// glibc has no length-limited strcoll!
P1 := nil;
P2 := nil;
Allocated1 := False;
Allocated2 := False;
try
P1 := StrBufLimit(S1, MaxLen, Buf1, High(Buf1), Allocated1);
P2 := StrBufLimit(S2, MaxLen, Buf2, High(Buf2), Allocated2);
if CaseSensitive then
Result := AnsiStrComp(P1, P2)
else
Result := AnsiStrIComp(P1, P2);
finally
if Allocated1 then
FreeMem(P1);
if Allocated2 then
FreeMem(P2);
end;
end;
function AnsiStrLComp(S1, S2: PChar; MaxLen: Cardinal): Integer;
{$IFDEF MSWINDOWS}
begin
Result := CompareString(LOCALE_USER_DEFAULT, 0,
S1, MaxLen, S2, MaxLen) - 2;
end;
{$ENDIF}
{$IFDEF LINUX}
begin
Result := InternalAnsiStrLComp(S1, S2, MaxLen, True);
end;
{$ENDIF}
function AnsiStrLIComp(S1, S2: PChar; MaxLen: Cardinal): Integer;
begin
{$IFDEF MSWINDOWS}
Result := CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE,
S1, MaxLen, S2, MaxLen) - 2;
{$ENDIF}
{$IFDEF LINUX}
Result := InternalAnsiStrLComp(S1, S2, MaxLen, False);
{$ENDIF}
end;
function AnsiStrLower(Str: PChar): PChar;
{$IFDEF MSWINDOWS}
begin
CharLower(Str);
Result := Str;
end;
{$ENDIF}
{$IFDEF LINUX}
var
Temp: WideString;
Squish: AnsiString;
I: Integer;
begin
Temp := Str; // expand and copy multibyte to widechar
for I := 1 to Length(Temp) do
Temp[I] := WideChar(towlower(UCS4Char(Temp[I])));
Squish := Temp; // reduce and copy widechar to multibyte
if Cardinal(Length(Squish)) > StrLen(Str) then
raise ERangeError.CreateRes(@SRangeError);
Move(Squish[1], Str^, Length(Squish));
Result := Str;
end;
{$ENDIF}
function AnsiStrUpper(Str: PChar): PChar;
{$IFDEF MSWINDOWS}
begin
CharUpper(Str);
Result := Str;
end;
{$ENDIF}
{$IFDEF LINUX}
var
Temp: WideString;
Squish: AnsiString;
I: Integer;
begin
Temp := Str; // expand and copy multibyte to widechar
for I := 1 to Length(Temp) do
Temp[I] := WideChar(towupper(UCS4Char(Temp[I])));
Squish := Temp; // reduce and copy widechar to multibyte
if Cardinal(Length(Squish)) > StrLen(Str) then
raise ERangeError.CreateRes(@SRangeError);
Move(Squish[1], Str^, Length(Squish));
Result := Str;
end;
{$ENDIF}
function WideUpperCase(const S: WideString): WideString;
{$IFDEF MSWINDOWS}
var
Len: Integer;
begin
Len := Length(S);
SetString(Result, PWideChar(S), Len);
if Len > 0 then CharUpperBuffW(Pointer(Result), Len);
end;
{$ENDIF}
{$IFDEF LINUX}
var
I: Integer;
P: PWideChar;
begin
SetLength(Result, Length(S));
P := @Result[1];
for I := 1 to Length(S) do
P[I-1] := WideChar(towupper(UCS4Char(S[I])));
end;
{$ENDIF}
function WideLowerCase(const S: WideString): WideString;
{$IFDEF MSWINDOWS}
var
Len: Integer;
begin
Len := Length(S);
SetString(Result, PWideChar(S), Len);
if Len > 0 then CharLowerBuffW(Pointer(Result), Len);
end;
{$ENDIF}
{$IFDEF LINUX}
var
I: Integer;
P: PWideChar;
begin
SetLength(Result, Length(S));
P := @Result[1];
for I := 1 to Length(S) do
P[I-1] := WideChar(towlower(UCS4Char(S[I])));
end;
{$ENDIF}
{$IFDEF MSWINDOWS}
function DumbItDownFor95(const S1, S2: WideString; CmpFlags: Integer): Integer;
var
a1, a2: AnsiString;
begin
a1 := s1;
a2 := s2;
Result := CompareStringA(LOCALE_USER_DEFAULT, CmpFlags, PChar(a1), Length(a1),
PChar(a2), Length(a2)) - 2;
end;
{$ENDIF}
function WideCompareStr(const S1, S2: WideString): Integer;
{$IFDEF MSWINDOWS}
begin
SetLastError(0);
Result := CompareStringW(LOCALE_USER_DEFAULT, 0, PWideChar(S1), Length(S1),
PWideChar(S2), Length(S2)) - 2;
case GetLastError of
0: ;
ERROR_CALL_NOT_IMPLEMENTED: Result := DumbItDownFor95(S1, S2, 0);
else
RaiseLastOSError;
end;
end;
{$ENDIF}
{$IFDEF LINUX}
var
UCS4_S1, UCS4_S2: UCS4String;
begin
UCS4_S1 := WideStringToUCS4String(S1);
UCS4_S2 := WideStringToUCS4String(S2);
// glibc 2.1.2 / 2.1.3 implementations of wcscoll() and wcsxfrm()
// have severe capacity limits. Comparing two 100k strings may
// exhaust the stack and kill the process.
// Fixed in glibc 2.1.91 and later.
SetLastError(0);
Result := wcscoll(PUCS4Chars(UCS4_S1), PUCS4Chars(UCS4_S2));
if GetLastError <> 0 then
RaiseLastOSError;
end;
{$ENDIF}
function WideSameStr(const S1, S2: WideString): Boolean;
begin
Result := WideCompareStr(S1, S2) = 0;
end;
function WideCompareText(const S1, S2: WideString): Integer;
begin
{$IFDEF MSWINDOWS}
SetLastError(0);
Result := CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, PWideChar(S1),
Length(S1), PWideChar(S2), Length(S2)) - 2;
case GetLastError of
0: ;
ERROR_CALL_NOT_IMPLEMENTED: Result := DumbItDownFor95(S1, S2, NORM_IGNORECASE);
else
RaiseLastOSError;
end;
{$ENDIF}
{$IFDEF LINUX}
Result := WideCompareStr(WideUpperCase(S1), WideUpperCase(S2));
{$ENDIF}
end;
function WideSameText(const S1, S2: WideString): Boolean;
begin
Result := WideCompareText(S1, S2) = 0;
end;
function Trim(const S: string): string;
var
I, L: Integer;
begin
L := Length(S);
I := 1;
while (I <= L) and (S[I] <= ' ') do Inc(I);
if I > L then Result := '' else
begin
while S[L] <= ' ' do Dec(L);
Result := Copy(S, I, L - I + 1);
end;
end;
function Trim(const S: WideString): WideString;
var
I, L: Integer;
begin
L := Length(S);
I := 1;
while (I <= L) and (S[I] <= ' ') do Inc(I);
if I > L then
Result := ''
else
begin
while S[L] <= ' ' do Dec(L);
Result := Copy(S, I, L - I + 1);
end;
end;
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, Maxint);
end;
function TrimLeft(const S: WideString): WideString;
var
I, L: Integer;
begin
L := Length(S);
I := 1;
while (I <= L) and (S[I] <= ' ') do Inc(I);
Result := Copy(S, I, Maxint);
end;
function TrimRight(const S: string): string;
var
I: Integer;
begin
I := Length(S);
while (I > 0) and (S[I] <= ' ') do Dec(I);
Result := Copy(S, 1, I);
end;
function TrimRight(const S: WideString): WideString;
var
I: Integer;
begin
I := Length(S);
while (I > 0) and (S[I] <= ' ') do Dec(I);
Result := Copy(S, 1, I);
end;
function QuotedStr(const S: string): string;
var
I: Integer;
begin
Result := S;
for I := Length(Result) downto 1 do
if Result[I] = '''' then Insert('''', Result, I);
Result := '''' + Result + '''';
end;
function AnsiQuotedStr(const S: string; Quote: Char): string;
var
P, Src, Dest: PChar;
AddCount: Integer;
begin
AddCount := 0;
P := AnsiStrScan(PChar(S), Quote);
while P <> nil do
begin
Inc(P);
Inc(AddCount);
P := AnsiStrScan(P, Quote);
end;
if AddCount = 0 then
begin
Result := Quote + S + Quote;
Exit;
end;
SetLength(Result, Length(S) + AddCount + 2);
Dest := Pointer(Result);
Dest^ := Quote;
Inc(Dest);
Src := Pointer(S);
P := AnsiStrScan(Src, Quote);
repeat
Inc(P);
Move(Src^, Dest^, P - Src);
Inc(Dest, P - Src);
Dest^ := Quote;
Inc(Dest);
Src := P;
P := AnsiStrScan(Src, Quote);
until P = nil;
P := StrEnd(Src);
Move(Src^, Dest^, P - Src);
Inc(Dest, P - Src);
Dest^ := Quote;
end;
function AnsiExtractQuotedStr(var Src: PChar; Quote: Char): string;
var
P, Dest: PChar;
DropCount: Integer;
begin
Result := '';
if (Src = nil) or (Src^ <> Quote) then Exit;
Inc(Src);
DropCount := 1;
P := Src;
Src := AnsiStrScan(Src, Quote);
while Src <> nil do // count adjacent pairs of quote chars
begin
Inc(Src);
if Src^ <> Quote then Break;
Inc(Src);
Inc(DropCount);
Src := AnsiStrScan(Src, Quote);
end;
if Src = nil then Src := StrEnd(P);
if ((Src - P) <= 1) then Exit;
if DropCount = 1 then
SetString(Result, P, Src - P - 1)
else
begin
SetLength(Result, Src - P - DropCount);
Dest := PChar(Result);
Src := AnsiStrScan(P, Quote);
while Src <> nil do
begin
Inc(Src);
if Src^ <> Quote then Break;
Move(P^, Dest^, Src - P);
Inc(Dest, Src - P);
Inc(Src);
P := Src;
Src := AnsiStrScan(Src, Quote);
end;
if Src = nil then Src := StrEnd(P);
Move(P^, Dest^, Src - P - 1);
end;
end;
function AnsiDequotedStr(const S: string; AQuote: Char): string;
var
LText: PChar;
begin
LText := PChar(S);
Result := AnsiExtractQuotedStr(LText, AQuote);
if Result = '' then
Result := S;
end;
function AdjustLineBreaks(const S: string; Style: TTextLineBreakStyle): string;
var
Source, SourceEnd, Dest: PChar;
DestLen: Integer;
L: Integer;
begin
Source := Pointer(S);
SourceEnd := Source + Length(S);
DestLen := Length(S);
while Source < SourceEnd do
begin
case Source^ of
#10:
if Style = tlbsCRLF then
Inc(DestLen);
#13:
if Style = tlbsCRLF then
if Source[1] = #10 then
Inc(Source)
else
Inc(DestLen)
else
if Source[1] = #10 then
Dec(DestLen);
else
if Source^ in LeadBytes then
begin
Source := StrNextChar(Source);
continue;
end;
end;
Inc(Source);
end;
if DestLen = Length(Source) then
Result := S
else
begin
Source := Pointer(S);
SetString(Result, nil, DestLen);
Dest := Pointer(Result);
while Source < SourceEnd do
case Source^ of
#10:
begin
if Style = tlbsCRLF then
begin
Dest^ := #13;
Inc(Dest);
end;
Dest^ := #10;
Inc(Dest);
Inc(Source);
end;
#13:
begin
if Style = tlbsCRLF then
begin
Dest^ := #13;
Inc(Dest);
end;
Dest^ := #10;
Inc(Dest);
Inc(Source);
if Source^ = #10 then Inc(Source);
end;
else
if Source^ in LeadBytes then
begin
L := StrCharLength(Source);
Move(Source^, Dest^, L);
Inc(Dest, L);
Inc(Source, L);
continue;
end;
Dest^ := Source^;
Inc(Dest);
Inc(Source);
end;
end;
end;
function IsValidIdent(const Ident: string; AllowDots: Boolean): Boolean;
const
Alpha = ['A'..'Z', 'a'..'z', '_'];
AlphaNumeric = Alpha + ['0'..'9'];
AlphaNumericDot = AlphaNumeric + ['.'];
var
I: Integer;
begin
Result := False;
if (Length(Ident) = 0) or not (Ident[1] in Alpha) then Exit;
if AllowDots then
for I := 2 to Length(Ident) do
begin
if not (Ident[I] in AlphaNumericDot) then Exit
end
else
for I := 2 to Length(Ident) do if not (Ident[I] in AlphaNumeric) then Exit;
Result := True;
end;
procedure CvtInt;
{ IN:
EAX: The integer value to be converted to text
ESI: Ptr to the right-hand side of the output buffer: LEA ESI, StrBuf[16]
ECX: Base for conversion: 0 for signed decimal, 10 or 16 for unsigned
EDX: Precision: zero padded minimum field width
OUT:
ESI: Ptr to start of converted text (not start of buffer)
ECX: Length of converted text
}
asm
OR CL,CL
JNZ @CvtLoop
@C1: OR EAX,EAX
JNS @C2
NEG EAX
CALL @C2
MOV AL,'-'
INC ECX
DEC ESI
MOV [ESI],AL
RET
@C2: MOV ECX,10
@CvtLoop:
PUSH EDX
PUSH ESI
@D1: XOR EDX,EDX
DIV ECX
DEC ESI
ADD DL,'0'
CMP DL,'0'+10
JB @D2
ADD DL,('A'-'0')-10
@D2: MOV [ESI],DL
OR EAX,EAX
JNE @D1
POP ECX
POP EDX
SUB ECX,ESI
SUB EDX,ECX
JBE @D5
ADD ECX,EDX
MOV AL,'0'
SUB ESI,EDX
JMP @z
@zloop: MOV [ESI+EDX],AL
@z: DEC EDX
JNZ @zloop
MOV [ESI],AL
@D5:
end;
procedure CvtIntW;
{ IN:
EAX: The integer value to be converted to text
ESI: Ptr to the right-hand side of the widechar output buffer: LEA ESI, WStrBuf[32]
ECX: Base for conversion: 0 for signed decimal, 10 or 16 for unsigned
EDX: Precision: zero padded minimum field width
OUT:
ESI: Ptr to start of converted widechar text (not start of buffer)
ECX: Character length of converted text
}
asm
OR CL,CL
JNZ @CvtLoop
@C1: OR EAX,EAX
JNS @C2
NEG EAX
CALL @C2
MOV AX,'-'
MOV [ESI-2],AX
SUB ESI, 2
INC ECX
RET
@C2: MOV ECX,10
@CvtLoop:
PUSH EDX
PUSH ESI
@D1: XOR EDX,EDX
DIV ECX
ADD DX,'0'
SUB ESI,2
CMP DX,'0'+10
JB @D2
ADD DX,('A'-'0')-10
@D2: MOV [ESI],DX
OR EAX,EAX
JNE @D1
POP ECX
POP EDX
SUB ECX,ESI
SHR ECX, 1
SUB EDX,ECX
JBE @D5
ADD ECX,EDX
SUB ESI,EDX
MOV AX,'0'
SUB ESI,EDX
JMP @z
@zloop: MOV [ESI+EDX*2],AX
@z: DEC EDX
JNZ @zloop
MOV [ESI],AX
@D5:
end;
function IntToStr(Value: Integer): string;
// FmtStr(Result, '%d', [Value]);
asm
PUSH ESI
MOV ESI, ESP
SUB ESP, 16
XOR ECX, ECX // base: 0 for signed decimal
PUSH EDX // result ptr
XOR EDX, EDX // zero filled field width: 0 for no leading zeros
CALL CvtInt
MOV EDX, ESI
POP EAX // result ptr
CALL System.@LStrFromPCharLen
ADD ESP, 16
POP ESI
end;
procedure CvtInt64W;
{ IN:
EAX: Address of the int64 value to be converted to text
ESI: Ptr to the right-hand side of the widechar output buffer: LEA ESI, WStrBuf[32]
ECX: Base for conversion: 10 or 16
EDX: Precision: zero padded minimum field width
OUT:
ESI: Ptr to start of converted widechar text (not start of buffer)
ECX: Character length of converted text
}
asm
OR CL, CL
JNZ @start
MOV ECX, 10
TEST [EAX + 4], $80000000
JZ @start
PUSH [EAX + 4]
PUSH [EAX]
MOV EAX, ESP
NEG [ESP] // negate the value
ADC [ESP + 4],0
NEG [ESP + 4]
CALL @start
INC ECX
MOV [ESI-2].Word, '-'
SUB ESI, 2
ADD ESP, 8
JMP @done
@start:
PUSH ESI
SUB ESP, 4
FNSTCW [ESP+2].Word // save
FNSTCW [ESP].Word // scratch
OR [ESP].Word, $0F00 // trunc toward zero, full precision
FLDCW [ESP].Word
MOV [ESP].Word, CX
FLD1
TEST [EAX + 4], $80000000 // test for negative
JZ @ld1 // FPU doesn't understand unsigned ints
PUSH [EAX + 4] // copy value before modifying
PUSH [EAX]
AND [ESP + 4], $7FFFFFFF // clear the sign bit
PUSH $7FFFFFFF
PUSH $FFFFFFFF
FILD [ESP + 8].QWord // load value
FILD [ESP].QWord
FADD ST(0), ST(2) // Add 1. Produces unsigned $80000000 in ST(0)
FADDP ST(1), ST(0) // Add $80000000 to value to replace the sign bit
ADD ESP, 16
JMP @ld2
@ld1:
FILD [EAX].QWord // value
@ld2:
FILD [ESP].Word // base
FLD ST(1)
@loop:
SUB ESI, 2
FPREM // accumulator mod base
FISTP [ESI].Word
FDIV ST(1), ST(0) // accumulator := acumulator / base
MOV AX, [ESI].Word // overlap long division op with int ops
ADD AX, '0'
CMP AX, '0'+10
JB @store
ADD AX, ('A'-'0')-10
@store:
MOV [ESI].Word, AX
FLD ST(1) // copy accumulator
FCOM ST(3) // if accumulator >= 1.0 then loop
FSTSW AX
SAHF
JAE @loop
FLDCW [ESP+2].Word
ADD ESP,4
FFREE ST(3)
FFREE ST(2)
FFREE ST(1);
FFREE ST(0);
@zeropad:
POP ECX // original ESI
SUB ECX,ESI
SHR ECX, 1 // ECX = char length of converted string
OR EDX,EDX
JS @done
SUB EDX,ECX
JBE @done // output longer than field width = no pad
SUB ESI,EDX
MOV AX,'0'
SUB ESI,EDX
ADD ECX,EDX
JMP @z
@zloop: MOV [ESI+EDX*2].Word,AX
@z: DEC EDX
JNZ @zloop
MOV [ESI].Word,AX
@done:
end;
procedure CvtInt64;
{ IN:
EAX: Address of the int64 value to be converted to text
ESI: Ptr to the right-hand side of the output buffer: LEA ESI, StrBuf[32]
ECX: Base for conversion: 0 for signed decimal, or 10 or 16 for unsigned
EDX: Precision: zero padded minimum field width
OUT:
ESI: Ptr to start of converted text (not start of buffer)
ECX: Byte length of converted text
}
asm
OR CL, CL
JNZ @start // CL = 0 => signed integer conversion
MOV ECX, 10
TEST [EAX + 4], $80000000
JZ @start
PUSH [EAX + 4]
PUSH [EAX]
MOV EAX, ESP
NEG [ESP] // negate the value
ADC [ESP + 4],0
NEG [ESP + 4]
CALL @start // perform unsigned conversion
MOV [ESI-1].Byte, '-' // tack on the negative sign
DEC ESI
INC ECX
ADD ESP, 8
RET
@start: // perform unsigned conversion
PUSH ESI
SUB ESP, 4
FNSTCW [ESP+2].Word // save
FNSTCW [ESP].Word // scratch
OR [ESP].Word, $0F00 // trunc toward zero, full precision
FLDCW [ESP].Word
MOV [ESP].Word, CX
FLD1
TEST [EAX + 4], $80000000 // test for negative
JZ @ld1 // FPU doesn't understand unsigned ints
PUSH [EAX + 4] // copy value before modifying
PUSH [EAX]
AND [ESP + 4], $7FFFFFFF // clear the sign bit
PUSH $7FFFFFFF
PUSH $FFFFFFFF
FILD [ESP + 8].QWord // load value
FILD [ESP].QWord
FADD ST(0), ST(2) // Add 1. Produces unsigned $80000000 in ST(0)
FADDP ST(1), ST(0) // Add $80000000 to value to replace the sign bit
ADD ESP, 16
JMP @ld2
@ld1:
FILD [EAX].QWord // value
@ld2:
FILD [ESP].Word // base
FLD ST(1)
@loop:
DEC ESI
FPREM // accumulator mod base
FISTP [ESP].Word
FDIV ST(1), ST(0) // accumulator := acumulator / base
MOV AL, [ESP].Byte // overlap long FPU division op with int ops
ADD AL, '0'
CMP AL, '0'+10
JB @store
ADD AL, ('A'-'0')-10
@store:
MOV [ESI].Byte, AL
FLD ST(1) // copy accumulator
FCOM ST(3) // if accumulator >= 1.0 then loop
FSTSW AX
SAHF
JAE @loop
FLDCW [ESP+2].Word
ADD ESP,4
FFREE ST(3)
FFREE ST(2)
FFREE ST(1);
FFREE ST(0);
POP ECX // original ESI
SUB ECX, ESI // ECX = length of converted string
SUB EDX,ECX
JBE @done // output longer than field width = no pad
SUB ESI,EDX
MOV AL,'0'
ADD ECX,EDX
JMP @z
@zloop: MOV [ESI+EDX].Byte,AL
@z: DEC EDX
JNZ @zloop
MOV [ESI].Byte,AL
@done:
end;
function IntToStr(Value: Int64): string;
// FmtStr(Result, '%d', [Value]);
asm
PUSH ESI
MOV ESI, ESP
SUB ESP, 32 // 32 chars
XOR ECX, ECX // base 10 signed
PUSH EAX // result ptr
XOR EDX, EDX // zero filled field width: 0 for no leading zeros
LEA EAX, Value;
CALL CvtInt64
MOV EDX, ESI
POP EAX // result ptr
CALL System.@LStrFromPCharLen
ADD ESP, 32
POP ESI
end;
function IntToHex(Value: Integer; Digits: Integer): string;
// FmtStr(Result, '%.*x', [Digits, Value]);
asm
CMP EDX, 32 // Digits < buffer length?
JBE @A1
XOR EDX, EDX
@A1: PUSH ESI
MOV ESI, ESP
SUB ESP, 32
PUSH ECX // result ptr
MOV ECX, 16 // base 16 EDX = Digits = field width
CALL CvtInt
MOV EDX, ESI
POP EAX // result ptr
CALL System.@LStrFromPCharLen
ADD ESP, 32
POP ESI
end;
function IntToHex(Value: Int64; Digits: Integer): string;
// FmtStr(Result, '%.*x', [Digits, Value]);
asm
CMP EAX, 32 // Digits < buffer length?
JLE @A1
XOR EAX, EAX
@A1: PUSH ESI
MOV ESI, ESP
SUB ESP, 32 // 32 chars
MOV ECX, 16 // base 16
PUSH EDX // result ptr
MOV EDX, EAX // zero filled field width: 0 for no leading zeros
LEA EAX, Value;
CALL CvtInt64
MOV EDX, ESI
POP EAX // result ptr
CALL System.@LStrFromPCharLen
ADD ESP, 32
POP ESI
end;
function StrToInt(const S: string): Integer;
var
E: Integer;
begin
Val(S, Result, E);
if E <> 0 then ConvertErrorFmt(SInvalidInteger, [S]);
end;
function StrToIntDef(const S: string; Default: Integer): Integer;
var
E: Integer;
begin
Val(S, Result, E);
if E <> 0 then Result := Default;
end;
function TryStrToInt(const S: string; out Value: Integer): Boolean;
var
E: Integer;
begin
Val(S, Value, E);
Result := E = 0;
end;
function StrToInt64(const S: string): Int64;
var
E: Integer;
begin
Val(S, Result, E);
if E <> 0 then ConvertErrorFmt(SInvalidInteger, [S]);
end;
function StrToInt64Def(const S: string; const Default: Int64): Int64;
var
E: Integer;
begin
Val(S, Result, E);
if E <> 0 then Result := Default;
end;
function TryStrToInt64(const S: string; out Value: Int64): Boolean;
var
E: Integer;
begin
Val(S, Value, E);
Result := E = 0;
end;
procedure VerifyBoolStrArray;
begin
if Length(TrueBoolStrs) = 0 then
begin
SetLength(TrueBoolStrs, 1);
TrueBoolStrs[0] := DefaultTrueBoolStr;
end;
if Length(FalseBoolStrs) = 0 then
begin
SetLength(FalseBoolStrs, 1);
FalseBoolStrs[0] := DefaultFalseBoolStr;
end;
end;
function StrToBool(const S: string): Boolean;
begin
if not TryStrToBool(S, Result) then
ConvertErrorFmt(SInvalidBoolean, [S]);
end;
function StrToBoolDef(const S: string; const Default: Boolean): Boolean;
begin
if not TryStrToBool(S, Result) then
Result := Default;
end;
function TryStrToBool(const S: string; out Value: Boolean): Boolean;
function CompareWith(const aArray: array of string): Boolean;
var
I: Integer;
begin
Result := False;
for I := Low(aArray) to High(aArray) do
if AnsiSameText(S, aArray[I]) then
begin
Result := True;
Break;
end;
end;
var
LResult: Extended;
begin
Result := TryStrToFloat(S, LResult);
if Result then
Value := LResult <> 0
else
begin
VerifyBoolStrArray;
Result := CompareWith(TrueBoolStrs);
if Result then
Value := True
else
begin
Result := CompareWith(FalseBoolStrs);
if Result then
Value := False;
end;
end;
end;
function BoolToStr(B: Boolean; UseBoolStrs: Boolean = False): string;
const
cSimpleBoolStrs: array [boolean] of String = ('0', '-1');
begin
if UseBoolStrs then
begin
VerifyBoolStrArray;
if B then
Result := TrueBoolStrs[0]
else
Result := FalseBoolStrs[0];
end
else
Result := cSimpleBoolStrs[B];
end;
type
PStrData = ^TStrData;
TStrData = record
Ident: Integer;
Str: string;
end;
function EnumStringModules(Instance: Longint; Data: Pointer): Boolean;
{$IFDEF MSWINDOWS}
var
Buffer: array [0..1023] of char;
begin
with PStrData(Data)^ do
begin
SetString(Str, Buffer,
LoadString(Instance, Ident, Buffer, sizeof(Buffer)));
Result := Str = '';
end;
end;
{$ENDIF}
{$IFDEF LINUX}
var
rs: TResStringRec;
Module: HModule;
begin
Module := Instance;
rs.Module := @Module;
with PStrData(Data)^ do
begin
rs.Identifier := Ident;
Str := LoadResString(@rs);
Result := Str = '';
end;
end;
{$ENDIF}
function FindStringResource(Ident: Integer): string;
var
StrData: TStrData;
begin
StrData.Ident := Ident;
StrData.Str := '';
EnumResourceModules(EnumStringModules, @StrData);
Result := StrData.Str;
end;
function LoadStr(Ident: Integer): string;
begin
Result := FindStringResource(Ident);
end;
function FmtLoadStr(Ident: Integer; const Args: array of const): string;
begin
FmtStr(Result, FindStringResource(Ident), Args);
end;
{ File management routines }
function FileOpen(const FileName: string; Mode: LongWord): Integer;
{$IFDEF MSWINDOWS}
const
AccessMode: array[0..2] of LongWord = (
GENERIC_READ,
GENERIC_WRITE,
GENERIC_READ or GENERIC_WRITE);
ShareMode: array[0..4] of LongWord = (
0,
0,
FILE_SHARE_READ,
FILE_SHARE_WRITE,
FILE_SHARE_READ or FILE_SHARE_WRITE);
begin
Result := -1;
if ((Mode and 3) <= fmOpenReadWrite) and
((Mode and $F0) <= fmShareDenyNone) then
Result := Integer(CreateFile(PChar(FileName), AccessMode[Mode and 3],
ShareMode[(Mode and $F0) shr 4], nil, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, 0));
end;
{$ENDIF}
{$IFDEF LINUX}
const
ShareMode: array[0..fmShareDenyNone shr 4] of Byte = (
0, //No share mode specified
F_WRLCK, //fmShareExclusive
F_RDLCK, //fmShareDenyWrite
0); //fmShareDenyNone
var
FileHandle, Tvar: Integer;
LockVar: TFlock;
smode: Byte;
begin
Result := -1;
if FileExists(FileName) and
((Mode and 3) <= fmOpenReadWrite) and
((Mode and $F0) <= fmShareDenyNone) then
begin
FileHandle := open(PChar(FileName), (Mode and 3), FileAccessRights);
if FileHandle = -1 then Exit;
smode := Mode and $F0 shr 4;
if ShareMode[smode] <> 0 then
begin
with LockVar do
begin
l_whence := SEEK_SET;
l_start := 0;
l_len := 0;
l_type := ShareMode[smode];
end;
Tvar := fcntl(FileHandle, F_SETLK, LockVar);
if Tvar = -1 then
begin
__close(FileHandle);
Exit;
end;
end;
Result := FileHandle;
end;
end;
{$ENDIF}
function FileCreate(const FileName: string): Integer;
{$IFDEF MSWINDOWS}
begin
Result := Integer(CreateFile(PChar(FileName), GENERIC_READ or GENERIC_WRITE,
0, nil, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0));
end;
{$ENDIF}
{$IFDEF LINUX}
begin
Result := FileCreate(FileName, FileAccessRights);
end;
{$ENDIF}
function FileCreate(const FileName: string; Rights: Integer): Integer;
{$IFDEF MSWINDOWS}
begin
Result := FileCreate(FileName);
end;
{$ENDIF}
{$IFDEF LINUX}
begin
Result := Integer(open(PChar(FileName), O_RDWR or O_CREAT or O_TRUNC, Rights));
end;
{$ENDIF}
function FileRead(Handle: Integer; var Buffer; Count: LongWord): Integer;
begin
{$IFDEF MSWINDOWS}
if not ReadFile(THandle(Handle), Buffer, Count, LongWord(Result), nil) then
Result := -1;
{$ENDIF}
{$IFDEF LINUX}
Result := __read(Handle, Buffer, Count);
{$ENDIF}
end;
function FileWrite(Handle: Integer; const Buffer; Count: LongWord): Integer;
begin
{$IFDEF MSWINDOWS}
if not WriteFile(THandle(Handle), Buffer, Count, LongWord(Result), nil) then
Result := -1;
{$ENDIF}
{$IFDEF LINUX}
Result := __write(Handle, Buffer, Count);
{$ENDIF}
end;
function FileSeek(Handle, Offset, Origin: Integer): Integer;
begin
{$IFDEF MSWINDOWS}
Result := SetFilePointer(THandle(Handle), Offset, nil, Origin);
{$ENDIF}
{$IFDEF LINUX}
Result := __lseek(Handle, Offset, Origin);
{$ENDIF}
end;
function FileSeek(Handle: Integer; const Offset: Int64; Origin: Integer): Int64;
{$IFDEF MSWINDOWS}
begin
Result := Offset;
Int64Rec(Result).Lo := SetFilePointer(THandle(Handle), Int64Rec(Result).Lo,
@Int64Rec(Result).Hi, Origin);
end;
{$ENDIF}
{$IFDEF LINUX}
var
Temp: Integer;
begin
Temp := Offset; // allow for range-checking
Result := FileSeek(Handle, Temp, Origin);
end;
{$ENDIF}
procedure FileClose(Handle: Integer);
begin
{$IFDEF MSWINDOWS}
CloseHandle(THandle(Handle));
{$ENDIF}
{$IFDEF LINUX}
__close(Handle); // No need to unlock since all locks are released on close.
{$ENDIF}
end;
function FileAge(const FileName: string): Integer;
{$IFDEF MSWINDOWS}
var
Handle: THandle;
FindData: TWin32FindData;
LocalFileTime: TFileTime;
begin
Handle := FindFirstFile(PChar(FileName), FindData);
if Handle <> INVALID_HANDLE_VALUE then
begin
Windows.FindClose(Handle);
if (FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) = 0 then
begin
FileTimeToLocalFileTime(FindData.ftLastWriteTime, LocalFileTime);
if FileTimeToDosDateTime(LocalFileTime, LongRec(Result).Hi,
LongRec(Result).Lo) then Exit;
end;
end;
Result := -1;
end;
{$ENDIF}
{$IFDEF LINUX}
var
st: TStatBuf;
begin
if stat(PChar(FileName), st) = 0 then
Result := st.st_mtime
else
Result := -1;
end;
{$ENDIF}
function FileExists(const FileName: string): Boolean;
{$IFDEF MSWINDOWS}
begin
Result := FileAge(FileName) <> -1;
end;
{$ENDIF}
{$IFDEF LINUX}
begin
Result := euidaccess(PChar(FileName), F_OK) = 0;
end;
{$ENDIF}
function DirectoryExists(const Directory: string): Boolean;
{$IFDEF LINUX}
var
st: TStatBuf;
begin
if stat(PChar(Directory), st) = 0 then
Result := S_ISDIR(st.st_mode)
else
Result := False;
end;
{$ENDIF}
{$IFDEF MSWINDOWS}
var
Code: Integer;
begin
Code := GetFileAttributes(PChar(Directory));
Result := (Code <> -1) and (FILE_ATTRIBUTE_DIRECTORY and Code <> 0);
end;
{$ENDIF}
function ForceDirectories(Dir: string): Boolean;
var
E: EInOutError;
begin
Result := True;
if Dir = '' then
begin
E := EInOutError.CreateRes(SCannotCreateDir);
E.ErrorCode := 3;
raise E;
end;
Dir := ExcludeTrailingPathDelimiter(Dir);
{$IFDEF MSWINDOWS}
if (Length(Dir) < 3) or DirectoryExists(Dir)
or (ExtractFilePath(Dir) = Dir) then Exit; // avoid 'xyz:\' problem.
{$ENDIF}
{$IFDEF LINUX}
if (Dir = '') or DirectoryExists(Dir) then Exit;
{$ENDIF}
Result := ForceDirectories(ExtractFilePath(Dir)) and CreateDir(Dir);
end;
function FileGetDate(Handle: Integer): Integer;
{$IFDEF MSWINDOWS}
var
FileTime, LocalFileTime: TFileTime;
begin
if GetFileTime(THandle(Handle), nil, nil, @FileTime) and
FileTimeToLocalFileTime(FileTime, LocalFileTime) and
FileTimeToDosDateTime(LocalFileTime, LongRec(Result).Hi,
LongRec(Result).Lo) then Exit;
Result := -1;
end;
{$ENDIF}
{$IFDEF LINUX}
var
st: TStatBuf;
begin
if fstat(Handle, st) = 0 then
Result := st.st_mtime
else
Result := -1;
end;
{$ENDIF}
function FileSetDate(const FileName: string; Age: Integer): Integer;
{$IFDEF MSWINDOWS}
var
f: THandle;
begin
f := FileOpen(FileName, fmOpenWrite);
if f = THandle(-1) then
Result := GetLastError
else
begin
Result := FileSetDate(f, Age);
FileClose(f);
end;
end;
{$ENDIF}
{$IFDEF LINUX}
var
ut: TUTimeBuffer;
begin
Result := 0;
ut.actime := Age;
ut.modtime := Age;
if utime(PChar(FileName), @ut) = -1 then
Result := GetLastError;
end;
{$ENDIF}
{$IFDEF MSWINDOWS}
function FileSetDate(Handle: Integer; Age: Integer): Integer;
var
LocalFileTime, FileTime: TFileTime;
begin
Result := 0;
if DosDateTimeToFileTime(LongRec(Age).Hi, LongRec(Age).Lo, LocalFileTime) and
LocalFileTimeToFileTime(LocalFileTime, FileTime) and
SetFileTime(Handle, nil, nil, @FileTime) then Exit;
Result := GetLastError;
end;
function FileGetAttr(const FileName: string): Integer;
begin
Result := GetFileAttributes(PChar(FileName));
end;
function FileSetAttr(const FileName: string; Attr: Integer): Integer;
begin
Result := 0;
if not SetFileAttributes(PChar(FileName), Attr) then
Result := GetLastError;
end;
{$ENDIF}
function FileIsReadOnly(const FileName: string): Boolean;
begin
{$IFDEF MSWINDOWS}
Result := (GetFileAttributes(PChar(FileName)) and faReadOnly) <> 0;
{$ENDIF}
{$IFDEF LINUX}
Result := (euidaccess(PChar(FileName), R_OK) = 0) and
(euidaccess(PChar(FileName), W_OK) <> 0);
{$ENDIF}
end;
function FileSetReadOnly(const FileName: string; ReadOnly: Boolean): Boolean;
{$IFDEF MSWINDOWS}
var
Flags: Integer;
begin
Result := False;
Flags := GetFileAttributes(PChar(FileName));
if Flags = -1 then Exit;
if ReadOnly then
Flags := Flags or faReadOnly
else
Flags := Flags and not faReadOnly;
Result := SetFileAttributes(PChar(FileName), Flags);
end;
{$ENDIF}
{$IFDEF LINUX}
var
st: TStatBuf;
Flags: Integer;
begin
Result := False;
if stat(PChar(FileName), st) <> 0 then Exit;
if ReadOnly then
Flags := st.st_mode and not (S_IWUSR or S_IWGRP or S_IWOTH)
else
Flags := st.st_mode or (S_IWUSR or S_IWGRP or S_IWOTH);
Result := chmod(PChar(FileName), Flags) = 0;
end;
{$ENDIF}
function FindMatchingFile(var F: TSearchRec): Integer;
{$IFDEF MSWINDOWS}
var
LocalFileTime: TFileTime;
begin
with F do
begin
while FindData.dwFileAttributes and ExcludeAttr <> 0 do
if not FindNextFile(FindHandle, FindData) then
begin
Result := GetLastError;
Exit;
end;
FileTimeToLocalFileTime(FindData.ftLastWriteTime, LocalFileTime);
FileTimeToDosDateTime(LocalFileTime, LongRec(Time).Hi,
LongRec(Time).Lo);
Size := FindData.nFileSizeLow;
Attr := FindData.dwFileAttributes;
Name := FindData.cFileName;
end;
Result := 0;
end;
{$ENDIF}
{$IFDEF LINUX}
var
PtrDirEnt: PDirEnt;
Scratch: TDirEnt;
StatBuf: TStatBuf;
LinkStatBuf: TStatBuf;
FName: string;
Attr: Integer;
Mode: mode_t;
begin
Result := -1;
PtrDirEnt := nil;
if readdir_r(F.FindHandle, @Scratch, PtrDirEnt) <> 0 then
Exit;
while PtrDirEnt <> nil do
begin
if fnmatch(PChar(F.Pattern), PtrDirEnt.d_name, 0) = 0 then
begin // F.PathOnly must include trailing backslash
FName := F.PathOnly + string(PtrDirEnt.d_name);
if lstat(PChar(FName), StatBuf) = 0 then
begin
Attr := 0;
Mode := StatBuf.st_mode;
if S_ISDIR(Mode) then
Attr := Attr or faDirectory
else
if not S_ISREG(Mode) then // directories shouldn't be treated as system files
begin
if S_ISLNK(Mode) then
begin
Attr := Attr or faSymLink;
if (stat(PChar(FName), LinkStatBuf) = 0) and
S_ISDIR(LinkStatBuf.st_mode) then
Attr := Attr or faDirectory
end;
Attr := Attr or faSysFile;
end;
if (PtrDirEnt.d_name[0] = '.') and (PtrDirEnt.d_name[1] <> #0) then
begin
if not ((PtrDirEnt.d_name[1] = '.') and (PtrDirEnt.d_name[2] = #0)) then
Attr := Attr or faHidden;
end;
if euidaccess(PChar(FName), W_OK) <> 0 then
Attr := Attr or faReadOnly;
if Attr and F.ExcludeAttr = 0 then
begin
F.Size := StatBuf.st_size;
F.Attr := Attr;
F.Mode := StatBuf.st_mode;
F.Name := PtrDirEnt.d_name;
F.Time := StatBuf.st_mtime;
Result := 0;
Break;
end;
end;
end;
Result := -1;
if readdir_r(F.FindHandle, @Scratch, PtrDirEnt) <> 0 then
Break;
end // End of While
end;
{$ENDIF}
function FindFirst(const Path: string; Attr: Integer;
var F: TSearchRec): Integer;
const
faSpecial = faHidden or faSysFile or faDirectory;
{$IFDEF MSWINDOWS}
begin
F.ExcludeAttr := not Attr and faSpecial;
F.FindHandle := FindFirstFile(PChar(Path), F.FindData);
if F.FindHandle <> INVALID_HANDLE_VALUE then
begin
Result := FindMatchingFile(F);
if Result <> 0 then FindClose(F);
end else
Result := GetLastError;
end;
{$ENDIF}
{$IFDEF LINUX}
begin
F.ExcludeAttr := not Attr and faSpecial;
F.PathOnly := ExtractFilePath(Path);
F.Pattern := ExtractFileName(Path);
if F.PathOnly = '' then
F.PathOnly := IncludeTrailingPathDelimiter(GetCurrentDir);
F.FindHandle := opendir(PChar(F.PathOnly));
if F.FindHandle <> nil then
begin
Result := FindMatchingFile(F);
if Result <> 0 then
FindClose(F);
end
else
Result:= GetLastError;
end;
{$ENDIF}
function FindNext(var F: TSearchRec): Integer;
begin
{$IFDEF MSWINDOWS}
if FindNextFile(F.FindHandle, F.FindData) then
Result := FindMatchingFile(F) else
Result := GetLastError;
{$ENDIF}
{$IFDEF LINUX}
Result := FindMatchingFile(F);
{$ENDIF}
end;
procedure FindClose(var F: TSearchRec);
begin
{$IFDEF MSWINDOWS}
if F.FindHandle <> INVALID_HANDLE_VALUE then
begin
Windows.FindClose(F.FindHandle);
F.FindHandle := INVALID_HANDLE_VALUE;
end;
{$ENDIF}
{$IFDEF LINUX}
if F.FindHandle <> nil then
begin
closedir(F.FindHandle);
F.FindHandle := nil;
end;
{$ENDIF}
end;
function DeleteFile(const FileName: string): Boolean;
begin
{$IFDEF MSWINDOWS}
Result := Windows.DeleteFile(PChar(FileName));
{$ENDIF}
{$IFDEF LINUX}
Result := unlink(PChar(FileName)) <> -1;
{$ENDIF}
end;
function RenameFile(const OldName, NewName: string): Boolean;
begin
{$IFDEF MSWINDOWS}
Result := MoveFile(PChar(OldName), PChar(NewName));
{$ENDIF}
{$IFDEF LINUX}
Result := __rename(PChar(OldName), PChar(NewName)) = 0;
{$ENDIF}
end;
function AnsiStrLastChar(P: PChar): PChar;
var
LastByte: Integer;
begin
LastByte := StrLen(P) - 1;
Result := @P[LastByte];
{$IFDEF MSWINDOWS}
if StrByteType(P, LastByte) = mbTrailByte then Dec(Result);
{$ENDIF}
{$IFDEF LINUX}
while StrByteType(P, Result - P) = mbTrailByte do Dec(Result);
{$ENDIF}
end;
function AnsiLastChar(const S: string): PChar;
var
LastByte: Integer;
begin
LastByte := Length(S);
if LastByte <> 0 then
begin
while ByteType(S, LastByte) = mbTrailByte do Dec(LastByte);
Result := @S[LastByte];
end
else
Result := nil;
end;
function LastDelimiter(const Delimiters, S: string): Integer;
var
P: PChar;
begin
Result := Length(S);
P := PChar(Delimiters);
while Result > 0 do
begin
if (S[Result] <> #0) and (StrScan(P, S[Result]) <> nil) then
{$IFDEF MSWINDOWS}
if (ByteType(S, Result) = mbTrailByte) then
Dec(Result)
else
Exit;
{$ENDIF}
{$IFDEF LINUX}
begin
if (ByteType(S, Result) <> mbTrailByte) then
Exit;
Dec(Result);
while ByteType(S, Result) = mbTrailByte do Dec(Result);
end;
{$ENDIF}
Dec(Result);
end;
end;
function ChangeFileExt(const FileName, Extension: string): string;
var
I: Integer;
begin
I := LastDelimiter('.' + PathDelim + DriveDelim,Filename);
if (I = 0) or (FileName[I] <> '.') then I := MaxInt;
Result := Copy(FileName, 1, I - 1) + Extension;
end;
function ExtractFilePath(const FileName: string): string;
var
I: Integer;
begin
I := LastDelimiter(PathDelim + DriveDelim, FileName);
Result := Copy(FileName, 1, I);
end;
function ExtractFileDir(const FileName: string): string;
var
I: Integer;
begin
I := LastDelimiter(PathDelim + DriveDelim, Filename);
if (I > 1) and (FileName[I] = PathDelim) and
(not IsDelimiter( PathDelim + DriveDelim, FileName, I-1)) then Dec(I);
Result := Copy(FileName, 1, I);
end;
function ExtractFileDrive(const FileName: string): string;
{$IFDEF MSWINDOWS}
var
I, J: Integer;
begin
if (Length(FileName) >= 2) and (FileName[2] = DriveDelim) then
Result := Copy(FileName, 1, 2)
else if (Length(FileName) >= 2) and (FileName[1] = PathDelim) and
(FileName[2] = PathDelim) then
begin
J := 0;
I := 3;
While (I < Length(FileName)) and (J < 2) do
begin
if FileName[I] = PathDelim then Inc(J);
if J < 2 then Inc(I);
end;
if FileName[I] = PathDelim then Dec(I);
Result := Copy(FileName, 1, I);
end else Result := '';
end;
{$ENDIF}
{$IFDEF LINUX}
begin
Result := ''; // Linux doesn't support drive letters
end;
{$ENDIF}
function ExtractFileName(const FileName: string): string;
var
I: Integer;
begin
I := LastDelimiter(PathDelim + DriveDelim, FileName);
Result := Copy(FileName, I + 1, MaxInt);
end;
function ExtractFileExt(const FileName: string): string;
var
I: Integer;
begin
I := LastDelimiter('.' + PathDelim + DriveDelim, FileName);
if (I > 0) and (FileName[I] = '.') then
Result := Copy(FileName, I, MaxInt) else
Result := '';
end;
function ExpandFileName(const FileName: string): string;
{$IFDEF MSWINDOWS}
var
FName: PChar;
Buffer: array[0..MAX_PATH - 1] of Char;
begin
SetString(Result, Buffer, GetFullPathName(PChar(FileName), SizeOf(Buffer),
Buffer, FName));
end;
{$ENDIF}
{$IFDEF LINUX}
function ExpandTilde(const InString: string): string;
var
W: wordexp_t;
SpacePos: Integer;
PostSpaceStr: string;
begin
Result := InString;
SpacePos := AnsiPos(' ', Result); // only expand stuff up to the first space in the filename
if SpacePos > 0 then // then just add the space and the rest of the string
PostSpaceStr := Copy(Result, SpacePos, Length(Result) - (SpacePos-1));
case wordexp(PChar(Result), W, WRDE_NOCMD) of
0: // success
begin
Result := PChar(W.we_wordv^);
wordfree(W);
end;
WRDE_NOSPACE: // error, but W may be partially allocated
wordfree(W);
end;
if PostSpaceStr <> '' then
Result := Result + PostSpaceStr;
end;
var
I, J: Integer;
LastWasPathDelim: Boolean;
TempName: string;
begin
Result := '';
if Length(Filename) = 0 then Exit;
if FileName[1] = PathDelim then
TempName := FileName
else
begin
TempName := FileName;
if FileName[1] = '~' then
TempName := ExpandTilde(TempName)
else
TempName := IncludeTrailingPathDelimiter(GetCurrentDir) + TempName;
end;
I := 1;
J := 1;
LastWasPathDelim := False;
while I <= Length(TempName) do
begin
case TempName[I] of
PathDelim:
if J < I then
begin
// Check for consecutive 'PathDelim' characters and skip them if present
if (I = 1) or (TempName[I - 1] <> PathDelim) then
Result := Result + Copy(TempName, J, I - J);
J := I;
// Set a flag indicating that we just processed a path delimiter
LastWasPathDelim := True;
end;
'.':
begin
// If the last character was a path delimiter then this '.' is
// possibly a relative path modifier
if LastWasPathDelim then
begin
// Check if the path ends in a '.'
if I < Length(TempName) then
begin
// If the next character is another '.' then this may be a relative path
// except if there is another '.' after that one. In this case simply
// treat this as just another filename.
if (TempName[I + 1] = '.') and
((I + 1 >= Length(TempName)) or (TempName[I + 2] <> '.')) then
begin
// Don't attempt to backup past the Root dir
if Length(Result) > 1 then
// For the purpose of this excercise, treat the last dir as a
// filename so we can use this function to remove it
Result := ExtractFilePath(ExcludeTrailingPathDelimiter(Result));
J := I;
end
// Simply skip over and ignore any 'current dir' constrcucts, './'
// or the remaining './' from a ../ constrcut.
else if TempName[I + 1] = PathDelim then
begin
Result := IncludeTrailingPathDelimiter(Result);
if TempName[I] in LeadBytes then
Inc(I, StrCharLength(@TempName[I]))
else
Inc(I);
J := I + 1;
end else
// If any of the above tests fail, then this is not a 'current dir' or
// 'parent dir' construct so just clear the state and continue.
LastWasPathDelim := False;
end else
begin
// Don't let the expanded path end in a 'PathDelim' character
Result := ExcludeTrailingPathDelimiter(Result);
J := I + 1;
end;
end;
end;
else
LastWasPathDelim := False;
end;
if TempName[I] in LeadBytes then
Inc(I, StrCharLength(@TempName[I]))
else
Inc(I);
end;
// This will finally append what is left
if (I - J > 1) or (TempName[I] <> PathDelim) then
Result := Result + Copy(TempName, J, I - J);
end;
{$ENDIF}
function ExpandFileNameCase(const FileName: string;
out MatchFound: TFilenameCaseMatch): string;
var
SR: TSearchRec;
FullPath, Name: string;
Temp: Integer;
FoundOne: Boolean;
{$IFDEF LINUX}
Scans: Byte;
FirstLetter, TestLetter: string;
{$ENDIF}
begin
Result := ExpandFileName(FileName);
FullPath := ExtractFilePath(Result);
Name := ExtractFileName(Result);
MatchFound := mkNone;
// if FullPath is not the root directory (portable)
if not SameFileName(FullPath, IncludeTrailingPathDelimiter(ExtractFileDrive(FullPath))) then
begin // Does the path need case-sensitive work?
Temp := FindFirst(FullPath, faAnyFile, SR);
FindClose(SR); // close search before going recursive
if Temp <> 0 then
begin
FullPath := ExcludeTrailingPathDelimiter(FullPath);
FullPath := ExpandFileNameCase(FullPath, MatchFound);
if MatchFound = mkNone then
Exit; // if we can't find the path, we certainly can't find the file!
FullPath := IncludeTrailingPathDelimiter(FullPath);
end;
end;
// Path is validated / adjusted. Now for the file itself
try
if FindFirst(FullPath + Name, faAnyFile, SR)= 0 then // exact match on filename
begin
if not (MatchFound in [mkSingleMatch, mkAmbiguous]) then // path might have been inexact
MatchFound := mkExactMatch;
Result := FullPath + SR.Name;
Exit;
end;
finally
FindClose(SR);
end;
FoundOne := False; // Windows should never get to here except for file-not-found
{$IFDEF LINUX}
{ Scan the directory.
To minimize the number of filenames tested, scan the directory
using upper/lowercase first letter + wildcard.
This results in two scans of the directory (particularly on Linux) but
vastly reduces the number of times we have to perform an expensive
locale-charset case-insensitive string compare. }
// First, scan for lowercase first letter
FirstLetter := AnsiLowerCase(Name[1]);
for Scans := 0 to 1 do
begin
Temp := FindFirst(FullPath + FirstLetter + '*', faAnyFile, SR);
while Temp = 0 do
begin
if AnsiSameText(SR.Name, Name) then
begin
if FoundOne then
begin // this is the second match
MatchFound := mkAmbiguous;
Exit;
end
else
begin
FoundOne := True;
Result := FullPath + SR.Name;
end;
end;
Temp := FindNext(SR);
end;
FindClose(SR);
TestLetter := AnsiUpperCase(Name[1]);
if TestLetter = FirstLetter then Break;
FirstLetter := TestLetter;
end;
{$ENDIF}
if MatchFound <> mkAmbiguous then
begin
if FoundOne then
MatchFound := mkSingleMatch
else
MatchFound := mkNone;
end;
end;
{$IFDEF MSWINDOWS}
function GetUniversalName(const FileName: string): string;
type
PNetResourceArray = ^TNetResourceArray;
TNetResourceArray = array[0..MaxInt div SizeOf(TNetResource) - 1] of TNetResource;
var
I, BufSize, NetResult: Integer;
Count, Size: LongWord;
Drive: Char;
NetHandle: THandle;
NetResources: PNetResourceArray;
RemoteNameInfo: array[0..1023] of Byte;
begin
Result := FileName;
if (Win32Platform <> VER_PLATFORM_WIN32_WINDOWS) or (Win32MajorVersion > 4) then
begin
Size := SizeOf(RemoteNameInfo);
if WNetGetUniversalName(PChar(FileName), UNIVERSAL_NAME_INFO_LEVEL,
@RemoteNameInfo, Size) <> NO_ERROR then Exit;
Result := PRemoteNameInfo(@RemoteNameInfo).lpUniversalName;
end else
begin
{ The following works around a bug in WNetGetUniversalName under Windows 95 }
Drive := UpCase(FileName[1]);
if (Drive < 'A') or (Drive > 'Z') or (Length(FileName) < 3) or
(FileName[2] <> ':') or (FileName[3] <> '\') then
Exit;
if WNetOpenEnum(RESOURCE_CONNECTED, RESOURCETYPE_DISK, 0, nil,
NetHandle) <> NO_ERROR then Exit;
try
BufSize := 50 * SizeOf(TNetResource);
GetMem(NetResources, BufSize);
try
while True do
begin
Count := $FFFFFFFF;
Size := BufSize;
NetResult := WNetEnumResource(NetHandle, Count, NetResources, Size);
if NetResult = ERROR_MORE_DATA then
begin
BufSize := Size;
ReallocMem(NetResources, BufSize);
Continue;
end;
if NetResult <> NO_ERROR then Exit;
for I := 0 to Count - 1 do
with NetResources^[I] do
if (lpLocalName <> nil) and (Drive = UpCase(lpLocalName[0])) then
begin
Result := lpRemoteName + Copy(FileName, 3, Length(FileName) - 2);
Exit;
end;
end;
finally
FreeMem(NetResources, BufSize);
end;
finally
WNetCloseEnum(NetHandle);
end;
end;
end;
function ExpandUNCFileName(const FileName: string): string;
begin
{ First get the local resource version of the file name }
Result := ExpandFileName(FileName);
if (Length(Result) >= 3) and (Result[2] = ':') and (Upcase(Result[1]) >= 'A')
and (Upcase(Result[1]) <= 'Z') then
Result := GetUniversalName(Result);
end;
{$ENDIF}
{$IFDEF LINUX}
function ExpandUNCFileName(const FileName: string): string;
begin
Result := ExpandFileName(FileName);
end;
{$ENDIF}
function ExtractRelativePath(const BaseName, DestName: string): string;
var
BasePath, DestPath: string;
BaseLead, DestLead: PChar;
BasePtr, DestPtr: PChar;
function ExtractFilePathNoDrive(const FileName: string): string;
begin
Result := ExtractFilePath(FileName);
Delete(Result, 1, Length(ExtractFileDrive(FileName)));
end;
function Next(var Lead: PChar): PChar;
begin
Result := Lead;
if Result = nil then Exit;
Lead := AnsiStrScan(Lead, PathDelim);
if Lead <> nil then
begin
Lead^ := #0;
Inc(Lead);
end;
end;
begin
if SameFilename(ExtractFileDrive(BaseName), ExtractFileDrive(DestName)) then
begin
BasePath := ExtractFilePathNoDrive(BaseName);
UniqueString(BasePath);
DestPath := ExtractFilePathNoDrive(DestName);
UniqueString(DestPath);
BaseLead := Pointer(BasePath);
BasePtr := Next(BaseLead);
DestLead := Pointer(DestPath);
DestPtr := Next(DestLead);
while (BasePtr <> nil) and (DestPtr <> nil) and SameFilename(BasePtr, DestPtr) do
begin
BasePtr := Next(BaseLead);
DestPtr := Next(DestLead);
end;
Result := '';
while BaseLead <> nil do
begin
Result := Result + '..' + PathDelim; { Do not localize }
Next(BaseLead);
end;
if (DestPtr <> nil) and (DestPtr^ <> #0) then
Result := Result + DestPtr + PathDelim;
if DestLead <> nil then
Result := Result + DestLead; // destlead already has a trailing backslash
Result := Result + ExtractFileName(DestName);
end
else
Result := DestName;
end;
{$IFDEF MSWINDOWS}
function ExtractShortPathName(const FileName: string): string;
var
Buffer: array[0..MAX_PATH - 1] of Char;
begin
SetString(Result, Buffer,
GetShortPathName(PChar(FileName), Buffer, SizeOf(Buffer)));
end;
{$ENDIF}
function FileSearch(const Name, DirList: string): string;
var
I, P, L: Integer;
C: Char;
begin
Result := Name;
P := 1;
L := Length(DirList);
while True do
begin
if FileExists(Result) then Exit;
while (P <= L) and (DirList[P] = PathSep) do Inc(P);
if P > L then Break;
I := P;
while (P <= L) and (DirList[P] <> PathSep) do
begin
if DirList[P] in LeadBytes then
P := NextCharIndex(DirList, P)
else
Inc(P);
end;
Result := Copy(DirList, I, P - I);
C := AnsiLastChar(Result)^;
if (C <> DriveDelim) and (C <> PathDelim) then
Result := Result + PathDelim;
Result := Result + Name;
end;
Result := '';
end;
{$IFDEF MSWINDOWS}
// This function is used if the OS doesn't support GetDiskFreeSpaceEx
function BackfillGetDiskFreeSpaceEx(Directory: PChar; var FreeAvailable,
TotalSpace: TLargeInteger; TotalFree: PLargeInteger): Bool; stdcall;
var
SectorsPerCluster, BytesPerSector, FreeClusters, TotalClusters: LongWord;
Temp: Int64;
Dir: PChar;
begin
if Directory <> nil then
Dir := Directory
else
Dir := nil;
Result := GetDiskFreeSpaceA(Dir, SectorsPerCluster, BytesPerSector,
FreeClusters, TotalClusters);
Temp := SectorsPerCluster * BytesPerSector;
FreeAvailable := Temp * FreeClusters;
TotalSpace := Temp * TotalClusters;
end;
function InternalGetDiskSpace(Drive: Byte;
var TotalSpace, FreeSpaceAvailable: Int64): Bool;
var
RootPath: array[0..4] of Char;
RootPtr: PChar;
begin
RootPtr := nil;
if Drive > 0 then
begin
RootPath[0] := Char(Drive + $40);
RootPath[1] := ':';
RootPath[2] := '\';
RootPath[3] := #0;
RootPtr := RootPath;
end;
Result := GetDiskFreeSpaceEx(RootPtr, FreeSpaceAvailable, TotalSpace, nil);
end;
function DiskFree(Drive: Byte): Int64;
var
TotalSpace: Int64;
begin
if not InternalGetDiskSpace(Drive, TotalSpace, Result) then
Result := -1;
end;
function DiskSize(Drive: Byte): Int64;
var
FreeSpace: Int64;
begin
if not InternalGetDiskSpace(Drive, Result, FreeSpace) then
Result := -1;
end;
{$ENDIF}
function FileDateToDateTime(FileDate: Integer): TDateTime;
{$IFDEF MSWINDOWS}
begin
Result :=
EncodeDate(
LongRec(FileDate).Hi shr 9 + 1980,
LongRec(FileDate).Hi shr 5 and 15,
LongRec(FileDate).Hi and 31) +
EncodeTime(
LongRec(FileDate).Lo shr 11,
LongRec(FileDate).Lo shr 5 and 63,
LongRec(FileDate).Lo and 31 shl 1, 0);
end;
{$ENDIF}
{$IFDEF LINUX}
var
UT: TUnixTime;
begin
localtime_r(@FileDate, UT);
Result := EncodeDate(UT.tm_year + 1900, UT.tm_mon + 1, UT.tm_mday) +
EncodeTime(UT.tm_hour, UT.tm_min, UT.tm_sec, 0);
end;
{$ENDIF}
function DateTimeToFileDate(DateTime: TDateTime): Integer;
{$IFDEF MSWINDOWS}
var
Year, Month, Day, Hour, Min, Sec, MSec: Word;
begin
DecodeDate(DateTime, Year, Month, Day);
if (Year < 1980) or (Year > 2107) then Result := 0 else
begin
DecodeTime(DateTime, Hour, Min, Sec, MSec);
LongRec(Result).Lo := (Sec shr 1) or (Min shl 5) or (Hour shl 11);
LongRec(Result).Hi := Day or (Month shl 5) or ((Year - 1980) shl 9);
end;
end;
{$ENDIF}
{$IFDEF LINUX}
var
tm: TUnixTime;
Year, Month, Day, Hour, Min, Sec, MSec: Word;
begin
DecodeDate(DateTime, Year, Month, Day);
{ Valid range for 32 bit Unix time_t: 1970 through 2038 }
if (Year < 1970) or (Year > 2038) then
Result := 0
else
begin
DecodeTime(DateTime, Hour, Min, Sec, MSec);
FillChar(tm, sizeof(tm), 0);
with tm do
begin
tm_sec := Sec;
tm_min := Min;
tm_hour := Hour;
tm_mday := Day;
tm_mon := Month - 1;
tm_year := Year - 1900;
tm_isdst := -1;
end;
Result := mktime(tm);
end;
end;
{$ENDIF}
function GetCurrentDir: string;
begin
GetDir(0, Result);
end;
function SetCurrentDir(const Dir: string): Boolean;
begin
{$IFDEF MSWINDOWS}
Result := SetCurrentDirectory(PChar(Dir));
{$ENDIF}
{$IFDEF LINUX}
Result := __chdir(PChar(Dir)) = 0;
{$ENDIF}
end;
function CreateDir(const Dir: string): Boolean;
begin
{$IFDEF MSWINDOWS}
Result := CreateDirectory(PChar(Dir), nil);
{$ENDIF}
{$IFDEF LINUX}
Result := __mkdir(PChar(Dir), mode_t(-1)) = 0;
{$ENDIF}
end;
function RemoveDir(const Dir: string): Boolean;
begin
{$IFDEF MSWINDOWS}
Result := RemoveDirectory(PChar(Dir));
{$ENDIF}
{$IFDEF LINUX}
Result := __rmdir(PChar(Dir)) = 0;
{$ENDIF}
end;
{ PChar routines }
function StrLen(const Str: PChar): Cardinal; assembler;
asm
MOV EDX,EDI
MOV EDI,EAX
MOV ECX,0FFFFFFFFH
XOR AL,AL
REPNE SCASB
MOV EAX,0FFFFFFFEH
SUB EAX,ECX
MOV EDI,EDX
end;
function StrEnd(const Str: PChar): PChar; assembler;
asm
MOV EDX,EDI
MOV EDI,EAX
MOV ECX,0FFFFFFFFH
XOR AL,AL
REPNE SCASB
LEA EAX,[EDI-1]
MOV EDI,EDX
end;
function StrMove(Dest: PChar; const Source: PChar; Count: Cardinal): PChar;
begin
Result := Dest;
Move(Source^, Dest^, Count);
end;
function StrCopy(Dest: PChar; const Source: PChar): PChar;
asm
PUSH EDI
PUSH ESI
MOV ESI,EAX
MOV EDI,EDX
MOV ECX,0FFFFFFFFH
XOR AL,AL
REPNE SCASB
NOT ECX
MOV EDI,ESI
MOV ESI,EDX
MOV EDX,ECX
MOV EAX,EDI
SHR ECX,2
REP MOVSD
MOV ECX,EDX
AND ECX,3
REP MOVSB
POP ESI
POP EDI
end;
function StrECopy(Dest: PChar; const Source: PChar): PChar; assembler;
asm
PUSH EDI
PUSH ESI
MOV ESI,EAX
MOV EDI,EDX
MOV ECX,0FFFFFFFFH
XOR AL,AL
REPNE SCASB
NOT ECX
MOV EDI,ESI
MOV ESI,EDX
MOV EDX,ECX
SHR ECX,2
REP MOVSD
MOV ECX,EDX
AND ECX,3
REP MOVSB
LEA EAX,[EDI-1]
POP ESI
POP EDI
end;
function StrLCopy(Dest: PChar; const Source: PChar; MaxLen: Cardinal): PChar; assembler;
asm
PUSH EDI
PUSH ESI
PUSH EBX
MOV ESI,EAX
MOV EDI,EDX
MOV EBX,ECX
XOR AL,AL
TEST ECX,ECX
JZ @@1
REPNE SCASB
JNE @@1
INC ECX
@@1: SUB EBX,ECX
MOV EDI,ESI
MOV ESI,EDX
MOV EDX,EDI
MOV ECX,EBX
SHR ECX,2
REP MOVSD
MOV ECX,EBX
AND ECX,3
REP MOVSB
STOSB
MOV EAX,EDX
POP EBX
POP ESI
POP EDI
end;
function StrPCopy(Dest: PChar; const Source: string): PChar;
begin
Result := StrLCopy(Dest, PChar(Source), Length(Source));
end;
function StrPLCopy(Dest: PChar; const Source: string;
MaxLen: Cardinal): PChar;
begin
Result := StrLCopy(Dest, PChar(Source), MaxLen);
end;
function StrCat(Dest: PChar; const Source: PChar): PChar;
begin
StrCopy(StrEnd(Dest), Source);
Result := Dest;
end;
function StrLCat(Dest: PChar; const Source: PChar; MaxLen: Cardinal): PChar; assembler;
asm
PUSH EDI
PUSH ESI
PUSH EBX
MOV EDI,Dest
MOV ESI,Source
MOV EBX,MaxLen
CALL StrEnd
MOV ECX,EDI
ADD ECX,EBX
SUB ECX,EAX
JBE @@1
MOV EDX,ESI
CALL StrLCopy
@@1: MOV EAX,EDI
POP EBX
POP ESI
POP EDI
end;
function StrComp(const Str1, Str2: PChar): Integer; assembler;
asm
PUSH EDI
PUSH ESI
MOV EDI,EDX
MOV ESI,EAX
MOV ECX,0FFFFFFFFH
XOR EAX,EAX
REPNE SCASB
NOT ECX
MOV EDI,EDX
XOR EDX,EDX
REPE CMPSB
MOV AL,[ESI-1]
MOV DL,[EDI-1]
SUB EAX,EDX
POP ESI
POP EDI
end;
function StrIComp(const Str1, Str2: PChar): Integer; assembler;
asm
PUSH EDI
PUSH ESI
MOV EDI,EDX
MOV ESI,EAX
MOV ECX,0FFFFFFFFH
XOR EAX,EAX
REPNE SCASB
NOT ECX
MOV EDI,EDX
XOR EDX,EDX
@@1: REPE CMPSB
JE @@4
MOV AL,[ESI-1]
CMP AL,'a'
JB @@2
CMP AL,'z'
JA @@2
SUB AL,20H
@@2: MOV DL,[EDI-1]
CMP DL,'a'
JB @@3
CMP DL,'z'
JA @@3
SUB DL,20H
@@3: SUB EAX,EDX
JE @@1
@@4: POP ESI
POP EDI
end;
function StrLComp(const Str1, Str2: PChar; MaxLen: Cardinal): Integer; assembler;
asm
PUSH EDI
PUSH ESI
PUSH EBX
MOV EDI,EDX
MOV ESI,EAX
MOV EBX,ECX
XOR EAX,EAX
OR ECX,ECX
JE @@1
REPNE SCASB
SUB EBX,ECX
MOV ECX,EBX
MOV EDI,EDX
XOR EDX,EDX
REPE CMPSB
MOV AL,[ESI-1]
MOV DL,[EDI-1]
SUB EAX,EDX
@@1: POP EBX
POP ESI
POP EDI
end;
function StrLIComp(const Str1, Str2: PChar; MaxLen: Cardinal): Integer; assembler;
asm
PUSH EDI
PUSH ESI
PUSH EBX
MOV EDI,EDX
MOV ESI,EAX
MOV EBX,ECX
XOR EAX,EAX
OR ECX,ECX
JE @@4
REPNE SCASB
SUB EBX,ECX
MOV ECX,EBX
MOV EDI,EDX
XOR EDX,EDX
@@1: REPE CMPSB
JE @@4
MOV AL,[ESI-1]
CMP AL,'a'
JB @@2
CMP AL,'z'
JA @@2
SUB AL,20H
@@2: MOV DL,[EDI-1]
CMP DL,'a'
JB @@3
CMP DL,'z'
JA @@3
SUB DL,20H
@@3: SUB EAX,EDX
JE @@1
@@4: POP EBX
POP ESI
POP EDI
end;
function StrScan(const Str: PChar; Chr: Char): PChar;
begin
Result := Str;
while Result^ <> Chr do
begin
if Result^ = #0 then
begin
Result := nil;
Exit;
end;
Inc(Result);
end;
end;
function StrRScan(const Str: PChar; Chr: Char): PChar;
var
MostRecentFound: PChar;
begin
if Chr = #0 then
Result := StrEnd(Str)
else
begin
Result := nil;
MostRecentFound := Str;
while True do
begin
while MostRecentFound^ <> Chr do
begin
if MostRecentFound^ = #0 then
Exit;
Inc(MostRecentFound);
end;
Result := MostRecentFound;
Inc(MostRecentFound);
end;
end;
end;
function StrPos(const Str1, Str2: PChar): PChar; assembler;
asm
PUSH EDI
PUSH ESI
PUSH EBX
OR EAX,EAX
JE @@2
OR EDX,EDX
JE @@2
MOV EBX,EAX
MOV EDI,EDX
XOR AL,AL
MOV ECX,0FFFFFFFFH
REPNE SCASB
NOT ECX
DEC ECX
JE @@2
MOV ESI,ECX
MOV EDI,EBX
MOV ECX,0FFFFFFFFH
REPNE SCASB
NOT ECX
SUB ECX,ESI
JBE @@2
MOV EDI,EBX
LEA EBX,[ESI-1]
@@1: MOV ESI,EDX
LODSB
REPNE SCASB
JNE @@2
MOV EAX,ECX
PUSH EDI
MOV ECX,EBX
REPE CMPSB
POP EDI
MOV ECX,EAX
JNE @@1
LEA EAX,[EDI-1]
JMP @@3
@@2: XOR EAX,EAX
@@3: POP EBX
POP ESI
POP EDI
end;
function StrUpper(Str: PChar): PChar; assembler;
asm
PUSH ESI
MOV ESI,Str
MOV EDX,Str
@@1: LODSB
OR AL,AL
JE @@2
CMP AL,'a'
JB @@1
CMP AL,'z'
JA @@1
SUB AL,20H
MOV [ESI-1],AL
JMP @@1
@@2: XCHG EAX,EDX
POP ESI
end;
function StrLower(Str: PChar): PChar; assembler;
asm
PUSH ESI
MOV ESI,Str
MOV EDX,Str
@@1: LODSB
OR AL,AL
JE @@2
CMP AL,'A'
JB @@1
CMP AL,'Z'
JA @@1
ADD AL,20H
MOV [ESI-1],AL
JMP @@1
@@2: XCHG EAX,EDX
POP ESI
end;
function StrPas(const Str: PChar): string;
begin
Result := Str;
end;
function StrAlloc(Size: Cardinal): PChar;
begin
Inc(Size, SizeOf(Cardinal));
GetMem(Result, Size);
Cardinal(Pointer(Result)^) := Size;
Inc(Result, SizeOf(Cardinal));
end;
function StrBufSize(const Str: PChar): Cardinal;
var
P: PChar;
begin
P := Str;
Dec(P, SizeOf(Cardinal));
Result := Cardinal(Pointer(P)^) - SizeOf(Cardinal);
end;
function StrNew(const Str: PChar): PChar;
var
Size: Cardinal;
begin
if Str = nil then Result := nil else
begin
Size := StrLen(Str) + 1;
Result := StrMove(StrAlloc(Size), Str, Size);
end;
end;
procedure StrDispose(Str: PChar);
begin
if Str <> nil then
begin
Dec(Str, SizeOf(Cardinal));
FreeMem(Str, Cardinal(Pointer(Str)^));
end;
end;
{ String formatting routines }
procedure FormatError(ErrorCode: Integer; Format: PChar; FmtLen: Cardinal);
const
FormatErrorStrs: array[0..1] of string = (
SInvalidFormat, SArgumentMissing);
var
Buffer: array[0..31] of Char;
begin
if FmtLen > 31 then FmtLen := 31;
if StrByteType(Format, FmtLen-1) = mbLeadByte then Dec(FmtLen);
StrMove(Buffer, Format, FmtLen);
Buffer[FmtLen] := #0;
ConvertErrorFmt(FormatErrorStrs[ErrorCode], [PChar(@Buffer)]);
end;
procedure FormatVarToStr(var S: string; const V: Variant);
begin
{if Assigned(System.VarToLStr) then
System.is(S, V)
else
System.Error(reVarInvalidOp); }
S:='';
end;
procedure FormatClearStr(var S: string);
begin
S := '';
end;
function FloatToTextEx(BufferArg: PChar; const Value; ValueType: TFloatValue;
Format: TFloatFormat; Precision, Digits: Integer;
const FormatSettings: TFormatSettings): Integer;
begin
Result := FloatToText(BufferArg, Value, ValueType, Format, Precision, Digits,
FormatSettings);
end;
function FormatBuf(var Buffer; BufLen: Cardinal; const Format;
FmtLen: Cardinal; const Args: array of const): Cardinal;
var
ArgIndex, Width, Prec: Integer;
BufferOrg, FormatOrg, FormatPtr, TempStr: PChar;
JustFlag: Byte;
StrBuf: array[0..64] of Char;
TempAnsiStr: string;
SaveGOT: Integer;
{ in: eax <-> Buffer }
{ in: edx <-> BufLen }
{ in: ecx <-> Format }
asm
PUSH EBX
PUSH ESI
PUSH EDI
MOV EDI,EAX
MOV ESI,ECX
{$IFDEF PIC}
PUSH ECX
CALL GetGOT
POP ECX
{$ELSE}
XOR EAX,EAX
{$ENDIF}
MOV SaveGOT,EAX
ADD ECX,FmtLen
MOV BufferOrg,EDI
XOR EAX,EAX
MOV ArgIndex,EAX
MOV TempStr,EAX
MOV TempAnsiStr,EAX
@Loop:
OR EDX,EDX
JE @Done
@NextChar:
CMP ESI,ECX
JE @Done
LODSB
CMP AL,'%'
JE @Format
@StoreChar:
STOSB
DEC EDX
JNE @NextChar
@Done:
MOV EAX,EDI
SUB EAX,BufferOrg
JMP @Exit
@Format:
CMP ESI,ECX
JE @Done
LODSB
CMP AL,'%'
JE @StoreChar
LEA EBX,[ESI-2]
MOV FormatOrg,EBX
@A0: MOV JustFlag,AL
CMP AL,'-'
JNE @A1
CMP ESI,ECX
JE @Done
LODSB
@A1: CALL @Specifier
CMP AL,':'
JNE @A2
MOV ArgIndex,EBX
CMP ESI,ECX
JE @Done
LODSB
JMP @A0
@A2: MOV Width,EBX
MOV EBX,-1
CMP AL,'.'
JNE @A3
CMP ESI,ECX
JE @Done
LODSB
CALL @Specifier
@A3: MOV Prec,EBX
MOV FormatPtr,ESI
PUSH ECX
PUSH EDX
CALL @Convert
POP EDX
MOV EBX,Width
SUB EBX,ECX //(* ECX <=> number of characters output *)
JAE @A4 //(* jump -> output smaller than width *)
XOR EBX,EBX
@A4: CMP JustFlag,'-'
JNE @A6
SUB EDX,ECX
JAE @A5
ADD ECX,EDX
XOR EDX,EDX
@A5: REP MOVSB
@A6: XCHG EBX,ECX
SUB EDX,ECX
JAE @A7
ADD ECX,EDX
XOR EDX,EDX
@A7: MOV AL,' '
REP STOSB
XCHG EBX,ECX
SUB EDX,ECX
JAE @A8
ADD ECX,EDX
XOR EDX,EDX
@A8: REP MOVSB
CMP TempStr,0
JE @A9
PUSH EDX
LEA EAX,TempStr
// PUSH EBX // GOT setup unnecessary for
// MOV EBX, SaveGOT // same-unit calls to Pascal procedures
CALL FormatClearStr
// POP EBX
POP EDX
@A9: POP ECX
MOV ESI,FormatPtr
JMP @Loop
@Specifier:
XOR EBX,EBX
CMP AL,'*'
JE @B3
@B1: CMP AL,'0'
JB @B5
CMP AL,'9'
JA @B5
IMUL EBX,EBX,10
SUB AL,'0'
MOVZX EAX,AL
ADD EBX,EAX
CMP ESI,ECX
JE @B2
LODSB
JMP @B1
@B2: POP EAX
JMP @Done
@B3: MOV EAX,ArgIndex
CMP EAX,Args.Integer[-4]
JG @B4
INC ArgIndex
MOV EBX,Args
CMP [EBX+EAX*8].Byte[4],vtInteger
MOV EBX,[EBX+EAX*8]
JE @B4
XOR EBX,EBX
@B4: CMP ESI,ECX
JE @B2
LODSB
@B5: RET
@Convert:
AND AL,0DFH
MOV CL,AL
MOV EAX,1
MOV EBX,ArgIndex
CMP EBX,Args.Integer[-4]
JG @ErrorExit
INC ArgIndex
MOV ESI,Args
LEA ESI,[ESI+EBX*8]
MOV EAX,[ESI].Integer[0] // TVarRec.data
MOVZX EDX,[ESI].Byte[4] // TVarRec.VType
{$IFDEF PIC}
MOV EBX, SaveGOT
ADD EBX, offset @CvtVector
MOV EBX, [EBX+EDX*4]
ADD EBX, SaveGOT
JMP EBX
{$ELSE}
JMP @CvtVector.Pointer[EDX*4]
{$ENDIF}
@CvtVector:
DD @CvtInteger // vtInteger
DD @CvtBoolean // vtBoolean
DD @CvtChar // vtChar
DD @CvtExtended // vtExtended
DD @CvtShortStr // vtString
DD @CvtPointer // vtPointer
DD @CvtPChar // vtPChar
DD @CvtObject // vtObject
DD @CvtClass // vtClass
DD @CvtWideChar // vtWideChar
DD @CvtPWideChar // vtPWideChar
DD @CvtAnsiStr // vtAnsiString
DD @CvtCurrency // vtCurrency
DD @CvtVariant // vtVariant
DD @CvtInterface // vtInterface
DD @CvtWideString // vtWideString
DD @CvtInt64 // vtInt64
@CvtBoolean:
@CvtObject:
@CvtClass:
@CvtWideChar:
@CvtInterface:
@CvtError:
XOR EAX,EAX
@ErrorExit:
CALL @ClearTmpAnsiStr
MOV EDX,FormatOrg
MOV ECX,FormatPtr
SUB ECX,EDX
{$IFDEF PC_MAPPED_EXCEPTIONS}
// Because of all the assembly code here, we can't call a routine
// that throws an exception if it looks like we're still on the
// stack. The static disassembler cannot give sufficient unwind
// frame info to unwind the confusion that is generated from the
// assembly code above. So before we throw the exception, we
// go to some lengths to excise ourselves from the stack chain.
// We were passed 12 bytes of parameters on the stack, and we have
// to make sure that we get rid of those, too.
MOV EBX, SaveGOT
MOV ESP, EBP // Ditch everthing to the frame
MOV EBP, [ESP + 4] // Get the return addr
MOV [ESP + 16], EBP // Move the ret addr up in the stack
POP EBP // Ditch the rest of the frame
ADD ESP, 12 // Ditch the space that was taken by params
JMP FormatError // Off to FormatErr
{$ELSE}
MOV EBX, SaveGOT
CALL FormatError
{$ENDIF}
// The above call raises an exception and does not return
@CvtInt64:
// CL <= format character
// EAX <= address of int64
// EBX <= TVarRec.VType
LEA ESI,StrBuf[32]
MOV EDX, Prec
CMP EDX, 32
JBE @I64_1 // zero padded field width > buffer => no padding
XOR EDX, EDX
@I64_1: MOV EBX, ECX
SUB CL, 'D'
JZ CvtInt64 // branch predict backward jump taken
MOV ECX, 16
CMP BL, 'X'
JE CvtInt64
MOV ECX, 10
CMP BL, 'U'
JE CvtInt64
JMP @CvtError
{ LEA EBX, TempInt64 // (input is array of const; save original)
MOV EDX, [EAX]
MOV [EBX], EDX
MOV EDX, [EAX + 4]
MOV [EBX + 4], EDX
// EBX <= address of TempInt64
CMP CL,'D'
JE @DecI64
CMP CL,'U'
JE @DecI64_2
CMP CL,'X'
JNE @CvtError
@HexI64:
MOV ECX,16 // hex divisor
JMP @CvtI64
@DecI64:
TEST DWORD PTR [EBX + 4], $80000000 // sign bit set?
JE @DecI64_2 // no -> bypass '-' output
NEG DWORD PTR [EBX] // negate lo-order, then hi-order
ADC DWORD PTR [EBX+4], 0
NEG DWORD PTR [EBX+4]
CALL @DecI64_2
MOV AL,'-'
INC ECX
DEC ESI
MOV [ESI],AL
RET
@DecI64_2: // unsigned int64 output
MOV ECX,10 // decimal divisor
@CvtI64:
LEA ESI,StrBuf[32]
@CvtI64_1:
PUSH EBX
PUSH ECX // save radix
PUSH 0
PUSH ECX // radix divisor (10 or 16 only)
MOV EAX, [EBX]
MOV EDX, [EBX + 4]
MOV EBX, SaveGOT
CALL System.@_llumod
POP ECX // saved radix
POP EBX
XCHG EAX, EDX // lo-value to EDX for character output
ADD DL,'0'
CMP DL,'0'+10
JB @CvtI64_2
ADD DL,('A'-'0')-10
@CvtI64_2:
DEC ESI
MOV [ESI],DL
PUSH EBX
PUSH ECX // save radix
PUSH 0
PUSH ECX // radix divisor (10 or 16 only)
MOV EAX, [EBX] // value := value DIV radix
MOV EDX, [EBX + 4]
MOV EBX, SaveGOT
CALL System.@_lludiv
POP ECX // saved radix
POP EBX
MOV [EBX], EAX
MOV [EBX + 4], EDX
OR EAX,EDX // anything left to output?
JNE @CvtI64_1 // no jump => EDX:EAX = 0
LEA ECX,StrBuf[32]
SUB ECX,ESI
MOV EDX,Prec
CMP EDX,16
JBE @CvtI64_3
RET
@CvtI64_3:
SUB EDX,ECX
JBE @CvtI64_5
ADD ECX,EDX
MOV AL,'0'
@CvtI64_4:
DEC ESI
MOV [ESI],AL
DEC EDX
JNE @CvtI64_4
@CvtI64_5:
RET
}
////////////////////////////////////////////////
@CvtInteger:
LEA ESI,StrBuf[16]
MOV EDX, Prec
MOV EBX, ECX
CMP EDX, 16
JBE @C1 // zero padded field width > buffer => no padding
XOR EDX, EDX
@C1: SUB CL, 'D'
JZ CvtInt // branch predict backward jump taken
MOV ECX, 16
CMP BL, 'X'
JE CvtInt
MOV ECX, 10
CMP BL, 'U'
JE CvtInt
JMP @CvtError
{ CMP CL,'D'
JE @C1
CMP CL,'U'
JE @C2
CMP CL,'X'
JNE @CvtError
MOV ECX,16
JMP @CvtLong
@C1: OR EAX,EAX
JNS @C2
NEG EAX
CALL @C2
MOV AL,'-'
INC ECX
DEC ESI
MOV [ESI],AL
RET
@C2: MOV ECX,10
@CvtLong:
LEA ESI,StrBuf[16]
@D1: XOR EDX,EDX
DIV ECX
ADD DL,'0'
CMP DL,'0'+10
JB @D2
ADD DL,('A'-'0')-10
@D2: DEC ESI
MOV [ESI],DL
OR EAX,EAX
JNE @D1
LEA ECX,StrBuf[16]
SUB ECX,ESI
MOV EDX,Prec
CMP EDX,16
JBE @D3
RET
@D3: SUB EDX,ECX
JBE @D5
ADD ECX,EDX
MOV AL,'0'
@D4: DEC ESI
MOV [ESI],AL
DEC EDX
JNE @D4
@D5: RET
}
@CvtChar:
CMP CL,'S'
JNE @CvtError
MOV ECX,1
RET
@CvtVariant:
CMP CL,'S'
JNE @CvtError
CMP [EAX].TVarData.VType,varNull
JBE @CvtEmptyStr
MOV EDX,EAX
LEA EAX,TempStr
// PUSH EBX // GOT setup unnecessary for
// MOV EBX, SaveGOT // same-unit calls to Pascal procedures
CALL FormatVarToStr
// POP EBX
MOV ESI,TempStr
JMP @CvtStrRef
@CvtEmptyStr:
XOR ECX,ECX
RET
@CvtShortStr:
CMP CL,'S'
JNE @CvtError
MOV ESI,EAX
LODSB
MOVZX ECX,AL
JMP @CvtStrLen
@CvtPWideChar:
MOV ESI,OFFSET System.@LStrFromPWChar
JMP @CvtWideThing
@CvtWideString:
MOV ESI,OFFSET System.@LStrFromWStr
@CvtWideThing:
ADD ESI, SaveGOT
{$IFDEF PIC}
MOV ESI, [ESI]
{$ENDIF}
CMP CL,'S'
JNE @CvtError
MOV EDX,EAX
LEA EAX,TempAnsiStr
PUSH EBX
MOV EBX, SaveGOT
CALL ESI
POP EBX
MOV ESI,TempAnsiStr
MOV EAX,ESI
JMP @CvtStrRef
@CvtAnsiStr:
CMP CL,'S'
JNE @CvtError
MOV ESI,EAX
@CvtStrRef:
OR ESI,ESI
JE @CvtEmptyStr
MOV ECX,[ESI-4]
@CvtStrLen:
CMP ECX,Prec
JA @E1
RET
@E1: MOV ECX,Prec
RET
@CvtPChar:
CMP CL,'S'
JNE @CvtError
MOV ESI,EAX
PUSH EDI
MOV EDI,EAX
XOR AL,AL
MOV ECX,Prec
JECXZ @F1
REPNE SCASB
JNE @F1
DEC EDI
@F1: MOV ECX,EDI
SUB ECX,ESI
POP EDI
RET
@CvtPointer:
CMP CL,'P'
JNE @CvtError
MOV EDX,8
MOV ECX,16
LEA ESI,StrBuf[16]
JMP CvtInt
@CvtCurrency:
MOV BH,fvCurrency
JMP @CvtFloat
@CvtExtended:
MOV BH,fvExtended
@CvtFloat:
MOV ESI,EAX
MOV BL,ffGeneral
CMP CL,'G'
JE @G2
MOV BL,ffExponent
CMP CL,'E'
JE @G2
MOV BL,ffFixed
CMP CL,'F'
JE @G1
MOV BL,ffNumber
CMP CL,'N'
JE @G1
CMP CL,'M'
JNE @CvtError
MOV BL,ffCurrency
@G1: MOV EAX,18
MOV EDX,Prec
CMP EDX,EAX
JBE @G3
MOV EDX,2
CMP CL,'M'
JNE @G3
MOVZX EDX,CurrencyDecimals
JMP @G3
@G2: MOV EAX,Prec
MOV EDX,3
CMP EAX,18
JBE @G3
MOV EAX,15
@G3: PUSH EBX
PUSH EAX
PUSH EDX
LEA EAX,StrBuf
MOV EDX,ESI
MOVZX ECX,BH
MOV EBX, SaveGOT
CALL FloatToText
MOV ECX,EAX
LEA ESI,StrBuf
RET
@ClearTmpAnsiStr:
PUSH EBX
PUSH EAX
LEA EAX,TempAnsiStr
MOV EBX, SaveGOT
CALL System.@LStrClr
POP EAX
POP EBX
RET
@Exit:
CALL @ClearTmpAnsiStr
POP EDI
POP ESI
POP EBX
end;
function FormatBuf(var Buffer; BufLen: Cardinal; const Format;
FmtLen: Cardinal; const Args: array of const;
const FormatSettings: TFormatSettings): Cardinal;
var
ArgIndex, Width, Prec: Integer;
BufferOrg, FormatOrg, FormatPtr, TempStr: PChar;
JustFlag: Byte;
StrBuf: array[0..64] of Char;
TempAnsiStr: string;
SaveGOT: Integer;
{ in: eax <-> Buffer }
{ in: edx <-> BufLen }
{ in: ecx <-> Format }
asm
PUSH EBX
PUSH ESI
PUSH EDI
MOV EDI,EAX
MOV ESI,ECX
{$IFDEF PIC}
PUSH ECX
CALL GetGOT
POP ECX
{$ELSE}
XOR EAX,EAX
{$ENDIF}
MOV SaveGOT,EAX
ADD ECX,FmtLen
MOV BufferOrg,EDI
XOR EAX,EAX
MOV ArgIndex,EAX
MOV TempStr,EAX
MOV TempAnsiStr,EAX
@Loop:
OR EDX,EDX
JE @Done
@NextChar:
CMP ESI,ECX
JE @Done
LODSB
CMP AL,'%'
JE @Format
@StoreChar:
STOSB
DEC EDX
JNE @NextChar
@Done:
MOV EAX,EDI
SUB EAX,BufferOrg
JMP @Exit
@Format:
CMP ESI,ECX
JE @Done
LODSB
CMP AL,'%'
JE @StoreChar
LEA EBX,[ESI-2]
MOV FormatOrg,EBX
@A0: MOV JustFlag,AL
CMP AL,'-'
JNE @A1
CMP ESI,ECX
JE @Done
LODSB
@A1: CALL @Specifier
CMP AL,':'
JNE @A2
MOV ArgIndex,EBX
CMP ESI,ECX
JE @Done
LODSB
JMP @A0
@A2: MOV Width,EBX
MOV EBX,-1
CMP AL,'.'
JNE @A3
CMP ESI,ECX
JE @Done
LODSB
CALL @Specifier
@A3: MOV Prec,EBX
MOV FormatPtr,ESI
PUSH ECX
PUSH EDX
CALL @Convert
POP EDX
MOV EBX,Width
SUB EBX,ECX //(* ECX <=> number of characters output *)
JAE @A4 //(* jump -> output smaller than width *)
XOR EBX,EBX
@A4: CMP JustFlag,'-'
JNE @A6
SUB EDX,ECX
JAE @A5
ADD ECX,EDX
XOR EDX,EDX
@A5: REP MOVSB
@A6: XCHG EBX,ECX
SUB EDX,ECX
JAE @A7
ADD ECX,EDX
XOR EDX,EDX
@A7: MOV AL,' '
REP STOSB
XCHG EBX,ECX
SUB EDX,ECX
JAE @A8
ADD ECX,EDX
XOR EDX,EDX
@A8: REP MOVSB
CMP TempStr,0
JE @A9
PUSH EDX
LEA EAX,TempStr
// PUSH EBX // GOT setup unnecessary for
// MOV EBX, SaveGOT // same-unit calls to Pascal procedures
CALL FormatClearStr
// POP EBX
POP EDX
@A9: POP ECX
MOV ESI,FormatPtr
JMP @Loop
@Specifier:
XOR EBX,EBX
CMP AL,'*'
JE @B3
@B1: CMP AL,'0'
JB @B5
CMP AL,'9'
JA @B5
IMUL EBX,EBX,10
SUB AL,'0'
MOVZX EAX,AL
ADD EBX,EAX
CMP ESI,ECX
JE @B2
LODSB
JMP @B1
@B2: POP EAX
JMP @Done
@B3: MOV EAX,ArgIndex
CMP EAX,Args.Integer[-4]
JG @B4
INC ArgIndex
MOV EBX,Args
CMP [EBX+EAX*8].Byte[4],vtInteger
MOV EBX,[EBX+EAX*8]
JE @B4
XOR EBX,EBX
@B4: CMP ESI,ECX
JE @B2
LODSB
@B5: RET
@Convert:
AND AL,0DFH
MOV CL,AL
MOV EAX,1
MOV EBX,ArgIndex
CMP EBX,Args.Integer[-4]
JG @ErrorExit
INC ArgIndex
MOV ESI,Args
LEA ESI,[ESI+EBX*8]
MOV EAX,[ESI].Integer[0] // TVarRec.data
MOVZX EDX,[ESI].Byte[4] // TVarRec.VType
{$IFDEF PIC}
MOV EBX, SaveGOT
ADD EBX, offset @CvtVector
MOV EBX, [EBX+EDX*4]
ADD EBX, SaveGOT
JMP EBX
{$ELSE}
JMP @CvtVector.Pointer[EDX*4]
{$ENDIF}
@CvtVector:
DD @CvtInteger // vtInteger
DD @CvtBoolean // vtBoolean
DD @CvtChar // vtChar
DD @CvtExtended // vtExtended
DD @CvtShortStr // vtString
DD @CvtPointer // vtPointer
DD @CvtPChar // vtPChar
DD @CvtObject // vtObject
DD @CvtClass // vtClass
DD @CvtWideChar // vtWideChar
DD @CvtPWideChar // vtPWideChar
DD @CvtAnsiStr // vtAnsiString
DD @CvtCurrency // vtCurrency
DD @CvtVariant // vtVariant
DD @CvtInterface // vtInterface
DD @CvtWideString // vtWideString
DD @CvtInt64 // vtInt64
@CvtBoolean:
@CvtObject:
@CvtClass:
@CvtWideChar:
@CvtInterface:
@CvtError:
XOR EAX,EAX
@ErrorExit:
CALL @ClearTmpAnsiStr
MOV EDX,FormatOrg
MOV ECX,FormatPtr
SUB ECX,EDX
{$IFDEF PC_MAPPED_EXCEPTIONS}
// Because of all the assembly code here, we can't call a routine
// that throws an exception if it looks like we're still on the
// stack. The static disassembler cannot give sufficient unwind
// frame info to unwind the confusion that is generated from the
// assembly code above. So before we throw the exception, we
// go to some lengths to excise ourselves from the stack chain.
// We were passed 12 bytes of parameters on the stack, and we have
// to make sure that we get rid of those, too.
MOV EBX, SaveGOT
MOV ESP, EBP // Ditch everthing to the frame
MOV EBP, [ESP + 4] // Get the return addr
MOV [ESP + 16], EBP // Move the ret addr up in the stack
POP EBP // Ditch the rest of the frame
ADD ESP, 12 // Ditch the space that was taken by params
JMP FormatError // Off to FormatErr
{$ELSE}
MOV EBX, SaveGOT
CALL FormatError
{$ENDIF}
// The above call raises an exception and does not return
@CvtInt64:
// CL <= format character
// EAX <= address of int64
// EBX <= TVarRec.VType
LEA ESI,StrBuf[32]
MOV EDX, Prec
CMP EDX, 32
JBE @I64_1 // zero padded field width > buffer => no padding
XOR EDX, EDX
@I64_1: MOV EBX, ECX
SUB CL, 'D'
JZ CvtInt64 // branch predict backward jump taken
MOV ECX, 16
CMP BL, 'X'
JE CvtInt64
MOV ECX, 10
CMP BL, 'U'
JE CvtInt64
JMP @CvtError
////////////////////////////////////////////////
@CvtInteger:
LEA ESI,StrBuf[16]
MOV EDX, Prec
MOV EBX, ECX
CMP EDX, 16
JBE @C1 // zero padded field width > buffer => no padding
XOR EDX, EDX
@C1: SUB CL, 'D'
JZ CvtInt // branch predict backward jump taken
MOV ECX, 16
CMP BL, 'X'
JE CvtInt
MOV ECX, 10
CMP BL, 'U'
JE CvtInt
JMP @CvtError
@CvtChar:
CMP CL,'S'
JNE @CvtError
MOV ECX,1
RET
@CvtVariant:
CMP CL,'S'
JNE @CvtError
CMP [EAX].TVarData.VType,varNull
JBE @CvtEmptyStr
MOV EDX,EAX
LEA EAX,TempStr
// PUSH EBX // GOT setup unnecessary for
// MOV EBX, SaveGOT // same-unit calls to Pascal procedures
CALL FormatVarToStr
// POP EBX
MOV ESI,TempStr
JMP @CvtStrRef
@CvtEmptyStr:
XOR ECX,ECX
RET
@CvtShortStr:
CMP CL,'S'
JNE @CvtError
MOV ESI,EAX
LODSB
MOVZX ECX,AL
JMP @CvtStrLen
@CvtPWideChar:
MOV ESI,OFFSET System.@LStrFromPWChar
JMP @CvtWideThing
@CvtWideString:
MOV ESI,OFFSET System.@LStrFromWStr
@CvtWideThing:
ADD ESI, SaveGOT
{$IFDEF PIC}
MOV ESI, [ESI]
{$ENDIF}
CMP CL,'S'
JNE @CvtError
MOV EDX,EAX
LEA EAX,TempAnsiStr
PUSH EBX
MOV EBX, SaveGOT
CALL ESI
POP EBX
MOV ESI,TempAnsiStr
MOV EAX,ESI
JMP @CvtStrRef
@CvtAnsiStr:
CMP CL,'S'
JNE @CvtError
MOV ESI,EAX
@CvtStrRef:
OR ESI,ESI
JE @CvtEmptyStr
MOV ECX,[ESI-4]
@CvtStrLen:
CMP ECX,Prec
JA @E1
RET
@E1: MOV ECX,Prec
RET
@CvtPChar:
CMP CL,'S'
JNE @CvtError
MOV ESI,EAX
PUSH EDI
MOV EDI,EAX
XOR AL,AL
MOV ECX,Prec
JECXZ @F1
REPNE SCASB
JNE @F1
DEC EDI
@F1: MOV ECX,EDI
SUB ECX,ESI
POP EDI
RET
@CvtPointer:
CMP CL,'P'
JNE @CvtError
MOV EDX,8
MOV ECX,16
LEA ESI,StrBuf[16]
JMP CvtInt
@CvtCurrency:
MOV BH,fvCurrency
JMP @CvtFloat
@CvtExtended:
MOV BH,fvExtended
@CvtFloat:
MOV ESI,EAX
MOV BL,ffGeneral
CMP CL,'G'
JE @G2
MOV BL,ffExponent
CMP CL,'E'
JE @G2
MOV BL,ffFixed
CMP CL,'F'
JE @G1
MOV BL,ffNumber
CMP CL,'N'
JE @G1
CMP CL,'M'
JNE @CvtError
MOV BL,ffCurrency
@G1: MOV EAX,18
MOV EDX,Prec
CMP EDX,EAX
JBE @G3
MOV EDX,2
CMP CL,'M'
JNE @G3
MOV EDX,FormatSettings
MOVZX EDX,[EDX].TFormatSettings.CurrencyDecimals
JMP @G3
@G2: MOV EAX,Prec
MOV EDX,3
CMP EAX,18
JBE @G3
MOV EAX,15
@G3: PUSH EBX
PUSH EAX
PUSH EDX
MOV EDX,[FormatSettings]
PUSH EDX
LEA EAX,StrBuf
MOV EDX,ESI
MOVZX ECX,BH
MOV EBX, SaveGOT
CALL FloatToTextEx
MOV ECX,EAX
LEA ESI,StrBuf
RET
@ClearTmpAnsiStr:
PUSH EBX
PUSH EAX
LEA EAX,TempAnsiStr
MOV EBX, SaveGOT
CALL System.@LStrClr
POP EAX
POP EBX
RET
@Exit:
CALL @ClearTmpAnsiStr
POP EDI
POP ESI
POP EBX
end;
function StrFmt(Buffer, Format: PChar; const Args: array of const): PChar;
begin
if (Buffer <> nil) and (Format <> nil) then
begin
Buffer[FormatBuf(Buffer^, MaxInt, Format^, StrLen(Format), Args)] := #0;
Result := Buffer;
end
else
Result := nil;
end;
function StrFmt(Buffer, Format: PChar; const Args: array of const;
const FormatSettings: TFormatSettings): PChar;
begin
if (Buffer <> nil) and (Format <> nil) then
begin
Buffer[FormatBuf(Buffer^, MaxInt, Format^, StrLen(Format), Args,
FormatSettings)] := #0;
Result := Buffer;
end
else
Result := nil;
end;
function StrLFmt(Buffer: PChar; MaxBufLen: Cardinal; Format: PChar;
const Args: array of const): PChar;
begin
if (Buffer <> nil) and (Format <> nil) then
begin
Buffer[FormatBuf(Buffer^, MaxBufLen, Format^, StrLen(Format), Args)] := #0;
Result := Buffer;
end
else
Result := nil;
end;
function StrLFmt(Buffer: PChar; MaxBufLen: Cardinal; Format: PChar;
const Args: array of const; const FormatSettings: TFormatSettings): PChar;
begin
if (Buffer <> nil) and (Format <> nil) then
begin
Buffer[FormatBuf(Buffer^, MaxBufLen, Format^, StrLen(Format), Args,
FormatSettings)] := #0;
Result := Buffer;
end
else
Result := nil;
end;
function Format(const Format: string; const Args: array of const): string;
begin
FmtStr(Result, Format, Args);
end;
function Format(const Format: string; const Args: array of const;
const FormatSettings: TFormatSettings): string;
begin
FmtStr(Result, Format, Args, FormatSettings);
end;
procedure FmtStr(var Result: string; const Format: string;
const Args: array of const);
var
Len, BufLen: Integer;
Buffer: array[0..4095] of Char;
begin
BufLen := SizeOf(Buffer);
if Length(Format) < (sizeof(Buffer) - (sizeof(Buffer) div 4)) then
Len := FormatBuf(Buffer, sizeof(Buffer) - 1, Pointer(Format)^, Length(Format), Args)
else
begin
BufLen := Length(Format);
Len := BufLen;
end;
if Len >= BufLen - 1 then
begin
while Len >= BufLen - 1 do
begin
Inc(BufLen, BufLen);
Result := ''; // prevent copying of existing data, for speed
SetLength(Result, BufLen);
Len := FormatBuf(Pointer(Result)^, BufLen - 1, Pointer(Format)^,
Length(Format), Args);
end;
SetLength(Result, Len);
end
else
SetString(Result, Buffer, Len);
end;
procedure FmtStr(var Result: string; const Format: string;
const Args: array of const; const FormatSettings: TFormatSettings);
var
Len, BufLen: Integer;
Buffer: array[0..4095] of Char;
begin
BufLen := SizeOf(Buffer);
if Length(Format) < (sizeof(Buffer) - (sizeof(Buffer) div 4)) then
Len := FormatBuf(Buffer, sizeof(Buffer) - 1, Pointer(Format)^, Length(Format),
Args, FormatSettings)
else
begin
BufLen := Length(Format);
Len := BufLen;
end;
if Len >= BufLen - 1 then
begin
while Len >= BufLen - 1 do
begin
Inc(BufLen, BufLen);
Result := ''; // prevent copying of existing data, for speed
SetLength(Result, BufLen);
Len := FormatBuf(Pointer(Result)^, BufLen - 1, Pointer(Format)^,
Length(Format), Args, FormatSettings);
end;
SetLength(Result, Len);
end
else
SetString(Result, Buffer, Len);
end;
procedure WideFormatError(ErrorCode: Integer; Format: PWideChar;
FmtLen: Cardinal);
var
WideFormat: WideString;
FormatText: string;
begin
SetLength(WideFormat, FmtLen);
SetString(WideFormat, Format, FmtLen);
FormatText := WideFormat;
FormatError(ErrorCode, PChar(FormatText), FmtLen);
end;
procedure WideFormatVarToStr(var S: WideString; const V: TVarData);
begin
{if Assigned(System.VarToWStrProc) then
System.VarToWStrProc(S, V)
else
System.Error(reVarInvalidOp); }
S:='Cutted';
end;
function WideFormatBuf(var Buffer; BufLen: Cardinal; const Format;
FmtLen: Cardinal; const Args: array of const): Cardinal;
var
ArgIndex, Width, Prec: Integer;
BufferOrg, FormatOrg, FormatPtr: PWideChar;
JustFlag: WideChar;
StrBuf: array[0..64] of WideChar;
TempWideStr: WideString;
SaveGOT: Integer;
{ in: eax <-> Buffer }
{ in: edx <-> BufLen }
{ in: ecx <-> Format }
asm
PUSH EBX
PUSH ESI
PUSH EDI
MOV EDI,EAX
MOV ESI,ECX
{$IFDEF PIC}
CALL GetGOT
{$ELSE}
XOR EAX,EAX
{$ENDIF}
MOV SaveGOT,EAX
MOV ECX,FmtLen
LEA ECX,[ECX*2+ESI]
MOV BufferOrg,EDI
XOR EAX,EAX
MOV ArgIndex,EAX
MOV TempWideStr,EAX
@Loop:
OR EDX,EDX
JE @Done
@NextChar:
CMP ESI,ECX
JE @Done
LODSW
CMP AX,'%'
JE @Format
@StoreChar:
STOSW
DEC EDX
JNE @NextChar
@Done:
MOV EAX,EDI
SUB EAX,BufferOrg
SHR EAX,1
JMP @Exit
@Format:
CMP ESI,ECX
JE @Done
LODSW
CMP AX,'%'
JE @StoreChar
LEA EBX,[ESI-4]
MOV FormatOrg,EBX
@A0: MOV JustFlag,AX
CMP AX,'-'
JNE @A1
CMP ESI,ECX
JE @Done
LODSW
@A1: CALL @Specifier
CMP AX,':'
JNE @A2
MOV ArgIndex,EBX
CMP ESI,ECX
JE @Done
LODSW
JMP @A0
@A2: MOV Width,EBX
MOV EBX,-1
CMP AX,'.'
JNE @A3
CMP ESI,ECX
JE @Done
LODSW
CALL @Specifier
@A3: MOV Prec,EBX
MOV FormatPtr,ESI
PUSH ECX
PUSH EDX
CALL @Convert
POP EDX
MOV EBX,Width
SUB EBX,ECX //(* ECX <=> number of characters output *)
JAE @A4 //(* jump -> output smaller than width *)
XOR EBX,EBX
@A4: CMP JustFlag,'-'
JNE @A6
SUB EDX,ECX
JAE @A5
ADD ECX,EDX
XOR EDX,EDX
@A5: REP MOVSW
@A6: XCHG EBX,ECX
SUB EDX,ECX
JAE @A7
ADD ECX,EDX
XOR EDX,EDX
@A7: MOV AX,' '
REP STOSW
XCHG EBX,ECX
SUB EDX,ECX
JAE @A8
ADD ECX,EDX
XOR EDX,EDX
@A8: REP MOVSW
POP ECX
MOV ESI,FormatPtr
JMP @Loop
@Specifier:
XOR EBX,EBX
CMP AX,'*'
JE @B3
@B1: CMP AX,'0'
JB @B5
CMP AX,'9'
JA @B5
IMUL EBX,EBX,10
SUB AX,'0'
MOVZX EAX,AX
ADD EBX,EAX
CMP ESI,ECX
JE @B2
LODSW
JMP @B1
@B2: POP EAX
JMP @Done
@B3: MOV EAX,ArgIndex
CMP EAX,Args.Integer[-4]
JG @B4
INC ArgIndex
MOV EBX,Args
CMP [EBX+EAX*8].Byte[4],vtInteger
MOV EBX,[EBX+EAX*8]
JE @B4
XOR EBX,EBX
@B4: CMP ESI,ECX
JE @B2
LODSW
@B5: RET
@Convert:
AND AL,0DFH
MOV CL,AL
MOV EAX,1
MOV EBX,ArgIndex
CMP EBX,Args.Integer[-4]
JG @ErrorExit
INC ArgIndex
MOV ESI,Args
LEA ESI,[ESI+EBX*8]
MOV EAX,[ESI].Integer[0] // TVarRec.data
MOVZX EDX,[ESI].Byte[4] // TVarRec.VType
{$IFDEF PIC}
MOV EBX, SaveGOT
ADD EBX, offset @CvtVector
MOV EBX, [EBX+EDX*4]
ADD EBX, SaveGOT
JMP EBX
{$ELSE}
JMP @CvtVector.Pointer[EDX*4]
{$ENDIF}
@CvtVector:
DD @CvtInteger // vtInteger
DD @CvtBoolean // vtBoolean
DD @CvtChar // vtChar
DD @CvtExtended // vtExtended
DD @CvtShortStr // vtString
DD @CvtPointer // vtPointer
DD @CvtPChar // vtPChar
DD @CvtObject // vtObject
DD @CvtClass // vtClass
DD @CvtWideChar // vtWideChar
DD @CvtPWideChar // vtPWideChar
DD @CvtAnsiStr // vtAnsiString
DD @CvtCurrency // vtCurrency
DD @CvtVariant // vtVariant
DD @CvtInterface // vtInterface
DD @CvtWideString // vtWideString
DD @CvtInt64 // vtInt64
@CvtBoolean:
@CvtObject:
@CvtClass:
@CvtInterface:
@CvtError:
XOR EAX,EAX
@ErrorExit:
CALL @ClearTmpWideStr
MOV EDX,FormatOrg
MOV ECX,FormatPtr
SUB ECX,EDX
SHR ECX,1
MOV EBX, SaveGOT
{$IFDEF PC_MAPPED_EXCEPTIONS}
// Because of all the assembly code here, we can't call a routine
// that throws an exception if it looks like we're still on the
// stack. The static disassembler cannot give sufficient unwind
// frame info to unwind the confusion that is generated from the
// assembly code above. So before we throw the exception, we
// go to some lengths to excise ourselves from the stack chain.
// We were passed 12 bytes of parameters on the stack, and we have
// to make sure that we get rid of those, too.
MOV ESP, EBP // Ditch everthing to the frame
MOV EBP, [ESP + 4] // Get the return addr
MOV [ESP + 16], EBP // Move the ret addr up in the stack
POP EBP // Ditch the rest of the frame
ADD ESP, 12 // Ditch the space that was taken by params
JMP WideFormatError // Off to FormatErr
{$ELSE}
CALL WideFormatError
{$ENDIF}
// The above call raises an exception and does not return
@CvtInt64:
// CL <= format character
// EAX <= address of int64
// EBX <= TVarRec.VType
LEA ESI,StrBuf[64]
MOV EDX, Prec
CMP EDX, 32
JBE @I64_1 // zero padded field width > buffer => no padding
XOR EDX, EDX
@I64_1: MOV EBX, ECX
SUB CL, 'D'
JZ CvtInt64W // branch predict backward jump taken
MOV ECX, 16
CMP BL, 'X'
JE CvtInt64W
MOV ECX, 10
CMP BL, 'U'
JE CvtInt64W
JMP @CvtError
@CvtInteger:
LEA ESI,StrBuf[32]
MOV EDX, Prec
MOV EBX, ECX
CMP EDX, 16
JBE @C1 // zero padded field width > buffer => no padding
XOR EDX, EDX
@C1: SUB CL, 'D'
JZ CvtIntW // branch predict backward jump taken
MOV ECX, 16
CMP BL, 'X'
JE CvtIntW
MOV ECX, 10
CMP BL, 'U'
JE CvtIntW
JMP @CvtError
@CvtChar:
CMP CL,'S'
JNE @CvtError
MOV EAX,ESI
MOV ECX,1
JMP @CvtAnsiThingLen
@CvtWideChar:
CMP CL,'S'
JNE @CvtError
MOV ECX,1
RET
@CvtVariant:
CMP CL,'S'
JNE @CvtError
CMP [EAX].TVarData.VType,varNull
JBE @CvtEmptyStr
MOV EDX,EAX
LEA EAX,TempWideStr
CALL WideFormatVarToStr
MOV ESI,TempWideStr
JMP @CvtWideStrRef
@CvtEmptyStr:
XOR ECX,ECX
RET
@CvtShortStr:
CMP CL,'S'
JNE @CvtError
MOVZX ECX,BYTE PTR [EAX]
INC EAX
@CvtAnsiThingLen:
MOV ESI,OFFSET System.@WStrFromPCharLen
JMP @CvtAnsiThing
@CvtPChar:
MOV ESI,OFFSET System.@WStrFromPChar
JMP @CvtAnsiThingTest
@CvtAnsiStr:
MOV ESI,OFFSET System.@WStrFromLStr
@CvtAnsiThingTest:
CMP CL,'S'
JNE @CvtError
@CvtAnsiThing:
ADD ESI, SaveGOT
{$IFDEF PIC}
MOV ESI, [ESI]
{$ENDIF}
MOV EDX,EAX
LEA EAX,TempWideStr
PUSH EBX
MOV EBX, SaveGOT
CALL ESI
POP EBX
MOV ESI,TempWideStr
JMP @CvtWideStrRef
@CvtWideString:
CMP CL,'S'
JNE @CvtError
MOV ESI,EAX
@CvtWideStrRef:
OR ESI,ESI
JE @CvtEmptyStr
MOV ECX,[ESI-4]
SHR ECX,1
@CvtWideStrLen:
CMP ECX,Prec
JA @E1
RET
@E1: MOV ECX,Prec
RET
@CvtPWideChar:
CMP CL,'S'
JNE @CvtError
MOV ESI,EAX
PUSH EDI
MOV EDI,EAX
XOR EAX,EAX
MOV ECX,Prec
JECXZ @F1
REPNE SCASW
JNE @F1
DEC EDI
DEC EDI
@F1: MOV ECX,EDI
SUB ECX,ESI
SHR ECX,1
POP EDI
RET
@CvtPointer:
CMP CL,'P'
JNE @CvtError
MOV EDX,8
MOV ECX,16
LEA ESI,StrBuf[32]
JMP CvtInt
@CvtCurrency:
MOV BH,fvCurrency
JMP @CvtFloat
@CvtExtended:
MOV BH,fvExtended
@CvtFloat:
MOV ESI,EAX
MOV BL,ffGeneral
CMP CL,'G'
JE @G2
MOV BL,ffExponent
CMP CL,'E'
JE @G2
MOV BL,ffFixed
CMP CL,'F'
JE @G1
MOV BL,ffNumber
CMP CL,'N'
JE @G1
CMP CL,'M'
JNE @CvtError
MOV BL,ffCurrency
@G1: MOV EAX,18
MOV EDX,Prec
CMP EDX,EAX
JBE @G3
MOV EDX,2
CMP CL,'M'
JNE @G3
MOVZX EDX,CurrencyDecimals
JMP @G3
@G2: MOV EAX,Prec
MOV EDX,3
CMP EAX,18
JBE @G3
MOV EAX,15
@G3: PUSH EBX
PUSH EAX
PUSH EDX
LEA EAX,StrBuf
MOV EDX,ESI
MOVZX ECX,BH
MOV EBX, SaveGOT
CALL FloatToText
MOV ECX,EAX
LEA EDX,StrBuf
LEA EAX,TempWideStr
MOV EBX, SaveGOT
CALL System.@WStrFromPCharLen
MOV ESI,TempWideStr
OR ESI,ESI
JE @CvtEmptyStr
MOV ECX,[ESI-4]
SHR ECX,1
RET
@ClearTmpWideStr:
PUSH EBX
PUSH EAX
LEA EAX,TempWideStr
MOV EBX, SaveGOT
CALL System.@WStrClr
POP EAX
POP EBX
RET
@Exit:
CALL @ClearTmpWideStr
POP EDI
POP ESI
POP EBX
end;
function WideFormatBuf(var Buffer; BufLen: Cardinal; const Format;
FmtLen: Cardinal; const Args: array of const;
const FormatSettings: TFormatSettings): Cardinal;
var
ArgIndex, Width, Prec: Integer;
BufferOrg, FormatOrg, FormatPtr: PWideChar;
JustFlag: WideChar;
StrBuf: array[0..64] of WideChar;
TempWideStr: WideString;
SaveGOT: Integer;
{ in: eax <-> Buffer }
{ in: edx <-> BufLen }
{ in: ecx <-> Format }
asm
PUSH EBX
PUSH ESI
PUSH EDI
MOV EDI,EAX
MOV ESI,ECX
{$IFDEF PIC}
CALL GetGOT
{$ELSE}
XOR EAX,EAX
{$ENDIF}
MOV SaveGOT,EAX
MOV ECX,FmtLen
LEA ECX,[ECX*2+ESI]
MOV BufferOrg,EDI
XOR EAX,EAX
MOV ArgIndex,EAX
MOV TempWideStr,EAX
@Loop:
OR EDX,EDX
JE @Done
@NextChar:
CMP ESI,ECX
JE @Done
LODSW
CMP AX,'%'
JE @Format
@StoreChar:
STOSW
DEC EDX
JNE @NextChar
@Done:
MOV EAX,EDI
SUB EAX,BufferOrg
SHR EAX,1
JMP @Exit
@Format:
CMP ESI,ECX
JE @Done
LODSW
CMP AX,'%'
JE @StoreChar
LEA EBX,[ESI-4]
MOV FormatOrg,EBX
@A0: MOV JustFlag,AX
CMP AX,'-'
JNE @A1
CMP ESI,ECX
JE @Done
LODSW
@A1: CALL @Specifier
CMP AX,':'
JNE @A2
MOV ArgIndex,EBX
CMP ESI,ECX
JE @Done
LODSW
JMP @A0
@A2: MOV Width,EBX
MOV EBX,-1
CMP AX,'.'
JNE @A3
CMP ESI,ECX
JE @Done
LODSW
CALL @Specifier
@A3: MOV Prec,EBX
MOV FormatPtr,ESI
PUSH ECX
PUSH EDX
CALL @Convert
POP EDX
MOV EBX,Width
SUB EBX,ECX //(* ECX <=> number of characters output *)
JAE @A4 //(* jump -> output smaller than width *)
XOR EBX,EBX
@A4: CMP JustFlag,'-'
JNE @A6
SUB EDX,ECX
JAE @A5
ADD ECX,EDX
XOR EDX,EDX
@A5: REP MOVSW
@A6: XCHG EBX,ECX
SUB EDX,ECX
JAE @A7
ADD ECX,EDX
XOR EDX,EDX
@A7: MOV AX,' '
REP STOSW
XCHG EBX,ECX
SUB EDX,ECX
JAE @A8
ADD ECX,EDX
XOR EDX,EDX
@A8: REP MOVSW
POP ECX
MOV ESI,FormatPtr
JMP @Loop
@Specifier:
XOR EBX,EBX
CMP AX,'*'
JE @B3
@B1: CMP AX,'0'
JB @B5
CMP AX,'9'
JA @B5
IMUL EBX,EBX,10
SUB AX,'0'
MOVZX EAX,AX
ADD EBX,EAX
CMP ESI,ECX
JE @B2
LODSW
JMP @B1
@B2: POP EAX
JMP @Done
@B3: MOV EAX,ArgIndex
CMP EAX,Args.Integer[-4]
JG @B4
INC ArgIndex
MOV EBX,Args
CMP [EBX+EAX*8].Byte[4],vtInteger
MOV EBX,[EBX+EAX*8]
JE @B4
XOR EBX,EBX
@B4: CMP ESI,ECX
JE @B2
LODSW
@B5: RET
@Convert:
AND AL,0DFH
MOV CL,AL
MOV EAX,1
MOV EBX,ArgIndex
CMP EBX,Args.Integer[-4]
JG @ErrorExit
INC ArgIndex
MOV ESI,Args
LEA ESI,[ESI+EBX*8]
MOV EAX,[ESI].Integer[0] // TVarRec.data
MOVZX EDX,[ESI].Byte[4] // TVarRec.VType
{$IFDEF PIC}
MOV EBX, SaveGOT
ADD EBX, offset @CvtVector
MOV EBX, [EBX+EDX*4]
ADD EBX, SaveGOT
JMP EBX
{$ELSE}
JMP @CvtVector.Pointer[EDX*4]
{$ENDIF}
@CvtVector:
DD @CvtInteger // vtInteger
DD @CvtBoolean // vtBoolean
DD @CvtChar // vtChar
DD @CvtExtended // vtExtended
DD @CvtShortStr // vtString
DD @CvtPointer // vtPointer
DD @CvtPChar // vtPChar
DD @CvtObject // vtObject
DD @CvtClass // vtClass
DD @CvtWideChar // vtWideChar
DD @CvtPWideChar // vtPWideChar
DD @CvtAnsiStr // vtAnsiString
DD @CvtCurrency // vtCurrency
DD @CvtVariant // vtVariant
DD @CvtInterface // vtInterface
DD @CvtWideString // vtWideString
DD @CvtInt64 // vtInt64
@CvtBoolean:
@CvtObject:
@CvtClass:
@CvtInterface:
@CvtError:
XOR EAX,EAX
@ErrorExit:
CALL @ClearTmpWideStr
MOV EDX,FormatOrg
MOV ECX,FormatPtr
SUB ECX,EDX
SHR ECX,1
MOV EBX, SaveGOT
{$IFDEF PC_MAPPED_EXCEPTIONS}
// Because of all the assembly code here, we can't call a routine
// that throws an exception if it looks like we're still on the
// stack. The static disassembler cannot give sufficient unwind
// frame info to unwind the confusion that is generated from the
// assembly code above. So before we throw the exception, we
// go to some lengths to excise ourselves from the stack chain.
// We were passed 12 bytes of parameters on the stack, and we have
// to make sure that we get rid of those, too.
MOV ESP, EBP // Ditch everthing to the frame
MOV EBP, [ESP + 4] // Get the return addr
MOV [ESP + 16], EBP // Move the ret addr up in the stack
POP EBP // Ditch the rest of the frame
ADD ESP, 12 // Ditch the space that was taken by params
JMP WideFormatError // Off to FormatErr
{$ELSE}
CALL WideFormatError
{$ENDIF}
// The above call raises an exception and does not return
@CvtInt64:
// CL <= format character
// EAX <= address of int64
// EBX <= TVarRec.VType
LEA ESI,StrBuf[64]
MOV EDX,Prec
CMP EDX, 32
JBE @I64_1 // zero padded field width > buffer => no padding
XOR EDX, EDX
@I64_1: MOV EBX, ECX
SUB CL, 'D'
JZ CvtInt64W // branch predict backward jump taken
MOV ECX,16
CMP BL, 'X'
JE CvtInt64W
MOV ECX, 10
CMP BL, 'U'
JE CvtInt64W
JMP @CvtError
@CvtInteger:
LEA ESI,StrBuf[32]
MOV EDX,Prec
MOV EBX, ECX
CMP EDX,16
JBE @C1 // zero padded field width > buffer => no padding
XOR EDX, EDX
@C1: SUB CL, 'D'
JZ CvtIntW // branch predict backward jump taken
MOV ECX, 16
CMP BL, 'X'
JE CvtIntW
MOV ECX, 10
CMP BL, 'U'
JE CvtIntW
JMP @CvtError
@CvtChar:
CMP CL,'S'
JNE @CvtError
MOV EAX,ESI
MOV ECX,1
JMP @CvtAnsiThingLen
@CvtWideChar:
CMP CL,'S'
JNE @CvtError
MOV ECX,1
RET
@CvtVariant:
CMP CL,'S'
JNE @CvtError
CMP [EAX].TVarData.VType,varNull
JBE @CvtEmptyStr
MOV EDX,EAX
LEA EAX,TempWideStr
CALL WideFormatVarToStr
MOV ESI,TempWideStr
JMP @CvtWideStrRef
@CvtEmptyStr:
XOR ECX,ECX
RET
@CvtShortStr:
CMP CL,'S'
JNE @CvtError
MOVZX ECX,BYTE PTR [EAX]
INC EAX
@CvtAnsiThingLen:
MOV ESI,OFFSET System.@WStrFromPCharLen
JMP @CvtAnsiThing
@CvtPChar:
MOV ESI,OFFSET System.@WStrFromPChar
JMP @CvtAnsiThingTest
@CvtAnsiStr:
MOV ESI,OFFSET System.@WStrFromLStr
@CvtAnsiThingTest:
CMP CL,'S'
JNE @CvtError
@CvtAnsiThing:
ADD ESI, SaveGOT
{$IFDEF PIC}
MOV ESI, [ESI]
{$ENDIF}
MOV EDX,EAX
LEA EAX,TempWideStr
PUSH EBX
MOV EBX, SaveGOT
CALL ESI
POP EBX
MOV ESI,TempWideStr
JMP @CvtWideStrRef
@CvtWideString:
CMP CL,'S'
JNE @CvtError
MOV ESI,EAX
@CvtWideStrRef:
OR ESI,ESI
JE @CvtEmptyStr
MOV ECX,[ESI-4]
SHR ECX,1
@CvtWideStrLen:
CMP ECX,Prec
JA @E1
RET
@E1: MOV ECX,Prec
RET
@CvtPWideChar:
CMP CL,'S'
JNE @CvtError
MOV ESI,EAX
PUSH EDI
MOV EDI,EAX
XOR EAX,EAX
MOV ECX,Prec
JECXZ @F1
REPNE SCASW
JNE @F1
DEC EDI
DEC EDI
@F1: MOV ECX,EDI
SUB ECX,ESI
SHR ECX,1
POP EDI
RET
@CvtPointer:
CMP CL,'P'
JNE @CvtError
MOV EDX,8
MOV ECX,16
LEA ESI,StrBuf[32]
JMP CvtInt
@CvtCurrency:
MOV BH,fvCurrency
JMP @CvtFloat
@CvtExtended:
MOV BH,fvExtended
@CvtFloat:
MOV ESI,EAX
MOV BL,ffGeneral
CMP CL,'G'
JE @G2
MOV BL,ffExponent
CMP CL,'E'
JE @G2
MOV BL,ffFixed
CMP CL,'F'
JE @G1
MOV BL,ffNumber
CMP CL,'N'
JE @G1
CMP CL,'M'
JNE @CvtError
MOV BL,ffCurrency
@G1: MOV EAX,18
MOV EDX,Prec
CMP EDX,EAX
JBE @G3
MOV EDX,2
CMP CL,'M'
JNE @G3
MOV EDX,FormatSettings
MOVZX EDX,[EDX].TFormatSettings.CurrencyDecimals
JMP @G3
@G2: MOV EAX,Prec
MOV EDX,3
CMP EAX,18
JBE @G3
MOV EAX,15
@G3: PUSH EBX
PUSH EAX
PUSH EDX
MOV EDX,[FormatSettings]
PUSH EDX
LEA EAX,StrBuf
MOV EDX,ESI
MOVZX ECX,BH
MOV EBX, SaveGOT
CALL FloatToTextEx
MOV ECX,EAX
LEA EDX,StrBuf
LEA EAX,TempWideStr
MOV EBX, SaveGOT
CALL System.@WStrFromPCharLen
MOV ESI,TempWideStr
OR ESI,ESI
JE @CvtEmptyStr
MOV ECX,[ESI-4]
SHR ECX,1
RET
@ClearTmpWideStr:
PUSH EBX
PUSH EAX
LEA EAX,TempWideStr
MOV EBX, SaveGOT
CALL System.@WStrClr
POP EAX
POP EBX
RET
@Exit:
CALL @ClearTmpWideStr
POP EDI
POP ESI
POP EBX
end;
procedure WideFmtStr(var Result: WideString; const Format: WideString;
const Args: array of const);
const
BufSize = 2048;
var
Len, BufLen: Integer;
Buffer: array[0..BufSize-1] of WideChar;
begin
if Length(Format) < (BufSize - (BufSize div 4)) then
begin
BufLen := BufSize;
Len := WideFormatBuf(Buffer, BufSize - 1, Pointer(Format)^, Length(Format), Args);
if Len < BufLen - 1 then
begin
SetString(Result, Buffer, Len);
Exit;
end;
end
else
begin
BufLen := Length(Format);
Len := BufLen;
end;
while Len >= BufLen - 1 do
begin
Inc(BufLen, BufLen);
Result := ''; // prevent copying of existing data, for speed
SetLength(Result, BufLen);
Len := WideFormatBuf(Pointer(Result)^, BufLen - 1, Pointer(Format)^,
Length(Format), Args);
end;
SetLength(Result, Len);
end;
procedure WideFmtStr(var Result: WideString; const Format: WideString;
const Args: array of const; const FormatSettings: TFormatSettings);
const
BufSize = 2048;
var
Len, BufLen: Integer;
Buffer: array[0..BufSize-1] of WideChar;
begin
if Length(Format) < (BufSize - (BufSize div 4)) then
begin
BufLen := BufSize;
Len := WideFormatBuf(Buffer, BufSize - 1, Pointer(Format)^,
Length(Format), Args, FormatSettings);
if Len < BufLen - 1 then
begin
SetString(Result, Buffer, Len);
Exit;
end;
end
else
begin
BufLen := Length(Format);
Len := BufLen;
end;
while Len >= BufLen - 1 do
begin
Inc(BufLen, BufLen);
Result := ''; // prevent copying of existing data, for speed
SetLength(Result, BufLen);
Len := WideFormatBuf(Pointer(Result)^, BufLen - 1, Pointer(Format)^,
Length(Format), Args, FormatSettings);
end;
SetLength(Result, Len);
end;
function WideFormat(const Format: WideString; const Args: array of const): WideString;
begin
WideFmtStr(Result, Format, Args);
end;
function WideFormat(const Format: WideString; const Args: array of const;
const FormatSettings: TFormatSettings): WideString;
begin
WideFmtStr(Result, Format, Args, FormatSettings);
end;
{ Floating point conversion routines }
const
// 1E18 as a 64-bit integer
Const1E18Lo = $0A7640000;
Const1E18Hi = $00DE0B6B3;
FCon1E18: Extended = 1E18;
DCon10: Integer = 10;
procedure PutExponent;
// Store exponent
// In AL = Exponent character ('E' or 'e')
// AH = Positive sign character ('+' or 0)
// BL = Zero indicator
// ECX = Minimum number of digits (0..4)
// EDX = Exponent
// EDI = Destination buffer
asm
PUSH ESI
{$IFDEF PIC}
PUSH EAX
PUSH ECX
CALL GetGOT
MOV ESI,EAX
POP ECX
POP EAX
{$ELSE}
XOR ESI,ESI
{$ENDIF}
STOSB
OR BL,BL
JNE @@0
XOR EDX,EDX
JMP @@1
@@0: OR EDX,EDX
JGE @@1
MOV AL,'-'
NEG EDX
JMP @@2
@@1: OR AH,AH
JE @@3
MOV AL,AH
@@2: STOSB
@@3: XCHG EAX,EDX
PUSH EAX
MOV EBX,ESP
@@4: XOR EDX,EDX
DIV [ESI].DCon10
ADD DL,'0'
MOV [EBX],DL
INC EBX
DEC ECX
OR EAX,EAX
JNE @@4
OR ECX,ECX
JG @@4
@@5: DEC EBX
MOV AL,[EBX]
STOSB
CMP EBX,ESP
JNE @@5
POP EAX
POP ESI
end;
function FloatToText(BufferArg: PChar; const Value; ValueType: TFloatValue;
Format: TFloatFormat; Precision, Digits: Integer): Integer;
var
Buffer: Cardinal;
FloatRec: TFloatRec;
SaveGOT: Integer;
DecimalSep: Char;
ThousandSep: Char;
CurrencyStr: Pointer;
CurrFmt: Byte;
NegCurrFmt: Byte;
asm
PUSH EDI
PUSH ESI
PUSH EBX
MOV Buffer,EAX
{$IFDEF PIC}
PUSH ECX
CALL GetGOT
MOV SaveGOT,EAX
MOV ECX,[EAX].OFFSET DecimalSeparator
MOV CL,[ECX]
MOV DecimalSep,CL
MOV ECX,[EAX].OFFSET ThousandSeparator
MOV CL,[ECX].Byte
MOV ThousandSep,CL
MOV ECX,[EAX].OFFSET CurrencyString
MOV ECX,[ECX].Integer
MOV CurrencyStr,ECX
MOV ECX,[EAX].OFFSET CurrencyFormat
MOV CL,[ECX].Byte
MOV CurrFmt,CL
MOV ECX,[EAX].OFFSET NegCurrFormat
MOV CL,[ECX].Byte
MOV NegCurrFmt,CL
POP ECX
{$ELSE}
MOV AL,DecimalSeparator
MOV DecimalSep,AL
MOV AL,ThousandSeparator
MOV ThousandSep,AL
MOV EAX,CurrencyString
MOV CurrencyStr,EAX
MOV AL,CurrencyFormat
MOV CurrFmt,AL
MOV AL,NegCurrFormat
MOV NegCurrFmt,AL
MOV SaveGOT,0
{$ENDIF}
MOV EAX,19
CMP CL,fvExtended
JNE @@2
MOV EAX,Precision
CMP EAX,2
JGE @@1
MOV EAX,2
@@1: CMP EAX,18
JLE @@2
MOV EAX,18
@@2: MOV Precision,EAX
PUSH EAX
MOV EAX,9999
CMP Format,ffFixed
JB @@3
MOV EAX,Digits
@@3: PUSH EAX
LEA EAX,FloatRec
CALL FloatToDecimal
MOV EDI,Buffer
MOVZX EAX,FloatRec.Exponent
SUB EAX,7FFFH
CMP EAX,2
JAE @@4
MOV ECX, EAX
CALL @@PutSign
LEA ESI,@@INFNAN[ECX+ECX*2]
ADD ESI,SaveGOT
MOV ECX,3
REP MOVSB
JMP @@7
@@4: LEA ESI,FloatRec.Digits
MOVZX EBX,Format
CMP BL,ffExponent
JE @@6
CMP BL,ffCurrency
JA @@5
MOVSX EAX,FloatRec.Exponent
CMP EAX,Precision
JLE @@6
@@5: MOV BL,ffGeneral
@@6: LEA EBX,@@FormatVector[EBX*4]
ADD EBX,SaveGOT
MOV EBX,[EBX]
ADD EBX,SaveGOT
CALL EBX
@@7: MOV EAX,EDI
SUB EAX,Buffer
POP EBX
POP ESI
POP EDI
JMP @@Exit
@@FormatVector:
DD @@PutFGeneral
DD @@PutFExponent
DD @@PutFFixed
DD @@PutFNumber
DD @@PutFCurrency
@@INFNAN: DB 'INFNAN'
// Get digit or '0' if at end of digit string
@@GetDigit:
LODSB
OR AL,AL
JNE @@a1
MOV AL,'0'
DEC ESI
@@a1: RET
// Store '-' if number is negative
@@PutSign:
CMP FloatRec.Negative,0
JE @@b1
MOV AL,'-'
STOSB
@@b1: RET
// Convert number using ffGeneral format
@@PutFGeneral:
CALL @@PutSign
MOVSX ECX,FloatRec.Exponent
XOR EDX,EDX
CMP ECX,Precision
JG @@c1
CMP ECX,-3
JL @@c1
OR ECX,ECX
JG @@c2
MOV AL,'0'
STOSB
CMP BYTE PTR [ESI],0
JE @@c6
MOV AL,DecimalSep
STOSB
NEG ECX
MOV AL,'0'
REP STOSB
JMP @@c3
@@c1: MOV ECX,1
INC EDX
@@c2: LODSB
OR AL,AL
JE @@c4
STOSB
LOOP @@c2
LODSB
OR AL,AL
JE @@c5
MOV AH,AL
MOV AL,DecimalSep
STOSW
@@c3: LODSB
OR AL,AL
JE @@c5
STOSB
JMP @@c3
@@c4: MOV AL,'0'
REP STOSB
@@c5: OR EDX,EDX
JE @@c6
XOR EAX,EAX
JMP @@PutFloatExpWithDigits
@@c6: RET
// Convert number using ffExponent format
@@PutFExponent:
CALL @@PutSign
CALL @@GetDigit
MOV AH,DecimalSep
STOSW
MOV ECX,Precision
DEC ECX
@@d1: CALL @@GetDigit
STOSB
LOOP @@d1
MOV AH,'+'
@@PutFloatExpWithDigits:
MOV ECX,Digits
CMP ECX,4
JBE @@PutFloatExp
XOR ECX,ECX
// Store exponent
// In AH = Positive sign character ('+' or 0)
// ECX = Minimum number of digits (0..4)
@@PutFloatExp:
MOV AL,'E'
MOV BL, FloatRec.Digits.Byte
MOVSX EDX,FloatRec.Exponent
DEC EDX
CALL PutExponent
RET
// Convert number using ffFixed or ffNumber format
@@PutFFixed:
@@PutFNumber:
CALL @@PutSign
// Store number in fixed point format
@@PutNumber:
MOV EDX,Digits
CMP EDX,18
JB @@f1
MOV EDX,18
@@f1: MOVSX ECX,FloatRec.Exponent
OR ECX,ECX
JG @@f2
MOV AL,'0'
STOSB
JMP @@f4
@@f2: XOR EBX,EBX
CMP Format,ffFixed
JE @@f3
MOV EAX,ECX
DEC EAX
MOV BL,3
DIV BL
MOV BL,AH
INC EBX
@@f3: CALL @@GetDigit
STOSB
DEC ECX
JE @@f4
DEC EBX
JNE @@f3
MOV AL,ThousandSep
TEST AL,AL
JZ @@f3
STOSB
MOV BL,3
JMP @@f3
@@f4: OR EDX,EDX
JE @@f7
MOV AL,DecimalSep
TEST AL,AL
JZ @@f4b
STOSB
@@f4b: JECXZ @@f6
MOV AL,'0'
@@f5: STOSB
DEC EDX
JE @@f7
INC ECX
JNE @@f5
@@f6: CALL @@GetDigit
STOSB
DEC EDX
JNE @@f6
@@f7: RET
// Convert number using ffCurrency format
@@PutFCurrency:
XOR EBX,EBX
MOV BL,CurrFmt.Byte
MOV ECX,0003H
CMP FloatRec.Negative,0
JE @@g1
MOV BL,NegCurrFmt.Byte
MOV ECX,040FH
@@g1: CMP BL,CL
JBE @@g2
MOV BL,CL
@@g2: ADD BL,CH
LEA EBX,@@MoneyFormats[EBX+EBX*4]
ADD EBX,SaveGOT
MOV ECX,5
@@g10: MOV AL,[EBX]
CMP AL,'@'
JE @@g14
PUSH ECX
PUSH EBX
CMP AL,'$'
JE @@g11
CMP AL,'*'
JE @@g12
STOSB
JMP @@g13
@@g11: CALL @@PutCurSym
JMP @@g13
@@g12: CALL @@PutNumber
@@g13: POP EBX
POP ECX
INC EBX
LOOP @@g10
@@g14: RET
// Store currency symbol string
@@PutCurSym:
PUSH ESI
MOV ESI,CurrencyStr
TEST ESI,ESI
JE @@h1
MOV ECX,[ESI-4]
REP MOVSB
@@h1: POP ESI
RET
// Currency formatting templates
@@MoneyFormats:
DB '$*@@@'
DB '*$@@@'
DB '$ *@@'
DB '* $@@'
DB '($*)@'
DB '-$*@@'
DB '$-*@@'
DB '$*-@@'
DB '(*$)@'
DB '-*$@@'
DB '*-$@@'
DB '*$-@@'
DB '-* $@'
DB '-$ *@'
DB '* $-@'
DB '$ *-@'
DB '$ -*@'
DB '*- $@'
DB '($ *)'
DB '(* $)'
@@Exit:
end;
function FloatToText(BufferArg: PChar; const Value; ValueType: TFloatValue;
Format: TFloatFormat; Precision, Digits: Integer;
const FormatSettings: TFormatSettings): Integer;
var
Buffer: Cardinal;
FloatRec: TFloatRec;
SaveGOT: Integer;
DecimalSep: Char;
ThousandSep: Char;
CurrencyStr: Pointer;
CurrFmt: Byte;
NegCurrFmt: Byte;
asm
PUSH EDI
PUSH ESI
PUSH EBX
MOV Buffer,EAX
{$IFDEF PIC}
PUSH ECX
CALL GetGOT
MOV SaveGOT,EAX
POP ECX
{$ENDIF}
MOV EAX,FormatSettings
MOV AL,[EAX].TFormatSettings.DecimalSeparator
MOV DecimalSep,AL
MOV EAX,FormatSettings
MOV AL,[EAX].TFormatSettings.ThousandSeparator
MOV ThousandSep,AL
MOV EAX,FormatSettings
MOV EAX,[EAX].TFormatSettings.CurrencyString
MOV CurrencyStr,EAX
MOV EAX,FormatSettings
MOV AL,[EAX].TFormatSettings.CurrencyFormat
MOV CurrFmt,AL
MOV EAX,FormatSettings
MOV AL,[EAX].TFormatSettings.NegCurrFormat
MOV NegCurrFmt,AL
MOV SaveGOT,0
MOV EAX,19
CMP CL,fvExtended
JNE @@2
MOV EAX,Precision
CMP EAX,2
JGE @@1
MOV EAX,2
@@1: CMP EAX,18
JLE @@2
MOV EAX,18
@@2: MOV Precision,EAX
PUSH EAX
MOV EAX,9999
CMP Format,ffFixed
JB @@3
MOV EAX,Digits
@@3: PUSH EAX
LEA EAX,FloatRec
CALL FloatToDecimal
MOV EDI,Buffer
MOVZX EAX,FloatRec.Exponent
SUB EAX,7FFFH
CMP EAX,2
JAE @@4
MOV ECX, EAX
CALL @@PutSign
LEA ESI,@@INFNAN[ECX+ECX*2]
ADD ESI,SaveGOT
MOV ECX,3
REP MOVSB
JMP @@7
@@4: LEA ESI,FloatRec.Digits
MOVZX EBX,Format
CMP BL,ffExponent
JE @@6
CMP BL,ffCurrency
JA @@5
MOVSX EAX,FloatRec.Exponent
CMP EAX,Precision
JLE @@6
@@5: MOV BL,ffGeneral
@@6: LEA EBX,@@FormatVector[EBX*4]
ADD EBX,SaveGOT
MOV EBX,[EBX]
ADD EBX,SaveGOT
CALL EBX
@@7: MOV EAX,EDI
SUB EAX,Buffer
POP EBX
POP ESI
POP EDI
JMP @@Exit
@@FormatVector:
DD @@PutFGeneral
DD @@PutFExponent
DD @@PutFFixed
DD @@PutFNumber
DD @@PutFCurrency
@@INFNAN: DB 'INFNAN'
// Get digit or '0' if at end of digit string
@@GetDigit:
LODSB
OR AL,AL
JNE @@a1
MOV AL,'0'
DEC ESI
@@a1: RET
// Store '-' if number is negative
@@PutSign:
CMP FloatRec.Negative,0
JE @@b1
MOV AL,'-'
STOSB
@@b1: RET
// Convert number using ffGeneral format
@@PutFGeneral:
CALL @@PutSign
MOVSX ECX,FloatRec.Exponent
XOR EDX,EDX
CMP ECX,Precision
JG @@c1
CMP ECX,-3
JL @@c1
OR ECX,ECX
JG @@c2
MOV AL,'0'
STOSB
CMP BYTE PTR [ESI],0
JE @@c6
MOV AL,DecimalSep
STOSB
NEG ECX
MOV AL,'0'
REP STOSB
JMP @@c3
@@c1: MOV ECX,1
INC EDX
@@c2: LODSB
OR AL,AL
JE @@c4
STOSB
LOOP @@c2
LODSB
OR AL,AL
JE @@c5
MOV AH,AL
MOV AL,DecimalSep
STOSW
@@c3: LODSB
OR AL,AL
JE @@c5
STOSB
JMP @@c3
@@c4: MOV AL,'0'
REP STOSB
@@c5: OR EDX,EDX
JE @@c6
XOR EAX,EAX
JMP @@PutFloatExpWithDigits
@@c6: RET
// Convert number using ffExponent format
@@PutFExponent:
CALL @@PutSign
CALL @@GetDigit
MOV AH,DecimalSep
STOSW
MOV ECX,Precision
DEC ECX
@@d1: CALL @@GetDigit
STOSB
LOOP @@d1
MOV AH,'+'
@@PutFloatExpWithDigits:
MOV ECX,Digits
CMP ECX,4
JBE @@PutFloatExp
XOR ECX,ECX
// Store exponent
// In AH = Positive sign character ('+' or 0)
// ECX = Minimum number of digits (0..4)
@@PutFloatExp:
MOV AL,'E'
MOV BL, FloatRec.Digits.Byte
MOVSX EDX,FloatRec.Exponent
DEC EDX
CALL PutExponent
RET
// Convert number using ffFixed or ffNumber format
@@PutFFixed:
@@PutFNumber:
CALL @@PutSign
// Store number in fixed point format
@@PutNumber:
MOV EDX,Digits
CMP EDX,18
JB @@f1
MOV EDX,18
@@f1: MOVSX ECX,FloatRec.Exponent
OR ECX,ECX
JG @@f2
MOV AL,'0'
STOSB
JMP @@f4
@@f2: XOR EBX,EBX
CMP Format,ffFixed
JE @@f3
MOV EAX,ECX
DEC EAX
MOV BL,3
DIV BL
MOV BL,AH
INC EBX
@@f3: CALL @@GetDigit
STOSB
DEC ECX
JE @@f4
DEC EBX
JNE @@f3
MOV AL,ThousandSep
TEST AL,AL
JZ @@f3
STOSB
MOV BL,3
JMP @@f3
@@f4: OR EDX,EDX
JE @@f7
MOV AL,DecimalSep
TEST AL,AL
JZ @@f4b
STOSB
@@f4b: JECXZ @@f6
MOV AL,'0'
@@f5: STOSB
DEC EDX
JE @@f7
INC ECX
JNE @@f5
@@f6: CALL @@GetDigit
STOSB
DEC EDX
JNE @@f6
@@f7: RET
// Convert number using ffCurrency format
@@PutFCurrency:
XOR EBX,EBX
MOV BL,CurrFmt.Byte
MOV ECX,0003H
CMP FloatRec.Negative,0
JE @@g1
MOV BL,NegCurrFmt.Byte
MOV ECX,040FH
@@g1: CMP BL,CL
JBE @@g2
MOV BL,CL
@@g2: ADD BL,CH
LEA EBX,@@MoneyFormats[EBX+EBX*4]
ADD EBX,SaveGOT
MOV ECX,5
@@g10: MOV AL,[EBX]
CMP AL,'@'
JE @@g14
PUSH ECX
PUSH EBX
CMP AL,'$'
JE @@g11
CMP AL,'*'
JE @@g12
STOSB
JMP @@g13
@@g11: CALL @@PutCurSym
JMP @@g13
@@g12: CALL @@PutNumber
@@g13: POP EBX
POP ECX
INC EBX
LOOP @@g10
@@g14: RET
// Store currency symbol string
@@PutCurSym:
PUSH ESI
MOV ESI,CurrencyStr
TEST ESI,ESI
JE @@h1
MOV ECX,[ESI-4]
REP MOVSB
@@h1: POP ESI
RET
// Currency formatting templates
@@MoneyFormats:
DB '$*@@@'
DB '*$@@@'
DB '$ *@@'
DB '* $@@'
DB '($*)@'
DB '-$*@@'
DB '$-*@@'
DB '$*-@@'
DB '(*$)@'
DB '-*$@@'
DB '*-$@@'
DB '*$-@@'
DB '-* $@'
DB '-$ *@'
DB '* $-@'
DB '$ *-@'
DB '$ -*@'
DB '*- $@'
DB '($ *)'
DB '(* $)'
@@Exit:
end;
function FloatToTextFmt(Buf: PChar; const Value; ValueType: TFloatValue;
Format: PChar): Integer;
var
Buffer: Pointer;
ThousandSep: Boolean;
DecimalSep: Char;
ThousandsSep: Char;
Scientific: Boolean;
Section: Integer;
DigitCount: Integer;
DecimalIndex: Integer;
FirstDigit: Integer;
LastDigit: Integer;
DigitPlace: Integer;
DigitDelta: Integer;
FloatRec: TFloatRec;
SaveGOT: Pointer;
asm
PUSH EDI
PUSH ESI
PUSH EBX
MOV Buffer,EAX
MOV EDI,EDX
MOV EBX,ECX
{$IFDEF PIC}
CALL GetGOT
MOV SaveGOT,EAX
MOV ECX,[EAX].OFFSET DecimalSeparator
MOV CL,[ECX].Byte
MOV DecimalSep,CL
MOV ECX,[EAX].OFFSET ThousandSeparator
MOV CL,[ECX].Byte
MOV ThousandsSep,CL
{$ELSE}
MOV SaveGOT,0
MOV AL,DecimalSeparator
MOV DecimalSep,AL
MOV AL,ThousandSeparator
MOV ThousandsSep,AL
{$ENDIF}
MOV ECX,2
CMP BL,fvExtended
JE @@1
MOV EAX,[EDI].Integer
OR EAX,[EDI].Integer[4]
JE @@2
MOV ECX,[EDI].Integer[4]
SHR ECX,31
JMP @@2
@@1: MOVZX EAX,[EDI].Word[8]
OR EAX,[EDI].Integer[0]
OR EAX,[EDI].Integer[4]
JE @@2
MOVZX ECX,[EDI].Word[8]
SHR ECX,15
@@2: CALL @@FindSection
JE @@5
CALL @@ScanSection
MOV EAX,DigitCount
MOV EDX,9999
CMP Scientific,0
JNE @@3
SUB EAX,DecimalIndex
MOV EDX,EAX
MOV EAX,18
@@3: PUSH EAX
PUSH EDX
LEA EAX,FloatRec
MOV EDX,EDI
MOV ECX,EBX
CALL FloatToDecimal
MOV AX,FloatRec.Exponent
CMP AX,8000H
JE @@5
CMP AX,7FFFH
JE @@5
CMP BL,fvExtended
JNE @@6
CMP AX,18
JLE @@6
CMP Scientific,0
JNE @@6
@@5: PUSH ffGeneral
PUSH 15
PUSH 0
MOV EAX,Buffer
MOV EDX,EDI
MOV ECX,EBX
CALL FloatToText
JMP @@Exit
@@6: CMP FloatRec.Digits.Byte,0
JNE @@7
MOV ECX,2
CALL @@FindSection
JE @@5
CMP ESI,Section
JE @@7
CALL @@ScanSection
@@7: CALL @@ApplyFormat
JMP @@Exit
// Find format section
// In ECX = Section index
// Out ESI = Section offset
// ZF = 1 if section is empty
@@FindSection:
MOV ESI,Format
JECXZ @@fs2
@@fs1: LODSB
CMP AL,"'"
JE @@fs4
CMP AL,'"'
JE @@fs4
OR AL,AL
JE @@fs2
CMP AL,';'
JNE @@fs1
LOOP @@fs1
MOV AL,byte ptr [ESI]
OR AL,AL
JE @@fs2
CMP AL,';'
JNE @@fs3
@@fs2: MOV ESI,Format
MOV AL,byte ptr [ESI]
OR AL,AL
JE @@fs3
CMP AL,';'
@@fs3: RET
@@fs4: MOV AH,AL
@@fs5: LODSB
CMP AL,AH
JE @@fs1
OR AL,AL
JNE @@fs5
JMP @@fs2
// Scan format section
@@ScanSection:
PUSH EBX
MOV Section,ESI
MOV EBX,32767
XOR ECX,ECX
XOR EDX,EDX
MOV DecimalIndex,-1
MOV ThousandSep,DL
MOV Scientific,DL
@@ss1: LODSB
@@ss2: CMP AL,'#'
JE @@ss10
CMP AL,'0'
JE @@ss11
CMP AL,'.'
JE @@ss13
CMP AL,','
JE @@ss14
CMP AL,"'"
JE @@ss15
CMP AL,'"'
JE @@ss15
CMP AL,'E'
JE @@ss20
CMP AL,'e'
JE @@ss20
CMP AL,';'
JE @@ss30
OR AL,AL
JNE @@ss1
JMP @@ss30
@@ss10: INC EDX
JMP @@ss1
@@ss11: CMP EDX,EBX
JGE @@ss12
MOV EBX,EDX
@@ss12: INC EDX
MOV ECX,EDX
JMP @@ss1
@@ss13: CMP DecimalIndex,-1
JNE @@ss1
MOV DecimalIndex,EDX
JMP @@ss1
@@ss14: MOV ThousandSep,1
JMP @@ss1
@@ss15: MOV AH,AL
@@ss16: LODSB
CMP AL,AH
JE @@ss1
OR AL,AL
JNE @@ss16
JMP @@ss30
@@ss20: LODSB
CMP AL,'-'
JE @@ss21
CMP AL,'+'
JNE @@ss2
@@ss21: MOV Scientific,1
@@ss22: LODSB
CMP AL,'0'
JE @@ss22
JMP @@ss2
@@ss30: MOV DigitCount,EDX
CMP DecimalIndex,-1
JNE @@ss31
MOV DecimalIndex,EDX
@@ss31: MOV EAX,DecimalIndex
SUB EAX,ECX
JLE @@ss32
XOR EAX,EAX
@@ss32: MOV LastDigit,EAX
MOV EAX,DecimalIndex
SUB EAX,EBX
JGE @@ss33
XOR EAX,EAX
@@ss33: MOV FirstDigit,EAX
POP EBX
RET
// Apply format string
@@ApplyFormat:
CMP Scientific,0
JE @@af1
MOV EAX,DecimalIndex
XOR EDX,EDX
JMP @@af3
@@af1: MOVSX EAX,FloatRec.Exponent
CMP EAX,DecimalIndex
JG @@af2
MOV EAX,DecimalIndex
@@af2: MOVSX EDX,FloatRec.Exponent
SUB EDX,DecimalIndex
@@af3: MOV DigitPlace,EAX
MOV DigitDelta,EDX
MOV ESI,Section
MOV EDI,Buffer
LEA EBX,FloatRec.Digits
CMP FloatRec.Negative,0
JE @@af10
CMP ESI,Format
JNE @@af10
MOV AL,'-'
STOSB
@@af10: LODSB
CMP AL,'#'
JE @@af20
CMP AL,'0'
JE @@af20
CMP AL,'.'
JE @@af10
CMP AL,','
JE @@af10
CMP AL,"'"
JE @@af25
CMP AL,'"'
JE @@af25
CMP AL,'E'
JE @@af30
CMP AL,'e'
JE @@af30
CMP AL,';'
JE @@af40
OR AL,AL
JE @@af40
@@af11: STOSB
JMP @@af10
@@af20: CALL @@PutFmtDigit
JMP @@af10
@@af25: MOV AH,AL
@@af26: LODSB
CMP AL,AH
JE @@af10
OR AL,AL
JE @@af40
STOSB
JMP @@af26
@@af30: MOV AH,[ESI]
CMP AH,'+'
JE @@af31
CMP AH,'-'
JNE @@af11
XOR AH,AH
@@af31: MOV ECX,-1
@@af32: INC ECX
INC ESI
CMP [ESI].Byte,'0'
JE @@af32
CMP ECX,4
JB @@af33
MOV ECX,4
@@af33: PUSH EBX
MOV BL,FloatRec.Digits.Byte
MOVSX EDX,FloatRec.Exponent
SUB EDX,DecimalIndex
CALL PutExponent
POP EBX
JMP @@af10
@@af40: MOV EAX,EDI
SUB EAX,Buffer
RET
// Store formatted digit
@@PutFmtDigit:
CMP DigitDelta,0
JE @@fd3
JL @@fd2
@@fd1: CALL @@fd3
DEC DigitDelta
JNE @@fd1
JMP @@fd3
@@fd2: INC DigitDelta
MOV EAX,DigitPlace
CMP EAX,FirstDigit
JLE @@fd4
JMP @@fd7
@@fd3: MOV AL,[EBX]
INC EBX
OR AL,AL
JNE @@fd5
DEC EBX
MOV EAX,DigitPlace
CMP EAX,LastDigit
JLE @@fd7
@@fd4: MOV AL,'0'
@@fd5: CMP DigitPlace,0
JNE @@fd6
MOV AH,AL
MOV AL,DecimalSep
STOSW
JMP @@fd7
@@fd6: STOSB
CMP ThousandSep,0
JE @@fd7
MOV EAX,DigitPlace
CMP EAX,1
JLE @@fd7
MOV DL,3
DIV DL
CMP AH,1
JNE @@fd7
MOV AL,ThousandsSep
TEST AL,AL
JZ @@fd7
STOSB
@@fd7: DEC DigitPlace
RET
@@exit:
POP EBX
POP ESI
POP EDI
end;
function FloatToTextFmt(Buf: PChar; const Value; ValueType: TFloatValue;
Format: PChar; const FormatSettings: TFormatSettings): Integer;
var
Buffer: Pointer;
ThousandSep: Boolean;
DecimalSep: Char;
ThousandsSep: Char;
Scientific: Boolean;
Section: Integer;
DigitCount: Integer;
DecimalIndex: Integer;
FirstDigit: Integer;
LastDigit: Integer;
DigitPlace: Integer;
DigitDelta: Integer;
FloatRec: TFloatRec;
SaveGOT: Pointer;
asm
PUSH EDI
PUSH ESI
PUSH EBX
MOV Buffer,EAX
MOV EDI,EDX
MOV EBX,ECX
{$IFDEF PIC}
CALL GetGOT
MOV SaveGOT,EAX
{$ELSE}
MOV SaveGOT,0
{$ENDIF}
MOV EAX,FormatSettings
MOV AL,[EAX].TFormatSettings.DecimalSeparator
MOV DecimalSep,AL
MOV EAX,FormatSettings
MOV AL,[EAX].TFormatSettings.ThousandSeparator
MOV ThousandsSep,AL
MOV ECX,2
CMP BL,fvExtended
JE @@1
MOV EAX,[EDI].Integer
OR EAX,[EDI].Integer[4]
JE @@2
MOV ECX,[EDI].Integer[4]
SHR ECX,31
JMP @@2
@@1: MOVZX EAX,[EDI].Word[8]
OR EAX,[EDI].Integer[0]
OR EAX,[EDI].Integer[4]
JE @@2
MOVZX ECX,[EDI].Word[8]
SHR ECX,15
@@2: CALL @@FindSection
JE @@5
CALL @@ScanSection
MOV EAX,DigitCount
MOV EDX,9999
CMP Scientific,0
JNE @@3
SUB EAX,DecimalIndex
MOV EDX,EAX
MOV EAX,18
@@3: PUSH EAX
PUSH EDX
LEA EAX,FloatRec
MOV EDX,EDI
MOV ECX,EBX
CALL FloatToDecimal
MOV AX,FloatRec.Exponent
CMP AX,8000H
JE @@5
CMP AX,7FFFH
JE @@5
CMP BL,fvExtended
JNE @@6
CMP AX,18
JLE @@6
CMP Scientific,0
JNE @@6
@@5: PUSH ffGeneral
PUSH 15
PUSH 0
MOV EAX,[FormatSettings]
PUSH EAX
MOV EAX,Buffer
MOV EDX,EDI
MOV ECX,EBX
CALL FloatToTextEx
JMP @@Exit
@@6: CMP FloatRec.Digits.Byte,0
JNE @@7
MOV ECX,2
CALL @@FindSection
JE @@5
CMP ESI,Section
JE @@7
CALL @@ScanSection
@@7: CALL @@ApplyFormat
JMP @@Exit
// Find format section
// In ECX = Section index
// Out ESI = Section offset
// ZF = 1 if section is empty
@@FindSection:
MOV ESI,Format
JECXZ @@fs2
@@fs1: LODSB
CMP AL,"'"
JE @@fs4
CMP AL,'"'
JE @@fs4
OR AL,AL
JE @@fs2
CMP AL,';'
JNE @@fs1
LOOP @@fs1
MOV AL,byte ptr [ESI]
OR AL,AL
JE @@fs2
CMP AL,';'
JNE @@fs3
@@fs2: MOV ESI,Format
MOV AL,byte ptr [ESI]
OR AL,AL
JE @@fs3
CMP AL,';'
@@fs3: RET
@@fs4: MOV AH,AL
@@fs5: LODSB
CMP AL,AH
JE @@fs1
OR AL,AL
JNE @@fs5
JMP @@fs2
// Scan format section
@@ScanSection:
PUSH EBX
MOV Section,ESI
MOV EBX,32767
XOR ECX,ECX
XOR EDX,EDX
MOV DecimalIndex,-1
MOV ThousandSep,DL
MOV Scientific,DL
@@ss1: LODSB
@@ss2: CMP AL,'#'
JE @@ss10
CMP AL,'0'
JE @@ss11
CMP AL,'.'
JE @@ss13
CMP AL,','
JE @@ss14
CMP AL,"'"
JE @@ss15
CMP AL,'"'
JE @@ss15
CMP AL,'E'
JE @@ss20
CMP AL,'e'
JE @@ss20
CMP AL,';'
JE @@ss30
OR AL,AL
JNE @@ss1
JMP @@ss30
@@ss10: INC EDX
JMP @@ss1
@@ss11: CMP EDX,EBX
JGE @@ss12
MOV EBX,EDX
@@ss12: INC EDX
MOV ECX,EDX
JMP @@ss1
@@ss13: CMP DecimalIndex,-1
JNE @@ss1
MOV DecimalIndex,EDX
JMP @@ss1
@@ss14: MOV ThousandSep,1
JMP @@ss1
@@ss15: MOV AH,AL
@@ss16: LODSB
CMP AL,AH
JE @@ss1
OR AL,AL
JNE @@ss16
JMP @@ss30
@@ss20: LODSB
CMP AL,'-'
JE @@ss21
CMP AL,'+'
JNE @@ss2
@@ss21: MOV Scientific,1
@@ss22: LODSB
CMP AL,'0'
JE @@ss22
JMP @@ss2
@@ss30: MOV DigitCount,EDX
CMP DecimalIndex,-1
JNE @@ss31
MOV DecimalIndex,EDX
@@ss31: MOV EAX,DecimalIndex
SUB EAX,ECX
JLE @@ss32
XOR EAX,EAX
@@ss32: MOV LastDigit,EAX
MOV EAX,DecimalIndex
SUB EAX,EBX
JGE @@ss33
XOR EAX,EAX
@@ss33: MOV FirstDigit,EAX
POP EBX
RET
// Apply format string
@@ApplyFormat:
CMP Scientific,0
JE @@af1
MOV EAX,DecimalIndex
XOR EDX,EDX
JMP @@af3
@@af1: MOVSX EAX,FloatRec.Exponent
CMP EAX,DecimalIndex
JG @@af2
MOV EAX,DecimalIndex
@@af2: MOVSX EDX,FloatRec.Exponent
SUB EDX,DecimalIndex
@@af3: MOV DigitPlace,EAX
MOV DigitDelta,EDX
MOV ESI,Section
MOV EDI,Buffer
LEA EBX,FloatRec.Digits
CMP FloatRec.Negative,0
JE @@af10
CMP ESI,Format
JNE @@af10
MOV AL,'-'
STOSB
@@af10: LODSB
CMP AL,'#'
JE @@af20
CMP AL,'0'
JE @@af20
CMP AL,'.'
JE @@af10
CMP AL,','
JE @@af10
CMP AL,"'"
JE @@af25
CMP AL,'"'
JE @@af25
CMP AL,'E'
JE @@af30
CMP AL,'e'
JE @@af30
CMP AL,';'
JE @@af40
OR AL,AL
JE @@af40
@@af11: STOSB
JMP @@af10
@@af20: CALL @@PutFmtDigit
JMP @@af10
@@af25: MOV AH,AL
@@af26: LODSB
CMP AL,AH
JE @@af10
OR AL,AL
JE @@af40
STOSB
JMP @@af26
@@af30: MOV AH,[ESI]
CMP AH,'+'
JE @@af31
CMP AH,'-'
JNE @@af11
XOR AH,AH
@@af31: MOV ECX,-1
@@af32: INC ECX
INC ESI
CMP [ESI].Byte,'0'
JE @@af32
CMP ECX,4
JB @@af33
MOV ECX,4
@@af33: PUSH EBX
MOV BL,FloatRec.Digits.Byte
MOVSX EDX,FloatRec.Exponent
SUB EDX,DecimalIndex
CALL PutExponent
POP EBX
JMP @@af10
@@af40: MOV EAX,EDI
SUB EAX,Buffer
RET
// Store formatted digit
@@PutFmtDigit:
CMP DigitDelta,0
JE @@fd3
JL @@fd2
@@fd1: CALL @@fd3
DEC DigitDelta
JNE @@fd1
JMP @@fd3
@@fd2: INC DigitDelta
MOV EAX,DigitPlace
CMP EAX,FirstDigit
JLE @@fd4
JMP @@fd7
@@fd3: MOV AL,[EBX]
INC EBX
OR AL,AL
JNE @@fd5
DEC EBX
MOV EAX,DigitPlace
CMP EAX,LastDigit
JLE @@fd7
@@fd4: MOV AL,'0'
@@fd5: CMP DigitPlace,0
JNE @@fd6
MOV AH,AL
MOV AL,DecimalSep
STOSW
JMP @@fd7
@@fd6: STOSB
CMP ThousandSep,0
JE @@fd7
MOV EAX,DigitPlace
CMP EAX,1
JLE @@fd7
MOV DL,3
DIV DL
CMP AH,1
JNE @@fd7
MOV AL,ThousandsSep
TEST AL,AL
JZ @@fd7
STOSB
@@fd7: DEC DigitPlace
RET
@@exit:
POP EBX
POP ESI
POP EDI
end;
const
// 8087 status word masks
mIE = $0001;
mDE = $0002;
mZE = $0004;
mOE = $0008;
mUE = $0010;
mPE = $0020;
mC0 = $0100;
mC1 = $0200;
mC2 = $0400;
mC3 = $4000;
procedure FloatToDecimal(var Result: TFloatRec; const Value;
ValueType: TFloatValue; Precision, Decimals: Integer);
var
StatWord: Word;
Exponent: Integer;
Temp: Double;
BCDValue: Extended;
SaveGOT: Pointer;
asm
PUSH EDI
PUSH ESI
PUSH EBX
MOV EBX,EAX
MOV ESI,EDX
{$IFDEF PIC}
PUSH ECX
CALL GetGOT
POP ECX
MOV SaveGOT,EAX
{$ELSE}
MOV SaveGOT,0
{$ENDIF}
CMP CL,fvExtended
JE @@1
CALL @@CurrToDecimal
JMP @@Exit
@@1: CALL @@ExtToDecimal
JMP @@Exit
// Convert Extended to decimal
@@ExtToDecimal:
MOV AX,[ESI].Word[8]
MOV EDX,EAX
AND EAX,7FFFH
JE @@ed1
CMP EAX,7FFFH
JNE @@ed10
// check for special values (INF, NAN)
TEST [ESI].Word[6],8000H
JZ @@ed2
// any significand bit set = NAN
// all significand bits clear = INF
CMP dword ptr [ESI], 0
JNZ @@ed0
CMP dword ptr [ESI+4], 80000000H
JZ @@ed2
@@ed0: INC EAX
@@ed1: XOR EDX,EDX
@@ed2: MOV [EBX].TFloatRec.Digits.Byte,0
JMP @@ed31
@@ed10: FLD TBYTE PTR [ESI]
SUB EAX,3FFFH
IMUL EAX,19728
SAR EAX,16
INC EAX
MOV Exponent,EAX
MOV EAX,18
SUB EAX,Exponent
FABS
PUSH EBX
MOV EBX,SaveGOT
CALL FPower10
POP EBX
FRNDINT
MOV EDI,SaveGOT
FLD [EDI].FCon1E18
FCOMP
FSTSW StatWord
FWAIT
TEST StatWord,mC0+mC3
JE @@ed11
FIDIV [EDI].DCon10
INC Exponent
@@ed11: FBSTP BCDValue
LEA EDI,[EBX].TFloatRec.Digits
MOV EDX,9
FWAIT
@@ed12: MOV AL,BCDValue[EDX-1].Byte
MOV AH,AL
SHR AL,4
AND AH,0FH
ADD AX,'00'
STOSW
DEC EDX
JNE @@ed12
XOR AL,AL
STOSB
@@ed20: MOV EDI,Exponent
ADD EDI,Decimals
JNS @@ed21
XOR EAX,EAX
JMP @@ed1
@@ed21: CMP EDI,Precision
JB @@ed22
MOV EDI,Precision
@@ed22: CMP EDI,18
JAE @@ed26
CMP [EBX].TFloatRec.Digits.Byte[EDI],'5'
JB @@ed25
@@ed23: MOV [EBX].TFloatRec.Digits.Byte[EDI],0
DEC EDI
JS @@ed24
INC [EBX].TFloatRec.Digits.Byte[EDI]
CMP [EBX].TFloatRec.Digits.Byte[EDI],'9'
JA @@ed23
JMP @@ed30
@@ed24: MOV [EBX].TFloatRec.Digits.Word,'1'
INC Exponent
JMP @@ed30
@@ed26: MOV EDI,18
@@ed25: MOV [EBX].TFloatRec.Digits.Byte[EDI],0
DEC EDI
JS @@ed32
CMP [EBX].TFloatRec.Digits.Byte[EDI],'0'
JE @@ed25
@@ed30: MOV DX,[ESI].Word[8]
@@ed30a:
MOV EAX,Exponent
@@ed31: SHR DX,15
MOV [EBX].TFloatRec.Exponent,AX
MOV [EBX].TFloatRec.Negative,DL
RET
@@ed32: XOR EDX,EDX
JMP @@ed30a
@@DecimalTable:
DD 10
DD 100
DD 1000
DD 10000
// Convert Currency to decimal
@@CurrToDecimal:
MOV EAX,[ESI].Integer[0]
MOV EDX,[ESI].Integer[4]
MOV ECX,EAX
OR ECX,EDX
JE @@cd20
OR EDX,EDX
JNS @@cd1
NEG EDX
NEG EAX
SBB EDX,0
@@cd1: XOR ECX,ECX
MOV EDI,Decimals
OR EDI,EDI
JGE @@cd2
XOR EDI,EDI
@@cd2: CMP EDI,4
JL @@cd4
MOV EDI,4
@@cd3: INC ECX
SUB EAX,Const1E18Lo
SBB EDX,Const1E18Hi
JNC @@cd3
DEC ECX
ADD EAX,Const1E18Lo
ADC EDX,Const1E18Hi
@@cd4: MOV Temp.Integer[0],EAX
MOV Temp.Integer[4],EDX
FILD Temp
MOV EDX,EDI
MOV EAX,4
SUB EAX,EDX
JE @@cd5
MOV EDI,SaveGOT
FIDIV @@DecimalTable.Integer[EDI+EAX*4-4]
@@cd5: FBSTP BCDValue
LEA EDI,[EBX].TFloatRec.Digits
FWAIT
OR ECX,ECX
JNE @@cd11
MOV ECX,9
@@cd10: MOV AL,BCDValue[ECX-1].Byte
MOV AH,AL
SHR AL,4
JNE @@cd13
MOV AL,AH
AND AL,0FH
JNE @@cd14
DEC ECX
JNE @@cd10
JMP @@cd20
@@cd11: MOV AL,CL
ADD AL,'0'
STOSB
MOV ECX,9
@@cd12: MOV AL,BCDValue[ECX-1].Byte
MOV AH,AL
SHR AL,4
@@cd13: ADD AL,'0'
STOSB
MOV AL,AH
AND AL,0FH
@@cd14: ADD AL,'0'
STOSB
DEC ECX
JNE @@cd12
MOV EAX,EDI
LEA ECX,[EBX].TFloatRec.Digits[EDX]
SUB EAX,ECX
@@cd15: MOV BYTE PTR [EDI],0
DEC EDI
CMP BYTE PTR [EDI],'0'
JE @@cd15
MOV EDX,[ESI].Integer[4]
SHR EDX,31
JMP @@cd21
@@cd20: XOR EAX,EAX
XOR EDX,EDX
MOV [EBX].TFloatRec.Digits.Byte[0],AL
@@cd21: MOV [EBX].TFloatRec.Exponent,AX
MOV [EBX].TFloatRec.Negative,DL
RET
@@Exit:
POP EBX
POP ESI
POP EDI
end;
function TextToFloat(Buffer: PChar; var Value;
ValueType: TFloatValue): Boolean;
const
// 8087 control word
// Infinity control = 1 Affine
// Rounding Control = 0 Round to nearest or even
// Precision Control = 3 64 bits
// All interrupts masked
CWNear: Word = $133F;
var
Temp: Integer;
CtrlWord: Word;
DecimalSep: Char;
SaveGOT: Integer;
asm
PUSH EDI
PUSH ESI
PUSH EBX
MOV ESI,EAX
MOV EDI,EDX
{$IFDEF PIC}
PUSH ECX
CALL GetGOT
POP EBX
MOV SaveGOT,EAX
MOV ECX,[EAX].OFFSET DecimalSeparator
MOV CL,[ECX].Byte
MOV DecimalSep,CL
{$ELSE}
MOV SaveGOT,0
MOV AL,DecimalSeparator
MOV DecimalSep,AL
MOV EBX,ECX
{$ENDIF}
FSTCW CtrlWord
FCLEX
{$IFDEF PIC}
FLDCW [EAX].CWNear
{$ELSE}
FLDCW CWNear
{$ENDIF}
FLDZ
CALL @@SkipBlanks
MOV BH, byte ptr [ESI]
CMP BH,'+'
JE @@1
CMP BH,'-'
JNE @@2
@@1: INC ESI
@@2: MOV ECX,ESI
CALL @@GetDigitStr
XOR EDX,EDX
MOV AL,[ESI]
CMP AL,DecimalSep
JNE @@3
INC ESI
CALL @@GetDigitStr
NEG EDX
@@3: CMP ECX,ESI
JE @@9
MOV AL, byte ptr [ESI]
AND AL,0DFH
CMP AL,'E'
JNE @@4
INC ESI
PUSH EDX
CALL @@GetExponent
POP EAX
ADD EDX,EAX
@@4: CALL @@SkipBlanks
CMP BYTE PTR [ESI],0
JNE @@9
MOV EAX,EDX
CMP BL,fvCurrency
JNE @@5
ADD EAX,4
@@5: PUSH EBX
MOV EBX,SaveGOT
CALL FPower10
POP EBX
CMP BH,'-'
JNE @@6
FCHS
@@6: CMP BL,fvExtended
JE @@7
FISTP QWORD PTR [EDI]
JMP @@8
@@7: FSTP TBYTE PTR [EDI]
@@8: FSTSW AX
TEST AX,mIE+mOE
JNE @@10
MOV AL,1
JMP @@11
@@9: FSTP ST(0)
@@10: XOR EAX,EAX
@@11: FCLEX
FLDCW CtrlWord
FWAIT
JMP @@Exit
@@SkipBlanks:
@@21: LODSB
OR AL,AL
JE @@22
CMP AL,' '
JE @@21
@@22: DEC ESI
RET
// Process string of digits
// Out EDX = Digit count
@@GetDigitStr:
XOR EAX,EAX
XOR EDX,EDX
@@31: LODSB
SUB AL,'0'+10
ADD AL,10
JNC @@32
{$IFDEF PIC}
XCHG SaveGOT,EBX
FIMUL [EBX].DCon10
XCHG SaveGOT,EBX
{$ELSE}
FIMUL DCon10
{$ENDIF}
MOV Temp,EAX
FIADD Temp
INC EDX
JMP @@31
@@32: DEC ESI
RET
// Get exponent
// Out EDX = Exponent (-4999..4999)
@@GetExponent:
XOR EAX,EAX
XOR EDX,EDX
MOV CL, byte ptr [ESI]
CMP CL,'+'
JE @@41
CMP CL,'-'
JNE @@42
@@41: INC ESI
@@42: MOV AL, byte ptr [ESI]
SUB AL,'0'+10
ADD AL,10
JNC @@43
INC ESI
IMUL EDX,10
ADD EDX,EAX
CMP EDX,500
JB @@42
@@43: CMP CL,'-'
JNE @@44
NEG EDX
@@44: RET
@@Exit:
POP EBX
POP ESI
POP EDI
end;
function TextToFloat(Buffer: PChar; var Value;
ValueType: TFloatValue; const FormatSettings: TFormatSettings): Boolean;
const
// 8087 control word
// Infinity control = 1 Affine
// Rounding Control = 0 Round to nearest or even
// Precision Control = 3 64 bits
// All interrupts masked
CWNear: Word = $133F;
var
Temp: Integer;
CtrlWord: Word;
DecimalSep: Char;
SaveGOT: Integer;
asm
PUSH EDI
PUSH ESI
PUSH EBX
MOV ESI,EAX
MOV EDI,EDX
{$IFDEF PIC}
PUSH ECX
CALL GetGOT
POP EBX
MOV SaveGOT,EAX
{$ELSE}
MOV SaveGOT,0
MOV EBX,ECX
{$ENDIF}
MOV EAX,FormatSettings
MOV AL,[EAX].TFormatSettings.DecimalSeparator
MOV DecimalSep,AL
FSTCW CtrlWord
FCLEX
{$IFDEF PIC}
FLDCW [EAX].CWNear
{$ELSE}
FLDCW CWNear
{$ENDIF}
FLDZ
CALL @@SkipBlanks
MOV BH, byte ptr [ESI]
CMP BH,'+'
JE @@1
CMP BH,'-'
JNE @@2
@@1: INC ESI
@@2: MOV ECX,ESI
CALL @@GetDigitStr
XOR EDX,EDX
MOV AL,[ESI]
CMP AL,DecimalSep
JNE @@3
INC ESI
CALL @@GetDigitStr
NEG EDX
@@3: CMP ECX,ESI
JE @@9
MOV AL, byte ptr [ESI]
AND AL,0DFH
CMP AL,'E'
JNE @@4
INC ESI
PUSH EDX
CALL @@GetExponent
POP EAX
ADD EDX,EAX
@@4: CALL @@SkipBlanks
CMP BYTE PTR [ESI],0
JNE @@9
MOV EAX,EDX
CMP BL,fvCurrency
JNE @@5
ADD EAX,4
@@5: PUSH EBX
MOV EBX,SaveGOT
CALL FPower10
POP EBX
CMP BH,'-'
JNE @@6
FCHS
@@6: CMP BL,fvExtended
JE @@7
FISTP QWORD PTR [EDI]
JMP @@8
@@7: FSTP TBYTE PTR [EDI]
@@8: FSTSW AX
TEST AX,mIE+mOE
JNE @@10
MOV AL,1
JMP @@11
@@9: FSTP ST(0)
@@10: XOR EAX,EAX
@@11: FCLEX
FLDCW CtrlWord
FWAIT
JMP @@Exit
@@SkipBlanks:
@@21: LODSB
OR AL,AL
JE @@22
CMP AL,' '
JE @@21
@@22: DEC ESI
RET
// Process string of digits
// Out EDX = Digit count
@@GetDigitStr:
XOR EAX,EAX
XOR EDX,EDX
@@31: LODSB
SUB AL,'0'+10
ADD AL,10
JNC @@32
{$IFDEF PIC}
XCHG SaveGOT,EBX
FIMUL [EBX].DCon10
XCHG SaveGOT,EBX
{$ELSE}
FIMUL DCon10
{$ENDIF}
MOV Temp,EAX
FIADD Temp
INC EDX
JMP @@31
@@32: DEC ESI
RET
// Get exponent
// Out EDX = Exponent (-4999..4999)
@@GetExponent:
XOR EAX,EAX
XOR EDX,EDX
MOV CL, byte ptr [ESI]
CMP CL,'+'
JE @@41
CMP CL,'-'
JNE @@42
@@41: INC ESI
@@42: MOV AL, byte ptr [ESI]
SUB AL,'0'+10
ADD AL,10
JNC @@43
INC ESI
IMUL EDX,10
ADD EDX,EAX
CMP EDX,500
JB @@42
@@43: CMP CL,'-'
JNE @@44
NEG EDX
@@44: RET
@@Exit:
POP EBX
POP ESI
POP EDI
end;
function FloatToStr(Value: Extended): string;
var
Buffer: array[0..63] of Char;
begin
SetString(Result, Buffer, FloatToText(Buffer, Value, fvExtended,
ffGeneral, 15, 0));
end;
function FloatToStr(Value: Extended;
const FormatSettings: TFormatSettings): string;
var
Buffer: array[0..63] of Char;
begin
SetString(Result, Buffer, FloatToText(Buffer, Value, fvExtended,
ffGeneral, 15, 0, FormatSettings));
end;
function CurrToStr(Value: Currency): string;
var
Buffer: array[0..63] of Char;
begin
SetString(Result, Buffer, FloatToText(Buffer, Value, fvCurrency,
ffGeneral, 0, 0));
end;
function CurrToStr(Value: Currency;
const FormatSettings: TFormatSettings): string;
var
Buffer: array[0..63] of Char;
begin
SetString(Result, Buffer, FloatToText(Buffer, Value, fvCurrency,
ffGeneral, 0, 0, FormatSettings));
end;
function TryFloatToCurr(const Value: Extended; out AResult: Currency): Boolean;
begin
Result := (Value >= MinCurrency) and (Value <= MaxCurrency);
if Result then
AResult := Value;
end;
function FloatToCurr(const Value: Extended): Currency;
begin
if not TryFloatToCurr(Value, Result) then
ConvertErrorFmt(SInvalidCurrency, [FloatToStr(Value)]);
end;
function FloatToStrF(Value: Extended; Format: TFloatFormat;
Precision, Digits: Integer): string;
var
Buffer: array[0..63] of Char;
begin
SetString(Result, Buffer, FloatToText(Buffer, Value, fvExtended,
Format, Precision, Digits));
end;
function FloatToStrF(Value: Extended; Format: TFloatFormat;
Precision, Digits: Integer; const FormatSettings: TFormatSettings): string;
var
Buffer: array[0..63] of Char;
begin
SetString(Result, Buffer, FloatToText(Buffer, Value, fvExtended,
Format, Precision, Digits, FormatSettings));
end;
function CurrToStrF(Value: Currency; Format: TFloatFormat;
Digits: Integer): string;
var
Buffer: array[0..63] of Char;
begin
SetString(Result, Buffer, FloatToText(Buffer, Value, fvCurrency,
Format, 0, Digits));
end;
function CurrToStrF(Value: Currency; Format: TFloatFormat;
Digits: Integer; const FormatSettings: TFormatSettings): string;
var
Buffer: array[0..63] of Char;
begin
SetString(Result, Buffer, FloatToText(Buffer, Value, fvCurrency,
Format, 0, Digits, FormatSettings));
end;
function FormatFloat(const Format: string; Value: Extended): string;
var
Buffer: array[0..255] of Char;
begin
if Length(Format) > SizeOf(Buffer) - 32 then ConvertError(SFormatTooLong);
SetString(Result, Buffer, FloatToTextFmt(Buffer, Value, fvExtended,
PChar(Format)));
end;
function FormatFloat(const Format: string; Value: Extended;
const FormatSettings: TFormatSettings): string;
var
Buffer: array[0..255] of Char;
begin
if Length(Format) > SizeOf(Buffer) - 32 then ConvertError(SFormatTooLong);
SetString(Result, Buffer, FloatToTextFmt(Buffer, Value, fvExtended,
PChar(Format), FormatSettings));
end;
function FormatCurr(const Format: string; Value: Currency): string;
var
Buffer: array[0..255] of Char;
begin
if Length(Format) > SizeOf(Buffer) - 32 then ConvertError(SFormatTooLong);
SetString(Result, Buffer, FloatToTextFmt(Buffer, Value, fvCurrency,
PChar(Format)));
end;
function FormatCurr(const Format: string; Value: Currency;
const FormatSettings: TFormatSettings): string;
var
Buffer: array[0..255] of Char;
begin
if Length(Format) > SizeOf(Buffer) - 32 then ConvertError(SFormatTooLong);
SetString(Result, Buffer, FloatToTextFmt(Buffer, Value, fvCurrency,
PChar(Format), FormatSettings));
end;
function StrToFloat(const S: string): Extended;
begin
if not TextToFloat(PChar(S), Result, fvExtended) then
ConvertErrorFmt(SInvalidFloat, [S]);
end;
function StrToFloat(const S: string;
const FormatSettings: TFormatSettings): Extended;
begin
if not TextToFloat(PChar(S), Result, fvExtended, FormatSettings) then
ConvertErrorFmt(SInvalidFloat, [S]);
end;
function StrToFloatDef(const S: string; const Default: Extended): Extended;
begin
if not TextToFloat(PChar(S), Result, fvExtended) then
Result := Default;
end;
function StrToFloatDef(const S: string; const Default: Extended;
const FormatSettings: TFormatSettings): Extended;
begin
if not TextToFloat(PChar(S), Result, fvExtended, FormatSettings) then
Result := Default;
end;
function TryStrToFloat(const S: string; out Value: Extended): Boolean;
begin
Result := TextToFloat(PChar(S), Value, fvExtended);
end;
function TryStrToFloat(const S: string; out Value: Extended;
const FormatSettings: TFormatSettings): Boolean;
begin
Result := TextToFloat(PChar(S), Value, fvExtended, FormatSettings);
end;
function TryStrToFloat(const S: string; out Value: Double): Boolean;
var
LValue: Extended;
begin
Result := TextToFloat(PChar(S), LValue, fvExtended);
if Result then
Value := LValue;
end;
function TryStrToFloat(const S: string; out Value: Double;
const FormatSettings: TFormatSettings): Boolean;
var
LValue: Extended;
begin
Result := TextToFloat(PChar(S), LValue, fvExtended, FormatSettings);
if Result then
Value := LValue;
end;
function TryStrToFloat(const S: string; out Value: Single): Boolean;
var
LValue: Extended;
begin
Result := TextToFloat(PChar(S), LValue, fvExtended);
if Result then
Value := LValue;
end;
function TryStrToFloat(const S: string; out Value: Single;
const FormatSettings: TFormatSettings): Boolean;
var
LValue: Extended;
begin
Result := TextToFloat(PChar(S), LValue, fvExtended, FormatSettings);
if Result then
Value := LValue;
end;
function StrToCurr(const S: string): Currency;
begin
if not TextToFloat(PChar(S), Result, fvCurrency) then
ConvertErrorFmt(SInvalidFloat, [S]);
end;
function StrToCurr(const S: string;
const FormatSettings: TFormatSettings): Currency;
begin
if not TextToFloat(PChar(S), Result, fvCurrency, FormatSettings) then
ConvertErrorFmt(SInvalidFloat, [S]);
end;
function StrToCurrDef(const S: string; const Default: Currency): Currency;
begin
if not TextToFloat(PChar(S), Result, fvCurrency) then
Result := Default;
end;
function StrToCurrDef(const S: string; const Default: Currency;
const FormatSettings: TFormatSettings): Currency;
begin
if not TextToFloat(PChar(S), Result, fvCurrency, FormatSettings) then
Result := Default;
end;
function TryStrToCurr(const S: string; out Value: Currency): Boolean;
begin
Result := TextToFloat(PChar(S), Value, fvCurrency);
end;
function TryStrToCurr(const S: string; out Value: Currency;
const FormatSettings: TFormatSettings): Boolean;
begin
Result := TextToFloat(PChar(S), Value, fvCurrency, FormatSettings);
end;
{ Date/time support routines }
const
FMSecsPerDay: Single = MSecsPerDay;
IMSecsPerDay: Integer = MSecsPerDay;
function DateTimeToTimeStamp(DateTime: TDateTime): TTimeStamp;
asm
PUSH EBX
{$IFDEF PIC}
PUSH EAX
CALL GetGOT
MOV EBX,EAX
POP EAX
{$ELSE}
XOR EBX,EBX
{$ENDIF}
MOV ECX,EAX
FLD DateTime
FMUL [EBX].FMSecsPerDay
SUB ESP,8
FISTP QWORD PTR [ESP]
FWAIT
POP EAX
POP EDX
OR EDX,EDX
JNS @@1
NEG EDX
NEG EAX
SBB EDX,0
DIV [EBX].IMSecsPerDay
NEG EAX
JMP @@2
@@1: DIV [EBX].IMSecsPerDay
@@2: ADD EAX,DateDelta
MOV [ECX].TTimeStamp.Time,EDX
MOV [ECX].TTimeStamp.Date,EAX
POP EBX
end;
procedure ValidateTimeStamp(const TimeStamp: TTimeStamp);
begin
if (TimeStamp.Time < 0) or (TimeStamp.Date <= 0) then
ConvertErrorFmt(SInvalidTimeStamp, [TimeStamp.Date, TimeStamp.Time]);
end;
function TimeStampToDateTime(const TimeStamp: TTimeStamp): TDateTime;
asm
PUSH EBX
{$IFDEF PIC}
PUSH EAX
CALL GetGOT
MOV EBX,EAX
POP EAX
{$ELSE}
XOR EBX,EBX
{$ENDIF}
PUSH EAX
CALL ValidateTimeStamp
POP EAX
MOV ECX,[EAX].TTimeStamp.Time
MOV EAX,[EAX].TTimeStamp.Date
SUB EAX,DateDelta
IMUL [EBX].IMSecsPerDay
OR EDX,EDX
JNS @@1
SUB EAX,ECX
SBB EDX,0
JMP @@2
@@1: ADD EAX,ECX
ADC EDX,0
@@2: PUSH EDX
PUSH EAX
FILD QWORD PTR [ESP]
FDIV [EBX].FMSecsPerDay
ADD ESP,8
POP EBX
end;
function MSecsToTimeStamp(MSecs: Comp): TTimeStamp;
asm
PUSH EBX
{$IFDEF PIC}
PUSH EAX
CALL GetGOT
MOV EBX,EAX
POP EAX
{$ELSE}
XOR EBX,EBX
{$ENDIF}
MOV ECX,EAX
MOV EAX,MSecs.Integer[0]
MOV EDX,MSecs.Integer[4]
DIV [EBX].IMSecsPerDay
MOV [ECX].TTimeStamp.Time,EDX
MOV [ECX].TTimeStamp.Date,EAX
POP EBX
end;
function TimeStampToMSecs(const TimeStamp: TTimeStamp): Comp;
asm
PUSH EBX
{$IFDEF PIC}
PUSH EAX
CALL GetGOT
MOV EBX,EAX
POP EAX
{$ELSE}
XOR EBX,EBX
{$ENDIF}
PUSH EAX
CALL ValidateTimeStamp
POP EAX
FILD [EAX].TTimeStamp.Date
FMUL [EBX].FMSecsPerDay
FIADD [EAX].TTimeStamp.Time
POP EBX
end;
{ Time encoding and decoding }
function TryEncodeTime(Hour, Min, Sec, MSec: Word; out Time: TDateTime): Boolean;
begin
Result := False;
if (Hour < HoursPerDay) and (Min < MinsPerHour) and (Sec < SecsPerMin) and (MSec < MSecsPerSec) then
begin
Time := (Hour * (MinsPerHour * SecsPerMin * MSecsPerSec) +
Min * (SecsPerMin * MSecsPerSec) +
Sec * MSecsPerSec +
MSec) / MSecsPerDay;
Result := True;
end;
end;
function EncodeTime(Hour, Min, Sec, MSec: Word): TDateTime;
begin
if not TryEncodeTime(Hour, Min, Sec, MSec, Result) then
ConvertError(STimeEncodeError);
end;
procedure DecodeTime(const DateTime: TDateTime; var Hour, Min, Sec, MSec: Word);
var
MinCount, MSecCount: Word;
begin
DivMod(DateTimeToTimeStamp(DateTime).Time, SecsPerMin * MSecsPerSec, MinCount, MSecCount);
DivMod(MinCount, MinsPerHour, Hour, Min);
DivMod(MSecCount, MSecsPerSec, Sec, MSec);
end;
{ Date encoding and decoding }
function IsLeapYear(Year: Word): Boolean;
begin
Result := (Year mod 4 = 0) and ((Year mod 100 <> 0) or (Year mod 400 = 0));
end;
function TryEncodeDate(Year, Month, Day: Word; out Date: TDateTime): Boolean;
var
I: Integer;
DayTable: PDayTable;
begin
Result := False;
DayTable := @MonthDays[IsLeapYear(Year)];
if (Year >= 1) and (Year <= 9999) and (Month >= 1) and (Month <= 12) and
(Day >= 1) and (Day <= DayTable^[Month]) then
begin
for I := 1 to Month - 1 do Inc(Day, DayTable^[I]);
I := Year - 1;
Date := I * 365 + I div 4 - I div 100 + I div 400 + Day - DateDelta;
Result := True;
end;
end;
function EncodeDate(Year, Month, Day: Word): TDateTime;
begin
if not TryEncodeDate(Year, Month, Day, Result) then
ConvertError(SDateEncodeError);
end;
function DecodeDateFully(const DateTime: TDateTime; var Year, Month, Day, DOW: Word): Boolean;
const
D1 = 365;
D4 = D1 * 4 + 1;
D100 = D4 * 25 - 1;
D400 = D100 * 4 + 1;
var
Y, M, D, I: Word;
T: Integer;
DayTable: PDayTable;
begin
T := DateTimeToTimeStamp(DateTime).Date;
if T <= 0 then
begin
Year := 0;
Month := 0;
Day := 0;
DOW := 0;
Result := False;
end else
begin
DOW := T mod 7 + 1;
Dec(T);
Y := 1;
while T >= D400 do
begin
Dec(T, D400);
Inc(Y, 400);
end;
DivMod(T, D100, I, D);
if I = 4 then
begin
Dec(I);
Inc(D, D100);
end;
Inc(Y, I * 100);
DivMod(D, D4, I, D);
Inc(Y, I * 4);
DivMod(D, D1, I, D);
if I = 4 then
begin
Dec(I);
Inc(D, D1);
end;
Inc(Y, I);
Result := IsLeapYear(Y);
DayTable := @MonthDays[Result];
M := 1;
while True do
begin
I := DayTable^[M];
if D < I then Break;
Dec(D, I);
Inc(M);
end;
Year := Y;
Month := M;
Day := D + 1;
end;
end;
function InternalDecodeDate(const DateTime: TDateTime; var Year, Month, Day, DOW: Word): Boolean;
begin
Result := DecodeDateFully(DateTime, Year, Month, Day, DOW);
Dec(DOW);
end;
procedure DecodeDate(const DateTime: TDateTime; var Year, Month, Day: Word);
var
Dummy: Word;
begin
DecodeDateFully(DateTime, Year, Month, Day, Dummy);
end;
{$IFDEF MSWINDOWS}
procedure DateTimeToSystemTime(const DateTime: TDateTime; var SystemTime: TSystemTime);
begin
with SystemTime do
begin
DecodeDateFully(DateTime, wYear, wMonth, wDay, wDayOfWeek);
Dec(wDayOfWeek);
DecodeTime(DateTime, wHour, wMinute, wSecond, wMilliseconds);
end;
end;
function SystemTimeToDateTime(const SystemTime: TSystemTime): TDateTime;
begin
with SystemTime do
begin
Result := EncodeDate(wYear, wMonth, wDay);
if Result >= 0 then
Result := Result + EncodeTime(wHour, wMinute, wSecond, wMilliSeconds)
else
Result := Result - EncodeTime(wHour, wMinute, wSecond, wMilliSeconds);
end;
end;
{$ENDIF}
function DayOfWeek(const DateTime: TDateTime): Word;
begin
Result := DateTimeToTimeStamp(DateTime).Date mod 7 + 1;
end;
function Date: TDateTime;
{$IFDEF MSWINDOWS}
var
SystemTime: TSystemTime;
begin
GetLocalTime(SystemTime);
with SystemTime do Result := EncodeDate(wYear, wMonth, wDay);
end;
{$ENDIF}
{$IFDEF LINUX}
var
T: TTime_T;
UT: TUnixTime;
begin
__time(@T);
localtime_r(@T, UT);
Result := EncodeDate(UT.tm_year + 1900, UT.tm_mon + 1, UT.tm_mday);
end;
{$ENDIF}
function Time: TDateTime;
{$IFDEF MSWINDOWS}
var
SystemTime: TSystemTime;
begin
GetLocalTime(SystemTime);
with SystemTime do
Result := EncodeTime(wHour, wMinute, wSecond, wMilliSeconds);
end;
{$ENDIF}
{$IFDEF LINUX}
var
T: TTime_T;
TV: TTimeVal;
UT: TUnixTime;
begin
gettimeofday(TV, nil);
T := TV.tv_sec;
localtime_r(@T, UT);
Result := EncodeTime(UT.tm_hour, UT.tm_min, UT.tm_sec, TV.tv_usec div 1000);
end;
{$ENDIF}
function GetTime: TDateTime;
begin
Result := Time;
end;
function Now: TDateTime;
{$IFDEF MSWINDOWS}
var
SystemTime: TSystemTime;
begin
GetLocalTime(SystemTime);
with SystemTime do
Result := EncodeDate(wYear, wMonth, wDay) +
EncodeTime(wHour, wMinute, wSecond, wMilliseconds);
end;
{$ENDIF}
{$IFDEF LINUX}
var
T: TTime_T;
TV: TTimeVal;
UT: TUnixTime;
begin
gettimeofday(TV, nil);
T := TV.tv_sec;
localtime_r(@T, UT);
Result := EncodeDate(UT.tm_year + 1900, UT.tm_mon + 1, UT.tm_mday) +
EncodeTime(UT.tm_hour, UT.tm_min, UT.tm_sec, TV.tv_usec div 1000);
end;
{$ENDIF}
function IncMonth(const DateTime: TDateTime; NumberOfMonths: Integer): TDateTime;
var
Year, Month, Day: Word;
begin
DecodeDate(DateTime, Year, Month, Day);
IncAMonth(Year, Month, Day, NumberOfMonths);
Result := EncodeDate(Year, Month, Day);
ReplaceTime(Result, DateTime);
end;
procedure IncAMonth(var Year, Month, Day: Word; NumberOfMonths: Integer = 1);
var
DayTable: PDayTable;
Sign: Integer;
begin
if NumberOfMonths >= 0 then Sign := 1 else Sign := -1;
Year := Year + (NumberOfMonths div 12);
NumberOfMonths := NumberOfMonths mod 12;
Inc(Month, NumberOfMonths);
if Word(Month-1) > 11 then // if Month <= 0, word(Month-1) > 11)
begin
Inc(Year, Sign);
Inc(Month, -12 * Sign);
end;
DayTable := @MonthDays[IsLeapYear(Year)];
if Day > DayTable^[Month] then Day := DayTable^[Month];
end;
procedure ReplaceTime(var DateTime: TDateTime; const NewTime: TDateTime);
begin
DateTime := Trunc(DateTime);
if DateTime >= 0 then
DateTime := DateTime + Abs(Frac(NewTime))
else
DateTime := DateTime - Abs(Frac(NewTime));
end;
procedure ReplaceDate(var DateTime: TDateTime; const NewDate: TDateTime);
var
Temp: TDateTime;
begin
Temp := NewDate;
ReplaceTime(Temp, DateTime);
DateTime := Temp;
end;
function CurrentYear: Word;
{$IFDEF MSWINDOWS}
var
SystemTime: TSystemTime;
begin
GetLocalTime(SystemTime);
Result := SystemTime.wYear;
end;
{$ENDIF}
{$IFDEF LINUX}
var
T: TTime_T;
UT: TUnixTime;
begin
__time(@T);
localtime_r(@T, UT);
Result := UT.tm_year + 1900;
end;
{$ENDIF}
{ Date/time to string conversions }
procedure DateTimeToString(var Result: string; const Format: string;
DateTime: TDateTime);
var
BufPos, AppendLevel: Integer;
Buffer: array[0..255] of Char;
procedure AppendChars(P: PChar; Count: Integer);
var
N: Integer;
begin
N := SizeOf(Buffer) - BufPos;
if N > Count then N := Count;
if N <> 0 then Move(P[0], Buffer[BufPos], N);
Inc(BufPos, N);
end;
procedure AppendString(const S: string);
begin
AppendChars(Pointer(S), Length(S));
end;
procedure AppendNumber(Number, Digits: Integer);
const
Format: array[0..3] of Char = '%.*d';
var
NumBuf: array[0..15] of Char;
begin
AppendChars(NumBuf, FormatBuf(NumBuf, SizeOf(NumBuf), Format,
SizeOf(Format), [Digits, Number]));
end;
procedure AppendFormat(Format: PChar);
var
Starter, Token, LastToken: Char;
DateDecoded, TimeDecoded, Use12HourClock,
BetweenQuotes: Boolean;
P: PChar;
Count: Integer;
Year, Month, Day, Hour, Min, Sec, MSec, H: Word;
procedure GetCount;
var
P: PChar;
begin
P := Format;
while Format^ = Starter do Inc(Format);
Count := Format - P + 1;
end;
procedure GetDate;
begin
if not DateDecoded then
begin
DecodeDate(DateTime, Year, Month, Day);
DateDecoded := True;
end;
end;
procedure GetTime;
begin
if not TimeDecoded then
begin
DecodeTime(DateTime, Hour, Min, Sec, MSec);
TimeDecoded := True;
end;
end;
{$IFDEF MSWINDOWS}
function ConvertEraString(const Count: Integer) : string;
var
FormatStr: string;
SystemTime: TSystemTime;
Buffer: array[Byte] of Char;
P: PChar;
begin
Result := '';
with SystemTime do
begin
wYear := Year;
wMonth := Month;
wDay := Day;
end;
FormatStr := 'gg';
if GetDateFormat(GetThreadLocale, DATE_USE_ALT_CALENDAR, @SystemTime,
PChar(FormatStr), Buffer, SizeOf(Buffer)) <> 0 then
begin
Result := Buffer;
if Count = 1 then
begin
case SysLocale.PriLangID of
LANG_JAPANESE:
Result := Copy(Result, 1, CharToBytelen(Result, 1));
LANG_CHINESE:
if (SysLocale.SubLangID = SUBLANG_CHINESE_TRADITIONAL)
and (ByteToCharLen(Result, Length(Result)) = 4) then
begin
P := Buffer + CharToByteIndex(Result, 3) - 1;
SetString(Result, P, CharToByteLen(P, 2));
end;
end;
end;
end;
end;
function ConvertYearString(const Count: Integer): string;
var
FormatStr: string;
SystemTime: TSystemTime;
Buffer: array[Byte] of Char;
begin
Result := '';
with SystemTime do
begin
wYear := Year;
wMonth := Month;
wDay := Day;
end;
if Count <= 2 then
FormatStr := 'yy' // avoid Win95 bug.
else
FormatStr := 'yyyy';
if GetDateFormat(GetThreadLocale, DATE_USE_ALT_CALENDAR, @SystemTime,
PChar(FormatStr), Buffer, SizeOf(Buffer)) <> 0 then
begin
Result := Buffer;
if (Count = 1) and (Result[1] = '0') then
Result := Copy(Result, 2, Length(Result)-1);
end;
end;
{$ENDIF}
{$IFDEF LINUX}
function FindEra(Date: Integer): Byte;
var
I : Byte;
begin
Result := 0;
for I := 1 to EraCount do
begin
if (EraRanges[I].StartDate <= Date) and
(EraRanges[I].EndDate >= Date) then
begin
Result := I;
Exit;
end;
end;
end;
function ConvertEraString(const Count: Integer) : String;
var
I : Byte;
begin
Result := '';
I := FindEra(Trunc(DateTime));
if I > 0 then
Result := EraNames[I];
end;
function ConvertYearString(const Count: Integer) : String;
var
I : Byte;
S : string;
begin
I := FindEra(Trunc(DateTime));
if I > 0 then
S := IntToStr(Year - EraYearOffsets[I])
else
S := IntToStr(Year);
while Length(S) < Count do
S := '0' + S;
if Length(S) > Count then
S := Copy(S, Length(S) - (Count - 1), Count);
Result := S;
end;
{$ENDIF}
begin
if (Format <> nil) and (AppendLevel < 2) then
begin
Inc(AppendLevel);
LastToken := ' ';
DateDecoded := False;
TimeDecoded := False;
Use12HourClock := False;
while Format^ <> #0 do
begin
Starter := Format^;
if Starter in LeadBytes then
begin
AppendChars(Format, StrCharLength(Format));
Format := StrNextChar(Format);
LastToken := ' ';
Continue;
end;
Format := StrNextChar(Format);
Token := Starter;
if Token in ['a'..'z'] then Dec(Token, 32);
if Token in ['A'..'Z'] then
begin
if (Token = 'M') and (LastToken = 'H') then Token := 'N';
LastToken := Token;
end;
case Token of
'Y':
begin
GetCount;
GetDate;
if Count <= 2 then
AppendNumber(Year mod 100, 2) else
AppendNumber(Year, 4);
end;
'G':
begin
GetCount;
GetDate;
AppendString(ConvertEraString(Count));
end;
'E':
begin
GetCount;
GetDate;
AppendString(ConvertYearString(Count));
end;
'M':
begin
GetCount;
GetDate;
case Count of
1, 2: AppendNumber(Month, Count);
3: AppendString(ShortMonthNames[Month]);
else
AppendString(LongMonthNames[Month]);
end;
end;
'D':
begin
GetCount;
case Count of
1, 2:
begin
GetDate;
AppendNumber(Day, Count);
end;
3: AppendString(ShortDayNames[DayOfWeek(DateTime)]);
4: AppendString(LongDayNames[DayOfWeek(DateTime)]);
5: AppendFormat(Pointer(ShortDateFormat));
else
AppendFormat(Pointer(LongDateFormat));
end;
end;
'H':
begin
GetCount;
GetTime;
BetweenQuotes := False;
P := Format;
while P^ <> #0 do
begin
if P^ in LeadBytes then
begin
P := StrNextChar(P);
Continue;
end;
case P^ of
'A', 'a':
if not BetweenQuotes then
begin
if ( (StrLIComp(P, 'AM/PM', 5) = 0)
or (StrLIComp(P, 'A/P', 3) = 0)
or (StrLIComp(P, 'AMPM', 4) = 0) ) then
Use12HourClock := True;
Break;
end;
'H', 'h':
Break;
'''', '"': BetweenQuotes := not BetweenQuotes;
end;
Inc(P);
end;
H := Hour;
if Use12HourClock then
if H = 0 then H := 12 else if H > 12 then Dec(H, 12);
if Count > 2 then Count := 2;
AppendNumber(H, Count);
end;
'N':
begin
GetCount;
GetTime;
if Count > 2 then Count := 2;
AppendNumber(Min, Count);
end;
'S':
begin
GetCount;
GetTime;
if Count > 2 then Count := 2;
AppendNumber(Sec, Count);
end;
'T':
begin
GetCount;
if Count = 1 then
AppendFormat(Pointer(ShortTimeFormat)) else
AppendFormat(Pointer(LongTimeFormat));
end;
'Z':
begin
GetCount;
GetTime;
if Count > 3 then Count := 3;
AppendNumber(MSec, Count);
end;
'A':
begin
GetTime;
P := Format - 1;
if StrLIComp(P, 'AM/PM', 5) = 0 then
begin
if Hour >= 12 then Inc(P, 3);
AppendChars(P, 2);
Inc(Format, 4);
Use12HourClock := TRUE;
end else
if StrLIComp(P, 'A/P', 3) = 0 then
begin
if Hour >= 12 then Inc(P, 2);
AppendChars(P, 1);
Inc(Format, 2);
Use12HourClock := TRUE;
end else
if StrLIComp(P, 'AMPM', 4) = 0 then
begin
if Hour < 12 then
AppendString(TimeAMString) else
AppendString(TimePMString);
Inc(Format, 3);
Use12HourClock := TRUE;
end else
if StrLIComp(P, 'AAAA', 4) = 0 then
begin
GetDate;
AppendString(LongDayNames[DayOfWeek(DateTime)]);
Inc(Format, 3);
end else
if StrLIComp(P, 'AAA', 3) = 0 then
begin
GetDate;
AppendString(ShortDayNames[DayOfWeek(DateTime)]);
Inc(Format, 2);
end else
AppendChars(@Starter, 1);
end;
'C':
begin
GetCount;
AppendFormat(Pointer(ShortDateFormat));
GetTime;
if (Hour <> 0) or (Min <> 0) or (Sec <> 0) then
begin
AppendChars(' ', 1);
AppendFormat(Pointer(LongTimeFormat));
end;
end;
'/':
if DateSeparator <> #0 then
AppendChars(@DateSeparator, 1);
':':
if TimeSeparator <> #0 then
AppendChars(@TimeSeparator, 1);
'''', '"':
begin
P := Format;
while (Format^ <> #0) and (Format^ <> Starter) do
begin
if Format^ in LeadBytes then
Format := StrNextChar(Format)
else
Inc(Format);
end;
AppendChars(P, Format - P);
if Format^ <> #0 then Inc(Format);
end;
else
AppendChars(@Starter, 1);
end;
end;
Dec(AppendLevel);
end;
end;
begin
BufPos := 0;
AppendLevel := 0;
if Format <> '' then AppendFormat(Pointer(Format)) else AppendFormat('C');
SetString(Result, Buffer, BufPos);
end;
procedure DateTimeToString(var Result: string; const Format: string;
DateTime: TDateTime; const FormatSettings: TFormatSettings);
var
BufPos, AppendLevel: Integer;
Buffer: array[0..255] of Char;
procedure AppendChars(P: PChar; Count: Integer);
var
N: Integer;
begin
N := SizeOf(Buffer) - BufPos;
if N > Count then N := Count;
if N <> 0 then Move(P[0], Buffer[BufPos], N);
Inc(BufPos, N);
end;
procedure AppendString(const S: string);
begin
AppendChars(Pointer(S), Length(S));
end;
procedure AppendNumber(Number, Digits: Integer);
const
Format: array[0..3] of Char = '%.*d';
var
NumBuf: array[0..15] of Char;
begin
AppendChars(NumBuf, FormatBuf(NumBuf, SizeOf(NumBuf), Format,
SizeOf(Format), [Digits, Number]));
end;
procedure AppendFormat(Format: PChar);
var
Starter, Token, LastToken: Char;
DateDecoded, TimeDecoded, Use12HourClock,
BetweenQuotes: Boolean;
P: PChar;
Count: Integer;
Year, Month, Day, Hour, Min, Sec, MSec, H: Word;
procedure GetCount;
var
P: PChar;
begin
P := Format;
while Format^ = Starter do Inc(Format);
Count := Format - P + 1;
end;
procedure GetDate;
begin
if not DateDecoded then
begin
DecodeDate(DateTime, Year, Month, Day);
DateDecoded := True;
end;
end;
procedure GetTime;
begin
if not TimeDecoded then
begin
DecodeTime(DateTime, Hour, Min, Sec, MSec);
TimeDecoded := True;
end;
end;
{$IFDEF MSWINDOWS}
function ConvertEraString(const Count: Integer) : string;
var
FormatStr: string;
SystemTime: TSystemTime;
Buffer: array[Byte] of Char;
P: PChar;
begin
Result := '';
with SystemTime do
begin
wYear := Year;
wMonth := Month;
wDay := Day;
end;
FormatStr := 'gg';
if GetDateFormat(GetThreadLocale, DATE_USE_ALT_CALENDAR, @SystemTime,
PChar(FormatStr), Buffer, SizeOf(Buffer)) <> 0 then
begin
Result := Buffer;
if Count = 1 then
begin
case SysLocale.PriLangID of
LANG_JAPANESE:
Result := Copy(Result, 1, CharToBytelen(Result, 1));
LANG_CHINESE:
if (SysLocale.SubLangID = SUBLANG_CHINESE_TRADITIONAL)
and (ByteToCharLen(Result, Length(Result)) = 4) then
begin
P := Buffer + CharToByteIndex(Result, 3) - 1;
SetString(Result, P, CharToByteLen(P, 2));
end;
end;
end;
end;
end;
function ConvertYearString(const Count: Integer): string;
var
FormatStr: string;
SystemTime: TSystemTime;
Buffer: array[Byte] of Char;
begin
Result := '';
with SystemTime do
begin
wYear := Year;
wMonth := Month;
wDay := Day;
end;
if Count <= 2 then
FormatStr := 'yy' // avoid Win95 bug.
else
FormatStr := 'yyyy';
if GetDateFormat(GetThreadLocale, DATE_USE_ALT_CALENDAR, @SystemTime,
PChar(FormatStr), Buffer, SizeOf(Buffer)) <> 0 then
begin
Result := Buffer;
if (Count = 1) and (Result[1] = '0') then
Result := Copy(Result, 2, Length(Result)-1);
end;
end;
{$ENDIF}
{$IFDEF LINUX}
function FindEra(Date: Integer): Byte;
var
I : Byte;
begin
Result := 0;
for I := 1 to EraCount do
begin
if (EraRanges[I].StartDate <= Date) and
(EraRanges[I].EndDate >= Date) then
begin
Result := I;
Exit;
end;
end;
end;
function ConvertEraString(const Count: Integer) : String;
var
I : Byte;
begin
Result := '';
I := FindEra(Trunc(DateTime));
if I > 0 then
Result := EraNames[I];
end;
function ConvertYearString(const Count: Integer) : String;
var
I : Byte;
S : string;
begin
I := FindEra(Trunc(DateTime));
if I > 0 then
S := IntToStr(Year - EraYearOffsets[I])
else
S := IntToStr(Year);
while Length(S) < Count do
S := '0' + S;
if Length(S) > Count then
S := Copy(S, Length(S) - (Count - 1), Count);
Result := S;
end;
{$ENDIF}
begin
if (Format <> nil) and (AppendLevel < 2) then
begin
Inc(AppendLevel);
LastToken := ' ';
DateDecoded := False;
TimeDecoded := False;
Use12HourClock := False;
while Format^ <> #0 do
begin
Starter := Format^;
if Starter in LeadBytes then
begin
AppendChars(Format, StrCharLength(Format));
Format := StrNextChar(Format);
LastToken := ' ';
Continue;
end;
Format := StrNextChar(Format);
Token := Starter;
if Token in ['a'..'z'] then Dec(Token, 32);
if Token in ['A'..'Z'] then
begin
if (Token = 'M') and (LastToken = 'H') then Token := 'N';
LastToken := Token;
end;
case Token of
'Y':
begin
GetCount;
GetDate;
if Count <= 2 then
AppendNumber(Year mod 100, 2) else
AppendNumber(Year, 4);
end;
'G':
begin
GetCount;
GetDate;
AppendString(ConvertEraString(Count));
end;
'E':
begin
GetCount;
GetDate;
AppendString(ConvertYearString(Count));
end;
'M':
begin
GetCount;
GetDate;
case Count of
1, 2: AppendNumber(Month, Count);
3: AppendString(FormatSettings.ShortMonthNames[Month]);
else
AppendString(FormatSettings.LongMonthNames[Month]);
end;
end;
'D':
begin
GetCount;
case Count of
1, 2:
begin
GetDate;
AppendNumber(Day, Count);
end;
3: AppendString(FormatSettings.ShortDayNames[DayOfWeek(DateTime)]);
4: AppendString(FormatSettings.LongDayNames[DayOfWeek(DateTime)]);
5: AppendFormat(Pointer(FormatSettings.ShortDateFormat));
else
AppendFormat(Pointer(FormatSettings.LongDateFormat));
end;
end;
'H':
begin
GetCount;
GetTime;
BetweenQuotes := False;
P := Format;
while P^ <> #0 do
begin
if P^ in LeadBytes then
begin
P := StrNextChar(P);
Continue;
end;
case P^ of
'A', 'a':
if not BetweenQuotes then
begin
if ( (StrLIComp(P, 'AM/PM', 5) = 0)
or (StrLIComp(P, 'A/P', 3) = 0)
or (StrLIComp(P, 'AMPM', 4) = 0) ) then
Use12HourClock := True;
Break;
end;
'H', 'h':
Break;
'''', '"': BetweenQuotes := not BetweenQuotes;
end;
Inc(P);
end;
H := Hour;
if Use12HourClock then
if H = 0 then H := 12 else if H > 12 then Dec(H, 12);
if Count > 2 then Count := 2;
AppendNumber(H, Count);
end;
'N':
begin
GetCount;
GetTime;
if Count > 2 then Count := 2;
AppendNumber(Min, Count);
end;
'S':
begin
GetCount;
GetTime;
if Count > 2 then Count := 2;
AppendNumber(Sec, Count);
end;
'T':
begin
GetCount;
if Count = 1 then
AppendFormat(Pointer(FormatSettings.ShortTimeFormat)) else
AppendFormat(Pointer(FormatSettings.LongTimeFormat));
end;
'Z':
begin
GetCount;
GetTime;
if Count > 3 then Count := 3;
AppendNumber(MSec, Count);
end;
'A':
begin
GetTime;
P := Format - 1;
if StrLIComp(P, 'AM/PM', 5) = 0 then
begin
if Hour >= 12 then Inc(P, 3);
AppendChars(P, 2);
Inc(Format, 4);
Use12HourClock := TRUE;
end else
if StrLIComp(P, 'A/P', 3) = 0 then
begin
if Hour >= 12 then Inc(P, 2);
AppendChars(P, 1);
Inc(Format, 2);
Use12HourClock := TRUE;
end else
if StrLIComp(P, 'AMPM', 4) = 0 then
begin
if Hour < 12 then
AppendString(FormatSettings.TimeAMString) else
AppendString(FormatSettings.TimePMString);
Inc(Format, 3);
Use12HourClock := TRUE;
end else
if StrLIComp(P, 'AAAA', 4) = 0 then
begin
GetDate;
AppendString(FormatSettings.LongDayNames[DayOfWeek(DateTime)]);
Inc(Format, 3);
end else
if StrLIComp(P, 'AAA', 3) = 0 then
begin
GetDate;
AppendString(FormatSettings.ShortDayNames[DayOfWeek(DateTime)]);
Inc(Format, 2);
end else
AppendChars(@Starter, 1);
end;
'C':
begin
GetCount;
AppendFormat(Pointer(FormatSettings.ShortDateFormat));
GetTime;
if (Hour <> 0) or (Min <> 0) or (Sec <> 0) then
begin
AppendChars(' ', 1);
AppendFormat(Pointer(FormatSettings.LongTimeFormat));
end;
end;
'/':
if DateSeparator <> #0 then
AppendChars(@FormatSettings.DateSeparator, 1);
':':
if TimeSeparator <> #0 then
AppendChars(@FormatSettings.TimeSeparator, 1);
'''', '"':
begin
P := Format;
while (Format^ <> #0) and (Format^ <> Starter) do
begin
if Format^ in LeadBytes then
Format := StrNextChar(Format)
else
Inc(Format);
end;
AppendChars(P, Format - P);
if Format^ <> #0 then Inc(Format);
end;
else
AppendChars(@Starter, 1);
end;
end;
Dec(AppendLevel);
end;
end;
begin
BufPos := 0;
AppendLevel := 0;
if Format <> '' then AppendFormat(Pointer(Format)) else AppendFormat('C');
SetString(Result, Buffer, BufPos);
end;
function TryFloatToDateTime(const Value: Extended; out AResult: TDateTime): Boolean;
begin
Result := not ((Value < MinDateTime) or (Value >= Int(MaxDateTime) + 1.0));
if Result then
AResult := Value;
end;
function FloatToDateTime(const Value: Extended): TDateTime;
begin
if not TryFloatToDateTime(Value, Result) then
ConvertErrorFmt(SInvalidDateTimeFloat, [Value]);
end;
function DateToStr(const DateTime: TDateTime): string;
begin
DateTimeToString(Result, ShortDateFormat, DateTime);
end;
function DateToStr(const DateTime: TDateTime;
const FormatSettings: TFormatSettings): string;
begin
DateTimeToString(Result, FormatSettings.ShortDateFormat, DateTime,
FormatSettings);
end;
function TimeToStr(const DateTime: TDateTime): string;
begin
DateTimeToString(Result, LongTimeFormat, DateTime);
end;
function TimeToStr(const DateTime: TDateTime;
const FormatSettings: TFormatSettings): string;
begin
DateTimeToString(Result, FormatSettings.LongTimeFormat, DateTime,
FormatSettings);
end;
function DateTimeToStr(const DateTime: TDateTime): string;
begin
DateTimeToString(Result, '', DateTime);
end;
function DateTimeToStr(const DateTime: TDateTime;
const FormatSettings: TFormatSettings): string;
begin
DateTimeToString(Result, '', DateTime, FormatSettings);
end;
function FormatDateTime(const Format: string; DateTime: TDateTime): string;
begin
DateTimeToString(Result, Format, DateTime);
end;
function FormatDateTime(const Format: string; DateTime: TDateTime;
const FormatSettings: TFormatSettings): string;
begin
DateTimeToString(Result, Format, DateTime, FormatSettings);
end;
{ String to date/time conversions }
type
TDateOrder = (doMDY, doDMY, doYMD);
procedure ScanBlanks(const S: string; var Pos: Integer);
var
I: Integer;
begin
I := Pos;
while (I <= Length(S)) and (S[I] = ' ') do Inc(I);
Pos := I;
end;
function ScanNumber(const S: string; var Pos: Integer;
var Number: Word; var CharCount: Byte): Boolean;
var
I: Integer;
N: Word;
begin
Result := False;
CharCount := 0;
ScanBlanks(S, Pos);
I := Pos;
N := 0;
while (I <= Length(S)) and (S[I] in ['0'..'9']) and (N < 1000) do
begin
N := N * 10 + (Ord(S[I]) - Ord('0'));
Inc(I);
end;
if I > Pos then
begin
CharCount := I - Pos;
Pos := I;
Number := N;
Result := True;
end;
end;
function ScanString(const S: string; var Pos: Integer;
const Symbol: string): Boolean;
begin
Result := False;
if Symbol <> '' then
begin
ScanBlanks(S, Pos);
if AnsiCompareText(Symbol, Copy(S, Pos, Length(Symbol))) = 0 then
begin
Inc(Pos, Length(Symbol));
Result := True;
end;
end;
end;
function ScanChar(const S: string; var Pos: Integer; Ch: Char): Boolean;
begin
Result := False;
ScanBlanks(S, Pos);
if (Pos <= Length(S)) and (S[Pos] = Ch) then
begin
Inc(Pos);
Result := True;
end;
end;
function GetDateOrder(const DateFormat: string): TDateOrder;
var
I: Integer;
begin
Result := doMDY;
I := 1;
while I <= Length(DateFormat) do
begin
case Chr(Ord(DateFormat[I]) and $DF) of
'E': Result := doYMD;
'Y': Result := doYMD;
'M': Result := doMDY;
'D': Result := doDMY;
else
Inc(I);
Continue;
end;
Exit;
end;
Result := doMDY;
end;
procedure ScanToNumber(const S: string; var Pos: Integer);
begin
while (Pos <= Length(S)) and not (S[Pos] in ['0'..'9']) do
begin
if S[Pos] in LeadBytes then
Pos := NextCharIndex(S, Pos)
else
Inc(Pos);
end;
end;
function GetEraYearOffset(const Name: string): Integer;
var
I: Integer;
begin
Result := 0;
for I := Low(EraNames) to High(EraNames) do
begin
if EraNames[I] = '' then Break;
if AnsiStrPos(PChar(EraNames[I]), PChar(Name)) <> nil then
begin
Result := EraYearOffsets[I];
Exit;
end;
end;
end;
function ScanDate(const S: string; var Pos: Integer;
var Date: TDateTime): Boolean; overload;
var
DateOrder: TDateOrder;
N1, N2, N3, Y, M, D: Word;
L1, L2, L3, YearLen: Byte;
CenturyBase: Integer;
EraName : string;
EraYearOffset: Integer;
function EraToYear(Year: Integer): Integer;
begin
{$IFDEF MSWINDOWS}
if SysLocale.PriLangID = LANG_KOREAN then
begin
if Year <= 99 then
Inc(Year, (CurrentYear + Abs(EraYearOffset)) div 100 * 100);
if EraYearOffset > 0 then
EraYearOffset := -EraYearOffset;
end
else
Dec(EraYearOffset);
{$ENDIF}
Result := Year + EraYearOffset;
end;
begin
Y := 0;
M := 0;
D := 0;
YearLen := 0;
Result := False;
DateOrder := GetDateOrder(ShortDateFormat);
EraYearOffset := 0;
if ShortDateFormat[1] = 'g' then // skip over prefix text
begin
ScanToNumber(S, Pos);
EraName := Trim(Copy(S, 1, Pos-1));
EraYearOffset := GetEraYearOffset(EraName);
end
else
if AnsiPos('e', ShortDateFormat) > 0 then
EraYearOffset := EraYearOffsets[1];
if not (ScanNumber(S, Pos, N1, L1) and ScanChar(S, Pos, DateSeparator) and
ScanNumber(S, Pos, N2, L2)) then Exit;
if ScanChar(S, Pos, DateSeparator) then
begin
if not ScanNumber(S, Pos, N3, L3) then Exit;
case DateOrder of
doMDY: begin Y := N3; YearLen := L3; M := N1; D := N2; end;
doDMY: begin Y := N3; YearLen := L3; M := N2; D := N1; end;
doYMD: begin Y := N1; YearLen := L1; M := N2; D := N3; end;
end;
if EraYearOffset > 0 then
Y := EraToYear(Y)
else
if (YearLen <= 2) then
begin
CenturyBase := CurrentYear - TwoDigitYearCenturyWindow;
Inc(Y, CenturyBase div 100 * 100);
if (TwoDigitYearCenturyWindow > 0) and (Y < CenturyBase) then
Inc(Y, 100);
end;
end else
begin
Y := CurrentYear;
if DateOrder = doDMY then
begin
D := N1; M := N2;
end else
begin
M := N1; D := N2;
end;
end;
ScanChar(S, Pos, DateSeparator);
ScanBlanks(S, Pos);
if SysLocale.FarEast and (System.Pos('ddd', ShortDateFormat) <> 0) then
begin // ignore trailing text
if ShortTimeFormat[1] in ['0'..'9'] then // stop at time digit
ScanToNumber(S, Pos)
else // stop at time prefix
repeat
while (Pos <= Length(S)) and (S[Pos] <> ' ') do Inc(Pos);
ScanBlanks(S, Pos);
until (Pos > Length(S)) or
(AnsiCompareText(TimeAMString, Copy(S, Pos, Length(TimeAMString))) = 0) or
(AnsiCompareText(TimePMString, Copy(S, Pos, Length(TimePMString))) = 0);
end;
Result := TryEncodeDate(Y, M, D, Date);
end;
function ScanDate(const S: string; var Pos: Integer; var Date: TDateTime;
const FormatSettings: TFormatSettings): Boolean; overload;
var
DateOrder: TDateOrder;
N1, N2, N3, Y, M, D: Word;
L1, L2, L3, YearLen: Byte;
CenturyBase: Integer;
EraName : string;
EraYearOffset: Integer;
function EraToYear(Year: Integer): Integer;
begin
{$IFDEF MSWINDOWS}
if SysLocale.PriLangID = LANG_KOREAN then
begin
if Year <= 99 then
Inc(Year, (CurrentYear + Abs(EraYearOffset)) div 100 * 100);
if EraYearOffset > 0 then
EraYearOffset := -EraYearOffset;
end
else
Dec(EraYearOffset);
{$ENDIF}
Result := Year + EraYearOffset;
end;
begin
Y := 0;
M := 0;
D := 0;
YearLen := 0;
Result := False;
DateOrder := GetDateOrder(FormatSettings.ShortDateFormat);
EraYearOffset := 0;
if FormatSettings.ShortDateFormat[1] = 'g' then // skip over prefix text
begin
ScanToNumber(S, Pos);
EraName := Trim(Copy(S, 1, Pos-1));
EraYearOffset := GetEraYearOffset(EraName);
end
else
if AnsiPos('e', FormatSettings.ShortDateFormat) > 0 then
EraYearOffset := EraYearOffsets[1];
if not (ScanNumber(S, Pos, N1, L1) and ScanChar(S, Pos, FormatSettings.DateSeparator) and
ScanNumber(S, Pos, N2, L2)) then Exit;
if ScanChar(S, Pos, FormatSettings.DateSeparator) then
begin
if not ScanNumber(S, Pos, N3, L3) then Exit;
case DateOrder of
doMDY: begin Y := N3; YearLen := L3; M := N1; D := N2; end;
doDMY: begin Y := N3; YearLen := L3; M := N2; D := N1; end;
doYMD: begin Y := N1; YearLen := L1; M := N2; D := N3; end;
end;
if EraYearOffset > 0 then
Y := EraToYear(Y)
else
if (YearLen <= 2) then
begin
CenturyBase := CurrentYear - FormatSettings.TwoDigitYearCenturyWindow;
Inc(Y, CenturyBase div 100 * 100);
if (FormatSettings.TwoDigitYearCenturyWindow > 0) and (Y < CenturyBase) then
Inc(Y, 100);
end;
end else
begin
Y := CurrentYear;
if DateOrder = doDMY then
begin
D := N1; M := N2;
end else
begin
M := N1; D := N2;
end;
end;
ScanChar(S, Pos, FormatSettings.DateSeparator);
ScanBlanks(S, Pos);
if SysLocale.FarEast and (System.Pos('ddd', FormatSettings.ShortDateFormat) <> 0) then
begin // ignore trailing text
if FormatSettings.ShortTimeFormat[1] in ['0'..'9'] then // stop at time digit
ScanToNumber(S, Pos)
else // stop at time prefix
repeat
while (Pos <= Length(S)) and (S[Pos] <> ' ') do Inc(Pos);
ScanBlanks(S, Pos);
until (Pos > Length(S)) or
(AnsiCompareText(FormatSettings.TimeAMString,
Copy(S, Pos, Length(FormatSettings.TimeAMString))) = 0) or
(AnsiCompareText(FormatSettings.TimePMString,
Copy(S, Pos, Length(FormatSettings.TimePMString))) = 0);
end;
Result := TryEncodeDate(Y, M, D, Date);
end;
function ScanTime(const S: string; var Pos: Integer;
var Time: TDateTime): Boolean; overload;
var
BaseHour: Integer;
Hour, Min, Sec, MSec: Word;
Junk: Byte;
begin
Result := False;
BaseHour := -1;
if ScanString(S, Pos, TimeAMString) or ScanString(S, Pos, 'AM') then
BaseHour := 0
else if ScanString(S, Pos, TimePMString) or ScanString(S, Pos, 'PM') then
BaseHour := 12;
if BaseHour >= 0 then ScanBlanks(S, Pos);
if not ScanNumber(S, Pos, Hour, Junk) then Exit;
Min := 0;
Sec := 0;
MSec := 0;
if ScanChar(S, Pos, TimeSeparator) then
begin
if not ScanNumber(S, Pos, Min, Junk) then Exit;
if ScanChar(S, Pos, TimeSeparator) then
begin
if not ScanNumber(S, Pos, Sec, Junk) then Exit;
if ScanChar(S, Pos, DecimalSeparator) then
if not ScanNumber(S, Pos, MSec, Junk) then Exit;
end;
end;
if BaseHour < 0 then
if ScanString(S, Pos, TimeAMString) or ScanString(S, Pos, 'AM') then
BaseHour := 0
else
if ScanString(S, Pos, TimePMString) or ScanString(S, Pos, 'PM') then
BaseHour := 12;
if BaseHour >= 0 then
begin
if (Hour = 0) or (Hour > 12) then Exit;
if Hour = 12 then Hour := 0;
Inc(Hour, BaseHour);
end;
ScanBlanks(S, Pos);
Result := TryEncodeTime(Hour, Min, Sec, MSec, Time);
end;
function ScanTime(const S: string; var Pos: Integer; var Time: TDateTime;
const FormatSettings: TFormatSettings): Boolean; overload;
var
BaseHour: Integer;
Hour, Min, Sec, MSec: Word;
Junk: Byte;
begin
Result := False;
BaseHour := -1;
if ScanString(S, Pos, FormatSettings.TimeAMString) or ScanString(S, Pos, 'AM') then
BaseHour := 0
else if ScanString(S, Pos, FormatSettings.TimePMString) or ScanString(S, Pos, 'PM') then
BaseHour := 12;
if BaseHour >= 0 then ScanBlanks(S, Pos);
if not ScanNumber(S, Pos, Hour, Junk) then Exit;
Min := 0;
Sec := 0;
MSec := 0;
if ScanChar(S, Pos, FormatSettings.TimeSeparator) then
begin
if not ScanNumber(S, Pos, Min, Junk) then Exit;
if ScanChar(S, Pos, FormatSettings.TimeSeparator) then
begin
if not ScanNumber(S, Pos, Sec, Junk) then Exit;
if ScanChar(S, Pos, FormatSettings.DecimalSeparator) then
if not ScanNumber(S, Pos, MSec, Junk) then Exit;
end;
end;
if BaseHour < 0 then
if ScanString(S, Pos, FormatSettings.TimeAMString) or ScanString(S, Pos, 'AM') then
BaseHour := 0
else
if ScanString(S, Pos, FormatSettings.TimePMString) or ScanString(S, Pos, 'PM') then
BaseHour := 12;
if BaseHour >= 0 then
begin
if (Hour = 0) or (Hour > 12) then Exit;
if Hour = 12 then Hour := 0;
Inc(Hour, BaseHour);
end;
ScanBlanks(S, Pos);
Result := TryEncodeTime(Hour, Min, Sec, MSec, Time);
end;
function StrToDate(const S: string): TDateTime;
begin
if not TryStrToDate(S, Result) then
ConvertErrorFmt(SInvalidDate, [S]);
end;
function StrToDate(const S: string;
const FormatSettings: TFormatSettings): TDateTime;
begin
if not TryStrToDate(S, Result, FormatSettings) then
ConvertErrorFmt(SInvalidDate, [S]);
end;
function StrToDateDef(const S: string; const Default: TDateTime): TDateTime;
begin
if not TryStrToDate(S, Result) then
Result := Default;
end;
function StrToDateDef(const S: string; const Default: TDateTime;
const FormatSettings: TFormatSettings): TDateTime;
begin
if not TryStrToDate(S, Result, FormatSettings) then
Result := Default;
end;
function TryStrToDate(const S: string; out Value: TDateTime): Boolean;
var
Pos: Integer;
begin
Pos := 1;
Result := ScanDate(S, Pos, Value) and (Pos > Length(S));
end;
function TryStrToDate(const S: string; out Value: TDateTime;
const FormatSettings: TFormatSettings): Boolean;
var
Pos: Integer;
begin
Pos := 1;
Result := ScanDate(S, Pos, Value, FormatSettings) and (Pos > Length(S));
end;
function StrToTime(const S: string): TDateTime;
begin
if not TryStrToTime(S, Result) then
ConvertErrorFmt(SInvalidTime, [S]);
end;
function StrToTime(const S: string;
const FormatSettings: TFormatSettings): TDateTime;
begin
if not TryStrToTime(S, Result, FormatSettings) then
ConvertErrorFmt(SInvalidTime, [S]);
end;
function StrToTimeDef(const S: string; const Default: TDateTime): TDateTime;
begin
if not TryStrToTime(S, Result) then
Result := Default;
end;
function StrToTimeDef(const S: string; const Default: TDateTime;
const FormatSettings: TFormatSettings): TDateTime;
begin
if not TryStrToTime(S, Result, FormatSettings) then
Result := Default;
end;
function TryStrToTime(const S: string; out Value: TDateTime): Boolean;
var
Pos: Integer;
begin
Pos := 1;
Result := ScanTime(S, Pos, Value) and (Pos > Length(S));
end;
function TryStrToTime(const S: string; out Value: TDateTime;
const FormatSettings: TFormatSettings): Boolean;
var
Pos: Integer;
begin
Pos := 1;
Result := ScanTime(S, Pos, Value, FormatSettings) and (Pos > Length(S));
end;
function StrToDateTime(const S: string): TDateTime;
begin
if not TryStrToDateTime(S, Result) then
ConvertErrorFmt(SInvalidDateTime, [S]);
end;
function StrToDateTime(const S: string;
const FormatSettings: TFormatSettings): TDateTime;
begin
if not TryStrToDateTime(S, Result, FormatSettings) then
ConvertErrorFmt(SInvalidDateTime, [S]);
end;
function StrToDateTimeDef(const S: string; const Default: TDateTime): TDateTime;
begin
if not TryStrToDateTime(S, Result) then
Result := Default;
end;
function StrToDateTimeDef(const S: string; const Default: TDateTime;
const FormatSettings: TFormatSettings): TDateTime;
begin
if not TryStrToDateTime(S, Result, FormatSettings) then
Result := Default;
end;
function TryStrToDateTime(const S: string; out Value: TDateTime): Boolean;
var
Pos: Integer;
Date, Time: TDateTime;
begin
Result := True;
Pos := 1;
Time := 0;
if not ScanDate(S, Pos, Date) or
not ((Pos > Length(S)) or ScanTime(S, Pos, Time)) then
// Try time only
Result := TryStrToTime(S, Value)
else
if Date >= 0 then
Value := Date + Time
else
Value := Date - Time;
end;
function TryStrToDateTime(const S: string; out Value: TDateTime;
const FormatSettings: TFormatSettings): Boolean;
var
Pos: Integer;
Date, Time: TDateTime;
begin
Result := True;
Pos := 1;
Time := 0;
if not ScanDate(S, Pos, Date, FormatSettings) or
not ((Pos > Length(S)) or ScanTime(S, Pos, Time, FormatSettings)) then
// Try time only
Result := TryStrToTime(S, Value, FormatSettings)
else
if Date >= 0 then
Value := Date + Time
else
Value := Date - Time;
end;
{ System error messages }
function SysErrorMessage(ErrorCode: Integer): string;
var
Buffer: array[0..255] of Char;
{$IFDEF MSWINDOWS}
var
Len: Integer;
begin
Len := FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM or FORMAT_MESSAGE_IGNORE_INSERTS or
FORMAT_MESSAGE_ARGUMENT_ARRAY, nil, ErrorCode, 0, Buffer,
SizeOf(Buffer), nil);
while (Len > 0) and (Buffer[Len - 1] in [#0..#32, '.']) do Dec(Len);
SetString(Result, Buffer, Len);
end;
{$ENDIF}
{$IFDEF LINUX}
begin
//Result := Format('System error: %4x',[ErrorCode]);
Result := strerror_r(ErrorCode, Buffer, sizeof(Buffer));
end;
{$ENDIF}
{ Initialization file support }
function GetLocaleStr(Locale, LocaleType: Integer; const Default: string): string;
{$IFDEF MSWINDOWS}
var
L: Integer;
Buffer: array[0..255] of Char;
begin
L := GetLocaleInfo(Locale, LocaleType, Buffer, SizeOf(Buffer));
if L > 0 then SetString(Result, Buffer, L - 1) else Result := Default;
end;
{$ENDIF}
{$IFDEF LINUX}
begin
Result := Default;
end;
{$ENDIF}
function GetLocaleChar(Locale, LocaleType: Integer; Default: Char): Char;
{$IFDEF MSWINDOWS}
var
Buffer: array[0..1] of Char;
begin
if GetLocaleInfo(Locale, LocaleType, Buffer, 2) > 0 then
Result := Buffer[0] else
Result := Default;
end;
{$ENDIF}
{$IFDEF LINUX}
begin
Result := Default;
end;
{$ENDIF}
{var
DefShortMonthNames: array[1..12] of Pointer = (@SShortMonthNameJan,
@SShortMonthNameFeb, @SShortMonthNameMar, @SShortMonthNameApr,
@SShortMonthNameMay, @SShortMonthNameJun, @SShortMonthNameJul,
@SShortMonthNameAug, @SShortMonthNameSep, @SShortMonthNameOct,
@SShortMonthNameNov, @SShortMonthNameDec);
DefLongMonthNames: array[1..12] of Pointer = (@SLongMonthNameJan,
@SLongMonthNameFeb, @SLongMonthNameMar, @SLongMonthNameApr,
@SLongMonthNameMay, @SLongMonthNameJun, @SLongMonthNameJul,
@SLongMonthNameAug, @SLongMonthNameSep, @SLongMonthNameOct,
@SLongMonthNameNov, @SLongMonthNameDec);
DefShortDayNames: array[1..7] of Pointer = (@SShortDayNameSun,
@SShortDayNameMon, @SShortDayNameTue, @SShortDayNameWed,
@SShortDayNameThu, @SShortDayNameFri, @SShortDayNameSat);
DefLongDayNames: array[1..7] of Pointer = (@SLongDayNameSun,
@SLongDayNameMon, @SLongDayNameTue, @SLongDayNameWed,
@SLongDayNameThu, @SLongDayNameFri, @SLongDayNameSat);
}
procedure GetMonthDayNames;
{$IFDEF MSWINDOWS}
var
I, Day: Integer;
DefaultLCID: LCID;
function LocalGetLocaleStr(LocaleType: Integer): string;
begin
Result := GetLocaleStr(DefaultLCID, LocaleType, '');
if Result = '' then Result := GetLocaleStr($409, LocaleType, '');
//Result := LoadResString(DefValues[Index]);
end;
begin
DefaultLCID := GetThreadLocale;
for I := 1 to 12 do
begin
ShortMonthNames[I] := LocalGetLocaleStr(LOCALE_SABBREVMONTHNAME1 + I - 1);
LongMonthNames[I] := LocalGetLocaleStr(LOCALE_SMONTHNAME1 + I - 1);
end;
for I := 1 to 7 do
begin
Day := (I + 5) mod 7;
ShortDayNames[I] := LocalGetLocaleStr(LOCALE_SABBREVDAYNAME1 + Day);
LongDayNames[I] := LocalGetLocaleStr(LOCALE_SDAYNAME1 + Day);
end;
end;
{$ELSE}
{$IFDEF LINUX}
function GetLocaleStr(LocaleIndex, Index: Integer;
const DefValues: array of Pointer): string;
var
temp: PChar;
begin
temp := nl_langinfo(LocaleIndex);
if (temp = nil) or (temp^ = #0) then
Result := LoadResString(DefValues[Index])
else
Result := temp;
end;
var
I: Integer;
begin
for I := 1 to 12 do
begin
ShortMonthNames[I] := GetLocaleStr(ABMON_1 + I - 1,
I - Low(DefShortMonthNames), DefShortMonthNames);
LongMonthNames[I] := GetLocaleStr(MON_1 + I - 1,
I - Low(DefLongMonthNames), DefLongMonthNames);
end;
for I := 1 to 7 do
begin
ShortDayNames[I] := GetLocaleStr(ABDAY_1 + I - 1,
I - Low(DefShortDayNames), DefShortDayNames);
LongDayNames[I] := GetLocaleStr(DAY_1 + I - 1,
I - Low(DefLongDayNames), DefLongDayNames);
end;
end;
{$ELSE}
var
I: Integer;
begin
for I := 1 to 12 do
begin
ShortMonthNames[I] := LoadResString(DefShortMonthNames[I]);
LongMonthNames[I] := LoadResString(DefLongMonthNames[I]);
end;
for I := 1 to 7 do
begin
ShortDayNames[I] := LoadResString(DefShortDayNames[I]);
LongDayNames[I] := LoadResString(DefLongDayNames[I]);
end;
end;
{$ENDIF}
{$ENDIF}
{$IFDEF MSWINDOWS}
procedure GetLocaleMonthDayNames(DefaultLCID: Integer;
var FormatSettings: TFormatSettings);
var
I, Day: Integer;
function LocalGetLocaleStr(LocaleType: Integer): string;
begin
Result := GetLocaleStr(DefaultLCID, LocaleType, '');
if Result = '' then Result := GetLocaleStr($409, LocaleType, '');
end;
begin
for I := 1 to 12 do
begin
FormatSettings.ShortMonthNames[I] := LocalGetLocaleStr(LOCALE_SABBREVMONTHNAME1 + I - 1);
FormatSettings.LongMonthNames[I] := LocalGetLocaleStr(LOCALE_SMONTHNAME1 + I - 1);
end;
for I := 1 to 7 do
begin
Day := (I + 5) mod 7;
FormatSettings.ShortDayNames[I] := LocalGetLocaleStr(LOCALE_SABBREVDAYNAME1 + Day);
FormatSettings.LongDayNames[I] := LocalGetLocaleStr(LOCALE_SDAYNAME1 + Day);
end;
end;
{$ENDIF}
{$IFDEF MSWINDOWS}
function EnumEraNames(Names: PChar): Integer; stdcall;
var
I: Integer;
begin
Result := 0;
I := Low(EraNames);
while EraNames[I] <> '' do
if (I = High(EraNames)) then
Exit
else Inc(I);
EraNames[I] := Names;
Result := 1;
end;
function EnumEraYearOffsets(YearOffsets: PChar): Integer; stdcall;
var
I: Integer;
begin
Result := 0;
I := Low(EraYearOffsets);
while EraYearOffsets[I] <> -1 do
if (I = High(EraYearOffsets)) then
Exit
else Inc(I);
EraYearOffsets[I] := StrToIntDef(YearOffsets, 0);
Result := 1;
end;
procedure GetEraNamesAndYearOffsets;
var
J: Integer;
CalendarType: CALTYPE;
begin
CalendarType := StrToIntDef(GetLocaleStr(GetThreadLocale,
LOCALE_IOPTIONALCALENDAR, '1'), 1);
if CalendarType in [CAL_JAPAN, CAL_TAIWAN, CAL_KOREA] then
begin
EnumCalendarInfoA(@EnumEraNames, GetThreadLocale, CalendarType,
CAL_SERASTRING);
for J := Low(EraYearOffsets) to High(EraYearOffsets) do
EraYearOffsets[J] := -1;
EnumCalendarInfoA(@EnumEraYearOffsets, GetThreadLocale, CalendarType,
CAL_IYEAROFFSETRANGE);
end;
end;
function TranslateDateFormat(const FormatStr: string): string;
var
I: Integer;
L: Integer;
CalendarType: CALTYPE;
RemoveEra: Boolean;
begin
I := 1;
Result := '';
CalendarType := StrToIntDef(GetLocaleStr(GetThreadLocale,
LOCALE_ICALENDARTYPE, '1'), 1);
if not (CalendarType in [CAL_JAPAN, CAL_TAIWAN, CAL_KOREA]) then
begin
RemoveEra := SysLocale.PriLangID in [LANG_JAPANESE, LANG_CHINESE, LANG_KOREAN];
if RemoveEra then
begin
While I <= Length(FormatStr) do
begin
if not (FormatStr[I] in ['g', 'G']) then
Result := Result + FormatStr[I];
Inc(I);
end;
end
else
Result := FormatStr;
Exit;
end;
while I <= Length(FormatStr) do
begin
if FormatStr[I] in LeadBytes then
begin
L := CharLength(FormatStr, I);
Result := Result + Copy(FormatStr, I, L);
Inc(I, L);
end else
begin
if StrLIComp(@FormatStr[I], 'gg', 2) = 0 then
begin
Result := Result + 'ggg';
Inc(I, 1);
end
else if StrLIComp(@FormatStr[I], 'yyyy', 4) = 0 then
begin
Result := Result + 'eeee';
Inc(I, 4-1);
end
else if StrLIComp(@FormatStr[I], 'yy', 2) = 0 then
begin
Result := Result + 'ee';
Inc(I, 2-1);
end
else if FormatStr[I] in ['y', 'Y'] then
Result := Result + 'e'
else
Result := Result + FormatStr[I];
Inc(I);
end;
end;
end;
{$ENDIF}
{$IFDEF LINUX}
procedure InitEras;
var
Count : Byte;
I, J, Pos : Integer;
Number : Word;
S : string;
Year, Month, Day: Word;
begin
EraCount := 0;
S := nl_langinfo(ERA);
if S = '' then
S := LoadResString(@SEraEntries);
Pos := 1;
for I := 1 to MaxEraCount do
begin
if Pos > Length(S) then Break;
if not(ScanChar(S, Pos, '+') or ScanChar(S, Pos, '-')) then Break;
// Eras in which year increases with negative time (eg Christian BC era)
// are not currently supported.
// EraRanges[I].Direction := S[Pos - 1];
// Era offset, in years from Gregorian calendar year
if not ScanChar(S, Pos, ':') then Break;
if ScanChar(S, Pos, '-') then
J := -1
else
J := 1;
if not ScanNumber(S, Pos, Number, Count) then Break;
EraYearOffsets[I] := J * Number; // apply sign to Number
// Era start date, in Gregorian year/month/day format
if not ScanChar(S, Pos, ':') then Break;
if not ScanNumber(S, Pos, Year, Count) then Break;
if not ScanChar(S, Pos, '/') then Break;
if not ScanNumber(S, Pos, Month, Count) then Break;
if not ScanChar(S, Pos, '/') then Break;
if not ScanNumber(S, Pos, Day, Count) then Break;
EraRanges[I].StartDate := Trunc(EncodeDate(Year, Month, Day));
EraYearOffsets[I] := Year - EraYearOffsets[I];
// Era end date, in Gregorian year/month/day format
if not ScanChar(S, Pos, ':') then Break;
if ScanString(S, Pos, '+*') then // positive infinity
EraRanges[I].EndDate := High(EraRanges[I].EndDate)
else if ScanString(S, Pos, '-*') then // negative infinity
EraRanges[I].EndDate := Low(EraRanges[I].EndDate)
else if not ScanNumber(S, Pos, Year, Count) then
Break
else
begin
if not ScanChar(S, Pos, '/') then Break;
if not ScanNumber(S, Pos, Month, Count) then Break;
if not ScanChar(S, Pos, '/') then Break;
if not ScanNumber(S, Pos, Day, Count) then Break;
EraRanges[I].EndDate := Trunc(EncodeDate(Year, Month, Day));
end;
// Era name, in locale charset
if not ScanChar(S, Pos, ':') then Break;
J := AnsiPos(':', Copy(S, Pos, Length(S) + 1 - Pos));
if J = 0 then Break;
EraNames[I] := Copy(S, Pos, J - 1);
Inc(Pos, J - 1);
// Optional Era format string for era year, in locale charset
if not ScanChar(S, Pos, ':') then Break;
J := AnsiPos(';', Copy(S, Pos, Length(S) + 1 - Pos));
if J = 0 then
J := 1 + Length(S) + 1 - Pos;
{if J = 0 then Break;}
EraYearFormats[I] := Copy(S, Pos, J - 1);
Inc(Pos, J - 1);
Inc(EraCount);
if not((Pos > Length(S)) or ScanChar(S, Pos, ';')) then Break;
end;
// Clear the rest of the era slots, including partial entry from failed parse
for I := EraCount+1 to MaxEraCount do
begin
EraNames[I] := '';
EraYearOffsets[I] := -1;
EraRanges[I].StartDate := High(EraRanges[I].StartDate);
EraRanges[I].EndDate := High(EraRanges[I].EndDate);
EraYearFormats[I] := '';
end;
end;
{$ENDIF}
{ Exception handling routines }
var
OutOfMemory: EOutOfMemory;
InvalidPointer: EInvalidPointer;
{ Convert physical address to logical address }
{ Format and return an exception error message }
function ExceptionErrorMessage(ExceptObject: TObject; ExceptAddr: Pointer;
Buffer: PChar; Size: Integer): Integer;
{$IFDEF MSWINDOWS}
function ConvertAddr(Address: Pointer): Pointer; assembler;
asm
TEST EAX,EAX { Always convert nil to nil }
JE @@1
SUB EAX, $1000 { offset from code start; code start set by linker to $1000 }
@@1:
end;
var
MsgPtr: PChar;
MsgEnd: PChar;
MsgLen: Integer;
ModuleName: array[0..MAX_PATH] of Char;
Temp: array[0..MAX_PATH] of Char;
//Format: array[0..255] of Char;
Info: TMemoryBasicInformation;
ConvertedAddress: Pointer;
begin
VirtualQuery(ExceptAddr, Info, sizeof(Info));
if (Info.State <> MEM_COMMIT) or
(GetModuleFilename(THandle(Info.AllocationBase), Temp, SizeOf(Temp)) = 0) then
begin
GetModuleFileName(HInstance, Temp, SizeOf(Temp));
ConvertedAddress := ConvertAddr(ExceptAddr);
end
else
Integer(ConvertedAddress) := Integer(ExceptAddr) - Integer(Info.AllocationBase);
StrLCopy(ModuleName, AnsiStrRScan(Temp, '\') + 1, SizeOf(ModuleName) - 1);
MsgPtr := '';
MsgEnd := '';
if ExceptObject is Exception then
begin
MsgPtr := PChar(Exception(ExceptObject).Message);
MsgLen := StrLen(MsgPtr);
if (MsgLen <> 0) and (MsgPtr[MsgLen - 1] <> '.') then MsgEnd := '.';
end;
{LoadString(FindResourceHInstance(HInstance),
PResStringRec(@SException).Identifier, Format, SizeOf(Format));
StrLFmt(Buffer, Size, Format, [ExceptObject.ClassName, ModuleName,
ConvertedAddress, MsgPtr, MsgEnd]); }
StrPCopy(Buffer, kol.Format(SException, [ExceptObject.ClassName, ModuleName,
ConvertedAddress, MsgPtr, MsgEnd]) );
Result := StrLen(Buffer);
end;
{$ENDIF}
{$IFDEF LINUX}
const
UnknownModuleName = '<unknown>';
var
MsgPtr: PChar;
MsgEnd: PChar;
MsgLen: Integer;
ModuleName: array[0..MAX_PATH] of Char;
Info: TDLInfo;
begin
MsgPtr := '';
MsgEnd := '';
if ExceptObject is Exception then
begin
MsgPtr := PChar(Exception(ExceptObject).Message);
MsgLen := StrLen(MsgPtr);
if (MsgLen <> 0) and (MsgPtr[MsgLen - 1] <> '.') then MsgEnd := '.';
end;
if (dladdr(ExceptAddr, Info) <> 0) and (Info.dli_fname <> nil) then
StrLCopy(ModuleName, AnsiStrRScan(Info.dli_fname, PathDelim) + 1, SizeOf(ModuleName) - 1)
else
StrLCopy(ModuleName, UnknownModuleName, SizeOf(ModuleName) - 1);
StrLFmt(Buffer, Size, PChar(SException), [ExceptObject.ClassName, ModuleName,
ExceptAddr, MsgPtr, MsgEnd]);
Result := StrLen(Buffer);
end;
{$ENDIF}
{ Display exception message box }
procedure ShowException(ExceptObject: TObject; ExceptAddr: Pointer);
{$IFDEF MSWINDOWS}
var
//Title: array[0..63] of Char;
Buffer: array[0..1023] of Char;
Dummy: Cardinal;
begin
ExceptionErrorMessage(ExceptObject, ExceptAddr, Buffer, SizeOf(Buffer));
if IsConsole then
begin
Flush(Output);
CharToOemA(Buffer, Buffer);
WriteFile(GetStdHandle(STD_ERROR_HANDLE), Buffer, StrLen(Buffer), Dummy, nil);
WriteFile(GetStdHandle(STD_ERROR_HANDLE), sLineBreak, 2, Dummy, nil);
end
else
begin
{ LoadString(FindResourceHInstance(HInstance), PResStringRec(@SExceptTitle).Identifier,
Title, SizeOf(Title));
MessageBox(0, Buffer, Title, MB_OK or MB_ICONSTOP or MB_TASKMODAL); }
MessageBox(0, Buffer, PChar(SExceptTitle), MB_OK or MB_ICONSTOP or MB_TASKMODAL);
end;
end;
{$ENDIF}
{$IFDEF LINUX}
var
Buffer: array[0..1023] of Char;
begin
ExceptionErrorMessage(ExceptObject, ExceptAddr, Buffer, Sizeof(Buffer));
if TTextRec(ErrOutput).Mode = fmOutput then
Flush(ErrOutput);
__write(STDERR_FILENO, Buffer, StrLen(Buffer));
end;
{$ENDIF}
{ Raise abort exception }
procedure Abort;
function ReturnAddr: Pointer;
asm
MOV EAX,[EBP + 4]
end;
begin
raise EAbort.CreateRes(SOperationAborted) at ReturnAddr;
end;
{ Raise out of memory exception }
procedure OutOfMemoryError;
begin
raise OutOfMemory;
end;
{ Exception class }
constructor Exception.Create(const Msg: string);
begin
FMessage := Msg;
end;
constructor Exception.CreateFmt(const Msg: string;
const Args: array of const);
begin
FMessage := Format(Msg, Args);
end;
constructor Exception.CreateRes(Ident: Integer);
begin
FMessage := LoadStr(Ident);
end;
constructor Exception.CreateRes(const ResStringRec: string);
begin
FMessage := ResStringRec;
end;
constructor Exception.CreateResFmt(Ident: Integer;
const Args: array of const);
begin
FMessage := Format(LoadStr(Ident), Args);
end;
constructor Exception.CreateResFmt(const ResStringRec: string;
const Args: array of const);
begin
FMessage := Format(ResStringRec, Args);
end;
constructor Exception.CreateHelp(const Msg: string; AHelpContext: Integer);
begin
FMessage := Msg;
FHelpContext := AHelpContext;
end;
constructor Exception.CreateFmtHelp(const Msg: string; const Args: array of const;
AHelpContext: Integer);
begin
FMessage := Format(Msg, Args);
FHelpContext := AHelpContext;
end;
constructor Exception.CreateResHelp(Ident: Integer; AHelpContext: Integer);
begin
FMessage := LoadStr(Ident);
FHelpContext := AHelpContext;
end;
constructor Exception.CreateResHelp(ResStringRec: PResStringRec;
AHelpContext: Integer);
begin
FMessage := LoadResString(ResStringRec);
FHelpContext := AHelpContext;
end;
constructor Exception.CreateResFmtHelp(Ident: Integer;
const Args: array of const;
AHelpContext: Integer);
begin
FMessage := Format(LoadStr(Ident), Args);
FHelpContext := AHelpContext;
end;
constructor Exception.CreateResFmtHelp(ResStringRec: PResStringRec;
const Args: array of const;
AHelpContext: Integer);
begin
FMessage := Format(LoadResString(ResStringRec), Args);
FHelpContext := AHelpContext;
end;
{ EHeapException class }
procedure EHeapException.FreeInstance;
begin
if AllowFree then
inherited FreeInstance;
end;
{ Create I/O exception }
function CreateInOutError: EInOutError;
type
TErrorRec = record
Code: Integer;
Ident: string;
end;
const
ErrorMap: array[0..6] of TErrorRec = (
(Code: 2; Ident: SFileNotFound),
(Code: 3; Ident: SInvalidFilename),
(Code: 4; Ident: STooManyOpenFiles),
(Code: 5; Ident: SAccessDenied),
(Code: 100; Ident: SEndOfFile),
(Code: 101; Ident: SDiskFull),
(Code: 106; Ident: SInvalidInput));
var
I: Integer;
InOutRes: Integer;
begin
I := Low(ErrorMap);
InOutRes := IOResult; // resets IOResult to zero
while (I <= High(ErrorMap)) and (ErrorMap[I].Code <> InOutRes) do Inc(I);
if I <= High(ErrorMap) then
Result := EInOutError.Create(ErrorMap[I].Ident) else
Result := EInOutError.CreateResFmt(SInOutError, [InOutRes]);
Result.ErrorCode := InOutRes;
end;
{ RTL error handler }
type
TExceptRec = record
EClass: ExceptClass;
EIdent: string;
end;
const
ExceptMap: array[Ord(reDivByZero)..Ord(High(TRuntimeError))] of TExceptRec = (
(EClass: EDivByZero; EIdent: SDivByZero),
(EClass: ERangeError; EIdent: SRangeError),
(EClass: EIntOverflow; EIdent: SIntOverflow),
(EClass: EInvalidOp; EIdent: SInvalidOp),
(EClass: EZeroDivide; EIdent: SZeroDivide),
(EClass: EOverflow; EIdent: SOverflow),
(EClass: EUnderflow; EIdent: SUnderflow),
(EClass: EInvalidCast; EIdent: SInvalidCast),
(EClass: EAccessViolation; EIdent: SAccessViolationNoArg),
(EClass: EPrivilege; EIdent: SPrivilege),
(EClass: EControlC; EIdent: SControlC),
(EClass: EStackOverflow; EIdent: SStackOverflow),
(EClass: EVariantError; EIdent: SInvalidVarCast),
(EClass: EVariantError; EIdent: SInvalidVarOp),
(EClass: EVariantError; EIdent: SDispatchError),
(EClass: EVariantError; EIdent: SVarArrayCreate),
(EClass: EVariantError; EIdent: SVarInvalid),
(EClass: EVariantError; EIdent: SVarArrayBounds),
(EClass: EAssertionFailed; EIdent: SAssertionFailed),
(EClass: EExternalException; EIdent: SExternalException),
(EClass: EIntfCastError; EIdent: SIntfCastError),
(EClass: ESafecallException; EIdent: SSafecallException)
{$IFDEF LINUX}
,
(EClass: EQuit; EIdent: SQuit),
(EClass: ECodesetConversion; EIdent: SCodesetConversionError)
{$ENDIF}
);
procedure ErrorHandler(ErrorCode: Byte; ErrorAddr: Pointer); export;
var
E: Exception;
begin
case ErrorCode of
Ord(reOutOfMemory):
E := OutOfMemory;
Ord(reInvalidPtr):
E := InvalidPointer;
Ord(reDivByZero)..Ord(High(TRuntimeError)):
begin
with ExceptMap[ErrorCode] do
E := EClass.Create(EIdent);
end;
else
E := CreateInOutError;
end;
raise E at ErrorAddr;
end;
{ Assertion error handler }
{ This is complicated by the desire to make it look like the exception }
{ happened in the user routine, so the debugger can give a decent stack }
{ trace. To make that feasible, AssertErrorHandler calls a helper function }
{ to create the exception object, so that AssertErrorHandler itself does }
{ not need any temps. After the exception object is created, the asm }
{ routine RaiseAssertException sets up the registers just as if the user }
{ code itself had raised the exception. }
function CreateAssertException(const Message, Filename: string;
LineNumber: Integer): Exception;
var
S: string;
begin
if Message <> '' then S := Message else S := SAssertionFailed;
Result := EAssertionFailed.CreateFmt(SAssertError,
[S, Filename, LineNumber]);
end;
{ This code is based on the following assumptions: }
{ - Our direct caller (AssertErrorHandler) has an EBP frame }
{ - ErrorStack points to where the return address would be if the }
{ user program had called System.@RaiseExcept directly }
procedure RaiseAssertException(const E: Exception; const ErrorAddr, ErrorStack: Pointer);
asm
MOV ESP,ECX
MOV [ESP],EDX
MOV EBP,[EBP]
JMP System.@RaiseExcept
end;
{ If you change this procedure, make sure it does not have any local variables }
{ or temps that need cleanup - they won't get cleaned up due to the way }
{ RaiseAssertException frame works. Also, it can not have an exception frame. }
procedure AssertErrorHandler(const Message, Filename: string;
LineNumber: Integer; ErrorAddr: Pointer);
var
E: Exception;
begin
E := CreateAssertException(Message, Filename, LineNumber);
{$IF Defined(LINUX)}
RaiseAssertException(E, ErrorAddr, PChar(@ErrorAddr)+8);
{$ELSEIF Defined(MSWINDOWS)}
RaiseAssertException(E, ErrorAddr, PChar(@ErrorAddr)+4);
{$ELSE}
{$MESSAGE ERROR 'AssertErrorHandler not implemented'}
{$IFEND}
end;
{$IFNDEF PC_MAPPED_EXCEPTIONS}
{ Abstract method invoke error handler }
procedure AbstractErrorHandler;
begin
raise EAbstractError.CreateRes(SAbstractError);
end;
{$ENDIF}
{$IFDEF LINUX}
const
TRAP_ZERODIVIDE = 0;
TRAP_SINGLESTEP = 1;
TRAP_NMI = 2;
TRAP_BREAKPOINT = 3;
TRAP_OVERFLOW = 4;
TRAP_BOUND = 5;
TRAP_INVINSTR = 6;
TRAP_DEVICENA = 7;
TRAP_DOUBLEFAULT = 8;
TRAP_FPOVERRUN = 9;
TRAP_BADTSS = 10;
TRAP_SEGMENTNP = 11;
TRAP_STACKFAULT = 12;
TRAP_GPFAULT = 13;
TRAP_PAGEFAULT = 14;
TRAP_RESERVED = 15;
TRAP_FPE = 16;
TRAP_ALIGNMENT = 17;
TRAP_MACHINECHECK = 18;
TRAP_CACHEFAULT = 19;
TRAP_UNKNOWN = -1;
function MapFPUStatus(Status: LongWord): TRuntimeError;
begin
if (Status and 1) = 1 then Result := System.reInvalidOp // STACK_CHECK or INVALID_OPERATION
else if (Status and 2) = 2 then Result := System.reInvalidOp // DENORMAL_OPERAND
else if (Status and 4) = 4 then Result := System.reZeroDivide // DIVIDE_BY_ZERO
else if (Status and 8) = 8 then Result := System.reOverflow // OVERFLOW
else if (Status and $10) = $10 then Result := System.reUnderflow // UNDERFLOW
else if (Status and $20) = $20 then Result := System.reInvalidOp // INEXACT_RESULT
else Result := System.reInvalidOp;
end;
function MapFPE(Context: PSigContext): TRuntimeError;
begin
case Context^.trapno of
TRAP_ZERODIVIDE:
Result := System.reDivByZero;
TRAP_FPOVERRUN:
Result := System.reInvalidOp;
TRAP_FPE:
Result := MapFPUStatus(Context^.fpstate^.sw);
else
Result := System.reInvalidOp;
end;
end;
function MapFault(Context: PSigContext): TRuntimeError;
begin
case Context^.trapno of
TRAP_OVERFLOW:
Result := System.reIntOverflow;
TRAP_BOUND:
Result := System.reRangeError;
TRAP_INVINSTR:
Result := System.rePrivInstruction; // This doesn't seem right, but we don't
// have an external exception to match!
TRAP_STACKFAULT:
Result := System.reStackOverflow;
TRAP_SEGMENTNP,
TRAP_GPFAULT:
Result := System.reAccessViolation;
TRAP_PAGEFAULT:
Result := System.reAccessViolation;
else
Result := System.reAccessViolation;
end;
end;
function MapSignal(SigNum: Integer; Context: PSigContext): LongWord;
var
Err: TRuntimeError;
begin
case SigNum of
SIGINT: { Control-C }
Err := System.reControlBreak;
SIGQUIT: { Quit key (Control-\) }
Err := System.reQuit;
SIGFPE: { Floating Point Error }
Err := MapFPE(Context);
SIGSEGV: { Segmentation Violation }
Err := MapFault(Context);
SIGILL: { Illegal Instruction }
Err := MapFault(Context);
SIGBUS: { Bus Error }
Err := MapFault(Context);
else
Err := System.reExternalException;
end;
Result := LongWord(Err) or (LongWord(SigNum) shl 16);
end;
{$ENDIF}
{$IFDEF MSWINDOWS}
function MapException(P: PExceptionRecord): TRuntimeError;
begin
case P.ExceptionCode of
STATUS_INTEGER_DIVIDE_BY_ZERO:
Result := System.reDivByZero;
STATUS_ARRAY_BOUNDS_EXCEEDED:
Result := System.reRangeError;
STATUS_INTEGER_OVERFLOW:
Result := System.reIntOverflow;
STATUS_FLOAT_INEXACT_RESULT,
STATUS_FLOAT_INVALID_OPERATION,
STATUS_FLOAT_STACK_CHECK:
Result := System.reInvalidOp;
STATUS_FLOAT_DIVIDE_BY_ZERO:
Result := System.reZeroDivide;
STATUS_FLOAT_OVERFLOW:
Result := System.reOverflow;
STATUS_FLOAT_UNDERFLOW,
STATUS_FLOAT_DENORMAL_OPERAND:
Result := System.reUnderflow;
STATUS_ACCESS_VIOLATION:
Result := System.reAccessViolation;
STATUS_PRIVILEGED_INSTRUCTION:
Result := System.rePrivInstruction;
STATUS_CONTROL_C_EXIT:
Result := System.reControlBreak;
STATUS_STACK_OVERFLOW:
Result := System.reStackOverflow;
else
Result := System.reExternalException;
end;
end;
function GetExceptionClass(P: PExceptionRecord): ExceptClass;
var
ErrorCode: Byte;
begin
ErrorCode := Byte(MapException(P));
Result := ExceptMap[ErrorCode].EClass;
end;
function GetExceptionObject(P: PExceptionRecord): Exception;
var
ErrorCode: Integer;
function CreateAVObject: Exception;
var
AccessOp: string; // string ID indicating the access type READ or WRITE
AccessAddress: Pointer;
MemInfo: TMemoryBasicInformation;
ModName: array[0..MAX_PATH] of Char;
begin
with P^ do
begin
if ExceptionInformation[0] = 0 then
AccessOp := SReadAccess
else
AccessOp := SWriteAccess;
AccessAddress := Pointer(ExceptionInformation[1]);
VirtualQuery(ExceptionAddress, MemInfo, SizeOf(MemInfo));
if (MemInfo.State = MEM_COMMIT) and
(GetModuleFileName(THandle(MemInfo.AllocationBase), ModName, SizeOf(ModName)) <> 0) then
Result := EAccessViolation.CreateFmt(sModuleAccessViolation,
[ExceptionAddress, ExtractFileName(ModName), AccessOp,
AccessAddress])
else
Result := EAccessViolation.CreateFmt(SAccessViolationArg3,
[ExceptionAddress, AccessOp, AccessAddress]);
end;
end;
begin
ErrorCode := Byte(MapException(P));
case ErrorCode of
3..10, 12..21:
with ExceptMap[ErrorCode] do Result := EClass.Create(EIdent);
11: Result := CreateAVObject;
else
Result := EExternalException.CreateFmt(SExternalException, [P.ExceptionCode]);
end;
if Result is EExternal then EExternal(Result).ExceptionRecord := P;
end;
{$ENDIF} { WIN32 }
{$IFDEF LINUX}
{
The ErrorCode has the translated error code in the low byte and the
original signal number in the high word.
}
function GetExceptionObject(ExceptionAddress: LongWord; AccessAddress: LongWord; ErrorCode: LongWord): Exception;
begin
case (ErrorCode and $ff) of
3..10, 12..21, 25:
begin
with ExceptMap[ErrorCode and $ff] do
Result := EClass.Create(EIdent);
end;
11:
Result := EAccessViolation.CreateFmt(SAccessViolationArg2, [Pointer(ExceptionAddress), Pointer(AccessAddress)]);
else
// Result := EExternalException.CreateFmt(SExternalException, [P.ExceptionCode]);
{ Not quite right - we need the original trap code, but that's lost }
Result := EExternalException.CreateFmt(SExternalException, [ErrorCode and $ff]);
end;
EExternal(Result).ExceptionAddress := ExceptionAddress;
EExternal(Result).AccessAddress := AccessAddress;
EExternal(Result).SignalNumber := ErrorCode shr 16;
end;
{$ENDIF}
{ RTL exception handler }
procedure ExceptHandler(ExceptObject: TObject; ExceptAddr: Pointer); far;
begin
ShowException(ExceptObject, ExceptAddr);
Halt(1);
end;
{$IFDEF LINUX}
{$IFDEF DEBUG}
{
Used for debugging the signal handlers.
}
procedure DumpContext(SigNum: Integer; context : PSigContext);
var
Buff: array [0..128] of char;
begin
StrFmt(Buff, 'Context for signal: %d', [SigNum]);
Writeln(Buff);
StrFmt(Buff, 'CS = %04X DS = %04X ES = %04X FS = %04X GS = %04X SS = %04X',
[context^.cs, context^.ds, context^.es, context^.fs, context^.gs, context^.ss]);
WriteLn(Buff);
StrFmt(Buff, 'EAX = %08X EBX = %08X ECX = %08X EDX = %08X',
[context^.eax, context^.ebx, context^.ecx, context^.edx]);
WriteLn(Buff);
StrFmt(Buff, 'EDI = %08X ESI = %08X EBP = %08X ESP = %08X',
[context^.edi, context^.esi, context^.ebp, context^.esp]);
WriteLn(Buff);
StrFmt(Buff, 'EIP = %08X EFLAGS = %08X ESP(signal) = %08X CR2 = %08X',
[context^.eip, context^.eflags, context^.esp_at_signal, context^.cr2]);
WriteLn(Buff);
StrFmt(Buff, 'trapno = %d, err = %08x', [context^.trapno, context^.err]);
WriteLn(Buff);
end;
{$ENDIF}
{
RaiseSignalException is called from SignalConverter, once we've made things look
like there's a legitimate stack frame above us. Now we will just create
an exception object, and raise it via a software raise.
}
procedure RaiseSignalException(ExceptionEIP: LongWord; FaultAddr: LongWord; ErrorCode: LongWord);
begin
raise GetExceptionObject(ExceptionEIP, FaultAddr, ErrorCode);
end;
{
SignalConverter is where we come when a signal is raised that we want to convert
to an exception. This function stands the best chance of being called with a
useable stack frame behind it for the purpose of stack unwinding. We can't
guarantee that, though. The stack was modified by the baseline signal handler
to make it look as though we were called by the faulting instruction. That way
the unwinder stands a chance of being able to clean things up.
}
procedure SignalConverter(ExceptionEIP: LongWord; FaultAddr: LongWord; ErrorCode: LongWord);
asm
{
Here's the tricky part. We arrived here directly by virtue of our
signal handler tweaking the execution context with our address. That
means there's no return address on the stack. The unwinder needs to
have a return address so that it can unwind past this function when
we raise the Delphi exception. We will use the faulting instruction
pointer as a fake return address. Because of the fencepost conditions
in the Delphi unwinder, we need to have an address that is strictly
greater than the actual faulting instruction, so we increment that
address by one. This may be in the middle of an instruction, but we
don't care, because we will never be returning to that address.
Finally, the way that we get this address onto the stack is important.
The compiler will generate unwind information for SignalConverter that
will attempt to undo any stack modifications that are made by this
function when unwinding past it. In this particular case, we don't want
that to happen, so we use some assembly language tricks to get around
the compiler noticing the stack modification.
}
MOV EBX, ESP // Get the current stack pointer
SUB EBX, 4 // Effectively decrement the stack by 4
MOV ESP, EBX // by doing a move to ESP with a register value
MOV [ESP], EAX // Store the instruction pointer into the new stack loc
INC [ESP] // Increment by one to keep the unwinder happy
{ Reset the FPU, or things can go south down the line from here }
FNINIT
FWAIT
{$IFDEF PIC}
PUSH EAX
PUSH ECX
CALL GetGOT
MOV EAX, [EAX].offset Default8087CW
FLDCW [EAX]
POP ECX
POP EAX
{$ELSE}
FLDCW Default8087CW
{$ENDIF}
PUSH EBP
MOV EBP, ESP
CALL RaiseSignalException
end;
function TlsGetValue(Key: Integer): Pointer; cdecl;
external libpthreadmodulename name 'pthread_getspecific';
{
Under Linux, we crawl out from underneath the OS signal handler before
we attempt to do anything with the signal. This is because the stack
has a bunch of OS frames on there that we cannot possibly unwind from.
So we use this routine to accomplish the dispatch, and then another
routine to handle the language level of the exception handling.
}
procedure SignalDispatcher(SigNum: Integer; SigInfo: PSigInfo; UContext: PUserContext); cdecl;
type
PGeneralRegisters = ^gregset_t;
var
GeneralRegisters: PGeneralRegisters;
begin
//DumpContext(SigNum, @context);
{
Some of the ways that we get here are can lead us to big trouble. For
example, if the signal is SIGINT or SIGQUIT, these will commonly be raised
to all threads in the process if the user generated them from the
keyboard. This is handled well by the Delphi threads, but if a non-Delphi
thread lets one of these get by unhandled, terrible things will happen.
So we look for that case, and eat SIGINT and SIGQUIT that have been issued
on threads that are not Delphi threads. If the signal is a SIGSEGV, or
other fatal sort of signal, and the thread that we're running on is not
a Delphi thread, then we are completely without options. We have no
recovery means, and we have to take the app down hard, right away.
}
if TlsGetValue(TlsIndex) = nil then
begin
if (SigNum = SIGINT) or (SigNum = SIGQUIT) then
Exit;
RunError(232);
end;
{
If we are processing another exception right now, we definitely do not
want to be dispatching any exceptions that are async, like SIGINT and
SIGQUIT. So we have check to see if OS signals are blocked. If they are,
we have to eat this signal right now.
}
if AreOSExceptionsBlocked and ((SigNum = SIGINT) or (SigNum = SIGQUIT)) then
Exit;
{
If someone wants to delay the handling of SIGINT or SIGQUIT until such
time as it's safe to handle it, they set DeferUserInterrupts to True.
Then we just set a global variable saying that a SIGINT or SIGQUIT was
issued. It is the responsibility of some other body of code at this
point to poll for changes to SIG(INT/QUIT)Issued
}
if DeferUserInterrupts then
begin
if SigNum = SIGINT then
begin
SIGINTIssued := True;
Exit;
end;
if SigNum = SIGQUIT then
begin
SIGQUITIssued := True;
Exit;
end;
end;
BlockOSExceptions;
GeneralRegisters := @UContext^.uc_mcontext.gregs;
GeneralRegisters^[REG_EAX] := GeneralRegisters^[REG_EIP];
GeneralRegisters^[REG_EDX] := UContext^.uc_mcontext.cr2;
GeneralRegisters^[REG_ECX] := MapSignal(SigNum, PSigContext(GeneralRegisters));
GeneralRegisters^[REG_EIP] := LongWord(@SignalConverter);
end;
type
TSignalMap = packed record
SigNum: Integer;
Abandon: Boolean;
OldAction: TSigAction;
Hooked: Boolean;
end;
var
Signals: array [0..RTL_SIGLAST] of TSignalMap =
( (SigNum: SIGINT;),
(SigNum: SIGFPE;),
(SigNum: SIGSEGV;),
(SigNum: SIGILL;),
(SigNum: SIGBUS;),
(SigNum: SIGQUIT;) );
function InquireSignal(RtlSigNum: Integer): TSignalState;
var
Action: TSigAction;
begin
if sigaction(Signals[RtlSigNum].SigNum, nil, @Action) = -1 then
raise Exception.CreateRes(@SSigactionFailed);
if (@Action.__sigaction_handler <> @SignalDispatcher) then
begin
if Signals[RtlSigNum].Hooked then
Result := ssOverridden
else
Result := ssNotHooked;
end
else
Result := ssHooked;
end;
procedure AbandonSignalHandler(RtlSigNum: Integer);
var
I: Integer;
begin
if RtlSigNum = RTL_SIGDEFAULT then
begin
for I := 0 to RTL_SIGLAST do
AbandonSignalHandler(I);
Exit;
end;
Signals[RtlSigNum].Abandon := True;
end;
procedure HookSignal(RtlSigNum: Integer);
var
Action: TSigAction;
I: Integer;
begin
if RtlSigNum = RTL_SIGDEFAULT then
begin
for I := 0 to RTL_SIGLAST do
HookSignal(I);
Exit;
end;
FillChar(Action, SizeOf(Action), 0);
Action.__sigaction_handler := @SignalDispatcher;
Action.sa_flags := SA_SIGINFO;
sigaddset(Action.sa_mask, SIGINT);
sigaddset(Action.sa_mask, SIGQUIT);
if sigaction(Signals[RtlSigNum].SigNum, @Action, @Signals[RtlSigNum].OldAction) = -1 then
raise Exception.CreateRes(@SSigactionFailed);
Signals[RtlSigNum].Hooked := True;
end;
procedure UnhookSignal(RtlSigNum: Integer; OnlyIfHooked: Boolean);
var
I: Integer;
begin
if RtlSigNum = RTL_SIGDEFAULT then
begin
for I := 0 to RTL_SIGLAST do
UnhookSignal(I, OnlyIfHooked);
Exit;
end;
if not Signals[RtlSigNum].Abandon then
begin
if OnlyIfHooked and (InquireSignal(RtlSigNum) <> ssHooked) then
Exit;
if sigaction(Signals[RtlSigNum].SigNum, @Signals[RtlSigNum].OldAction, Nil) = -1 then
raise Exception.CreateRes(@SSigactionFailed);
Signals[RtlSigNum].Hooked := False;
end;
end;
procedure UnhookOSExceptions;
begin
if not Assigned(HookOSExceptionsProc) then
UnhookSignal(RTL_SIGDEFAULT, True);
end;
procedure HookOSExceptions;
begin
if Assigned(HookOSExceptionsProc) then
HookOSExceptionsProc
else
begin
HookSignal(RTL_SIGDEFAULT);
end;
end;
{$ENDIF} // LINUX
procedure InitExceptions;
begin
OutOfMemory := EOutOfMemory.CreateRes(SOutOfMemory);
InvalidPointer := EInvalidPointer.CreateRes(SInvalidPointer);
ErrorProc := ErrorHandler;
ExceptProc := @ExceptHandler;
ExceptionClass := Exception;
{$IFDEF MSWINDOWS}
ExceptClsProc := @GetExceptionClass;
ExceptObjProc := @GetExceptionObject;
{$ENDIF}
AssertErrorProc := @AssertErrorHandler;
{$IFNDEF PC_MAPPED_EXCEPTIONS}
// We don't hook this under PC mapped exceptions, because
// we have no idea what the parameters were to the procedure
// in question. Hence we cannot hope to unwind the stack in
// our handler. Since we just throw an exception from our
// handler, that pretty much rules out using this without
// exorbitant compiler support. If you do hook AbstractErrorProc,
// you must make sure that you never throw an exception from
// your handler if PC_MAPPED_EXCEPTIONS is defined.
AbstractErrorProc := @AbstractErrorHandler;
{$ENDIF}
{$IFDEF LINUX}
if not IsLibrary then
HookOSExceptions;
{$ENDIF}
end;
procedure DoneExceptions;
begin
if Assigned(OutOfMemory) then
begin
OutOfMemory.AllowFree := True;
OutOfMemory.FreeInstance;
OutOfMemory := nil;
end;
if Assigned(InvalidPointer) then
begin
InvalidPointer.AllowFree := True;
InvalidPointer.Free;
InvalidPointer := nil;
end;
ErrorProc := nil;
ExceptProc := nil;
ExceptionClass := nil;
{$IFDEF MSWINDOWS}
ExceptClsProc := nil;
ExceptObjProc := nil;
{$ENDIF}
AssertErrorProc := nil;
{$IFDEF LINUX}
if not IsLibrary then
UnhookOSExceptions;
{$ENDIF}
end;
{$IFDEF MSWINDOWS}
procedure InitPlatformId;
var
OSVersionInfo: TOSVersionInfo;
begin
OSVersionInfo.dwOSVersionInfoSize := SizeOf(OSVersionInfo);
if GetVersionEx(OSVersionInfo) then
with OSVersionInfo do
begin
Win32Platform := dwPlatformId;
Win32MajorVersion := dwMajorVersion;
Win32MinorVersion := dwMinorVersion;
if Win32Platform = VER_PLATFORM_WIN32_WINDOWS then
Win32BuildNumber := dwBuildNumber and $FFFF
else
Win32BuildNumber := dwBuildNumber;
Win32CSDVersion := szCSDVersion;
end;
end;
function CheckWin32Version(AMajor: Integer; AMinor: Integer = 0): Boolean;
begin
Result := (Win32MajorVersion > AMajor) or
((Win32MajorVersion = AMajor) and
(Win32MinorVersion >= AMinor));
end;
function GetFileVersion(const AFileName: string): Cardinal;
var
FileName: string;
InfoSize, Wnd: DWORD;
VerBuf: Pointer;
FI: PVSFixedFileInfo;
VerSize: DWORD;
begin
Result := Cardinal(-1);
// GetFileVersionInfo modifies the filename parameter data while parsing.
// Copy the string const into a local variable to create a writeable copy.
FileName := AFileName;
UniqueString(FileName);
InfoSize := GetFileVersionInfoSize(PChar(FileName), Wnd);
if InfoSize <> 0 then
begin
GetMem(VerBuf, InfoSize);
try
if GetFileVersionInfo(PChar(FileName), Wnd, InfoSize, VerBuf) then
if VerQueryValue(VerBuf, '\', Pointer(FI), VerSize) then
Result:= FI.dwFileVersionMS;
finally
FreeMem(VerBuf);
end;
end;
end;
procedure Beep;
begin
MessageBeep(0);
end;
{$ENDIF}
{$IFDEF LINUX}
procedure Beep;
var
ch: Char;
FileDes: Integer;
begin
if isatty(STDERR_FILENO) = 1 then
FileDes := STDERR_FILENO
else
if isatty(STDOUT_FILENO) = 1 then
FileDes := STDOUT_FILENO
else
begin
// Neither STDERR_FILENO nor STDOUT_FILENO are open
// terminals (TTYs). It is not possible to safely
// write the beep character.
Exit;
end;
ch := #7;
__write(FileDes, ch, 1);
end;
{$ENDIF}
{ MBCS functions }
function ByteTypeTest(P: PChar; Index: Integer): TMbcsByteType;
{$IFDEF MSWINDOWS}
var
I: Integer;
begin
Result := mbSingleByte;
if (P = nil) or (P[Index] = #$0) then Exit;
if (Index = 0) then
begin
if P[0] in LeadBytes then Result := mbLeadByte;
end
else
begin
I := Index - 1;
while (I >= 0) and (P[I] in LeadBytes) do Dec(I);
if ((Index - I) mod 2) = 0 then Result := mbTrailByte
else if P[Index] in LeadBytes then Result := mbLeadByte;
end;
end;
{$ENDIF}
{$IFDEF LINUX}
var
I, L: Integer;
begin
Result := mbSingleByte;
if (P = nil) or (P[Index] = #$0) then Exit;
I := 0;
repeat
if P[I] in LeadBytes then
L := StrCharLength(P + I)
else
L := 1;
Inc(I, L);
until (I > Index);
if (L <> 1) then
if (I - L = Index) then
Result := mbLeadByte
else
Result := mbTrailByte;
end;
{$ENDIF}
function ByteType(const S: string; Index: Integer): TMbcsByteType;
begin
Result := mbSingleByte;
if SysLocale.FarEast then
Result := ByteTypeTest(PChar(S), Index-1);
end;
function StrByteType(Str: PChar; Index: Cardinal): TMbcsByteType;
begin
Result := mbSingleByte;
if SysLocale.FarEast then
Result := ByteTypeTest(Str, Index);
end;
function ByteToCharLen(const S: string; MaxLen: Integer): Integer;
begin
if Length(S) < MaxLen then MaxLen := Length(S);
Result := ByteToCharIndex(S, MaxLen);
end;
function ByteToCharIndex(const S: string; Index: Integer): Integer;
var
I: Integer;
begin
Result := 0;
if (Index <= 0) or (Index > Length(S)) then Exit;
Result := Index;
if not SysLocale.FarEast then Exit;
I := 1;
Result := 0;
while I <= Index do
begin
if S[I] in LeadBytes then
I := NextCharIndex(S, I)
else
Inc(I);
Inc(Result);
end;
end;
procedure CountChars(const S: string; MaxChars: Integer; var CharCount, ByteCount: Integer);
var
C, L, B: Integer;
begin
L := Length(S);
C := 1;
B := 1;
while (B < L) and (C < MaxChars) do
begin
Inc(C);
if S[B] in LeadBytes then
B := NextCharIndex(S, B)
else
Inc(B);
end;
if (C = MaxChars) and (B < L) and (S[B] in LeadBytes) then
B := NextCharIndex(S, B) - 1;
CharCount := C;
ByteCount := B;
end;
function CharToByteIndex(const S: string; Index: Integer): Integer;
var
Chars: Integer;
begin
Result := 0;
if (Index <= 0) or (Index > Length(S)) then Exit;
if (Index > 1) and SysLocale.FarEast then
begin
CountChars(S, Index-1, Chars, Result);
if (Chars < (Index-1)) or (Result >= Length(S)) then
Result := 0 // Char index out of range
else
Inc(Result);
end
else
Result := Index;
end;
function CharToByteLen(const S: string; MaxLen: Integer): Integer;
var
Chars: Integer;
begin
Result := 0;
if MaxLen <= 0 then Exit;
if MaxLen > Length(S) then MaxLen := Length(S);
if SysLocale.FarEast then
begin
CountChars(S, MaxLen, Chars, Result);
if Result > Length(S) then
Result := Length(S);
end
else
Result := MaxLen;
end;
{ MBCS Helper functions }
function StrCharLength(const Str: PChar): Integer;
begin
{$IFDEF LINUX}
Result := mblen(Str, MB_CUR_MAX);
if (Result = -1) then Result := 1;
{$ENDIF}
{$IFDEF MSWINDOWS}
if SysLocale.FarEast then
Result := Integer(CharNext(Str)) - Integer(Str)
else
Result := 1;
{$ENDIF}
end;
function StrNextChar(const Str: PChar): PChar;
begin
{$IFDEF LINUX}
Result := Str + StrCharLength(Str);
{$ENDIF}
{$IFDEF MSWINDOWS}
Result := CharNext(Str);
{$ENDIF}
end;
function CharLength(const S: string; Index: Integer): Integer;
begin
Result := 1;
assert((Index > 0) and (Index <= Length(S)));
if SysLocale.FarEast and (S[Index] in LeadBytes) then
Result := StrCharLength(PChar(S) + Index - 1);
end;
function NextCharIndex(const S: string; Index: Integer): Integer;
begin
Result := Index + 1;
assert((Index > 0) and (Index <= Length(S)));
if SysLocale.FarEast and (S[Index] in LeadBytes) then
Result := Index + StrCharLength(PChar(S) + Index - 1);
end;
function IsPathDelimiter(const S: string; Index: Integer): Boolean;
begin
Result := (Index > 0) and (Index <= Length(S)) and (S[Index] = PathDelim)
and (ByteType(S, Index) = mbSingleByte);
end;
function IsDelimiter(const Delimiters, S: string; Index: Integer): Boolean;
begin
Result := False;
if (Index <= 0) or (Index > Length(S)) or (ByteType(S, Index) <> mbSingleByte) then exit;
Result := StrScan(PChar(Delimiters), S[Index]) <> nil;
end;
function IncludeTrailingBackslash(const S: string): string;
begin
Result := IncludeTrailingPathDelimiter(S);
end;
function IncludeTrailingPathDelimiter(const S: string): string;
begin
Result := S;
if not IsPathDelimiter(Result, Length(Result)) then
Result := Result + PathDelim;
end;
function ExcludeTrailingBackslash(const S: string): string;
begin
Result := ExcludeTrailingPathDelimiter(S);
end;
function ExcludeTrailingPathDelimiter(const S: string): string;
begin
Result := S;
if IsPathDelimiter(Result, Length(Result)) then
SetLength(Result, Length(Result)-1);
end;
function AnsiPos(const Substr, S: string): Integer;
var
P: PChar;
begin
Result := 0;
P := AnsiStrPos(PChar(S), PChar(SubStr));
if P <> nil then
Result := Integer(P) - Integer(PChar(S)) + 1;
end;
function AnsiCompareFileName(const S1, S2: string): Integer;
begin
{$IFDEF MSWINDOWS}
Result := AnsiCompareStr(AnsiLowerCaseFileName(S1), AnsiLowerCaseFileName(S2));
{$ENDIF}
{$IFDEF LINUX}
Result := AnsiCompareStr(S1, S2);
{$ENDIF}
end;
function SameFileName(const S1, S2: string): Boolean;
begin
Result := AnsiCompareFileName(S1, S2) = 0;
end;
function AnsiLowerCaseFileName(const S: string): string;
{$IFDEF MSWINDOWS}
var
I,L: Integer;
begin
if SysLocale.FarEast then
begin
L := Length(S);
SetLength(Result, L);
I := 1;
while I <= L do
begin
Result[I] := S[I];
if S[I] in LeadBytes then
begin
Inc(I);
Result[I] := S[I];
end
else
if Result[I] in ['A'..'Z'] then Inc(Byte(Result[I]), 32);
Inc(I);
end;
end
else
Result := AnsiLowerCase(S);
end;
{$ENDIF}
{$IFDEF LINUX}
begin
Result := AnsiLowerCase(S);
end;
{$ENDIF}
function AnsiUpperCaseFileName(const S: string): string;
{$IFDEF MSWINDOWS}
var
I,L: Integer;
begin
if SysLocale.FarEast then
begin
L := Length(S);
SetLength(Result, L);
I := 1;
while I <= L do
begin
Result[I] := S[I];
if S[I] in LeadBytes then
begin
Inc(I);
Result[I] := S[I];
end
else
if Result[I] in ['a'..'z'] then Dec(Byte(Result[I]), 32);
Inc(I);
end;
end
else
Result := AnsiUpperCase(S);
end;
{$ENDIF}
{$IFDEF LINUX}
begin
Result := AnsiUpperCase(S);
end;
{$ENDIF}
function AnsiStrPos(Str, SubStr: PChar): PChar;
var
L1, L2: Cardinal;
ByteType : TMbcsByteType;
begin
Result := nil;
if (Str = nil) or (Str^ = #0) or (SubStr = nil) or (SubStr^ = #0) then Exit;
L1 := StrLen(Str);
L2 := StrLen(SubStr);
Result := StrPos(Str, SubStr);
while (Result <> nil) and ((L1 - Cardinal(Result - Str)) >= L2) do
begin
ByteType := StrByteType(Str, Integer(Result-Str));
{$IFDEF MSWINDOWS}
if (ByteType <> mbTrailByte) and
(CompareString(LOCALE_USER_DEFAULT, 0, Result, L2, SubStr, L2) = CSTR_EQUAL) then Exit;
if (ByteType = mbLeadByte) then Inc(Result);
{$ENDIF}
{$IFDEF LINUX}
if (ByteType <> mbTrailByte) and
(strncmp(Result, SubStr, L2) = 0) then Exit;
{$ENDIF}
Inc(Result);
Result := StrPos(Result, SubStr);
end;
Result := nil;
end;
function AnsiStrRScan(Str: PChar; Chr: Char): PChar;
begin
Str := AnsiStrScan(Str, Chr);
Result := Str;
if Chr <> #$0 then
begin
while Str <> nil do
begin
Result := Str;
Inc(Str);
Str := AnsiStrScan(Str, Chr);
end;
end
end;
function AnsiStrScan(Str: PChar; Chr: Char): PChar;
begin
Result := StrScan(Str, Chr);
while Result <> nil do
begin
{$IFDEF MSWINDOWS}
case StrByteType(Str, Integer(Result-Str)) of
mbSingleByte: Exit;
mbLeadByte: Inc(Result);
end;
{$ENDIF}
{$IFDEF LINUX}
if StrByteType(Str, Integer(Result-Str)) = mbSingleByte then Exit;
{$ENDIF}
Inc(Result);
Result := StrScan(Result, Chr);
end;
end;
{$IFDEF MSWINDOWS}
function LCIDToCodePage(ALcid: LCID): Integer;
var
Buffer: array [0..6] of Char;
begin
GetLocaleInfo(ALcid, LOCALE_IDEFAULTANSICODEPAGE, Buffer, SizeOf(Buffer));
Result:= StrToIntDef(Buffer, GetACP);
end;
{$ENDIF}
procedure InitSysLocale;
{$IFDEF MSWINDOWS}
var
DefaultLCID: LCID;
DefaultLangID: LANGID;
AnsiCPInfo: TCPInfo;
// I: Integer;
// BufferA: array [128..255] of Char;
// BufferW: array [128..256] of Word;
// PCharA: PChar;
procedure InitLeadBytes;
var
I: Integer;
J: Byte;
begin
GetCPInfo(CP_ACP, AnsiCPInfo);
with AnsiCPInfo do
begin
I := 0;
while (I < MAX_LEADBYTES) and ((LeadByte[I] or LeadByte[I + 1]) <> 0) do
begin
for J := LeadByte[I] to LeadByte[I + 1] do
Include(LeadBytes, Char(J));
Inc(I, 2);
end;
end;
end;
begin
{ Set default to English (US). }
SysLocale.DefaultLCID := $0409;
SysLocale.PriLangID := LANG_ENGLISH;
SysLocale.SubLangID := SUBLANG_ENGLISH_US;
DefaultLCID := GetThreadLocale;
if DefaultLCID <> 0 then SysLocale.DefaultLCID := DefaultLCID;
DefaultLangID := Word(DefaultLCID);
if DefaultLangID <> 0 then
begin
SysLocale.PriLangID := DefaultLangID and $3ff;
SysLocale.SubLangID := DefaultLangID shr 10;
end;
LeadBytes := [];
if (Win32MajorVersion > 4) and (Win32Platform = VER_PLATFORM_WIN32_NT) then
SysLocale.MiddleEast := True
else
SysLocale.MiddleEast := GetSystemMetrics(SM_MIDEASTENABLED) <> 0;
SysLocale.FarEast := GetSystemMetrics(SM_DBCSENABLED) <> 0;
if SysLocale.FarEast then
InitLeadBytes;
end;
{$ENDIF}
{$IFDEF LINUX}
var
I: Integer;
buf: array [0..3] of char;
begin
FillChar(SysLocale, sizeof(SysLocale), 0);
SysLocale.FarEast := MB_CUR_MAX <> 1;
if not SysLocale.FarEast then Exit;
buf[1] := #0;
for I := 1 to 255 do
begin
buf[0] := Chr(I);
if mblen(buf, 1) <> 1 then Include(LeadBytes, Char(I));
end;
end;
{$ENDIF}
procedure GetFormatSettings;
{$IFDEF MSWINDOWS}
var
HourFormat, TimePrefix, TimePostfix: string;
DefaultLCID: Integer;
begin
InitSysLocale;
GetMonthDayNames;
if SysLocale.FarEast then GetEraNamesAndYearOffsets;
DefaultLCID := GetThreadLocale;
CurrencyString := GetLocaleStr(DefaultLCID, LOCALE_SCURRENCY, '');
CurrencyFormat := StrToIntDef(GetLocaleStr(DefaultLCID, LOCALE_ICURRENCY, '0'), 0);
NegCurrFormat := StrToIntDef(GetLocaleStr(DefaultLCID, LOCALE_INEGCURR, '0'), 0);
ThousandSeparator := GetLocaleChar(DefaultLCID, LOCALE_STHOUSAND, ',');
DecimalSeparator := GetLocaleChar(DefaultLCID, LOCALE_SDECIMAL, '.');
CurrencyDecimals := StrToIntDef(GetLocaleStr(DefaultLCID, LOCALE_ICURRDIGITS, '0'), 0);
DateSeparator := GetLocaleChar(DefaultLCID, LOCALE_SDATE, '/');
ShortDateFormat := TranslateDateFormat(GetLocaleStr(DefaultLCID, LOCALE_SSHORTDATE, 'm/d/yy'));
LongDateFormat := TranslateDateFormat(GetLocaleStr(DefaultLCID, LOCALE_SLONGDATE, 'mmmm d, yyyy'));
TimeSeparator := GetLocaleChar(DefaultLCID, LOCALE_STIME, ':');
TimeAMString := GetLocaleStr(DefaultLCID, LOCALE_S1159, 'am');
TimePMString := GetLocaleStr(DefaultLCID, LOCALE_S2359, 'pm');
TimePrefix := '';
TimePostfix := '';
if StrToIntDef(GetLocaleStr(DefaultLCID, LOCALE_ITLZERO, '0'), 0) = 0 then
HourFormat := 'h' else
HourFormat := 'hh';
if StrToIntDef(GetLocaleStr(DefaultLCID, LOCALE_ITIME, '0'), 0) = 0 then
if StrToIntDef(GetLocaleStr(DefaultLCID, LOCALE_ITIMEMARKPOSN, '0'), 0) = 0 then
TimePostfix := ' AMPM'
else
TimePrefix := 'AMPM ';
ShortTimeFormat := TimePrefix + HourFormat + ':mm' + TimePostfix;
LongTimeFormat := TimePrefix + HourFormat + ':mm:ss' + TimePostfix;
ListSeparator := GetLocaleChar(DefaultLCID, LOCALE_SLIST, ',');
end;
{$ELSE}
{$IFDEF LINUX}
const
//first boolean is p_cs_precedes, second is p_sep_by_space
CurrencyFormats: array[boolean, boolean] of byte = ((1, 3),(0, 2));
//first boolean is n_cs_precedes, second is n_sep_by_space and finally n_sign_posn
NegCurrFormats: array[boolean, boolean, 0..4] of byte =
(((4,5,7,6,7),(15,8,10,13,10)),((0,1,3,1,2),(14,9,11,9,12)));
function TranslateFormat(s: PChar; const Default: string): string;
begin
Result := '';
while s^ <> #0 do
begin
if s^ = '%' then
begin
inc(s);
case s^ of
'a': Result := Result + 'ddd';
'A': Result := Result + 'dddd';
'b': Result := Result + 'MMM';
'B': Result := Result + 'MMMM';
'c': Result := Result + 'c';
// 'C': year / 100 not supported
'd': Result := Result + 'dd';
'D': Result := Result + 'MM/dd/yy';
'e': Result := Result + 'd';
// 'E': alternate format not supported
'g': Result := Result + 'yy';
'G': Result := Result + 'yyyy';
'h': Result := Result + 'MMM';
'H': Result := Result + 'HH';
'I': Result := Result + 'hh';
// 'j': day of year not supported
'k': Result := Result + 'H';
'l': Result := Result + 'h';
'm': Result := Result + 'MM';
'M': Result := Result + 'nn'; // minutes! not months!
'n': Result := Result + sLineBreak; // line break
// 'O': alternate format not supported
'P', // P's implied lowercasing of locale string is not supported
'p': Result := Result + 'AMPM';
'r': Result := Result + TranslateFormat(nl_langInfo(T_FMT_AMPM),'');
'R': Result := Result + 'HH:mm';
// 's': number of seconds since Epoch not supported
'S': Result := Result + 'ss';
't': Result := Result + #9; // tab char
'T': Result := Result + 'HH:mm:ss';
// 'u': day of week 1..7 not supported
// 'U': week number of the year not supported
// 'V': week number of the year not supported
// 'w': day of week 0..6 not supported
// 'W': week number of the year not supported
'x': Result := Result + TranslateFormat(nl_langInfo(D_FMT),'');
'X': Result := Result + TranslateFormat(nl_langinfo(T_FMT),'');
'y': Result := Result + 'yy';
'Y': Result := Result + 'yyyy';
// 'z': GMT offset is not supported
'%': Result := Result + '%';
end;
end
else
Result := Result + s^;
Inc(s);
end;
if Result = '' then
Result := Default;
end;
function GetFirstCharacter(const SrcString, match: string): char;
var
i, p: integer;
begin
result := match[1];
for i := 1 to length(SrcString) do begin
p := Pos(SrcString[i], match);
if p > 0 then
begin
result := match[p];
break;
end;
end;
end;
var
P: PLConv;
begin
InitSysLocale;
GetMonthDayNames;
if SysLocale.FarEast then InitEras;
CurrencyString := '';
CurrencyFormat := 0;
NegCurrFormat := 0;
ThousandSeparator := ',';
DecimalSeparator := '.';
CurrencyDecimals := 0;
P := localeconv;
if P <> nil then
begin
if P^.currency_symbol <> nil then
CurrencyString := P^.currency_symbol;
if (Byte(P^.p_cs_precedes) in [0..1]) and
(Byte(P^.p_sep_by_space) in [0..1]) then
begin
CurrencyFormat := CurrencyFormats[P^.p_cs_precedes, P^.p_sep_by_space];
if P^.p_sign_posn in [0..4] then
NegCurrFormat := NegCurrFormats[P^.n_cs_precedes, P^.n_sep_by_space,
P^.n_sign_posn];
end;
// #0 is valid for ThousandSeparator. Indicates no thousand separator.
ThousandSeparator := P^.thousands_sep^;
// #0 is not valid for DecimalSeparator.
if P^.decimal_point <> #0 then
DecimalSeparator := P^.decimal_point^;
CurrencyDecimals := P^.frac_digits;
end;
ShortDateFormat := TranslateFormat(nl_langinfo(D_FMT),'m/d/yy');
LongDateFormat := TranslateFormat(nl_langinfo(D_T_FMT), ShortDateFormat);
ShortTimeFormat := TranslateFormat(nl_langinfo(T_FMT), 'hh:mm AMPM');
LongTimeFormat := TranslateFormat(nl_langinfo(T_FMT_AMPM), ShortTimeFormat);
DateSeparator := GetFirstCharacter(ShortDateFormat, '/.-');
TimeSeparator := GetFirstCharacter(ShortTimeFormat, ':.');
TimeAMString := nl_langinfo(AM_STR);
TimePMString := nl_langinfo(PM_STR);
ListSeparator := ',';
end;
{$ELSE}
var
HourFormat, TimePrefix, TimePostfix: string;
begin
InitSysLocale;
GetMonthDayNames;
CurrencyString := '';
CurrencyFormat := 0;
NegCurrFormat := 0;
ThousandSeparator := ',';
DecimalSeparator := '.';
CurrencyDecimals := 0;
DateSeparator := '/';
ShortDateFormat := 'm/d/yy';
LongDateFormat := 'mmmm d, yyyy';
TimeSeparator := ':';
TimeAMString := 'am';
TimePMString := 'pm';
TimePrefix := '';
TimePostfix := '';
HourFormat := 'h';
TimePostfix := ' AMPM';
ShortTimeFormat := TimePrefix + HourFormat + ':mm' + TimePostfix;
LongTimeFormat := TimePrefix + HourFormat + ':mm:ss' + TimePostfix;
ListSeparator := ',';
end;
{$ENDIF}
{$ENDIF}
{$IFDEF MSWINDOWS}
procedure GetLocaleFormatSettings(LCID: Integer;
var FormatSettings: TFormatSettings);
var
HourFormat, TimePrefix, TimePostfix: string;
DefaultLCID: Integer;
begin
if IsValidLocale(LCID, LCID_INSTALLED) then
DefaultLCID := LCID
else
DefaultLCID := GetThreadLocale;
GetLocaleMonthDayNames(LCID, FormatSettings);
with FormatSettings do
begin
CurrencyString := GetLocaleStr(DefaultLCID, LOCALE_SCURRENCY, '');
CurrencyFormat := StrToIntDef(GetLocaleStr(DefaultLCID, LOCALE_ICURRENCY, '0'), 0);
NegCurrFormat := StrToIntDef(GetLocaleStr(DefaultLCID, LOCALE_INEGCURR, '0'), 0);
ThousandSeparator := GetLocaleChar(DefaultLCID, LOCALE_STHOUSAND, ',');
DecimalSeparator := GetLocaleChar(DefaultLCID, LOCALE_SDECIMAL, '.');
CurrencyDecimals := StrToIntDef(GetLocaleStr(DefaultLCID, LOCALE_ICURRDIGITS, '0'), 0);
DateSeparator := GetLocaleChar(DefaultLCID, LOCALE_SDATE, '/');
ShortDateFormat := TranslateDateFormat(GetLocaleStr(DefaultLCID, LOCALE_SSHORTDATE, 'm/d/yy'));
LongDateFormat := TranslateDateFormat(GetLocaleStr(DefaultLCID, LOCALE_SLONGDATE, 'mmmm d, yyyy'));
TimeSeparator := GetLocaleChar(DefaultLCID, LOCALE_STIME, ':');
TimeAMString := GetLocaleStr(DefaultLCID, LOCALE_S1159, 'am');
TimePMString := GetLocaleStr(DefaultLCID, LOCALE_S2359, 'pm');
TimePrefix := '';
TimePostfix := '';
if StrToIntDef(GetLocaleStr(DefaultLCID, LOCALE_ITLZERO, '0'), 0) = 0 then
HourFormat := 'h' else
HourFormat := 'hh';
if StrToIntDef(GetLocaleStr(DefaultLCID, LOCALE_ITIME, '0'), 0) = 0 then
if StrToIntDef(GetLocaleStr(DefaultLCID, LOCALE_ITIMEMARKPOSN, '0'), 0) = 0 then
TimePostfix := ' AMPM'
else
TimePrefix := 'AMPM ';
ShortTimeFormat := TimePrefix + HourFormat + ':mm' + TimePostfix;
LongTimeFormat := TimePrefix + HourFormat + ':mm:ss' + TimePostfix;
ListSeparator := GetLocaleChar(DefaultLCID, LOCALE_SLIST, ',');
end;
end;
{$ENDIF}
function StringReplace(const S, OldPattern, NewPattern: string;
Flags: TReplaceFlags): string;
var
SearchStr, Patt, NewStr: string;
Offset: Integer;
begin
if rfIgnoreCase in Flags then
begin
SearchStr := AnsiUpperCase(S);
Patt := AnsiUpperCase(OldPattern);
end else
begin
SearchStr := S;
Patt := OldPattern;
end;
NewStr := S;
Result := '';
while SearchStr <> '' do
begin
Offset := AnsiPos(Patt, SearchStr);
if Offset = 0 then
begin
Result := Result + NewStr;
Break;
end;
Result := Result + Copy(NewStr, 1, Offset - 1) + NewPattern;
NewStr := Copy(NewStr, Offset + Length(OldPattern), MaxInt);
if not (rfReplaceAll in Flags) then
begin
Result := Result + NewStr;
Break;
end;
SearchStr := Copy(SearchStr, Offset + Length(Patt), MaxInt);
end;
end;
function WrapText(const Line, BreakStr: string; const BreakChars: TSysCharSet;
MaxCol: Integer): string;
const
QuoteChars = ['''', '"'];
var
Col, Pos: Integer;
LinePos, LineLen: Integer;
BreakLen, BreakPos: Integer;
QuoteChar, CurChar: Char;
ExistingBreak: Boolean;
L: Integer;
begin
Col := 1;
Pos := 1;
LinePos := 1;
BreakPos := 0;
QuoteChar := #0;
ExistingBreak := False;
LineLen := Length(Line);
BreakLen := Length(BreakStr);
Result := '';
while Pos <= LineLen do
begin
CurChar := Line[Pos];
if CurChar in LeadBytes then
begin
L := CharLength(Line, Pos) - 1;
Inc(Pos, L);
Inc(Col, L);
end
else
begin
if CurChar in QuoteChars then
if QuoteChar = #0 then
QuoteChar := CurChar
else if CurChar = QuoteChar then
QuoteChar := #0;
if QuoteChar = #0 then
begin
if CurChar = BreakStr[1] then
begin
ExistingBreak := StrLComp(Pointer(BreakStr), Pointer(@Line[Pos]), BreakLen) = 0;
if ExistingBreak then
begin
Inc(Pos, BreakLen-1);
BreakPos := Pos;
end;
end;
if not ExistingBreak then
if CurChar in BreakChars then
BreakPos := Pos;
end;
end;
Inc(Pos);
Inc(Col);
if not (QuoteChar in QuoteChars) and (ExistingBreak or
((Col > MaxCol) and (BreakPos > LinePos))) then
begin
Col := 1;
Result := Result + Copy(Line, LinePos, BreakPos - LinePos + 1);
if not (CurChar in QuoteChars) then
begin
while Pos <= LineLen do
begin
if Line[Pos] in BreakChars then
begin
Inc(Pos);
ExistingBreak := False;
end
else
begin
if StrLComp(Pointer(@Line[Pos]), sLineBreak, Length(sLineBreak)) = 0 then
begin
Inc(Pos, Length(sLineBreak));
ExistingBreak := True;
end
else
Break;
end;
end;
end;
if (Pos <= LineLen) and not ExistingBreak then
Result := Result + BreakStr;
Inc(BreakPos);
LinePos := BreakPos;
Pos := LinePos;
ExistingBreak := False;
end;
end;
Result := Result + Copy(Line, LinePos, MaxInt);
end;
function WrapText(const Line: string; MaxCol: Integer): string;
begin
Result := WrapText(Line, sLineBreak, [' ', '-', #9], MaxCol); { do not localize }
end;
function FindCmdLineSwitch(const Switch: string; const Chars: TSysCharSet;
IgnoreCase: Boolean): Boolean;
var
I: Integer;
S: string;
begin
for I := 1 to ParamCount do
begin
S := ParamStr(I);
if (Chars = []) or (S[1] in Chars) then
if IgnoreCase then
begin
if (AnsiCompareText(Copy(S, 2, Maxint), Switch) = 0) then
begin
Result := True;
Exit;
end;
end
else begin
if (AnsiCompareStr(Copy(S, 2, Maxint), Switch) = 0) then
begin
Result := True;
Exit;
end;
end;
end;
Result := False;
end;
function FindCmdLineSwitch(const Switch: string): Boolean;
begin
Result := FindCmdLineSwitch(Switch, SwitchChars, True);
end;
function FindCmdLineSwitch(const Switch: string; IgnoreCase: Boolean): Boolean;
begin
Result := FindCmdLineSwitch(Switch, SwitchChars, IgnoreCase);
end;
{ Package info structures }
type
PPkgName = ^TPkgName;
TPkgName = packed record
HashCode: Byte;
Name: array[0..255] of Char;
end;
{ PackageUnitFlags:
bit meaning
-----------------------------------------------------------------------------------------
0 | main unit
1 | package unit (dpk source)
2 | $WEAKPACKAGEUNIT unit
3 | original containment of $WEAKPACKAGEUNIT (package into which it was compiled)
4 | implicitly imported
5..7 | reserved
}
PUnitName = ^TUnitName;
TUnitName = packed record
Flags : Byte;
HashCode: Byte;
Name: array[0..255] of Char;
end;
{ Package flags:
bit meaning
-----------------------------------------------------------------------------------------
0 | 1: never-build 0: always build
1 | 1: design-time only 0: not design-time only on => bit 2 = off
2 | 1: run-time only 0: not run-time only on => bit 1 = off
3 | 1: do not check for dup units 0: perform normal dup unit check
4..25 | reserved
26..27| (producer) 0: pre-V4, 1: undefined, 2: c++, 3: Pascal
28..29| reserved
30..31| 0: EXE, 1: Package DLL, 2: Library DLL, 3: undefined
}
PPackageInfoHeader = ^TPackageInfoHeader;
TPackageInfoHeader = packed record
Flags: Cardinal;
RequiresCount: Integer;
{Requires: array[0..9999] of TPkgName;
ContainsCount: Integer;
Contains: array[0..9999] of TUnitName;}
end;
function PackageInfoTable(Module: HMODULE): PPackageInfoHeader;
var
ResInfo: HRSRC;
Data: THandle;
begin
Result := nil;
ResInfo := FindResource(Module, 'PACKAGEINFO', RT_RCDATA);
if ResInfo <> 0 then
begin
Data := LoadResource(Module, ResInfo);
if Data <> 0 then
try
Result := LockResource(Data);
UnlockResource(Data);
finally
FreeResource(Data);
end;
end;
end;
function GetModuleName(Module: HMODULE): string;
var
ModName: array[0..MAX_PATH] of Char;
begin
SetString(Result, ModName, GetModuleFileName(Module, ModName, SizeOf(ModName)));
end;
var
Reserved: Integer;
procedure CheckForDuplicateUnits(Module: HMODULE);
var
ModuleFlags: Cardinal;
function IsUnitPresent(HC: Byte; UnitName: PChar; Module: HMODULE;
const ModuleName: string; var UnitPackage: string): Boolean;
var
I: Integer;
InfoTable: PPackageInfoHeader;
LibModule: PLibModule;
PkgName: PPkgName;
UName : PUnitName;
Count: Integer;
begin
Result := True;
if (StrIComp(UnitName, 'SysInit') <> 0) and
(StrIComp(UnitName, PChar(ModuleName)) <> 0) then
begin
LibModule := LibModuleList;
while LibModule <> nil do
begin
if LibModule.Instance <> Cardinal(Module) then
begin
InfoTable := PackageInfoTable(HMODULE(LibModule.Instance));
if (InfoTable <> nil) and (InfoTable.Flags and pfModuleTypeMask = pfPackageModule) and
((InfoTable.Flags and pfIgnoreDupUnits) = (ModuleFlags and pfIgnoreDupUnits)) then
begin
PkgName := PPkgName(Integer(InfoTable) + SizeOf(InfoTable^));
Count := InfoTable.RequiresCount;
{ Skip the Requires list }
for I := 0 to Count - 1 do Inc(Integer(PkgName), StrLen(PkgName.Name) + 2);
Count := Integer(Pointer(PkgName)^);
UName := PUnitName(Integer(PkgName) + 4);
for I := 0 to Count - 1 do
begin
with UName^ do
// Test Flags to ignore weak package units
if ((HashCode = HC) or (HashCode = 0) or (HC = 0)) and
((Flags and $06) = 0) and (StrIComp(UnitName, Name) = 0) then
begin
UnitPackage := ChangeFileExt(ExtractFileName(
GetModuleName(HMODULE(LibModule.Instance))), '');
Exit;
end;
Inc(Integer(UName), StrLen(UName.Name) + 3);
end;
end;
end;
LibModule := LibModule.Next;
end;
end;
Result := False;
end;
function FindLibModule(Module: HModule): PLibModule;
begin
Result := LibModuleList;
while Result <> nil do
begin
if Result.Instance = Cardinal(Module) then Exit;
Result := Result.Next;
end;
end;
procedure InternalUnitCheck(Module: HModule);
var
I: Integer;
InfoTable: PPackageInfoHeader;
UnitPackage: string;
ModuleName: string;
PkgName: PPkgName;
UName: PUnitName;
Count: Integer;
LibModule: PLibModule;
begin
InfoTable := PackageInfoTable(Module);
if (InfoTable <> nil) and (InfoTable.Flags and pfModuleTypeMask = pfPackageModule) then
begin
if ModuleFlags = 0 then ModuleFlags := InfoTable.Flags;
ModuleName := ChangeFileExt(ExtractFileName(GetModuleName(Module)), '');
PkgName := PPkgName(Integer(InfoTable) + SizeOf(InfoTable^));
Count := InfoTable.RequiresCount;
for I := 0 to Count - 1 do
begin
with PkgName^ do
{$IFDEF MSWINDOWS}
InternalUnitCheck(GetModuleHandle(PChar(ChangeFileExt(Name, '.bpl'))));
{$ENDIF}
{$IFDEF LINUX}
InternalUnitCheck(GetModuleHandle(Name));
{$ENDIF}
Inc(Integer(PkgName), StrLen(PkgName.Name) + 2);
end;
LibModule := FindLibModule(Module);
if (LibModule = nil) or ((LibModule <> nil) and (LibModule.Reserved <> Reserved)) then
begin
if LibModule <> nil then LibModule.Reserved := Reserved;
Count := Integer(Pointer(PkgName)^);
UName := PUnitName(Integer(PkgName) + 4);
for I := 0 to Count - 1 do
begin
with UName^ do
// Test Flags to ignore weak package units
if ((Flags and ufWeakPackageUnit) = 0 ) and
IsUnitPresent(HashCode, Name, Module, ModuleName, UnitPackage) then
raise EPackageError.CreateResFmt(SDuplicatePackageUnit,
[ModuleName, Name, UnitPackage]);
Inc(Integer(UName), StrLen(UName.Name) + 3);
end;
end;
end;
end;
begin
Inc(Reserved);
ModuleFlags := 0;
InternalUnitCheck(Module);
end;
{$IFDEF LINUX}
function LoadLibrary(ModuleName: PChar): HMODULE;
begin
Result := HMODULE(dlopen(ModuleName, RTLD_LAZY));
end;
function FreeLibrary(Module: HMODULE): LongBool;
begin
Result := LongBool(dlclose(Pointer(Module)));
end;
function GetProcAddress(Module: HMODULE; Proc: PChar): Pointer;
var
Info: TDLInfo;
Error: PChar;
ModHandle: HMODULE;
begin
// dlsym doesn't clear the error state when the function succeeds
dlerror;
Result := dlsym(Pointer(Module), Proc);
Error := dlerror;
if Error <> nil then
Result := nil
else if dladdr(Result, Info) <> 0 then
begin
{ In glibc 2.1.3 and earlier, dladdr returns a nil dli_fname
for addresses in the main program file. In glibc 2.1.91 and
later, dladdr fills in the dli_fname for addresses in the
main program file, but dlopen will segfault when given
the main program file name.
Workaround: Check the symbol base address against the main
program file's base address, and only call dlopen with a nil
filename to get the module name of the main program. }
if Info.dli_fbase = ExeBaseAddress then
Info.dli_fname := nil;
ModHandle := HMODULE(dlopen(Info.dli_fname, RTLD_LAZY));
if ModHandle <> 0 then
begin
dlclose(Pointer(ModHandle));
if ModHandle <> Module then
Result := nil;
end;
end else Result := nil;
end;
type
plink_map = ^link_map;
link_map = record
l_addr: Pointer;
l_name: PChar;
l_ld: Pointer;
l_next, l_prev: plink_map;
end;
pr_debug = ^r_debug;
r_debug = record
r_version: Integer;
r_map: plink_map;
r_brk: Pointer;
r_state: Integer;
r_ldbase: Pointer;
end;
var
_r_debug: pr_debug = nil;
function ScanLinkMap(Func: Pointer): plink_map;
var
linkmap: plink_map;
function Eval(linkmap: plink_map; Func: Pointer): Boolean;
asm
// MOV ECX,[EBP]
PUSH EBP
CALL EDX
POP ECX
end;
begin
if _r_debug = nil then
_r_debug := dlsym(RTLD_DEFAULT, '_r_debug');
if _r_debug = nil then
begin
Assert(False, 'Unable to locate ''_r_debug'' symbol'); // do not localize
Result := nil;
Exit;
end;
linkmap := _r_debug.r_map;
while linkmap <> nil do
begin
if not Eval(linkmap, Func) then Break;
linkmap := linkmap.l_next;
end;
Result := linkmap;
end;
function InitModule(linkmap: plink_map): HMODULE;
begin
if linkmap <> nil then
begin
Result := HMODULE(dlopen(linkmap.l_name, RTLD_LAZY));
if Result <> 0 then
dlclose(Pointer(Result));
end else Result := 0;
end;
function GetModuleHandle(ModuleName: PChar): HMODULE;
function CheckModuleName(linkmap: plink_map): Boolean;
var
BaseName: PChar;
begin
Result := True;
if ((ModuleName = nil) and ((linkmap.l_name = nil) or (linkmap.l_name[0] = #0))) or
((ModuleName[0] = PathDelim) and (StrComp(ModuleName, linkmap.l_name) = 0)) then
begin
Result := False;
Exit;
end else
begin
// Locate the start of the actual filename
BaseName := StrRScan(linkmap.l_name, PathDelim);
if BaseName = nil then
BaseName := linkmap.l_name
else Inc(BaseName); // The filename is actually located at BaseName+1
if StrComp(ModuleName, BaseName) = 0 then
begin
Result := False;
Exit;
end;
end;
end;
begin
Result := InitModule(ScanLinkMap(@CheckModuleName));
end;
function GetPackageModuleHandle(PackageName: PChar): HMODULE;
var
PkgName: array[0..MAX_PATH] of Char;
function CheckPackageName(linkmap: plink_map): Boolean;
var
BaseName: PChar;
begin
Result := True;
if linkmap.l_name <> nil then
begin
// Locate the start of the actual filename
BaseName := StrRScan(linkmap.l_name, PathDelim);
if BaseName = nil then
BaseName := linkmap.l_name // If there is no path info, just use the whole name
else Inc(BaseName); // The filename is actually located at BaseName+1
Result := StrPos(BaseName, PkgName) = nil;
end;
end;
procedure MakePkgName(Prefix, Name: PChar);
begin
StrCopy(PkgName, Prefix);
StrLCat(PkgName, Name, sizeof(PkgName)-1);
PkgName[High(PkgName)] := #0;
end;
begin
if (PackageName = nil) or (StrScan(PackageName, PathDelim) <> nil) then
Result := 0
else
begin
MakePkgName('bpl', PackageName); // First check the default prefix
Result := InitModule(ScanLinkMap(@CheckPackageName));
if Result = 0 then
begin
MakePkgName('dcl', PackageName); // Next check the design-time prefix
Result := InitModule(ScanLinkMap(@CheckPackageName));
if Result = 0 then
begin
MakePkgName('', PackageName); // finally check without a prefix
Result := InitModule(ScanLinkMap(@CheckPackageName));
end;
end;
end;
end;
{$ENDIF}
{$IFDEF MSWINDOWS}
procedure Sleep; external kernel32 name 'Sleep'; stdcall;
{$ENDIF}
{$IFDEF LINUX}
procedure Sleep(milliseconds: Cardinal);
begin
usleep(milliseconds * 1000); // usleep is in microseconds
end;
{$ENDIF}
{ InitializePackage }
procedure InitializePackage(Module: HMODULE);
type
TPackageLoad = procedure;
var
PackageLoad: TPackageLoad;
begin
CheckForDuplicateUnits(Module);
@PackageLoad := GetProcAddress(Module, 'Initialize'); //Do not localize
if Assigned(PackageLoad) then
PackageLoad
else
raise EPackageError.CreateFmt(sInvalidPackageFile, [GetModuleName(Module)]);
end;
{ FinalizePackage }
procedure FinalizePackage(Module: HMODULE);
type
TPackageUnload = procedure;
var
PackageUnload: TPackageUnload;
begin
@PackageUnload := GetProcAddress(Module, 'Finalize'); //Do not localize
if Assigned(PackageUnload) then
PackageUnload
else
raise EPackageError.CreateRes(sInvalidPackageHandle);
end;
{ LoadPackage }
function LoadPackage(const Name: string): HMODULE;
{$IFDEF LINUX}
var
DLErrorMsg: string;
{$ENDIF}
begin
{$IFDEF MSWINDOWS}
Result := SafeLoadLibrary(Name);
{$ENDIF}
{$IFDEF LINUX}
Result := HMODULE(dlOpen(PChar(Name), PkgLoadingMode));
{$ENDIF}
if Result = 0 then
begin
{$IFDEF LINUX}
DLErrorMsg := dlerror;
{$ENDIF}
raise EPackageError.CreateResFmt(sErrorLoadingPackage,
[Name,
{$IFDEF MSWINDOWS}SysErrorMessage(GetLastError){$ENDIF}
{$IFDEF LINUX}DLErrorMsg{$ENDIF}]);
end;
try
InitializePackage(Result);
except
{$IFDEF MSWINDOWS}
FreeLibrary(Result);
{$ENDIF}
{$IFDEF LINUX}
dlclose(Pointer(Result));
{$ENDIF}
raise;
end;
end;
{ UnloadPackage }
procedure UnloadPackage(Module: HMODULE);
begin
FinalizePackage(Module);
{$IFDEF MSWINDOWS}
FreeLibrary(Module);
{$ENDIF}
{$IFDEF LINUX}
dlclose(Pointer(Module));
InvalidateModuleCache;
{$ENDIF}
end;
{ GetPackageInfo }
procedure GetPackageInfo(Module: HMODULE; Param: Pointer; var Flags: Integer;
InfoProc: TPackageInfoProc);
var
InfoTable: PPackageInfoHeader;
I: Integer;
PkgName: PPkgName;
UName: PUnitName;
Count: Integer;
begin
InfoTable := PackageInfoTable(Module);
if not Assigned(InfoTable) then
raise EPackageError.CreateFmt(SCannotReadPackageInfo,
[ExtractFileName(GetModuleName(Module))]);
Flags := InfoTable.Flags;
with InfoTable^ do
begin
PkgName := PPkgName(Integer(InfoTable) + SizeOf(InfoTable^));
Count := RequiresCount;
for I := 0 to Count - 1 do
begin
InfoProc(PkgName.Name, ntRequiresPackage, 0, Param);
Inc(Integer(PkgName), StrLen(PkgName.Name) + 2);
end;
Count := Integer(Pointer(PkgName)^);
UName := PUnitName(Integer(PkgName) + 4);
for I := 0 to Count - 1 do
begin
InfoProc(UName.Name, ntContainsUnit, UName.Flags, Param);
Inc(Integer(UName), StrLen(UName.Name) + 3);
end;
if Flags and pfPackageModule <> 0 then
begin
PkgName := PPkgName(UName);
InfoProc(PkgName.Name, ntDcpBpiName, 0, Param);
end;
end;
end;
function GetPackageDescription(ModuleName: PChar): string;
var
ResModule: HMODULE;
ResInfo: HRSRC;
ResData: HGLOBAL;
{$IFDEF LINUX}
DLErrorMsg: string;
{$ENDIF}
begin
Result := '';
ResModule := LoadResourceModule(ModuleName);
if ResModule = 0 then
begin
{$IFDEF MSWINDOWS}
ResModule := LoadLibraryEx(ModuleName, 0, LOAD_LIBRARY_AS_DATAFILE);
{$ENDIF}
{$IFDEF LINUX}
ResModule := HMODULE(dlopen(ModuleName, RTLD_LAZY));
{$ENDIF}
if ResModule = 0 then
begin
{$IFDEF LINUX}
DLErrorMsg := dlerror;
{$ENDIF}
raise EPackageError.CreateResFmt(sErrorLoadingPackage,
[ModuleName,
{$IFDEF MSWINDOWS}SysErrorMessage(GetLastError){$ENDIF}
{$IFDEF LINUX}DLErrorMsg{$ENDIF}]);
end;
end;
try
ResInfo := FindResource(ResModule, 'DESCRIPTION', RT_RCDATA);
if ResInfo <> 0 then
begin
ResData := LoadResource(ResModule, ResInfo);
if ResData <> 0 then
try
Result := PWideChar(LockResource(ResData));
UnlockResource(ResData);
finally
FreeResource(ResData);
end;
end;
finally
{$IFDEF MSWINDOWS}
FreeLibrary(ResModule);
{$ENDIF}
{$IFDEF LINUX}
dlclose(Pointer(ResModule));
{$ENDIF}
end;
end;
procedure RaiseLastOSError;
begin
RaiseLastOSError(GetLastError);
end;
procedure RaiseLastOSError(LastError: Integer);
var
Error: EOSError;
begin
if LastError <> 0 then
Error := EOSError.CreateResFmt(SOSError, [LastError,
SysErrorMessage(LastError)])
else
Error := EOSError.CreateRes(SUnkOSError);
Error.ErrorCode := LastError;
raise Error;
end;
{$IFDEF MSWINDOWS}
{ RaiseLastWin32Error }
procedure RaiseLastWin32Error;
begin
RaiseLastOSError;
end;
{ Win32Check }
function Win32Check(RetVal: BOOL): BOOL;
begin
if not RetVal then RaiseLastOSError;
Result := RetVal;
end;
{$ENDIF}
type
PTerminateProcInfo = ^TTerminateProcInfo;
TTerminateProcInfo = record
Next: PTerminateProcInfo;
Proc: TTerminateProc;
end;
var
TerminateProcList: PTerminateProcInfo = nil;
procedure AddTerminateProc(TermProc: TTerminateProc);
var
P: PTerminateProcInfo;
begin
New(P);
P^.Next := TerminateProcList;
P^.Proc := TermProc;
TerminateProcList := P;
end;
function CallTerminateProcs: Boolean;
var
PI: PTerminateProcInfo;
begin
Result := True;
PI := TerminateProcList;
while Result and (PI <> nil) do
begin
Result := PI^.Proc;
PI := PI^.Next;
end;
end;
procedure FreeTerminateProcs;
var
PI: PTerminateProcInfo;
begin
while TerminateProcList <> nil do
begin
PI := TerminateProcList;
TerminateProcList := PI^.Next;
Dispose(PI);
end;
end;
{ --- }
function AL1(const P): LongWord;
asm
MOV EDX,DWORD PTR [P]
XOR EDX,DWORD PTR [P+4]
XOR EDX,DWORD PTR [P+8]
XOR EDX,DWORD PTR [P+12]
MOV EAX,EDX
end;
function AL2(const P): LongWord;
asm
MOV EDX,DWORD PTR [P]
ROR EDX,5
XOR EDX,DWORD PTR [P+4]
ROR EDX,5
XOR EDX,DWORD PTR [P+8]
ROR EDX,5
XOR EDX,DWORD PTR [P+12]
MOV EAX,EDX
end;
const
AL1s: array[0..3] of LongWord = ($FFFFFFF0, $FFFFEBF0, 0, $FFFFFFFF);
AL2s: array[0..3] of LongWord = ($42C3ECEF, $20F7AEB6, $D1C2F74E, $3F6574DE);
procedure ALV;
begin
raise Exception.CreateRes(SNL);
end;
function ALR: Pointer;
var
LibModule: PLibModule;
begin
if MainInstance <> 0 then
Result := Pointer(LoadResource(MainInstance, FindResource(MainInstance, 'DVCLAL',
RT_RCDATA)))
else
begin
Result := nil;
LibModule := LibModuleList;
while LibModule <> nil do
begin
with LibModule^ do
begin
Result := Pointer(LoadResource(Instance, FindResource(Instance, 'DVCLAL',
RT_RCDATA)));
if Result <> nil then Break;
end;
LibModule := LibModule.Next;
end;
end;
end;
function GDAL: LongWord;
type
TDVCLAL = array[0..3] of LongWord;
PDVCLAL = ^TDVCLAL;
var
P: Pointer;
A1, A2: LongWord;
PAL1s, PAL2s: PDVCLAL;
ALOK: Boolean;
begin
P := ALR;
if P <> nil then
begin
A1 := AL1(P^);
A2 := AL2(P^);
Result := A1;
PAL1s := @AL1s;
PAL2s := @AL2s;
ALOK := ((A1 = PAL1s[0]) and (A2 = PAL2s[0])) or
((A1 = PAL1s[1]) and (A2 = PAL2s[1])) or
((A1 = PAL1s[2]) and (A2 = PAL2s[2]));
FreeResource(Integer(P));
if not ALOK then ALV;
end else Result := AL1s[3];
end;
procedure RCS;
var
P: Pointer;
ALOK: Boolean;
begin
P := ALR;
if P <> nil then
begin
ALOK := (AL1(P^) = AL1s[2]) and (AL2(P^) = AL2s[2]);
FreeResource(Integer(P));
end else ALOK := False;
if not ALOK then ALV;
end;
procedure RPR;
var
AL: LongWord;
begin
AL := GDAL;
if (AL <> AL1s[1]) and (AL <> AL1s[2]) then ALV;
end;
{$IFDEF MSWINDOWS}
procedure InitDriveSpacePtr;
var
Kernel: THandle;
begin
Kernel := GetModuleHandle(Windows.Kernel32);
if Kernel <> 0 then
@GetDiskFreeSpaceEx := GetProcAddress(Kernel, 'GetDiskFreeSpaceExA');
if not Assigned(GetDiskFreeSpaceEx) then
GetDiskFreeSpaceEx := @BackfillGetDiskFreeSpaceEx;
end;
{$ENDIF}
// Win95 does not return the actual value of the result.
// These implementations are consistent on all platforms.
function InterlockedIncrement(var I: Integer): Integer;
asm
MOV EDX,1
XCHG EAX,EDX
LOCK XADD [EDX],EAX
INC EAX
end;
function InterlockedDecrement(var I: Integer): Integer;
asm
MOV EDX,-1
XCHG EAX,EDX
LOCK XADD [EDX],EAX
DEC EAX
end;
function InterlockedExchange(var A: Integer; B: Integer): Integer;
asm
XCHG [EAX],EDX
MOV EAX,EDX
end;
// The InterlockedExchangeAdd Win32 API is not available on Win95.
function InterlockedExchangeAdd(var A: Integer; B: Integer): Integer;
asm
XCHG EAX,EDX
LOCK XADD [EDX],EAX
end;
{ TSimpleRWSync }
constructor TSimpleRWSync.Create;
begin
inherited Create;
InitializeCriticalSection(FLock);
end;
destructor TSimpleRWSync.Destroy;
begin
inherited Destroy;
DeleteCriticalSection(FLock);
end;
function TSimpleRWSync.BeginWrite: Boolean;
begin
EnterCriticalSection(FLock);
Result := True;
end;
procedure TSimpleRWSync.EndWrite;
begin
LeaveCriticalSection(FLock);
end;
procedure TSimpleRWSync.BeginRead;
begin
EnterCriticalSection(FLock);
end;
procedure TSimpleRWSync.EndRead;
begin
LeaveCriticalSection(FLock);
end;
{ TThreadLocalCounter }
const
Alive = High(Integer);
destructor TThreadLocalCounter.Destroy;
var
P, Q: PThreadInfo;
I: Integer;
begin
for I := 0 to High(FHashTable) do
begin
P := FHashTable[I];
FHashTable[I] := nil;
while P <> nil do
begin
Q := P;
P := P^.Next;
FreeMem(Q);
end;
end;
inherited Destroy;
end;
function TThreadLocalCounter.HashIndex: Byte;
var
H: Word;
begin
H := Word(GetCurrentThreadID);
Result := (WordRec(H).Lo xor WordRec(H).Hi) and 15;
end;
procedure TThreadLocalCounter.Open(var Thread: PThreadInfo);
var
P: PThreadInfo;
CurThread: Cardinal;
H: Byte;
begin
H := HashIndex;
CurThread := GetCurrentThreadID;
P := FHashTable[H];
while (P <> nil) and (P.ThreadID <> CurThread) do
P := P.Next;
if P = nil then
begin
P := Recycle;
if P = nil then
begin
P := PThreadInfo(AllocMem(sizeof(TThreadInfo)));
P.ThreadID := CurThread;
P.Active := Alive;
// Another thread could start traversing the list between when we set the
// head to P and when we assign to P.Next. Initializing P.Next to point
// to itself will make others spin until we assign the tail to P.Next.
P.Next := P;
P.Next := PThreadInfo(InterlockedExchange(Integer(FHashTable[H]), Integer(P)));
end;
end;
Thread := P;
end;
procedure TThreadLocalCounter.Close(var Thread: PThreadInfo);
begin
Thread := nil;
end;
procedure TThreadLocalCounter.Delete(var Thread: PThreadInfo);
begin
Thread.ThreadID := 0;
Thread.Active := 0;
end;
function TThreadLocalCounter.Recycle: PThreadInfo;
var
Gen: Integer;
begin
Result := FHashTable[HashIndex];
while (Result <> nil) do
begin
Gen := InterlockedExchange(Result.Active, Alive);
if Gen <> Alive then
begin
Result.ThreadID := GetCurrentThreadID;
Exit;
end
else
Result := Result.Next;
end;
end;
{$IFDEF MSWINDOWS}
{ TMultiReadExclusiveWriteSynchronizer }
const
mrWriteRequest = $FFFF; // 65535 concurrent read requests (threads)
// 32768 concurrent write requests (threads)
// only one write lock at a time
// 2^32 lock recursions per thread (read and write combined)
constructor TMultiReadExclusiveWriteSynchronizer.Create;
begin
inherited Create;
FSentinel := mrWriteRequest;
FReadSignal := CreateEvent(nil, True, True, nil); // manual reset, start signaled
FWriteSignal := CreateEvent(nil, False, False, nil); // auto reset, start blocked
FWaitRecycle := INFINITE;
tls := TThreadLocalCounter.Create;
end;
destructor TMultiReadExclusiveWriteSynchronizer.Destroy;
begin
BeginWrite;
inherited Destroy;
CloseHandle(FReadSignal);
CloseHandle(FWriteSignal);
tls.Free;
end;
procedure TMultiReadExclusiveWriteSynchronizer.BlockReaders;
begin
ResetEvent(FReadSignal);
end;
procedure TMultiReadExclusiveWriteSynchronizer.UnblockReaders;
begin
SetEvent(FReadSignal);
end;
procedure TMultiReadExclusiveWriteSynchronizer.UnblockOneWriter;
begin
SetEvent(FWriteSignal);
end;
procedure TMultiReadExclusiveWriteSynchronizer.WaitForReadSignal;
begin
WaitForSingleObject(FReadSignal, FWaitRecycle);
end;
procedure TMultiReadExclusiveWriteSynchronizer.WaitForWriteSignal;
begin
WaitForSingleObject(FWriteSignal, FWaitRecycle);
end;
{$IFDEF DEBUG_MREWS}
var
x: Integer;
procedure TMultiReadExclusiveWriteSynchronizer.Debug(const Msg: string);
begin
OutputDebugString(PChar(Format('%d %s Thread=%x Sentinel=%d, FWriterID=%x',
[InterlockedIncrement(x), Msg, GetCurrentThreadID, FSentinel, FWriterID])));
end;
{$ENDIF}
function TMultiReadExclusiveWriteSynchronizer.BeginWrite: Boolean;
var
Thread: PThreadInfo;
HasReadLock: Boolean;
ThreadID: Cardinal;
Test: Integer;
OldRevisionLevel: Cardinal;
begin
{
States of FSentinel (roughly - during inc/dec's, the states may not be exactly what is said here):
mrWriteRequest: A reader or a writer can get the lock
1 - (mrWriteRequest-1): A reader (possibly more than one) has the lock
0: A writer (possibly) just got the lock, if returned from the main write While loop
< 0, but not a multiple of mrWriteRequest: Writer(s) want the lock, but reader(s) have it.
New readers should be blocked, but current readers should be able to call BeginRead
< 0, but a multiple of mrWriteRequest: Writer(s) waiting for a writer to finish
}
{$IFDEF DEBUG_MREWS}
Debug('Write enter------------------------------------');
{$ENDIF}
Result := True;
ThreadID := GetCurrentThreadID;
if FWriterID <> ThreadID then // somebody or nobody has a write lock
begin
// Prevent new readers from entering while we wait for the existing readers
// to exit.
BlockReaders;
OldRevisionLevel := FRevisionLevel;
tls.Open(Thread);
// We have another lock already. It must be a read lock, because if it
// were a write lock, FWriterID would be our threadid.
HasReadLock := Thread.RecursionCount > 0;
if HasReadLock then // acquiring a write lock requires releasing read locks
InterlockedIncrement(FSentinel);
{$IFDEF DEBUG_MREWS}
Debug('Write before loop');
{$ENDIF}
// InterlockedExchangeAdd returns prev value
while InterlockedExchangeAdd(FSentinel, -mrWriteRequest) <> mrWriteRequest do
begin
{$IFDEF DEBUG_MREWS}
Debug('Write loop');
Sleep(1000); // sleep to force / debug race condition
Debug('Write loop2a');
{$ENDIF}
// Undo what we did, since we didn't get the lock
Test := InterlockedExchangeAdd(FSentinel, mrWriteRequest);
// If the old value (in Test) was 0, then we may be able to
// get the lock (because it will now be mrWriteRequest). So,
// we continue the loop to find out. Otherwise, we go to sleep,
// waiting for a reader or writer to signal us.
if Test <> 0 then
begin
{$IFDEF DEBUG_MREWS}
Debug('Write starting to wait');
{$ENDIF}
WaitForWriteSignal;
end
{$IFDEF DEBUG_MREWS}
else
Debug('Write continue')
{$ENDIF}
end;
// At the EndWrite, first Writers are awoken, and then Readers are awoken.
// If a Writer got the lock, we don't want the readers to do busy
// waiting. This Block resets the event in case the situation happened.
BlockReaders;
// Put our read lock marker back before we lose track of it
if HasReadLock then
InterlockedDecrement(FSentinel);
FWriterID := ThreadID;
Result := Integer(OldRevisionLevel) = (InterlockedIncrement(Integer(FRevisionLevel)) - 1);
end;
Inc(FWriteRecursionCount);
{$IFDEF DEBUG_MREWS}
Debug('Write lock-----------------------------------');
{$ENDIF}
end;
procedure TMultiReadExclusiveWriteSynchronizer.EndWrite;
var
Thread: PThreadInfo;
begin
{$IFDEF DEBUG_MREWS}
Debug('Write end');
{$ENDIF}
assert(FWriterID = GetCurrentThreadID);
tls.Open(Thread);
Dec(FWriteRecursionCount);
if FWriteRecursionCount = 0 then
begin
FWriterID := 0;
InterlockedExchangeAdd(FSentinel, mrWriteRequest);
{$IFDEF DEBUG_MREWS}
Debug('Write about to UnblockOneWriter');
{$ENDIF}
UnblockOneWriter;
{$IFDEF DEBUG_MREWS}
Debug('Write about to UnblockReaders');
{$ENDIF}
UnblockReaders;
end;
if Thread.RecursionCount = 0 then
tls.Delete(Thread);
{$IFDEF DEBUG_MREWS}
Debug('Write unlock');
{$ENDIF}
end;
procedure TMultiReadExclusiveWriteSynchronizer.BeginRead;
var
Thread: PThreadInfo;
WasRecursive: Boolean;
SentValue: Integer;
begin
{$IFDEF DEBUG_MREWS}
Debug('Read enter');
{$ENDIF}
tls.Open(Thread);
Inc(Thread.RecursionCount);
WasRecursive := Thread.RecursionCount > 1;
if FWriterID <> GetCurrentThreadID then
begin
{$IFDEF DEBUG_MREWS}
Debug('Trying to get the ReadLock (we did not have a write lock)');
{$ENDIF}
// In order to prevent recursive Reads from causing deadlock,
// we need to always WaitForReadSignal if not recursive.
// This prevents unnecessarily decrementing the FSentinel, and
// then immediately incrementing it again.
if not WasRecursive then
begin
// Make sure we don't starve writers. A writer will
// always set the read signal when it is done, and it is initially on.
WaitForReadSignal;
while (InterlockedDecrement(FSentinel) <= 0) do
begin
{$IFDEF DEBUG_MREWS}
Debug('Read loop');
{$ENDIF}
// Because the InterlockedDecrement happened, it is possible that
// other threads "think" we have the read lock,
// even though we really don't. If we are the last reader to do this,
// then SentValue will become mrWriteRequest
SentValue := InterlockedIncrement(FSentinel);
// So, if we did inc it to mrWriteRequest at this point,
// we need to signal the writer.
if SentValue = mrWriteRequest then
UnblockOneWriter;
// This sleep below prevents starvation of writers
Sleep(0);
{$IFDEF DEBUG_MREWS}
Debug('Read loop2 - waiting to be signaled');
{$ENDIF}
WaitForReadSignal;
{$IFDEF DEBUG_MREWS}
Debug('Read signaled');
{$ENDIF}
end;
end;
end;
{$IFDEF DEBUG_MREWS}
Debug('Read lock');
{$ENDIF}
end;
procedure TMultiReadExclusiveWriteSynchronizer.EndRead;
var
Thread: PThreadInfo;
Test: Integer;
begin
{$IFDEF DEBUG_MREWS}
Debug('Read end');
{$ENDIF}
tls.Open(Thread);
Dec(Thread.RecursionCount);
if (Thread.RecursionCount = 0) then
begin
tls.Delete(Thread);
// original code below commented out
if (FWriterID <> GetCurrentThreadID) then
begin
Test := InterlockedIncrement(FSentinel);
// It is possible for Test to be mrWriteRequest
// or, it can be = 0, if the write loops:
// Test := InterlockedExchangeAdd(FSentinel, mrWriteRequest) + mrWriteRequest;
// Did not get executed before this has called (the sleep debug makes it happen faster)
{$IFDEF DEBUG_MREWS}
Debug(Format('Read UnblockOneWriter may be called. Test=%d', [Test]));
{$ENDIF}
if Test = mrWriteRequest then
UnblockOneWriter
else if Test <= 0 then // We may have some writers waiting
begin
if (Test mod mrWriteRequest) = 0 then
UnblockOneWriter; // No more readers left (only writers) so signal one of them
end;
end;
end;
{$IFDEF DEBUG_MREWS}
Debug('Read unlock');
{$ENDIF}
end;
{$ENDIF} //MSWINDOWS for TMultiReadExclusiveWriteSynchronizer
procedure FreeAndNil(var Obj);
var
Temp: TObject;
begin
Temp := TObject(Obj);
Pointer(Obj) := nil;
Temp.Free;
end;
{ Interface support routines }
function Supports(const Instance: IInterface; const IID: TGUID; out Intf): Boolean;
begin
Result := (Instance <> nil) and (Instance.QueryInterface(IID, Intf) = 0);
end;
function Supports(const Instance: TObject; const IID: TGUID; out Intf): Boolean;
var
LUnknown: IUnknown;
begin
Result := (Instance <> nil) and
((Instance.GetInterface(IUnknown, LUnknown) and Supports(LUnknown, IID, Intf)) or
Instance.GetInterface(IID, Intf));
end;
function Supports(const Instance: IInterface; const IID: TGUID): Boolean;
var
Temp: IInterface;
begin
Result := Supports(Instance, IID, Temp);
end;
function Supports(const Instance: TObject; const IID: TGUID): Boolean;
var
Temp: IInterface;
begin
Result := Supports(Instance, IID, Temp);
end;
function Supports(const AClass: TClass; const IID: TGUID): Boolean;
begin
Result := AClass.GetInterfaceEntry(IID) <> nil;
end;
{$IFDEF MSWINDOWS}
{ TLanguages }
var
FTempLanguages: TLanguages;
function EnumLocalesCallback(LocaleID: PChar): Integer; stdcall;
begin
Result := FTempLanguages.LocalesCallback(LocaleID);
end;
{ Query the OS for information for a specified locale. Unicode version. Works correctly on Asian WinNT. }
function GetLocaleDataW(ID: LCID; Flag: DWORD): string;
var
Buffer: array[0..1023] of WideChar;
begin
Buffer[0] := #0;
GetLocaleInfoW(ID, Flag, Buffer, SizeOf(Buffer) div 2);
Result := Buffer;
end;
{ Query the OS for information for a specified locale. ANSI Version. Works correctly on Asian Win95. }
function GetLocaleDataA(ID: LCID; Flag: DWORD): string;
var
Buffer: array[0..1023] of Char;
begin
Buffer[0] := #0;
SetString(Result, Buffer, GetLocaleInfoA(ID, Flag, Buffer, SizeOf(Buffer)) - 1);
end;
{ Called for each supported locale. }
function TLanguages.LocalesCallback(LocaleID: PChar): Integer; stdcall;
var
AID: LCID;
ShortLangName: string;
GetLocaleDataProc: function (ID: LCID; Flag: DWORD): string;
begin
if Win32Platform = VER_PLATFORM_WIN32_NT then
GetLocaleDataProc := @GetLocaleDataW
else
GetLocaleDataProc := @GetLocaleDataA;
AID := StrToInt('$' + Copy(LocaleID, 5, 4));
ShortLangName := GetLocaleDataProc(AID, LOCALE_SABBREVLANGNAME);
if ShortLangName <> '' then
begin
SetLength(FSysLangs, Length(FSysLangs) + 1);
with FSysLangs[High(FSysLangs)] do
begin
FName := GetLocaleDataProc(AID, LOCALE_SLANGUAGE);
FLCID := AID;
FExt := ShortLangName;
end;
end;
Result := 1;
end;
constructor TLanguages.Create;
begin
inherited Create;
FTempLanguages := Self;
EnumSystemLocales(@EnumLocalesCallback, LCID_SUPPORTED);
end;
function TLanguages.GetCount: Integer;
begin
Result := High(FSysLangs) + 1;
end;
function TLanguages.GetExt(Index: Integer): string;
begin
Result := FSysLangs[Index].FExt;
end;
function TLanguages.GetID(Index: Integer): string;
begin
Result := HexDisplayPrefix + IntToHex(FSysLangs[Index].FLCID, 8);
end;
function TLanguages.GetLCID(Index: Integer): LCID;
begin
Result := FSysLangs[Index].FLCID;
end;
function TLanguages.GetName(Index: Integer): string;
begin
Result := FSysLangs[Index].FName;
end;
function TLanguages.GetNameFromLocaleID(ID: LCID): string;
var
Index: Integer;
begin
Result := sUnknown;
Index := IndexOf(ID);
if Index <> - 1 then Result := Name[Index];
if Result = '' then Result := sUnknown;
end;
function TLanguages.GetNameFromLCID(const ID: string): string;
begin
Result := NameFromLocaleID[StrToIntDef(ID, 0)];
end;
function TLanguages.IndexOf(ID: LCID): Integer;
begin
for Result := Low(FSysLangs) to High(FSysLangs) do
if FSysLangs[Result].FLCID = ID then Exit;
Result := -1;
end;
var
FLanguages: TLanguages;
function Languages: TLanguages;
begin
if FLanguages = nil then
FLanguages := TLanguages.Create;
Result := FLanguages;
end;
function SafeLoadLibrary(const Filename: string; ErrorMode: UINT): HMODULE;
var
OldMode: UINT;
FPUControlWord: Word;
begin
OldMode := SetErrorMode(ErrorMode);
try
asm
FNSTCW FPUControlWord
end;
try
Result := LoadLibrary(PChar(Filename));
finally
asm
FNCLEX
FLDCW FPUControlWord
end;
end;
finally
SetErrorMode(OldMode);
end;
end;
{$ENDIF}
{$IFDEF LINUX}
function SafeLoadLibrary(const FileName: string; Dummy: LongWord): HMODULE;
var
FPUControlWord: Word;
begin
asm
FNSTCW FPUControlWord
end;
try
Result := LoadLibrary(PChar(Filename));
finally
asm
FNCLEX
FLDCW FPUControlWord
end;
end;
end;
{$ENDIF}
{$IFDEF MSWINDOWS}
function GetEnvironmentVariable(const Name: string): string;
const
BufSize = 1024;
var
Len: Integer;
Buffer: array[0..BufSize - 1] of Char;
begin
Result := '';
Len := Windows.GetEnvironmentVariable(PChar(Name), @Buffer, BufSize);
if Len < BufSize then
SetString(Result, PChar(@Buffer), Len)
else
begin
SetLength(Result, Len - 1);
Windows.GetEnvironmentVariable(PChar(Name), PChar(Result), Len);
end;
end;
{$ENDIF}
{$IFDEF LINUX}
function GetEnvironmentVariable(const Name: string): string;
begin
Result := getenv(PChar(Name));
end;
{$ENDIF}
{$IFDEF LINUX}
procedure CheckLocale;
var
P,Q: PChar;
begin
P := gnu_get_libc_version();
Q := getenv('LC_ALL');
if (Q = nil) or (Q[0] = #0) then
Q := getenv('LANG');
// 2.1.3 <= current version < 2.1.91
if (strverscmp('2.1.3', P) <= 0) and
(strverscmp(P, '2.1.91') < 0) and
((Q = nil) or (Q[0] = #0)) then
begin
// GNU libc 2.1.3 will segfault in towupper() if environment variables don't
// specify a locale. This can happen when Apache launches CGI subprocesses.
// Solution: set a locale if the environment variable is missing.
// Works in 2.1.2, fixed in glibc 2.1.91 and later
setlocale(LC_ALL, 'POSIX');
end
else
// Configure the process locale settings according to
// the system environment variables (LC_CTYPE, LC_COLLATE, etc)
setlocale(LC_ALL, '');
// Note:
// POSIX/C is the default locale on many Unix systems, but its 7-bit charset
// causes char to widechar conversions to fail on any high-ascii
// character. To support high-ascii charset conversions, set the
// LC_CTYPE environment variable to something else or call setlocale to set
// the LC_CTYPE information for this process. It doesn't matter what
// you set it to, as long as it's not POSIX.
if StrComp(nl_langinfo(_NL_CTYPE_CODESET_NAME), 'ANSI_X3.4-1968') = 0 then
setlocale(LC_CTYPE, 'en_US'); // selects codepage ISO-8859-1
end;
procedure PropagateSignals;
var
Exc: TObject;
begin
{
If there is a current exception pending, then we're shutting down because
it went unhandled. If that exception is the result of a signal, then we
need to propagate that back out to the world as a real signal death. See
the discussion at http://www2.cons.org/cracauer/sigint.html for more info.
}
Exc := ExceptObject;
if (Exc <> nil) and (Exc is EExternal) then
kill(getpid, EExternal(Exc).SignalNumber);
end;
{
Under Win32, SafeCallError is implemented in ComObj. Under Linux, we
don't have ComObj, so we've substituted a similar mechanism here.
}
procedure SafeCallError(ErrorCode: Integer; ErrorAddr: Pointer);
var
ExcMsg: String;
begin
ExcMsg := GetSafeCallExceptionMsg;
SetSafeCallExceptionMsg('');
if ExcMsg <> '' then
begin
raise ESafeCallException.Create(ExcMsg) at GetSafeCallExceptionAddr;
end
else
raise ESafeCallException.CreateRes(@SSafecallException);
end;
{$ENDIF}
initialization
if ModuleIsCpp then HexDisplayPrefix := '0x';
InitExceptions;
{$IFDEF LINUX}
SafeCallErrorProc := @SafeCallError;
ExitProcessProc := PropagateSignals;
CheckLocale;
{$ENDIF}
{$IFDEF MSWINDOWS}
InitPlatformId;
InitDriveSpacePtr;
{$ENDIF}
GetFormatSettings; { Win implementation uses platform id }
finalization
{$IFDEF MSWINDOWS}
FreeAndNil(FLanguages);
{$ENDIF}
{$IFDEF LINUX}
if libuuidHandle <> nil then
dlclose(libuuidHandle);
{$ENDIF}
FreeTerminateProcs;
DoneExceptions;
end.