Общий алгоритм генерации конечного автомата для yield: различия между версиями

Материал из Вики проекта PascalABC.NET
Перейти к навигацииПерейти к поиску
Строка 23: Строка 23:
</source>  
</source>  
4. В заключительном состоянии в конце Result := False. Или сделать это в начале алгоритма
4. В заключительном состоянии в конце Result := False. Или сделать это в начале алгоритма
===Пример===
Функция
<source lang="Delphi">
function f<T>(a: array of T): sequence of T;
begin
  foreach var x in a do
    yield x;
end;
</source>
после Loweringа заменяется на следующую:
<source lang="Delphi">
  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
</source>
В результате генерации конечного автомата получаем старым методом с case получаем следующий код:
<source lang="Delphi">
  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
</source>
Здесь хорошо видно, что количество состояний на один больше количества yield

Версия от 11:14, 6 июля 2016

Конечный автомат генерируется по коду тела функции, содержащей 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