2009-10-30 2 views
180

Я создаю небольшой проект в смешанных C и C++. Я строю одну маленькую государственную машину в центре одной из моих рабочих потоков.C государственный дизайн машины

Мне было интересно, если вы, гуру на SO, поделитесь своими технологиями проектирования штата.

ПРИМЕЧАНИЕ: Я в основном после того, как попытался & проверенные методы внедрения.

ОБНОВЛЕНО: Основываясь на всем громадном входе собранной на SO, я остановился на этой архитектуре:

enter image description here

+3

Ответы здесь очень хорошие. Также посмотрите на этот дублированный вопрос, который содержит несколько хороших ответов: http://stackoverflow.com/questions/1371460/state-machines-tutorials –

+2

Это также интересно: http://stackoverflow.com/questions/133214/is -there-a-typ-state-machine-implementation-pattern –

+1

related: http://stackoverflow.com/questions/2101961/python-state-machine-design – jldupont

ответ

157

Государственные машины, которые я разработал ранее (C, а не C++), все сводились к массиву struct и циклу. Структура в основном состоит из государственного и событий (для взгляда вверх) и функция, которая возвращает новое состояние, что-то вроде:

typedef struct { 
    int st; 
    int ev; 
    int (*fn)(void); 
} tTransition; 

Затем вы определяете состояния и события с простыми определяет (в ANY те специальные маркеры, смотри ниже):

#define ST_ANY    -1 
#define ST_INIT    0 
#define ST_ERROR    1 
#define ST_TERM    2 
: : 
#define EV_ANY    -1 
#define EV_KEYPRESS  5000 
#define EV_MOUSEMOVE  5001 

Тогда можно определить все функции, которые вызываются переходами:

static int GotKey (void) { ... }; 
static int FsmError (void) { ... }; 

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

Использование глобалов не так плохо, как кажется, поскольку FSM обычно заперт внутри одного блока компиляции, и все переменные являются статическими для этого устройства (именно поэтому я использовал цитаты вокруг «глобального» выше - они более общий в FSM, чем по-настоящему глобальный). Как и во всех глобальных переменных, это требует осторожности.

Массив переходов затем определяет все возможные переходы и функции, которые вызываются для этих переходов (в том числе догоняющего все последний):

tTransition trans[] = { 
    { ST_INIT, EV_KEYPRESS, &GotKey}, 
    : : 
    { ST_ANY, EV_ANY, &FsmError} 
}; 
#define TRANS_COUNT (sizeof(trans)/sizeof(*trans)) 

Что это означает: если вы в ST_INIT и вы получите событие EV_KEYPRESS, позвоните по телефону GotKey.

работа автомата, то становится относительно простой циклом:

state = ST_INIT; 
while (state != ST_TERM) { 
    event = GetNextEvent(); 
    for (i = 0; i < TRANS_COUNT; i++) { 
     if ((state == trans[i].st) || (ST_ANY == trans[i].st)) { 
      if ((event == trans[i].ev) || (EV_ANY == trans[i].ev)) { 
       state = (trans[i].fn)(); 
       break; 
      } 
     } 
    } 
} 

Как упоминалось выше, обратите внимание на использовании ST_ANY как дикие-карты, позволяя событие не вызвать функцию независимо от того, текущего состояния , EV_ANY также работает аналогично, позволяя любому событию в конкретном состоянии вызывать функцию.

