From 5fd2116943163be2e16e5cc6ff02f5897a202253 Mon Sep 17 00:00:00 2001 From: Vladislav Slobodenyuk Date: Fri, 30 Jan 2026 19:45:32 +0900 Subject: [PATCH] Created the simple-state-machines library. --- load/simple-state-machines.el | 176 +++++++++++++++++++++++++++++++++ load/simple-state-machines.elc | Bin 0 -> 3819 bytes load/zpg.el | 2 +- load/zpg.elc | Bin 25275 -> 25457 bytes 4 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 load/simple-state-machines.el create mode 100644 load/simple-state-machines.elc diff --git a/load/simple-state-machines.el b/load/simple-state-machines.el new file mode 100644 index 0000000..b7f213b --- /dev/null +++ b/load/simple-state-machines.el @@ -0,0 +1,176 @@ +;;; simple-state-machines.el --- My simple state machine macro. -*- lexical-binding: t -*- + +;; Author: iamtoaster +;; Version: 0.1 +;; Package-Requires: (cl-lib) +;; Homepage: +;; Keywords: FSM, Macro + +;; This file is not part of GNU Emacs + +;; This program is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; This is my implementation of simple finite state machines. This package +;; provides a defstate-machine macro and an update-state function. They can be +;; combined to create a simple FSM with three states like so: +;; +;; (ssm-defstate-machine test +;; :states (one two three) +;; :initial-state three +;; :transitions ((one . two) +;; (two . three) +;; (three . one)) +;; :handlers ((two . (lambda (name) (ssm-update-state name 'three))) +;; (three . (lambda (name) (ssm-update-state name 'one))) +;; (one . (lambda (name) (ssm-update-state name 'two))))) +;; +;; The code above will create a state machine with three states, appropriately +;; named 'one', 'two' and 'three', by specifying them after the :states key. The +;; initial state will be set to state 'three'. If no initial state is provided, +;; the first state in the list will be used automatically. +;; +;; Then, we define valid transitions between states using the :transitions key. +;; If we try to change the state in a way that is not defined in this alist, +;; `update-state' will signal an error. +;; +;; Finally, we specify handlers for states using the :handlers key. See +;; `defstate-machine' for information. +;; +;; This macro call will provide us with the following variables: +;; 'test-state-table' -- Stores the state list, verbatim. +;; 'test-transition-table' -- Stores the valid transitions, verbatim. +;; 'test-current-state' -- Stores the current state. +;; 'test-state-data' -- Optional data for the current state, initialized to nil. +;; 'test-state-handlers' -- Stores the mapping from the states to their handlers, +;; verbatim. +;; +;; As well as the following functions: +;; 'test-step' -- Calls the handler for the current state. +;; 'test-set-state' -- Tries to set the current state to the provided one, and +;; signals an error if the transition is invalid. + +;;; Code: + +(require 'cl-lib) + +(defmacro ssm-defstate-machine (name &rest args) + "Defines a state machine with name NAME, STATES and TRANSITIONS between +them. If INITIAL-STATE is nil, the first state will be used. + +Arguments are specified as keyword/argument pairs. The following +arguments are defined: + +:states LIST -- LIST should be a list of symbols each representing a +state. LIST will not be evaluated. + +:transitions ALIST -- ALIST should be an alist of the form (FROM . TO) +where both FROM and TO are names of states available in the :states LIST. + +:handlers ALIST -- ALIST should be an alist of the form (STATE . +HANDLER) where STATE is one of the states in the :states LIST and +HANDLER is a function that can be called by funcall. It is advised to +provide a handler for all of the state, because an error will be +signalled if you try to step on a state with no handler. + +The following optional arguments are defined: + +:initial-state SYMBOL -- If SYMBOL is non-nil and present in the :states +LIST, it will be set as the initial state of this state machine. +Otherwise, the first state in the :states LIST will be used. + +(fn NAME &key INITIAL-STATE STATES TRANSITIONS HANDLERS)" + (declare (indent defun)) + (let ((machine-name (symbol-name name)) + (states (plist-get args :states)) + (transitions (plist-get args :transitions)) + (handlers (plist-get args :handlers)) + (initial-state (plist-get args :initial-state))) + (if (not initial-state) + (setq initial-state (car states))) + (cl-assert (cl-find initial-state states) t + "Cannot find the initial state in the state list.") + (dolist (entry handlers) + (cl-assert (cl-find (car entry) states)) t) + (dolist (entry transitions) + (cl-assert (and (cl-find (car entry) states) + (cl-find (cdr entry) states)) + nil "Invalid transition: Either the source or the target state does not exist.")) + (let ((state-table (intern (concat machine-name "-state-table"))) + (transition-table (intern (concat machine-name "-transition-table"))) + (current-state (intern (concat machine-name "-current-state"))) + (state-data (intern (concat machine-name "-state-data"))) + (step-fn (intern (concat machine-name "-step"))) + (state-set-fn (intern (concat machine-name "-set-state"))) + (handlers-table (intern (concat machine-name "-state-handlers")))) + `(progn + (defvar ,state-table ',states + ,(concat "The state table of the " machine-name " state machine.")) + (defvar ,transition-table ',transitions + ,(concat "The transitions table of the " machine-name " state machine.\n +Each entry of this alist has the form (FROM . TO).")) + (defvar ,current-state ',initial-state + ,(concat "The current state of the " machine-name " state machine.")) + (defvar ,state-data nil + ,(concat "The data of the current state of the " machine-name " state machine.")) + (defvar ,handlers-table ',handlers + ,(concat "An alist mapping states of the " + machine-name + " state machine to their respective handlers.")) + ,@(let ((handler (make-symbol "handler")) + (prev-state (make-symbol "prev-state"))) + `((defun ,step-fn () + ,(concat "The stepping function of the " + machine-name + " state machine.\n\nIt tries to find a handler for the current state (see `" + (symbol-name current-state) + "' and\n`" + (symbol-name handlers-table) + "'),and then call it with the name of this state machine\nas its only argument.") + (let ((,handler (alist-get ,current-state ,handlers-table)) + (,prev-state ,current-state)) + (cl-assert ,handler nil "There is no handler for the current state") + (funcall ,handler ,machine-name) + (cl-assert + (cl-find + (cons ,prev-state ,current-state) + ,transition-table + :test 'equal) + "Invalid state transition. State machine is now in inconsistent state."))) + (defun ,state-set-fn (state &optional new-data) + ,(concat + "Sets the state of " + machine-name + " state machine. Signals an error if the state transition\nis invalid, see `" + (symbol-name transition-table) + "' for a list of valid transitions.") + (cl-assert + (cl-find + (cons ,current-state state) + ,transition-table + :test 'equal) t + "Invalid state transition.") + (setq ,current-state state + ,state-data new-data)))))))) + +(defun ssm-update-state (machine new-state &optional new-data) + "Updates the state of the state machine MACHINE. Signals an error if the +state transition is invalid." + (let ((state-fn + (intern-soft (concat machine "-set-state")))) + (funcall state-fn new-state new-data))) + +(provide 'simple-state-machines) +;;; simple-state-machines.el ends here diff --git a/load/simple-state-machines.elc b/load/simple-state-machines.elc new file mode 100644 index 0000000000000000000000000000000000000000..905a51f2acb49eda40f9b41d2f631d0e9efa1d48 GIT binary patch literal 3819 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