我正在编写一个脚本解释器,我已经设法进入工作状态。它有一个解析脚本并生成字节码的编译器,以及一个执行字节码的VM。
解释器的核心是一个带有巨大case
语句的循环,看起来像这样:
case CurrentOpcode.Operation of
OP_1: DoOp1(CurrentOpcode);
OP_2: DoOp2(CurrentOpcode);
...
OP_N: DoOpN(CurrentOpcode);
end;
分析告诉我,无论出于何种原因,我的脚本执行都花费了大量时间在case
语句中,这对我来说似乎很奇怪,所以我正在寻找一种优化它的方法。显而易见的解决方案,因为所有操作函数基本上具有相同的签名,是创建由操作码的Operation
值索引的方法指针数组。但是Operation
被声明为枚举,并且能够将其声明为const数组会很好,所以如果我将来添加更多的操作码,编译器可以提醒我更新数组。
由于方法指针存储运行时状态,(对其运行的对象的Self
引用),我无法创建方法指针的const数组。 (无论如何,这也不是一个好主意,因为很可能我最终会同时运行多个脚本。)但方法只是语法糖。类似的东西:
procedure TMyObject.DoSomething(x, y: integer);
真的意味着:
procedure TMyObject_DoSomething(Self: TMyObject; x, y: integer);
所以我应该能够以后一种形式声明一个函数指针类型并以那种方式分配它,然后我只需要在调用它时显式传递Self
作为第一个参数。但编译器并不喜欢这样。
type TOpcodeProc = procedure (Self: TScriptVM; Opcode: TOpcode);
const OPCODE: TOpcodeProc = TScriptVM.DoOp1;
[DCC Error]: E2009 Incompatible types: 'regular procedure and method pointer'
我尝试了不同的变体来尝试让它进行编译,但它们都会出错。有没有办法让这个编译?
答案 0 :(得分:9)
声明:
const
OPCODE: array[TOperation] of Pointer = (
@TScriptVM.DoOp1,
@TScriptVM.DoOp2,
...
@TScriptVM.DoOpN
);
呼叫:
TOpcodeProc(OPCODE[CurrentOpcode.Operation])(Self, CurrentOpcode);
更酷的东西:
var
OpCodeProcs: array[TOperation] of TOpCodeProc absolute OPCODE;
调用该方法的语法更好:
OpCodeProcs[CurrentOpcode.Operation](Self, CurrentOpcode);
好的是因为编译器阻止你为OpCodeProcs变量分配一些东西的绝对值!
答案 1 :(得分:5)
对于问题的不变部分没有解决方案,但这是你如何消除case
:
type
TTestMethod = procedure(Instance: TObject; Param1, Param2: Integer);
TTest = class(TObject)
private
FMethods: array[0..1] of TTestMethod;
procedure InitMethods;
procedure CallMethod(ID: Integer; Param1, Param2: Integer);
protected
procedure TestMethod0(Param1, Param2: Integer);
procedure TestMethod1(Param1, Param2: Integer);
end;
procedure TTest.InitMethods;
begin
FMethods[0] := TTestMethod(@TTest.TestMethod0);
FMethods[1] := TTestMethod(@TTest.TestMethod1);
end;
procedure TTest.CallMethod(ID: Integer; Param1, Param2: Integer);
begin
FMethods[ID](Self, Param1, Param2);
end;