Это также может гарантировать, что, если вы дойдете до конца массива переходов, вы получите сообщение об ошибке с указанием вашей FSM не была построена правильно (с помощью ST_ANY/EV_ANY комбинации.

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

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


И, как ldog состояний в комментарий, вы можете избежать глобалам вообще, передавая указатель на структуру ко всем функциям (и используя, что в цикле обработки событий). Это позволит нескольким государственным машинам работать бок о бок без помех.

Просто создайте тип структуры, который содержит данные, специфичные для машины (состояние минимально), и используйте это вместо глобальных.

Причина, по которой я редко делал это, объясняется просто тем, что большинство состояний, которые я написал, были одноточечными (одноразовый, в процессе запуска, чтение файла конфигурации, например), не нужно запускать более одного экземпляра. Но это имеет значение, если вам нужно запустить более одного.

+0

Ницца ... спасибо за ваш вклад. – jldupont

+0

Да, очень приятно. Я могу украсть некоторые идеи из этого. –

+0

Это выглядит красиво и практично. Я бы предпочел, чтобы вы использовали гигантский оператор switch вместо повторения через массив переходов ... Я думаю, что вижу несколько преимуществ этого, но я был бы признателен, если бы вы объяснили, почему вы это делаете гигантского переключателя. Кроме того, незначительный nit: все компиляторы, которые я использую в эти дни, поддерживают перечисления, поэтому я бы предпочел использовать перечисления для таких вещей, как состояния. Я всегда определяю первое и последнее перечисление, которые не являются допустимыми состояниями, поэтому я могу написать проверку диапазона, например: 'Assert (stateInvalid steveha

1

Ваш вопрос достаточно общий,
Вот две ссылки на статьи, которые могли бы быть полезным,

  1. Embedded State Machine Implementation

    This article describes a simple approach to implementing a state machine for an embedded system. For purposes of this article, a state machine is defined as an algorithm that can be in one of a small number of states. A state is a condition that causes a prescribed relationship of inputs to outputs, and of inputs to next states.
    A savvy reader will quickly note that the state machines described in this article are Mealy machines. A Mealy machine is a state machine where the outputs are a function of both present state and input, as opposed to a Moore machine, in which the outputs are a function only of state.

    • Coding State Machines in C and C++

      My preoccupation in this article is with state-machine fundamentals and some straightforward programming guidelines for coding state machines in C or C++. I hope that these simple techniques can become more common, so that you (and others) can readily see the state-machine structure right from the source code.

+0

Крайне полезно, +1 – vines

+5

Ссылки мертвы или не указывают на то, что предлагает сообщение. – Sonny

2

Это series of Ars OpenForum posts о сложном несколько бит логики управления включает в себя очень простой в последующей реализации в качестве конечного автомата в С.

75

Другие ответы хороши, но очень «легкий» реализация я использовал, когда состояние машины очень просто выглядит, как:

enum state { ST_NEW, ST_OPEN, ST_SHIFT, ST_END }; 

enum state current_state = ST_NEW; 

while (current_state != ST_END) 
{ 
    input = get_input(); 

    switch (current_state) 
    { 
     case ST_NEW: 
     /* Do something with input and set current_state */ 
     break; 

     case ST_OPEN: 
     /* Do something different and set current_state */ 
     break; 

     /* ... etc ... */ 
    } 
} 

Я хотел бы использовать это, когда состояние машины достаточно, чтобы просто указатель функции & Подход таблицы перехода штата является излишним. Это часто полезно для поэтапного или поэтапного разбора.

+6

+1. Я действительно использовал этот метод для простых случаев. – paxdiablo

+23

+1 для использования перечисления в пользу '# def'. –

19

Обязательно проверьте работу Миро Samek (блог State Space, сайт State Machines & Tools), статьи которого на C/C++ Users Journal были велики.

Сайт содержит полную реализацию (C/C++) как с открытым исходным кодом и коммерческой лицензии машинно рамочной структуры состояния (QP Framework), обработчик события (QEP), основной инструмент моделирования (QM) и инструмент трассировки (QSpy), которые позволяют рисовать государственные машины, создавать код и отлаживать их.

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

Веб-сайт также содержит ссылки на несколько пакетов поддержки плат для использования программного обеспечения со встроенными платформами.

+0

Ваш юмор ускользает от меня, но ваши ссылки полезны. – jldupont

+0

Я изменил название вопроса в соответствии с вашими каламбурами. – jldupont

+0

@jldupont: Я просто хотел, чтобы было лучше уточнить. Теперь я удалил ненужные части моего ответа. –

10

Я сделал что-то похожее на то, что описывает paxdiablo, только вместо массива состояний/событийных переходов я установил двумерный массив указателей на функции, значение события как индекс одной оси и текущее состояние, как другое. Затем я просто звоню state = state_table[event][state](params), и правильная вещь случается. Ячейки, представляющие недействительные комбинации состояний/событий, получают указатель на функцию, которая это говорит, конечно.

Очевидно, что это работает только в том случае, если значения состояния и события являются смежными диапазонами и начинаются с 0 или достаточно близко.

+1

Похоже, что это решение не масштабируется красиво: слишком много заполнения таблицы, нет? – jldupont

+2

+1. Проблема масштабирования здесь - память - у моего собственного решения есть проблема масштабирования повторно, т. Е. Время сканирования таблицы переходов (хотя вы можете вручную оптимизировать для наиболее распространенных переходов). Этот человек жертвует памятью для скорости - это просто компромисс.Вероятно, вам понадобятся проверки границ, но это не плохое решение. – paxdiablo

+0

Ребята - Мой комментарий не вышел так, как предполагалось: я имел в виду, что он гораздо более трудоемкий и подверженный ошибкам. Если вы добавляете состояние/событие, необходимо выполнить много редактирования. – jldupont

29

Вы могли бы рассмотреть State Machine Compiler http://smc.sourceforge.net/

Эта великолепная утилита с открытым исходным кодом принимает описание автомата на простом языке и компилирует его в какой-либо одной из дюжины языков - включая C и C++. Сама утилита написана на Java и может быть включена как часть сборки.

Причина, по которой это делается, а не ручное кодирование с использованием шаблона состояния GoF или любого другого подхода, заключается в том, что, как только ваша машина состояний выражается в виде кода, базовая структура имеет тенденцию исчезать под весом шаблона, который должен быть сгенерирован для его поддержки. Использование этого подхода дает вам отличное разделение проблем, и вы сохраняете структуру своего государственного аппарата «видимой». Автоматически сгенерированный код переходит в модули, которые вам не нужно трогать, так что вы можете вернуться и сфокусироваться на структуре конечного автомата, не оказывая влияния на поддерживаемый код, который вы написали.

Извините, я испытываю чрезмерное энтузиазм и, несомненно, отстраняю всех. Но это первоклассная утилита и хорошо документирована.

+12

+1. Нет ничего плохого в том, что вы с энтузиазмом относитесь к инструменту, который экономит ваше время и силы. Я до сих пор помню парсеры для компиляторов вначале, а затем обнаружил lex/yacc. Никогда не оглядывался назад. – paxdiablo

4

Чрезвычайно непроверенный, но забавный код, теперь в более изысканной версии, чем мой первоначальный ответ; уточненный версии можно найти на сайте mercurial.intuxication.org:

sm.h

#ifndef SM_ARGS 
#error "SM_ARGS undefined: " \ 
    "use '#define SM_ARGS (void)' to get an empty argument list" 
#endif 

#ifndef SM_STATES 
#error "SM_STATES undefined: " \ 
    "you must provide a list of comma-separated states" 
#endif 

typedef void (*sm_state) SM_ARGS; 
static const sm_state SM_STATES; 

#define sm_transit(STATE) ((sm_state (*) SM_ARGS)STATE) 

#define sm_def(NAME) \ 
    static sm_state NAME ## _fn SM_ARGS; \ 
    static const sm_state NAME = (sm_state)NAME ## _fn; \ 
    static sm_state NAME ## _fn SM_ARGS 

example.c

#include <stdio.h> 

#define SM_ARGS (int i) 
#define SM_STATES EVEN, ODD 
#include "sm.h" 

sm_def(EVEN) 
{ 
    printf("even %i\n", i); 
    return ODD; 
} 

sm_def(ODD) 
{ 
    printf("odd %i\n", i); 
    return EVEN; 
} 

int main(void) 
{ 
    int i = 0; 
    sm_state state = EVEN; 

    for(; i < 10; ++i) 
     state = sm_transit(state)(i); 

    return 0; 
} 
+14

Мне очень нравится «непроверенный» комментарий. Кажется, указывает, что есть степени непроверенности, и что вы приложили немало усилий, чтобы не тестировать его :-) – paxdiablo

+0

@Christoph ссылка в этом ответе сломана. Кроме того, вы протестировали этот код или нет? Если он был протестирован и работает, вы должны удалить это из ответа. Также, возможно, покажите пример того, какой код это приведет, как только макросы будут расширены. Мне нравится общая идея. – Joakim

2

пила это где-то

#define FSM 
#define STATE(x)  s_##x : 
#define NEXTSTATE(x) goto s_##x 

FSM { 
    STATE(x) { 
    ... 
    NEXTSTATE(y); 
    } 

    STATE(y) { 
    ... 
    if (x == 0) 
     NEXTSTATE(y); 
    else 
     NEXTSTATE(x); 
    } 
} 
+1

Это интересно, но не повышается, пока вы не приведете пример или два (и, возможно, результат де-макроса), или какое-то обсуждение того, почему это может быть более практичным, чем другое. Интересное использование сиротских кронштейнов и макросов. Я предполагаю, что что-то подобное может быть сделано на языке, который делает какую-то оптимизацию хвостовой рекурсии; вы можете использовать прямые вызовы функций и не беспокоиться о перегрузке пространства стека с помощью функции call garbage (что, по моему мнению, является тем, что макросы здесь преодолевают) –

+2

Преимущества этого метода ...? Я вижу несколько недостатков, таких как обфускация макросов и использование 'goto', который создает зависимость от предопределяющей многозадачной ОС. –

32

Простите меня за нарушение каждого le в информатике, но конечный автомат является одним из немногих (я могу считать только два раза), где оператор goto не только более эффективен, но и делает ваш код более чистым и понятным для чтения. Поскольку операторы goto основаны на таблицах, вы можете назвать свои состояния вместо того, чтобы отслеживать беспорядок чисел или использовать перечисление. Это также делает намного более чистый код, так как вам не нужны все лишние круговые указатели функций или огромные операторы switch и while. Я упоминал, что это более эффективно?

Вот что состояние машины может выглядеть следующим образом:

void state_machine() { 
first_state: 
    // Do some stuff here 
    switch(some_var) { 
    case 0: 
     goto first_state; 
    case 1: 
     goto second_state; 
    default: 
     return; 
    } 

second_state: 
    // Do some stuff here 
    switch(some_var) { 
    case 0: 
     goto first_state; 
    case 1: 
     goto second_state; 
    default: 
     return; 
    } 
} 

Вы получаете общую идею. Дело в том, что вы можете эффективно использовать государственный автомат, а тот, который относительно легко читать, и кричит читателю, что он смотрит на конечный автомат.Обратите внимание, что если вы используете операторы goto, вы все равно должны быть осторожны, так как очень легко стрелять себе в ногу, делая это.

+4

Это работает только в том случае, если конечный автомат находится в объекте верхнего уровня. В тот момент, когда какой-то другой объект, который иногда запрашивает/отправляет сообщения, должен иметь состояние, вы застряли в этом подходе (это или вам нужно сделать его намного сложнее) – skrebbel

+11

+1 для использования инструкции goto –

+1

Это действительно заставляет вас использовать упреждающую многозадачность во всех, кроме самых простых случаях. –

5

