Подтвердить что ты не робот

Возможно ли реализовать функциональность выхода Python в автономном режиме C?

Недавно я пришел через ключевое слово yield в Python (а также JavaScript). Я понимаю, что это primarliy используется для шаблона генератора, но конструкция языка, похоже, используется в асинхронных функциях, а также где мои интересы лежат, В асинхронных функциях он может просто действовать как синтаксический сахар, и я знаю, что существуют альтернативные шаблоны для достижения такого же эффекта. Но мне это нравится - ЛЕТ!

Я хочу знать, могу ли я сделать что-то подобное в C (даже с встроенной сборкой). Я наткнулся на реализацию Java, используя потоки https://github.com/mherrmann/java-generator-functions, которые я могу более или менее реализовать на C. Однако это не будет самостоятельной реализацией, и мой интерес чисто в самостоятельной реализации.

Приходя к C-подпрограммам (http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html), один из недостатков заключается в том, что объекты стека не могут использоваться. Однако я все еще в порядке, поскольку текущие асинхронные реализации обратного вызова также не могут использовать стек. Однако проблема заключается в самостоятельной реализации - я не могу придумать способ сбора всех переменных регистра и сохранения их без размещенной среды.

Возможно, существует решение с использованием setjmp/longjmp, однако я уверен, что они не могут быть реализованы отдельно.

Итак, вопрос: возможно ли реализовать функциональность выхода Python в автономном C?

Лично я думаю, что исчерпал возможности, поэтому я попрошу об этом. Если бы у вас была хостинг-реализация, как бы вы ее реализовали (желательно с помощью некоторой макромагии)? У меня довольно уродливая реализация, которую я буду публиковать позже, если ничего не придет.

Также я не хочу реализаций С++. Если вы не можете обернуть С++ чистыми функциями C.

EDIT: Основное требование состоит в том, что функция генератора должна быть повторно введена.

4b9b3361

Ответ 1

Итераторы в Python следуют этому шаблону: вы вызываете их (с аргументами) и возвращаете объект. Вы повторно вызываете этот объект .next() или .__next__(), и он проходит через итератор.

Мы можем сделать что-то подобное:

typedef struct iterator{
    int yield_position; /* Where to jump to */
    void *yield_state; /* opaque container for local variables */
    void *(*next)(iterator*); /* Function taking "this" argument 
                               returning a pointer to whatever we yielded */
} iterator;

iterator *make_generator(/* arguments? */){
    iterator *result = malloc(sizeof(iterator)); /* Caller frees */
    result->yield_position = 0;
    /* Optionally allocate/initialize yield_state here */
    result->next = do_generator;
    return result;
}

void *do_generator(iterator *this){
    struct whatever *result;
    switch(this->yield_position){
        case 0:
            /* Do something */
            this->yield_position = 1;
            /* Save local variables to this->yield_state if necessary */
            return (void *) result;
        case 1:
            /* Initialize local variables from this->yield_state */
            /* Etc.*/
    }
}

void free_generator(iterator *iter){
    /* Free iter->yield_state if necessary */
    free(iter);
}

Так как метки меток могут использоваться практически везде, коммутатор должен иметь возможность, например, если необходимо, перейдите в середину цикла. Вам, вероятно, все равно придется повторно инициализировать переменные цикла и т.д.

Он называется так:

iterator *iter = make_generator(/* arguments? */);
struct whatever *foo = iter->next(iter);
/* etc. */
free_generator(iter);

Передача аргумента this вручную становится утомительной, поэтому определите макрос:

#DEFINE NEXT(iter) ((iter)->next(iter))

Ответ 2

Игнорируя специфический для языка жаргон, то, что вы ищете, называется "сопрограммы". Саймон Татхам придумал что-то, что выглядит и ведет себя очень похоже на сопрограммы с некоторой магией препроцессора. Он работает не совсем так, но притворяется таким образом, что это полезно в большинстве случаев.

Подробнее см. здесь.

В зависимости от вашей точной проблемы этого может быть достаточно, или может быть и не так. Преимущество этого метода, во всяком случае, заключается в том, что он работает со стандартом C; нет необходимости в нестандартных компиляторах.

Ответ 3

Я собираюсь ответить с помощью setjmp и longjmp, поскольку эти интерфейсы являются стандартными, и вы можете легко найти их реализации для любой платформы HW. Они независимы, но зависят от HW.

struct _yield_state {
    jmp_buf buf;
    _Bool yielded;
};

#define yieldable  static struct _yield_state _state; \
                   if (_state.yielded) longjmp(_state.buf, 1); else {}


#define yield(x)  if (setjmp(_state.buf)) { _state.yielded = false;          }\
                  else                    { _state.yielded = true;  return x }

int func(int a, int b)
{
    yieldable;

    if (a > b)
        yield(0);

    return a + b;
}

Здесь вы можете найти пример setjmp и longjmp здесь. Это чистая сборка, специфическая только для основного оборудования.