|
1 <?php |
|
2 /** |
|
3 * Zend Framework |
|
4 * |
|
5 * LICENSE |
|
6 * |
|
7 * This source file is subject to the new BSD license that is bundled |
|
8 * with this package in the file LICENSE.txt. |
|
9 * It is also available through the world-wide-web at this URL: |
|
10 * http://framework.zend.com/license/new-bsd |
|
11 * If you did not receive a copy of the license and are unable to |
|
12 * obtain it through the world-wide-web, please send an email |
|
13 * to license@zend.com so we can send you a copy immediately. |
|
14 * |
|
15 * @category Zend |
|
16 * @package Zend_Search_Lucene |
|
17 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) |
|
18 * @license http://framework.zend.com/license/new-bsd New BSD License |
|
19 * @version $Id: FSM.php 20096 2010-01-06 02:05:09Z bkarwin $ |
|
20 */ |
|
21 |
|
22 /** Zend_Search_Lucene_FSMAction */ |
|
23 require_once 'Zend/Search/Lucene/FSMAction.php'; |
|
24 |
|
25 /** |
|
26 * Abstract Finite State Machine |
|
27 * |
|
28 * Take a look on Wikipedia state machine description: http://en.wikipedia.org/wiki/Finite_state_machine |
|
29 * |
|
30 * Any type of Transducers (Moore machine or Mealy machine) also may be implemented by using this abstract FSM. |
|
31 * process() methods invokes a specified actions which may construct FSM output. |
|
32 * Actions may be also used to signal, that we have reached Accept State |
|
33 * |
|
34 * @category Zend |
|
35 * @package Zend_Search_Lucene |
|
36 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) |
|
37 * @license http://framework.zend.com/license/new-bsd New BSD License |
|
38 */ |
|
39 abstract class Zend_Search_Lucene_FSM |
|
40 { |
|
41 /** |
|
42 * Machine States alphabet |
|
43 * |
|
44 * @var array |
|
45 */ |
|
46 private $_states = array(); |
|
47 |
|
48 /** |
|
49 * Current state |
|
50 * |
|
51 * @var integer|string |
|
52 */ |
|
53 private $_currentState = null; |
|
54 |
|
55 /** |
|
56 * Input alphabet |
|
57 * |
|
58 * @var array |
|
59 */ |
|
60 private $_inputAphabet = array(); |
|
61 |
|
62 /** |
|
63 * State transition table |
|
64 * |
|
65 * [sourceState][input] => targetState |
|
66 * |
|
67 * @var array |
|
68 */ |
|
69 private $_rules = array(); |
|
70 |
|
71 /** |
|
72 * List of entry actions |
|
73 * Each action executes when entering the state |
|
74 * |
|
75 * [state] => action |
|
76 * |
|
77 * @var array |
|
78 */ |
|
79 private $_entryActions = array(); |
|
80 |
|
81 /** |
|
82 * List of exit actions |
|
83 * Each action executes when exiting the state |
|
84 * |
|
85 * [state] => action |
|
86 * |
|
87 * @var array |
|
88 */ |
|
89 private $_exitActions = array(); |
|
90 |
|
91 /** |
|
92 * List of input actions |
|
93 * Each action executes when entering the state |
|
94 * |
|
95 * [state][input] => action |
|
96 * |
|
97 * @var array |
|
98 */ |
|
99 private $_inputActions = array(); |
|
100 |
|
101 /** |
|
102 * List of input actions |
|
103 * Each action executes when entering the state |
|
104 * |
|
105 * [state1][state2] => action |
|
106 * |
|
107 * @var array |
|
108 */ |
|
109 private $_transitionActions = array(); |
|
110 |
|
111 /** |
|
112 * Finite State machine constructor |
|
113 * |
|
114 * $states is an array of integers or strings with a list of possible machine states |
|
115 * constructor treats fist list element as a sturt state (assignes it to $_current state). |
|
116 * It may be reassigned by setState() call. |
|
117 * States list may be empty and can be extended later by addState() or addStates() calls. |
|
118 * |
|
119 * $inputAphabet is the same as $states, but represents input alphabet |
|
120 * it also may be extended later by addInputSymbols() or addInputSymbol() calls. |
|
121 * |
|
122 * $rules parameter describes FSM transitions and has a structure: |
|
123 * array( array(sourseState, input, targetState[, inputAction]), |
|
124 * array(sourseState, input, targetState[, inputAction]), |
|
125 * array(sourseState, input, targetState[, inputAction]), |
|
126 * ... |
|
127 * ) |
|
128 * Rules also can be added later by addRules() and addRule() calls. |
|
129 * |
|
130 * FSM actions are very flexible and may be defined by addEntryAction(), addExitAction(), |
|
131 * addInputAction() and addTransitionAction() calls. |
|
132 * |
|
133 * @param array $states |
|
134 * @param array $inputAphabet |
|
135 * @param array $rules |
|
136 */ |
|
137 public function __construct($states = array(), $inputAphabet = array(), $rules = array()) |
|
138 { |
|
139 $this->addStates($states); |
|
140 $this->addInputSymbols($inputAphabet); |
|
141 $this->addRules($rules); |
|
142 } |
|
143 |
|
144 /** |
|
145 * Add states to the state machine |
|
146 * |
|
147 * @param array $states |
|
148 */ |
|
149 public function addStates($states) |
|
150 { |
|
151 foreach ($states as $state) { |
|
152 $this->addState($state); |
|
153 } |
|
154 } |
|
155 |
|
156 /** |
|
157 * Add state to the state machine |
|
158 * |
|
159 * @param integer|string $state |
|
160 */ |
|
161 public function addState($state) |
|
162 { |
|
163 $this->_states[$state] = $state; |
|
164 |
|
165 if ($this->_currentState === null) { |
|
166 $this->_currentState = $state; |
|
167 } |
|
168 } |
|
169 |
|
170 /** |
|
171 * Set FSM state. |
|
172 * No any action is invoked |
|
173 * |
|
174 * @param integer|string $state |
|
175 * @throws Zend_Search_Exception |
|
176 */ |
|
177 public function setState($state) |
|
178 { |
|
179 if (!isset($this->_states[$state])) { |
|
180 require_once 'Zend/Search/Exception.php'; |
|
181 throw new Zend_Search_Exception('State \'' . $state . '\' is not on of the possible FSM states.'); |
|
182 } |
|
183 |
|
184 $this->_currentState = $state; |
|
185 } |
|
186 |
|
187 /** |
|
188 * Get FSM state. |
|
189 * |
|
190 * @return integer|string $state|null |
|
191 */ |
|
192 public function getState() |
|
193 { |
|
194 return $this->_currentState; |
|
195 } |
|
196 |
|
197 /** |
|
198 * Add symbols to the input alphabet |
|
199 * |
|
200 * @param array $inputAphabet |
|
201 */ |
|
202 public function addInputSymbols($inputAphabet) |
|
203 { |
|
204 foreach ($inputAphabet as $inputSymbol) { |
|
205 $this->addInputSymbol($inputSymbol); |
|
206 } |
|
207 } |
|
208 |
|
209 /** |
|
210 * Add symbol to the input alphabet |
|
211 * |
|
212 * @param integer|string $inputSymbol |
|
213 */ |
|
214 public function addInputSymbol($inputSymbol) |
|
215 { |
|
216 $this->_inputAphabet[$inputSymbol] = $inputSymbol; |
|
217 } |
|
218 |
|
219 |
|
220 /** |
|
221 * Add transition rules |
|
222 * |
|
223 * array structure: |
|
224 * array( array(sourseState, input, targetState[, inputAction]), |
|
225 * array(sourseState, input, targetState[, inputAction]), |
|
226 * array(sourseState, input, targetState[, inputAction]), |
|
227 * ... |
|
228 * ) |
|
229 * |
|
230 * @param array $rules |
|
231 */ |
|
232 public function addRules($rules) |
|
233 { |
|
234 foreach ($rules as $rule) { |
|
235 $this->addrule($rule[0], $rule[1], $rule[2], isset($rule[3])?$rule[3]:null); |
|
236 } |
|
237 } |
|
238 |
|
239 /** |
|
240 * Add symbol to the input alphabet |
|
241 * |
|
242 * @param integer|string $sourceState |
|
243 * @param integer|string $input |
|
244 * @param integer|string $targetState |
|
245 * @param Zend_Search_Lucene_FSMAction|null $inputAction |
|
246 * @throws Zend_Search_Exception |
|
247 */ |
|
248 public function addRule($sourceState, $input, $targetState, $inputAction = null) |
|
249 { |
|
250 if (!isset($this->_states[$sourceState])) { |
|
251 require_once 'Zend/Search/Exception.php'; |
|
252 throw new Zend_Search_Exception('Undefined source state (' . $sourceState . ').'); |
|
253 } |
|
254 if (!isset($this->_states[$targetState])) { |
|
255 require_once 'Zend/Search/Exception.php'; |
|
256 throw new Zend_Search_Exception('Undefined target state (' . $targetState . ').'); |
|
257 } |
|
258 if (!isset($this->_inputAphabet[$input])) { |
|
259 require_once 'Zend/Search/Exception.php'; |
|
260 throw new Zend_Search_Exception('Undefined input symbol (' . $input . ').'); |
|
261 } |
|
262 |
|
263 if (!isset($this->_rules[$sourceState])) { |
|
264 $this->_rules[$sourceState] = array(); |
|
265 } |
|
266 if (isset($this->_rules[$sourceState][$input])) { |
|
267 require_once 'Zend/Search/Exception.php'; |
|
268 throw new Zend_Search_Exception('Rule for {state,input} pair (' . $sourceState . ', '. $input . ') is already defined.'); |
|
269 } |
|
270 |
|
271 $this->_rules[$sourceState][$input] = $targetState; |
|
272 |
|
273 |
|
274 if ($inputAction !== null) { |
|
275 $this->addInputAction($sourceState, $input, $inputAction); |
|
276 } |
|
277 } |
|
278 |
|
279 |
|
280 /** |
|
281 * Add state entry action. |
|
282 * Several entry actions are allowed. |
|
283 * Action execution order is defined by addEntryAction() calls |
|
284 * |
|
285 * @param integer|string $state |
|
286 * @param Zend_Search_Lucene_FSMAction $action |
|
287 */ |
|
288 public function addEntryAction($state, Zend_Search_Lucene_FSMAction $action) |
|
289 { |
|
290 if (!isset($this->_states[$state])) { |
|
291 require_once 'Zend/Search/Exception.php'; |
|
292 throw new Zend_Search_Exception('Undefined state (' . $state. ').'); |
|
293 } |
|
294 |
|
295 if (!isset($this->_entryActions[$state])) { |
|
296 $this->_entryActions[$state] = array(); |
|
297 } |
|
298 |
|
299 $this->_entryActions[$state][] = $action; |
|
300 } |
|
301 |
|
302 /** |
|
303 * Add state exit action. |
|
304 * Several exit actions are allowed. |
|
305 * Action execution order is defined by addEntryAction() calls |
|
306 * |
|
307 * @param integer|string $state |
|
308 * @param Zend_Search_Lucene_FSMAction $action |
|
309 */ |
|
310 public function addExitAction($state, Zend_Search_Lucene_FSMAction $action) |
|
311 { |
|
312 if (!isset($this->_states[$state])) { |
|
313 require_once 'Zend/Search/Exception.php'; |
|
314 throw new Zend_Search_Exception('Undefined state (' . $state. ').'); |
|
315 } |
|
316 |
|
317 if (!isset($this->_exitActions[$state])) { |
|
318 $this->_exitActions[$state] = array(); |
|
319 } |
|
320 |
|
321 $this->_exitActions[$state][] = $action; |
|
322 } |
|
323 |
|
324 /** |
|
325 * Add input action (defined by {state, input} pair). |
|
326 * Several input actions are allowed. |
|
327 * Action execution order is defined by addInputAction() calls |
|
328 * |
|
329 * @param integer|string $state |
|
330 * @param integer|string $input |
|
331 * @param Zend_Search_Lucene_FSMAction $action |
|
332 */ |
|
333 public function addInputAction($state, $inputSymbol, Zend_Search_Lucene_FSMAction $action) |
|
334 { |
|
335 if (!isset($this->_states[$state])) { |
|
336 require_once 'Zend/Search/Exception.php'; |
|
337 throw new Zend_Search_Exception('Undefined state (' . $state. ').'); |
|
338 } |
|
339 if (!isset($this->_inputAphabet[$inputSymbol])) { |
|
340 require_once 'Zend/Search/Exception.php'; |
|
341 throw new Zend_Search_Exception('Undefined input symbol (' . $inputSymbol. ').'); |
|
342 } |
|
343 |
|
344 if (!isset($this->_inputActions[$state])) { |
|
345 $this->_inputActions[$state] = array(); |
|
346 } |
|
347 if (!isset($this->_inputActions[$state][$inputSymbol])) { |
|
348 $this->_inputActions[$state][$inputSymbol] = array(); |
|
349 } |
|
350 |
|
351 $this->_inputActions[$state][$inputSymbol][] = $action; |
|
352 } |
|
353 |
|
354 /** |
|
355 * Add transition action (defined by {state, input} pair). |
|
356 * Several transition actions are allowed. |
|
357 * Action execution order is defined by addTransitionAction() calls |
|
358 * |
|
359 * @param integer|string $sourceState |
|
360 * @param integer|string $targetState |
|
361 * @param Zend_Search_Lucene_FSMAction $action |
|
362 */ |
|
363 public function addTransitionAction($sourceState, $targetState, Zend_Search_Lucene_FSMAction $action) |
|
364 { |
|
365 if (!isset($this->_states[$sourceState])) { |
|
366 require_once 'Zend/Search/Exception.php'; |
|
367 throw new Zend_Search_Exception('Undefined source state (' . $sourceState. ').'); |
|
368 } |
|
369 if (!isset($this->_states[$targetState])) { |
|
370 require_once 'Zend/Search/Exception.php'; |
|
371 throw new Zend_Search_Exception('Undefined source state (' . $targetState. ').'); |
|
372 } |
|
373 |
|
374 if (!isset($this->_transitionActions[$sourceState])) { |
|
375 $this->_transitionActions[$sourceState] = array(); |
|
376 } |
|
377 if (!isset($this->_transitionActions[$sourceState][$targetState])) { |
|
378 $this->_transitionActions[$sourceState][$targetState] = array(); |
|
379 } |
|
380 |
|
381 $this->_transitionActions[$sourceState][$targetState][] = $action; |
|
382 } |
|
383 |
|
384 |
|
385 /** |
|
386 * Process an input |
|
387 * |
|
388 * @param mixed $input |
|
389 * @throws Zend_Search_Exception |
|
390 */ |
|
391 public function process($input) |
|
392 { |
|
393 if (!isset($this->_rules[$this->_currentState])) { |
|
394 require_once 'Zend/Search/Exception.php'; |
|
395 throw new Zend_Search_Exception('There is no any rule for current state (' . $this->_currentState . ').'); |
|
396 } |
|
397 if (!isset($this->_rules[$this->_currentState][$input])) { |
|
398 require_once 'Zend/Search/Exception.php'; |
|
399 throw new Zend_Search_Exception('There is no any rule for {current state, input} pair (' . $this->_currentState . ', ' . $input . ').'); |
|
400 } |
|
401 |
|
402 $sourceState = $this->_currentState; |
|
403 $targetState = $this->_rules[$this->_currentState][$input]; |
|
404 |
|
405 if ($sourceState != $targetState && isset($this->_exitActions[$sourceState])) { |
|
406 foreach ($this->_exitActions[$sourceState] as $action) { |
|
407 $action->doAction(); |
|
408 } |
|
409 } |
|
410 if (isset($this->_inputActions[$sourceState]) && |
|
411 isset($this->_inputActions[$sourceState][$input])) { |
|
412 foreach ($this->_inputActions[$sourceState][$input] as $action) { |
|
413 $action->doAction(); |
|
414 } |
|
415 } |
|
416 |
|
417 |
|
418 $this->_currentState = $targetState; |
|
419 |
|
420 if (isset($this->_transitionActions[$sourceState]) && |
|
421 isset($this->_transitionActions[$sourceState][$targetState])) { |
|
422 foreach ($this->_transitionActions[$sourceState][$targetState] as $action) { |
|
423 $action->doAction(); |
|
424 } |
|
425 } |
|
426 if ($sourceState != $targetState && isset($this->_entryActions[$targetState])) { |
|
427 foreach ($this->_entryActions[$targetState] as $action) { |
|
428 $action->doAction(); |
|
429 } |
|
430 } |
|
431 } |
|
432 |
|
433 public function reset() |
|
434 { |
|
435 if (count($this->_states) == 0) { |
|
436 require_once 'Zend/Search/Exception.php'; |
|
437 throw new Zend_Search_Exception('There is no any state defined for FSM.'); |
|
438 } |
|
439 |
|
440 $this->_currentState = $this->_states[0]; |
|
441 } |
|
442 } |
|
443 |