Метод, который мне нравится для государственных машин (по крайней мере, для управления программой), заключается в использовании указателей функций. Каждое состояние представлено другой функцией. Функция принимает входной символ и возвращает указатель на функцию для следующего состояния. Центральные диспетчерские мониторы отправляют следующий вход, подают его в текущее состояние и обрабатывают результат.

Набрав на него немного странно, так как C не имеет способа указывать типы указателей функций, возвращающихся сами, поэтому функции состояния возвращают void*. Но вы можете сделать что-то вроде этого:

typedef void* (*state_handler)(input_symbol_t); 
void dispatch_fsm() 
{ 
    state_handler current = initial_handler; 
    /* Let's assume returning null indicates end-of-machine */ 
    while (current) { 
     current = current(get_input); 
    } 
} 

Тогда ваши индивидуальные государственные функции могут перейти на их вход, чтобы обработать и вернуть соответствующее значение.

+0

+1 Это действительно приятно, и обеспечивает хорошие возможности для работы с функциями внутри функций перехода. –

3

Придя к этому позднему (как обычно), но просматривая ответы на сегодняшний день, я думаю, что что-то важное отсутствует;

Я нашел в своих проектах, что это может быть очень полезно не имеет функцию для каждого действительного сочетания состояния/события. Мне нравится идея эффективно иметь 2D-таблицу состояний/событий. Но мне нравится, что элементы таблицы являются более чем простым указателем на функцию. Вместо этого я пытаюсь организовать свой проект, поэтому в его сердце он содержит кучу простых атомных элементов или действий. Таким образом, я могу перечислить эти простые атомные элементы на каждом пересечении таблицы состояния/события. Идея состоит в том, что вам не нужно, чтобы определить массу N квадратных (обычно очень простых) функций. Почему у кого-то такое склонное к ошибкам, трудоемкое, трудно написанное, трудночитаемое, вы называете его?

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

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

+2

Может быть, пример будет приятным, нет? – jldupont

+1

Реалистичный пример, который может быть представлен изолированно, представляет собой сложную задачу, которая потребует больше времени, чем я готов дать сейчас. Есть ли что-нибудь в моем посте, которое особенно трудно понять? Может быть, я могу выразить это более четко. Идея очень проста; Не определяйте механизм состояния, для которого требуется отдельная функция для каждой комбинации событий/состояний, вы получаете слишком много функций таким образом. Вместо этого найдите другой способ описать функциональность, которую вы хотите для этой комбинации событий/состояний, по крайней мере, в большинстве случаев. –

+2

Понял: пример псевдокода был бы хорош, но ваша точка понятна. – jldupont

5

простейшего случая

enum event_type { ET_THIS, ET_THAT }; 
union event_parm { uint8_t this; uint16_t that; } 
static void handle_event(enum event_type event, union event_parm parm) 
{ 
    static enum { THIS, THAT } state; 
    switch (state) 
    { 
    case THIS: 
    switch (event) 
    { 
     case ET_THIS: 
     // Handle event. 
     break; 

     default: 
     // Unhandled events in this state. 
     break; 
    } 
    break; 

    case THAT: 
    // Handle state. 
    break; 
    } 
} 

Очки: Государство является частным, а не только к единице компиляции, но и к event_handler. Специальные случаи могут обрабатываться отдельно от главного выключателя, используя любую конструкцию, которая считается необходимой.

Более сложный случай

Когда переключатель получает больше, чем несколько экранов полной, разделить его на отдельные функции, которые обрабатывают каждое состояние, используя таблицу состояний для поиска функции непосредственно. Состояние по-прежнему является приватным для обработчика событий. Функции обработчика состояния возвращают следующее состояние. При необходимости некоторые события могут по-прежнему получать специальное лечение в главном обработчике событий. Я хотел бы бросить в псевдо-событий для состояния входа и выхода и, возможно, начать состояние машины:

enum state_type { THIS, THAT, FOO, NA }; 
enum event_type { ET_START, ET_ENTER, ET_EXIT, ET_THIS, ET_THAT, ET_WHATEVER, ET_TIMEOUT }; 
union event_parm { uint8_t this; uint16_t that; }; 
static void handle_event(enum event_type event, union event_parm parm) 
{ 
    static enum state_type state; 
    static void (* const state_handler[])(enum event_type event, union event_parm parm) = { handle_this, handle_that }; 
    enum state_type next_state = state_handler[state](event, parm); 
    if (NA != next_state && state != next_state) 
    { 
    (void)state_handler[state](ET_EXIT, 0); 
    state = next_state; 
    (void)state_handler[state](ET_ENTER, 0); 
    } 
} 

Я не уверен, если я прибил синтаксис, особенно в отношении массива указателей на функции.Я не запускал ни одно из этого через компилятор. После обзора я заметил, что я забыл явно отказаться от следующего состояния при обработке псевдо-событий (скобки (void) перед вызовом state_handler()). Это то, что мне нравится делать, даже если компиляторы молча принимают молчание. Он сообщает читателям кода, что «да, я действительно имел в виду вызывать функцию без использования возвращаемого значения», и она может остановить инструменты статического анализа от предупреждения об этом. Это может быть особенным, потому что я не помню, чтобы кто-то другой это делал.

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

Состояние обработчика будет выглядеть так:

static enum state_type handle_this(enum event_type event, union event_parm parm) 
{ 
    enum state_type next_state = NA; 
    switch (event) 
    { 
    case ET_ENTER: 
    // Start a timer to do whatever. 
    // Do other stuff necessary when entering this state. 
    break; 

    case ET_WHATEVER: 
    // Switch state. 
    next_state = THAT; 
    break; 

    case ET_TIMEOUT: 
    // Switch state. 
    next_state = FOO; 
    break; 

    case ET_EXIT: 
    // Stop the timer. 
    // Generally clean up this state. 
    break; 
    } 
    return next_state; 
} 

Больше сложности

Когда модуль компиляции становится слишком большим (то, что вы чувствуете, что, я должен сказать, около 1000 строк), положить каждый обработчик состояния в отдельном файле. Когда каждый обработчик состояния становится длиннее, чем несколько экранов, разделяйте каждое событие в отдельной функции, подобно тому, как разделялся переключатель состояния. Вы можете сделать это несколькими способами, отдельно от состояния или с помощью общей таблицы или объединения различных схем. Некоторые из них были охвачены другими. Сортируйте свои таблицы и используйте двоичный поиск, если требуется скорость.

Обобщенное программирование

Я хотел бы препроцессор для решения таких проблем, как сортировка таблиц или даже генерации автоматов из описаний, что позволяет «писать программы о программах». Я считаю, что это то, что люди Boost используют шаблоны C++, но я нахожу синтаксис загадочным.

Двумерные таблицы

Я использовал таблицы состояние/событие в прошлом, но я должен сказать, что для простейших случаев я не считаю их необходимыми, и я предпочитаю ясность и удобочитаемость переключателя если он действительно проходит за один экран. Для более сложных случаев таблицы быстро выходят из-под контроля, как отмечали другие. Идиомы, которые я здесь представляю, позволяют добавлять множество событий и состояний, когда вам это нравится, без необходимости поддерживать таблицу потребления памяти (даже если это может быть программная память).

Отказ от ответственности

Особые потребности могут сделать эти идиомы менее полезны, но я нашел их очень ясно и ремонтопригодны.

+0

Я бы избегал «this» как имя переменной или символ только для ассоциации, даже если это не зарезервированное слово. – XTL

3

Alrght, я думаю, что мой немного отличается от всех остальных. Немного больше разделения кода и данных, чем я вижу в других ответах. Я действительно прочитал теорию, чтобы написать это, в котором реализуется полный регулярный язык (без регулярных выражений, к сожалению). Ульман, Минский, Хомский. Не могу сказать, что я все это понял, но я сделал из старых мастеров как можно более прямо: через их слова.

