Есть ли способ объявить константу ссылки на метод во время компиляции?

Я работаю над интерпретатором script, который мне удалось получить в рабочем состоянии. Он имеет компилятор, который анализирует script и генерирует байт-код, и виртуальную машину, которая выполняет байт-код.

В основе интерпретатора лежит цикл с гигантским выражением case, который выглядит примерно так:

case CurrentOpcode.Operation of
  OP_1: DoOp1(CurrentOpcode);
  OP_2: DoOp2(CurrentOpcode);
  ...
  OP_N: DoOpN(CurrentOpcode);
end;

Профилирование говорит мне, что по какой-то причине мое выполнение script проводит значительное количество времени внутри этого оператора case, что кажется мне странным, поэтому я ищу способ его оптимизации. Очевидное решение, так как все операционные функции в основном имеют одну и ту же подпись, заключается в создании массива указателей методов, индексированных по значению опкода Operation. Но Operation объявляется как enum, и было бы неплохо объявить это как массив const, чтобы, если я добавлю больше кодов операций в будущем, компилятор может напомнить мне об обновлении массива.

Так как указатель метода хранит состояние времени выполнения (ссылка Self на объект, с которым он работает,), я не могу создать массив const указателей методов. (В любом случае это не будет хорошей идеей, так как вполне вероятно, что в итоге я буду работать одновременно с несколькими script.) Но методы - это просто синтаксический сахар. Что-то вроде:

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'

Я пробовал разные варианты, чтобы попытаться собрать его, но все они дают ошибки. Есть ли способ получить это для компиляции?

Ответ 1

Декларация:

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!

Ответ 2

Нет решения для постоянной части вашего вопроса, но вот как вы могли бы устранить 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;