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 0000000..905a51f Binary files /dev/null and b/load/simple-state-machines.elc differ diff --git a/load/zpg.el b/load/zpg.el index 30e24e6..ce692cc 100644 --- a/load/zpg.el +++ b/load/zpg.el @@ -1,6 +1,6 @@ ;;; zpg.el --- A zero player game -*- lexical-binding: t; -*- -;; Author: Dizzar +;; Author: iamtoaster ;; Version: 0.0.0 ;; Package-Requires: () ;; Homepage: diff --git a/load/zpg.elc b/load/zpg.elc index acaf1c3..d60734b 100644 Binary files a/load/zpg.elc and b/load/zpg.elc differ