Я использую указатель функции на предикат, который определяет переход в состояние «да» или состояние «нет». Это облегчает создание акцептора конечного состояния для обычного языка, который вы программируете более похожим на ассемблере образом. Пожалуйста, не откладывайте мои глупые выборы. 'czek' == 'check'. 'grok' == [искать его в Hacker Dictionary].

Так что для каждой итерации czek вызывает предикатную функцию с текущим символом в качестве аргумента. Если предикат возвращает true, символ потребляется (указатель продвинут), и мы следуем «y», чтобы выбрать следующее состояние. Если предикат возвращает false, символ НЕ потребляется, и мы следуем «n». Поэтому каждая инструкция - это двусторонняя ветка! Должно быть, я читал «Историю Мела» в то время.

Этот код исходит от my postscript interpreter и превратился в его текущую форму с большим руководством от ребята на comp.lang.c. Поскольку postcript в принципе не имеет синтаксиса (требуется только сбалансированные скобки), такой же как и синтаксический анализатор, является обычным языком Accepter.

/* currentstr is set to the start of string by czek 
    and used by setrad (called by israd) to set currentrad 
    which is used by israddig to determine if the character 
    in question is valid for the specified radix 
    -- 
    a little semantic checking in the syntax! 
*/ 
char *currentstr; 
int currentrad; 
void setrad(void) { 
    char *end; 
    currentrad = strtol(currentstr, &end, 10); 
    if (*end != '#' /* just a sanity check, 
         the automaton should already have determined this */ 
    || currentrad > 36 
    || currentrad < 2) 
     fatal("bad radix"); /* should probably be a simple syntaxerror */ 
} 

/* 
    character classes 
    used as tests by automatons under control of czek 
*/ 
char *alpha = "" "ABCDE" "FGHIJ" "KLMNO" "PQRST" "UVWXYZ"; 
#define EQ(a,b) a==b 
#define WITHIN(a,b) strchr(a,b)!=NULL 
int israd (int c) { 
    if (EQ('#',c)) { setrad(); return true; } 
    return false; 
} 
int israddig(int c) { 
    return strchrnul(alpha,toupper(c))-alpha <= currentrad; 
} 
int isdot (int c) {return EQ('.',c);} 
int ise (int c) {return WITHIN("eE",c);} 
int issign (int c) {return WITHIN("+-",c);} 
int isdel (int c) {return WITHIN("()<>[]{}/%",c);} 
int isreg (int c) {return c!=EOF && !isspace(c) && !isdel(c);} 
#undef WITHIN 
#undef EQ 

/* 
    the automaton type 
*/ 
typedef struct { int (*pred)(int); int y, n; } test; 

/* 
    automaton to match a simple decimal number 
*/ 
/* /^[+-]?[0-9]+$/ */ 
test fsm_dec[] = { 
/* 0*/ { issign, 1, 1 }, 
/* 1*/ { isdigit, 2, -1 }, 
/* 2*/ { isdigit, 2, -1 }, 
}; 
int acc_dec(int i) { return i==2; } 

/* 
    automaton to match a radix number 
*/ 
/* /^[0-9]+[#][a-Z0-9]+$/ */ 
test fsm_rad[] = { 
/* 0*/ { isdigit, 1, -1 }, 
/* 1*/ { isdigit, 1, 2 }, 
/* 2*/ { israd, 3, -1 }, 
/* 3*/ { israddig, 4, -1 }, 
/* 4*/ { israddig, 4, -1 }, 
}; 
int acc_rad(int i) { return i==4; } 

/* 
    automaton to match a real number 
*/ 
/* /^[+-]?(d+(.d*)?)|(d*.d+)([eE][+-]?d+)?$/ */ 
/* represents the merge of these (simpler) expressions 
    [+-]?[0-9]+\.[0-9]*([eE][+-]?[0-9]+)? 
    [+-]?[0-9]*\.[0-9]+([eE][+-]?[0-9]+)? 
    The complexity comes from ensuring at least one 
    digit in the integer or the fraction with optional 
    sign and optional optionally-signed exponent. 
    So passing isdot in state 3 means at least one integer digit has been found 
    but passing isdot in state 4 means we must find at least one fraction digit 
    via state 5 or the whole thing is a bust. 
*/ 
test fsm_real[] = { 
/* 0*/ { issign, 1, 1 }, 
/* 1*/ { isdigit, 2, 4 }, 
/* 2*/ { isdigit, 2, 3 }, 
/* 3*/ { isdot, 6, 7 }, 
/* 4*/ { isdot, 5, -1 }, 
/* 5*/ { isdigit, 6, -1 }, 
/* 6*/ { isdigit, 6, 7 }, 
/* 7*/ { ise,  8, -1 }, 
/* 8*/ { issign, 9, 9 }, 
/* 9*/ { isdigit, 10, -1 }, 
/*10*/ { isdigit, 10, -1 }, 
}; 
int acc_real(int i) { 
    switch(i) { 
     case 2: /* integer */ 
     case 6: /* real */ 
     case 10: /* real with exponent */ 
      return true; 
    } 
    return false; 
} 

/* 
    Helper function for grok. 
    Execute automaton against the buffer, 
    applying test to each character: 
     on success, consume character and follow 'y' transition. 
     on failure, do not consume but follow 'n' transition. 
    Call yes function to determine if the ending state 
    is considered an acceptable final state. 
    A transition to -1 represents rejection by the automaton 
*/ 
int czek (char *s, test *fsm, int (*yes)(int)) { 
    int sta = 0; 
    currentstr = s; 
    while (sta!=-1 && *s) { 
     if (fsm[sta].pred((int)*s)) { 
      sta=fsm[sta].y; 
      s++; 
     } else { 
      sta=fsm[sta].n; 
     } 
    } 
    return yes(sta); 
} 

