Общий алгоритм генерации конечного автомата для yield
Конечный автомат генерируется по коду тела функции, содержащей yield, после Lowering.
Вначале был реализован алгоритм как в C# - с помощью case (switch) по состояниям. Этот алгоритм включал в себя некоторую сложность: при наличии меток после lowering часть кода после метки приходилось выносить за case и организовывалась сложная логика переходов.
Последней каплей стал автовывод типов с узлами auto_type - присваивания для автовывода должны быть записаны в естественном порядке.
Поэтому было решено заменить case на блок вложенных if в начале с переходами по меткам. Этот способ по генерации кода оказывается проще и равномернее: если внутри кода встречается своя метка, то это никак не надло специально обрабатывать.
Кроме того, тесты показали, что код с вложенными if в PascalABC.NET работает быстрее кода с case.
Общий алгоритм
1. Количество yield равно количеству состояний плюс нулевое состояние
2. Сформировать в начале секцию переходов с помощью if
3. После секции переходов помечать меткой текущего состояния и от текущего места до yield переносить код. Код yield expr заменить на
current := <yielded-value>
state := <next-state>
Result := true
exit
4. В заключительном состоянии в конце Result := False. Или сделать это в начале алгоритма
Пример
Функция
function f<T>(a: array of T): sequence of T;
begin
foreach var x in a do
yield x;
end;
после Loweringа заменяется на следующую:
function <yield_helper_locals_type_detector>f<T>(a: array of T): sequence of T;
label lb#1,lb#2;
begin
var $coll$1: IEnumerable<yield_unknown_foreach_type>;
$coll$1 := a
var $enumerator$1 := $coll$1.GetEnumerator();
lb#2:
if not $enumerator$1.MoveNext() then
goto lb#1
var $x1 := $enumerator$1.Current;
yield $x1
goto lb#2
lb#1:
end
В результате генерации конечного автомата получаем старым методом с case получаем следующий код:
type
clyield#1 = class(IEnumerable<T>,IEnumerator<T>)
public_modifer
<$coll$1>MethodLocalVariable__1: IEnumerable<yield_unknown_foreach_type>;
<$enumerator$1>MethodLocalVariable__2: yield_unknown_expression_type;
<$x1>MethodLocalVariable__3: yield_unknown_expression_type;
<>MethodFormalParam__a: array of T;
<>1__state: integer;
<>2__current: T;
...
function MoveNext(): boolean;
label lb#1,lb#2;
begin
case <>1__state of
0:
begin
<$coll$1>MethodLocalVariable__1 := <>MethodFormalParam__a
<$enumerator$1>MethodLocalVariable__2 := <$coll$1>MethodLocalVariable__1.GetEnumerator()
goto lb#2
end
1:
begin
goto lb#2
goto lb#1
exit
end
end;
lb#2:
if not <$enumerator$1>MethodLocalVariable__2.MoveNext() then
goto lb#1
<$x1>MethodLocalVariable__3 := <$enumerator$1>MethodLocalVariable__2.Current
<>2__current := <$x1>MethodLocalVariable__3
<>1__state := 1
Result := True
exit
lb#1:
end
Здесь хорошо видно, что количество состояний на один больше количества yield