File size: 10,725 Bytes
3b70664
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
(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)))