/* 
    Helper function for toke. 
    Interpret the contents of the buffer, 
    trying automatons to match number formats; 
    and falling through to a switch for special characters. 
    Any token consisting of all regular characters 
    that cannot be interpreted as a number is an executable name 
*/ 
object grok (state *st, char *s, int ns, 
    object *src, 
    int (*next)(state *,object *), 
    void (*back)(state *,int, object *)) { 

    if (czek(s, fsm_dec, acc_dec)) { 
     long num; 
     num = strtol(s,NULL,10); 
     if ((num==LONG_MAX || num==LONG_MIN) && errno==ERANGE) { 
      error(st,limitcheck); 
/*  } else if (num > INT_MAX || num < INT_MIN) { */ 
/*   error(limitcheck, OP_token); */ 
     } else { 
      return consint(num); 
     } 
    } 

    else if (czek(s, fsm_rad, acc_rad)) { 
     long ra,num; 
     ra = (int)strtol(s,NULL,10); 
     if (ra > 36 || ra < 2) { 
      error(st,limitcheck); 
     } 
     num = strtol(strchr(s,'#')+1, NULL, (int)ra); 
     if ((num==LONG_MAX || num==LONG_MIN) && errno==ERANGE) { 
      error(st,limitcheck); 
/*  } else if (num > INT_MAX || num < INT_MAX) { */ 
/*   error(limitcheck, OP_token); */ 
     } else { 
      return consint(num); 
     } 
    } 

    else if (czek(s, fsm_real, acc_real)) { 
     double num; 
     num = strtod(s,NULL); 
     if ((num==HUGE_VAL || num==-HUGE_VAL) && errno==ERANGE) { 
      error(st,limitcheck); 
     } else { 
      return consreal(num); 
     } 
    } 

    else switch(*s) { 
     case '(': { 
      int c, defer=1; 
      char *sp = s; 

      while (defer && (c=next(st,src)) != EOF) { 
       switch(c) { 
        case '(': defer++; break; 
        case ')': defer--; 
         if (!defer) goto endstring; 
         break; 
        case '\\': c=next(st,src); 
         switch(c) { 
          case '\n': continue; 
          case 'a': c = '\a'; break; 
          case 'b': c = '\b'; break; 
          case 'f': c = '\f'; break; 
          case 'n': c = '\n'; break; 
          case 'r': c = '\r'; break; 
          case 't': c = '\t'; break; 
          case 'v': c = '\v'; break; 
          case '\'': case '\"': 
          case '(': case ')': 
          default: break; 
         } 
       } 
       if (sp-s>ns) error(st,limitcheck); 
       else *sp++ = c; 
      } 
endstring: *sp=0; 
      return cvlit(consstring(st,s,sp-s)); 
     } 

     case '<': { 
      int c; 
      char d, *x = "abcdef", *sp = s; 
      while (c=next(st,src), c!='>' && c!=EOF) { 
       if (isspace(c)) continue; 
       if (isxdigit(c)) c = strchr(x,tolower(c)) - x; 
       else error(st,syntaxerror); 
       d = (char)c << 4; 
       while (isspace(c=next(st,src))) /*loop*/; 
       if (isxdigit(c)) c = strchr(x,tolower(c)) - x; 
       else error(st,syntaxerror); 
       d |= (char)c; 
       if (sp-s>ns) error(st,limitcheck); 
       *sp++ = d; 
      } 
      *sp = 0; 
      return cvlit(consstring(st,s,sp-s)); 
     } 

     case '{': { 
      object *a; 
      size_t na = 100; 
      size_t i; 
      object proc; 
      object fin; 

      fin = consname(st,"}"); 
      (a = malloc(na * sizeof(object))) || (fatal("failure to malloc"),0); 
      for (i=0 ; objcmp(st,a[i]=toke(st,src,next,back),fin) != 0; i++) { 
       if (i == na-1) 
       (a = realloc(a, (na+=100) * sizeof(object))) || (fatal("failure to malloc"),0); 
      } 
      proc = consarray(st,i); 
      { size_t j; 
       for (j=0; j<i; j++) { 
        a_put(st, proc, j, a[j]); 
       } 
      } 
      free(a); 
      return proc; 
     } 

     case '/': { 
      s[1] = (char)next(st,src); 
      puff(st, s+2, ns-2, src, next, back); 
      if (s[1] == '/') { 
       push(consname(st,s+2)); 
       opexec(st, op_cuts.load); 
       return pop(); 
      } 
      return cvlit(consname(st,s+1)); 
     } 

     default: return consname(st,s); 
    } 
    return null; /* should be unreachable */ 
} 

/* 
    Helper function for toke. 
    Read into buffer any regular characters. 
    If we read one too many characters, put it back 
    unless it's whitespace. 
*/ 
int puff (state *st, char *buf, int nbuf, 
    object *src, 
    int (*next)(state *,object *), 
    void (*back)(state *,int, object *)) { 
    int c; 
    char *s = buf; 
    while (isreg(c=next(st,src))) { 
     if (s-buf >= nbuf-1) return false; 
     *s++ = c; 
    } 
    *s = 0; 
    if (!isspace(c) && c != EOF) back(st,c,src); /* eat interstice */ 
    return true; 
} 

/* 
    Helper function for Stoken Ftoken. 
    Read a token from src using next and back. 
    Loop until having read a bona-fide non-whitespace non-comment character. 
    Call puff to read into buffer up to next delimiter or space. 
    Call grok to figure out what it is. 
*/ 
#define NBUF MAXLINE 
object toke (state *st, object *src, 
     int (*next)(state *, object *), 
     void (*back)(state *, int, object *)) { 
    char buf[NBUF] = "", *s=buf; 
    int c,sta = 1; 
    object o; 

    do { 
     c=next(st,src); 
     //if (c==EOF) return null; 
     if (c=='%') { 
      if (DUMPCOMMENTS) fputc(c, stdout); 
      do { 
       c=next(st,src); 
       if (DUMPCOMMENTS) fputc(c, stdout); 
      } while (c!='\n' && c!='\f' && c!=EOF); 
     } 
    } while (c!=EOF && isspace(c)); 
    if (c==EOF) return null; 
    *s++ = c; 
    *s = 0; 
    if (!isdel(c)) sta=puff(st, s,NBUF-1,src,next,back); 

    if (sta) { 
     o=grok(st,buf,NBUF-1,src,next,back); 
     return o; 
    } else { 
     return null; 
    } 
} 
+2

Это то, что любой генератор парсера или лексера будет радостно излучать для вас. Это безобидно. Если вы хотите закодировать его вручную, это вызывает сомнения. Конечно, у него есть педагогическая заслуга. –

2

Учитывая, что вы подразумеваете вы можете использовать C++ и, следовательно, OO код, я предложил бы оценить «картину GoF'state (GoF = Банды четырех, ребята, написавшие книгу шаблонов проектирования, которые принесли образцы дизайна в центр внимания).

Это не особенно сложно, и оно широко используется и обсуждается, поэтому легко увидеть примеры и объяснения на линии.

Он также, скорее всего, будет распознан любым другим, поддерживающим ваш код позднее.

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

Ниже приведены некоторые ссылки на государственного образца 'Гоф', как говорит Крейг:

+0

больше похож на комментарий: могу ли я предложить вам рассматривать его как таковой? т. е. не размещать его в разделе «ответ». – jldupont

+0

Было бы хорошо, если бы вы могли предоставить хорошую ссылку URL для «шаблона состояния GoF», для тех, кто не знаком с ним. –

+1

@jldupont - справедливый комментарий.Я изменил текст, чтобы сделать его правильным ответом, поскольку я чувствую, что на основе личного опыта, что, если нет конкретных проблем с производительностью, подход GoF отлично работает и будет иметь относительно большую «пользовательскую базу» – Mick

3

boost.org поставляется с 2-мя различными реализациями состояния диаграммы:

Как всегда, импульс будет пучком вы в шаблон ад.

Первая библиотека предназначена для критически важных состояний машин. Вторая библиотека дает вам прямой путь перехода от UML Statechart к коду.

Вот SO question asking for a comparison between the two, на котором оба автора отвечают.

0

Вы можете использовать библиотеку с открытым исходным кодом OpenFST.

OpenFst is a library for constructing, combining, optimizing, and searching weighted finite-state transducers (FSTs). Weighted finite-state transducers are automata where each transition has an input label, an output label, and a weight. The more familiar finite-state acceptor is represented as a transducer with each transition's input and output label equal. Finite-state acceptors are used to represent sets of strings (specifically, regular or rational sets); finite-state transducers are used to represent binary relations between pairs of strings (specifically, rational transductions). The weights can be used to represent the cost of taking a particular transition.

2

Мне очень понравился ответ paxdiable и решил реализовать все недостающие функции для моего приложения, как сторожевых переменных и состояние машины конкретных данных.

Я загрузил свою реализацию на этот сайт, чтобы поделиться с сообществом. Он был протестирован с использованием IAR Embedded Workbench для ARM.

https://sourceforge.net/projects/compactfsm/

8

Очень красивый шаблон на основе C++ государственная машина «структура» дается Стефан Heinzmann в его article.

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

