├── .clang-format ├── .clang-tidy ├── .envrc ├── .gitignore ├── LICENSE ├── README.md ├── flake.lock ├── flake.nix ├── meson.build ├── scripts └── amalgamate.sh ├── single-include └── fsm.hpp ├── src ├── fsm │ ├── ctfsm.hpp │ └── meson.build ├── meson.build └── utility │ ├── existence_verifier.hpp │ ├── meson.build │ ├── type_map.hpp │ └── type_set.hpp ├── subprojects └── catch2.wrap └── test ├── catch_main.cpp ├── fsm.cpp ├── meson.build └── utility_test.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | AlignAfterOpenBracket: Align 3 | AlignConsecutiveMacros: "true" 4 | AlignConsecutiveAssignments: "true" 5 | AlignConsecutiveDeclarations: "true" 6 | AlignEscapedNewlines: Right 7 | AlignOperands: "true" 8 | AlignTrailingComments: "true" 9 | AllowAllArgumentsOnNextLine: "true" 10 | AllowAllConstructorInitializersOnNextLine: "true" 11 | AllowAllParametersOfDeclarationOnNextLine: "true" 12 | AllowShortBlocksOnASingleLine: "true" 13 | AllowShortCaseLabelsOnASingleLine: "false" 14 | AllowShortFunctionsOnASingleLine: None 15 | AllowShortIfStatementsOnASingleLine: Never 16 | AllowShortLambdasOnASingleLine: Inline 17 | AllowShortLoopsOnASingleLine: "false" 18 | AlwaysBreakAfterReturnType: None 19 | AlwaysBreakBeforeMultilineStrings: "false" 20 | AlwaysBreakTemplateDeclarations: "Yes" 21 | BinPackArguments: "false" 22 | BinPackParameters: "false" 23 | BreakBeforeBinaryOperators: All 24 | BreakBeforeBraces: Allman 25 | BreakBeforeTernaryOperators: "true" 26 | BreakConstructorInitializers: BeforeColon 27 | BreakInheritanceList: BeforeColon 28 | BreakStringLiterals: "false" 29 | ColumnLimit: "100" 30 | CompactNamespaces: "false" 31 | ConstructorInitializerAllOnOneLineOrOnePerLine: "true" 32 | Cpp11BracedListStyle: "true" 33 | DerivePointerAlignment: "true" 34 | DisableFormat: "false" 35 | EmptyLineBeforeAccessModifier: "Leave" 36 | ExperimentalAutoDetectBinPacking: "false" 37 | FixNamespaceComments: "true" 38 | IncludeBlocks: Regroup 39 | IndentAccessModifiers: "true" 40 | IndentCaseLabels: "true" 41 | IndentPPDirectives: AfterHash 42 | IndentWidth: "4" 43 | IndentWrappedFunctionNames: "true" 44 | KeepEmptyLinesAtTheStartOfBlocks: "false" 45 | Language: Cpp 46 | MaxEmptyLinesToKeep: "1" 47 | NamespaceIndentation: All 48 | PenaltyBreakAssignment: "1" 49 | PenaltyBreakBeforeFirstCallParameter: "1" 50 | PenaltyBreakComment: "1" 51 | PenaltyBreakFirstLessLess: "5" 52 | PenaltyBreakString: "100" 53 | PenaltyBreakTemplateDeclaration: "1" 54 | PenaltyExcessCharacter: "5" 55 | PenaltyReturnTypeOnItsOwnLine: "100" 56 | PointerAlignment: Right 57 | ReflowComments: "true" 58 | SortIncludes: "true" 59 | SpaceAfterCStyleCast: "true" 60 | SpaceAfterLogicalNot: "false" 61 | SpaceAfterTemplateKeyword: "false" 62 | SpaceBeforeAssignmentOperators: "true" 63 | SpaceBeforeCpp11BracedList: "true" 64 | SpaceBeforeCtorInitializerColon: "false" 65 | SpaceBeforeInheritanceColon: "true" 66 | SpaceBeforeParens: ControlStatements 67 | SpaceBeforeRangeBasedForLoopColon: "false" 68 | SpaceInEmptyParentheses: "false" 69 | SpacesBeforeTrailingComments: "0" 70 | SpacesInAngles: "false" 71 | SpacesInCStyleCastParentheses: "false" 72 | SpacesInContainerLiterals: "false" 73 | SpacesInParentheses: "false" 74 | SpacesInSquareBrackets: "false" 75 | Standard: Cpp11 76 | TabWidth: "4" 77 | UseTab: Never 78 | IncludeCategories: 79 | - Regex: "" 80 | Priority: 1 81 | - Regex: "<[a-z_]+>" 82 | Priority: 2 83 | - Regex: "<.+>" 84 | Priority: 3 85 | - Regex: '\".+\"' 86 | Priority: 4 87 | - Regex: '\".+/.+\"' 88 | Priority: 5 89 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | Checks: >- 2 | bugprone-*, 3 | cppcoreguidelines-*, 4 | google-*, 5 | misc-*, 6 | modernize-*, 7 | performance-*, 8 | readability-*, 9 | -bugprone-lambda-function-name, 10 | -bugprone-reserved-identifier, 11 | -cppcoreguidelines-avoid-goto, 12 | -cppcoreguidelines-avoid-magic-numbers, 13 | -cppcoreguidelines-avoid-non-const-global-variables, 14 | -cppcoreguidelines-non-private-member-variables-in-classes, 15 | -cppcoreguidelines-pro-bounds-array-to-pointer-decay, 16 | -cppcoreguidelines-pro-type-vararg, 17 | -google-readability-braces-around-statements, 18 | -google-readability-function-size, 19 | -misc-no-recursion, 20 | -misc-non-private-member-variables-in-classes, 21 | -modernize-return-braced-init-list, 22 | -modernize-use-nodiscard, 23 | -modernize-use-trailing-return-type, 24 | -performance-unnecessary-value-param, 25 | -readability-magic-numbers, 26 | -cppcoreguidelines-pro-type-union-access, 27 | -google-explicit-constructor, 28 | -Wc++17-extensions 29 | 30 | CheckOptions: 31 | - key: readability-function-cognitive-complexity.Threshold 32 | value: 100 33 | - key: readability-function-cognitive-complexity.IgnoreMacros 34 | value: true 35 | # Set naming conventions for your style below: 36 | # See https://clang.llvm.org/extra/clang-tidy/checks/readability-identifier-naming.html 37 | - key: readability-identifier-naming.ClassCase 38 | value: lower_case 39 | - key: readability-identifier-naming.NamespaceCase 40 | value: lower_case 41 | - key: readability-identifier-naming.StructCase 42 | value: lower_case 43 | - key: readability-suspicious-call-argument.PrefixSimilarAbove 44 | value: '30' 45 | - key: cppcoreguidelines-no-malloc.Reallocations 46 | value: '::realloc' 47 | - key: cppcoreguidelines-owning-memory.LegacyResourceConsumers 48 | value: '::free;::realloc;::freopen;::fclose' 49 | - key: modernize-use-auto.MinTypeNameLength 50 | value: '5' 51 | - key: bugprone-narrowing-conversions.PedanticMode 52 | value: 'false' 53 | - key: bugprone-unused-return-value.CheckedFunctions 54 | value: '::std::async;::std::launder;::std::remove;::std::remove_if;::std::unique;::std::unique_ptr::release;::std::basic_string::empty;::std::vector::empty;::std::back_inserter;::std::distance;::std::find;::std::find_if;::std::inserter;::std::lower_bound;::std::make_pair;::std::map::count;::std::map::find;::std::map::lower_bound;::std::multimap::equal_range;::std::multimap::upper_bound;::std::set::count;::std::set::find;::std::setfill;::std::setprecision;::std::setw;::std::upper_bound;::std::vector::at;::bsearch;::ferror;::feof;::isalnum;::isalpha;::isblank;::iscntrl;::isdigit;::isgraph;::islower;::isprint;::ispunct;::isspace;::isupper;::iswalnum;::iswprint;::iswspace;::isxdigit;::memchr;::memcmp;::strcmp;::strcoll;::strncmp;::strpbrk;::strrchr;::strspn;::strstr;::wcscmp;::access;::bind;::connect;::difftime;::dlsym;::fnmatch;::getaddrinfo;::getopt;::htonl;::htons;::iconv_open;::inet_addr;::isascii;::isatty;::mmap;::newlocale;::openat;::pathconf;::pthread_equal;::pthread_getspecific;::pthread_mutex_trylock;::readdir;::readlink;::recvmsg;::regexec;::scandir;::semget;::setjmp;::shm_open;::shmget;::sigismember;::strcasecmp;::strsignal;::ttyname' 55 | - key: cppcoreguidelines-macro-usage.CheckCapsOnly 56 | value: 'false' 57 | - key: readability-inconsistent-declaration-parameter-name.Strict 58 | value: 'false' 59 | - key: readability-suspicious-call-argument.DiceDissimilarBelow 60 | value: '60' 61 | - key: readability-suspicious-call-argument.Equality 62 | value: 'true' 63 | - key: misc-uniqueptr-reset-release.IncludeStyle 64 | value: llvm 65 | - key: bugprone-easily-swappable-parameters.QualifiersMix 66 | value: 'false' 67 | - key: bugprone-suspicious-string-compare.WarnOnImplicitComparison 68 | value: 'true' 69 | - key: bugprone-argument-comment.CommentNullPtrs 70 | value: '0' 71 | - key: cppcoreguidelines-narrowing-conversions.WarnOnFloatingPointNarrowingConversion 72 | value: 'true' 73 | - key: cppcoreguidelines-init-variables.IncludeStyle 74 | value: llvm 75 | - key: modernize-loop-convert.MakeReverseRangeHeader 76 | value: '' 77 | - key: readability-suspicious-call-argument.SuffixSimilarAbove 78 | value: '30' 79 | - key: misc-definitions-in-headers.HeaderFileExtensions 80 | value: ';h;hpp' 81 | - key: cppcoreguidelines-narrowing-conversions.WarnOnIntegerNarrowingConversion 82 | value: 'true' 83 | - key: bugprone-easily-swappable-parameters.IgnoredParameterNames 84 | value: '"";iterator;Iterator;begin;Begin;end;End;first;First;last;Last;lhs;LHS;rhs;RHS' 85 | - key: modernize-loop-convert.UseCxx20ReverseRanges 86 | value: 'true' 87 | - key: cppcoreguidelines-prefer-member-initializer.UseAssignment 88 | value: 'false' 89 | - key: performance-type-promotion-in-math-fn.IncludeStyle 90 | value: llvm 91 | - key: readability-function-cognitive-complexity.DescribeBasicIncrements 92 | value: 'true' 93 | - key: bugprone-suspicious-include.ImplementationFileExtensions 94 | value: 'c;cc;cpp;cxx' 95 | - key: modernize-loop-convert.MakeReverseRangeFunction 96 | value: '' 97 | - key: readability-inconsistent-declaration-parameter-name.IgnoreMacros 98 | value: 'true' 99 | - key: bugprone-suspicious-missing-comma.SizeThreshold 100 | value: '5' 101 | - key: readability-identifier-naming.IgnoreFailedSplit 102 | value: 'false' 103 | - key: readability-qualified-auto.AddConstToQualified 104 | value: 'true' 105 | - key: bugprone-sizeof-expression.WarnOnSizeOfThis 106 | value: 'true' 107 | - key: bugprone-string-constructor.WarnOnLargeLength 108 | value: 'true' 109 | - key: cppcoreguidelines-explicit-virtual-functions.OverrideSpelling 110 | value: override 111 | - key: readability-identifier-naming.NamespaceIgnoredRegexp 112 | value: '' 113 | - key: google-global-names-in-headers.HeaderFileExtensions 114 | value: ';h;hh;hpp;hxx' 115 | - key: readability-uppercase-literal-suffix.IgnoreMacros 116 | value: 'true' 117 | - key: modernize-make-shared.IgnoreMacros 118 | value: 'true' 119 | - key: bugprone-dynamic-static-initializers.HeaderFileExtensions 120 | value: ';h;hh;hpp;hxx' 121 | - key: bugprone-suspicious-enum-usage.StrictMode 122 | value: 'false' 123 | - key: performance-unnecessary-copy-initialization.AllowedTypes 124 | value: '' 125 | - key: bugprone-suspicious-missing-comma.MaxConcatenatedTokens 126 | value: '5' 127 | - key: modernize-use-transparent-functors.SafeMode 128 | value: 'false' 129 | - key: readability-suspicious-call-argument.Levenshtein 130 | value: 'true' 131 | - key: misc-throw-by-value-catch-by-reference.CheckThrowTemporaries 132 | value: 'true' 133 | - key: bugprone-not-null-terminated-result.WantToUseSafeFunctions 134 | value: 'true' 135 | - key: bugprone-string-constructor.LargeLengthThreshold 136 | value: '8388608' 137 | - key: readability-simplify-boolean-expr.ChainedConditionalAssignment 138 | value: 'false' 139 | - key: cert-oop54-cpp.WarnOnlyIfThisHasSuspiciousField 140 | value: 'false' 141 | - key: bugprone-exception-escape.FunctionsThatShouldNotThrow 142 | value: '' 143 | - key: performance-inefficient-vector-operation.EnableProto 144 | value: 'false' 145 | - key: modernize-make-shared.MakeSmartPtrFunction 146 | value: 'std::make_shared' 147 | - key: modernize-loop-convert.MaxCopySize 148 | value: '16' 149 | - key: readability-suspicious-call-argument.PrefixDissimilarBelow 150 | value: '25' 151 | - key: google-build-namespaces.HeaderFileExtensions 152 | value: ';h;hh;hpp;hxx' 153 | - key: bugprone-easily-swappable-parameters.MinimumLength 154 | value: '2' 155 | - key: cppcoreguidelines-pro-bounds-constant-array-index.GslHeader 156 | value: '' 157 | - key: readability-function-size.LineThreshold 158 | value: '4294967295' 159 | - key: modernize-make-shared.MakeSmartPtrFunctionHeader 160 | value: '' 161 | - key: modernize-use-override.IgnoreDestructors 162 | value: 'false' 163 | - key: bugprone-sizeof-expression.WarnOnSizeOfConstant 164 | value: 'true' 165 | - key: readability-redundant-string-init.StringNames 166 | value: '::std::basic_string_view;::std::basic_string' 167 | - key: modernize-make-unique.IgnoreDefaultInitialization 168 | value: 'true' 169 | - key: modernize-use-emplace.ContainersWithPushBack 170 | value: '::std::vector;::std::list;::std::deque' 171 | - key: modernize-make-unique.IncludeStyle 172 | value: llvm 173 | - key: modernize-use-override.OverrideSpelling 174 | value: override 175 | - key: readability-suspicious-call-argument.LevenshteinDissimilarBelow 176 | value: '50' 177 | - key: bugprone-argument-comment.CommentStringLiterals 178 | value: '0' 179 | - key: readability-identifier-naming.StructSuffix 180 | value: '' 181 | - key: google-readability-braces-around-statements.ShortStatementLines 182 | value: '1' 183 | - key: cppcoreguidelines-pro-type-member-init.IgnoreArrays 184 | value: 'false' 185 | - key: readability-else-after-return.WarnOnUnfixable 186 | value: 'true' 187 | - key: modernize-use-emplace.IgnoreImplicitConstructors 188 | value: 'false' 189 | - key: cppcoreguidelines-macro-usage.IgnoreCommandLineMacros 190 | value: 'true' 191 | - key: readability-suspicious-call-argument.Substring 192 | value: 'true' 193 | - key: modernize-use-equals-delete.IgnoreMacros 194 | value: 'true' 195 | - key: readability-identifier-naming.ClassIgnoredRegexp 196 | value: '' 197 | - key: cppcoreguidelines-pro-bounds-constant-array-index.IncludeStyle 198 | value: llvm 199 | - key: readability-suspicious-call-argument.Abbreviations 200 | value: 'arr=array;cnt=count;idx=index;src=source;stmt=statement;cpy=copy;dest=destination;dist=distancedst=distance;ptr=pointer;wdth=width;str=string;ln=line;srv=server;attr=attribute;ref=reference;buf=buffer;col=column;nr=number;vec=vector;len=length;elem=element;val=value;i=index;var=variable;hght=height;cl=client;num=number;pos=position;lst=list;addr=address' 201 | - key: bugprone-misplaced-widening-cast.CheckImplicitCasts 202 | value: 'false' 203 | - key: readability-uppercase-literal-suffix.NewSuffixes 204 | value: '' 205 | - key: modernize-loop-convert.MinConfidence 206 | value: reasonable 207 | - key: readability-uniqueptr-delete-release.PreferResetCall 208 | value: 'false' 209 | - key: misc-definitions-in-headers.UseHeaderFileExtension 210 | value: 'true' 211 | - key: misc-throw-by-value-catch-by-reference.MaxSize 212 | value: '-1' 213 | - key: google-readability-namespace-comments.SpacesBeforeComments 214 | value: '2' 215 | - key: bugprone-suspicious-missing-comma.RatioThreshold 216 | value: '0.200000' 217 | - key: cppcoreguidelines-no-malloc.Allocations 218 | value: '::malloc;::calloc' 219 | - key: bugprone-narrowing-conversions.IgnoreConversionFromTypes 220 | value: '' 221 | - key: readability-function-size.BranchThreshold 222 | value: '4294967295' 223 | - key: readability-implicit-bool-conversion.AllowIntegerConditions 224 | value: 'false' 225 | - key: readability-function-size.StatementThreshold 226 | value: '800' 227 | - key: readability-identifier-naming.IgnoreMainLikeFunctions 228 | value: 'false' 229 | - key: cppcoreguidelines-init-variables.MathHeader 230 | value: '' 231 | - key: google-runtime-int.SignedTypePrefix 232 | value: int 233 | - key: google-readability-function-size.StatementThreshold 234 | value: '800' 235 | - key: readability-identifier-naming.StructPrefix 236 | value: '' 237 | - key: readability-suspicious-call-argument.DiceSimilarAbove 238 | value: '70' 239 | - key: modernize-use-equals-default.IgnoreMacros 240 | value: 'true' 241 | - key: readability-suspicious-call-argument.Abbreviation 242 | value: 'true' 243 | - key: cppcoreguidelines-special-member-functions.AllowSoleDefaultDtor 244 | value: 'false' 245 | - key: readability-identifier-naming.NamespaceSuffix 246 | value: '' 247 | - key: modernize-use-emplace.SmartPointers 248 | value: '::std::shared_ptr;::std::unique_ptr;::std::auto_ptr;::std::weak_ptr' 249 | - key: cppcoreguidelines-no-malloc.Deallocations 250 | value: '::free' 251 | - key: bugprone-dangling-handle.HandleClasses 252 | value: 'std::basic_string_view;std::experimental::basic_string_view' 253 | - key: misc-unused-parameters.StrictMode 254 | value: 'false' 255 | - key: readability-suspicious-call-argument.JaroWinklerSimilarAbove 256 | value: '85' 257 | - key: readability-simplify-subscript-expr.Types 258 | value: '::std::basic_string;::std::basic_string_view;::std::vector;::std::array' 259 | - key: performance-unnecessary-copy-initialization.ExcludedContainerTypes 260 | value: '' 261 | - key: modernize-replace-auto-ptr.IncludeStyle 262 | value: llvm 263 | - key: performance-move-const-arg.CheckTriviallyCopyableMove 264 | value: 'true' 265 | - key: readability-static-accessed-through-instance.NameSpecifierNestingThreshold 266 | value: '3' 267 | - key: readability-function-size.VariableThreshold 268 | value: '4294967295' 269 | - key: cert-dcl16-c.NewSuffixes 270 | value: 'L;LL;LU;LLU' 271 | - key: bugprone-narrowing-conversions.WarnOnFloatingPointNarrowingConversion 272 | value: 'true' 273 | - key: readability-identifier-naming.GetConfigPerFile 274 | value: 'true' 275 | - key: modernize-use-default-member-init.UseAssignment 276 | value: 'false' 277 | - key: readability-function-size.NestingThreshold 278 | value: '4294967295' 279 | - key: modernize-use-override.AllowOverrideAndFinal 280 | value: 'false' 281 | - key: cppcoreguidelines-narrowing-conversions.IgnoreConversionFromTypes 282 | value: '' 283 | - key: readability-function-size.ParameterThreshold 284 | value: '4294967295' 285 | - key: modernize-pass-by-value.ValuesOnly 286 | value: 'false' 287 | - key: readability-function-cognitive-complexity.IgnoreMacros 288 | value: 'true' 289 | - key: modernize-loop-convert.IncludeStyle 290 | value: llvm 291 | - key: cert-str34-c.DiagnoseSignedUnsignedCharComparisons 292 | value: 'false' 293 | - key: bugprone-narrowing-conversions.WarnWithinTemplateInstantiation 294 | value: 'false' 295 | - key: readability-identifier-naming.StructIgnoredRegexp 296 | value: '' 297 | - key: bugprone-suspicious-string-compare.WarnOnLogicalNotComparison 298 | value: 'false' 299 | - key: readability-redundant-smartptr-get.IgnoreMacros 300 | value: 'true' 301 | - key: cppcoreguidelines-explicit-virtual-functions.AllowOverrideAndFinal 302 | value: 'false' 303 | - key: readability-identifier-naming.NamespacePrefix 304 | value: '' 305 | - key: readability-identifier-naming.AggressiveDependentMemberLookup 306 | value: 'false' 307 | - key: readability-identifier-naming.StructCase 308 | value: lower_case 309 | - key: modernize-use-emplace.TupleTypes 310 | value: '::std::pair;::std::tuple' 311 | - key: modernize-use-emplace.TupleMakeFunctions 312 | value: '::std::make_pair;::std::make_tuple' 313 | - key: cppcoreguidelines-owning-memory.LegacyResourceProducers 314 | value: '::malloc;::aligned_alloc;::realloc;::calloc;::fopen;::freopen;::tmpfile' 315 | - key: bugprone-easily-swappable-parameters.SuppressParametersUsedTogether 316 | value: 'true' 317 | - key: bugprone-argument-comment.StrictMode 318 | value: '0' 319 | - key: modernize-replace-random-shuffle.IncludeStyle 320 | value: llvm 321 | - key: modernize-use-bool-literals.IgnoreMacros 322 | value: 'true' 323 | - key: bugprone-easily-swappable-parameters.NamePrefixSuffixSilenceDissimilarityTreshold 324 | value: '1' 325 | - key: bugprone-unhandled-self-assignment.WarnOnlyIfThisHasSuspiciousField 326 | value: 'true' 327 | - key: google-readability-namespace-comments.ShortNamespaceLines 328 | value: '10' 329 | - key: readability-suspicious-call-argument.JaroWinklerDissimilarBelow 330 | value: '75' 331 | - key: bugprone-suspicious-string-compare.StringCompareLikeFunctions 332 | value: '' 333 | - key: modernize-avoid-bind.PermissiveParameterList 334 | value: 'false' 335 | - key: readability-suspicious-call-argument.Suffix 336 | value: 'true' 337 | - key: modernize-use-override.FinalSpelling 338 | value: final 339 | - key: modernize-use-noexcept.ReplacementString 340 | value: '' 341 | - key: modernize-use-using.IgnoreMacros 342 | value: 'true' 343 | - key: cppcoreguidelines-explicit-virtual-functions.FinalSpelling 344 | value: final 345 | - key: readability-suspicious-call-argument.MinimumIdentifierNameLength 346 | value: '3' 347 | - key: bugprone-narrowing-conversions.WarnOnIntegerNarrowingConversion 348 | value: 'true' 349 | - key: modernize-loop-convert.NamingStyle 350 | value: CamelCase 351 | - key: cppcoreguidelines-pro-type-member-init.UseAssignment 352 | value: 'false' 353 | - key: readability-identifier-naming.ClassSuffix 354 | value: '' 355 | - key: bugprone-suspicious-include.HeaderFileExtensions 356 | value: ';h;hh;hpp;hxx' 357 | - key: performance-no-automatic-move.AllowedTypes 358 | value: '' 359 | - key: readability-suspicious-call-argument.SubstringDissimilarBelow 360 | value: '40' 361 | - key: google-runtime-int.UnsignedTypePrefix 362 | value: uint 363 | - key: bugprone-argument-comment.CommentIntegerLiterals 364 | value: '0' 365 | - key: performance-for-range-copy.WarnOnAllAutoCopies 366 | value: 'false' 367 | - key: modernize-pass-by-value.IncludeStyle 368 | value: llvm 369 | - key: bugprone-argument-comment.CommentFloatLiterals 370 | value: '0' 371 | - key: bugprone-too-small-loop-variable.MagnitudeBitsUpperLimit 372 | value: '16' 373 | - key: readability-simplify-boolean-expr.ChainedConditionalReturn 374 | value: 'false' 375 | - key: readability-else-after-return.WarnOnConditionVariables 376 | value: 'true' 377 | - key: modernize-use-nullptr.NullMacros 378 | value: 'NULL' 379 | - key: readability-suspicious-call-argument.SuffixDissimilarBelow 380 | value: '25' 381 | - key: bugprone-argument-comment.CommentCharacterLiterals 382 | value: '0' 383 | - key: cppcoreguidelines-macro-usage.AllowedRegexp 384 | value: '^DEBUG_*' 385 | - key: readability-suspicious-call-argument.LevenshteinSimilarAbove 386 | value: '66' 387 | - key: readability-identifier-naming.NamespaceCase 388 | value: lower_case 389 | - key: cppcoreguidelines-narrowing-conversions.PedanticMode 390 | value: 'false' 391 | - key: modernize-make-shared.IgnoreDefaultInitialization 392 | value: 'true' 393 | - key: readability-suspicious-call-argument.JaroWinkler 394 | value: 'true' 395 | - key: bugprone-implicit-widening-of-multiplication-result.UseCXXHeadersInCppSources 396 | value: 'true' 397 | - key: modernize-make-shared.IncludeStyle 398 | value: llvm 399 | - key: readability-suspicious-call-argument.Prefix 400 | value: 'true' 401 | - key: cppcoreguidelines-special-member-functions.AllowMissingMoveFunctions 402 | value: 'false' 403 | - key: bugprone-implicit-widening-of-multiplication-result.UseCXXStaticCastsInCppSources 404 | value: 'true' 405 | - key: bugprone-signed-char-misuse.CharTypdefsToIgnore 406 | value: '' 407 | - key: cppcoreguidelines-explicit-virtual-functions.IgnoreDestructors 408 | value: 'true' 409 | - key: modernize-make-unique.IgnoreMacros 410 | value: 'true' 411 | - key: performance-for-range-copy.AllowedTypes 412 | value: '' 413 | - key: readability-identifier-naming.ClassPrefix 414 | value: '' 415 | - key: bugprone-argument-comment.CommentBoolLiterals 416 | value: '0' 417 | - key: readability-braces-around-statements.ShortStatementLines 418 | value: '0' 419 | - key: readability-identifier-naming.ClassCase 420 | value: lower_case 421 | - key: bugprone-argument-comment.CommentUserDefinedLiterals 422 | value: '0' 423 | - key: performance-inefficient-string-concatenation.StrictMode 424 | value: 'false' 425 | - key: readability-redundant-declaration.IgnoreMacros 426 | value: 'true' 427 | - key: bugprone-easily-swappable-parameters.IgnoredParameterTypeSuffixes 428 | value: 'bool;Bool;_Bool;it;It;iterator;Iterator;inputit;InputIt;forwardit;FowardIt;bidirit;BidirIt;constiterator;const_iterator;Const_Iterator;Constiterator;ConstIterator;RandomIt;randomit;random_iterator;ReverseIt;reverse_iterator;reverse_const_iterator;ConstReverseIterator;Const_Reverse_Iterator;const_reverse_iterator;Constreverseiterator;constreverseiterator' 429 | - key: modernize-make-unique.MakeSmartPtrFunction 430 | value: 'std::make_unique' 431 | - key: google-runtime-int.TypeSuffix 432 | value: '' 433 | - key: readability-implicit-bool-conversion.AllowPointerConditions 434 | value: 'false' 435 | - key: modernize-make-unique.MakeSmartPtrFunctionHeader 436 | value: '' 437 | - key: bugprone-signal-handler.AsyncSafeFunctionSet 438 | value: POSIX 439 | - key: bugprone-easily-swappable-parameters.ModelImplicitConversions 440 | value: 'true' 441 | - key: readability-suspicious-call-argument.SubstringSimilarAbove 442 | value: '50' 443 | - key: cppcoreguidelines-narrowing-conversions.WarnWithinTemplateInstantiation 444 | value: 'false' 445 | - key: cppcoreguidelines-narrowing-conversions.WarnOnEquivalentBitWidth 446 | value: 'true' 447 | - key: cppcoreguidelines-special-member-functions.AllowMissingMoveFunctionsWhenCopyIsDeleted 448 | value: 'false' 449 | - key: modernize-use-noexcept.UseNoexceptFalse 450 | value: 'true' 451 | - key: readability-function-cognitive-complexity.Threshold 452 | value: '100' 453 | - key: cppcoreguidelines-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic 454 | value: 'true' 455 | - key: bugprone-argument-comment.IgnoreSingleArgument 456 | value: '0' 457 | - key: bugprone-narrowing-conversions.WarnOnEquivalentBitWidth 458 | value: 'true' 459 | - key: bugprone-sizeof-expression.WarnOnSizeOfIntegerExpression 460 | value: 'false' 461 | - key: performance-faster-string-find.StringLikeClasses 462 | value: '::std::basic_string;::std::basic_string_view' 463 | - key: bugprone-assert-side-effect.CheckFunctionCalls 464 | value: 'false' 465 | - key: bugprone-string-constructor.StringNames 466 | value: '::std::basic_string;::std::basic_string_view' 467 | - key: bugprone-assert-side-effect.AssertMacros 468 | value: assert,NSAssert,NSCAssert 469 | - key: bugprone-exception-escape.IgnoredExceptions 470 | value: '' 471 | - key: bugprone-signed-char-misuse.DiagnoseSignedUnsignedCharComparisons 472 | value: 'true' 473 | - key: modernize-use-default-member-init.IgnoreMacros 474 | value: 'true' 475 | - key: llvm-qualified-auto.AddConstToQualified 476 | value: 'false' 477 | - key: llvm-else-after-return.WarnOnConditionVariables 478 | value: 'false' 479 | - key: bugprone-sizeof-expression.WarnOnSizeOfCompareToConstant 480 | value: 'true' 481 | - key: modernize-raw-string-literal.DelimiterStem 482 | value: lit 483 | - key: readability-suspicious-call-argument.Dice 484 | value: 'true' 485 | - key: misc-throw-by-value-catch-by-reference.WarnOnLargeObjects 486 | value: 'false' 487 | - key: modernize-raw-string-literal.ReplaceShorterLiterals 488 | value: 'false' 489 | - key: performance-inefficient-vector-operation.VectorLikeClasses 490 | value: '::std::vector' 491 | - key: modernize-use-auto.RemoveStars 492 | value: 'false' 493 | - key: bugprone-implicit-widening-of-multiplication-result.IncludeStyle 494 | value: llvm 495 | - key: readability-redundant-member-init.IgnoreBaseInCopyConstructors 496 | value: 'false' 497 | - key: modernize-replace-disallow-copy-and-assign-macro.MacroName 498 | value: DISALLOW_COPY_AND_ASSIGN 499 | - key: llvm-else-after-return.WarnOnUnfixable 500 | value: 'false' -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | use flake -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/** 2 | subprojects/*/** 3 | .cache/** 4 | .direnv/** -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Carmine Margiotta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Compile time finite state machine 2 | 3 | [![CodeFactor](https://img.shields.io/codefactor/grade/github/cmargiotta/compile-time-fsm?style=for-the-badge)](https://www.codefactor.io/repository/github/cmargiotta/compile-time-fsm) 4 | ![Alla pugna!](https://img.shields.io/badge/ALLA-PUGNA-F70808?style=for-the-badge) 5 | 6 | The `fsm` provides an easy way to manage finite state machines, almost without overhead. 7 | 8 | This library requires C++20 and is based on meson build system. 9 | 10 | To run unit tests: 11 | ```bash 12 | $ meson build 13 | $ ninja -C build test 14 | ``` 15 | 16 | To generate the single-include header, [quom](https://github.com/Viatorus/quom) is required: 17 | ```bash 18 | $ source scripts/amalgamate.sh 19 | ``` 20 | 21 | ## Usage 22 | ### Declaration 23 | 24 | Every state and every handled event is simply a type, with only two mandatory requirements: 25 | - a state **must** provide a public alias, `transitions`, that is a `ctfsm::type_map` of `event`s and their relative target states, basically the edges list of a graph, events must be unique; 26 | - a state **must** be defaultly constructible. 27 | 28 | And can, **optionally**: 29 | - expose a `void on_enter()` function, that will be called every time the fsm enters in this state; optionally it can receive the event instance: `void on_enter(event& e)`; 30 | - expose a `void on_exit()` function, that will be called every time the fsm exits from this state, optionally it can receive the event instance: `void on_exit(event& e)`; 31 | 32 | ```cpp 33 | struct on; 34 | struct off; 35 | 36 | struct switch_toggle {}; 37 | struct blackout {} 38 | 39 | struct on 40 | { 41 | using transitions = ctfsm::type_map< 42 | std::pair, 43 | std::pair 44 | >; 45 | 46 | void on_exit(blackout& event) 47 | { 48 | ... 49 | } 50 | 51 | void on_exit() 52 | { 53 | ... 54 | } 55 | }; 56 | 57 | struct off 58 | { 59 | using transitions = ctfsm::type_map< 60 | std::pair 61 | >; 62 | 63 | void on_enter() 64 | { 65 | ... 66 | } 67 | }; 68 | ``` 69 | 70 | In this case: 71 | - `on` handles the event triggered when changing state from `on` with the `blackout` event with a different handler than other exit events; 72 | - `off` handles every `on_enter` event; 73 | 74 | While `switch_toggle` and `blackout` are valid events, it can be useful to include data and event handlers in them: 75 | 76 | ```cpp 77 | struct switch_toggle 78 | { 79 | int data1; 80 | std::string data2; 81 | 82 | void on_transit() 83 | { 84 | ... 85 | } 86 | }; 87 | ``` 88 | 89 | `switch_toggle` provides a payload that will be accessible to states in their event handlers. The on_transit event handler will be triggered every time the state machine handles an event of this type. 90 | 91 | Finally, to build an `fsm` from these states, doing: 92 | 93 | ```cpp 94 | ctfsm::fsm state_machine; 95 | ``` 96 | 97 | is enough, all reachable states will be deduced from the provided initial state and `on` will be the starting state. 98 | 99 | ### Lifetimes 100 | 101 | Every state is default-constructed when the fsm is constructed. 102 | Their duration is exactly the same of the fsm that owns them. 103 | 104 | ### Handling events 105 | 106 | Given an event instance, 107 | 108 | ```cpp 109 | event t; 110 | ``` 111 | 112 | to trigger a state change in the fsm: 113 | 114 | ```cpp 115 | state_machine.handle_event(t); 116 | ``` 117 | 118 | or 119 | 120 | ```cpp 121 | state_machine(t); 122 | ``` 123 | 124 | This call will trigger, in this order: 125 | 126 | 1. `current_state.on_exit(event&)` if available, `.on_exit()` otherwise; 127 | 2. `t.on_transit()` if available; 128 | 3. `next_state.on_enter(event&)` if available, `.on_enter()` otherwise; 129 | 130 | If the event is default initializable, it is possible to: 131 | 132 | ```cpp 133 | state_machine.handle_event(); 134 | ``` 135 | 136 | ### Talking with state instances 137 | 138 | Sometimes, it is necessary to invoke a method on the current state to update its data members or apply specific application logic. The invoke_on_current function is provided to facilitate this type of operation. 139 | 140 | ```cpp 141 | fsm.invoke_on_current([](auto& current_state, auto& fsm) { 142 | current_state.work(fsm); 143 | }); 144 | ``` 145 | 146 | This example implies that every state of `fsm` provides a function `.work` that takes a reference to the fsm itself. 147 | This allows to update the fsm state inside an `invoke_on_current` execution. The operation sequence in this case is: 148 | 1. `invoke_on_current` is invoked; 149 | 2. inside it, an `fsm.handle_event` is triggered; 150 | 3. `on_exit` of the current state is invoked, if present; 151 | 4. `on_transit` of the event is invoked, if present; 152 | 5. `on_enter` of the next state is invoked, if present; 153 | 6. `invoke_on_current` execution continues. 154 | 155 | ## API documentation 156 | 157 | ### FSM 158 | | Function | Description | 159 | |--- |--- | 160 | | `constexpr bool handle_event(event)` | *This function is enabled only if exceptions are disabled with `-fno-exceptions` flag*
The event is delivered to states and the current state is updated if a valid transaction for this event is found. If this event cannot be handled by the current state, the function returns `false`.| 161 | | `constexpr void handle_event(event)` | *This function is enabled only if exceptions are enabled*
The event is delivered to states and the current state is updated if a valid transaction for this event is found. If this event cannot be handled by the current state, an `std::runtime_error` is thrown.| 162 | | `constexpr bool handle_event()` | *This function is enabled only if exceptions are disabled with `-fno-exceptions` flag*
The event of type `event_t` is default-constructed and delivered to states and the current state is updated if a valid transaction for this event is found. If this event cannot be handled by the current state, the function returns `false`.| 163 | | `constexpr void handle_event()` | *This function is enabled only if exceptions are enabled*
The event of type `event_t` is default-constructed and delivered to states and the current state is updated if a valid transaction for this event is found. If this event cannot be handled by the current state, an `std::runtime_error` is thrown.| 164 | | `constexpr const id_type& get_current_state_id() const noexcept` | The ID of the current state is returned: if every state has a member named `id` of the same type, the `id` of the current state is returned, otherwise `typeid(current_state_t).name()` is returned. | 165 | | `constexpr bool is_current_state() const noexcept` | Returns `true` if `T` is the type of the current state. | 166 | | `constexpr auto invoke_on_current(auto lambda) noexcept` | Lambda must be invocable with a reference to the current state instance and the fsm itself. The value returned by lambda is forwarded to the caller. | 167 | 168 | ### State 169 | - A state class must provide a `transitions` alias, a `ctfs::type_map` composed by `std::pair`s where the first type is an event and the second type is the target state after that event is triggered. If no `transitions` alias is provided, the state will be a sink state. 170 | - Each state can provide a publicly accessible `id` field. The library will use this field only if every state of the FSM provides an `id` field of the same type. 171 | - A state can handle `exit` events, which are triggered when the FSM changes state while it is the current state. An unlimited number of `void on_exit(E& event)` overloads and a `void on_exit()` method can be provided. The no-argument handler will be invoked only for events that do not have a specific overload. A state without `exit` handlers is also completely valid. 172 | - A state can handle `enter` events, which are triggered when the FSM changes state while it is the target state. An unlimited number of `void on_enter(E& event)` overloads and a `void on_enter()` method can be provided. The no-argument handler will be invoked only for events that do not have a specific overload. A state without `enter` handlers is also completely valid. 173 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1694529238, 9 | "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "flake-utils_2": { 22 | "locked": { 23 | "lastModified": 1642700792, 24 | "narHash": "sha256-XqHrk7hFb+zBvRg6Ghl+AZDq03ov6OshJLiSWOoX5es=", 25 | "owner": "numtide", 26 | "repo": "flake-utils", 27 | "rev": "846b2ae0fc4cc943637d3d1def4454213e203cba", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "numtide", 32 | "repo": "flake-utils", 33 | "type": "github" 34 | } 35 | }, 36 | "mach-nix": { 37 | "inputs": { 38 | "flake-utils": "flake-utils_2", 39 | "nixpkgs": "nixpkgs", 40 | "pypi-deps-db": "pypi-deps-db" 41 | }, 42 | "locked": { 43 | "lastModified": 1654084003, 44 | "narHash": "sha256-j/XrVVistvM+Ua+0tNFvO5z83isL+LBgmBi9XppxuKA=", 45 | "owner": "DavHau", 46 | "repo": "mach-nix", 47 | "rev": "7e14360bde07dcae32e5e24f366c83272f52923f", 48 | "type": "github" 49 | }, 50 | "original": { 51 | "id": "mach-nix", 52 | "ref": "3.5.0", 53 | "type": "indirect" 54 | } 55 | }, 56 | "nixpkgs": { 57 | "locked": { 58 | "lastModified": 1643805626, 59 | "narHash": "sha256-AXLDVMG+UaAGsGSpOtQHPIKB+IZ0KSd9WS77aanGzgc=", 60 | "owner": "NixOS", 61 | "repo": "nixpkgs", 62 | "rev": "554d2d8aa25b6e583575459c297ec23750adb6cb", 63 | "type": "github" 64 | }, 65 | "original": { 66 | "id": "nixpkgs", 67 | "ref": "nixos-unstable", 68 | "type": "indirect" 69 | } 70 | }, 71 | "nixpkgs_2": { 72 | "locked": { 73 | "lastModified": 1698134075, 74 | "narHash": "sha256-foCD+nuKzfh49bIoiCBur4+Fx1nozo+4C/6k8BYk4sg=", 75 | "owner": "NixOS", 76 | "repo": "nixpkgs", 77 | "rev": "8efd5d1e283604f75a808a20e6cde0ef313d07d4", 78 | "type": "github" 79 | }, 80 | "original": { 81 | "id": "nixpkgs", 82 | "ref": "nixos-unstable", 83 | "type": "indirect" 84 | } 85 | }, 86 | "pypi-deps-db": { 87 | "flake": false, 88 | "locked": { 89 | "lastModified": 1643877077, 90 | "narHash": "sha256-jv8pIvRFTP919GybOxXE5TfOkrjTbdo9QiCO1TD3ZaY=", 91 | "owner": "DavHau", 92 | "repo": "pypi-deps-db", 93 | "rev": "da53397f0b782b0b18deb72ef8e0fb5aa7c98aa3", 94 | "type": "github" 95 | }, 96 | "original": { 97 | "owner": "DavHau", 98 | "repo": "pypi-deps-db", 99 | "type": "github" 100 | } 101 | }, 102 | "root": { 103 | "inputs": { 104 | "flake-utils": "flake-utils", 105 | "mach-nix": "mach-nix", 106 | "nixpkgs": "nixpkgs_2" 107 | } 108 | }, 109 | "systems": { 110 | "locked": { 111 | "lastModified": 1681028828, 112 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 113 | "owner": "nix-systems", 114 | "repo": "default", 115 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 116 | "type": "github" 117 | }, 118 | "original": { 119 | "owner": "nix-systems", 120 | "repo": "default", 121 | "type": "github" 122 | } 123 | } 124 | }, 125 | "root": "root", 126 | "version": 7 127 | } 128 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | nixpkgs.url = "nixpkgs/nixos-unstable"; 4 | flake-utils.url = "github:numtide/flake-utils"; 5 | mach-nix.url = "mach-nix/3.5.0"; 6 | }; 7 | outputs = { self, nixpkgs, flake-utils, mach-nix }: 8 | flake-utils.lib.eachDefaultSystem (system: 9 | let 10 | pkgs = import nixpkgs { 11 | inherit system; 12 | overlays = [ ]; 13 | config.allowUnfree = true; 14 | }; 15 | in 16 | rec { 17 | nixConfig.sandbox = "relaxed"; 18 | devShell = pkgs.mkShell { 19 | nativeBuildInputs = with pkgs; [ pkg-config ]; 20 | 21 | buildInputs = 22 | with pkgs; 23 | [ 24 | meson 25 | ninja 26 | gcc13 27 | clang-tools_16 28 | 29 | ( 30 | mach-nix.lib."${system}".mkPython { 31 | requirements = '' 32 | quom 33 | ''; 34 | } 35 | ) 36 | ]; 37 | }; 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('ctfsm', 'cpp', default_options: ['cpp_std=c++20']) 2 | 3 | if get_option('buildtype') == 'debug' 4 | add_project_arguments('-O0', '-g3', '-Wall', language : 'cpp') 5 | else 6 | add_project_arguments('-O3', '-Wall', language : 'cpp') 7 | endif 8 | 9 | cpp = meson.get_compiler('cpp') 10 | 11 | subdir('src') 12 | subdir('test') 13 | 14 | quom = find_program('quom', required: false) 15 | 16 | if quom.found() 17 | quom_output = run_command(quom, '-I', meson.project_source_root() + '/src/', meson.project_source_root() + '/src/fsm/fsm.hpp', meson.project_build_root() + '/single-include/fsm.hpp', capture: true) 18 | 19 | if quom_output.returncode() != 0 20 | warning('Cannot build the single include header:') 21 | warning(quom_output.stderr()) 22 | endif 23 | endif 24 | -------------------------------------------------------------------------------- /scripts/amalgamate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if command -v quom &> /dev/null 4 | then 5 | echo "Amalgamating headers" 6 | quom src/fsm/fsm.hpp -I src/ ./single-include/fsm.hpp 7 | exit 8 | fi 9 | 10 | echo "quom not found, cannot amalgamate headers." -------------------------------------------------------------------------------- /single-include/fsm.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file fsm.hpp 3 | * @author Carmine Margiotta (cmargiotta@posteo.net) 4 | * 5 | * @copyright Copyright (c) 2022 6 | */ 7 | 8 | #ifndef FSM_HPP 9 | #define FSM_HPP 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | /** 18 | * @file existence_verifier.hpp 19 | * @author Carmine Margiotta (cmargiotta@posteo.net) 20 | * 21 | * @copyright Copyright (c) 2022 22 | */ 23 | 24 | #ifndef UTILITY_EXISTENCE_VERIFIER_HPP 25 | #define UTILITY_EXISTENCE_VERIFIER_HPP 26 | 27 | #include 28 | 29 | /** 30 | * @brief Build a concept that verifies the existence of the given member. 31 | */ 32 | #define MAKE_EXISTENCE_VERIFIER(member) \ 33 | namespace ctfsm \ 34 | { \ 35 | template \ 36 | concept has_##member = requires(T instance) { std::declval().member; }; \ 37 | \ 38 | template \ 39 | concept has_##member##_method = requires(T instance, args... arguments) { \ 40 | { \ 41 | instance.member(arguments...) \ 42 | } -> std::same_as; \ 43 | } || (sizeof...(args) == 0 && requires(T instance) { \ 44 | { \ 45 | instance.member() \ 46 | } -> std::same_as; \ 47 | }); \ 48 | } 49 | 50 | #endif// UTILITY_EXISTENCE_VERIFIER_HPP 51 | 52 | /** 53 | * @file type_map.hpp 54 | * @author Carmine Margiotta (cmargiotta@posteo.net) 55 | * 56 | * @copyright Copyright (c) 2022 57 | */ 58 | 59 | #ifndef TYPE_TRAITS_TYPE_MAP_HPP 60 | #define TYPE_TRAITS_TYPE_MAP_HPP 61 | 62 | #include 63 | #include 64 | 65 | /** 66 | * @file type_set.hpp 67 | * @author Carmine Margiotta (cmargiotta@posteo.net) 68 | * 69 | * @copyright Copyright (c) 2022 70 | */ 71 | 72 | #ifndef UTILITY_TYPE_SET_HPP 73 | #define UTILITY_TYPE_SET_HPP 74 | 75 | #include 76 | #include 77 | #include 78 | 79 | namespace ctfsm 80 | { 81 | /** 82 | * @brief Checks if the Element type is in the given std::tuple 83 | * 84 | * @tparam element 85 | * @tparam containers 86 | */ 87 | template 88 | struct contains : contains 89 | { 90 | }; 91 | 92 | template 93 | struct contains> : std::disjunction...> 94 | { 95 | }; 96 | 97 | template::value> 98 | struct type_set_insert; 99 | 100 | template 101 | struct type_set_insert, true> 102 | { 103 | // Element is already in Set_, nothing to do, Delta is empty 104 | public: 105 | using set = std::tuple; 106 | using delta = std::tuple<>; 107 | }; 108 | 109 | template 110 | struct type_set_insert, false> 111 | { 112 | // Element is not in set_, append it to the set and report it in Delta 113 | public: 114 | using set = std::tuple; 115 | using delta = std::tuple; 116 | }; 117 | 118 | /** 119 | * @brief Given an std::tuple and a type set, type_set_merge<...>::Set will be the given set 120 | * with appended types from the tuple. Sorting is not guaranteed, unicity is guaranteed. 121 | * 122 | * @tparam elements_tuple 123 | * @tparam base_set the set to be updated 124 | */ 125 | template 126 | struct type_set_merge; 127 | 128 | template 129 | struct type_set_merge, std::tuple> 130 | { 131 | private: 132 | // Recursive call (until elements is not empty) 133 | using tail_set = type_set_merge, std::tuple>; 134 | // type_set_insert call 135 | using current = type_set_insert; 136 | 137 | public: 138 | using set = typename current::set; 139 | using delta = decltype(std::tuple_cat(std::declval(), 140 | std::declval())); 141 | }; 142 | 143 | template 144 | struct type_set_merge, std::tuple> 145 | { 146 | // Base case, no more elements to insert, nothing to do 147 | public: 148 | using set = std::tuple; 149 | using delta = std::tuple<>; 150 | }; 151 | 152 | /** 153 | * @brief Build a type_set from the given parameter pack of types, removing repetitions 154 | * 155 | * @tparam elements 156 | */ 157 | template 158 | struct type_set : type_set_merge, std::tuple<>> 159 | { 160 | }; 161 | 162 | /** 163 | * @brief Build a type_set from the given std::tuple, removing repetitions 164 | * 165 | * @tparam elements 166 | */ 167 | template 168 | struct type_set> : type_set 169 | { 170 | }; 171 | 172 | /** 173 | * @brief Obtain the nth element of the given type set 174 | * 175 | * @tparam n 176 | * @tparam set 177 | */ 178 | template 179 | using nth_type_set_element = std::tuple_element; 180 | 181 | template 182 | struct type_set_find_element_helper; 183 | 184 | template 185 | struct type_set_find_element_helper, index_> 186 | { 187 | // Exploring the types list 188 | public: 189 | static constexpr std::size_t index 190 | = type_set_find_element_helper, index_ + 1>::index; 191 | }; 192 | 193 | template 194 | struct type_set_find_element_helper, index_> 195 | { 196 | // Element found, exposing index 197 | public: 198 | static constexpr std::size_t index = index_; 199 | }; 200 | 201 | /** 202 | * @brief Find an element in the given set and expose its index 203 | * 204 | * @tparam element 205 | * @tparam set 206 | */ 207 | template 208 | using type_set_find_element = type_set_find_element_helper; 209 | }// namespace ctfsm 210 | 211 | #endif// UTILITY_TYPE_SET_HPP 212 | 213 | namespace ctfsm 214 | { 215 | /** 216 | * @brief Extract an std::tuple of keys from the given type map 217 | * 218 | * @tparam map 219 | */ 220 | template 221 | struct extract_keys; 222 | 223 | /** 224 | * @brief Extract an std::tuple of values from the given type map 225 | * 226 | * @tparam map 227 | */ 228 | template 229 | struct extract_values; 230 | 231 | /** 232 | * @brief An std::tuple of std::pair is considered a type_map, where the pair is composed of 233 | * 234 | * 235 | * @tparam data 236 | */ 237 | template 238 | struct type_map; 239 | 240 | /** 241 | * @brief Find the value corresponding to the given Key, if it does not exist void is exposed 242 | * 243 | * @tparam key 244 | * @tparam map 245 | */ 246 | template 247 | struct find_by_key; 248 | 249 | template 250 | struct find_by_key, data...>> 251 | : find_by_key> 252 | { 253 | // Key has not been found, keep searching it 254 | }; 255 | 256 | template 257 | struct find_by_key> 258 | { 259 | // Key has not been found, expose void 260 | public: 261 | using result = void; 262 | }; 263 | 264 | template 265 | struct find_by_key, data...>> 266 | { 267 | // Key has been found, expose the result 268 | public: 269 | using result = value; 270 | }; 271 | 272 | template 273 | struct extract_values, data...>> 274 | { 275 | public: 276 | using values = decltype(std::tuple_cat( 277 | std::declval>(), 278 | std::declval>::values>())); 279 | }; 280 | 281 | template<> 282 | struct extract_values> 283 | { 284 | // Base case, all values have been extracted, nothing to do 285 | public: 286 | using values = std::tuple<>; 287 | }; 288 | 289 | template 290 | struct extract_keys, data...>> 291 | { 292 | // Iterating through the Map and composing the Keys tuple 293 | public: 294 | using keys = decltype(std::tuple_cat( 295 | std::declval>(), 296 | std::declval>::keys>())); 297 | }; 298 | 299 | template<> 300 | struct extract_keys> 301 | { 302 | // Base case, all keys have been extracted, nothing to do 303 | public: 304 | using keys = std::tuple<>; 305 | }; 306 | 307 | template 308 | struct type_map, data_...> 309 | { 310 | private: 311 | using type = type_map, data_...>; 312 | 313 | public: 314 | using data = std::tuple, data_...>; 315 | 316 | static constexpr bool valid 317 | = (std::tuple_size::keys>::set>::value 318 | == std::tuple_size::value); 319 | }; 320 | 321 | template<> 322 | struct type_map<> 323 | { 324 | public: 325 | using data = std::tuple<>; 326 | 327 | static constexpr bool valid = true; 328 | }; 329 | }// namespace ctfsm 330 | 331 | #endif// TYPE_TRAITS_TYPE_MAP_HPP 332 | 333 | MAKE_EXISTENCE_VERIFIER(on_enter) 334 | MAKE_EXISTENCE_VERIFIER(on_exit) 335 | MAKE_EXISTENCE_VERIFIER(on_transit) 336 | MAKE_EXISTENCE_VERIFIER(id) 337 | 338 | #ifdef __EXCEPTIONS 339 | # define HANDLE_EVENT_RETURN_TYPE void 340 | #else 341 | # define HANDLE_EVENT_RETURN_TYPE bool 342 | #endif 343 | 344 | namespace ctfsm 345 | { 346 | /** 347 | * @brief A finite state machine, it can only handle a specific designed set of states. Only the 348 | * initial state has to be provided. 349 | * 350 | * @tparam state 351 | */ 352 | template 353 | class fsm 354 | { 355 | private: 356 | template> 357 | struct state_expander; 358 | 359 | template 360 | struct state_expander, std::tuple> 361 | { 362 | // Base case, there are no more nodes to expand. This handles eventual cycles in 363 | // the graph too. 364 | public: 365 | using states = std::tuple; 366 | }; 367 | 368 | template 369 | struct state_expander, set> 370 | { 371 | // Expand a single state and every reachable state from that 372 | public: 373 | // Adding State and the reachable nodes from it to set 374 | using state_set = ctfsm::type_set_merge< 375 | typename ctfsm::extract_values::values, 376 | typename ctfsm::type_set_merge, set>::set>; 377 | 378 | // Expanding the nodes that were not already in set 379 | using states = 380 | typename state_expander::states; 381 | }; 382 | 383 | template 384 | struct state_expander, set> 385 | { 386 | // Iterate through the states list and expand every state 387 | private: 388 | using state_set = ctfsm::type_set_merge< 389 | typename ctfsm::extract_values::values, 390 | typename ctfsm::type_set_merge, set>::set>; 391 | 392 | public: 393 | using states = typename ctfsm::type_set_merge< 394 | typename state_expander, set>::states, 395 | typename state_expander, set>::states>::set; 396 | }; 397 | 398 | template 399 | struct variant_builder; 400 | 401 | template 402 | struct variant_builder> 403 | { 404 | public: 405 | using variant = std::variant; 406 | }; 407 | 408 | template 409 | using variant_builder_t = typename variant_builder::variant; 410 | 411 | template 412 | struct id_types_verifier; 413 | 414 | template 415 | struct id_types_verifier> 416 | { 417 | public: 418 | static_assert( 419 | std::conjunction_v...>, 420 | "Every state ID is of the same type"); 421 | 422 | using type = typename std::remove_cv::type; 423 | }; 424 | 425 | template 426 | struct id_types_verifier> 427 | { 428 | public: 429 | using type = decltype(typeid(state).name()); 430 | }; 431 | 432 | template 433 | struct id_extractor 434 | { 435 | static const inline auto id = typeid(state).name(); 436 | }; 437 | 438 | template 439 | struct id_extractor 440 | { 441 | static constexpr auto id = state::id; 442 | }; 443 | 444 | public: 445 | using states = typename state_expander>::states; 446 | using id_type = typename id_types_verifier::type; 447 | 448 | states _states; 449 | variant_builder_t _current_state; 450 | const id_type* _current_state_id; 451 | 452 | private: 453 | template 454 | constexpr void invoke_on_exit(current_state& current, _event& event) noexcept 455 | { 456 | if constexpr (ctfsm::has_on_exit_method>) 457 | { 458 | current.on_exit(event); 459 | } 460 | else if constexpr (ctfsm::has_on_exit_method) 461 | { 462 | current.on_exit(); 463 | } 464 | } 465 | 466 | template 467 | constexpr void invoke_on_enter(current_state& current, _event& event) noexcept 468 | { 469 | if constexpr (ctfsm::has_on_enter_method>) 470 | { 471 | current.on_enter(event); 472 | } 473 | else if constexpr (ctfsm::has_on_enter_method) 474 | { 475 | current.on_enter(); 476 | } 477 | } 478 | 479 | template 480 | constexpr void invoke_on_transit(_event& event) noexcept 481 | { 482 | if constexpr (!has_on_transit_method<_event, void>) 483 | { 484 | return; 485 | } 486 | else 487 | { 488 | event.on_transit(); 489 | } 490 | } 491 | 492 | template 493 | constexpr HANDLE_EVENT_RETURN_TYPE handle_event_(auto&) 494 | requires(std::is_same_v) 495 | { 496 | #ifdef __EXCEPTIONS 497 | throw std::runtime_error("Unhandled transation"); 498 | #else 499 | return false; 500 | #endif 501 | } 502 | 503 | template 504 | constexpr HANDLE_EVENT_RETURN_TYPE handle_event_(auto& event) noexcept 505 | { 506 | invoke_on_exit(std::get(_states), event); 507 | invoke_on_transit(event); 508 | 509 | _current_state = &std::get(_states); 510 | _current_state_id = &id_extractor::id; 511 | 512 | invoke_on_enter(std::get(_states), event); 513 | 514 | #ifndef __EXCEPTIONS 515 | return true; 516 | #endif 517 | } 518 | 519 | public: 520 | /** 521 | * @brief Default constructor, defaulty constructs every state and sets the initial 522 | * state as current 523 | */ 524 | constexpr fsm() noexcept 525 | : _current_state(&std::get(_states)), 526 | _current_state_id(&id_extractor::id) 527 | { 528 | } 529 | 530 | /** 531 | * @brief Reset the finite state machine */ 532 | constexpr auto reset() noexcept -> void 533 | { 534 | _current_state = &std::get(_states); 535 | _current_state_id = &id_extractor::id; 536 | } 537 | 538 | /** 539 | * @brief Handles an incoming event, as mapped in current_state::transitions 540 | * 541 | * @param event 542 | */ 543 | #ifndef __EXCEPTIONS 544 | [[nodiscard]] 545 | #endif 546 | constexpr HANDLE_EVENT_RETURN_TYPE 547 | handle_event(auto& event) 548 | { 549 | return std::visit( 550 | [this, &event](auto* current) 551 | { 552 | using current_state = std::remove_pointer_t; 553 | using target_state = 554 | typename find_by_key, 555 | typename current_state::transitions>::result; 556 | 557 | static_assert(current_state::transitions::valid, 558 | "transitions events must be unique"); 559 | 560 | return this->handle_event_(event); 561 | }, 562 | _current_state); 563 | } 564 | 565 | constexpr HANDLE_EVENT_RETURN_TYPE handle_event(auto&& event) 566 | { 567 | return handle_event(event); 568 | } 569 | 570 | /** 571 | * @brief Construct an empty event and handle it 572 | * 573 | * @tparam event 574 | */ 575 | template 576 | #ifndef __EXCEPTIONS 577 | [[nodiscard]] 578 | #endif 579 | constexpr HANDLE_EVENT_RETURN_TYPE 580 | handle_event() 581 | { 582 | return handle_event(event {}); 583 | } 584 | 585 | /** 586 | * @brief FSM can also be directly invoked with an event 587 | * 588 | * @param event 589 | */ 590 | constexpr void operator()(auto&& event) 591 | { 592 | handle_event(event); 593 | } 594 | 595 | /** 596 | * @brief Get current state id 597 | * 598 | * @return constexpr const id_type& 599 | */ 600 | constexpr const id_type& get_current_state_id() const noexcept 601 | { 602 | return *_current_state_id; 603 | } 604 | 605 | /** 606 | * @brief Returns true if T is the current state 607 | */ 608 | template 609 | constexpr bool is_current_state() const noexcept 610 | { 611 | return std::holds_alternative(_current_state); 612 | } 613 | 614 | /** 615 | * @brief Invoke the given lambda passing a reference to the current state 616 | * 617 | * @param lambda 618 | * @return the lambda return value 619 | */ 620 | constexpr auto invoke_on_current(auto lambda) noexcept 621 | { 622 | return std::visit([this, lambda](auto current) { return lambda(*current, *this); }, 623 | _current_state); 624 | } 625 | }; 626 | }// namespace ctfsm 627 | 628 | #endif// FSM_HPP 629 | -------------------------------------------------------------------------------- /src/fsm/ctfsm.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file fsm.hpp 3 | * @author Carmine Margiotta (cmargiotta@posteo.net) 4 | * 5 | * @copyright Copyright (c) 2022 6 | */ 7 | 8 | #ifndef FSM_HPP 9 | #define FSM_HPP 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "utility/existence_verifier.hpp" 18 | #include "utility/type_map.hpp" 19 | #include "utility/type_set.hpp" 20 | 21 | MAKE_EXISTENCE_VERIFIER(on_enter) 22 | MAKE_EXISTENCE_VERIFIER(on_exit) 23 | MAKE_EXISTENCE_VERIFIER(on_transit) 24 | MAKE_EXISTENCE_VERIFIER(id) 25 | 26 | #ifdef __EXCEPTIONS 27 | # define HANDLE_EVENT_RETURN_TYPE void 28 | #else 29 | # define HANDLE_EVENT_RETURN_TYPE bool 30 | #endif 31 | 32 | namespace ctfsm 33 | { 34 | /** 35 | * @brief A finite state machine, it can only handle a specific designed set of states. Only the 36 | * initial state has to be provided. 37 | * 38 | * @tparam state 39 | */ 40 | template 41 | class fsm 42 | { 43 | private: 44 | template> 45 | struct state_expander; 46 | 47 | template 48 | struct state_expander, std::tuple> 49 | { 50 | // Base case, there are no more nodes to expand. This handles eventual cycles in 51 | // the graph too. 52 | public: 53 | using states = std::tuple; 54 | }; 55 | 56 | template 57 | struct state_expander, set> 58 | { 59 | // Expand a single state and every reachable state from that 60 | public: 61 | // Adding State and the reachable nodes from it to set 62 | using state_set = ctfsm::type_set_merge< 63 | typename ctfsm::extract_values::values, 64 | typename ctfsm::type_set_merge, set>::set>; 65 | 66 | // Expanding the nodes that were not already in set 67 | using states = 68 | typename state_expander::states; 69 | }; 70 | 71 | template 72 | struct state_expander, set> 73 | { 74 | // Iterate through the states list and expand every state 75 | private: 76 | using state_set = ctfsm::type_set_merge< 77 | typename ctfsm::extract_values::values, 78 | typename ctfsm::type_set_merge, set>::set>; 79 | 80 | public: 81 | using states = typename ctfsm::type_set_merge< 82 | typename state_expander, set>::states, 83 | typename state_expander, set>::states>::set; 84 | }; 85 | 86 | template 87 | struct variant_builder; 88 | 89 | template 90 | struct variant_builder> 91 | { 92 | public: 93 | using variant = std::variant; 94 | }; 95 | 96 | template 97 | using variant_builder_t = typename variant_builder::variant; 98 | 99 | template 100 | struct id_types_verifier; 101 | 102 | template 103 | struct id_types_verifier> 104 | { 105 | public: 106 | static_assert( 107 | std::conjunction_v...>, 108 | "Every state ID is of the same type"); 109 | 110 | using type = typename std::remove_cv::type; 111 | }; 112 | 113 | template 114 | struct id_types_verifier> 115 | { 116 | public: 117 | using type = decltype(typeid(state).name()); 118 | }; 119 | 120 | template 121 | struct id_extractor 122 | { 123 | static const inline auto id = typeid(state).name(); 124 | }; 125 | 126 | template 127 | struct id_extractor 128 | { 129 | static constexpr auto id = state::id; 130 | }; 131 | 132 | public: 133 | using states = typename state_expander>::states; 134 | using id_type = typename id_types_verifier::type; 135 | 136 | states _states; 137 | variant_builder_t _current_state; 138 | const id_type* _current_state_id; 139 | 140 | private: 141 | template 142 | constexpr void invoke_on_exit(current_state& current, _event& event) noexcept 143 | { 144 | if constexpr (ctfsm::has_on_exit_method>) 145 | { 146 | current.on_exit(event); 147 | } 148 | else if constexpr (ctfsm::has_on_exit_method) 149 | { 150 | current.on_exit(); 151 | } 152 | } 153 | 154 | template 155 | constexpr void invoke_on_enter(current_state& current, _event& event) noexcept 156 | { 157 | if constexpr (ctfsm::has_on_enter_method>) 158 | { 159 | current.on_enter(event); 160 | } 161 | else if constexpr (ctfsm::has_on_enter_method) 162 | { 163 | current.on_enter(); 164 | } 165 | } 166 | 167 | template 168 | constexpr void invoke_on_transit(_event& event) noexcept 169 | { 170 | if constexpr (!has_on_transit_method<_event, void>) 171 | { 172 | return; 173 | } 174 | else 175 | { 176 | event.on_transit(); 177 | } 178 | } 179 | 180 | template 181 | constexpr HANDLE_EVENT_RETURN_TYPE handle_event_(auto&) 182 | requires(std::is_same_v) 183 | { 184 | #ifdef __EXCEPTIONS 185 | throw std::runtime_error("Unhandled transation"); 186 | #else 187 | return false; 188 | #endif 189 | } 190 | 191 | template 192 | constexpr HANDLE_EVENT_RETURN_TYPE handle_event_(auto& event) noexcept 193 | { 194 | invoke_on_exit(std::get(_states), event); 195 | invoke_on_transit(event); 196 | 197 | _current_state = &std::get(_states); 198 | _current_state_id = &id_extractor::id; 199 | 200 | invoke_on_enter(std::get(_states), event); 201 | 202 | #ifndef __EXCEPTIONS 203 | return true; 204 | #endif 205 | } 206 | 207 | public: 208 | /** 209 | * @brief Default constructor, defaulty constructs every state and sets the initial 210 | * state as current 211 | */ 212 | constexpr fsm() noexcept 213 | : _current_state(&std::get(_states)), 214 | _current_state_id(&id_extractor::id) 215 | { 216 | } 217 | 218 | /** 219 | * @brief Reset the finite state machine */ 220 | constexpr auto reset() noexcept -> void 221 | { 222 | _current_state = &std::get(_states); 223 | _current_state_id = &id_extractor::id; 224 | } 225 | 226 | /** 227 | * @brief Handles an incoming event, as mapped in current_state::transitions 228 | * 229 | * @param event 230 | */ 231 | #ifndef __EXCEPTIONS 232 | [[nodiscard]] 233 | #endif 234 | constexpr HANDLE_EVENT_RETURN_TYPE 235 | handle_event(auto& event) 236 | { 237 | return std::visit( 238 | [this, &event](auto* current) 239 | { 240 | using current_state = std::remove_pointer_t; 241 | using target_state = 242 | typename find_by_key, 243 | typename current_state::transitions>::result; 244 | 245 | static_assert(current_state::transitions::valid, 246 | "transitions events must be unique"); 247 | 248 | return this->handle_event_(event); 249 | }, 250 | _current_state); 251 | } 252 | 253 | constexpr HANDLE_EVENT_RETURN_TYPE handle_event(auto&& event) 254 | { 255 | return handle_event(event); 256 | } 257 | 258 | /** 259 | * @brief Construct an empty event and handle it 260 | * 261 | * @tparam event 262 | */ 263 | template 264 | #ifndef __EXCEPTIONS 265 | [[nodiscard]] 266 | #endif 267 | constexpr HANDLE_EVENT_RETURN_TYPE 268 | handle_event() 269 | { 270 | return handle_event(event {}); 271 | } 272 | 273 | /** 274 | * @brief FSM can also be directly invoked with an event 275 | * 276 | * @param event 277 | */ 278 | constexpr void operator()(auto&& event) 279 | { 280 | handle_event(event); 281 | } 282 | 283 | /** 284 | * @brief Get current state id 285 | * 286 | * @return constexpr const id_type& 287 | */ 288 | constexpr const id_type& get_current_state_id() const noexcept 289 | { 290 | return *_current_state_id; 291 | } 292 | 293 | /** 294 | * @brief Returns true if T is the current state 295 | */ 296 | template 297 | constexpr bool is_current_state() const noexcept 298 | { 299 | return std::holds_alternative(_current_state); 300 | } 301 | 302 | /** 303 | * @brief Invoke the given lambda passing a reference to the current state 304 | * 305 | * @param lambda 306 | * @return the lambda return value 307 | */ 308 | constexpr auto invoke_on_current(auto lambda) noexcept 309 | { 310 | return std::visit([this, lambda](auto current) { return lambda(*current, *this); }, 311 | _current_state); 312 | } 313 | }; 314 | }// namespace ctfsm 315 | 316 | #endif// FSM_HPP 317 | -------------------------------------------------------------------------------- /src/fsm/meson.build: -------------------------------------------------------------------------------- 1 | ctfsm = declare_dependency( 2 | include_directories: ['./'], 3 | dependencies: [ 4 | utility 5 | ] 6 | ) -------------------------------------------------------------------------------- /src/meson.build: -------------------------------------------------------------------------------- 1 | subdir('utility') 2 | subdir('fsm') 3 | -------------------------------------------------------------------------------- /src/utility/existence_verifier.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file existence_verifier.hpp 3 | * @author Carmine Margiotta (cmargiotta@posteo.net) 4 | * 5 | * @copyright Copyright (c) 2022 6 | */ 7 | 8 | #ifndef UTILITY_EXISTENCE_VERIFIER_HPP 9 | #define UTILITY_EXISTENCE_VERIFIER_HPP 10 | 11 | #include 12 | 13 | /** 14 | * @brief Build a concept that verifies the existence of the given member. 15 | */ 16 | #define MAKE_EXISTENCE_VERIFIER(member) \ 17 | namespace ctfsm \ 18 | { \ 19 | template \ 20 | concept has_##member = requires(T instance) { std::declval().member; }; \ 21 | \ 22 | template \ 23 | concept has_##member##_method = requires(T instance, args... arguments) { \ 24 | { \ 25 | instance.member(arguments...) \ 26 | } -> std::same_as; \ 27 | } || (sizeof...(args) == 0 && requires(T instance) { \ 28 | { \ 29 | instance.member() \ 30 | } -> std::same_as; \ 31 | }); \ 32 | } 33 | 34 | #endif// UTILITY_EXISTENCE_VERIFIER_HPP 35 | -------------------------------------------------------------------------------- /src/utility/meson.build: -------------------------------------------------------------------------------- 1 | utility = declare_dependency( 2 | include_directories: ['../'] 3 | ) -------------------------------------------------------------------------------- /src/utility/type_map.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file type_map.hpp 3 | * @author Carmine Margiotta (cmargiotta@posteo.net) 4 | * 5 | * @copyright Copyright (c) 2022 6 | */ 7 | 8 | #ifndef TYPE_TRAITS_TYPE_MAP_HPP 9 | #define TYPE_TRAITS_TYPE_MAP_HPP 10 | 11 | #include 12 | #include 13 | 14 | #include "type_set.hpp" 15 | 16 | namespace ctfsm 17 | { 18 | /** 19 | * @brief Extract an std::tuple of keys from the given type map 20 | * 21 | * @tparam map 22 | */ 23 | template 24 | struct extract_keys; 25 | 26 | /** 27 | * @brief Extract an std::tuple of values from the given type map 28 | * 29 | * @tparam map 30 | */ 31 | template 32 | struct extract_values; 33 | 34 | /** 35 | * @brief An std::tuple of std::pair is considered a type_map, where the pair is composed of 36 | * 37 | * 38 | * @tparam data 39 | */ 40 | template 41 | struct type_map; 42 | 43 | /** 44 | * @brief Find the value corresponding to the given Key, if it does not exist void is exposed 45 | * 46 | * @tparam key 47 | * @tparam map 48 | */ 49 | template 50 | struct find_by_key; 51 | 52 | template 53 | struct find_by_key, data...>> 54 | : find_by_key> 55 | { 56 | // Key has not been found, keep searching it 57 | }; 58 | 59 | template 60 | struct find_by_key> 61 | { 62 | // Key has not been found, expose void 63 | public: 64 | using result = void; 65 | }; 66 | 67 | template 68 | struct find_by_key, data...>> 69 | { 70 | // Key has been found, expose the result 71 | public: 72 | using result = value; 73 | }; 74 | 75 | template 76 | struct extract_values, data...>> 77 | { 78 | public: 79 | using values = decltype(std::tuple_cat( 80 | std::declval>(), 81 | std::declval>::values>())); 82 | }; 83 | 84 | template<> 85 | struct extract_values> 86 | { 87 | // Base case, all values have been extracted, nothing to do 88 | public: 89 | using values = std::tuple<>; 90 | }; 91 | 92 | template 93 | struct extract_keys, data...>> 94 | { 95 | // Iterating through the Map and composing the Keys tuple 96 | public: 97 | using keys = decltype(std::tuple_cat( 98 | std::declval>(), 99 | std::declval>::keys>())); 100 | }; 101 | 102 | template<> 103 | struct extract_keys> 104 | { 105 | // Base case, all keys have been extracted, nothing to do 106 | public: 107 | using keys = std::tuple<>; 108 | }; 109 | 110 | template 111 | struct type_map, data_...> 112 | { 113 | private: 114 | using type = type_map, data_...>; 115 | 116 | public: 117 | using data = std::tuple, data_...>; 118 | 119 | static constexpr bool valid 120 | = (std::tuple_size::keys>::set>::value 121 | == std::tuple_size::value); 122 | }; 123 | 124 | template<> 125 | struct type_map<> 126 | { 127 | public: 128 | using data = std::tuple<>; 129 | 130 | static constexpr bool valid = true; 131 | }; 132 | }// namespace ctfsm 133 | 134 | #endif// TYPE_TRAITS_TYPE_MAP_HPP 135 | -------------------------------------------------------------------------------- /src/utility/type_set.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file type_set.hpp 3 | * @author Carmine Margiotta (cmargiotta@posteo.net) 4 | * 5 | * @copyright Copyright (c) 2022 6 | */ 7 | 8 | #ifndef UTILITY_TYPE_SET_HPP 9 | #define UTILITY_TYPE_SET_HPP 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | namespace ctfsm 16 | { 17 | /** 18 | * @brief Checks if the Element type is in the given std::tuple 19 | * 20 | * @tparam element 21 | * @tparam containers 22 | */ 23 | template 24 | struct contains : contains 25 | { 26 | }; 27 | 28 | template 29 | struct contains> : std::disjunction...> 30 | { 31 | }; 32 | 33 | template::value> 34 | struct type_set_insert; 35 | 36 | template 37 | struct type_set_insert, true> 38 | { 39 | // Element is already in Set_, nothing to do, Delta is empty 40 | public: 41 | using set = std::tuple; 42 | using delta = std::tuple<>; 43 | }; 44 | 45 | template 46 | struct type_set_insert, false> 47 | { 48 | // Element is not in set_, append it to the set and report it in Delta 49 | public: 50 | using set = std::tuple; 51 | using delta = std::tuple; 52 | }; 53 | 54 | /** 55 | * @brief Given an std::tuple and a type set, type_set_merge<...>::Set will be the given set 56 | * with appended types from the tuple. Sorting is not guaranteed, unicity is guaranteed. 57 | * 58 | * @tparam elements_tuple 59 | * @tparam base_set the set to be updated 60 | */ 61 | template 62 | struct type_set_merge; 63 | 64 | template 65 | struct type_set_merge, std::tuple> 66 | { 67 | private: 68 | // Recursive call (until elements is not empty) 69 | using tail_set = type_set_merge, std::tuple>; 70 | // type_set_insert call 71 | using current = type_set_insert; 72 | 73 | public: 74 | using set = typename current::set; 75 | using delta = decltype(std::tuple_cat(std::declval(), 76 | std::declval())); 77 | }; 78 | 79 | template 80 | struct type_set_merge, std::tuple> 81 | { 82 | // Base case, no more elements to insert, nothing to do 83 | public: 84 | using set = std::tuple; 85 | using delta = std::tuple<>; 86 | }; 87 | 88 | /** 89 | * @brief Build a type_set from the given parameter pack of types, removing repetitions 90 | * 91 | * @tparam elements 92 | */ 93 | template 94 | struct type_set : type_set_merge, std::tuple<>> 95 | { 96 | }; 97 | 98 | /** 99 | * @brief Build a type_set from the given std::tuple, removing repetitions 100 | * 101 | * @tparam elements 102 | */ 103 | template 104 | struct type_set> : type_set 105 | { 106 | }; 107 | 108 | /** 109 | * @brief Obtain the nth element of the given type set 110 | * 111 | * @tparam n 112 | * @tparam set 113 | */ 114 | template 115 | using nth_type_set_element = std::tuple_element; 116 | 117 | template 118 | struct type_set_find_element_helper; 119 | 120 | template 121 | struct type_set_find_element_helper, index_> 122 | { 123 | // Exploring the types list 124 | public: 125 | static constexpr std::size_t index 126 | = type_set_find_element_helper, index_ + 1>::index; 127 | }; 128 | 129 | template 130 | struct type_set_find_element_helper, index_> 131 | { 132 | // Element found, exposing index 133 | public: 134 | static constexpr std::size_t index = index_; 135 | }; 136 | 137 | /** 138 | * @brief Find an element in the given set and expose its index 139 | * 140 | * @tparam element 141 | * @tparam set 142 | */ 143 | template 144 | using type_set_find_element = type_set_find_element_helper; 145 | }// namespace ctfsm 146 | 147 | #endif// UTILITY_TYPE_SET_HPP 148 | -------------------------------------------------------------------------------- /subprojects/catch2.wrap: -------------------------------------------------------------------------------- 1 | [wrap-file] 2 | directory = Catch2-2.13.7 3 | source_url = https://github.com/catchorg/Catch2/archive/v2.13.7.zip 4 | source_filename = Catch2-2.13.7.zip 5 | source_hash = 3f3ccd90ad3a8fbb1beeb15e6db440ccdcbebe378dfd125d07a1f9a587a927e9 6 | patch_filename = catch2_2.13.7-1_patch.zip 7 | patch_url = https://wrapdb.mesonbuild.com/v2/catch2_2.13.7-1/get_patch 8 | patch_hash = 2f7369645d747e5bd866317ac1dd4c3d04dc97d3aad4fc6b864bdf75d3b57158 9 | 10 | [provide] 11 | catch2 = catch2_dep 12 | 13 | -------------------------------------------------------------------------------- /test/catch_main.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_ENABLE_BENCHMARKING 2 | 3 | #define CATCH_CONFIG_MAIN 4 | 5 | #include -------------------------------------------------------------------------------- /test/fsm.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_ENABLE_BENCHMARKING 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | struct switch_on 11 | { 12 | bool transited = false; 13 | 14 | void on_transit() 15 | { 16 | transited = true; 17 | } 18 | }; 19 | 20 | struct switch_off 21 | { 22 | }; 23 | 24 | struct force 25 | { 26 | }; 27 | 28 | struct state_off; 29 | struct state_on; 30 | 31 | struct state_on 32 | { 33 | using transitions 34 | = ctfsm::type_map, std::pair>; 35 | 36 | static constexpr std::string_view id {"ON"}; 37 | static constinit inline bool switched_off = false; 38 | static constinit inline bool on_entered = false; 39 | static constinit inline bool forced = false; 40 | 41 | void on_enter(switch_on&) 42 | { 43 | on_entered = true; 44 | } 45 | 46 | void on_enter() 47 | { 48 | forced = true; 49 | } 50 | 51 | void on_exit() 52 | { 53 | switched_off = true; 54 | } 55 | }; 56 | 57 | struct state_off 58 | { 59 | using transitions 60 | = ctfsm::type_map, std::pair>; 61 | 62 | static constexpr std::string_view id {"OFF"}; 63 | static constinit inline bool entered = false; 64 | 65 | void on_enter() 66 | { 67 | entered = true; 68 | } 69 | }; 70 | 71 | TEST_CASE("FSM basic usage", "[fsm]") 72 | { 73 | ctfsm::fsm fsm; 74 | switch_on on; 75 | switch_off off; 76 | 77 | REQUIRE(fsm.get_current_state_id() == "ON"); 78 | REQUIRE(!state_on::on_entered); 79 | 80 | fsm.handle_event(on); 81 | 82 | REQUIRE(fsm.get_current_state_id() == "ON"); 83 | REQUIRE(fsm.is_current_state()); 84 | REQUIRE(state_on::on_entered); 85 | REQUIRE(on.transited); 86 | 87 | fsm.handle_event(switch_off()); 88 | 89 | REQUIRE(fsm.get_current_state_id() == "OFF"); 90 | REQUIRE(fsm.is_current_state()); 91 | REQUIRE(state_on::switched_off); 92 | 93 | REQUIRE_THROWS(fsm.handle_event(off)); 94 | 95 | fsm.handle_event(); 96 | 97 | REQUIRE(fsm.get_current_state_id() == "ON"); 98 | REQUIRE(fsm.is_current_state()); 99 | 100 | fsm(switch_off()); 101 | REQUIRE(fsm.get_current_state_id() == "OFF"); 102 | REQUIRE(fsm.is_current_state()); 103 | 104 | fsm(on); 105 | REQUIRE(fsm.get_current_state_id() == "ON"); 106 | REQUIRE(fsm.is_current_state()); 107 | 108 | fsm(switch_off()); 109 | REQUIRE(fsm.get_current_state_id() == "OFF"); 110 | REQUIRE(fsm.is_current_state()); 111 | 112 | fsm(force {}); 113 | REQUIRE(fsm.get_current_state_id() == "ON"); 114 | REQUIRE(state_on::forced); 115 | REQUIRE(fsm.is_current_state()); 116 | 117 | auto current_id = fsm.invoke_on_current([](auto&& current, auto& fsm) { return current.id; }); 118 | REQUIRE(current_id == "ON"); 119 | 120 | fsm.reset(); 121 | REQUIRE(fsm.get_current_state_id() == "ON"); 122 | } 123 | -------------------------------------------------------------------------------- /test/meson.build: -------------------------------------------------------------------------------- 1 | if not meson.is_subproject() 2 | catch2 = dependency('catch2') 3 | test_main = static_library('test_main', sources: ['catch_main.cpp'], dependencies: [catch2]) 4 | 5 | utility_test = executable( 6 | 'utility', 7 | 'utility_test.cpp', 8 | link_with: [test_main], 9 | dependencies: [ctfsm, utility, catch2] 10 | ) 11 | 12 | fsm_test = executable( 13 | 'fsm', 14 | 'fsm.cpp', 15 | link_with: [test_main], 16 | dependencies: [ctfsm, catch2] 17 | ) 18 | 19 | test('utility test', utility_test) 20 | test('fsm test', fsm_test) 21 | endif -------------------------------------------------------------------------------- /test/utility_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | MAKE_EXISTENCE_VERIFIER(a); 8 | MAKE_EXISTENCE_VERIFIER(b); 9 | MAKE_EXISTENCE_VERIFIER(c); 10 | MAKE_EXISTENCE_VERIFIER(d); 11 | 12 | using namespace ctfsm; 13 | 14 | struct A 15 | { 16 | private: 17 | int b; 18 | 19 | public: 20 | int a; 21 | int c(int, float); 22 | int c(double); 23 | int d(); 24 | }; 25 | 26 | static_assert(!has_b); 27 | static_assert(has_a); 28 | static_assert(has_c_method); 29 | static_assert(has_c_method); 30 | static_assert(has_d_method); 31 | static_assert(has_c_method); 32 | static_assert(!has_c_method); 33 | static_assert(!has_c_method); 34 | 35 | using map = type_map, std::pair>; 36 | 37 | static_assert(std::is_same_v::values, std::tuple>); 38 | static_assert(std::is_same_v::keys, std::tuple>); 39 | static_assert(std::is_same_v::result, float>); 40 | static_assert(std::is_same_v::result, char>); 41 | 42 | using set = type_set::set; 43 | 44 | static_assert(std::is_same_v>); 45 | static_assert(contains::value); 46 | static_assert(contains::value); 47 | static_assert(contains::value); 48 | static_assert(contains::value); 49 | static_assert(!contains::value); 50 | 51 | using merge_res = type_set_merge, set>; 52 | 53 | static_assert(contains::value); 54 | static_assert(contains::value); 55 | static_assert(contains::value); 56 | static_assert(contains::value); 57 | static_assert(contains::value); 58 | static_assert(contains::value); 59 | 60 | static_assert(contains::value); 61 | static_assert(contains::value); 62 | static_assert(!contains::value); 63 | static_assert(!contains::value); 64 | static_assert(!contains::value); 65 | static_assert(!contains::value); --------------------------------------------------------------------------------