├── .gitignore ├── LICENSE ├── README.md ├── State-Oriented_Programming.pdf ├── c ├── hsm.c ├── hsm.h ├── hsmtst.c ├── make.bat └── watch.c ├── cpp ├── hsm.cpp ├── hsm.hpp ├── hsmtst.cpp ├── hsmtst.sln ├── hsmtst.vcxproj ├── hsmtst.vcxproj.filters ├── make.bat ├── watch.cpp ├── watch.sln ├── watch.vcxproj └── watch.vcxproj.filters └── img ├── qhsmtst.jpg ├── thumbnail.jpg └── watch.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Brought to you by: 2 | [![Quantum Leaps](https://www.state-machine.com/attachments/logo_ql_400.png)](https://www.state-machine.com) 3 | 4 | --------------------------------------------------------------------- 5 | [![State-Oriented Programming Article](img/thumbnail.jpg)](State-Oriented_Programming.pdf) 6 | 7 | # State Oriented Programming 8 | # Hierarchical State Machines in C/C++ 9 | 10 | ## Miro Samek and Paul Y. Montgomery 11 | ## May 13, 2000 12 | 13 | ["State-Oriented Programming"](State-Oriented_Programming.pdf) was 14 | a cover story article published originally in 15 | [Embedded Systems Programming](https://www.embedded.com/state-oriented-programming) 16 | magazine in August 2000. This repository contains the article PDF and the updated code. 17 | 18 | The code accompanying the "State-Oriented Programming" ESP article is licensed 19 | under the open source MIT license (see the top-level comments in the hsm.h/c files). 20 | 21 | The code is organized into two subdirectories: C and Cpp. Each 22 | subdirectory contains the make.bat batch file to build the code 23 | with the GCC compiler. This should work both on Windows (requires 24 | MinGW or similar on GCC for Windows) and Linux/MacOS. The examples 25 | include the digital watch discussed in the article as well as the 26 | "qhsmtst" test for the hierarchical state machine implementation (see 27 | below). 28 | 29 | Alternatively to using the make.bat file, you can simply type the 30 | following command at the command prompt: 31 | 32 | In the C directory: 33 | 34 | `gcc watch.c hsm.c -o watch` 35 | 36 | In the Cpp directory: 37 | 38 | `g++ watch.cpp hsm.cpp -o watch` 39 | 40 | 41 | For your convenience we have included the Windows executable file 42 | (`watch.exe`), which you can try directly. You inject events 43 | into the watch state machine by typing numbers on your keyboard: 44 | (0=MODE_EVT, 1=SET_EVT, 2=TICK_EVT, 3+ to exit). 45 | 46 | ![Watch state machine](img/watch.jpg) 47 | 48 | Here is an example run for the C version of the watch code: 49 | 50 | ``` 51 | C:\tmp\State-Oriented_Programming\c>gcc watch.c hsm.c -o watch 52 | 53 | C:\tmp\State-Oriented_Programming\c>watch 54 | Enter: 55 | 0 for MODE_EVT 56 | 1 for SET_EVT 57 | 2 for TICK_EVT 58 | 3+ to exit 59 | 60 | Watch::setting-START->hour; 61 | Event<-0 62 | time: 1:00:00 63 | Event<-0 64 | time: 2:00:00 65 | Event<-1 66 | Watch::hour-SET; 67 | Event<-0 68 | time: 2:01:00 69 | Event<-0 70 | time: 2:02:00 71 | Event<-1 72 | Watch::minute-SET; 73 | Event<-0 74 | date: 01-02 75 | Event<-0 76 | date: 01-03 77 | Event<-1 78 | Watch::day-SET; 79 | Event<-0 80 | date: 02-03 81 | Event<-0 82 | date: 03-03 83 | Event<-1 84 | Watch::month-SET;time: 2:02:00 85 | Event<-2 86 | Watch::time-TICK;time: 2:02:01 87 | Event<-2 88 | Watch::time-TICK;time: 2:02:02 89 | Event<-0 90 | Watch::time-MODE;date: 03-03 91 | Event<-2 92 | Watch::date-TICK;date: 03-03 93 | Event<-2 94 | Watch::date-TICK;date: 03-03 95 | Event<-1 96 | Watch::timekeeping-SET;Watch::setting-START->hour; 97 | Event<-1 98 | Watch::hour-SET; 99 | Event<-1 100 | Watch::minute-SET; 101 | Event<-1 102 | Watch::day-SET; 103 | Event<-1 104 | Watch::month-SET;date: 03-03 105 | Event<-0 106 | Watch::date-MODE;time: 2:02:04 107 | Event<-4 108 | 109 | C:\tmp\State-Oriented_Programming\c> 110 | ``` 111 | 112 | ## Embedded Projects 113 | 114 | In order to use the code in your embedded projects you would need to 115 | extract files `hsm.h` and `hsm.c` from the C subdirectory, or files 116 | `hsm.hpp` and `hsm.cpp` form the Cpp subdirectory. We have compiled the 117 | code with a variety of C and C++ compilers, including: VC++ and 118 | Borland compilers for Windows, GCC compiler for Linux, ARM Software 119 | Development Toolkit v. 2.0 and 2.5 C compiler, and Green Hills MULTI 120 | 2000 C and EC++ ARM/THUMB compilers. 121 | 122 | 123 | ## Notes About C++ Implementation 124 | 125 | We have noticed one potential problem with one aspect of the C++ 126 | implementation. Depending on the compiler you would use you may 127 | encounter compilation errors in casting (upcasting) event handlers 128 | to a Hsm member function pointer (EvtHndlr). This upcasting is 129 | necessary to configure the state machine in the constructor. 130 | In our code we use the most commonly accepted by different compilers 131 | cast: 132 | 133 | `(EvtHndlr)&::` 134 | 135 | newer C++ compilers (but not EC++ compilers) may accept construct: 136 | 137 | `reinterpret_cast(&::)` 138 | 139 | Your compiler may allow you to use a simpler form: 140 | 141 | `(EvtHndlr)` 142 | 143 | since specifying class with the scope operator :: should not be 144 | necessary inside the class method (the constructor). 145 | 146 | 147 | ## The QHsmTst Example 148 | 149 | Since the publication of the original article, we've added a more 150 | exhaustive example of a state machine called QHsmTst. The state machine 151 | is completely artificial, but it contains all possible combinations of 152 | transitions up to 3 levels of state nesting. 153 | 154 | > **NOTE:** This example is borrowed from the book 155 | ["Practical Statecharts in C/C++" by Miro Samek](https://www.state-machine.com/psicc), 156 | published in 2002. 157 | 158 | 159 | ![QHsmTst state machine](img/qhsmtst.jpg) 160 | 161 | The C version of the example is located in the C directory. You build it 162 | exactly like the watch example: 163 | 164 | `gcc hsmtst.c hsm.c -o hsmtst` 165 | 166 | The C++ version of the example is located in the C directory. You build it 167 | exactly like the watch example: 168 | 169 | `g++ hsmtst.cpp hsm.cpp -o hsmtst` 170 | 171 | Here is an example run for the C++ version of the HsmTst code: 172 | 173 | ``` 174 | C:\tmp\State-Oriented_Programming\cpp>g++ hsmtst.cpp hsm.cpp -o hsmtst 175 | 176 | C:\tmp\State-Oriented_Programming\cpp>hsmtst 177 | Events: 178 | a-h for triggering events 179 | x to exit 180 | 181 | top-ENTRY;top-INIT;s1-ENTRY;s1-INIT;s11-ENTRY; 182 | Event<-a 183 | s1-A;s11-EXIT;s1-EXIT;s1-ENTRY;s1-INIT;s11-ENTRY; 184 | Event<-e 185 | top-E;s11-EXIT;s1-EXIT;s2-ENTRY;s21-ENTRY;s211-ENTRY; 186 | Event<-e 187 | top-E;s211-EXIT;s21-EXIT;s2-EXIT;s2-ENTRY;s21-ENTRY;s211-ENTRY; 188 | Event<-a 189 | 190 | Event<-h 191 | s21-H;s211-EXIT;s21-EXIT;s21-ENTRY;s21-INIT;s211-ENTRY; 192 | Event<-h 193 | 194 | Event<-x 195 | 196 | C:\tmp\State-Oriented_Programming\cpp> 197 | ``` 198 | 199 | 200 | ## Updates 201 | Since the publication of the "State-Oriented Programming" article, the 202 | presented concepts and implementations have been completely revised, 203 | improved and expanded in many direcctions. Among others, the **Real-Time 204 | Framework** suggested in the article has been created and the vastly 205 | improved hierarchical state machine implementation is now part of that 206 | framework. Please see the QP Real-Time Embedded Frameworks (RTEFs) at: 207 | 208 | https://www.state-machine.com/ 209 | 210 | -------------------------------------------------------------------------------- /State-Oriented_Programming.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuantumLeaps/State-Oriented-Programming/7c5e433655921f98b8722a601df9e2efbc2988d4/State-Oriented_Programming.pdf -------------------------------------------------------------------------------- /c/hsm.c: -------------------------------------------------------------------------------- 1 | /** 2 | * hsm.c -- Hierarchical State Machine implementation 3 | * 4 | * Copyright 2000 Miro Samek. All rights reserved. 5 | * 6 | * This software is licensed under the following open source MIT license: 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining 9 | * a copy of this software and associated documentation files (the "Software"), 10 | * to deal in the Software without restriction, including without limitation 11 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | * and/or sell copies of the Software, and to permit persons to whom the 13 | * Software is furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included 16 | * in all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 19 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 21 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | * DEALINGS IN THE SOFTWARE. 25 | * 26 | * Contact information: 27 | * miro@quantum-leaps.com 28 | */ 29 | #include "hsm.h" 30 | 31 | static Msg const startMsg = { START_EVT }; 32 | static Msg const entryMsg = { ENTRY_EVT }; 33 | static Msg const exitMsg = { EXIT_EVT }; 34 | #define MAX_STATE_NESTING 8 35 | 36 | /* State Ctor...............................................................*/ 37 | void StateCtor(State *me, char const *name, State *super, EvtHndlr hndlr) { 38 | me->name = name; 39 | me->super = super; 40 | me->hndlr = hndlr; 41 | } 42 | 43 | /* Hsm Ctor.................................................................*/ 44 | void HsmCtor(Hsm *me, char const *name, EvtHndlr topHndlr) { 45 | StateCtor(&me->top, "top", 0, topHndlr); 46 | me->name = name; 47 | } 48 | 49 | /* enter and start the top state............................................*/ 50 | void HsmOnStart(Hsm *me) { 51 | me->curr = &me->top; 52 | me->next = 0; 53 | StateOnEvent(me->curr, me, &entryMsg); 54 | while (StateOnEvent(me->curr, me, &startMsg), me->next) { 55 | State *entryPath[MAX_STATE_NESTING]; 56 | register State **trace = entryPath; 57 | register State *s; 58 | *trace = 0; 59 | for (s = me->next; s != me->curr; s = s->super) { 60 | *(++trace) = s; /* trace path to target */ 61 | } 62 | while ((s = *trace--)) { /* retrace entry from source */ 63 | StateOnEvent(s, me, &entryMsg); 64 | } 65 | me->curr = me->next; 66 | me->next = 0; 67 | } 68 | } 69 | 70 | /* state machine "engine"...................................................*/ 71 | void HsmOnEvent(Hsm *me, Msg const *msg) { 72 | State *entryPath[MAX_STATE_NESTING]; 73 | register State **trace; 74 | register State *s; 75 | for (s = me->curr; s; s = s->super) { 76 | me->source = s; /* level of outermost event handler */ 77 | msg = StateOnEvent(s, me, msg); 78 | if (msg == 0) { 79 | if (me->next) { /* state transition taken? */ 80 | trace = entryPath; 81 | *trace = 0; 82 | for (s = me->next; s != me->curr; s = s->super) { 83 | *(++trace) = s; /* trace path to target */ 84 | } 85 | while ((s = *trace--)) { /* retrace entry from LCA */ 86 | StateOnEvent(s, me, &entryMsg); 87 | } 88 | me->curr = me->next; 89 | me->next = 0; 90 | while (StateOnEvent(me->curr, me, &startMsg), me->next) { 91 | trace = entryPath; 92 | *trace = 0; 93 | for (s = me->next; s != me->curr; s = s->super) { 94 | *(++trace) = s; /* record path to target */ 95 | } 96 | while ((s = *trace--)) { /* retrace the entry */ 97 | StateOnEvent(s, me, &entryMsg); 98 | } 99 | me->curr = me->next; 100 | me->next = 0; 101 | } 102 | } 103 | break; /* event processed */ 104 | } 105 | } 106 | } 107 | 108 | /* exit current states and all superstates up to LCA .......................*/ 109 | void HsmExit_(Hsm *me, unsigned char toLca) { 110 | register State *s = me->curr; 111 | while (s != me->source) { 112 | StateOnEvent(s, me, &exitMsg); 113 | s = s->super; 114 | } 115 | while (toLca--) { 116 | StateOnEvent(s, me, &exitMsg); 117 | s = s->super; 118 | } 119 | me->curr = s; 120 | } 121 | 122 | /* find # of levels to Least Common Ancestor................................*/ 123 | unsigned char HsmToLCA_(Hsm *me, State *target) { 124 | unsigned char toLca = 0; 125 | if (me->source == target) { 126 | return 1; 127 | } 128 | for (State *s = me->source; s; ++toLca, s = s->super) { 129 | for (State *t = target; t; t = t->super) { 130 | if (s == t) { 131 | return toLca; 132 | } 133 | } 134 | } 135 | return 0; 136 | } 137 | -------------------------------------------------------------------------------- /c/hsm.h: -------------------------------------------------------------------------------- 1 | /** 2 | * hsm.h -- Hierarchical State Machine interface 3 | * 4 | * Copyright 2000 Miro Samek. All rights reserved. 5 | * 6 | * This software is licensed under the following open source MIT license: 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining 9 | * a copy of this software and associated documentation files (the "Software"), 10 | * to deal in the Software without restriction, including without limitation 11 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | * and/or sell copies of the Software, and to permit persons to whom the 13 | * Software is furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included 16 | * in all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 19 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 21 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | * DEALINGS IN THE SOFTWARE. 25 | * 26 | * Contact information: 27 | * miro@quantum-leaps.com 28 | */ 29 | #ifndef hsm_h 30 | #define hsm_h 31 | 32 | typedef int Event; 33 | typedef struct { 34 | Event evt; 35 | } Msg; 36 | 37 | typedef struct Hsm Hsm; 38 | typedef Msg const *(*EvtHndlr)(Hsm*, Msg const*); 39 | 40 | typedef struct State State; 41 | struct State { 42 | State *super; /* pointer to superstate */ 43 | EvtHndlr hndlr; /* state's handler function */ 44 | char const *name; 45 | }; 46 | 47 | void StateCtor(State *me, char const *name, State *super, EvtHndlr hndlr); 48 | #define StateOnEvent(me_, ctx_, msg_) \ 49 | (*(me_)->hndlr)((ctx_), (msg_)) 50 | 51 | struct Hsm { /* Hierarchical State Machine base class */ 52 | char const *name; /* pointer to static name */ 53 | State *curr; /* current state */ 54 | State *next; /* next state (non 0 if transition taken) */ 55 | State *source; /* source state during last transition */ 56 | State top; /* top-most state object */ 57 | }; 58 | 59 | void HsmCtor(Hsm *me, char const *name, EvtHndlr topHndlr); 60 | void HsmOnStart(Hsm *me); /* enter and start the top state */ 61 | void HsmOnEvent(Hsm *me, Msg const *msg); /* "HSM engine" */ 62 | 63 | /* protected: */ 64 | unsigned char HsmToLCA_(Hsm *me, State *target); 65 | void HsmExit_(Hsm *me, unsigned char toLca); 66 | /* get current state */ 67 | #define STATE_CURR(me_) (((Hsm *)me_)->curr) 68 | /* take start transition (no states need to be exited) */ 69 | #define STATE_START(me_, target_) (((Hsm *)me_)->next = (target_)) 70 | /* take a state transition (exit states up to the LCA) */ 71 | #define STATE_TRAN(me_, target_) if (1) { \ 72 | static unsigned char toLca_ = 0xFF; \ 73 | assert(((Hsm *)me_)->next == 0); \ 74 | if (toLca_ == 0xFF) \ 75 | toLca_ = HsmToLCA_((Hsm *)(me_), (target_)); \ 76 | HsmExit_((Hsm *)(me_), toLca_); \ 77 | ((Hsm *)(me_))->next = (target_); \ 78 | } else ((void)0) 79 | 80 | #define START_EVT ((Event)(-1)) 81 | #define ENTRY_EVT ((Event)(-2)) 82 | #define EXIT_EVT ((Event)(-3)) 83 | 84 | #endif /* hsm_h */ 85 | -------------------------------------------------------------------------------- /c/hsmtst.c: -------------------------------------------------------------------------------- 1 | /** hsmtest.c -- Hierarchical State Machine test harness. 2 | * This is an implementation of the example found in 3 | * Practical StateCharts in C/C++ by Miro Samek. 4 | * Intent of program is to exercise the state machine implementation. 5 | * An earlier implementation published in ESP August 2000 had errors 6 | * that were identified by kevin.fleming@philips.com 7 | * M. Samek 02-11-25 8 | */ 9 | 10 | #include 11 | #include 12 | #include "hsm.h" 13 | 14 | typedef struct HsmTest HsmTest; 15 | struct HsmTest { 16 | Hsm super; 17 | State s1; 18 | State s11; 19 | State s2; 20 | State s21; 21 | State s211; 22 | int foo; 23 | }; 24 | 25 | enum HsmTestEvents { 26 | A_SIG, B_SIG, C_SIG, D_SIG, E_SIG, F_SIG, G_SIG, H_SIG 27 | }; 28 | 29 | Msg const *HsmTest_top(HsmTest *me, Msg const *msg) { 30 | switch (msg->evt) { 31 | case START_EVT: 32 | printf("top-INIT;"); 33 | STATE_START(me, &me->s1); 34 | return 0; 35 | case ENTRY_EVT: 36 | printf("top-ENTRY;"); 37 | return 0; 38 | case EXIT_EVT: 39 | printf("top-EXIT;"); 40 | return 0; 41 | case E_SIG: 42 | printf("top-E;"); 43 | STATE_TRAN(me, &me->s211); 44 | return 0; 45 | } 46 | return msg; 47 | } 48 | 49 | Msg const *HsmTest_s1(HsmTest *me, Msg const *msg) { 50 | switch (msg->evt) { 51 | case START_EVT: 52 | printf("s1-INIT;"); 53 | STATE_START(me, &me->s11); 54 | return 0; 55 | case ENTRY_EVT: 56 | printf("s1-ENTRY;"); 57 | return 0; 58 | case EXIT_EVT: 59 | printf("s1-EXIT;"); 60 | return 0; 61 | case A_SIG: 62 | printf("s1-A;"); 63 | STATE_TRAN(me, &me->s1); 64 | return 0; 65 | case B_SIG: 66 | printf("s1-B;"); 67 | STATE_TRAN(me, &me->s11); 68 | return 0; 69 | case C_SIG: 70 | printf("s1-C;"); 71 | STATE_TRAN(me, &me->s2); 72 | return 0; 73 | case D_SIG: 74 | printf("s1-D;"); 75 | STATE_TRAN(me, &((Hsm *)me)->top); 76 | return 0; 77 | case F_SIG: 78 | printf("s1-F;"); 79 | STATE_TRAN(me, &me->s211); 80 | return 0; 81 | } 82 | return msg; 83 | } 84 | 85 | Msg const *HsmTest_s11(HsmTest *me, Msg const *msg) { 86 | switch (msg->evt) { 87 | case ENTRY_EVT: 88 | printf("s11-ENTRY;"); 89 | return 0; 90 | case EXIT_EVT: 91 | printf("s11-EXIT;"); 92 | return 0; 93 | case G_SIG: 94 | printf("s11-G;"); 95 | STATE_TRAN(me, &me->s211); 96 | return 0; 97 | case H_SIG: 98 | if (me->foo) { 99 | printf("s11-H;"); 100 | me->foo = 0; 101 | return 0; 102 | } 103 | break; 104 | } 105 | return msg; 106 | } 107 | 108 | Msg const *HsmTest_s2(HsmTest *me, Msg const *msg) { 109 | switch (msg->evt) { 110 | case START_EVT: 111 | printf("s2-INIT;"); 112 | STATE_START(me, &me->s21); 113 | return 0; 114 | case ENTRY_EVT: 115 | printf("s2-ENTRY;"); 116 | return 0; 117 | case EXIT_EVT: 118 | printf("s2-EXIT;"); 119 | return 0; 120 | case C_SIG: 121 | printf("s2-C;"); 122 | STATE_TRAN(me, &me->s1); 123 | return 0; 124 | case F_SIG: 125 | printf("s2-F;"); 126 | STATE_TRAN(me, &me->s11); 127 | return 0; 128 | } 129 | return msg; 130 | } 131 | 132 | Msg const *HsmTest_s21(HsmTest *me, Msg const *msg) { 133 | switch (msg->evt) { 134 | case START_EVT: 135 | printf("s21-INIT;"); 136 | STATE_START(me, &me->s211); 137 | return 0; 138 | case ENTRY_EVT: 139 | printf("s21-ENTRY;"); 140 | return 0; 141 | case EXIT_EVT: 142 | printf("s21-EXIT;"); 143 | return 0; 144 | case B_SIG: 145 | printf("s21-B;"); 146 | STATE_TRAN(me, &me->s211); 147 | return 0; 148 | case H_SIG: 149 | if (!me->foo) { 150 | printf("s21-H;"); 151 | me->foo = 1; 152 | STATE_TRAN(me, &me->s21); 153 | return 0; 154 | } 155 | break; 156 | } 157 | return msg; 158 | } 159 | 160 | Msg const *HsmTest_s211(HsmTest *me, Msg const *msg) { 161 | switch (msg->evt) { 162 | case ENTRY_EVT: 163 | printf("s211-ENTRY;"); 164 | return 0; 165 | case EXIT_EVT: 166 | printf("s211-EXIT;"); 167 | return 0; 168 | case D_SIG: 169 | printf("s211-D;"); 170 | STATE_TRAN(me, &me->s21); 171 | return 0; 172 | case G_SIG: 173 | printf("s211-G;"); 174 | STATE_TRAN(me, &((Hsm *)me)->top); 175 | return 0; 176 | } 177 | return msg; 178 | } 179 | 180 | void HsmTestCtor(HsmTest *me) { 181 | HsmCtor((Hsm *)me, "HsmTest", (EvtHndlr)HsmTest_top); 182 | StateCtor(&me->s1, "s1", &((Hsm *)me)->top, (EvtHndlr)HsmTest_s1); 183 | StateCtor(&me->s11, "s11", &me->s1, (EvtHndlr)HsmTest_s11); 184 | StateCtor(&me->s2, "s2", &((Hsm *)me)->top, (EvtHndlr)HsmTest_s2); 185 | StateCtor(&me->s21, "s21", &me->s2, (EvtHndlr)HsmTest_s21); 186 | StateCtor(&me->s211, "s211", &me->s21, (EvtHndlr)HsmTest_s211); 187 | me->foo = 0; 188 | } 189 | 190 | const Msg HsmTestMsg[] = { 191 | { A_SIG }, { B_SIG }, { C_SIG }, { D_SIG }, 192 | { E_SIG }, { F_SIG }, { G_SIG }, { H_SIG } 193 | }; 194 | 195 | int main() { 196 | HsmTest hsmTest; 197 | 198 | printf("Events:\n" 199 | "a-h for triggering events\n" 200 | "x to exit\n\n"); 201 | 202 | HsmTestCtor(&hsmTest); 203 | HsmOnStart((Hsm *)&hsmTest); 204 | for (;;) { 205 | char c; 206 | printf("\nEvent<-"); 207 | c = getc(stdin); 208 | getc(stdin); 209 | if (c < 'a' || 'h' < c) { 210 | break; 211 | } 212 | HsmOnEvent((Hsm *)&hsmTest, &HsmTestMsg[c - 'a']); 213 | } 214 | return 0; 215 | } 216 | -------------------------------------------------------------------------------- /c/make.bat: -------------------------------------------------------------------------------- 1 | gcc watch.c hsm.c -o watch -pedantic -Wall -Wextra 2 | 3 | gcc hsmtst.c hsm.c -o hsmtst -pedantic -Wall -Wextra 4 | -------------------------------------------------------------------------------- /c/watch.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Simple digital watch example 3 | * M. Samek, 01/07/00 4 | */ 5 | #include "hsm.h" 6 | 7 | #include 8 | #include 9 | 10 | typedef struct Watch Watch; 11 | struct Watch { 12 | Hsm super; 13 | State timekeeping, time, date; 14 | State setting, hour, minute, day, month; 15 | State *timekeepingHist; 16 | int tsec, tmin, thour, dday, dmonth; 17 | }; 18 | 19 | enum WatchEvents { 20 | Watch_MODE_EVT, 21 | Watch_SET_EVT, 22 | Watch_TICK_EVT 23 | }; 24 | 25 | /* lookup table for the days of a month */ 26 | static int const day_of_month_lut[] = { 27 | 0, /* unused month #0 */ 28 | 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 29 | }; 30 | 31 | void WatchShowTime(Watch *me) { 32 | printf("time: %2d:%02d:%02d", 33 | me->thour, me->tmin, me->tsec); 34 | } 35 | 36 | void WatchShowDate(Watch *me) { 37 | printf("date: %02d-%02d", me->dmonth, me->dday); 38 | } 39 | 40 | void WatchTick(Watch *me) { 41 | if (++me->tsec == 60) { 42 | me->tsec = 0; 43 | if (++me->tmin == 60) { 44 | me->tmin = 0; 45 | if (++me->thour == 24) { 46 | me->thour = 0; 47 | if (++me->dday > day_of_month_lut[me->dmonth]) { 48 | me->dday = 1; 49 | if (++me->dmonth == 13) { 50 | me->dmonth = 1; 51 | } 52 | } 53 | } 54 | } 55 | } 56 | } 57 | 58 | Msg const *Watch_top(Watch *me, Msg const *msg) { 59 | switch (msg->evt) { 60 | case START_EVT: 61 | STATE_START(me, &me->setting); 62 | return 0; 63 | } 64 | return msg; 65 | } 66 | 67 | Msg const *Watch_timekeeping(Watch *me, Msg const *msg) { 68 | switch (msg->evt) { 69 | case START_EVT: 70 | STATE_START(me, me->timekeepingHist); 71 | return 0; 72 | case EXIT_EVT: 73 | me->timekeepingHist = STATE_CURR(me); 74 | return 0; 75 | case Watch_SET_EVT: 76 | STATE_TRAN(me, &me->setting); 77 | printf("Watch::timekeeping-SET;"); 78 | return 0; 79 | case Watch_TICK_EVT: 80 | WatchTick(me); 81 | printf("Watch::timekeeping-TICK;"); 82 | return 0; 83 | } 84 | return msg; 85 | } 86 | 87 | Msg const *Watch_time(Watch *me, Msg const *msg) { 88 | switch (msg->evt) { 89 | case ENTRY_EVT: 90 | WatchShowTime(me); 91 | return 0; 92 | case Watch_MODE_EVT: 93 | STATE_TRAN(me, &me->date); 94 | printf("Watch::time-MODE;"); 95 | return 0; 96 | case Watch_TICK_EVT: 97 | printf("Watch::time-TICK;"); 98 | WatchTick(me); 99 | WatchShowTime(me); 100 | return 0; 101 | } 102 | return msg; 103 | } 104 | 105 | Msg const *Watch_date(Watch *me, Msg const *msg) { 106 | switch (msg->evt) { 107 | case ENTRY_EVT: 108 | WatchShowDate(me); 109 | return 0; 110 | case Watch_MODE_EVT: 111 | STATE_TRAN(me, &me->time); 112 | printf("Watch::date-MODE;"); 113 | return 0; 114 | case Watch_TICK_EVT: 115 | printf("Watch::date-TICK;"); 116 | WatchTick(me); 117 | WatchShowDate(me); 118 | return 0; 119 | } 120 | return msg; 121 | } 122 | 123 | Msg const *Watch_setting(Watch *me, Msg const *msg) { 124 | switch (msg->evt) { 125 | case START_EVT: 126 | STATE_START(me, &me->hour); 127 | printf("Watch::setting-START->hour;"); 128 | return 0; 129 | } 130 | return msg; 131 | } 132 | 133 | Msg const *Watch_hour(Watch *me, Msg const *msg) { 134 | switch (msg->evt) { 135 | case Watch_MODE_EVT: 136 | if (++me->thour == 60) { 137 | me->thour = 0; 138 | } 139 | WatchShowTime(me); 140 | return 0; 141 | case Watch_SET_EVT: 142 | STATE_TRAN(me, &me->minute); 143 | printf("Watch::hour-SET;"); 144 | return 0; 145 | } 146 | return msg; 147 | } 148 | 149 | Msg const *Watch_minute(Watch *me, Msg const *msg) { 150 | switch (msg->evt) { 151 | case Watch_MODE_EVT: 152 | if (++me->tmin == 60) { 153 | me->tmin = 0; 154 | } 155 | WatchShowTime(me); 156 | return 0; 157 | case Watch_SET_EVT: 158 | STATE_TRAN(me, &me->day); 159 | printf("Watch::minute-SET;"); 160 | return 0; 161 | } 162 | return msg; 163 | } 164 | 165 | Msg const *Watch_day(Watch *me, Msg const *msg) { 166 | switch (msg->evt) { 167 | case Watch_MODE_EVT: 168 | if (++me->dday > day_of_month_lut[me->dmonth]) { 169 | me->dday = 1; 170 | } 171 | WatchShowDate(me); 172 | return 0; 173 | case Watch_SET_EVT: 174 | STATE_TRAN(me, &me->month); 175 | printf("Watch::day-SET;"); 176 | return 0; 177 | } 178 | return msg; 179 | } 180 | 181 | Msg const *Watch_month(Watch *me, Msg const *msg) { 182 | switch (msg->evt) { 183 | case Watch_MODE_EVT: 184 | if (++me->dmonth > 12 ) { 185 | me->dmonth = 1; 186 | } 187 | WatchShowDate(me); 188 | return 0; 189 | case Watch_SET_EVT: 190 | STATE_TRAN(me, &me->timekeeping); 191 | printf("Watch::month-SET;"); 192 | return 0; 193 | } 194 | return msg; 195 | } 196 | 197 | void WatchCtor(Watch *me) { 198 | HsmCtor((Hsm *)me, "Watch", (EvtHndlr)Watch_top); 199 | StateCtor(&me->timekeeping, "timekeeping", 200 | &((Hsm *)me)->top, (EvtHndlr)Watch_timekeeping); 201 | StateCtor(&me->time, "time", &me->timekeeping, 202 | (EvtHndlr)Watch_time); 203 | StateCtor(&me->date, "date", &me->timekeeping, 204 | (EvtHndlr)Watch_date); 205 | StateCtor(&me->setting, "setting", &((Hsm *)me)->top, 206 | (EvtHndlr)Watch_setting); 207 | StateCtor(&me->hour, "hour", &me->setting, 208 | (EvtHndlr)Watch_hour); 209 | StateCtor(&me->minute, "minute", &me->setting, 210 | (EvtHndlr)Watch_minute); 211 | StateCtor(&me->day, "day", &me->setting, 212 | (EvtHndlr)Watch_day); 213 | StateCtor(&me->month, "month", &me->setting, 214 | (EvtHndlr)Watch_month); 215 | 216 | me->timekeepingHist = &me->time; 217 | me->tsec = me->tmin = me->thour = 0; 218 | me->dday = me->dmonth = 1; 219 | } 220 | 221 | const Msg watchMsg[] = { 222 | { Watch_MODE_EVT }, 223 | { Watch_SET_EVT }, 224 | { Watch_TICK_EVT } 225 | }; 226 | 227 | int main() { 228 | Watch watch; 229 | 230 | printf("Enter:\n" 231 | "0 for MODE_EVT\n" 232 | "1 for SET_EVT\n" 233 | "2 for TICK_EVT\n" 234 | "3+ to exit\n\n"); 235 | 236 | WatchCtor(&watch); 237 | HsmOnStart((Hsm *)&watch); 238 | for (;;) { 239 | char c; 240 | printf("\nEvent<-"); 241 | c = getc(stdin); 242 | getc(stdin); 243 | if (c < '0' || '2' < c) { 244 | break; 245 | } 246 | HsmOnEvent((Hsm *)&watch, &watchMsg[c - '0']); 247 | } 248 | return 0; 249 | } 250 | -------------------------------------------------------------------------------- /cpp/hsm.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // hsm.cpp -- Hierarchical State Machine implementation 3 | // 4 | // Copyright 2000 Miro Samek. All rights reserved. 5 | // 6 | // This software is licensed under the following open source MIT license: 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining 9 | // a copy of this software and associated documentation files (the "Software"), 10 | // to deal in the Software without restriction, including without limitation 11 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | // and/or sell copies of the Software, and to permit persons to whom the 13 | // Software is furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included 16 | // in all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 19 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 21 | // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | // DEALINGS IN THE SOFTWARE. 25 | // 26 | // Contact information: 27 | // miro@quantum-leaps.com 28 | // 29 | #include 30 | #include "hsm.hpp" 31 | 32 | static Msg const startMsg = { START_EVT }; 33 | static Msg const entryMsg = { ENTRY_EVT }; 34 | static Msg const exitMsg = { EXIT_EVT }; 35 | #define MAX_STATE_NESTING 8 36 | 37 | // State Ctor................................................................. 38 | State::State(char const *n, State *s, EvtHndlr h) 39 | : super(s), hndlr(h), name(n) 40 | {} 41 | 42 | // Hsm Ctor................................................................... 43 | Hsm::Hsm(char const *n, EvtHndlr topHndlr) 44 | : name(n), top("top", 0, topHndlr) 45 | {} 46 | 47 | // enter and start the top state.............................................. 48 | void Hsm::onStart() { 49 | curr = ⊤ 50 | next = 0; 51 | curr->onEvent(this, &entryMsg); 52 | while (curr->onEvent(this, &startMsg), next) { 53 | State *entryPath[MAX_STATE_NESTING]; 54 | State **trace = entryPath; 55 | State* s = next; 56 | *trace = 0; 57 | for (; s != curr; s = s->super) { 58 | *(++trace) = s; // trace path to target 59 | } 60 | while ((s = *trace--)) { // retrace entry from source 61 | s->onEvent(this, &entryMsg); 62 | } 63 | curr = next; 64 | next = 0; 65 | } 66 | } 67 | 68 | // state machine "engine"..................................................... 69 | void Hsm::onEvent(Msg const *msg) { 70 | State *entryPath[MAX_STATE_NESTING]; 71 | State **trace; 72 | for (State *s = curr; s; s = s->super) { 73 | source = s; // level of outermost event handler 74 | msg = s->onEvent(this, msg); 75 | if (msg == 0) { // processed? 76 | if (next) { // state transition taken? 77 | trace = entryPath; 78 | *trace = 0; 79 | for (s = next; s != curr; s = s->super) { 80 | *(++trace) = s; // trace path to target 81 | } 82 | while ((s = *trace--)) { // retrace entry from LCA 83 | s->onEvent(this, &entryMsg); 84 | } 85 | curr = next; 86 | next = 0; 87 | while (curr->onEvent(this, &startMsg), next) { 88 | trace = entryPath; 89 | *trace = 0; 90 | for (s = next; s != curr; s = s->super) { 91 | *(++trace) = s; // record path to target 92 | } 93 | while ((s = *trace--)) { // retrace the entry 94 | s->onEvent(this, &entryMsg); 95 | } 96 | curr = next; 97 | next = 0; 98 | } 99 | } 100 | break; // event processed 101 | } 102 | } 103 | } 104 | 105 | // exit current states and all superstates up to LCA ......................... 106 | void Hsm::exit_(unsigned char toLca) { 107 | State *s = curr; 108 | while (s != source) { 109 | s->onEvent(this, &exitMsg); 110 | s = s->super; 111 | } 112 | while ((toLca--)) { 113 | s->onEvent(this, &exitMsg); 114 | s = s->super; 115 | } 116 | curr = s; 117 | } 118 | 119 | // find # of levels to Least Common Ancestor.................................. 120 | unsigned char Hsm::toLCA_(State *target) { 121 | unsigned char toLca = 0; 122 | if (source == target) { 123 | return 1; 124 | } 125 | for (State *s = source; s; ++toLca, s = s->super) { 126 | for (State *t = target; t; t = t->super) { 127 | if (s == t) { 128 | return toLca; 129 | } 130 | } 131 | } 132 | return 0; 133 | } 134 | -------------------------------------------------------------------------------- /cpp/hsm.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // hsm.cpp -- Hierarchical State Machine implementation 3 | // 4 | // Copyright 2000 Miro Samek. All rights reserved. 5 | // 6 | // This software is licensed under the following open source MIT license: 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining 9 | // a copy of this software and associated documentation files (the "Software"), 10 | // to deal in the Software without restriction, including without limitation 11 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | // and/or sell copies of the Software, and to permit persons to whom the 13 | // Software is furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included 16 | // in all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 19 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 21 | // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | // DEALINGS IN THE SOFTWARE. 25 | // 26 | // Contact information: 27 | // miro@quantum-leaps.com 28 | // 29 | #ifndef HSM_HPP_ 30 | #define HSM_HPP_ 31 | 32 | typedef int Event; 33 | struct Msg { 34 | Event evt; 35 | }; 36 | 37 | class Hsm; // forward declaration 38 | typedef Msg const *(Hsm::*EvtHndlr)(Msg const *); 39 | 40 | class State { 41 | State *super; // pointer to superstate 42 | EvtHndlr hndlr; // state's handler function 43 | char const *name; 44 | public: 45 | State(char const *name, State *super, EvtHndlr hndlr); 46 | private: 47 | Msg const *onEvent(Hsm *ctx, Msg const *msg) { 48 | return (ctx->*hndlr)(msg); 49 | } 50 | friend class Hsm; 51 | }; 52 | 53 | class Hsm { // Hierarchical State Machine base class 54 | State *curr; // current state of the state machine 55 | char const *name; // pointer to static name 56 | protected: 57 | State *next; // next state (non 0 if transition taken) 58 | State *source; // source state during last transition 59 | State top; // top-most state object 60 | public: 61 | Hsm(char const *name, EvtHndlr topHndlr); // ctor 62 | void onStart(); // enter and start the top state 63 | void onEvent(Msg const *msg); // state machine "engine" 64 | protected: 65 | unsigned char toLCA_(State *target); 66 | void exit_(unsigned char toLca); 67 | State *STATE_CURR() { return curr; } 68 | void STATE_START(State *target) { 69 | //assert(next == 0); 70 | next = target; 71 | } 72 | }; 73 | 74 | # define STATE_TRAN(target_) do { \ 75 | static unsigned char toLca_ = 0xFF; \ 76 | assert(next == 0); \ 77 | if (toLca_ == 0xFF) \ 78 | toLca_ = toLCA_(target_); \ 79 | exit_(toLca_); \ 80 | next = (target_); \ 81 | } while (0) 82 | 83 | #define START_EVT ((Event)(-1)) 84 | #define ENTRY_EVT ((Event)(-2)) 85 | #define EXIT_EVT ((Event)(-3)) 86 | 87 | #endif // HSM_HPP_ 88 | -------------------------------------------------------------------------------- /cpp/hsmtst.cpp: -------------------------------------------------------------------------------- 1 | // hsmtest.c -- Hierarchical State Machine test harness. 2 | // This is an implementation of the example found in 3 | // Practical StateCharts in C/C++ by Miro Samek. 4 | // Intent of program is to exercise the state machine implementation. 5 | // An earlier implementation published in ESP August 2000 had errors 6 | // that were identified by kevin.fleming@philips.com 7 | // M. Samek 02-11-25 8 | // 9 | 10 | #include "hsm.hpp" 11 | 12 | #include 13 | #include 14 | 15 | class HsmTest : public Hsm { 16 | int myFoo; 17 | protected: 18 | State s1; 19 | State s11; 20 | State s2; 21 | State s21; 22 | State s211; 23 | public: 24 | HsmTest(); 25 | Msg const *topHndlr(Msg const *msg); 26 | Msg const *s1Hndlr(Msg const *msg); 27 | Msg const *s11Hndlr(Msg const *msg); 28 | Msg const *s2Hndlr(Msg const *msg); 29 | Msg const *s21Hndlr(Msg const *msg); 30 | Msg const *s211Hndlr(Msg const *msg); 31 | }; 32 | 33 | enum HsmTestEvents { 34 | A_SIG, B_SIG, C_SIG, D_SIG, E_SIG, F_SIG, G_SIG, H_SIG 35 | }; 36 | 37 | Msg const *HsmTest::topHndlr(Msg const *msg) { 38 | switch (msg->evt) { 39 | case START_EVT: 40 | printf("top-INIT;"); 41 | STATE_START(&s1); 42 | return 0; 43 | case ENTRY_EVT: 44 | printf("top-ENTRY;"); 45 | return 0; 46 | case EXIT_EVT: 47 | printf("top-EXIT;"); 48 | return 0; 49 | case E_SIG: 50 | printf("top-E;"); 51 | STATE_TRAN(&s211); 52 | return 0; 53 | } 54 | return msg; 55 | } 56 | 57 | Msg const *HsmTest::s1Hndlr(Msg const *msg) { 58 | switch (msg->evt) { 59 | case START_EVT: 60 | printf("s1-INIT;"); 61 | STATE_START(&s11); 62 | return 0; 63 | case ENTRY_EVT: 64 | printf("s1-ENTRY;"); 65 | return 0; 66 | case EXIT_EVT: 67 | printf("s1-EXIT;"); 68 | return 0; 69 | case A_SIG: 70 | printf("s1-A;"); 71 | STATE_TRAN(&s1); 72 | return 0; 73 | case B_SIG: 74 | printf("s1-B;"); 75 | STATE_TRAN(&s11); 76 | return 0; 77 | case C_SIG: 78 | printf("s1-C;"); 79 | STATE_TRAN(&s2); 80 | return 0; 81 | case D_SIG: 82 | printf("s1-D;"); 83 | STATE_TRAN(&top); 84 | return 0; 85 | case F_SIG: 86 | printf("s1-F;"); 87 | STATE_TRAN(&s211); 88 | return 0; 89 | } 90 | return msg; 91 | } 92 | 93 | Msg const *HsmTest::s11Hndlr(Msg const *msg) { 94 | switch (msg->evt) { 95 | case ENTRY_EVT: 96 | printf("s11-ENTRY;"); 97 | return 0; 98 | case EXIT_EVT: 99 | printf("s11-EXIT;"); 100 | return 0; 101 | case G_SIG: 102 | printf("s11-G;"); 103 | STATE_TRAN(&s211); 104 | return 0; 105 | case H_SIG: 106 | if (myFoo) { 107 | printf("s11-H;"); 108 | myFoo = 0; 109 | return 0; 110 | } 111 | break; 112 | } 113 | return msg; 114 | } 115 | 116 | Msg const *HsmTest::s2Hndlr(Msg const *msg) { 117 | switch (msg->evt) { 118 | case START_EVT: 119 | printf("s2-INIT;"); 120 | STATE_START(&s21); 121 | return 0; 122 | case ENTRY_EVT: 123 | printf("s2-ENTRY;"); 124 | return 0; 125 | case EXIT_EVT: 126 | printf("s2-EXIT;"); 127 | return 0; 128 | case C_SIG: 129 | printf("s2-C;"); 130 | STATE_TRAN(&s1); 131 | return 0; 132 | case F_SIG: 133 | printf("s2-F;"); 134 | STATE_TRAN(&s11); 135 | return 0; 136 | } 137 | return msg; 138 | } 139 | 140 | Msg const *HsmTest::s21Hndlr(Msg const *msg) { 141 | switch (msg->evt) { 142 | case START_EVT: 143 | printf("s21-INIT;"); 144 | STATE_START(&s211); 145 | return 0; 146 | case ENTRY_EVT: 147 | printf("s21-ENTRY;"); 148 | return 0; 149 | case EXIT_EVT: 150 | printf("s21-EXIT;"); 151 | return 0; 152 | case B_SIG: 153 | printf("s21-B;"); 154 | STATE_TRAN(&s211); 155 | return 0; 156 | case H_SIG: 157 | if (!myFoo) { 158 | printf("s21-H;"); 159 | myFoo = 1; 160 | STATE_TRAN(&s21); 161 | return 0; 162 | } 163 | break; 164 | } 165 | return msg; 166 | } 167 | 168 | Msg const *HsmTest::s211Hndlr(Msg const *msg) { 169 | switch (msg->evt) { 170 | case ENTRY_EVT: 171 | printf("s211-ENTRY;"); 172 | return 0; 173 | case EXIT_EVT: 174 | printf("s211-EXIT;"); 175 | return 0; 176 | case D_SIG: 177 | printf("s211-D;"); 178 | STATE_TRAN(&s21); 179 | return 0; 180 | case G_SIG: 181 | printf("s211-G;"); 182 | STATE_TRAN(&top); 183 | return 0; 184 | } 185 | return msg; 186 | } 187 | 188 | HsmTest::HsmTest() 189 | : Hsm("HsmTest", static_cast(&HsmTest::topHndlr)), 190 | s1("s1", &top, static_cast(&HsmTest::s1Hndlr)), 191 | s11("s11", &s1, static_cast(&HsmTest::s11Hndlr)), 192 | s2("s2", &top, static_cast(&HsmTest::s2Hndlr)), 193 | s21("s21", &s2, static_cast(&HsmTest::s21Hndlr)), 194 | s211("s211", &s21, static_cast(&HsmTest::s211Hndlr)) 195 | { 196 | myFoo = 0; 197 | } 198 | 199 | const Msg HsmTestMsg[] = { 200 | { A_SIG }, { B_SIG }, { C_SIG }, { D_SIG }, 201 | { E_SIG }, { F_SIG }, { G_SIG } ,{ H_SIG } 202 | }; 203 | 204 | int main() { 205 | HsmTest hsmTest; 206 | 207 | printf("Events:\n" 208 | "a-h for triggering events\n" 209 | "x to exit\n\n"); 210 | 211 | hsmTest.onStart(); 212 | for (;;) { 213 | int c; 214 | printf("\nEvent<-"); 215 | c = getc(stdin); 216 | getc(stdin); 217 | if (c < 'a' || 'h' < c) { 218 | break; 219 | } 220 | hsmTest.onEvent(&HsmTestMsg[c - 'a']); 221 | } 222 | return 0; 223 | } 224 | -------------------------------------------------------------------------------- /cpp/hsmtst.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio Version 16 3 | VisualStudioVersion = 16.0.30804.86 4 | MinimumVisualStudioVersion = 10.0.40219.1 5 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "hsmtst", "hsmtst.vcxproj", "{8CC465F7-872E-4D03-B93C-1B64858B4E11}" 6 | EndProject 7 | Global 8 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 9 | Debug|Win32 = Debug|Win32 10 | Debug|x64 = Debug|x64 11 | QSpy|Win32 = QSpy|Win32 12 | QSpy|x64 = QSpy|x64 13 | QSpy64|Win32 = QSpy64|Win32 14 | QSpy64|x64 = QSpy64|x64 15 | Release|Win32 = Release|Win32 16 | Release|x64 = Release|x64 17 | EndGlobalSection 18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 19 | {8CC465F7-872E-4D03-B93C-1B64858B4E11}.Debug|Win32.ActiveCfg = Debug|Win32 20 | {8CC465F7-872E-4D03-B93C-1B64858B4E11}.Debug|Win32.Build.0 = Debug|Win32 21 | {8CC465F7-872E-4D03-B93C-1B64858B4E11}.Debug|x64.ActiveCfg = Debug|x64 22 | {8CC465F7-872E-4D03-B93C-1B64858B4E11}.Debug|x64.Build.0 = Debug|x64 23 | {8CC465F7-872E-4D03-B93C-1B64858B4E11}.QSpy|Win32.ActiveCfg = QSpy|Win32 24 | {8CC465F7-872E-4D03-B93C-1B64858B4E11}.QSpy|Win32.Build.0 = QSpy|Win32 25 | {8CC465F7-872E-4D03-B93C-1B64858B4E11}.QSpy|x64.ActiveCfg = QSpy|x64 26 | {8CC465F7-872E-4D03-B93C-1B64858B4E11}.QSpy|x64.Build.0 = QSpy|x64 27 | {8CC465F7-872E-4D03-B93C-1B64858B4E11}.QSpy64|Win32.ActiveCfg = QSpy64|Win32 28 | {8CC465F7-872E-4D03-B93C-1B64858B4E11}.QSpy64|Win32.Build.0 = QSpy64|Win32 29 | {8CC465F7-872E-4D03-B93C-1B64858B4E11}.QSpy64|x64.ActiveCfg = QSpy64|x64 30 | {8CC465F7-872E-4D03-B93C-1B64858B4E11}.QSpy64|x64.Build.0 = QSpy64|x64 31 | {8CC465F7-872E-4D03-B93C-1B64858B4E11}.Release|Win32.ActiveCfg = Release|Win32 32 | {8CC465F7-872E-4D03-B93C-1B64858B4E11}.Release|Win32.Build.0 = Release|Win32 33 | {8CC465F7-872E-4D03-B93C-1B64858B4E11}.Release|x64.ActiveCfg = Release|x64 34 | {8CC465F7-872E-4D03-B93C-1B64858B4E11}.Release|x64.Build.0 = Release|x64 35 | EndGlobalSection 36 | GlobalSection(SolutionProperties) = preSolution 37 | HideSolutionNode = FALSE 38 | EndGlobalSection 39 | GlobalSection(ExtensibilityGlobals) = postSolution 40 | SolutionGuid = {435A4978-E6C4-4DB5-997D-40C45B465DD2} 41 | EndGlobalSection 42 | EndGlobal 43 | -------------------------------------------------------------------------------- /cpp/hsmtst.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Debug 10 | x64 11 | 12 | 13 | QSpy64 14 | Win32 15 | 16 | 17 | QSpy64 18 | x64 19 | 20 | 21 | QSpy 22 | x64 23 | 24 | 25 | Release 26 | Win32 27 | 28 | 29 | QSpy 30 | Win32 31 | 32 | 33 | Release 34 | x64 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | {8CC465F7-872E-4D03-B93C-1B64858B4E11} 46 | hsmtst 47 | Win32Proj 48 | 10.0 49 | 50 | 51 | 52 | Application 53 | NotSet 54 | v143 55 | 56 | 57 | Application 58 | NotSet 59 | v143 60 | 61 | 62 | Application 63 | NotSet 64 | v143 65 | 66 | 67 | Application 68 | NotSet 69 | v143 70 | 71 | 72 | Application 73 | NotSet 74 | true 75 | v143 76 | 77 | 78 | Application 79 | NotSet 80 | true 81 | v143 82 | 83 | 84 | Application 85 | NotSet 86 | v143 87 | 88 | 89 | Application 90 | NotSet 91 | v143 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | <_ProjectFileVersion>10.0.40219.1 123 | $(SolutionDir)$(Configuration)\ 124 | $(Configuration)\ 125 | true 126 | true 127 | $(SolutionDir)$(Configuration)\ 128 | $(Configuration)\ 129 | false 130 | false 131 | $(SolutionDir)$(Configuration)\ 132 | QSpy64\ 133 | $(Configuration)\ 134 | QSpy64\ 135 | true 136 | true 137 | true 138 | true 139 | 140 | 141 | QSpy64\ 142 | QSpy64\ 143 | 144 | 145 | 146 | Disabled 147 | .;../../../include;../../../ports/win32-qv;%(AdditionalIncludeDirectories) 148 | QP_CONFIG;WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 149 | false 150 | 151 | 152 | Default 153 | MultiThreaded 154 | NotUsing 155 | Level4 156 | ProgramDatabase 157 | 4127 158 | Default 159 | 160 | 161 | %(AdditionalDependencies) 162 | %(AdditionalLibraryDirectories) 163 | true 164 | Console 165 | MachineX86 166 | 167 | 168 | cmd /c "del $(OutDir)qstamp.obj" 169 | 170 | 171 | 172 | 173 | Disabled 174 | %(AdditionalIncludeDirectories) 175 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 176 | false 177 | 178 | 179 | Default 180 | MultiThreaded 181 | NotUsing 182 | EnableAllWarnings 183 | ProgramDatabase 184 | 4127 185 | Default 186 | stdcpp20 187 | stdc11 188 | false 189 | 190 | 191 | %(AdditionalDependencies) 192 | %(AdditionalLibraryDirectories) 193 | true 194 | Console 195 | 196 | 197 | cmd /c "del $(OutDir)qstamp.obj" 198 | 199 | 200 | 201 | 202 | MaxSpeed 203 | true 204 | .;../../../include;../../../ports/win32-qv;%(AdditionalIncludeDirectories) 205 | NDEBUG;QP_CONFIG;WIN32;_CONSOLE;%(PreprocessorDefinitions) 206 | false 207 | 208 | 209 | Default 210 | MultiThreaded 211 | true 212 | NotUsing 213 | Level4 214 | ProgramDatabase 215 | 4127 216 | 217 | 218 | %(AdditionalDependencies) 219 | %(AdditionalLibraryDirectories) 220 | true 221 | Console 222 | true 223 | true 224 | MachineX86 225 | 226 | 227 | cmd /c "del $(OutDir)qstamp.obj" 228 | 229 | 230 | 231 | 232 | MaxSpeed 233 | true 234 | %(AdditionalIncludeDirectories) 235 | NDEBUG;WIN32;_CONSOLE;%(PreprocessorDefinitions) 236 | false 237 | 238 | 239 | Default 240 | MultiThreaded 241 | true 242 | NotUsing 243 | Level4 244 | ProgramDatabase 245 | 4127 246 | 247 | 248 | %(AdditionalDependencies) 249 | %(AdditionalLibraryDirectories) 250 | true 251 | Console 252 | true 253 | true 254 | 255 | 256 | cmd /c "del $(OutDir)qstamp.obj" 257 | 258 | 259 | 260 | 261 | Disabled 262 | .;../../../include;../../../ports/win32-qv;$(QTOOLS)/qspy/include;%(AdditionalIncludeDirectories) 263 | Q_SPY;QP_CONFIG;WIN32;_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) 264 | false 265 | 266 | 267 | Default 268 | MultiThreaded 269 | NotUsing 270 | Level4 271 | ProgramDatabase 272 | 4127 273 | 274 | 275 | ws2_32.lib;%(AdditionalDependencies) 276 | %(AdditionalLibraryDirectories) 277 | true 278 | Console 279 | MachineX86 280 | 281 | 282 | cmd /c "del $(OutDir)qstamp.obj" 283 | 284 | 285 | 286 | 287 | Disabled 288 | $(QTOOLS)/qspy/include;%(AdditionalIncludeDirectories) 289 | Q_SPY;WIN32;_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) 290 | false 291 | 292 | 293 | Default 294 | MultiThreaded 295 | NotUsing 296 | Level4 297 | ProgramDatabase 298 | 4127 299 | 300 | 301 | %(AdditionalDependencies) 302 | %(AdditionalLibraryDirectories) 303 | true 304 | Console 305 | 306 | 307 | cmd /c "del $(OutDir)qstamp.obj" 308 | 309 | 310 | 311 | 312 | Disabled 313 | .;../../../include;../../../ports/win32-qv;$(QTOOLS)/qspy/include;%(AdditionalIncludeDirectories) 314 | Q_SPY;QP_CONFIG;WIN32;_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) 315 | false 316 | 317 | 318 | Default 319 | MultiThreaded 320 | NotUsing 321 | Level4 322 | ProgramDatabase 323 | 4127 324 | 325 | 326 | ws2_32.lib;%(AdditionalDependencies) 327 | %(AdditionalLibraryDirectories) 328 | true 329 | Console 330 | MachineX64 331 | 332 | 333 | cmd /c "del $(OutDir)qstamp.obj" 334 | 335 | 336 | 337 | 338 | Disabled 339 | $(QTOOLS)/qspy/include;%(AdditionalIncludeDirectories) 340 | Q_SPY;WIN32;_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) 341 | false 342 | 343 | 344 | Default 345 | MultiThreaded 346 | NotUsing 347 | Level4 348 | ProgramDatabase 349 | 4127 350 | 351 | 352 | %(AdditionalDependencies) 353 | %(AdditionalLibraryDirectories) 354 | true 355 | Console 356 | 357 | 358 | cmd /c "del $(OutDir)qstamp.obj" 359 | 360 | 361 | 362 | 363 | 364 | -------------------------------------------------------------------------------- /cpp/hsmtst.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /cpp/make.bat: -------------------------------------------------------------------------------- 1 | g++ watch.cpp hsm.cpp -o watch -pedantic -Wall -Wextra 2 | 3 | g++ hsmtst.cpp hsm.cpp -o hsmtst -pedantic -Wall -Wextra 4 | -------------------------------------------------------------------------------- /cpp/watch.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Simple digital watch example 3 | // M. Samek, 01/07/00 4 | // 5 | #include "hsm.hpp" 6 | 7 | #include 8 | #include 9 | 10 | class Watch : public Hsm { 11 | protected: 12 | State timekeeping, time, date; 13 | State setting, hour, minute, day, month; 14 | State *timekeepingHist; 15 | private: 16 | int tsec, tmin, thour, dday, dmonth; 17 | 18 | public: 19 | Watch(); 20 | Msg const *topHndlr(Msg const *msg); 21 | Msg const *timekeepingHndlr(Msg const *msg); 22 | Msg const *timeHndlr(Msg const *msg); 23 | Msg const *dateHndlr(Msg const *msg); 24 | Msg const *settingHndlr(Msg const *msg); 25 | Msg const *hourHndlr(Msg const *msg); 26 | Msg const *minuteHndlr(Msg const *msg); 27 | Msg const *dayHndlr(Msg const *msg); 28 | Msg const *monthHndlr(Msg const *msg); 29 | void tick(); 30 | void showTime(); 31 | void showDate(); 32 | }; 33 | 34 | enum WatchEvents { 35 | Watch_MODE_EVT, 36 | Watch_SET_EVT, 37 | Watch_TICK_EVT 38 | }; 39 | 40 | // lookup table for the days of a month 41 | static int const day_of_month_lut[] = { 42 | 0, /* unused month #0 */ 43 | 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 44 | }; 45 | 46 | void Watch::showTime() { 47 | printf("time: %2d:%02d:%02d", thour, tmin, tsec); 48 | } 49 | 50 | void Watch::showDate() { 51 | printf("date: %02d-%02d", dmonth, dday); 52 | } 53 | 54 | void Watch::tick() { 55 | if (++tsec == 60) { 56 | tsec = 0; 57 | if (++tmin == 60) { 58 | tmin = 0; 59 | if (++thour == 24) { 60 | thour = 0; 61 | if (++dday > day_of_month_lut[dmonth]) { 62 | dday = 1; 63 | if (++dmonth == 13) { 64 | dmonth = 1; 65 | } 66 | } 67 | } 68 | } 69 | } 70 | } 71 | 72 | Msg const *Watch::topHndlr(Msg const *msg) { 73 | switch (msg->evt) { 74 | case START_EVT: 75 | STATE_START(&setting); 76 | return 0; 77 | } 78 | return msg; 79 | } 80 | 81 | Msg const *Watch::timekeepingHndlr(Msg const *msg) { 82 | switch (msg->evt) { 83 | case START_EVT: 84 | STATE_START(timekeepingHist); 85 | return 0; 86 | case EXIT_EVT: 87 | timekeepingHist = STATE_CURR(); 88 | return 0; 89 | case Watch_SET_EVT: 90 | STATE_TRAN(&setting); 91 | printf("Watch::timekeeping-SET;"); 92 | return 0; 93 | case Watch_TICK_EVT: 94 | tick(); 95 | printf("Watch::timekeeping-TICK;"); 96 | return 0; 97 | } 98 | return msg; 99 | } 100 | 101 | Msg const *Watch::timeHndlr(Msg const *msg) { 102 | switch (msg->evt) { 103 | case ENTRY_EVT: 104 | showTime(); 105 | return 0; 106 | case Watch_MODE_EVT: 107 | STATE_TRAN(&date); 108 | printf("Watch::time-MODE;"); 109 | return 0; 110 | case Watch_TICK_EVT: 111 | printf("Watch::time-TICK;"); 112 | tick(); 113 | showTime(); 114 | return 0; 115 | } 116 | return msg; 117 | } 118 | 119 | Msg const *Watch::dateHndlr(Msg const *msg) { 120 | switch (msg->evt) { 121 | case ENTRY_EVT: 122 | showDate(); 123 | return 0; 124 | case Watch_MODE_EVT: 125 | STATE_TRAN(&time); 126 | printf("Watch::date-MODE;"); 127 | return 0; 128 | case Watch_TICK_EVT: 129 | printf("Watch::date-TICK;"); 130 | tick(); 131 | showDate(); 132 | return 0; 133 | } 134 | return msg; 135 | } 136 | 137 | Msg const *Watch::settingHndlr(Msg const *msg) { 138 | switch (msg->evt) { 139 | case START_EVT: 140 | STATE_START(&hour); 141 | printf("Watch::setting-START->hour;"); 142 | return 0; 143 | } 144 | return msg; 145 | } 146 | 147 | Msg const *Watch::hourHndlr(Msg const *msg) { 148 | switch (msg->evt) { 149 | case Watch_MODE_EVT: 150 | if (++thour == 60) { 151 | thour = 0; 152 | } 153 | showTime(); 154 | return 0; 155 | case Watch_SET_EVT: 156 | STATE_TRAN(&minute); 157 | printf("Watch::hour-SET;"); 158 | return 0; 159 | } 160 | return msg; 161 | } 162 | 163 | Msg const *Watch::minuteHndlr(Msg const *msg) { 164 | switch (msg->evt) { 165 | case Watch_MODE_EVT: 166 | if (++tmin == 60) { 167 | tmin = 0; 168 | } 169 | showTime(); 170 | return 0; 171 | case Watch_SET_EVT: 172 | STATE_TRAN(&day); 173 | return 0; 174 | } 175 | return msg; 176 | } 177 | 178 | Msg const *Watch::dayHndlr(Msg const *msg) { 179 | switch (msg->evt) { 180 | case Watch_MODE_EVT: 181 | if (++dday > day_of_month_lut[dmonth]) { 182 | dday = 1; 183 | } 184 | showDate(); 185 | return 0; 186 | case Watch_SET_EVT: 187 | STATE_TRAN(&month); 188 | printf("Watch::day-SET;"); 189 | return 0; 190 | } 191 | return msg; 192 | } 193 | 194 | Msg const *Watch::monthHndlr(Msg const *msg) { 195 | switch (msg->evt) { 196 | case Watch_MODE_EVT: 197 | if (++dmonth > 12 ) { 198 | dmonth = 1; 199 | } 200 | showDate(); 201 | return 0; 202 | case Watch_SET_EVT: 203 | STATE_TRAN(&timekeeping); 204 | printf("Watch::month-SET;"); 205 | return 0; 206 | } 207 | return msg; 208 | } 209 | 210 | Watch::Watch() 211 | : Hsm("Watch", static_cast(&Watch::topHndlr)), 212 | timekeeping("timekeeping", &top, static_cast(&Watch::timekeepingHndlr)), 213 | time("time", &timekeeping, static_cast(&Watch::timeHndlr)), 214 | date("date", &timekeeping, static_cast(&Watch::dateHndlr)), 215 | setting("setting", &top, static_cast(&Watch::settingHndlr)), 216 | hour("hour", &setting, static_cast(&Watch::hourHndlr)), 217 | minute("minute", &setting, static_cast(&Watch::minuteHndlr)), 218 | day("day", &setting, static_cast(&Watch::dayHndlr)), 219 | month("month", &setting, static_cast(&Watch::monthHndlr)), 220 | tsec(0), tmin(0), thour(0), dday(1), dmonth(1) 221 | { 222 | timekeepingHist = &time; 223 | } 224 | 225 | const Msg watchMsg[] = { 226 | { Watch_MODE_EVT }, 227 | { Watch_SET_EVT }, 228 | { Watch_TICK_EVT } 229 | }; 230 | 231 | int main() { 232 | Watch watch; 233 | 234 | printf("Events:\n" 235 | "0 for MODE_EVT\n" 236 | "1 for SET_EVT\n" 237 | "2 for TICK_EVT\n" 238 | "3+ to exit\n\n"); 239 | 240 | watch.onStart(); 241 | for (;;) { 242 | int c; 243 | printf("\nEvent<-"); 244 | c = getc(stdin); 245 | getc(stdin); 246 | if (c < '0' || '2' < c) { 247 | break; 248 | } 249 | watch.onEvent(&watchMsg[c - '0']); 250 | } 251 | return 0; 252 | } 253 | -------------------------------------------------------------------------------- /cpp/watch.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio Version 16 3 | VisualStudioVersion = 16.0.30804.86 4 | MinimumVisualStudioVersion = 10.0.40219.1 5 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "watch", "watch.vcxproj", "{8CC465F7-872E-4D03-B93C-1B64858B4E11}" 6 | EndProject 7 | Global 8 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 9 | Debug|Win32 = Debug|Win32 10 | Debug|x64 = Debug|x64 11 | QSpy|Win32 = QSpy|Win32 12 | QSpy|x64 = QSpy|x64 13 | QSpy64|Win32 = QSpy64|Win32 14 | QSpy64|x64 = QSpy64|x64 15 | Release|Win32 = Release|Win32 16 | Release|x64 = Release|x64 17 | EndGlobalSection 18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 19 | {8CC465F7-872E-4D03-B93C-1B64858B4E11}.Debug|Win32.ActiveCfg = Debug|Win32 20 | {8CC465F7-872E-4D03-B93C-1B64858B4E11}.Debug|Win32.Build.0 = Debug|Win32 21 | {8CC465F7-872E-4D03-B93C-1B64858B4E11}.Debug|x64.ActiveCfg = Debug|x64 22 | {8CC465F7-872E-4D03-B93C-1B64858B4E11}.Debug|x64.Build.0 = Debug|x64 23 | {8CC465F7-872E-4D03-B93C-1B64858B4E11}.QSpy|Win32.ActiveCfg = QSpy|Win32 24 | {8CC465F7-872E-4D03-B93C-1B64858B4E11}.QSpy|Win32.Build.0 = QSpy|Win32 25 | {8CC465F7-872E-4D03-B93C-1B64858B4E11}.QSpy|x64.ActiveCfg = QSpy|x64 26 | {8CC465F7-872E-4D03-B93C-1B64858B4E11}.QSpy|x64.Build.0 = QSpy|x64 27 | {8CC465F7-872E-4D03-B93C-1B64858B4E11}.QSpy64|Win32.ActiveCfg = QSpy64|Win32 28 | {8CC465F7-872E-4D03-B93C-1B64858B4E11}.QSpy64|Win32.Build.0 = QSpy64|Win32 29 | {8CC465F7-872E-4D03-B93C-1B64858B4E11}.QSpy64|x64.ActiveCfg = QSpy64|x64 30 | {8CC465F7-872E-4D03-B93C-1B64858B4E11}.QSpy64|x64.Build.0 = QSpy64|x64 31 | {8CC465F7-872E-4D03-B93C-1B64858B4E11}.Release|Win32.ActiveCfg = Release|Win32 32 | {8CC465F7-872E-4D03-B93C-1B64858B4E11}.Release|Win32.Build.0 = Release|Win32 33 | {8CC465F7-872E-4D03-B93C-1B64858B4E11}.Release|x64.ActiveCfg = Release|x64 34 | {8CC465F7-872E-4D03-B93C-1B64858B4E11}.Release|x64.Build.0 = Release|x64 35 | EndGlobalSection 36 | GlobalSection(SolutionProperties) = preSolution 37 | HideSolutionNode = FALSE 38 | EndGlobalSection 39 | GlobalSection(ExtensibilityGlobals) = postSolution 40 | SolutionGuid = {435A4978-E6C4-4DB5-997D-40C45B465DD2} 41 | EndGlobalSection 42 | EndGlobal 43 | -------------------------------------------------------------------------------- /cpp/watch.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Debug 10 | x64 11 | 12 | 13 | QSpy64 14 | Win32 15 | 16 | 17 | QSpy64 18 | x64 19 | 20 | 21 | QSpy 22 | x64 23 | 24 | 25 | Release 26 | Win32 27 | 28 | 29 | QSpy 30 | Win32 31 | 32 | 33 | Release 34 | x64 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | {8CC465F7-872E-4D03-B93C-1B64858B4E11} 46 | watch 47 | Win32Proj 48 | 10.0 49 | 50 | 51 | 52 | Application 53 | NotSet 54 | v143 55 | 56 | 57 | Application 58 | NotSet 59 | v143 60 | 61 | 62 | Application 63 | NotSet 64 | v143 65 | 66 | 67 | Application 68 | NotSet 69 | v143 70 | 71 | 72 | Application 73 | NotSet 74 | true 75 | v143 76 | 77 | 78 | Application 79 | NotSet 80 | true 81 | v143 82 | 83 | 84 | Application 85 | NotSet 86 | v143 87 | 88 | 89 | Application 90 | NotSet 91 | v143 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | <_ProjectFileVersion>10.0.40219.1 123 | $(SolutionDir)$(Configuration)\ 124 | $(Configuration)\ 125 | true 126 | true 127 | $(SolutionDir)$(Configuration)\ 128 | $(Configuration)\ 129 | false 130 | false 131 | $(SolutionDir)$(Configuration)\ 132 | QSpy64\ 133 | $(Configuration)\ 134 | QSpy64\ 135 | true 136 | true 137 | true 138 | true 139 | 140 | 141 | QSpy64\ 142 | QSpy64\ 143 | 144 | 145 | 146 | Disabled 147 | .;../../../include;../../../ports/win32-qv;%(AdditionalIncludeDirectories) 148 | QP_CONFIG;WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 149 | false 150 | 151 | 152 | Default 153 | MultiThreaded 154 | NotUsing 155 | Level4 156 | ProgramDatabase 157 | 4127 158 | Default 159 | 160 | 161 | %(AdditionalDependencies) 162 | %(AdditionalLibraryDirectories) 163 | true 164 | Console 165 | MachineX86 166 | 167 | 168 | cmd /c "del $(OutDir)qstamp.obj" 169 | 170 | 171 | 172 | 173 | Disabled 174 | %(AdditionalIncludeDirectories) 175 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 176 | false 177 | 178 | 179 | Default 180 | MultiThreaded 181 | NotUsing 182 | EnableAllWarnings 183 | ProgramDatabase 184 | 4127 185 | Default 186 | stdcpp20 187 | stdc11 188 | false 189 | 190 | 191 | %(AdditionalDependencies) 192 | %(AdditionalLibraryDirectories) 193 | true 194 | Console 195 | 196 | 197 | cmd /c "del $(OutDir)qstamp.obj" 198 | 199 | 200 | 201 | 202 | MaxSpeed 203 | true 204 | .;../../../include;../../../ports/win32-qv;%(AdditionalIncludeDirectories) 205 | NDEBUG;QP_CONFIG;WIN32;_CONSOLE;%(PreprocessorDefinitions) 206 | false 207 | 208 | 209 | Default 210 | MultiThreaded 211 | true 212 | NotUsing 213 | Level4 214 | ProgramDatabase 215 | 4127 216 | 217 | 218 | %(AdditionalDependencies) 219 | %(AdditionalLibraryDirectories) 220 | true 221 | Console 222 | true 223 | true 224 | MachineX86 225 | 226 | 227 | cmd /c "del $(OutDir)qstamp.obj" 228 | 229 | 230 | 231 | 232 | MaxSpeed 233 | true 234 | %(AdditionalIncludeDirectories) 235 | NDEBUG;WIN32;_CONSOLE;%(PreprocessorDefinitions) 236 | false 237 | 238 | 239 | Default 240 | MultiThreaded 241 | true 242 | NotUsing 243 | Level4 244 | ProgramDatabase 245 | 4127 246 | 247 | 248 | %(AdditionalDependencies) 249 | %(AdditionalLibraryDirectories) 250 | true 251 | Console 252 | true 253 | true 254 | 255 | 256 | cmd /c "del $(OutDir)qstamp.obj" 257 | 258 | 259 | 260 | 261 | Disabled 262 | .;../../../include;../../../ports/win32-qv;$(QTOOLS)/qspy/include;%(AdditionalIncludeDirectories) 263 | Q_SPY;QP_CONFIG;WIN32;_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) 264 | false 265 | 266 | 267 | Default 268 | MultiThreaded 269 | NotUsing 270 | Level4 271 | ProgramDatabase 272 | 4127 273 | 274 | 275 | ws2_32.lib;%(AdditionalDependencies) 276 | %(AdditionalLibraryDirectories) 277 | true 278 | Console 279 | MachineX86 280 | 281 | 282 | cmd /c "del $(OutDir)qstamp.obj" 283 | 284 | 285 | 286 | 287 | Disabled 288 | $(QTOOLS)/qspy/include;%(AdditionalIncludeDirectories) 289 | Q_SPY;WIN32;_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) 290 | false 291 | 292 | 293 | Default 294 | MultiThreaded 295 | NotUsing 296 | Level4 297 | ProgramDatabase 298 | 4127 299 | 300 | 301 | %(AdditionalDependencies) 302 | %(AdditionalLibraryDirectories) 303 | true 304 | Console 305 | 306 | 307 | cmd /c "del $(OutDir)qstamp.obj" 308 | 309 | 310 | 311 | 312 | Disabled 313 | .;../../../include;../../../ports/win32-qv;$(QTOOLS)/qspy/include;%(AdditionalIncludeDirectories) 314 | Q_SPY;QP_CONFIG;WIN32;_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) 315 | false 316 | 317 | 318 | Default 319 | MultiThreaded 320 | NotUsing 321 | Level4 322 | ProgramDatabase 323 | 4127 324 | 325 | 326 | ws2_32.lib;%(AdditionalDependencies) 327 | %(AdditionalLibraryDirectories) 328 | true 329 | Console 330 | MachineX64 331 | 332 | 333 | cmd /c "del $(OutDir)qstamp.obj" 334 | 335 | 336 | 337 | 338 | Disabled 339 | $(QTOOLS)/qspy/include;%(AdditionalIncludeDirectories) 340 | Q_SPY;WIN32;_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) 341 | false 342 | 343 | 344 | Default 345 | MultiThreaded 346 | NotUsing 347 | Level4 348 | ProgramDatabase 349 | 4127 350 | 351 | 352 | %(AdditionalDependencies) 353 | %(AdditionalLibraryDirectories) 354 | true 355 | Console 356 | 357 | 358 | cmd /c "del $(OutDir)qstamp.obj" 359 | 360 | 361 | 362 | 363 | 364 | -------------------------------------------------------------------------------- /cpp/watch.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /img/qhsmtst.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuantumLeaps/State-Oriented-Programming/7c5e433655921f98b8722a601df9e2efbc2988d4/img/qhsmtst.jpg -------------------------------------------------------------------------------- /img/thumbnail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuantumLeaps/State-Oriented-Programming/7c5e433655921f98b8722a601df9e2efbc2988d4/img/thumbnail.jpg -------------------------------------------------------------------------------- /img/watch.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuantumLeaps/State-Oriented-Programming/7c5e433655921f98b8722a601df9e2efbc2988d4/img/watch.jpg --------------------------------------------------------------------------------