Главное нововведение в том, что компилятор генерирует очень эффективный код. Пустые действия входа/выхода не требуют затрат. Непустые действия входа/выхода включены. Компилятор также проверяет полноту государственной диаграммы. Отсутствующие действия генерируют ошибки связывания. Единственное, что не поймано, это отсутствие Top::init.

Это очень хорошая альтернатива реализации Miro Samek, если вы можете жить без того, чего не хватает - это далеко не полная реализация UML Statechart, хотя она правильно реализует семантику UML, тогда как код Samek по дизайну не обрабатывать действия выхода/перехода/ввода в правильном порядке.

Если этот код работает для того, что вам нужно сделать, и у вас есть достойный компилятор C++ для вашей системы, он, вероятно, будет работать лучше, чем реализация C/C++ в Miro. Компилятор генерирует для вас сжатую, O (1) переходную машину. Если проверка сборки сборок подтверждает, что оптимизация работает по желанию, вы приближаетесь к теоретической производительности. Лучшая часть: это относительно крошечный, понятный код.

#ifndef HSM_HPP 
#define HSM_HPP 

// This code is from: 
// Yet Another Hierarchical State Machine 
// by Stefan Heinzmann 
// Overload issue 64 december 2004 
// http://accu.org/index.php/journals/252 

/* This is a basic implementation of UML Statecharts. 
* The key observation is that the machine can only 
* be in a leaf state at any given time. The composite 
* states are only traversed, never final. 
* Only the leaf states are ever instantiated. The composite 
* states are only mechanisms used to generate code. They are 
* never instantiated. 
*/ 

// Helpers 

// A gadget from Herb Sutter's GotW #71 -- depends on SFINAE 
template<class D, class B> 
class IsDerivedFrom { 
    class Yes { char a[1]; }; 
    class No { char a[10]; }; 
    static Yes Test(B*); // undefined 
    static No Test(...); // undefined 
public: 
    enum { Res = sizeof(Test(static_cast<D*>(0))) == sizeof(Yes) ? 1 : 0 }; 
}; 

template<bool> class Bool {}; 

// Top State, Composite State and Leaf State 

template <typename H> 
struct TopState { 
    typedef H Host; 
    typedef void Base; 
    virtual void handler(Host&) const = 0; 
    virtual unsigned getId() const = 0; 
}; 

template <typename H, unsigned id, typename B> 
struct CompState; 

template <typename H, unsigned id, typename B = CompState<H, 0, TopState<H> > > 
struct CompState : B { 
    typedef B Base; 
    typedef CompState<H, id, Base> This; 
    template <typename X> void handle(H& h, const X& x) const { Base::handle(h, x); } 
    static void init(H&); // no implementation 
    static void entry(H&) {} 
    static void exit(H&) {} 
}; 

template <typename H> 
struct CompState<H, 0, TopState<H> > : TopState<H> { 
    typedef TopState<H> Base; 
    typedef CompState<H, 0, Base> This; 
    template <typename X> void handle(H&, const X&) const {} 
    static void init(H&); // no implementation 
    static void entry(H&) {} 
    static void exit(H&) {} 
}; 

template <typename H, unsigned id, typename B = CompState<H, 0, TopState<H> > > 
struct LeafState : B { 
    typedef H Host; 
    typedef B Base; 
    typedef LeafState<H, id, Base> This; 
    template <typename X> void handle(H& h, const X& x) const { Base::handle(h, x); } 
    virtual void handler(H& h) const { handle(h, *this); } 
    virtual unsigned getId() const { return id; } 
    static void init(H& h) { h.next(obj); } // don't specialize this 
    static void entry(H&) {} 
    static void exit(H&) {} 
    static const LeafState obj; // only the leaf states have instances 
}; 

template <typename H, unsigned id, typename B> 
const LeafState<H, id, B> LeafState<H, id, B>::obj; 

// Transition Object 

template <typename C, typename S, typename T> 
// Current, Source, Target 
struct Tran { 
    typedef typename C::Host Host; 
    typedef typename C::Base CurrentBase; 
    typedef typename S::Base SourceBase; 
    typedef typename T::Base TargetBase; 
    enum { // work out when to terminate template recursion 
     eTB_CB = IsDerivedFrom<TargetBase, CurrentBase>::Res, 
     eS_CB = IsDerivedFrom<S, CurrentBase>::Res, 
     eS_C = IsDerivedFrom<S, C>::Res, 
     eC_S = IsDerivedFrom<C, S>::Res, 
     exitStop = eTB_CB && eS_C, 
     entryStop = eS_C || eS_CB && !eC_S 
    }; 
    // We use overloading to stop recursion. 
    // The more natural template specialization 
    // method would require to specialize the inner 
    // template without specializing the outer one, 
    // which is forbidden. 
    static void exitActions(Host&, Bool<true>) {} 
    static void exitActions(Host&h, Bool<false>) { 
     C::exit(h); 
     Tran<CurrentBase, S, T>::exitActions(h, Bool<exitStop>()); 
    } 
    static void entryActions(Host&, Bool<true>) {} 
    static void entryActions(Host& h, Bool<false>) { 
     Tran<CurrentBase, S, T>::entryActions(h, Bool<entryStop>()); 
     C::entry(h); 
    } 
    Tran(Host & h) : host_(h) { 
     exitActions(host_, Bool<false>()); 
    } 
    ~Tran() { 
     Tran<T, S, T>::entryActions(host_, Bool<false>()); 
     T::init(host_); 
    } 
    Host& host_; 
}; 

// Initializer for Compound States 

template <typename T> 
struct Init { 
    typedef typename T::Host Host; 
    Init(Host& h) : host_(h) {} 
    ~Init() { 
     T::entry(host_); 
     T::init(host_); 
    } 
    Host& host_; 
}; 

#endif // HSM_HPP 

Тестовый код следует.

#include <cstdio> 
#include "hsm.hpp" 
#include "hsmtest.hpp" 

/* Implements the following state machine from Miro Samek's 
* Practical Statecharts in C/C++ 
* 
* |-init-----------------------------------------------------| 
* |       s0        | 
* |----------------------------------------------------------| 
* |               | 
* | |-init-----------|  |-------------------------| | 
* | |  s1  |---c--->|   s2   | | 
* | |----------------|<--c----|-------------------------| | 
* | |    |  |       | | 
* |<-d-| |-init-------| |  | |-init----------------| | | 
* | | |  s11 |<----f----| |   s21  | | | 
* | /--| |------------| |  | |---------------------| | | 
* | a | |   | |  | |      | | | 
* | \->| |   |------g--------->|-init------| | | | 
* | | |____________| |  | |-b->| s211 |---g--->| 
* | |----b---^  |------f------->|   | | | | 
* | |________________|  | |<-d-|___________|<--e----| 
* |        | |_____________________| | | 
* |        |_________________________| | 
* |__________________________________________________________| 
*/ 

class TestHSM; 

typedef CompState<TestHSM,0>  Top; 
typedef CompState<TestHSM,1,Top> S0; 
typedef CompState<TestHSM,2,S0>  S1; 
typedef LeafState<TestHSM,3,S1>  S11; 
typedef CompState<TestHSM,4,S0>  S2; 
typedef CompState<TestHSM,5,S2>  S21; 
typedef LeafState<TestHSM,6,S21>   S211; 

enum Signal { A_SIG, B_SIG, C_SIG, D_SIG, E_SIG, F_SIG, G_SIG, H_SIG }; 

