1
2 /* Web Polygraph http://www.web-polygraph.org/
3 * Copyright 2003-2011 The Measurement Factory
4 * Licensed under the Apache License, Version 2.0 */
5
6 #include "base/polygraph.h"
7 #include "pgl/pgl.h"
8
9 #include <fstream>
10
11 #include "xstd/RegEx.h"
12 #include "xstd/Size.h"
13 #include "xstd/gadgets.h"
14 #include "base/opts.h"
15 #include "base/polyOpts.h"
16 #include "base/CmdLine.h"
17 #include "base/RndPermut.h"
18 #include "pgl/PglPp.h"
19 #include "pgl/PglCtx.h"
20
21 #include "pgl/PglStaticSemx.h"
22 #include "pgl/PglContainerSym.h"
23 #include "pgl/PglStringSym.h"
24 #include "pgl/PglRegExSym.h"
25 #include "pgl/AgentSymIter.h"
26 #include "pgl/AclSym.h"
27 #include "pgl/RobotSym.h"
28
29
30 class MyOpts: public OptGrp {
31 public:
MyOpts()32 MyOpts():
33 theHelpOpt(this, "help", "list of options"),
34 theVersOpt(this, "version", "package version info"),
35 theCfgName(this, "config <filename>", "PGL configuration"),
36 theCfgDirs(this, "cfg_dirs <dirs>", "directories for PGL #includes"),
37 theGlbRngSeed(this, "global_rng_seed <int>","per-test r.n.g. seed", 1)
38 {}
39
40 virtual bool validate() const;
41
42 public:
43 HelpOpt theHelpOpt;
44 VersionOpt theVersOpt;
45 StrOpt theCfgName;
46 StrArrOpt theCfgDirs;
47 IntOpt theGlbRngSeed;
48 };
49
50 class Rules;
51
52 class RuleCondnItem {
53 public:
RuleCondnItem(RegEx * anExpr=0,RegExExpr::Oper anOper=RegExExpr::opNone)54 RuleCondnItem(RegEx *anExpr = 0, RegExExpr::Oper anOper = RegExExpr::opNone): oper(anOper), expr(anExpr) {}
55
56 bool operator ==(const RuleCondnItem &i) const;
operator !=(const RuleCondnItem & i) const57 bool operator !=(const RuleCondnItem &i) const { return !(*this == i); }
58 int cmp(const RuleCondnItem &i) const;
59
60 RuleCondnItem negate() const;
61
62 ostream &print(ostream &os) const;
63
64 public:
65 RegExExpr::Oper oper;
66 RegEx *expr;
67 };
68
69 class RuleCondn;
70 class RuleCondnHash {
71 public:
72 RuleCondnHash(const RuleCondn &aBase, int expCount = 0);
73
expCount() const74 int expCount() const { return theExpCount; }
capacity() const75 int capacity() const { return theIds.capacity(); }
76
77 int idxOf(const RuleCondnItem &i) const; // returns -1 if none
78
79 void add(const RuleCondnItem &i, int idx);
80
81 protected:
82 int hash(const RuleCondnItem &i) const;
83
84 protected:
85 const RuleCondn &theBase;
86 Array<int> theIds;
87 int theExpCount;
88 int theCount;
89 };
90
91 // array of possibly negated RegExes
92 class RuleCondn {
93 public:
94 RuleCondn(int aCapacity = 0);
95
count() const96 int count() const { return theItems.count(); }
97 bool has(const RuleCondnItem &h, int offset = 0) const;
98
item(int idx) const99 const RuleCondnItem &item(int idx) const { return theItems.item(idx); }
100
101 bool operator ==(const RuleCondn &condn) const;
operator !=(const RuleCondn & condn) const102 bool operator !=(const RuleCondn &condn) const { return !(*this == condn); }
103 int cmp(const RuleCondn &c) const;
104
105 bool possible() const;
106 bool covers(const RuleCondn &c) const;
107
108 void add(const RuleCondnItem &i);
109
110 void sort();
111 void prune();
112 void merge(const RuleCondn &c);
113 void simplify(Rules &rules, int offset);
114
115 ostream &print(ostream &os) const;
116
117 protected:
118 void createHash(int expCount = 8);
119 void reHash();
120
121 private:
122 Array<RuleCondnItem> theItems;
123 RuleCondnHash *theHash;
124 };
125
126 // condition -> action
127 class AclRule {
128 public:
129 enum Action { rlUnknown, rlAllow, rlDeny, rlRewrite, rlAny };
130
131 public:
132 AclRule(Action anAction, RuleCondn *aCondn);
133
action() const134 Action action() const { return theAction; }
condn() const135 const RuleCondn &condn() const { return *theCondn; }
136
137 bool operator ==(const AclRule &rule) const;
operator !=(const AclRule & rule) const138 bool operator !=(const AclRule &rule) const { return !(*this == rule); }
139 int cmp(const AclRule &r) const;
140 bool covers(const AclRule &r) const;
141
142 void sort();
143 void prune();
144 void simplify(Rules &rules, int offset);
145
146 ostream &print(ostream &os) const;
147
148 protected:
149 Action theAction;
150 RuleCondn *theCondn;
151 };
152
153 // array of rules
154 class Rules: public Array<AclRule*> {
155 public:
Rules()156 Rules(): Array<AclRule*>(16) {}
~Rules()157 ~Rules() { while (count()) delete pop(); }
158
159 const AclRule *covered(const AclRule &r, int excIdx = -1) const;
160
161 //const AclRule *haveOtherAction(const AclRule &rule) const;
162 //const AclRule *haveAnyAction(const AclRule &rule) const;
163
164 const AclRule *commit(const AclRule &rule);
165
166 void sort();
167 void prune();
168 void simplify();
169
170 ostream &print(ostream &os) const;
171 };
172
173 class Normalizer {
174 public:
175 Normalizer(AclRule::Action anAction, const RegExExpr &expr, Rules &rules);
176
177 void run();
178
179 protected:
180 void doNoneOp();
181 void doNotOp();
182 void doAndOp();
183 void doOrOp();
184
185 protected:
186 AclRule::Action theAction;
187 const RegExExpr &theExpr;
188 Rules &theRules;
189 };
190
191
192 static MyOpts TheOpts;
193
194
195 /* MyOpt */
196
validate() const197 bool MyOpts::validate() const {
198 if (!theCfgName)
199 cerr << "must specify PGL configuration file (--config)" << endl;
200 else
201 return true;
202 return false;
203 }
204
205
206 /* RuleCondnItem */
207
operator ==(const RuleCondnItem & item) const208 bool RuleCondnItem::operator ==(const RuleCondnItem &item) const {
209 return oper == item.oper && expr->image() == item.expr->image();
210 }
211
cmp(const RuleCondnItem & i) const212 int RuleCondnItem::cmp(const RuleCondnItem &i) const {
213 if (const int operDiff = oper - i.oper)
214 return operDiff;
215
216 return expr->image().cmp(i.expr->image());
217 }
218
negate() const219 RuleCondnItem RuleCondnItem::negate() const {
220 const RegExExpr::Oper o = oper == RegExExpr::opNone ?
221 RegExExpr::opNot : RegExExpr::opNone;
222 return RuleCondnItem(expr, o);
223 }
224
print(ostream & os) const225 ostream &RuleCondnItem::print(ostream &os) const {
226 if (oper == RegExExpr::opNot)
227 os << "not ";
228
229 const char *scope = 0;
230 if (expr->image().cmp("user_group=", 11) == 0)
231 scope = "group";
232 else
233 if (expr->image().cmp("url=", 4) == 0)
234 scope = "url";
235 else
236 if (expr->image().cmp("url_host=", 9) == 0)
237 scope = "server-name";
238 else
239 if (expr->image().cmp("url_host_ip=", 12) == 0)
240 scope = "server-ip";
241 else
242 if (expr->image().cmp("url_path=", 9) == 0)
243 scope = "url";
244
245 os << scope << ' ';
246
247 if ((expr->flags() & RegEx::reExact) == 0)
248 os << "matches ";
249
250 os << '"' << expr->pattern() << '"';
251
252 return os;
253 }
254
255
256 /* RuleCondnHash */
257
RuleCondnHash(const RuleCondn & aBase,int expCount)258 RuleCondnHash::RuleCondnHash(const RuleCondn &aBase, int expCount):
259 theBase(aBase), theExpCount(expCount), theCount(0) {
260 theIds.resize(expCount*3 + 1);
261 }
262
idxOf(const RuleCondnItem & i) const263 int RuleCondnHash::idxOf(const RuleCondnItem &i) const {
264 const int pos = hash(i);
265 for (int p = pos, c = 0; theIds[p]; ++p, p %= theIds.count(), ++c) {
266 const int idx = theIds[p] - 1;
267 if (theBase.item(idx) == i)
268 return idx;
269 Assert(c < theIds.count());
270 }
271 return -1;
272 }
273
add(const RuleCondnItem & i,int idx)274 void RuleCondnHash::add(const RuleCondnItem &i, int idx) {
275 Assert(theCount < theIds.count());
276 Assert(idx >= 0);
277
278 int p = hash(i);
279 for (; theIds[p]; ++p, p %= theIds.count()) {
280 const int idx = theIds[p] - 1;
281 if (theBase.item(idx) == i)
282 return; // do not hash duplicates?
283 }
284
285 theIds[p] = idx + 1;
286 theCount++;
287 }
288
StrHash(const String & s)289 inline int StrHash(const String &s) {
290 const int step = Max(1, s.len() / 16);
291 unsigned int res = 0;
292 for (int i = 0; i < s.len(); i += step)
293 res = res*33U + s.data()[i];
294
295 return Max(1, abs((int)res));
296 }
297
hash(const RuleCondnItem & i) const298 int RuleCondnHash::hash(const RuleCondnItem &i) const {
299 // XXX: replace StrHash with String::hash
300 return abs(i.oper ^ StrHash(i.expr->image())) % theIds.count();
301 }
302
303
304 /* RuleCondn */
305
RuleCondn(int aCapacity)306 RuleCondn::RuleCondn(int aCapacity): theItems(aCapacity), theHash(0) {
307 if (aCapacity > 1)
308 createHash(aCapacity);
309 }
310
operator ==(const RuleCondn & c) const311 bool RuleCondn::operator ==(const RuleCondn &c) const {
312 if (count() != c.count())
313 return false;
314
315 for (int i = 0; i < c.count(); ++i) {
316 if (!has(c.item(i)))
317 return false;
318 }
319
320 return true;
321 }
322
has(const RuleCondnItem & h,int offset) const323 bool RuleCondn::has(const RuleCondnItem &h, int offset) const {
324 if (theHash)
325 return theHash->idxOf(h) >= offset;
326
327 for (int i = offset; i < count(); ++i) {
328 if (item(i) == h)
329 return true;
330 }
331 return false;
332 }
333
possible() const334 bool RuleCondn::possible() const {
335 for (int i = 0; i < count(); ++i) {
336 if (has(item(i).negate(), i+1))
337 return false;
338 }
339 return true;
340 }
341
covers(const RuleCondn & c) const342 bool RuleCondn::covers(const RuleCondn &c) const {
343 for (int i = 0; i < count(); ++i) {
344 if (!c.has(item(i)))
345 return false;
346 }
347 return true;
348 }
349
cmp(const RuleCondn & c) const350 int RuleCondn::cmp(const RuleCondn &c) const {
351 if (int cntDiff = count() - c.count())
352 return cntDiff;
353
354 Assert(count() == c.count());
355 for (int i = 0; i < count(); ++i) {
356 if (int itemDiff = item(i).cmp(c.item(i)))
357 return itemDiff;
358 }
359
360 return 0;
361 }
362
363 static
cmpRuleCondnItems(const void * ip1,const void * ip2)364 int cmpRuleCondnItems(const void *ip1, const void *ip2) {
365 const RuleCondnItem &i1 = *(const RuleCondnItem*)ip1;
366 const RuleCondnItem &i2 = *(const RuleCondnItem*)ip2;
367 return (i1.cmp(i2));
368 }
369
sort()370 void RuleCondn::sort() {
371 delete theHash;
372 theHash = 0;
373 qsort(theItems.items(), count(), sizeof(*theItems.items()), &cmpRuleCondnItems);
374 createHash(count());
375 }
376
prune()377 void RuleCondn::prune() {
378 Array<RuleCondnItem> prunedItems;
379
380 // remove duplicates
381 for (int i = 0; i < count(); ++i) {
382 if (!has(item(i), i+1))
383 prunedItems.append(item(i));
384 }
385
386 if (prunedItems.count() != count()) {
387 theItems = prunedItems;
388 reHash();
389 }
390 }
391
merge(const RuleCondn & c)392 void RuleCondn::merge(const RuleCondn &c) {
393 theItems.stretch(count() + c.count());
394 for (int i = 0; i < c.count(); ++i)
395 if (!has(c.item(i)))
396 add(c.item(i));
397 }
398
simplify(Rules & rules,int offset)399 void RuleCondn::simplify(Rules &rules, int offset) {
400 Array<RuleCondnItem> prunedItems;
401
402 for (int i = 0; i < count(); ++i) {
403 const RuleCondnItem &ci = item(i);
404 const RuleCondnItem notCi = ci.negate();
405 bool ejectCandidate = true;
406 for (int r = offset; ejectCandidate && r < rules.count(); ++r) {
407 ejectCandidate = rules[r]->condn().has(ci) ||
408 !rules[r]->condn().has(notCi);
409 }
410 if (!ejectCandidate)
411 prunedItems.append(ci);
412 }
413
414 if (prunedItems.count() != count()) {
415 theItems = prunedItems;
416 reHash();
417 }
418 }
419
print(ostream & os) const420 ostream &RuleCondn::print(ostream &os) const {
421 if (!count())
422 return os << "any";
423
424 for (int i = 0; i < count(); ++i) {
425 if (i)
426 os << " and ";
427 item(i).print(os);
428 }
429 return os;
430 }
431
add(const RuleCondnItem & i)432 void RuleCondn::add(const RuleCondnItem &i) {
433 if (count() && !theHash)
434 createHash();
435
436 if (theHash && count() > 0.75*theHash->capacity())
437 reHash();
438
439 if (theHash)
440 theHash->add(i, theItems.count());
441
442 theItems.append(i);
443 }
444
createHash(int expCount)445 void RuleCondn::createHash(int expCount) {
446 if (theHash && expCount <= theHash->expCount())
447 return;
448
449 if (theHash)
450 delete theHash;
451 theHash = new RuleCondnHash(*this, expCount);
452
453 for (int i = 0; i < count(); ++i)
454 theHash->add(item(i), i);
455 }
456
reHash()457 void RuleCondn::reHash() {
458 delete theHash;
459 theHash = 0;
460 createHash(count());
461 }
462
463 /* AclRule */
464
AclRule(Action anAction,RuleCondn * aCondn)465 AclRule::AclRule(Action anAction, RuleCondn *aCondn):
466 theAction(anAction), theCondn(aCondn) {
467 }
468
operator ==(const AclRule & rule) const469 bool AclRule::operator ==(const AclRule &rule) const {
470 return theAction == rule.theAction && (*theCondn) == (*rule.theCondn);
471 }
472
covers(const AclRule & r) const473 bool AclRule::covers(const AclRule &r) const {
474 return action() == r.action() && theCondn->covers(*r.theCondn);
475 }
476
cmp(const AclRule & r) const477 int AclRule::cmp(const AclRule &r) const {
478 if (const int cndDiff = theCondn->cmp(*r.theCondn))
479 return cndDiff;
480
481 return action() - r.action();
482 }
483
sort()484 void AclRule::sort() {
485 theCondn->sort();
486 }
487
prune()488 void AclRule::prune() {
489 theCondn->prune();
490 }
491
simplify(Rules & rules,int offset)492 void AclRule::simplify(Rules &rules, int offset) {
493 theCondn->simplify(rules, offset);
494 }
495
print(ostream & os) const496 ostream &AclRule::print(ostream &os) const {
497 if (theAction == rlAllow)
498 os << "allow ";
499 else
500 if (theAction == rlDeny)
501 os << "deny ";
502 else
503 if (theAction == rlRewrite)
504 os << "rewrite ";
505 else
506 Assert(false);
507
508 Assert(theCondn);
509 theCondn->print(os);
510 return os << endl;
511 }
512
513 /* Rules */
514
515 #if FUTURE_CODE
516 // checks both for (A,a) and (A,any)
cover(const AclRule & rule) const517 const AclRule *Rules::cover(const AclRule &rule) const {
518 for (int i = 0; i < count(); ++i) {
519 if (item(i)->action() != rule.action())
520 continue;
521 if (item(i).body() == AclRule::AnyCondn)
522 return &item(i);
523 if (rule.body() == item(i)::body())
524 return &item(i);
525 }
526 return 0;
527 }
528
529 // checks for both (!A,a) and (!A,any)
coverOtherAction(const AclRule & rule) const530 const AclRule *Rules::coverOtherAction(const AclRule &rule) const {
531 for (int i = 0; i < count(); ++i) {
532 if (item(i).action() == rule.action())
533 continue;
534 if (item(i).body() == AclRule::AnyCondn)
535 return &item(i);
536 if (rule.body() == item(i)::body())
537 return &item(i);
538 }
539 return 0;
540 }
541
542 // checks for (*,a)
coverAnyAction(const AclRule & rule) const543 const AclRule *Rules::coverAnyAction(const AclRule &rule) const {
544 for (int i = 0; i < count(); ++i) {
545 if (rule.body() == item(i)::body())
546 return &item(i);
547 }
548 return 0;
549 }
550
commit(const AclRule & rule)551 const AclRule *Rules::commit(const AclRule &rule) {
552 append(rule.clone());
553 return last();
554 }
555 #endif
556
covered(const AclRule & r,int excIdx) const557 const AclRule *Rules::covered(const AclRule &r, int excIdx) const {
558 for (int i = 0; i < count(); ++i) {
559 if (i == excIdx)
560 continue;
561 if (item(i)->covers(r))
562 return item(i);
563 }
564 return 0;
565 }
566
prune()567 void Rules::prune() {
568 // prune individual rules
569 {for (int i = 0; i < count(); ++i)
570 item(i)->prune();
571 }
572
573 // remove duplicates and rules with impossible conditions
574 // also remove rules that are covered by more general rules
575 {for (int i = 0; i < count();) {
576 const AclRule &r = *item(i);
577 bool bad = !r.condn().possible() || covered(*item(i), i);
578 for (int goodIdx = 0; !bad && goodIdx < i; ++goodIdx) {
579 bad = *item(goodIdx) == r;
580 }
581 if (bad)
582 eject(i);
583 else
584 ++i;
585 }}
586 }
587
588 static
cmpRules(const void * rp1,const void * rp2)589 int cmpRules(const void *rp1, const void *rp2) {
590 const AclRule *r1 = *(const AclRule**)rp1;
591 const AclRule *r2 = *(const AclRule**)rp2;
592 return (r1->cmp(*r2));
593 }
594
sort()595 void Rules::sort() {
596 // sort individual rules
597 for (int i = 0; i < count(); ++i)
598 item(i)->sort();
599
600 qsort(items(), count(), sizeof(*items()), &cmpRules);
601 }
602
simplify()603 void Rules::simplify() {
604 for (int i = 0; i < count(); ++i)
605 item(i)->simplify(*this, i+1);
606 }
607
print(ostream & os) const608 ostream &Rules::print(ostream &os) const {
609 for (int i = 0; i < count(); ++i) {
610 //cout << setw(4) << setfill('0') << i << setfill(' ') << ' ';
611 item(i)->print(cout);
612 }
613 return os;
614 }
615
616 /* Normalizer */
617
Normalizer(AclRule::Action anAction,const RegExExpr & anExpr,Rules & aRules)618 Normalizer::Normalizer(AclRule::Action anAction, const RegExExpr &anExpr, Rules &aRules):
619 theAction(anAction), theExpr(anExpr), theRules(aRules) {
620 }
621
doNoneOp()622 void Normalizer::doNoneOp() {
623 RuleCondn *condn = new RuleCondn(1);
624 condn->add(RuleCondnItem(theExpr.theVal));
625 AclRule *rule = new AclRule(theAction, condn);
626 theRules.append(rule);
627 }
628
doNotOp()629 void Normalizer::doNotOp() {
630 RegExExpr *e = theExpr.theLhs;
631 Assert(e);
632 switch (e->theOper) {
633 case RegExExpr::opNone: {
634 RuleCondn *condn = new RuleCondn(1);
635 condn->add(RuleCondnItem(e->theVal, RegExExpr::opNot));
636 AclRule *rule = new AclRule(theAction, condn);
637 theRules.append(rule);
638 break;
639 }
640 case RegExExpr::opNot: {
641 Normalizer n(theAction, *e->theLhs, theRules);
642 n.run();
643 break;
644 }
645 case RegExExpr::opAnd: {
646 RegExExpr notL(e->theLhs, RegExExpr::opNot, 0);
647 RegExExpr notR(e->theRhs, RegExExpr::opNot, 0);
648 RegExExpr x(¬L, RegExExpr::opOr, ¬R);
649 Normalizer n(theAction, x, theRules);
650 n.run();
651 break;
652 }
653 case RegExExpr::opOr: {
654 RegExExpr notL(e->theLhs, RegExExpr::opNot, 0);
655 RegExExpr notR(e->theRhs, RegExExpr::opNot, 0);
656 RegExExpr x(¬L, RegExExpr::opAnd, ¬R);
657 Normalizer n(theAction, x, theRules);
658 n.run();
659 break;
660 }
661 default:
662 Assert(false);
663 }
664 }
665
doOrOp()666 void Normalizer::doOrOp() {
667 Normalizer lhs(theAction, *theExpr.theLhs, theRules);
668 lhs.run();
669
670 Normalizer rhs(theAction, *theExpr.theRhs, theRules);
671 rhs.run();
672 }
673
doAndOp()674 void Normalizer::doAndOp() {
675 Rules lhsRules;
676 Normalizer lhs(theAction, *theExpr.theLhs, lhsRules);
677 lhs.run();
678
679 Rules rhsRules;
680 Normalizer rhs(theAction, *theExpr.theRhs, rhsRules);
681 rhs.run();
682
683 //rhsRules.prune();
684 //lhsRules.prune();
685
686 theRules.stretch(theRules.count() + lhsRules.count()*rhsRules.count());
687 for (int l = 0; l < lhsRules.count(); ++l) {
688 for (int r = 0; r < rhsRules.count(); ++r) {
689 RuleCondn *condn = new RuleCondn;
690 condn->merge(lhsRules[l]->condn());
691 condn->merge(rhsRules[r]->condn());
692 AclRule *rule = new AclRule(theAction, condn);
693 theRules.append(rule);
694 }
695 }
696 }
697
run()698 void Normalizer::run() {
699 switch (theExpr.theOper) {
700 case RegExExpr::opNone: {
701 doNoneOp();
702 break;
703 }
704 case RegExExpr::opNot: {
705 doNotOp();
706 break;
707 }
708 case RegExExpr::opAnd: {
709 doAndOp();
710 break;
711 }
712 case RegExExpr::opOr: {
713 doOrOp();
714 break;
715 }
716 default:
717 Assert(false);
718 }
719 }
720
721 static
normalizeRules(AclRule::Action action,const RegExExpr & expr,Rules & rules)722 void normalizeRules(AclRule::Action action, const RegExExpr &expr, Rules &rules) {
723 Normalizer n(action, expr, rules);
724 n.run();
725 }
726
main(int argc,char ** argv)727 int main(int argc, char **argv) {
728 CmdLine cmd;
729 cmd.configure(Array<OptGrp*>() << &TheOpts);
730 if (!cmd.parse(argc, argv) || !TheOpts.validate())
731 return -1;
732
733 configureStream(cout, 2);
734 configureStream(clog, 3);
735
736 GlbPermut().reseed(TheOpts.theGlbRngSeed);
737
738 clog << argv[0] << ": parsing..." << endl;
739 TheOpts.theCfgDirs.copy(PglPp::TheDirs);
740 PglStaticSemx::Interpret(TheOpts.theCfgName);
741
742 clog << argv[0] << ": collecting rules..." << endl;
743 // collect all rules
744 Rules rules;
745 for (AgentSymIter ai(PglStaticSemx::TheAgentsToUse, RobotSym::TheType, false); ai; ++ai) {
746 const RobotSym &robot = (const RobotSym&)*ai.agent();
747 if (AclSym *acl = robot.acl()) {
748 if (acl->allow())
749 normalizeRules(AclRule::rlAllow, *acl->allow(), rules);
750 if (acl->deny())
751 normalizeRules(AclRule::rlDeny, *acl->deny(), rules);
752 if (acl->rewrite())
753 normalizeRules(AclRule::rlRewrite, *acl->rewrite(), rules);
754 }
755 }
756
757 // collect all groups
758 //for (int i = 0; i < PglSemx::TheUserGroupsToUse; ++i)
759 // TheGroups.append(PglSemx::TheUserGroupsToUse[i]);
760
761 clog << argv[0] << ": pruning " << rules.count() << " rules ..." << endl;
762 rules.prune();
763 // check for conflicts
764 // check for coverage (holes)
765
766 clog << argv[0] << ": sorting " << rules.count() << " rules ..." << endl;
767 rules.sort();
768
769 clog << argv[0] << ": simplifying..." << endl;
770 rules.simplify(); // prune based on order
771
772 clog << argv[0] << ": printing " << rules.count() << " rules ..." << endl;
773 rules.print(cout);
774
775 return 0;
776 }
777