|
|
// Copyright 2005-2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the 'License');
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an 'AS IS' BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// See www.openfst.org for extensive documentation on this weighted
// finite-state transducer library.
//
// Composition filters to support lookahead matchers, useful for improving
// composition efficiency with certain inputs.
#ifndef FST_LOOKAHEAD_FILTER_H_
#define FST_LOOKAHEAD_FILTER_H_
#include <sys/types.h>
#include <cstdint>
#include <memory>
#include <vector>
#include <fst/log.h>
#include <fst/arc.h>
#include <fst/filter-state.h>
#include <fst/fst-decl.h>
#include <fst/fst.h>
#include <fst/lookahead-matcher.h>
#include <fst/matcher.h>
#include <fst/properties.h>
#include <fst/util.h>
namespace fst {
// Identifies and verifies the capabilities of the matcher to be used for
// lookahead with the composition filters below. This version is passed two
// matchers.
template <class Matcher1, class Matcher2> MatchType LookAheadMatchType(const Matcher1 &m1, const Matcher2 &m2) { const auto type1 = m1.Type(false); const auto type2 = m2.Type(false); if (type1 == MATCH_OUTPUT && m1.Flags() & kOutputLookAheadMatcher) { return MATCH_OUTPUT; } else if (type2 == MATCH_INPUT && m2.Flags() & kInputLookAheadMatcher) { return MATCH_INPUT; } else if (m1.Flags() & kOutputLookAheadMatcher && m1.Type(true) == MATCH_OUTPUT) { return MATCH_OUTPUT; } else if (m2.Flags() & kInputLookAheadMatcher && m2.Type(true) == MATCH_INPUT) { return MATCH_INPUT; } else { return MATCH_NONE; } }
// Identifies and verifies the capabilities of the matcher to be used for
// lookahead with the composition filters below. This version uses the FST's
// default matchers.
template <class Arc> MatchType LookAheadMatchType(const Fst<Arc> &fst1, const Fst<Arc> &fst2) { LookAheadMatcher<Fst<Arc>> matcher1(fst1, MATCH_OUTPUT); LookAheadMatcher<Fst<Arc>> matcher2(fst2, MATCH_INPUT); return LookAheadMatchType(matcher1, matcher2); }
// LookAheadSelector is a helper class for selecting among possibly distinct
// FST and matcher types without using a common base class. This lets us avoid
// virtual function calls. It stores and returns the appropriate FSTs and
// matcher for lookahead. It is templated on the matcher types. General case
// has no methods.
template <class Matcher1, class Matcher2, MatchType MT> class LookAheadSelector {};
// Stores and returns the appropriate FST and matcher for lookahead. Specialized
// for two matchers of same type with the (match) type argument determining
// which is used for lookahead.
template <class Matcher, MatchType MT> class LookAheadSelector<Matcher, Matcher, MT> { public: using FST = typename Matcher::FST;
LookAheadSelector(Matcher *lmatcher1, Matcher *lmatcher2, MatchType type) : lmatcher1_(lmatcher1->Copy()), lmatcher2_(lmatcher2->Copy()), type_(type) {}
LookAheadSelector(const LookAheadSelector<Matcher, Matcher, MT> &selector) : lmatcher1_(selector.lmatcher1_->Copy()), lmatcher2_(selector.lmatcher2_->Copy()), type_(selector.type_) {}
const FST &GetFst() const { return type_ == MATCH_OUTPUT ? lmatcher2_->GetFst() : lmatcher1_->GetFst(); }
Matcher *GetMatcher() const { return type_ == MATCH_OUTPUT ? lmatcher1_.get() : lmatcher2_.get(); }
private: std::unique_ptr<Matcher> lmatcher1_; std::unique_ptr<Matcher> lmatcher2_; MatchType type_; };
// Stores and returns the appropriate FST and matcher for lookahead.
// Specialized for lookahead on input labels.
template <class Matcher1, class Matcher2> class LookAheadSelector<Matcher1, Matcher2, MATCH_INPUT> { public: using FST1 = typename Matcher1::FST;
LookAheadSelector(Matcher1 *lmatcher1, Matcher2 *lmatcher2, MatchType) : fst_(lmatcher1->GetFst().Copy()), lmatcher_(lmatcher2->Copy()) {}
LookAheadSelector( const LookAheadSelector<Matcher1, Matcher2, MATCH_INPUT> &selector) : fst_(selector.fst_->Copy()), lmatcher_(selector.lmatcher_->Copy()) {}
const FST1 &GetFst() const { return *fst_; }
Matcher2 *GetMatcher() const { return lmatcher_.get(); }
private: std::unique_ptr<const FST1> fst_; std::unique_ptr<Matcher2> lmatcher_; };
// Stores and returns the appropriate FST and matcher for lookahead.
// Specialized for lookahead on output labels.
template <class Matcher1, class Matcher2> class LookAheadSelector<Matcher1, Matcher2, MATCH_OUTPUT> { public: using FST2 = typename Matcher2::FST;
LookAheadSelector(Matcher1 *lmatcher1, Matcher2 *lmatcher2, MatchType) : fst_(lmatcher2->GetFst().Copy()), lmatcher_(lmatcher1->Copy()) {}
LookAheadSelector( const LookAheadSelector<Matcher1, Matcher2, MATCH_OUTPUT> &selector) : fst_(selector.fst_->Copy()), lmatcher_(selector.lmatcher_->Copy()) {}
const FST2 &GetFst() const { return *fst_; }
Matcher1 *GetMatcher() const { return lmatcher_.get(); }
private: std::unique_ptr<const FST2> fst_; std::unique_ptr<Matcher1> lmatcher_; };
// This filter uses a lookahead matcher in FilterArc(arc1, arc2) to examine the
// future of the composition state (arc1.nextstate, arc2.nextstate), blocking
// moving forward when its determined to be
// non-coaccessible. It is templated on an underlying filter, typically the
// epsilon filter. Which matcher is the lookahead matcher is determined by the
// template argument MT unless it is MATCH_BOTH. In that case, both matcher
// arguments must be lookahead matchers of the same type and one will be
// selected by LookAheadMatchType() based on their capability.
template <class Filter, class M1 = LookAheadMatcher<typename Filter::FST1>, class M2 = M1, MatchType MT = MATCH_BOTH> class LookAheadComposeFilter { public: using Arc = typename Filter::Arc; using StateId = typename Arc::StateId; using Weight = typename Arc::Weight;
using FST1 = typename Filter::FST1; using FST2 = typename Filter::FST2; using Matcher1 = typename Filter::Matcher1; using Matcher2 = typename Filter::Matcher2; using FilterState = typename Filter::FilterState;
LookAheadComposeFilter(const FST1 &fst1, const FST2 &fst2, M1 *matcher1, M2 *matcher2) : filter_(fst1, fst2, matcher1, matcher2), lookahead_type_(MT == MATCH_BOTH ? LookAheadMatchType(*filter_.GetMatcher1(), *filter_.GetMatcher2()) : MT), selector_(filter_.GetMatcher1(), filter_.GetMatcher2(), lookahead_type_), flags_(lookahead_type_ == MATCH_OUTPUT ? filter_.GetMatcher1()->Flags() : filter_.GetMatcher2()->Flags()) { if (lookahead_type_ == MATCH_NONE) { FSTERROR() << "LookAheadComposeFilter: 1st argument cannot " << "match/look-ahead on output labels and 2nd argument " << "cannot match/look-ahead on input labels"; } selector_.GetMatcher()->InitLookAheadFst(selector_.GetFst()); }
LookAheadComposeFilter( const LookAheadComposeFilter<Filter, M1, M2, MT> &filter, bool safe = false) : filter_(filter.filter_, safe), lookahead_type_(filter.lookahead_type_), selector_(filter_.GetMatcher1(), filter_.GetMatcher2(), lookahead_type_), flags_(filter.flags_) { selector_.GetMatcher()->InitLookAheadFst(selector_.GetFst(), true); }
FilterState Start() const { return filter_.Start(); }
void SetState(StateId s1, StateId s2, const FilterState &fs) { filter_.SetState(s1, s2, fs); }
FilterState FilterArc(Arc *arc1, Arc *arc2) const { lookahead_arc_ = false; const FilterState &fs = filter_.FilterArc(arc1, arc2); if (fs == FilterState::NoState()) return FilterState::NoState(); return LookAheadOutput() ? LookAheadFilterArc(arc1, arc2, fs) : LookAheadFilterArc(arc2, arc1, fs); }
void FilterFinal(Weight *weight1, Weight *weight2) const { filter_.FilterFinal(weight1, weight2); }
// Returns matchers; ownership stays with filter.
Matcher1 *GetMatcher1() { return filter_.GetMatcher1(); }
Matcher2 *GetMatcher2() { return filter_.GetMatcher2(); }
const LookAheadSelector<Matcher1, Matcher2, MT> &Selector() const { return selector_; }
uint64_t Properties(uint64_t inprops) const { auto outprops = filter_.Properties(inprops); if (lookahead_type_ == MATCH_NONE) outprops |= kError; return outprops; }
uint32_t LookAheadFlags() const { return flags_; }
bool LookAheadArc() const { return lookahead_arc_; }
bool LookAheadOutput() const { if (MT == MATCH_OUTPUT) { return true; } else if (MT == MATCH_INPUT) { return false; } else if (lookahead_type_ == MATCH_OUTPUT) { return true; } else { return false; } }
private: FilterState LookAheadFilterArc(Arc *arca, Arc *arcb, const FilterState &fs) const { auto &labela = LookAheadOutput() ? arca->olabel : arca->ilabel; if (labela != 0 && !(flags_ & kLookAheadNonEpsilons)) return fs; if (labela == 0 && !(flags_ & kLookAheadEpsilons)) return fs; lookahead_arc_ = true; selector_.GetMatcher()->SetState(arca->nextstate); return selector_.GetMatcher()->LookAheadFst(selector_.GetFst(), arcb->nextstate) ? fs : FilterState::NoState(); }
Filter filter_; // Underlying filter.
MatchType lookahead_type_; // Lookahead match type.
LookAheadSelector<Matcher1, Matcher2, MT> selector_; uint32_t flags_; // Lookahead flags.
mutable bool lookahead_arc_; // Look-ahead performed at last FilterArc()?
LookAheadComposeFilter &operator=(const LookAheadComposeFilter &) = delete; };
// This filter adds weight-pushing to a lookahead composition filter using the
// LookAheadWeight() method of matcher argument. It is templated on an
// underlying lookahead filter, typically the basic lookahead filter.
// Weight-pushing in composition brings weights forward as much as possible
// based on the lookahead information.
template <class Filter, class M1 = LookAheadMatcher<typename Filter::FST1>, class M2 = M1, MatchType MT = MATCH_BOTH> class PushWeightsComposeFilter { public: using Arc = typename Filter::Arc; using StateId = typename Filter::StateId; using Weight = typename Filter::Weight;
using FST1 = typename Filter::FST1; using FST2 = typename Filter::FST2; using Matcher1 = typename Filter::Matcher1; using Matcher2 = typename Filter::Matcher2;
using FilterState1 = typename Filter::FilterState; using FilterState2 = WeightFilterState<Weight>; using FilterState = PairFilterState<FilterState1, FilterState2>;
PushWeightsComposeFilter(const FST1 &fst1, const FST2 &fst2, M1 *matcher1, M2 *matcher2) : filter_(fst1, fst2, matcher1, matcher2), fs_(FilterState::NoState()) {}
PushWeightsComposeFilter( const PushWeightsComposeFilter<Filter, M1, M2, MT> &filter, bool safe = false) : filter_(filter.filter_, safe), fs_(FilterState::NoState()) {}
FilterState Start() const { return FilterState(filter_.Start(), FilterState2(Weight::One())); }
void SetState(StateId s1, StateId s2, const FilterState &fs) { fs_ = fs; filter_.SetState(s1, s2, fs.GetState1()); }
FilterState FilterArc(Arc *arc1, Arc *arc2) const { const auto &fs1 = filter_.FilterArc(arc1, arc2); if (fs1 == FilterState1::NoState()) return FilterState::NoState(); if (!(LookAheadFlags() & kLookAheadWeight)) { return FilterState(fs1, FilterState2(Weight::One())); } const auto &lweight = LookAheadArc() ? Selector().GetMatcher()->LookAheadWeight() : Weight::One(); const auto &fs2 = fs_.GetState2(); const auto &fweight = fs2.GetWeight(); // Disallows Zero() weight futures.
if (lweight == Weight::Zero()) return FilterState::NoState(); arc2->weight = Divide(Times(arc2->weight, lweight), fweight); return FilterState(fs1, FilterState2(lweight.Quantize())); }
void FilterFinal(Weight *weight1, Weight *weight2) const { filter_.FilterFinal(weight1, weight2); if (!(LookAheadFlags() & kLookAheadWeight) || *weight1 == Weight::Zero()) { return; } const auto &fs2 = fs_.GetState2(); const auto &fweight = fs2.GetWeight(); *weight1 = Divide(*weight1, fweight); }
// Returns matchers; ownership states with filter.
Matcher1 *GetMatcher1() { return filter_.GetMatcher1(); }
Matcher2 *GetMatcher2() { return filter_.GetMatcher2(); }
const LookAheadSelector<Matcher1, Matcher2, MT> &Selector() const { return filter_.Selector(); }
uint32_t LookAheadFlags() const { return filter_.LookAheadFlags(); }
bool LookAheadArc() const { return filter_.LookAheadArc(); }
bool LookAheadOutput() const { return filter_.LookAheadOutput(); }
uint64_t Properties(uint64_t props) const { return filter_.Properties(props) & kWeightInvariantProperties; }
private: Filter filter_; // Underlying filter.
FilterState fs_; // Current filter state.
PushWeightsComposeFilter &operator=(const PushWeightsComposeFilter &) = delete; };
// This filter adds label-pushing to a lookahead composition filter using the
// LookAheadPrefix() method of the matcher argument. It is templated on an
// underlying filter, typically the basic lookahead or weight-pushing lookahead
// filter. Label-pushing in composition matches labels as early as possible
// based on the lookahead information.
template <class Filter, class M1 = LookAheadMatcher<typename Filter::FST1>, class M2 = M1, MatchType MT = MATCH_BOTH> class PushLabelsComposeFilter { public: using Arc = typename Filter::Arc; using Label = typename Arc::Label; using StateId = typename Arc::StateId; using Weight = typename Arc::Weight;
using FST1 = typename Filter::FST1; using FST2 = typename Filter::FST2; using Matcher1 = MultiEpsMatcher<typename Filter::Matcher1>; using Matcher2 = MultiEpsMatcher<typename Filter::Matcher2>; using FilterState1 = typename Filter::FilterState; using FilterState2 = IntegerFilterState<Label>; using FilterState = PairFilterState<FilterState1, FilterState2>;
PushLabelsComposeFilter(const FST1 &fst1, const FST2 &fst2, M1 *matcher1, M2 *matcher2) : filter_(fst1, fst2, matcher1, matcher2), fs_(FilterState::NoState()), fst1_(filter_.GetMatcher1()->GetFst()), fst2_(filter_.GetMatcher2()->GetFst()), matcher1_(fst1_, MATCH_OUTPUT, filter_.LookAheadOutput() ? kMultiEpsList : kMultiEpsLoop, filter_.GetMatcher1(), /*own_matcher=*/false), matcher2_(fst2_, MATCH_INPUT, filter_.LookAheadOutput() ? kMultiEpsLoop : kMultiEpsList, filter_.GetMatcher2(), /*own_matcher=*/false) {}
PushLabelsComposeFilter( const PushLabelsComposeFilter<Filter, M1, M2, MT> &filter, bool safe = false) : filter_(filter.filter_, safe), fs_(FilterState::NoState()), fst1_(filter_.GetMatcher1()->GetFst()), fst2_(filter_.GetMatcher2()->GetFst()), matcher1_(fst1_, MATCH_OUTPUT, filter_.LookAheadOutput() ? kMultiEpsList : kMultiEpsLoop, filter_.GetMatcher1(), /*own_matcher=*/false), matcher2_(fst2_, MATCH_INPUT, filter_.LookAheadOutput() ? kMultiEpsLoop : kMultiEpsList, filter_.GetMatcher2(), /*own_matcher=*/false) {}
FilterState Start() const { return FilterState(filter_.Start(), FilterState2(kNoLabel)); }
void SetState(StateId s1, StateId s2, const FilterState &fs) { fs_ = fs; filter_.SetState(s1, s2, fs.GetState1()); if (!(LookAheadFlags() & kLookAheadPrefix)) return; narcsa_ = LookAheadOutput() ? internal::NumArcs(fst1_, s1) : internal::NumArcs(fst2_, s2); const auto &fs2 = fs_.GetState2(); const auto &flabel = fs2.GetState(); GetMatcher1()->ClearMultiEpsLabels(); GetMatcher2()->ClearMultiEpsLabels(); if (flabel != kNoLabel) { // Have a lookahead label?
GetMatcher1()->AddMultiEpsLabel(flabel); // Yes, make it a multi-epsilon
GetMatcher2()->AddMultiEpsLabel(flabel); // label so that it matches the
} // implicit epsilon arc to be
} // modified below when pushing.
FilterState FilterArc(Arc *arc1, Arc *arc2) const { if (!(LookAheadFlags() & kLookAheadPrefix)) { return FilterState(filter_.FilterArc(arc1, arc2), FilterState2(kNoLabel)); } const auto &fs2 = fs_.GetState2(); const auto &flabel = fs2.GetState(); if (flabel != kNoLabel) { // Have a lookahead label?
return LookAheadOutput() ? PushedLabelFilterArc(arc1, arc2, flabel) : PushedLabelFilterArc(arc2, arc1, flabel); } const auto &fs1 = filter_.FilterArc(arc1, arc2); if (fs1 == FilterState1::NoState()) return FilterState::NoState(); if (!LookAheadArc()) return FilterState(fs1, FilterState2(kNoLabel)); return LookAheadOutput() ? PushLabelFilterArc(arc1, arc2, fs1) : PushLabelFilterArc(arc2, arc1, fs1); }
void FilterFinal(Weight *weight1, Weight *weight2) const { filter_.FilterFinal(weight1, weight2); if (!(LookAheadFlags() & kLookAheadPrefix) || *weight1 == Weight::Zero()) { return; } const auto &fs2 = fs_.GetState2(); const auto &flabel = fs2.GetState(); if (flabel != kNoLabel) *weight1 = Weight::Zero(); }
// Returns matchers; ownership states with filter.
Matcher1 *GetMatcher1() { return &matcher1_; }
Matcher2 *GetMatcher2() { return &matcher2_; }
uint64_t Properties(uint64_t iprops) const { const auto oprops = filter_.Properties(iprops); if (LookAheadOutput()) { return oprops & kOLabelInvariantProperties; } else { return oprops & kILabelInvariantProperties; } }
private: const LookAheadSelector<typename Filter::Matcher1, typename Filter::Matcher2, MT> &Selector() const { return filter_.Selector(); }
// Consumes an already pushed label.
FilterState PushedLabelFilterArc(Arc *arca, Arc *arcb, Label flabel) const { auto &labela = LookAheadOutput() ? arca->olabel : arca->ilabel; const auto &labelb = LookAheadOutput() ? arcb->ilabel : arcb->olabel; if (labelb != kNoLabel) { return FilterState::NoState(); // Blocks non-(multi-)epsilon label
} else if (labela == flabel) { labela = 0; // Converts match to multi-epsilon to epsilon.
return Start(); } else if (labela == 0) { if (narcsa_ == 1) return fs_; // Takes epsilon, keeping state with label.
Selector().GetMatcher()->SetState(arca->nextstate); if (Selector().GetMatcher()->LookAheadLabel(flabel)) { return fs_; // Takes epsilon, keeping state with label.
} else { return FilterState::NoState(); // Blocks non-coaccessible path.
} } else { return FilterState::NoState(); // Blocks mismatch to multi-epsilon label.
} }
// Pushes a label forward when possible.
FilterState PushLabelFilterArc(Arc *arca, Arc *arcb, const FilterState1 &fs1) const { auto &labela = LookAheadOutput() ? arca->olabel : arca->ilabel; const auto &labelb = LookAheadOutput() ? arcb->olabel : arcb->ilabel; if (labelb != 0) { // No place to push.
return FilterState(fs1, FilterState2(kNoLabel)); } if (labela != 0 && // Wrong lookahead prefix type?
LookAheadFlags() & kLookAheadNonEpsilonPrefix) { return FilterState(fs1, FilterState2(kNoLabel)); } Arc larc(kNoLabel, kNoLabel, Weight::Zero(), kNoStateId); if (Selector().GetMatcher()->LookAheadPrefix(&larc)) { // Have prefix arc?
labela = LookAheadOutput() ? larc.ilabel : larc.olabel; arcb->ilabel = larc.ilabel; // Goes forward on that arc,
arcb->olabel = larc.olabel; // thus pushing the label.
arcb->weight = Times(arcb->weight, larc.weight); arcb->nextstate = larc.nextstate; return FilterState(fs1, FilterState2(labela)); } else { return FilterState(fs1, FilterState2(kNoLabel)); } }
uint32_t LookAheadFlags() const { return filter_.LookAheadFlags(); }
bool LookAheadArc() const { return filter_.LookAheadArc(); }
bool LookAheadOutput() const { return filter_.LookAheadOutput(); }
Filter filter_; // Underlying filter.
FilterState fs_; // Current filter state.
const FST1 &fst1_; const FST2 &fst2_; Matcher1 matcher1_; // Multi-epsilon matcher for fst1_.
Matcher2 matcher2_; // Multi-epsilon matcher for fst2_.
ssize_t narcsa_; // Number of arcs leaving look-ahead match FST.
PushLabelsComposeFilter &operator=(const PushLabelsComposeFilter &) = delete; };
// Convenience class for setting up composition with a default lookahead matcher
// and filter.
template <class Arc, MatchType type> class DefaultLookAhead { public: using M = Matcher<Fst<Arc>>; using ComposeFilter = SequenceComposeFilter<M>; using FstMatcher = M; };
// Specializes for MATCH_INPUT to allow lookahead.
template <class Arc> class DefaultLookAhead<Arc, MATCH_INPUT> { public: using M = LookAheadMatcher<Fst<Arc>>; using SF = SequenceComposeFilter<M>; using ComposeFilter = LookAheadComposeFilter<SF, M>; using FstMatcher = M; };
// Specializes for MATCH_OUTPUT to allow lookahead.
template <class Arc> class DefaultLookAhead<Arc, MATCH_OUTPUT> { public: using M = LookAheadMatcher<Fst<Arc>>; using SF = AltSequenceComposeFilter<M>; using ComposeFilter = LookAheadComposeFilter<SF, M>; using FstMatcher = M; };
// Specializes for StdArc to allow weight and label pushing.
template <> class DefaultLookAhead<StdArc, MATCH_INPUT> { public: using M = LookAheadMatcher<Fst<StdArc>>; using SF = SequenceComposeFilter<M>; using LF = LookAheadComposeFilter<SF, M>; using WF = PushWeightsComposeFilter<LF, M>; using ComposeFilter = PushLabelsComposeFilter<WF, M>; using FstMatcher = M; };
// Specializes for StdArc to allow weight and label pushing.
template <> class DefaultLookAhead<StdArc, MATCH_OUTPUT> { public: using M = LookAheadMatcher<Fst<StdArc>>; using SF = AltSequenceComposeFilter<M>; using LF = LookAheadComposeFilter<SF, M>; using WF = PushWeightsComposeFilter<LF, M>; using ComposeFilter = PushLabelsComposeFilter<WF, M>; using FstMatcher = M; };
// Specializes for LogArc to allow weight and label pushing.
template <> class DefaultLookAhead<LogArc, MATCH_INPUT> { public: using M = LookAheadMatcher<Fst<LogArc>>; using SF = SequenceComposeFilter<M>; using LF = LookAheadComposeFilter<SF, M>; using WF = PushWeightsComposeFilter<LF, M>; using ComposeFilter = PushLabelsComposeFilter<WF, M>; using FstMatcher = M; };
// Specializes for LogArc to allow weight and label pushing.
template <> class DefaultLookAhead<LogArc, MATCH_OUTPUT> { public: using M = LookAheadMatcher<Fst<LogArc>>; using SF = AltSequenceComposeFilter<M>; using LF = LookAheadComposeFilter<SF, M>; using WF = PushWeightsComposeFilter<LF, M>; using ComposeFilter = PushLabelsComposeFilter<WF, M>; using FstMatcher = M; };
} // namespace fst
#endif // FST_LOOKAHEAD_FILTER_H_
|