class TestHSM { 
public: 
    TestHSM() { Top::init(*this); } 
    ~TestHSM() {} 
    void next(const TopState<TestHSM>& state) { 
     state_ = &state; 
    } 
    Signal getSig() const { return sig_; } 
    void dispatch(Signal sig) { 
     sig_ = sig; 
     state_->handler(*this); 
    } 
    void foo(int i) { 
     foo_ = i; 
    } 
    int foo() const { 
     return foo_; 
    } 
private: 
    const TopState<TestHSM>* state_; 
    Signal sig_; 
    int foo_; 
}; 

bool testDispatch(char c) { 
    static TestHSM test; 
    if (c<'a' || 'h'<c) { 
     return false; 
    } 
    printf("Signal<-%c", c); 
    test.dispatch((Signal)(c-'a')); 
    printf("\n"); 
    return true; 
} 

int main(int, char**) { 
    testDispatch('a'); 
    testDispatch('e'); 
    testDispatch('e'); 
    testDispatch('a'); 
    testDispatch('h'); 
    testDispatch('h'); 
    return 0; 
} 

#define HSMHANDLER(State) \ 
    template<> template<typename X> inline void State::handle(TestHSM& h, const X& x) const 

HSMHANDLER(S0) { 
    switch (h.getSig()) { 
    case E_SIG: { Tran<X, This, S211> t(h); 
     printf("s0-E;"); 
     return; } 
    default: 
     break; 
    } 
    return Base::handle(h, x); 
} 

HSMHANDLER(S1) { 
    switch (h.getSig()) { 
    case A_SIG: { Tran<X, This, S1> t(h); 
     printf("s1-A;"); return; } 
    case B_SIG: { Tran<X, This, S11> t(h); 
     printf("s1-B;"); return; } 
    case C_SIG: { Tran<X, This, S2> t(h); 
     printf("s1-C;"); return; } 
    case D_SIG: { Tran<X, This, S0> t(h); 
     printf("s1-D;"); return; } 
    case F_SIG: { Tran<X, This, S211> t(h); 
     printf("s1-F;"); return; } 
    default: break; 
    } 
    return Base::handle(h, x); 
} 

HSMHANDLER(S11) { 
    switch (h.getSig()) { 
    case G_SIG: { Tran<X, This, S211> t(h); 
     printf("s11-G;"); return; } 
    case H_SIG: if (h.foo()) { 
      printf("s11-H"); 
      h.foo(0); return; 
     } break; 
    default: break; 
    } 
    return Base::handle(h, x); 
} 

HSMHANDLER(S2) { 
    switch (h.getSig()) { 
    case C_SIG: { Tran<X, This, S1> t(h); 
     printf("s2-C"); return; } 
    case F_SIG: { Tran<X, This, S11> t(h); 
     printf("s2-F"); return; } 
    default: break; 
    } 
    return Base::handle(h, x); 
} 

HSMHANDLER(S21) { 
    switch (h.getSig()) { 
    case B_SIG: { Tran<X, This, S211> t(h); 
     printf("s21-B;"); return; } 
    case H_SIG: if (!h.foo()) { 
      Tran<X, This, S21> t(h); 
      printf("s21-H;"); h.foo(1); 
      return; 
     } break; 
    default: break; 
    } 
    return Base::handle(h, x); 
} 

HSMHANDLER(S211) { 
    switch (h.getSig()) { 
    case D_SIG: { Tran<X, This, S21> t(h); 
     printf("s211-D;"); return; } 
    case G_SIG: { Tran<X, This, S0> t(h); 
     printf("s211-G;"); return; } 
    } 
    return Base::handle(h, x); 
} 

