我正在实现一个解释器,我的解释器支持的一个函数就像Delphi的Format
。事实上,我正在使用SysUtils.Format
实现我的功能。但是,我在构建函数的第二个参数array of TVarRec
。
假设我有以下代码。现在,我只假设解释代码需要访问哪些Delphi变量(iVar1
和iVar2
),但我仍然不知道如何将它们放入Format
的结构中需要(arFormatArgs
)。
type TFormatArgs = array of TVarRec;
procedure RunInterpretedFormatFunction;
var
iMyAge: integer;
iMyIQ: integer;
sCode: string;
sText: string;
begin
iMyAge := 5;
iMyIQ := -5;
sCode := 'Format(''My age is %d and my IQ is %d'', [iMyAge, iMyIQ])';
sText := FormatThis(sCode, iMyAge, iMyIQ);
end;
function FormatThis(sFormatCode: string; iVar1: integer; iVar2: integer): string;
var
sFormatString: string;
arFormatArgs: TFormatArgs;
begin
sFormatString := GetFormatString(sFormatCode); // I can implement this function
arFormatArgs := ConstructFormatArgs(iVar1, iVar2); // NEED HELP HERE!
result := SysUtils.Format(sFormatString, arFormatArgs);
end;
如何在Delphi(而不是Assembly)中实现我的ConstructFormatArgs
函数?
答案 0 :(得分:6)
const数组使您可以自由添加字符串,整数,浮点数等,并将这些格式化为字符串。您可以添加的项目数量没有限制。
Delphi处理这个问题的方式是const数组实际上是TVarRec的数组。
TVarRec是以下类型的记录:
TVarRec = record
case Byte of
vtInteger: (VInteger: Integer; VType: Byte);
vtBoolean: (VBoolean: Boolean);
vtChar: (VChar: Char);
vtExtended: (VExtended: PExtended);
vtString: (VString: PShortString);
vtPointer: (VPointer: Pointer);
vtPChar: (VPChar: PChar);
vtObject: (VObject: TObject);
vtClass: (VClass: TClass);
vtWideChar: (VWideChar: WideChar);
vtPWideChar: (VPWideChar: PWideChar);
vtAnsiString: (VAnsiString: Pointer);
vtCurrency: (VCurrency: PCurrency);
vtVariant: (VVariant: PVariant);
TVarRec
内的值类型由VType
值决定。
这使您可以灵活地添加您希望const数组的任何类型,就像在Format()函数中一样:
格式('%s是一个字符串,%d是一个整数',['string',10]);
在您自己的过程中使用const数组没什么大不了的。看一下这个例子:
procedure VarArraySample( AVarArray : array of const );
var
i : integer;
begin
for i := 0 to High(AVarArray) do
do_something;
end;
函数High()返回数组的最后一个索引。
您还可以转换TVarRec的内容。这个例子取自Delphi的在线帮助并进行了一些修改。该函数将TVarRec转换为字符串:
function VarRecToStr( AVarRec : TVarRec ) : string;
const
Bool : array[Boolean] of string = ('False', 'True');
begin
case AVarRec.VType of
vtInteger: Result := IntToStr(AVarRec.VInteger);
vtBoolean: Result := Bool[AVarRec.VBoolean];
vtChar: Result := AVarRec.VChar;
vtExtended: Result := FloatToStr(AVarRec.VExtended^);
vtString: Result := AVarRec.VString^;
vtPChar: Result := AVarRec.VPChar;
vtObject: Result := AVarRec.VObject.ClassName;
vtClass: Result := AVarRec.VClass.ClassName;
vtAnsiString: Result := string(AVarRec.VAnsiString);
vtCurrency: Result := CurrToStr(AVarRec.VCurrency^);
vtVariant: Result := string(AVarRec.VVariant^);
else
result := '';
end;
end;
您可以将上述两个函数组合到一个函数中,该函数将const数组中的所有元素转换为一个字符串:
function VarArrayToStr( AVarArray : array of const ) : string;
var
i : integer;
begin
result := '';
for i := 0 to High(AVarArray) do
result := result + VarRecToStr( AVarArray[i] );
end;
您现在可以创建自己的Format()函数。 Format()函数扫描%,并用const数组中的值替换%something,具体取决于格式说明符和精度说明符。
答案 1 :(得分:4)
如您所知,array of const
与array of TVarRec
相同。要构造一个,首先声明诸如数组,然后设置每个元素的值,就像任何其他数组一样。
TVarRec
是一个变体记录,这意味着它可以包含许多不同类型的值。它有一个字段VType
,用于指示它所包含的值的类型。在其他字段中,一次只有一个具有有效值。设置VType
字段,然后设置相应的值字段,例如VInteger
或VString
。
请注意,某些字段确实是指针,例如VVariant
和VInt64
。当您指定这些指针值时,您需要确保只要Format
需要它们,它们指向的任何内容仍然可访问且有效。
其他字段是其实际值类型的无类型版本。其中包括VAnsiString
和VInterface
。当您指定这些字段时,请注意它们不会保持普通AnsiString
或IUnknown
变量的常用引用计数,因此请再次观察这些变量的生命周期。
编译器通常是唯一生成此类数组的东西,因此几乎没有可用的参考代码来查看它们是如何构建的。相反,您可以查看使用const数组的其他代码。例如,我在几年前实施了a Unicode-aware Format
function for the JCL。它使用有限状态机一次解析一个字符的格式字符串。每次完成解析参数字符串时,它都会从输入数组中获取相应的参数,并根据字符串和参数类型对其进行格式化。
它使用了最少的汇编程序,只是为了一些较小的效率,而不是因为它确实是必要的。作为参考,所有汇编程序都附有注释中等效的Delphi代码。
答案 2 :(得分:2)
在https://groups.google.com/forum/#!topic/borland.public.delphi.objectpascal/-xb6O0qX2zc
找到此代码procedure test(numArgs:integer; MyFormattingString:string);
var
v:array of tvarrec;
i:integer;
begin
setlength(v, numArgs);
for i:=1 to numArgs do
begin
v[i-1].vtype:=vtpchar;
v[i-1].vtpchar:=strnew(pchar(myDataSet.FieldByName(inttostr(i)).asstring));
end;
memo1.lines.add(Format(MyFormattingString,v);
for i:=1 to numArgs do strdispose(v[i-1].vtpchar);
end;
不回答我必须处理的所有事情,但我想我现在知道如何构建TVarRec数组。