9

我正在实现一个解释器,我的解释器将支持的功能之一就像 Delphi 的Format. 事实上,我正在使用SysUtils.Format. 但是,我在构建函数的第二个参数时遇到了问题,即array of TVarRec.

假设我有以下代码。现在,我只是假设解释代码需要访问哪些 Delphi 变量 (iVar1iVar2),但我仍然不知道如何将它们放入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;

ConstructFormatArgs如何在 Delphi(而不是 Assembly)中实现我的功能?

4

3 回答 3

8

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() 函数中一样:

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() 函数扫描 % 并将 %something 替换为 const 数组中的值,具体取决于格式说明符和精度说明符。

于 2013-10-23T03:45:49.310 回答
5

如您所知,array of constarray of TVarRec. 要构造一个,首先声明一个数组,然后设置每个元素的值,就像你设置任何其他数组一样。

TVarRec是一个变体记录,这意味着它可以保存许多不同类型的值。它有一个字段 ,VType来指示它所持有的值的类型。在其他字段中,一次只有一个具有有效值。设置VType字段,然后设置对应的值字段,如VIntegerVString

请注意,某些字段实际上是指针,例如VVariantVInt64。当您分配这些指针值时,您需要确保它们指向的任何内容在需要时保持可访问和有效Format

其他字段是其实际值类型的无类型版本。这些包括VAnsiStringVInterface。当您分配给这些字段时,请注意它们不会像普通AnsiStringIUnknown变量那样维护通常的引用计数,因此请再次注意这些变量的生命周期。

编译器通常是唯一生成此类数组的东西,因此几乎没有参考代码可用于查看它们是如何构建的。相反,您可以查看使用 const 数组的其他代码。例如,几年前我为 JCL实现了一个 Unicode 感知功能。Format它使用有限状态机一次一个字符地解析格式字符串。每次完成对参数字符串的解析时,它都会从输入数组中获取相应的参数,并根据字符串和参数类型对其进行格式化。

它使用了最少的汇编程序,而且只是为了提高效率,而不是因为它真的有必要。作为参考,所有的汇编程序都在注释中附有等效的 Delphi 代码。

于 2013-10-23T03:52:30.893 回答
4

在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 数组。

于 2013-10-23T04:51:33.460 回答