1 // replace-util.h
2
3
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 //
16 // Copyright 2005-2010 Google, Inc.
17 // Author: riley@google.com (Michael Riley)
18 //
19
20 // \file
21 // Utility classes for the recursive replacement of Fsts (RTNs).
22
23 #ifndef FST_LIB_REPLACE_UTIL_H__
24 #define FST_LIB_REPLACE_UTIL_H__
25
26 #include <vector>
27 using std::vector;
28 #include <unordered_map>
29 using std::unordered_map;
30 using std::unordered_multimap;
31 #include <unordered_set>
32 using std::unordered_set;
33 using std::unordered_multiset;
34 #include <map>
35
36 #include <fst/connect.h>
37 #include <fst/mutable-fst.h>
38 #include <fst/topsort.h>
39
40
41 namespace fst {
42
43 // This specifies what labels to output on the call or return arc
44 // REPLACE_LABEL_NEITHER puts epsilon labels on both input and output
45 // REPLACE_LABEL_BOTH puts non-epsilon labels on both input and output
46 // REPLACE_LABEL_INPUT puts non-epsilon on input and epsilon on output
47 // REPLACE_LABEL_OUTPUT puts epsilon on input and non-epsilon on output
48 // Note that REPLACE_LABEL_INPUT and REPLACE_LABEL_OUTPUT produce transducers
49 enum ReplaceLabelType { REPLACE_LABEL_NEITHER = 1, REPLACE_LABEL_INPUT = 2,
50 REPLACE_LABEL_OUTPUT = 3, REPLACE_LABEL_BOTH = 4 };
51
52 // By default ReplaceUtil will copy the input label of the 'replace arc'.
53 // The call_label_type and return_label_type options specify how to manage
54 // the labels of the call arc and the return arc of the replace FST
55 template <class A>
56 struct ReplaceUtilOptions : CacheOptions {
57 int64 root; // root rule for expansion
58 ReplaceLabelType call_label_type; // how to label call arc
59 ReplaceLabelType return_label_type; // how to label return arc
60 int64 return_label; // specifies label to put on return arc
61
ReplaceUtilOptionsReplaceUtilOptions62 ReplaceUtilOptions(int64 r, ReplaceLabelType call_label_type,
63 ReplaceLabelType return_label_type, int64 return_label)
64 : root(r),
65 call_label_type(call_label_type),
66 return_label_type(return_label_type),
67 return_label(return_label) {}
68
ReplaceUtilOptionsReplaceUtilOptions69 explicit ReplaceUtilOptions(int64 r)
70 : root(r),
71 call_label_type(REPLACE_LABEL_INPUT),
72 return_label_type(REPLACE_LABEL_NEITHER),
73 return_label(0) {}
74
ReplaceUtilOptionsReplaceUtilOptions75 ReplaceUtilOptions(int64 r, bool epsilon_replace_arc) // b/w compatibility
76 : root(r),
77 call_label_type((epsilon_replace_arc) ? REPLACE_LABEL_NEITHER :
78 REPLACE_LABEL_INPUT),
79 return_label_type(REPLACE_LABEL_NEITHER),
80 return_label(0) {}
81
ReplaceUtilOptionsReplaceUtilOptions82 ReplaceUtilOptions()
83 : root(kNoLabel),
84 call_label_type(REPLACE_LABEL_INPUT),
85 return_label_type(REPLACE_LABEL_NEITHER),
86 return_label(0) {}
87 };
88
89 template <class Arc>
90 void Replace(const vector<pair<typename Arc::Label, const Fst<Arc>* > >&,
91 MutableFst<Arc> *, fst::ReplaceUtilOptions<Arc> opts);
92
93 // Utility class for the recursive replacement of Fsts (RTNs). The
94 // user provides a set of Label, Fst pairs at construction. These are
95 // used by methods for testing cyclic dependencies and connectedness
96 // and doing RTN connection and specific Fst replacement by label or
97 // for various optimization properties. The modified results can be
98 // obtained with the GetFstPairs() or GetMutableFstPairs() methods.
99 template <class Arc>
100 class ReplaceUtil {
101 public:
102 typedef typename Arc::Label Label;
103 typedef typename Arc::Weight Weight;
104 typedef typename Arc::StateId StateId;
105
106 typedef pair<Label, const Fst<Arc>*> FstPair;
107 typedef pair<Label, MutableFst<Arc>*> MutableFstPair;
108 typedef unordered_map<Label, Label> NonTerminalHash;
109
110 // Constructs from mutable Fsts; Fst ownership given to ReplaceUtil.
111 ReplaceUtil(const vector<MutableFstPair> &fst_pairs,
112 const ReplaceUtilOptions<Arc> &opts);
113
114 // Constructs from Fsts; Fst ownership retained by caller.
115 ReplaceUtil(const vector<FstPair> &fst_pairs,
116 const ReplaceUtilOptions<Arc> &opts);
117
118 // Constructs from ReplaceFst internals; ownership retained by caller.
119 ReplaceUtil(const vector<const Fst<Arc> *> &fst_array,
120 const NonTerminalHash &nonterminal_hash,
121 const ReplaceUtilOptions<Arc> &opts);
122
~ReplaceUtil()123 ~ReplaceUtil() {
124 for (Label i = 0; i < fst_array_.size(); ++i)
125 delete fst_array_[i];
126 }
127
128 // True if the non-terminal dependencies are cyclic. Cyclic
129 // dependencies will result in an unexpandable replace fst.
CyclicDependencies()130 bool CyclicDependencies() const {
131 GetDependencies(false);
132 return depprops_ & kCyclic;
133 }
134
135 // Returns true if no useless Fsts, states or transitions.
Connected()136 bool Connected() const {
137 GetDependencies(false);
138 uint64 props = kAccessible | kCoAccessible;
139 for (Label i = 0; i < fst_array_.size(); ++i) {
140 if (!fst_array_[i])
141 continue;
142 if (fst_array_[i]->Properties(props, true) != props || !depaccess_[i])
143 return false;
144 }
145 return true;
146 }
147
148 // Removes useless Fsts, states and transitions.
149 void Connect();
150
151 // Replaces Fsts specified by labels.
152 // Does nothing if there are cyclic dependencies.
153 void ReplaceLabels(const vector<Label> &labels);
154
155 // Replaces Fsts that have at most 'nstates' states, 'narcs' arcs and
156 // 'nnonterm' non-terminals (updating in reverse dependency order).
157 // Does nothing if there are cyclic dependencies.
158 void ReplaceBySize(size_t nstates, size_t narcs, size_t nnonterms);
159
160 // Replaces singleton Fsts.
161 // Does nothing if there are cyclic dependencies.
ReplaceTrivial()162 void ReplaceTrivial() { ReplaceBySize(2, 1, 1); }
163
164 // Replaces non-terminals that have at most 'ninstances' instances
165 // (updating in dependency order).
166 // Does nothing if there are cyclic dependencies.
167 void ReplaceByInstances(size_t ninstances);
168
169 // Replaces non-terminals that have only one instance.
170 // Does nothing if there are cyclic dependencies.
ReplaceUnique()171 void ReplaceUnique() { ReplaceByInstances(1); }
172
173 // Returns Label, Fst pairs; Fst ownership retained by ReplaceUtil.
174 void GetFstPairs(vector<FstPair> *fst_pairs);
175
176 // Returns Label, MutableFst pairs; Fst ownership given to caller.
177 void GetMutableFstPairs(vector<MutableFstPair> *mutable_fst_pairs);
178
179 private:
180 // Per Fst statistics
181 struct ReplaceStats {
182 StateId nstates; // # of states
183 StateId nfinal; // # of final states
184 size_t narcs; // # of arcs
185 Label nnonterms; // # of non-terminals in Fst
186 size_t nref; // # of non-terminal instances referring to this Fst
187
188 // # of times that ith Fst references this Fst
189 map<Label, size_t> inref;
190 // # of times that this Fst references the ith Fst
191 map<Label, size_t> outref;
192
ReplaceStatsReplaceStats193 ReplaceStats()
194 : nstates(0),
195 nfinal(0),
196 narcs(0),
197 nnonterms(0),
198 nref(0) {}
199 };
200
201 // Check Mutable Fsts exist o.w. create them.
202 void CheckMutableFsts();
203
204 // Computes the dependency graph of the replace Fsts.
205 // If 'stats' is true, dependency statistics computed as well.
206 void GetDependencies(bool stats) const;
207
ClearDependencies()208 void ClearDependencies() const {
209 depfst_.DeleteStates();
210 stats_.clear();
211 depprops_ = 0;
212 have_stats_ = false;
213 }
214
215 // Get topological order of dependencies. Returns false with cyclic input.
216 bool GetTopOrder(const Fst<Arc> &fst, vector<Label> *toporder) const;
217
218 // Update statistics assuming that jth Fst will be replaced.
219 void UpdateStats(Label j);
220
221 Label root_label_; // root non-terminal
222 Label root_fst_; // root Fst ID
223 ReplaceLabelType call_label_type_; // see Replace()
224 ReplaceLabelType return_label_type_; // see Replace()
225 int64 return_label_; // see Replace()
226 vector<const Fst<Arc> *> fst_array_; // Fst per ID
227 vector<MutableFst<Arc> *> mutable_fst_array_; // MutableFst per ID
228 vector<Label> nonterminal_array_; // Fst ID to non-terminal
229 NonTerminalHash nonterminal_hash_; // non-terminal to Fst ID
230 mutable VectorFst<Arc> depfst_; // Fst ID dependencies
231 mutable vector<bool> depaccess_; // Fst ID accessibility
232 mutable uint64 depprops_; // dependency Fst props
233 mutable bool have_stats_; // have dependency statistics
234 mutable vector<ReplaceStats> stats_; // Per Fst statistics
235 DISALLOW_COPY_AND_ASSIGN(ReplaceUtil);
236 };
237
238 template <class Arc>
ReplaceUtil(const vector<MutableFstPair> & fst_pairs,const ReplaceUtilOptions<Arc> & opts)239 ReplaceUtil<Arc>::ReplaceUtil(
240 const vector<MutableFstPair> &fst_pairs,
241 const ReplaceUtilOptions<Arc> &opts)
242 : root_label_(opts.root),
243 call_label_type_(opts.call_label_type),
244 return_label_type_(opts.return_label_type),
245 return_label_(opts.return_label),
246 depprops_(0),
247 have_stats_(false) {
248 fst_array_.push_back(0);
249 mutable_fst_array_.push_back(0);
250 nonterminal_array_.push_back(kNoLabel);
251 for (Label i = 0; i < fst_pairs.size(); ++i) {
252 Label label = fst_pairs[i].first;
253 MutableFst<Arc> *fst = fst_pairs[i].second;
254 nonterminal_hash_[label] = fst_array_.size();
255 nonterminal_array_.push_back(label);
256 fst_array_.push_back(fst);
257 mutable_fst_array_.push_back(fst);
258 }
259 root_fst_ = nonterminal_hash_[root_label_];
260 if (!root_fst_)
261 FSTERROR() << "ReplaceUtil: no root FST for label: " << root_label_;
262 }
263
264 template <class Arc>
ReplaceUtil(const vector<FstPair> & fst_pairs,const ReplaceUtilOptions<Arc> & opts)265 ReplaceUtil<Arc>::ReplaceUtil(
266 const vector<FstPair> &fst_pairs,
267 const ReplaceUtilOptions<Arc> &opts)
268 : root_label_(opts.root),
269 call_label_type_(opts.call_label_type),
270 return_label_type_(opts.return_label_type),
271 return_label_(opts.return_label),
272 depprops_(0),
273 have_stats_(false) {
274 fst_array_.push_back(0);
275 nonterminal_array_.push_back(kNoLabel);
276 for (Label i = 0; i < fst_pairs.size(); ++i) {
277 Label label = fst_pairs[i].first;
278 const Fst<Arc> *fst = fst_pairs[i].second;
279 nonterminal_hash_[label] = fst_array_.size();
280 nonterminal_array_.push_back(label);
281 fst_array_.push_back(fst->Copy());
282 }
283 root_fst_ = nonterminal_hash_[root_label_];
284 if (!root_fst_)
285 FSTERROR() << "ReplaceUtil: no root FST for label: " << root_label_;
286 }
287
288 template <class Arc>
ReplaceUtil(const vector<const Fst<Arc> * > & fst_array,const NonTerminalHash & nonterminal_hash,const ReplaceUtilOptions<Arc> & opts)289 ReplaceUtil<Arc>::ReplaceUtil(
290 const vector<const Fst<Arc> *> &fst_array,
291 const NonTerminalHash &nonterminal_hash,
292 const ReplaceUtilOptions<Arc> &opts)
293 : root_fst_(opts.root),
294 call_label_type_(opts.call_label_type),
295 return_label_type_(opts.return_label_type),
296 return_label_(opts.return_label),
297 nonterminal_array_(fst_array.size()),
298 nonterminal_hash_(nonterminal_hash),
299 depprops_(0),
300 have_stats_(false) {
301 fst_array_.push_back(0);
302 for (Label i = 1; i < fst_array.size(); ++i)
303 fst_array_.push_back(fst_array[i]->Copy());
304 for (typename NonTerminalHash::const_iterator it =
305 nonterminal_hash.begin(); it != nonterminal_hash.end(); ++it)
306 nonterminal_array_[it->second] = it->first;
307 root_label_ = nonterminal_array_[root_fst_];
308 }
309
310 template <class Arc>
GetDependencies(bool stats)311 void ReplaceUtil<Arc>::GetDependencies(bool stats) const {
312 if (depfst_.NumStates() > 0) {
313 if (stats && !have_stats_)
314 ClearDependencies();
315 else
316 return;
317 }
318
319 have_stats_ = stats;
320 if (have_stats_)
321 stats_.reserve(fst_array_.size());
322
323 for (Label i = 0; i < fst_array_.size(); ++i) {
324 depfst_.AddState();
325 depfst_.SetFinal(i, Weight::One());
326 if (have_stats_)
327 stats_.push_back(ReplaceStats());
328 }
329 depfst_.SetStart(root_fst_);
330
331 // An arc from each state (representing the fst) to the
332 // state representing the fst being replaced
333 for (Label i = 0; i < fst_array_.size(); ++i) {
334 const Fst<Arc> *ifst = fst_array_[i];
335 if (!ifst)
336 continue;
337 for (StateIterator<Fst<Arc> > siter(*ifst); !siter.Done(); siter.Next()) {
338 StateId s = siter.Value();
339 if (have_stats_) {
340 ++stats_[i].nstates;
341 if (ifst->Final(s) != Weight::Zero())
342 ++stats_[i].nfinal;
343 }
344 for (ArcIterator<Fst<Arc> > aiter(*ifst, s);
345 !aiter.Done(); aiter.Next()) {
346 if (have_stats_)
347 ++stats_[i].narcs;
348 const Arc& arc = aiter.Value();
349
350 typename NonTerminalHash::const_iterator it =
351 nonterminal_hash_.find(arc.olabel);
352 if (it != nonterminal_hash_.end()) {
353 Label j = it->second;
354 depfst_.AddArc(i, Arc(arc.olabel, arc.olabel, Weight::One(), j));
355 if (have_stats_) {
356 ++stats_[i].nnonterms;
357 ++stats_[j].nref;
358 ++stats_[j].inref[i];
359 ++stats_[i].outref[j];
360 }
361 }
362 }
363 }
364 }
365
366 // Gets accessibility info
367 SccVisitor<Arc> scc_visitor(0, &depaccess_, 0, &depprops_);
368 DfsVisit(depfst_, &scc_visitor);
369 }
370
371 template <class Arc>
UpdateStats(Label j)372 void ReplaceUtil<Arc>::UpdateStats(Label j) {
373 if (!have_stats_) {
374 FSTERROR() << "ReplaceUtil::UpdateStats: stats not available";
375 return;
376 }
377
378 if (j == root_fst_) // can't replace root
379 return;
380
381 typedef typename map<Label, size_t>::iterator Iter;
382 for (Iter in = stats_[j].inref.begin();
383 in != stats_[j].inref.end();
384 ++in) {
385 Label i = in->first;
386 size_t ni = in->second;
387 stats_[i].nstates += stats_[j].nstates * ni;
388 stats_[i].narcs += (stats_[j].narcs + 1) * ni; // narcs - 1 + 2 (eps)
389 stats_[i].nnonterms += (stats_[j].nnonterms - 1) * ni;
390 stats_[i].outref.erase(stats_[i].outref.find(j));
391 for (Iter out = stats_[j].outref.begin();
392 out != stats_[j].outref.end();
393 ++out) {
394 Label k = out->first;
395 size_t nk = out->second;
396 stats_[i].outref[k] += ni * nk;
397 }
398 }
399
400 for (Iter out = stats_[j].outref.begin();
401 out != stats_[j].outref.end();
402 ++out) {
403 Label k = out->first;
404 size_t nk = out->second;
405 stats_[k].nref -= nk;
406 stats_[k].inref.erase(stats_[k].inref.find(j));
407 for (Iter in = stats_[j].inref.begin();
408 in != stats_[j].inref.end();
409 ++in) {
410 Label i = in->first;
411 size_t ni = in->second;
412 stats_[k].inref[i] += ni * nk;
413 stats_[k].nref += ni * nk;
414 }
415 }
416 }
417
418 template <class Arc>
CheckMutableFsts()419 void ReplaceUtil<Arc>::CheckMutableFsts() {
420 if (mutable_fst_array_.size() == 0) {
421 for (Label i = 0; i < fst_array_.size(); ++i) {
422 if (!fst_array_[i]) {
423 mutable_fst_array_.push_back(0);
424 } else {
425 mutable_fst_array_.push_back(new VectorFst<Arc>(*fst_array_[i]));
426 delete fst_array_[i];
427 fst_array_[i] = mutable_fst_array_[i];
428 }
429 }
430 }
431 }
432
433 template <class Arc>
Connect()434 void ReplaceUtil<Arc>::Connect() {
435 CheckMutableFsts();
436 uint64 props = kAccessible | kCoAccessible;
437 for (Label i = 0; i < mutable_fst_array_.size(); ++i) {
438 if (!mutable_fst_array_[i])
439 continue;
440 if (mutable_fst_array_[i]->Properties(props, false) != props)
441 fst::Connect(mutable_fst_array_[i]);
442 }
443 GetDependencies(false);
444 for (Label i = 0; i < mutable_fst_array_.size(); ++i) {
445 MutableFst<Arc> *fst = mutable_fst_array_[i];
446 if (fst && !depaccess_[i]) {
447 delete fst;
448 fst_array_[i] = 0;
449 mutable_fst_array_[i] = 0;
450 }
451 }
452 ClearDependencies();
453 }
454
455 template <class Arc>
GetTopOrder(const Fst<Arc> & fst,vector<Label> * toporder)456 bool ReplaceUtil<Arc>::GetTopOrder(const Fst<Arc> &fst,
457 vector<Label> *toporder) const {
458 // Finds topological order of dependencies.
459 vector<StateId> order;
460 bool acyclic = false;
461
462 TopOrderVisitor<Arc> top_order_visitor(&order, &acyclic);
463 DfsVisit(fst, &top_order_visitor);
464 if (!acyclic) {
465 LOG(WARNING) << "ReplaceUtil::GetTopOrder: Cyclical label dependencies";
466 return false;
467 }
468
469 toporder->resize(order.size());
470 for (Label i = 0; i < order.size(); ++i)
471 (*toporder)[order[i]] = i;
472
473 return true;
474 }
475
476 template <class Arc>
ReplaceLabels(const vector<Label> & labels)477 void ReplaceUtil<Arc>::ReplaceLabels(const vector<Label> &labels) {
478 CheckMutableFsts();
479 unordered_set<Label> label_set;
480 for (Label i = 0; i < labels.size(); ++i)
481 if (labels[i] != root_label_) // can't replace root
482 label_set.insert(labels[i]);
483
484 // Finds Fst dependencies restricted to the labels requested.
485 GetDependencies(false);
486 VectorFst<Arc> pfst(depfst_);
487 for (StateId i = 0; i < pfst.NumStates(); ++i) {
488 vector<Arc> arcs;
489 for (ArcIterator< VectorFst<Arc> > aiter(pfst, i);
490 !aiter.Done(); aiter.Next()) {
491 const Arc &arc = aiter.Value();
492 Label label = nonterminal_array_[arc.nextstate];
493 if (label_set.count(label) > 0)
494 arcs.push_back(arc);
495 }
496 pfst.DeleteArcs(i);
497 for (size_t j = 0; j < arcs.size(); ++j)
498 pfst.AddArc(i, arcs[j]);
499 }
500
501 vector<Label> toporder;
502 if (!GetTopOrder(pfst, &toporder)) {
503 ClearDependencies();
504 return;
505 }
506
507 // Visits Fsts in reverse topological order of dependencies and
508 // performs replacements.
509 for (Label o = toporder.size() - 1; o >= 0; --o) {
510 vector<FstPair> fst_pairs;
511 StateId s = toporder[o];
512 for (ArcIterator< VectorFst<Arc> > aiter(pfst, s);
513 !aiter.Done(); aiter.Next()) {
514 const Arc &arc = aiter.Value();
515 Label label = nonterminal_array_[arc.nextstate];
516 const Fst<Arc> *fst = fst_array_[arc.nextstate];
517 fst_pairs.push_back(make_pair(label, fst));
518 }
519 if (fst_pairs.empty())
520 continue;
521 Label label = nonterminal_array_[s];
522 const Fst<Arc> *fst = fst_array_[s];
523 fst_pairs.push_back(make_pair(label, fst));
524
525 Replace(fst_pairs, mutable_fst_array_[s],
526 ReplaceUtilOptions<Arc>(label, call_label_type_,
527 return_label_type_, return_label_));
528 }
529 ClearDependencies();
530 }
531
532 template <class Arc>
ReplaceBySize(size_t nstates,size_t narcs,size_t nnonterms)533 void ReplaceUtil<Arc>::ReplaceBySize(size_t nstates, size_t narcs,
534 size_t nnonterms) {
535 vector<Label> labels;
536 GetDependencies(true);
537
538 vector<Label> toporder;
539 if (!GetTopOrder(depfst_, &toporder)) {
540 ClearDependencies();
541 return;
542 }
543
544 for (Label o = toporder.size() - 1; o >= 0; --o) {
545 Label j = toporder[o];
546 if (stats_[j].nstates <= nstates &&
547 stats_[j].narcs <= narcs &&
548 stats_[j].nnonterms <= nnonterms) {
549 labels.push_back(nonterminal_array_[j]);
550 UpdateStats(j);
551 }
552 }
553 ReplaceLabels(labels);
554 }
555
556 template <class Arc>
ReplaceByInstances(size_t ninstances)557 void ReplaceUtil<Arc>::ReplaceByInstances(size_t ninstances) {
558 vector<Label> labels;
559 GetDependencies(true);
560
561 vector<Label> toporder;
562 if (!GetTopOrder(depfst_, &toporder)) {
563 ClearDependencies();
564 return;
565 }
566 for (Label o = 0; o < toporder.size(); ++o) {
567 Label j = toporder[o];
568 if (stats_[j].nref <= ninstances) {
569 labels.push_back(nonterminal_array_[j]);
570 UpdateStats(j);
571 }
572 }
573 ReplaceLabels(labels);
574 }
575
576 template <class Arc>
GetFstPairs(vector<FstPair> * fst_pairs)577 void ReplaceUtil<Arc>::GetFstPairs(vector<FstPair> *fst_pairs) {
578 CheckMutableFsts();
579 fst_pairs->clear();
580 for (Label i = 0; i < fst_array_.size(); ++i) {
581 Label label = nonterminal_array_[i];
582 const Fst<Arc> *fst = fst_array_[i];
583 if (!fst)
584 continue;
585 fst_pairs->push_back(make_pair(label, fst));
586 }
587 }
588
589 template <class Arc>
GetMutableFstPairs(vector<MutableFstPair> * mutable_fst_pairs)590 void ReplaceUtil<Arc>::GetMutableFstPairs(
591 vector<MutableFstPair> *mutable_fst_pairs) {
592 CheckMutableFsts();
593 mutable_fst_pairs->clear();
594 for (Label i = 0; i < mutable_fst_array_.size(); ++i) {
595 Label label = nonterminal_array_[i];
596 MutableFst<Arc> *fst = mutable_fst_array_[i];
597 if (!fst)
598 continue;
599 mutable_fst_pairs->push_back(make_pair(label, fst->Copy()));
600 }
601 }
602
603 } // namespace fst
604
605 #endif // FST_LIB_REPLACE_UTIL_H__
606