#define HSMENTRY(State) \ 
    template<> inline void State::entry(TestHSM&) { \ 
     printf(#State "-ENTRY;"); \ 
    } 

HSMENTRY(S0) 
HSMENTRY(S1) 
HSMENTRY(S11) 
HSMENTRY(S2) 
HSMENTRY(S21) 
HSMENTRY(S211) 

#define HSMEXIT(State) \ 
    template<> inline void State::exit(TestHSM&) { \ 
     printf(#State "-EXIT;"); \ 
    } 

HSMEXIT(S0) 
HSMEXIT(S1) 
HSMEXIT(S11) 
HSMEXIT(S2) 
HSMEXIT(S21) 
HSMEXIT(S211) 

#define HSMINIT(State, InitState) \ 
    template<> inline void State::init(TestHSM& h) { \ 
     Init<InitState> i(h); \ 
     printf(#State "-INIT;"); \ 
    } 

HSMINIT(Top, S0) 
HSMINIT(S0, S1) 
HSMINIT(S1, S11) 
HSMINIT(S2, S21) 
HSMINIT(S21, S211) 
+0

Хмм ... в вашем коде отсутствует. Прежде всего, вы включаете два заголовка, но предоставляете только первый. Когда я просто комментирую оператор «include», я получаю эту ошибку при компиляции: d: \ 1 \ hsm> g ++ test.cpp test.cpp: 195: 1: ошибка: специализация «static void CompState :: init ( H &) [с H = TestHSM; unsigned int id = 0u; B = CompState >] 'после создания экземпляра –

+0

Мне пришлось перенести определения всех HSMINIT(), которые должны быть выше класса TestHSM, и он компилируется и работает нормально (; единственное, что неверно, это тот факт, что все переходы являются «внешними», в то время как они должны быть «внутренними» - в этой статье были некоторые споры, и автор решил, что «extrenal» был прав, но используемые стрелы предполагают «внутреннее». –

4

Другим интересным инструментом с открытым исходным кодом является Yakindu Statechart Tools on statecharts.org. Он использует диаграммы состояния Harel и, таким образом, обеспечивает иерархические и параллельные состояния и генерирует код C и C++ (а также Java). Он не использует библиотеки, но следует подходом «простого кода». Код в основном применяет структуры коммутационных шкафов. Генераторы кода также могут быть настроены. Кроме того, инструмент предоставляет множество других функций.

1

Это старое сообщение с большим количеством ответов, но я думал, что добавлю свой подход к машине конечного состояния в C. Я создал скрипт Python для создания скелетного кода C для любого количества состояний. Этот сценарий зарегистрирован на GituHub по адресу FsmTemplateC

Этот пример основан на других подходах, о которых я читал. Он не использует операторы goto или switch, а вместо этого имеет функции перехода в матрицу указателя (справочная таблица). Код опирается на большой многострочный макрос инициализатора и функции C99 (назначенные инициализаторы и сложные литералы), поэтому, если вам не нравятся эти вещи, вам может не понравиться этот подход.

Вот Python скрипт из turnstile example, который генерирует скелет C-кода с использованием FsmTemplateC:

# dict parameter for generating FSM 
fsm_param = { 
    # main FSM struct type string 
    'type': 'FsmTurnstile', 
    # struct type and name for passing data to state machine functions 
    # by pointer (these custom names are optional) 
    'fopts': { 
     'type': 'FsmTurnstileFopts', 
     'name': 'fopts' 
    }, 
    # list of states 
    'states': ['locked', 'unlocked'], 
    # list of inputs (can be any length > 0) 
    'inputs': ['coin', 'push'], 
    # map inputs to commands (next desired state) using a transition table 
    # index of array corresponds to 'inputs' array 
    # for this example, index 0 is 'coin', index 1 is 'push' 
    'transitiontable': { 
     # current state | 'coin' | 'push' | 
     'locked':  ['unlocked',  ''], 
     'unlocked':  [  '', 'locked'] 
    } 
} 

# folder to contain generated code 
folder = 'turnstile_example' 
# function prefix 
prefix = 'fsm_turnstile' 

# generate FSM code 
code = fsm.Fsm(fsm_param).genccode(folder, prefix) 

Заголовок генерируемый вывод содержит определений типов:

/* function options (EDIT) */ 
typedef struct FsmTurnstileFopts { 
    /* define your options struct here */ 
} FsmTurnstileFopts; 

/* transition check */ 
typedef enum eFsmTurnstileCheck { 
    EFSM_TURNSTILE_TR_RETREAT, 
    EFSM_TURNSTILE_TR_ADVANCE, 
    EFSM_TURNSTILE_TR_CONTINUE, 
    EFSM_TURNSTILE_TR_BADINPUT 
} eFsmTurnstileCheck; 

/* states (enum) */ 
typedef enum eFsmTurnstileState { 
    EFSM_TURNSTILE_ST_LOCKED, 
    EFSM_TURNSTILE_ST_UNLOCKED, 
    EFSM_TURNSTILE_NUM_STATES 
} eFsmTurnstileState; 

/* inputs (enum) */ 
typedef enum eFsmTurnstileInput { 
    EFSM_TURNSTILE_IN_COIN, 
    EFSM_TURNSTILE_IN_PUSH, 
    EFSM_TURNSTILE_NUM_INPUTS, 
    EFSM_TURNSTILE_NOINPUT 
} eFsmTurnstileInput; 

/* finite state machine struct */ 
typedef struct FsmTurnstile { 
    eFsmTurnstileInput input; 
    eFsmTurnstileCheck check; 
    eFsmTurnstileState cur; 
    eFsmTurnstileState cmd; 
    eFsmTurnstileState **transition_table; 
    void (***state_transitions)(struct FsmTurnstile *, FsmTurnstileFopts *); 
    void (*run)(struct FsmTurnstile *, FsmTurnstileFopts *, const eFsmTurnstileInput); 
} FsmTurnstile; 

/* transition functions */ 
typedef void (*pFsmTurnstileStateTransitions)(struct FsmTurnstile *, FsmTurnstileFopts *); 
  • перечисление eFsmTurnstileCheck используется для определить, заблокирован ли переход с EFSM_TURNSTILE_TR_RETREAT, разрешено продвигаться с EFSM_TURNSTILE_TR_ADVANCE или функцией c всем не предшествовал переход с EFSM_TURNSTILE_TR_CONTINUE.
  • enum eFsmTurnstileState - это просто список состояний.
  • enum eFsmTurnstileInput - это просто список входов.
  • Структура FsmTurnstile является сердцем конечного автомата с проверкой перехода, таблицей поиска функций, текущим состоянием, командным состоянием и псевдонимом основной функции, которая запускает машину.
  • Каждый указатель функции (псевдоним) в FsmTurnstile должен вызываться только из структуры и должен иметь свой первый вход как указатель на себя, чтобы поддерживать постоянное состояние, объектно-ориентированный стиль.

Теперь для объявления функций в заголовке:

/* fsm declarations */ 
void fsm_turnstile_locked_locked (FsmTurnstile *fsm, FsmTurnstileFopts *fopts); 
void fsm_turnstile_locked_unlocked (FsmTurnstile *fsm, FsmTurnstileFopts *fopts); 
void fsm_turnstile_unlocked_locked (FsmTurnstile *fsm, FsmTurnstileFopts *fopts); 
void fsm_turnstile_unlocked_unlocked (FsmTurnstile *fsm, FsmTurnstileFopts *fopts); 
void fsm_turnstile_run (FsmTurnstile *fsm, FsmTurnstileFopts *fopts, const eFsmTurnstileInput input); 

Имен функций в формате {prefix}_{from}_{to}, где {from} является предыдущим (текущее) состоянием и {to} является следующим состоянием. Обратите внимание, что если таблица переходов не допускает определенных переходов, будет установлен указатель NULL вместо указателя функции. Наконец, магия происходит с макросом. Здесь мы строим переходную таблицу (матрицу государственных перечислений) и функции переходного состояния посмотреть таблицу (матрицу указателей на функции):

/* creation macro */ 
#define FSM_TURNSTILE_CREATE() \ 
{ \ 
    .input = EFSM_TURNSTILE_NOINPUT, \ 
    .check = EFSM_TURNSTILE_TR_CONTINUE, \ 
    .cur = EFSM_TURNSTILE_ST_LOCKED, \ 
    .cmd = EFSM_TURNSTILE_ST_LOCKED, \ 
    .transition_table = (eFsmTurnstileState * [EFSM_TURNSTILE_NUM_STATES]) { \ 
     (eFsmTurnstileState [EFSM_TURNSTILE_NUM_INPUTS]) { \ 
      EFSM_TURNSTILE_ST_UNLOCKED, \ 
      EFSM_TURNSTILE_ST_LOCKED \ 
     }, \ 
     (eFsmTurnstileState [EFSM_TURNSTILE_NUM_INPUTS]) { \ 
      EFSM_TURNSTILE_ST_UNLOCKED, \ 
      EFSM_TURNSTILE_ST_LOCKED \ 
     } \ 
    }, \ 
    .state_transitions = (pFsmTurnstileStateTransitions * [EFSM_TURNSTILE_NUM_STATES]) { \ 
     (pFsmTurnstileStateTransitions [EFSM_TURNSTILE_NUM_STATES]) { \ 
      fsm_turnstile_locked_locked, \ 
      fsm_turnstile_locked_unlocked \ 
     }, \ 
     (pFsmTurnstileStateTransitions [EFSM_TURNSTILE_NUM_STATES]) { \ 
      fsm_turnstile_unlocked_locked, \ 
      fsm_turnstile_unlocked_unlocked \ 
     } \ 
    }, \ 
    .run = fsm_turnstile_run \ 
} 

При создании FSM, макрос FSM_EXAMPLE_CREATE() должен быть использован.

Теперь в исходном коде должна быть заполнена каждая заявленная выше функция перехода состояния. Структуру FsmTurnstileFopts можно использовать для передачи данных в/из конечного автомата. Каждый переход должен установить fsm->check равным EFSM_EXAMPLE_TR_RETREAT, чтобы заблокировать его от перехода или EFSM_EXAMPLE_TR_ADVANCE, чтобы он мог перейти в командное состояние. Рабочий пример можно найти на (FsmTemplateC) [https://github.com/ChisholmKyle/FsmTemplateC].

Вот очень простой фактическое использование в коде:

/* create fsm */ 
FsmTurnstile fsm = FSM_TURNSTILE_CREATE(); 
/* create fopts */ 
FsmTurnstileFopts fopts = { 
    .msg = "" 
}; 
/* initialize input */ 
eFsmTurnstileInput input = EFSM_TURNSTILE_NOINPUT; 

/* main loop */ 
for (;;) { 
    /* wait for timer signal, inputs, interrupts, whatever */ 
    /* optionally set the input (my_input = EFSM_TURNSTILE_IN_PUSH for example) */ 
    /* run state machine */ 
    my_fsm.run(&my_fsm, &my_fopts, my_input); 
} 

Все, что заголовок бизнес и все эти функции только, чтобы иметь простой и быстрый интерфейс стоит в моем уме.

0
void (* StateController)(void); 
void state1(void); 
void state2(void); 

void main() 
{ 
StateController=&state1; 
while(1) 
{ 
    (* StateController)(); 
} 
} 

void state1(void) 
{ 
//do something in state1 
StateController=&state2; 
} 

void state2(void) 
{ 
//do something in state2 
//Keep changing function direction based on state transition 
StateController=&state1; 
} 
+0

Дальнейшая оптимизация для обеспечения безопасности с помощью массива постоянных указателей функций на функции –

0

Я лично использую самосвязывающие структуры в сочетании с указательными массивами. Я загрузил учебник по GitHub некоторое время назад, ссылка:

https://github.com/mmelchger/polling_state_machine_c

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

Смежные вопросы