C State-Design - PullRequest
       104

C State-Design

191 голосов
/ 30 октября 2009

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

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

ПРИМЕЧАНИЕ: Я в основном после проверенных и проверенных методов реализации.

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

An event pump points to an event integrator which points to a dispatcher. The dispatcher points to 1 through n actions which point back to the event integrator. A transition table with wildcards points to the dispatcher.

Ответы [ 25 ]

4 голосов
/ 13 января 2013

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

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

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

4 голосов
/ 24 апреля 2015

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

3 голосов
/ 15 января 2012

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

Как всегда, boost перенесет вас в адский шаблон.

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

Вот вопрос SO, требующий сравнения между двумя , когда оба автора отвечают.

3 голосов
/ 20 июля 2011

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

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

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

Этот код взят прямо из моего интерпретатора постскриптума и развился в его нынешнюю форму под большим руководством коллег по comp.lang.c. Поскольку postscript в основном не имеет синтаксиса (требующего только сбалансированные скобки), Regular Language 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 = "0123456789" "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 = "0123456789abcdef", *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;
    }
}
3 голосов
/ 23 ноября 2009

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

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

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

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

2 голосов
/ 16 декабря 2011

Учитывая, что вы подразумеваете, что вы можете использовать C ++ и, следовательно, OO-код, я бы посоветовал оценить шаблон состояния GoF (GoF = Gang of Four, ребята, которые написали книгу шаблонов проектирования, которая вывела шаблоны проектирования в центр внимания).

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

Вполне вероятно, что кто-то другой, кто поддержит ваш код на более позднем этапе, узнает его.

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

Ниже приведены некоторые ссылки на шаблон состояния «Gof», как предлагает Крейг:

2 голосов
/ 31 октября 2009

видел это где-то

#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);
  }
}
2 голосов
/ 30 октября 2009

Эта серия постов Ars OpenForum о некотором сложном кусочке логики управления включает очень простую реализацию в качестве конечного автомата в C.

1 голос
/ 20 декабря 2009

Я успешно использовал State Machine Compiler в проектах Java и Python.

1 голос
/ 30 октября 2009

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

  1. Реализация встроенного конечного автомата

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

    • Машины состояний кодирования в C и C ++

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...