- PVSM.RU - https://www.pvsm.ru -
В моей практике часто возникали ситуации, когда применение конечного автомата являлось наиболее верным решением, однако от него приходилось отказываться ввиду срочности разработки, сложности поддержки, или же по каким-либо иным причинам. В этом посте мне хотелось бы поделиться с вами разработанным мною решением, позволяющим без труда встраивать в свои проекты конечные автоматы с возможностью наглядного отображения структуры дерева.
Сразу оговорюсь: данное решение предназначено для интеграции в проекты, разработанные на C++. Поддержка других языков на данный момент отсутствует (если будут надобность, их поддержка будет добавлена в будущем).
Решение представляет из себя связку из двух этапов:
Рассмотрим использование этого решения на примере распознавания входного сообщения, принятого откуда-то из вне.
Параметры сообщения следующие:
Достоверно известно, что сообщение состоит из последовательности строк: «команда» + «параметры», разделенной символом пробела. Строка «команда» может принимать значения «read» или «set», а «параметры» требуется только если введена команда «set». В этом случае ожидается значение для int-переменной.
git submodule add git@github.com:Vadimatorik/module_fsm.git
git submodule add git@github.com:Vadimatorik/plantuml_to_fsm_tree_generator.git
class user_string_parsing_class {
public:
user_string_parsing_class();
void start_parsing ( char* string );
private:
/// Указатель на текущий символ строки.
char* p = nullptr;
/// Данные, которые мы будем считывать и записывать.
int data = 0;
/// Конечный автомат.
fsm_class< user_string_parsing_class > fsm;
};
@startuml
[*] --> start
state "team_search" as start {
start: Ищем во входной строке
start: поддерживаемую команду.
}
start --> fts: 0
start --> arg1: 1
start --> arg2: 2
state "team_search_fail" as fts {
fts: Поддерживаемой команды
fts: найдено не было.
fts: Выводим список поддерживаемых
fts: команд.
}
state "set_param" as arg1 {
arg1: Проверяем входной параметр.
}
state "read_param" as arg2 {
arg2: Выводим требуемый параметр.
}
arg1 --> spd: 0
arg1 --> spn: 1
state "set_param_done" as spd {
spd: Выводим сообщение о том,
spd: что параметр был принят верно.
}
state "set_param_fail" as spn {
spn: Выводим сообщение о том,
spn: что параметр не был принят.
}
@enduml
#pragma once
#include "fsm.h"
#define HANDLER_USPC_FSM_STEP(NAME_STEP)
static int NAME_STEP ( const fsm_step< user_string_parsing_class >* previous_step,
user_string_parsing_class* obj )
class user_string_parsing_class {
public:
user_string_parsing_class();
void start_parsing ( char* string );
HANDLER_USPC_FSM_STEP( fsm_step_func_team_search );
HANDLER_USPC_FSM_STEP( fsm_step_func_team_search_fail );
HANDLER_USPC_FSM_STEP( fsm_step_func_set_param );
HANDLER_USPC_FSM_STEP( fsm_step_func_read_param );
HANDLER_USPC_FSM_STEP( fsm_step_func_set_param_done );
HANDLER_USPC_FSM_STEP( fsm_step_func_set_param_fail );
private:
/// Указатель на текущий символ строки.
char* p = nullptr;
/// Данные, которые мы будем считывать и записывать.
int data = 0;
/// Конечный автомат.
fsm_class< user_string_parsing_class > fsm;
};
/// Имя вершины формируется как:
/// имя классе + имя старового метода + _fsm_step
/// user_string_parsing_class_ + team_search + _fsm_step
extern const fsm_step< user_string_parsing_class >
user_string_parsing_class_team_search_fsm_step;
user_string_parsing_class::user_string_parsing_class() {
/// Производим связывание машины состояния с начальным шагом и объектом.
this->fsm.relinking( &user_string_parsing_class_team_search_fsm_step, this );
}
void user_string_parsing_class::start_parsing ( char* string ) {
/// Сохраняем указатель на сообщение, которое будем проверять.
this->p = string;
/// Запускаем анализ сообщения.
this->fsm.start();
}
#define FSM_F_USPC
const fsm_step< user_string_parsing_class >*
previous_step, user_string_parsing_class* obj
int user_string_parsing_class::fsm_step_func_team_search ( FSM_F_USPC ) {
( void )previous_step;
if ( strncmp( obj->p, "set", sizeof( "set" ) - 1 ) == 0 ) {
obj->p += sizeof( "set" );
return 1;
}
if ( strcmp( obj->p, "read" ) == 0 ) {
return 2;
}
return 0;
}
int user_string_parsing_class::fsm_step_func_team_search_fail ( FSM_F_USPC ) {
( void )previous_step;
( void )obj;
cout << "Command search fail! Available commands: set, read." << endl << endl;
return 0;
}
int user_string_parsing_class::fsm_step_func_set_param ( FSM_F_USPC ) {
( void )previous_step;
int r;
r = sscanf( obj->p, "%d", &obj->data );
return ( r == 1 ) ? 0 : 1;
}
int user_string_parsing_class::fsm_step_func_read_param ( FSM_F_USPC ) {
( void )previous_step;
cout << "Data = " << obj->data << endl;
return 0;
}
int user_string_parsing_class::fsm_step_func_set_param_done ( FSM_F_USPC ) {
( void )previous_step;
( void )obj;
cout << "The parameter done set!" << endl << endl;
return 0;
}
int user_string_parsing_class::fsm_step_func_set_param_fail ( FSM_F_USPC ) {
( void )previous_step;
( void )obj;
cout << "The parameter must be int!" << endl << endl;
return 0;
}
#include <fstream>
#include <stdint.h>
#include <string.h>
#include "user_string_parsing.h"
using namespace std;
/// Наше входящее сообщение будет находиться тут.
char buf[512];
/// Метод эмитирует приход нового сообщения.
void get_data ( char* b, int n ) {
switch( n ) {
case 0: memcpy( b, "fsdfhsd", sizeof("fsdfhsd") ); break;
case 1: memcpy( b, "read", sizeof("read") ); break;
case 2: memcpy( b, "set", sizeof("set") ); break;
case 3: memcpy( b, "set sfdf", sizeof("set sfdf") ); break;
case 4: memcpy( b, "set 21", sizeof("set 21") ); break;
case 5: memcpy( b, "read", sizeof("read") ); break;
}
}
int main ( void ) {
user_string_parsing_class usp;
for ( int l = 0; l < 6; l++ ) {
get_data( buf, l );
usp.start_parsing( buf );
}
return 0;
}
CPP := g++
CPP_FLAGS := -O0 -g3 -Werror -Wall -Wextra -std=c++1z
LDFLAGS := -O0 -g3 -Werror -Wall -Wextra
PU = plantuml_to_fsm_tree_generator/build/plantuml_to_fsm_tree_generator
# Собираем все необходимые данные из папки user_code.
USER_CPP_FILE := $(shell find user_code/ -maxdepth 5 -type f -name "*.cpp" )
USER_DIR := $(shell find user_code/ -maxdepth 5 -type d -name "*" )
USER_PATH := $(addprefix -I, $(USER_DIR))
USER_OBJ_FILE := $(addprefix build/obj/, $(USER_CPP_FILE))
USER_OBJ_FILE := $(patsubst %.cpp, %.o, $(USER_OBJ_FILE))
PROJECT_PATH += $(USER_PATH)
PROJECT_OBJ_FILE += $(USER_OBJ_FILE)
FSM_PU_FILE = $(shell find user_code/ -maxdepth 5 -type f -name "*.pu" )
FSM_CPP_FILE += $(patsubst %.pu, %.cpp, $(FSM_PU_FILE))
FSM_OBJ_FILE += $(patsubst %.pu, build/obj/%.o, $(FSM_PU_FILE))
PROJECT_OBJ_FILE += $(FSM_OBJ_FILE)
include module_fsm/makefile
%.cpp: %.pu
@echo [PU] $<
@$(PU) $< $@ user_string_parsing_class user_string_parsing.h
build/obj/%.o: %.cpp
@echo [CPP] $<
@mkdir -p $(dir $@)
@$(CPP) $(CPP_FLAGS)
$(PROJECT_PATH)
-c $< -o $@
all: $(PROJECT_OBJ_FILE)
@$(CPP) $(PROJECT_OBJ_FILE) -o build/example
@size --format=Berkeley build/example
clean:
@rm -R build/
@echo Clean all!
pfsm_build:
mkdir -p plantuml_to_fsm_tree_generator/build
cd plantuml_to_fsm_tree_generator/build && qmake -qt=qt5 .. && make
pfsm_clean:
cd plantuml_to_fsm_tree_generator/ && rm -R build
pfsm_rebuild:
cd plantuml_to_fsm_tree_generator/ && rm -R build
mkdir plantuml_to_fsm_tree_generator/build
cd plantuml_to_fsm_tree_generator/build && qmake -qt=qt5 .. && make
make pfsm_build
make all
Описанный в статье пример можно загрузить и попробовать собрать отсюда [3].
Обо всех неточностях, опечатках, ошибках, пожеланиях просьба писать в личных сообщениях.
Автор: Вадим
Источник [4]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/c-3/274939
Ссылки в тексте:
[1] проекта: https://github.com/Vadimatorik/plantuml_to_fsm_tree_generator
[2] fsm: https://github.com/Vadimatorik/module_fsm
[3] отсюда: https://github.com/Vadimatorik/plantuml_to_fsm_tree_generator_example
[4] Источник: https://habrahabr.ru/post/350880/?utm_source=habrahabr&utm_medium=rss&utm_campaign=350880
Нажмите здесь для печати.