Branch data Line data Source code
1 : : // $Id: DFA.cc 6219 2008-10-01 05:39:07Z vern $
2 : : //
3 : : // See the file "COPYING" in the main distribution directory for copyright.
4 : :
5 : : #include "config.h"
6 : :
7 : : #include "EquivClass.h"
8 : : #include "DFA.h"
9 : : #include "md5.h"
10 : :
11 : : int dfa_state_cache_size = 10000;
12 : :
13 : : unsigned int DFA_State::transition_counter = 0;
14 : :
15 : : DFA_State::DFA_State(int arg_state_num, const EquivClass* ec,
16 : : NFA_state_list* arg_nfa_states,
17 : 582 : AcceptingSet* arg_accept)
18 : : {
19 : 582 : state_num = arg_state_num;
20 : 582 : num_sym = ec->NumClasses();
21 : 582 : nfa_states = arg_nfa_states;
22 : 582 : accept = arg_accept;
23 : 582 : mark = 0;
24 : 582 : lock = 0;
25 : :
26 : 582 : SymPartition(ec);
27 : :
28 : 582 : xtions = new DFA_State_Handle*[num_sym];
29 : :
30 [ + + ][ # # ]: 11753 : for ( int i = 0; i < num_sym; ++i )
31 : 11171 : xtions[i] = DFA_UNCOMPUTED_STATE_PTR;
32 : 582 : }
33 : :
34 : 0 : DFA_State::~DFA_State()
35 : : {
36 [ # # ][ # # ]: 0 : for ( int i = 0; i < num_sym; ++i )
[ # # ]
37 : : {
38 : 0 : DFA_State_Handle* s = xtions[i];
39 [ # # ][ # # ]: 0 : if ( s && s != DFA_UNCOMPUTED_STATE_PTR )
[ # # ][ # # ]
[ # # ][ # # ]
40 : 0 : StateUnref(s);
41 : : }
42 : :
43 [ # # ][ # # ]: 0 : delete [] xtions;
[ # # ]
44 [ # # ][ # # ]: 0 : delete nfa_states;
[ # # ]
45 [ # # ][ # # ]: 0 : delete accept;
[ # # ]
46 [ # # ][ # # ]: 0 : delete meta_ec;
[ # # ]
47 [ # # ][ # # ]: 0 : }
[ # # ]
48 : :
49 : 1571 : void DFA_State::AddXtion(int sym, DFA_State_Handle* next_state)
50 : : {
51 : : // The order is important here: first StateRef() the new,
52 : : // then StateUnref() the old. Otherwise, we may get a problem
53 : : // if both are equal.
54 : :
55 [ + + ]: 1571 : if ( next_state )
56 : 1490 : StateRef(next_state);
57 : :
58 [ + - ][ - + ]: 1571 : if ( xtions[sym] && xtions[sym] != DFA_UNCOMPUTED_STATE_PTR )
59 : 0 : StateUnref(xtions[sym]);
60 : :
61 : 1571 : xtions[sym] = next_state;
62 : 1571 : }
63 : :
64 : 582 : void DFA_State::SymPartition(const EquivClass* ec)
65 : : {
66 : : // Partitioning is done by creating equivalence classes for those
67 : : // characters which have out-transitions from the given state. Thus
68 : : // we are really creating equivalence classes of equivalence classes.
69 : 582 : meta_ec = new EquivClass(ec->NumClasses());
70 : :
71 [ - + ]: 582 : assert(nfa_states);
72 [ + + ]: 10662 : for ( int i = 0; i < nfa_states->length(); ++i )
73 : : {
74 : 10080 : NFA_State* n = (*nfa_states)[i];
75 : 10080 : int sym = n->TransSym();
76 : :
77 [ + + ]: 10080 : if ( sym == SYM_EPSILON )
78 : 23 : continue;
79 : :
80 [ + + ]: 10057 : if ( sym != SYM_CCL )
81 : : { // character transition
82 [ + - ]: 6476 : if ( ec->IsRep(sym) )
83 : : {
84 : 6476 : sym = ec->SymEquivClass(sym);
85 : 6476 : meta_ec->UniqueChar(sym);
86 : : }
87 : 6476 : continue;
88 : : }
89 : :
90 : : // Character class.
91 : 3581 : meta_ec->CCL_Use(n->TransCCL());
92 : : }
93 : :
94 : 582 : meta_ec->BuildECs();
95 : 582 : }
96 : :
97 : 1438 : DFA_State_Handle* DFA_State::ComputeXtion(int sym, DFA_Machine* machine)
98 : : {
99 : : // Make sure we will not expire...
100 [ - + ]: 1438 : assert(IsLocked());
101 : :
102 : 1438 : int equiv_sym = meta_ec->EquivRep(sym);
103 [ + + + - ]: 1438 : if ( xtions[equiv_sym] != DFA_UNCOMPUTED_STATE_PTR &&
[ + + ]
104 : : StateIsValid(xtions[equiv_sym]) )
105 : : {
106 : 760 : AddXtion(sym, xtions[equiv_sym]);
107 : 760 : return xtions[sym];
108 : : }
109 : :
110 : 678 : const EquivClass* ec = machine->EC();
111 : :
112 : : DFA_State_Handle* next_d;
113 : :
114 : 678 : NFA_state_list* ns = SymFollowSet(equiv_sym, ec);
115 [ + + ]: 678 : if ( ns->length() > 0 )
116 : : {
117 : 638 : NFA_state_list* state_set = epsilon_closure(ns);
118 [ + + ]: 638 : if ( ! machine->StateSetToDFA_State(state_set, next_d, ec) )
119 [ + - ]: 638 : delete state_set;
120 : : }
121 : : else
122 : : {
123 [ + - ]: 40 : delete ns;
124 : 40 : next_d = 0; // Jam
125 : : }
126 : :
127 : 678 : AddXtion(equiv_sym, next_d);
128 [ + + ]: 678 : if ( sym != equiv_sym )
129 : 133 : AddXtion(sym, next_d);
130 : :
131 : 1438 : return xtions[sym];
132 : : }
133 : :
134 : 0 : void DFA_State::AppendIfNew(int sym, int_list* sym_list)
135 : : {
136 [ # # ]: 0 : for ( int i = 0; i < sym_list->length(); ++i )
137 [ # # ]: 0 : if ( (*sym_list)[i] == sym )
138 : 0 : return;
139 : :
140 : 0 : sym_list->append(sym);
141 : : }
142 : :
143 : 678 : NFA_state_list* DFA_State::SymFollowSet(int ec_sym, const EquivClass* ec)
144 : : {
145 : 678 : NFA_state_list* ns = new NFA_state_list;
146 : :
147 [ - + ]: 678 : assert(nfa_states);
148 : :
149 [ + + ]: 34095 : for ( int i = 0; i < nfa_states->length(); ++i )
150 : : {
151 : 33417 : NFA_State* n = (*nfa_states)[i];
152 : :
153 [ + + ]: 33417 : if ( n->TransSym() == SYM_CCL )
154 : : { // it's a character class
155 : 14453 : CCL* ccl = n->TransCCL();
156 : 14453 : int_list* syms = ccl->Syms();
157 : :
158 [ + + ]: 14453 : if ( ccl->IsNegated() )
159 : : {
160 : : int j;
161 [ + - ]: 19793 : for ( j = 0; j < syms->length(); ++j )
162 : : {
163 : : // Loop through (sorted) negated
164 : : // character class, which has
165 : : // presumably already been converted
166 : : // over to equivalence classes.
167 [ + + ]: 19793 : if ( (*syms)[j] >= ec_sym )
168 : 10600 : break;
169 : : }
170 : :
171 [ + - ][ + + ]: 10600 : if ( j >= syms->length() || (*syms)[j] > ec_sym )
[ + + ]
172 : : // Didn't find ec_sym in ccl.
173 : 10080 : n->AddXtionsTo(ns);
174 : :
175 : 10600 : continue;
176 : : }
177 : :
178 [ + + ]: 9052 : for ( int j = 0; j < syms->length(); ++j )
179 : : {
180 [ + + ]: 5199 : if ( (*syms)[j] > ec_sym )
181 : 1843 : break;
182 : :
183 [ + + ]: 3356 : if ( (*syms)[j] == ec_sym )
184 : : {
185 : 288 : n->AddXtionsTo(ns);
186 : 288 : break;
187 : : }
188 : : }
189 : : }
190 : :
191 [ + + ]: 18964 : else if ( n->TransSym() == SYM_EPSILON )
192 : : { // do nothing
193 : : }
194 : :
195 [ + - ]: 18960 : else if ( ec->IsRep(n->TransSym()) )
196 : : {
197 [ + + ]: 18960 : if ( ec_sym == ec->SymEquivClass(n->TransSym()) )
198 : 630 : n->AddXtionsTo(ns);
199 : : }
200 : : }
201 : :
202 : 678 : ns->resize(0);
203 : 678 : return ns;
204 : : }
205 : :
206 : 0 : void DFA_State::ClearMarks()
207 : : {
208 [ # # ]: 0 : if ( mark )
209 : : {
210 : 0 : SetMark(0);
211 : :
212 [ # # ]: 0 : for ( int i = 0; i < num_sym; ++i )
213 : : {
214 : 0 : DFA_State_Handle* s = xtions[i];
215 : :
216 [ # # ][ # # ]: 0 : if ( s && s != DFA_UNCOMPUTED_STATE_PTR )
217 : 0 : (*xtions[i])->ClearMarks();
218 : : }
219 : : }
220 : 0 : }
221 : :
222 : 0 : void DFA_State::Describe(ODesc* d) const
223 : : {
224 : 0 : d->Add("DFA state");
225 : 0 : }
226 : :
227 : 0 : void DFA_State::Dump(FILE* f, DFA_Machine* m)
228 : : {
229 [ # # ]: 0 : if ( mark )
230 : 0 : return;
231 : :
232 : 0 : fprintf(f, "\nDFA state %d:", StateNum());
233 : :
234 [ # # ]: 0 : if ( accept )
235 : : {
236 [ # # ]: 0 : for ( int i = 0; i < accept->length(); ++i )
237 : : fprintf(f, "%s accept #%d",
238 [ # # ]: 0 : i > 0 ? "," : "", int((*accept)[i]));
239 : : }
240 : :
241 : 0 : fprintf(f, "\n");
242 : :
243 : 0 : int num_trans = 0;
244 [ # # ]: 0 : for ( int sym = 0; sym < num_sym; ++sym )
245 : : {
246 : 0 : DFA_State_Handle* s = xtions[sym];
247 : :
248 [ # # ]: 0 : if ( ! s )
249 : 0 : continue;
250 : :
251 : : // Look ahead for compression.
252 : : int i;
253 [ # # ]: 0 : for ( i = sym + 1; i < num_sym; ++i )
254 [ # # ]: 0 : if ( xtions[i] != s )
255 : 0 : break;
256 : :
257 : : char xbuf[512];
258 : :
259 : 0 : int r = m->Rep(sym);
260 [ # # ]: 0 : if ( ! r )
261 : 0 : r = '.';
262 : :
263 [ # # ]: 0 : if ( i == sym + 1 )
264 : 0 : sprintf(xbuf, "'%c'", r);
265 : : else
266 : 0 : sprintf(xbuf, "'%c'-'%c'", r, m->Rep(i-1));
267 : :
268 [ # # ]: 0 : if ( s == DFA_UNCOMPUTED_STATE_PTR )
269 : : fprintf(f, "%stransition on %s to <uncomputed>",
270 [ # # ]: 0 : ++num_trans == 1 ? "\t" : "\n\t", xbuf);
271 : : else
272 : : fprintf(f, "%stransition on %s to state %d",
273 : : ++num_trans == 1 ? "\t" : "\n\t", xbuf,
274 [ # # ]: 0 : (*s)->StateNum());
275 : :
276 : 0 : sym = i - 1;
277 : : }
278 : :
279 [ # # ]: 0 : if ( num_trans > 0 )
280 : 0 : fprintf(f, "\n");
281 : :
282 : 0 : SetMark(this);
283 : :
284 [ # # ]: 0 : for ( int sym = 0; sym < num_sym; ++sym )
285 : : {
286 : 0 : DFA_State_Handle* s = xtions[sym];
287 : :
288 [ # # ][ # # ]: 0 : if ( s && s != DFA_UNCOMPUTED_STATE_PTR )
289 : 0 : (*s)->Dump(f, m);
290 : : }
291 : : }
292 : :
293 : 0 : void DFA_State::Stats(unsigned int* computed, unsigned int* uncomputed)
294 : : {
295 [ # # ]: 0 : for ( int sym = 0; sym < num_sym; ++sym )
296 : : {
297 : 0 : DFA_State_Handle* s = xtions[sym];
298 : :
299 [ # # ]: 0 : if ( s == DFA_UNCOMPUTED_STATE_PTR )
300 : 0 : (*uncomputed)++;
301 : : else
302 : 0 : (*computed)++;
303 : : }
304 : 0 : }
305 : :
306 : 0 : unsigned int DFA_State::Size()
307 : : {
308 : : return sizeof(*this)
309 : : + pad_size(sizeof(DFA_State*) * num_sym)
310 : : + (accept ? pad_size(sizeof(int) * accept->length()) : 0)
311 : : + (nfa_states ? pad_size(sizeof(NFA_State*) * nfa_states->length()) : 0)
312 : : + (meta_ec ? meta_ec->Size() : 0)
313 [ # # ][ # # ]: 0 : + (centry ? padded_sizeof(CacheEntry) : 0);
[ # # ][ # # ]
314 : : }
315 : :
316 : :
317 : 444 : DFA_State_Cache::DFA_State_Cache(int arg_maxsize)
318 : : {
319 : 444 : maxsize = arg_maxsize;
320 : 444 : head = tail = 0;
321 : 444 : hits = misses = 0;
322 : 444 : }
323 : :
324 : 4 : DFA_State_Cache::~DFA_State_Cache()
325 : : {
326 : 4 : IterCookie* i = states.InitForIteration();
327 : : CacheEntry* e;
328 [ + + ][ # # ]: 8 : while ( (e = (CacheEntry*) states.NextEntry(i)) )
329 : : {
330 [ - + ][ # # ]: 4 : assert(e->state);
331 : 4 : StateInvalidate(e->state);
332 [ + - # # ]: 4 : delete e->hash;
333 : 4 : delete e;
334 : : }
335 : 4 : }
336 : :
337 : : DFA_State_Handle* DFA_State_Cache::Lookup(const NFA_state_list& nfas,
338 : 1082 : HashKey** hash)
339 : : {
340 : : // We assume that state ID's don't exceed 10 digits, plus
341 : : // we allow one more character for the delimiter.
342 : 1082 : md5_byte_t id_tag[nfas.length() * 11 + 1];
343 : 1082 : md5_byte_t* p = id_tag;
344 : :
345 [ + + ]: 37015 : for ( int i = 0; i < nfas.length(); ++i )
346 : : {
347 : 35933 : NFA_State* n = nfas[i];
348 [ + + + - ]: 35933 : if ( n->TransSym() != SYM_EPSILON || n->Accept() != NO_ACCEPT )
[ + - ]
349 : : {
350 : 35933 : int id = n->ID();
351 [ + + ]: 146549 : do
352 : : {
353 : 146549 : *p++ = '0' + (char)(id % 10);
354 : 146549 : id /= 10;
355 : : }
356 : : while ( id > 0 );
357 : 35933 : *p++ = '&';
358 : : }
359 : : }
360 : :
361 : 1082 : *p++ = '\0';
362 : :
363 : : // We use the short MD5 instead of the full string for the
364 : : // HashKey because the data is copied into the key.
365 : : md5_state_t state;
366 : : md5_byte_t digest[16];
367 : :
368 : 1082 : md5_init(&state);
369 : 1082 : md5_append(&state, id_tag, p - id_tag);
370 : 1082 : md5_finish(&state, digest);
371 : :
372 : 1082 : *hash = new HashKey(&digest, sizeof(digest));
373 : 1082 : CacheEntry* e = states.Lookup(*hash);
374 [ + + ]: 1082 : if ( ! e )
375 : : {
376 : 582 : ++misses;
377 : 582 : return 0;
378 : : }
379 : :
380 [ + - ]: 500 : delete *hash;
381 : 500 : *hash = 0;
382 : :
383 : 500 : MoveToFront(e);
384 : :
385 : 1082 : return e->state;
386 : : }
387 : :
388 : 582 : DFA_State_Handle* DFA_State_Cache::Insert(DFA_State* state, HashKey* hash)
389 : : {
390 : : CacheEntry* e;
391 : :
392 : : #ifdef EXPIRE_DFA_STATES
393 : : if ( states.Length() == maxsize )
394 : : {
395 : : // Remove oldest unlocked entry.
396 : : for ( e = tail; e; e = e->prev )
397 : : if ( ! (*e->state)->lock )
398 : : break;
399 : : if ( e )
400 : : Remove(e);
401 : : }
402 : : #endif
403 : :
404 : 582 : e = new CacheEntry;
405 : :
406 : : #ifdef EXPIRE_DFA_STATES
407 : : // Insert as head.
408 : : e->state = new DFA_State_Handle(state);
409 : : e->state->state->centry = e;
410 : : #else
411 : 582 : e->state = state;
412 : 582 : e->state->centry = e;
413 : : #endif
414 : 582 : e->hash = hash;
415 : 582 : e->prev = 0;
416 : 582 : e->next = head;
417 [ + + ]: 582 : if ( head )
418 : 138 : head->prev = e;
419 : 582 : head = e;
420 [ + + ]: 582 : if ( ! tail )
421 : 444 : tail = e;
422 : :
423 : 582 : states.Insert(hash, e);
424 : :
425 : 582 : return e->state;
426 : : }
427 : :
428 : 0 : void DFA_State_Cache::Remove(CacheEntry* e)
429 : : {
430 [ # # ]: 0 : if ( e == head )
431 : : {
432 : 0 : head = e->next;
433 [ # # ]: 0 : if ( head )
434 : 0 : head->prev = 0;
435 : : }
436 : : else
437 : 0 : e->prev->next = e->next;
438 : :
439 [ # # ]: 0 : if ( e == tail )
440 : : {
441 : 0 : tail = e->prev;
442 [ # # ]: 0 : if ( tail )
443 : 0 : tail->next = 0;
444 : : }
445 : : else
446 : 0 : e->next->prev = e->prev;
447 : :
448 : 0 : states.Remove(e->hash);
449 : :
450 [ # # ]: 0 : assert(e->state);
451 : 0 : StateInvalidate(e->state);
452 [ # # ]: 0 : delete e->hash;
453 : 0 : delete e;
454 : 0 : }
455 : :
456 : 500 : void DFA_State_Cache::MoveToFront(CacheEntry* e)
457 : : {
458 : 500 : ++hits;
459 : :
460 [ + + ]: 500 : if ( e->prev )
461 : : {
462 : 444 : e->prev->next = e->next;
463 : :
464 [ + - ]: 444 : if ( e->next )
465 : 444 : e->next->prev = e->prev;
466 : : else
467 : 0 : tail = e->prev;
468 : :
469 : 444 : e->prev = 0;
470 : 444 : e->next = head;
471 : :
472 : 444 : head->prev = e;
473 : 444 : head = e;
474 : : }
475 : 500 : }
476 : :
477 : 0 : void DFA_State_Cache::GetStats(Stats* s)
478 : : {
479 : 0 : s->dfa_states = 0;
480 : 0 : s->nfa_states = 0;
481 : 0 : s->computed = 0;
482 : 0 : s->uncomputed = 0;
483 : 0 : s->mem = 0;
484 : 0 : s->hits = hits;
485 : 0 : s->misses = misses;
486 : :
487 : : CacheEntry* e;
488 : :
489 : 0 : IterCookie* i = states.InitForIteration();
490 [ # # ]: 0 : while ( (e = (CacheEntry*) states.NextEntry(i)) )
491 : : {
492 : 0 : ++s->dfa_states;
493 : 0 : s->nfa_states += (*e->state)->NFAStateNum();
494 : 0 : (*e->state)->Stats(&s->computed, &s->uncomputed);
495 : 0 : s->mem += pad_size((*e->state)->Size()) + padded_sizeof(*e->state);
496 : : }
497 : 0 : }
498 : :
499 : 444 : DFA_Machine::DFA_Machine(NFA_Machine* n, EquivClass* arg_ec)
500 : : {
501 : 444 : state_count = 0;
502 : :
503 : 444 : nfa = n;
504 : 444 : Ref(n);
505 : :
506 : 444 : ec = arg_ec;
507 : :
508 : 444 : dfa_state_cache = new DFA_State_Cache(dfa_state_cache_size);
509 : :
510 : 444 : NFA_state_list* ns = new NFA_state_list;
511 : 444 : ns->append(n->FirstState());
512 : :
513 [ + - # # ]: 444 : if ( ns->length() > 0 )
514 : : {
515 : 444 : NFA_state_list* state_set = epsilon_closure(ns);
516 : 444 : (void) StateSetToDFA_State(state_set, start_state, ec);
517 : :
518 : 444 : StateRef(start_state);
519 : 444 : StateLock(start_state);
520 : : }
521 : : else
522 : 0 : start_state = 0; // Jam
523 : 444 : }
524 : :
525 : 4 : DFA_Machine::~DFA_Machine()
526 : : {
527 [ + - ][ # # ]: 4 : if ( start_state )
[ # # ]
528 : : {
529 : 4 : StateUnlock(start_state);
530 : 4 : StateUnref(start_state);
531 : : }
532 : :
533 [ + - ][ # # ]: 4 : delete dfa_state_cache;
[ # # ]
534 : 4 : Unref(nfa);
535 [ + - ][ # # ]: 4 : }
[ # # ]
536 : :
537 : 0 : void DFA_Machine::Describe(ODesc* d) const
538 : : {
539 : 0 : d->Add("DFA machine");
540 : 0 : }
541 : :
542 : 0 : void DFA_Machine::Dump(FILE* f)
543 : : {
544 : 0 : (*start_state)->Dump(f, this);
545 : 0 : (*start_state)->ClearMarks();
546 : 0 : }
547 : :
548 : 0 : void DFA_Machine::DumpStats(FILE* f)
549 : : {
550 : : DFA_State_Cache::Stats stats;
551 : 0 : dfa_state_cache->GetStats(&stats);
552 : :
553 : : fprintf(f, "Computed dfa_states = %d; Classes = %d; Computed trans. = %d; Uncomputed trans. = %d\n",
554 : : stats.dfa_states, EC()->NumClasses(),
555 : 0 : stats.computed, stats.uncomputed);
556 : :
557 : : fprintf(f, "DFA cache hits = %d; misses = %d\n",
558 : 0 : stats.hits, stats.misses);
559 : 0 : }
560 : :
561 : 0 : unsigned int DFA_Machine::MemoryAllocation() const
562 : : {
563 : : DFA_State_Cache::Stats s;
564 : 0 : dfa_state_cache->GetStats(&s);
565 : :
566 : : // FIXME: Count *ec?
567 : : return padded_sizeof(*this)
568 : : + s.mem
569 : : + padded_sizeof(*start_state)
570 : 0 : + nfa->MemoryAllocation();
571 : : }
572 : :
573 : : int DFA_Machine::StateSetToDFA_State(NFA_state_list* state_set,
574 : 1082 : DFA_State_Handle*& d, const EquivClass* ec)
575 : : {
576 : : HashKey* hash;
577 : 1082 : d = dfa_state_cache->Lookup(*state_set, &hash);
578 : :
579 [ + + - + ]: 1082 : assert((! d) || StateIsValid(d));
[ - + ]
580 [ + + ]: 1082 : if ( d )
581 : 500 : return 0;
582 : :
583 : 582 : AcceptingSet* accept = new AcceptingSet;
584 [ + + ]: 10662 : for ( int i = 0; i < state_set->length(); ++i )
585 : : {
586 : 10080 : int acc = (*state_set)[i]->Accept();
587 : :
588 [ + + ]: 10080 : if ( acc != NO_ACCEPT )
589 : : {
590 : : int j;
591 [ - + ]: 23 : for ( j = 0; j < accept->length(); ++j )
592 [ # # ]: 0 : if ( (*accept)[j] == acc )
593 : 0 : break;
594 : :
595 [ + - ]: 23 : if ( j >= accept->length() )
596 : : // It's not already present.
597 : 23 : accept->append(acc);
598 : : }
599 : : }
600 : :
601 [ + + ]: 582 : if ( accept->length() == 0 )
602 : : {
603 [ + - ]: 559 : delete accept;
604 : 559 : accept = 0;
605 : : }
606 : : else
607 : : {
608 : 23 : accept->sort(int_list_cmp);
609 : 23 : accept->resize(0);
610 : : }
611 : :
612 : 1664 : DFA_State* ds = new DFA_State(state_count++, ec, state_set, accept);
613 : 582 : d = dfa_state_cache->Insert(ds, hash);
614 : :
615 : 582 : return 1;
616 : : }
617 : :
618 : 0 : int DFA_Machine::Rep(int sym)
619 : : {
620 [ # # ]: 0 : for ( int i = 0; i < NUM_SYM; ++i )
621 [ # # ]: 0 : if ( ec->SymEquivClass(i) == sym )
622 : 0 : return i;
623 : :
624 : 0 : return -1;
625 [ + - ][ + - ]: 6 : }
|