Download regex-math/pattern-compiler.lisp from Snapkitty/nekomata: direct link, hf CLI and curl.
- Browser
- Download file 10.7 kB
-
https://huggingface.co/Snapkitty/nekomata/resolve/main/regex-math/pattern-compiler.lisp
- Command line
-
hf download hf://Snapkitty/nekomata/regex-math/pattern-compiler.lisp
-
curl -L -o pattern-compiler.lisp https://huggingface.co/Snapkitty/nekomata/resolve/main/regex-math/pattern-compiler.lisp
10.7 kB
| (in-package #:nekod.regex) | |
| ;;; NFA and DFA compilation from regex algebra | |
| ;;; Pipeline: REGEX_AST -> NFA -> epsilon-closure -> subset construction -> DFA | |
| ;;; --- NFA representation --- | |
| (defstruct nfa-state | |
| (id 0 :type integer) | |
| (transitions nil :type list) | |
| (epsilon nil :type list) | |
| (accepting nil :type boolean)) | |
| (defstruct nfa | |
| (states (make-array 0 :adjustable t :fill-pointer 0)) | |
| (initial 0 :type integer) | |
| (accepting 0 :type integer)) | |
| (defvar *nfa-state-counter* 0) | |
| (defun fresh-nfa-state (&optional accepting) | |
| (let ((s (make-nfa-state :id (incf *nfa-state-counter*) | |
| :accepting accepting))) | |
| s)) | |
| ;;; --- Thompson's construction: regex -> NFA --- | |
| (defun regex-to-nfa (re) | |
| "Convert regex algebra tree to NFA via Thompson's construction." | |
| (let ((*nfa-state-counter* 0)) | |
| (regex-to-nfa-inner re))) | |
| (defun regex-to-nfa-inner (re) | |
| (etypecase re | |
| (re-empty | |
| (let ((s (fresh-nfa-state nil)) | |
| (f (fresh-nfa-state t))) | |
| (make-nfa :states (make-array 2 :initial-contents (list s f) :adjustable t :fill-pointer 2) | |
| :initial (nfa-state-id s) | |
| :accepting (nfa-state-id f)))) | |
| (re-epsilon | |
| (let ((s (fresh-nfa-state nil)) | |
| (f (fresh-nfa-state t))) | |
| (push (nfa-state-id f) (nfa-state-epsilon s)) | |
| (make-nfa :states (make-array 2 :initial-contents (list s f) :adjustable t :fill-pointer 2) | |
| :initial (nfa-state-id s) | |
| :accepting (nfa-state-id f)))) | |
| (re-literal | |
| (let* ((chars (re-literal-value re)) | |
| (states (loop for i from 0 to (length chars) | |
| collect (fresh-nfa-state (= i (length chars)))))) | |
| (loop for i from 0 below (length chars) | |
| for from-state = (nth i states) | |
| for to-state = (nth (1+ i) states) | |
| do (push (cons (char chars i) (nfa-state-id to-state)) | |
| (nfa-state-transitions from-state))) | |
| (make-nfa :states (make-array (length states) :initial-contents states | |
| :adjustable t :fill-pointer (length states)) | |
| :initial (nfa-state-id (first states)) | |
| :accepting (nfa-state-id (car (last states)))))) | |
| (re-concat | |
| (let ((left (regex-to-nfa-inner (re-concat-left re))) | |
| (right (regex-to-nfa-inner (re-concat-right re)))) | |
| (let ((left-accept (find (nfa-accepting left) (nfa-states left) | |
| :key #'nfa-state-id))) | |
| (setf (nfa-state-accepting left-accept) nil) | |
| (push (nfa-initial right) (nfa-state-epsilon left-accept))) | |
| (let ((all-states (concatenate 'vector (nfa-states left) (nfa-states right)))) | |
| (make-nfa :states (make-array (length all-states) :initial-contents all-states | |
| :adjustable t :fill-pointer (length all-states)) | |
| :initial (nfa-initial left) | |
| :accepting (nfa-accepting right))))) | |
| (re-union | |
| (let ((left (regex-to-nfa-inner (re-union-left re))) | |
| (right (regex-to-nfa-inner (re-union-right re))) | |
| (start (fresh-nfa-state nil)) | |
| (final (fresh-nfa-state t))) | |
| (push (nfa-initial left) (nfa-state-epsilon start)) | |
| (push (nfa-initial right) (nfa-state-epsilon start)) | |
| (let ((left-accept (find (nfa-accepting left) (nfa-states left) | |
| :key #'nfa-state-id)) | |
| (right-accept (find (nfa-accepting right) (nfa-states right) | |
| :key #'nfa-state-id))) | |
| (setf (nfa-state-accepting left-accept) nil) | |
| (setf (nfa-state-accepting right-accept) nil) | |
| (push (nfa-state-id final) (nfa-state-epsilon left-accept)) | |
| (push (nfa-state-id final) (nfa-state-epsilon right-accept))) | |
| (let ((all (concatenate 'vector | |
| (vector start final) | |
| (nfa-states left) | |
| (nfa-states right)))) | |
| (make-nfa :states (make-array (length all) :initial-contents all | |
| :adjustable t :fill-pointer (length all)) | |
| :initial (nfa-state-id start) | |
| :accepting (nfa-state-id final))))) | |
| (re-kleene | |
| (let ((inner (regex-to-nfa-inner (re-kleene-inner re))) | |
| (start (fresh-nfa-state nil)) | |
| (final (fresh-nfa-state t))) | |
| (push (nfa-initial inner) (nfa-state-epsilon start)) | |
| (push (nfa-state-id final) (nfa-state-epsilon start)) | |
| (let ((inner-accept (find (nfa-accepting inner) (nfa-states inner) | |
| :key #'nfa-state-id))) | |
| (setf (nfa-state-accepting inner-accept) nil) | |
| (push (nfa-initial inner) (nfa-state-epsilon inner-accept)) | |
| (push (nfa-state-id final) (nfa-state-epsilon inner-accept))) | |
| (let ((all (concatenate 'vector | |
| (vector start final) | |
| (nfa-states inner)))) | |
| (make-nfa :states (make-array (length all) :initial-contents all | |
| :adjustable t :fill-pointer (length all)) | |
| :initial (nfa-state-id start) | |
| :accepting (nfa-state-id final))))) | |
| (re-plus | |
| (regex-to-nfa-inner (re-concat (re-plus-inner re) | |
| (re-kleene (re-plus-inner re))))) | |
| (re-optional | |
| (regex-to-nfa-inner (re-union re (make-re-epsilon)))))) | |
| ;;; --- Epsilon closure --- | |
| (defun find-nfa-state (nfa id) | |
| (find id (nfa-states nfa) :key #'nfa-state-id)) | |
| (defun epsilon-closure (nfa state-ids) | |
| "Compute epsilon-closure of a set of NFA state IDs." | |
| (let ((closure (make-hash-table)) | |
| (stack (copy-list state-ids))) | |
| (dolist (s state-ids) (setf (gethash s closure) t)) | |
| (loop while stack | |
| do (let* ((sid (pop stack)) | |
| (state (find-nfa-state nfa sid))) | |
| (when state | |
| (dolist (eps (nfa-state-epsilon state)) | |
| (unless (gethash eps closure) | |
| (setf (gethash eps closure) t) | |
| (push eps stack)))))) | |
| (let ((result nil)) | |
| (maphash (lambda (k v) (declare (ignore v)) (push k result)) closure) | |
| (sort result #'<)))) | |
| (defun nfa-move (nfa state-ids char) | |
| "Compute set of states reachable from state-ids on input char." | |
| (let ((result nil)) | |
| (dolist (sid state-ids) | |
| (let ((state (find-nfa-state nfa sid))) | |
| (when state | |
| (dolist (tr (nfa-state-transitions state)) | |
| (when (char= (car tr) char) | |
| (pushnew (cdr tr) result)))))) | |
| result)) | |
| ;;; --- Subset construction: NFA -> DFA --- | |
| (defstruct (dfa-state (:constructor make-dfa-state-struct)) | |
| id | |
| transitions | |
| accepting) | |
| (defstruct dfa | |
| states | |
| initial | |
| accepting) | |
| (defun make-dfa-state (id &optional accepting) | |
| (make-dfa-state-struct :id id | |
| :transitions (make-hash-table :test #'equal) | |
| :accepting accepting)) | |
| (defun dfa-add-transition (dfa from-id char to-id) | |
| (let ((from-state (aref (dfa-states dfa) from-id))) | |
| (setf (gethash char (dfa-state-transitions from-state)) to-id))) | |
| (defun dfa-step (dfa state-id input-char) | |
| (let ((state (aref (dfa-states dfa) state-id))) | |
| (gethash input-char (dfa-state-transitions state)))) | |
| (defun dfa-run (dfa input) | |
| "Run DFA on input string. Returns T if accepted." | |
| (let ((current-state (dfa-initial dfa))) | |
| (loop for c across input | |
| do (let ((next (dfa-step dfa current-state c))) | |
| (if next | |
| (setf current-state next) | |
| (return-from dfa-run nil)))) | |
| (dfa-state-accepting (aref (dfa-states dfa) current-state)))) | |
| (defun nfa-to-dfa (nfa) | |
| "Convert NFA to DFA via subset construction." | |
| (let* ((initial-closure (epsilon-closure nfa (list (nfa-initial nfa)))) | |
| (dfa-states-list nil) | |
| (state-map (make-hash-table :test #'equal)) | |
| (state-counter 0) | |
| (worklist nil) | |
| (alphabet (collect-alphabet nfa))) | |
| (setf (gethash initial-closure state-map) state-counter) | |
| (push (cons initial-closure state-counter) dfa-states-list) | |
| (incf state-counter) | |
| (push initial-closure worklist) | |
| (let ((transitions nil)) | |
| (loop while worklist | |
| do (let* ((current (pop worklist)) | |
| (current-id (gethash current state-map))) | |
| (dolist (c alphabet) | |
| (let* ((moved (nfa-move nfa current c)) | |
| (target (and moved (epsilon-closure nfa moved)))) | |
| (when target | |
| (unless (gethash target state-map) | |
| (setf (gethash target state-map) state-counter) | |
| (push (cons target state-counter) dfa-states-list) | |
| (incf state-counter) | |
| (push target worklist)) | |
| (push (list current-id c (gethash target state-map)) | |
| transitions)))))) | |
| (let* ((n state-counter) | |
| (states (make-array n))) | |
| (loop for i from 0 below n | |
| do (let* ((entry (find i dfa-states-list :key #'cdr)) | |
| (nfa-ids (car entry)) | |
| (accepting (some (lambda (sid) | |
| (let ((s (find-nfa-state nfa sid))) | |
| (and s (nfa-state-accepting s)))) | |
| nfa-ids))) | |
| (setf (aref states i) (make-dfa-state i accepting)))) | |
| (let ((result (make-dfa :states states :initial 0 :accepting nil))) | |
| (dolist (tr transitions) | |
| (dfa-add-transition result (first tr) (second tr) (third tr))) | |
| (setf (dfa-accepting result) | |
| (loop for i from 0 below n | |
| when (dfa-state-accepting (aref states i)) | |
| collect i)) | |
| result))))) | |
| (defun collect-alphabet (nfa) | |
| "Collect all characters used in NFA transitions." | |
| (let ((chars nil)) | |
| (loop for state across (nfa-states nfa) | |
| do (dolist (tr (nfa-state-transitions state)) | |
| (pushnew (car tr) chars))) | |
| chars)) | |
| ;;; --- Top-level compilation --- | |
| (defun compile-regex-to-dfa (re) | |
| "Compile regex algebra tree directly to DFA. No runtime regex needed." | |
| (nfa-to-dfa (regex-to-nfa re))) | |