Initial commit: FunASR Speech Recognition Toolkit
Update API Documentation / build-api-docs (push) Has been cancelled

Add complete FunASR codebase including models, runtime, and documentation.
This commit is contained in:
freedakgmail
2026-07-09 22:38:58 +08:00
commit 6116b1f3c6
3683 changed files with 990984 additions and 0 deletions
+20
View File
@@ -0,0 +1,20 @@
all:
include ../kaldi.mk
TESTFILES = const-integer-set-test stl-utils-test text-utils-test \
edit-distance-test hash-list-test kaldi-io-test parse-options-test \
kaldi-table-test simple-options-test kaldi-thread-test
OBJFILES = text-utils.o kaldi-io.o kaldi-holder.o kaldi-table.o \
parse-options.o simple-options.o simple-io-funcs.o \
kaldi-semaphore.o kaldi-thread.o
LIBNAME = kaldi-util
ADDLIBS = ../matrix/kaldi-matrix.a ../base/kaldi-base.a
include ../makefiles/default_rules.mk
@@ -0,0 +1,994 @@
///////////////////////////////////////////////////////////////////////////////
// This is a modified version of the std::basic_filebuf from libc++
// (http://libcxx.llvm.org/).
// It allows one to create basic_filebuf from an existing FILE* handle or file
// descriptor.
//
// This file is dual licensed under the MIT and the University of Illinois Open
// Source License licenses. See LICENSE.TXT for details (included at the
// bottom).
///////////////////////////////////////////////////////////////////////////////
#ifndef KALDI_UTIL_BASIC_FILEBUF_H_
#define KALDI_UTIL_BASIC_FILEBUF_H_
///////////////////////////////////////////////////////////////////////////////
#include <fstream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
///////////////////////////////////////////////////////////////////////////////
namespace kaldi {
///////////////////////////////////////////////////////////////////////////////
template <typename CharT, typename Traits = std::char_traits<CharT> >
class basic_filebuf : public std::basic_streambuf<CharT, Traits> {
public:
typedef CharT char_type;
typedef Traits traits_type;
typedef typename traits_type::int_type int_type;
typedef typename traits_type::pos_type pos_type;
typedef typename traits_type::off_type off_type;
typedef typename traits_type::state_type state_type;
basic_filebuf();
basic_filebuf(basic_filebuf&& rhs);
virtual ~basic_filebuf();
basic_filebuf& operator=(basic_filebuf&& rhs);
void swap(basic_filebuf& rhs);
bool is_open() const;
basic_filebuf* open(const char* s, std::ios_base::openmode mode);
basic_filebuf* open(const std::string& s, std::ios_base::openmode mode);
basic_filebuf* open(int fd, std::ios_base::openmode mode);
basic_filebuf* open(FILE* f, std::ios_base::openmode mode);
basic_filebuf* close();
FILE* file() { return this->_M_file; }
int fd() { return fileno(this->_M_file); }
protected:
int_type underflow() override;
int_type pbackfail(int_type c = traits_type::eof()) override;
int_type overflow(int_type c = traits_type::eof()) override;
std::basic_streambuf<char_type, traits_type>*
setbuf(char_type* s, std::streamsize n) override;
pos_type seekoff(off_type off, std::ios_base::seekdir way,
std::ios_base::openmode wch =
std::ios_base::in | std::ios_base::out) override;
pos_type seekpos(pos_type sp,
std::ios_base::openmode wch =
std::ios_base::in | std::ios_base::out) override;
int sync() override;
void imbue(const std::locale& loc) override;
protected:
char* _M_extbuf;
const char* _M_extbufnext;
const char* _M_extbufend;
char _M_extbuf_min[8];
size_t _M_ebs;
char_type* _M_intbuf;
size_t _M_ibs;
FILE* _M_file;
const std::codecvt<char_type, char, state_type>* _M_cv;
state_type _M_st;
state_type _M_st_last;
std::ios_base::openmode _M_om;
std::ios_base::openmode _M_cm;
bool _M_owns_eb;
bool _M_owns_ib;
bool _M_always_noconv;
const char* _M_get_mode(std::ios_base::openmode mode);
bool _M_read_mode();
void _M_write_mode();
};
///////////////////////////////////////////////////////////////////////////////
template <class CharT, class Traits>
basic_filebuf<CharT, Traits>::basic_filebuf()
: _M_extbuf(nullptr),
_M_extbufnext(nullptr),
_M_extbufend(nullptr),
_M_ebs(0),
_M_intbuf(nullptr),
_M_ibs(0),
_M_file(nullptr),
_M_cv(nullptr),
_M_st(),
_M_st_last(),
_M_om(std::ios_base::openmode(0)),
_M_cm(std::ios_base::openmode(0)),
_M_owns_eb(false),
_M_owns_ib(false),
_M_always_noconv(false) {
if (std::has_facet<std::codecvt<char_type, char, state_type> >
(this->getloc())) {
_M_cv = &std::use_facet<std::codecvt<char_type, char, state_type> >
(this->getloc());
_M_always_noconv = _M_cv->always_noconv();
}
setbuf(0, 4096);
}
///////////////////////////////////////////////////////////////////////////////
template <class CharT, class Traits>
basic_filebuf<CharT, Traits>::basic_filebuf(basic_filebuf&& rhs)
: std::basic_streambuf<CharT, Traits>(rhs) {
if (rhs._M_extbuf == rhs._M_extbuf_min) {
_M_extbuf = _M_extbuf_min;
_M_extbufnext = _M_extbuf + (rhs._M_extbufnext - rhs._M_extbuf);
_M_extbufend = _M_extbuf + (rhs._M_extbufend - rhs._M_extbuf);
} else {
_M_extbuf = rhs._M_extbuf;
_M_extbufnext = rhs._M_extbufnext;
_M_extbufend = rhs._M_extbufend;
}
_M_ebs = rhs._M_ebs;
_M_intbuf = rhs._M_intbuf;
_M_ibs = rhs._M_ibs;
_M_file = rhs._M_file;
_M_cv = rhs._M_cv;
_M_st = rhs._M_st;
_M_st_last = rhs._M_st_last;
_M_om = rhs._M_om;
_M_cm = rhs._M_cm;
_M_owns_eb = rhs._M_owns_eb;
_M_owns_ib = rhs._M_owns_ib;
_M_always_noconv = rhs._M_always_noconv;
if (rhs.pbase()) {
if (rhs.pbase() == rhs._M_intbuf)
this->setp(_M_intbuf, _M_intbuf + (rhs. epptr() - rhs.pbase()));
else
this->setp(reinterpret_cast<char_type*>(_M_extbuf),
reinterpret_cast<char_type*>(_M_extbuf)
+ (rhs. epptr() - rhs.pbase()));
this->pbump(rhs. pptr() - rhs.pbase());
} else if (rhs.eback()) {
if (rhs.eback() == rhs._M_intbuf)
this->setg(_M_intbuf, _M_intbuf + (rhs.gptr() - rhs.eback()),
_M_intbuf + (rhs.egptr() - rhs.eback()));
else
this->setg(reinterpret_cast<char_type*>(_M_extbuf),
reinterpret_cast<char_type*>(_M_extbuf) +
(rhs.gptr() - rhs.eback()),
reinterpret_cast<char_type*>(_M_extbuf) +
(rhs.egptr() - rhs.eback()));
}
rhs._M_extbuf = nullptr;
rhs._M_extbufnext = nullptr;
rhs._M_extbufend = nullptr;
rhs._M_ebs = 0;
rhs._M_intbuf = nullptr;
rhs._M_ibs = 0;
rhs._M_file = nullptr;
rhs._M_st = state_type();
rhs._M_st_last = state_type();
rhs._M_om = std::ios_base::openmode(0);
rhs._M_cm = std::ios_base::openmode(0);
rhs._M_owns_eb = false;
rhs._M_owns_ib = false;
rhs.setg(0, 0, 0);
rhs.setp(0, 0);
}
///////////////////////////////////////////////////////////////////////////////
template <class CharT, class Traits>
inline
basic_filebuf<CharT, Traits>&
basic_filebuf<CharT, Traits>::operator=(basic_filebuf&& rhs) {
close();
swap(rhs);
return *this;
}
///////////////////////////////////////////////////////////////////////////////
template <class CharT, class Traits>
basic_filebuf<CharT, Traits>::~basic_filebuf() {
// try
// {
// close();
// }
// catch (...)
// {
// }
if (_M_owns_eb)
delete [] _M_extbuf;
if (_M_owns_ib)
delete [] _M_intbuf;
}
///////////////////////////////////////////////////////////////////////////////
template <class CharT, class Traits>
void
basic_filebuf<CharT, Traits>::swap(basic_filebuf& rhs) {
std::basic_streambuf<char_type, traits_type>::swap(rhs);
if (_M_extbuf != _M_extbuf_min && rhs._M_extbuf != rhs._M_extbuf_min) {
std::swap(_M_extbuf, rhs._M_extbuf);
std::swap(_M_extbufnext, rhs._M_extbufnext);
std::swap(_M_extbufend, rhs._M_extbufend);
} else {
ptrdiff_t ln = _M_extbufnext - _M_extbuf;
ptrdiff_t le = _M_extbufend - _M_extbuf;
ptrdiff_t rn = rhs._M_extbufnext - rhs._M_extbuf;
ptrdiff_t re = rhs._M_extbufend - rhs._M_extbuf;
if (_M_extbuf == _M_extbuf_min && rhs._M_extbuf != rhs._M_extbuf_min) {
_M_extbuf = rhs._M_extbuf;
rhs._M_extbuf = rhs._M_extbuf_min;
} else if (_M_extbuf != _M_extbuf_min &&
rhs._M_extbuf == rhs._M_extbuf_min) {
rhs._M_extbuf = _M_extbuf;
_M_extbuf = _M_extbuf_min;
}
_M_extbufnext = _M_extbuf + rn;
_M_extbufend = _M_extbuf + re;
rhs._M_extbufnext = rhs._M_extbuf + ln;
rhs._M_extbufend = rhs._M_extbuf + le;
}
std::swap(_M_ebs, rhs._M_ebs);
std::swap(_M_intbuf, rhs._M_intbuf);
std::swap(_M_ibs, rhs._M_ibs);
std::swap(_M_file, rhs._M_file);
std::swap(_M_cv, rhs._M_cv);
std::swap(_M_st, rhs._M_st);
std::swap(_M_st_last, rhs._M_st_last);
std::swap(_M_om, rhs._M_om);
std::swap(_M_cm, rhs._M_cm);
std::swap(_M_owns_eb, rhs._M_owns_eb);
std::swap(_M_owns_ib, rhs._M_owns_ib);
std::swap(_M_always_noconv, rhs._M_always_noconv);
if (this->eback() == reinterpret_cast<char_type*>(rhs._M_extbuf_min)) {
ptrdiff_t n = this->gptr() - this->eback();
ptrdiff_t e = this->egptr() - this->eback();
this->setg(reinterpret_cast<char_type*>(_M_extbuf_min),
reinterpret_cast<char_type*>(_M_extbuf_min) + n,
reinterpret_cast<char_type*>(_M_extbuf_min) + e);
} else if (this->pbase() ==
reinterpret_cast<char_type*>(rhs._M_extbuf_min)) {
ptrdiff_t n = this->pptr() - this->pbase();
ptrdiff_t e = this->epptr() - this->pbase();
this->setp(reinterpret_cast<char_type*>(_M_extbuf_min),
reinterpret_cast<char_type*>(_M_extbuf_min) + e);
this->pbump(n);
}
if (rhs.eback() == reinterpret_cast<char_type*>(_M_extbuf_min)) {
ptrdiff_t n = rhs.gptr() - rhs.eback();
ptrdiff_t e = rhs.egptr() - rhs.eback();
rhs.setg(reinterpret_cast<char_type*>(rhs._M_extbuf_min),
reinterpret_cast<char_type*>(rhs._M_extbuf_min) + n,
reinterpret_cast<char_type*>(rhs._M_extbuf_min) + e);
} else if (rhs.pbase() == reinterpret_cast<char_type*>(_M_extbuf_min)) {
ptrdiff_t n = rhs.pptr() - rhs.pbase();
ptrdiff_t e = rhs.epptr() - rhs.pbase();
rhs.setp(reinterpret_cast<char_type*>(rhs._M_extbuf_min),
reinterpret_cast<char_type*>(rhs._M_extbuf_min) + e);
rhs.pbump(n);
}
}
///////////////////////////////////////////////////////////////////////////////
template <class CharT, class Traits>
inline
void
swap(basic_filebuf<CharT, Traits>& x, basic_filebuf<CharT, Traits>& y) {
x.swap(y);
}
///////////////////////////////////////////////////////////////////////////////
template <class CharT, class Traits>
inline
bool
basic_filebuf<CharT, Traits>::is_open() const {
return _M_file != nullptr;
}
///////////////////////////////////////////////////////////////////////////////
template <class CharT, class Traits>
const char* basic_filebuf<CharT, Traits>::
_M_get_mode(std::ios_base::openmode mode) {
switch ((mode & ~std::ios_base::ate) | 0) {
case std::ios_base::out:
case std::ios_base::out | std::ios_base::trunc:
return "w";
case std::ios_base::out | std::ios_base::app:
case std::ios_base::app:
return "a";
break;
case std::ios_base::in:
return "r";
case std::ios_base::in | std::ios_base::out:
return "r+";
case std::ios_base::in | std::ios_base::out | std::ios_base::trunc:
return "w+";
case std::ios_base::in | std::ios_base::out | std::ios_base::app:
case std::ios_base::in | std::ios_base::app:
return "a+";
case std::ios_base::out | std::ios_base::binary:
case std::ios_base::out | std::ios_base::trunc | std::ios_base::binary:
return "wb";
case std::ios_base::out | std::ios_base::app | std::ios_base::binary:
case std::ios_base::app | std::ios_base::binary:
return "ab";
case std::ios_base::in | std::ios_base::binary:
return "rb";
case std::ios_base::in | std::ios_base::out | std::ios_base::binary:
return "r+b";
case std::ios_base::in | std::ios_base::out | std::ios_base::trunc |
std::ios_base::binary:
return "w+b";
case std::ios_base::in | std::ios_base::out | std::ios_base::app |
std::ios_base::binary:
case std::ios_base::in | std::ios_base::app | std::ios_base::binary:
return "a+b";
default:
return nullptr;
}
}
///////////////////////////////////////////////////////////////////////////////
template <class CharT, class Traits>
basic_filebuf<CharT, Traits>*
basic_filebuf<CharT, Traits>::
open(const char* s, std::ios_base::openmode mode) {
basic_filebuf<CharT, Traits>* rt = nullptr;
if (_M_file == nullptr) {
const char* md= _M_get_mode(mode);
if (md) {
_M_file = fopen(s, md);
if (_M_file) {
rt = this;
_M_om = mode;
if (mode & std::ios_base::ate) {
if (fseek(_M_file, 0, SEEK_END)) {
fclose(_M_file);
_M_file = nullptr;
rt = nullptr;
}
}
}
}
}
return rt;
}
///////////////////////////////////////////////////////////////////////////////
template <class CharT, class Traits>
inline
basic_filebuf<CharT, Traits>*
basic_filebuf<CharT, Traits>::open(const std::string& s,
std::ios_base::openmode mode) {
return open(s.c_str(), mode);
}
///////////////////////////////////////////////////////////////////////////////
template <class CharT, class Traits>
basic_filebuf<CharT, Traits>*
basic_filebuf<CharT, Traits>::open(int fd, std::ios_base::openmode mode) {
const char* md= this->_M_get_mode(mode);
if (md) {
this->_M_file= fdopen(fd, md);
this->_M_om = mode;
return this;
} else {
return nullptr;
}
}
///////////////////////////////////////////////////////////////////////////////
template <class CharT, class Traits>
basic_filebuf<CharT, Traits>*
basic_filebuf<CharT, Traits>::open(FILE* f, std::ios_base::openmode mode) {
this->_M_file = f;
this->_M_om = mode;
return this;
}
///////////////////////////////////////////////////////////////////////////////
template <class CharT, class Traits>
basic_filebuf<CharT, Traits>*
basic_filebuf<CharT, Traits>::close() {
basic_filebuf<CharT, Traits>* rt = nullptr;
if (_M_file) {
rt = this;
std::unique_ptr<FILE, int(*)(FILE*)> h(_M_file, fclose);
if (sync())
rt = nullptr;
if (fclose(h.release()) == 0)
_M_file = nullptr;
else
rt = nullptr;
}
return rt;
}
///////////////////////////////////////////////////////////////////////////////
template <class CharT, class Traits>
typename basic_filebuf<CharT, Traits>::int_type
basic_filebuf<CharT, Traits>::underflow() {
if (_M_file == nullptr)
return traits_type::eof();
bool initial = _M_read_mode();
char_type buf;
if (this->gptr() == nullptr)
this->setg(&buf, &buf+1, &buf+1);
const size_t unget_sz = initial ? 0 : std::
min<size_t>((this->egptr() - this->eback()) / 2, 4);
int_type c = traits_type::eof();
if (this->gptr() == this->egptr()) {
memmove(this->eback(), this->egptr() - unget_sz,
unget_sz * sizeof(char_type));
if (_M_always_noconv) {
size_t nmemb = static_cast<size_t>
(this->egptr() - this->eback() - unget_sz);
nmemb = fread(this->eback() + unget_sz, 1, nmemb, _M_file);
if (nmemb != 0) {
this->setg(this->eback(),
this->eback() + unget_sz,
this->eback() + unget_sz + nmemb);
c = traits_type::to_int_type(*this->gptr());
}
} else {
memmove(_M_extbuf, _M_extbufnext, _M_extbufend - _M_extbufnext);
_M_extbufnext = _M_extbuf + (_M_extbufend - _M_extbufnext);
_M_extbufend = _M_extbuf +
(_M_extbuf == _M_extbuf_min ? sizeof(_M_extbuf_min) : _M_ebs);
size_t nmemb = std::min(static_cast<size_t>(_M_ibs - unget_sz),
static_cast<size_t>
(_M_extbufend - _M_extbufnext));
std::codecvt_base::result r;
_M_st_last = _M_st;
size_t nr = fread(
reinterpret_cast<void*>(const_cast<char_type*>(_M_extbufnext)),
1, nmemb, _M_file);
if (nr != 0) {
if (!_M_cv)
throw std::bad_cast();
_M_extbufend = _M_extbufnext + nr;
char_type* inext;
r = _M_cv->in(_M_st, _M_extbuf, _M_extbufend, _M_extbufnext,
this->eback() + unget_sz,
this->eback() + _M_ibs, inext);
if (r == std::codecvt_base::noconv) {
this->setg(reinterpret_cast<char_type*>(_M_extbuf),
reinterpret_cast<char_type*>(_M_extbuf),
const_cast<char_type*>(_M_extbufend));
c = traits_type::to_int_type(*this->gptr());
} else if (inext != this->eback() + unget_sz) {
this->setg(this->eback(), this->eback() + unget_sz, inext);
c = traits_type::to_int_type(*this->gptr());
}
}
}
} else {
c = traits_type::to_int_type(*this->gptr());
}
if (this->eback() == &buf)
this->setg(0, 0, 0);
return c;
}
///////////////////////////////////////////////////////////////////////////////
template <class CharT, class Traits>
typename basic_filebuf<CharT, Traits>::int_type
basic_filebuf<CharT, Traits>::pbackfail(int_type c) {
if (_M_file && this->eback() < this->gptr()) {
if (traits_type::eq_int_type(c, traits_type::eof())) {
this->gbump(-1);
return traits_type::not_eof(c);
}
if ((_M_om & std::ios_base::out) ||
traits_type::eq(traits_type::to_char_type(c), this->gptr()[-1])) {
this->gbump(-1);
*this->gptr() = traits_type::to_char_type(c);
return c;
}
}
return traits_type::eof();
}
///////////////////////////////////////////////////////////////////////////////
template <class CharT, class Traits>
typename basic_filebuf<CharT, Traits>::int_type
basic_filebuf<CharT, Traits>::overflow(int_type c) {
if (_M_file == nullptr)
return traits_type::eof();
_M_write_mode();
char_type buf;
char_type* pb_save = this->pbase();
char_type* epb_save = this->epptr();
if (!traits_type::eq_int_type(c, traits_type::eof())) {
if (this->pptr() == nullptr)
this->setp(&buf, &buf+1);
*this->pptr() = traits_type::to_char_type(c);
this->pbump(1);
}
if (this->pptr() != this->pbase()) {
if (_M_always_noconv) {
size_t nmemb = static_cast<size_t>(this->pptr() - this->pbase());
if (fwrite(this->pbase(), sizeof(char_type),
nmemb, _M_file) != nmemb)
return traits_type::eof();
} else {
char* extbe = _M_extbuf;
std::codecvt_base::result r;
do {
if (!_M_cv)
throw std::bad_cast();
const char_type* e;
r = _M_cv->out(_M_st, this->pbase(), this->pptr(), e,
_M_extbuf, _M_extbuf + _M_ebs, extbe);
if (e == this->pbase())
return traits_type::eof();
if (r == std::codecvt_base::noconv) {
size_t nmemb = static_cast<size_t>
(this->pptr() - this->pbase());
if (fwrite(this->pbase(), 1, nmemb, _M_file) != nmemb)
return traits_type::eof();
} else if (r == std::codecvt_base::ok ||
r == std::codecvt_base::partial) {
size_t nmemb = static_cast<size_t>(extbe - _M_extbuf);
if (fwrite(_M_extbuf, 1, nmemb, _M_file) != nmemb)
return traits_type::eof();
if (r == std::codecvt_base::partial) {
this->setp(const_cast<char_type*>(e),
this->pptr());
this->pbump(this->epptr() - this->pbase());
}
} else {
return traits_type::eof();
}
} while (r == std::codecvt_base::partial);
}
this->setp(pb_save, epb_save);
}
return traits_type::not_eof(c);
}
///////////////////////////////////////////////////////////////////////////////
template <class CharT, class Traits>
std::basic_streambuf<CharT, Traits>*
basic_filebuf<CharT, Traits>::setbuf(char_type* s, std::streamsize n) {
this->setg(0, 0, 0);
this->setp(0, 0);
if (_M_owns_eb)
delete [] _M_extbuf;
if (_M_owns_ib)
delete [] _M_intbuf;
_M_ebs = n;
if (_M_ebs > sizeof(_M_extbuf_min)) {
if (_M_always_noconv && s) {
_M_extbuf = reinterpret_cast<char*>(s);
_M_owns_eb = false;
} else {
_M_extbuf = new char[_M_ebs];
_M_owns_eb = true;
}
} else {
_M_extbuf = _M_extbuf_min;
_M_ebs = sizeof(_M_extbuf_min);
_M_owns_eb = false;
}
if (!_M_always_noconv) {
_M_ibs = std::max<std::streamsize>(n, sizeof(_M_extbuf_min));
if (s && _M_ibs >= sizeof(_M_extbuf_min)) {
_M_intbuf = s;
_M_owns_ib = false;
} else {
_M_intbuf = new char_type[_M_ibs];
_M_owns_ib = true;
}
} else {
_M_ibs = 0;
_M_intbuf = 0;
_M_owns_ib = false;
}
return this;
}
///////////////////////////////////////////////////////////////////////////////
template <class CharT, class Traits>
typename basic_filebuf<CharT, Traits>::pos_type
basic_filebuf<CharT, Traits>::seekoff(off_type off, std::ios_base::seekdir way,
std::ios_base::openmode) {
if (!_M_cv)
throw std::bad_cast();
int width = _M_cv->encoding();
if (_M_file == nullptr || (width <= 0 && off != 0) || sync())
return pos_type(off_type(-1));
// width > 0 || off == 0
int whence;
switch (way) {
case std::ios_base::beg:
whence = SEEK_SET;
break;
case std::ios_base::cur:
whence = SEEK_CUR;
break;
case std::ios_base::end:
whence = SEEK_END;
break;
default:
return pos_type(off_type(-1));
}
#if _WIN32
if (fseek(_M_file, width > 0 ? width * off : 0, whence))
return pos_type(off_type(-1));
pos_type r = ftell(_M_file);
#else
if (fseeko(_M_file, width > 0 ? width * off : 0, whence))
return pos_type(off_type(-1));
pos_type r = ftello(_M_file);
#endif
r.state(_M_st);
return r;
}
///////////////////////////////////////////////////////////////////////////////
template <class CharT, class Traits>
typename basic_filebuf<CharT, Traits>::pos_type
basic_filebuf<CharT, Traits>::seekpos(pos_type sp, std::ios_base::openmode) {
if (_M_file == nullptr || sync())
return pos_type(off_type(-1));
#if _WIN32
if (fseek(_M_file, sp, SEEK_SET))
return pos_type(off_type(-1));
#else
if (fseeko(_M_file, sp, SEEK_SET))
return pos_type(off_type(-1));
#endif
_M_st = sp.state();
return sp;
}
///////////////////////////////////////////////////////////////////////////////
template <class CharT, class Traits>
int
basic_filebuf<CharT, Traits>::sync() {
if (_M_file == nullptr)
return 0;
if (!_M_cv)
throw std::bad_cast();
if (_M_cm & std::ios_base::out) {
if (this->pptr() != this->pbase())
if (overflow() == traits_type::eof())
return -1;
std::codecvt_base::result r;
do {
char* extbe;
r = _M_cv->unshift(_M_st, _M_extbuf, _M_extbuf + _M_ebs, extbe);
size_t nmemb = static_cast<size_t>(extbe - _M_extbuf);
if (fwrite(_M_extbuf, 1, nmemb, _M_file) != nmemb)
return -1;
} while (r == std::codecvt_base::partial);
if (r == std::codecvt_base::error)
return -1;
if (fflush(_M_file))
return -1;
} else if (_M_cm & std::ios_base::in) {
off_type c;
state_type state = _M_st_last;
bool update_st = false;
if (_M_always_noconv) {
c = this->egptr() - this->gptr();
} else {
int width = _M_cv->encoding();
c = _M_extbufend - _M_extbufnext;
if (width > 0) {
c += width * (this->egptr() - this->gptr());
} else {
if (this->gptr() != this->egptr()) {
const int off = _M_cv->length(state, _M_extbuf,
_M_extbufnext,
this->gptr() - this->eback());
c += _M_extbufnext - _M_extbuf - off;
update_st = true;
}
}
}
#if _WIN32
if (fseek(_M_file_, -c, SEEK_CUR))
return -1;
#else
if (fseeko(_M_file, -c, SEEK_CUR))
return -1;
#endif
if (update_st)
_M_st = state;
_M_extbufnext = _M_extbufend = _M_extbuf;
this->setg(0, 0, 0);
_M_cm = std::ios_base::openmode(0);
}
return 0;
}
///////////////////////////////////////////////////////////////////////////////
template <class CharT, class Traits>
void
basic_filebuf<CharT, Traits>::imbue(const std::locale& loc) {
sync();
_M_cv = &std::use_facet<std::codecvt<char_type, char, state_type> >(loc);
bool old_anc = _M_always_noconv;
_M_always_noconv = _M_cv->always_noconv();
if (old_anc != _M_always_noconv) {
this->setg(0, 0, 0);
this->setp(0, 0);
// invariant, char_type is char, else we couldn't get here
// need to dump _M_intbuf
if (_M_always_noconv) {
if (_M_owns_eb)
delete [] _M_extbuf;
_M_owns_eb = _M_owns_ib;
_M_ebs = _M_ibs;
_M_extbuf = reinterpret_cast<char*>(_M_intbuf);
_M_ibs = 0;
_M_intbuf = nullptr;
_M_owns_ib = false;
} else { // need to obtain an _M_intbuf.
// If _M_extbuf is user-supplied, use it, else new _M_intbuf
if (!_M_owns_eb && _M_extbuf != _M_extbuf_min) {
_M_ibs = _M_ebs;
_M_intbuf = reinterpret_cast<char_type*>(_M_extbuf);
_M_owns_ib = false;
_M_extbuf = new char[_M_ebs];
_M_owns_eb = true;
} else {
_M_ibs = _M_ebs;
_M_intbuf = new char_type[_M_ibs];
_M_owns_ib = true;
}
}
}
}
///////////////////////////////////////////////////////////////////////////////
template <class CharT, class Traits>
bool
basic_filebuf<CharT, Traits>::_M_read_mode() {
if (!(_M_cm & std::ios_base::in)) {
this->setp(0, 0);
if (_M_always_noconv)
this->setg(reinterpret_cast<char_type*>(_M_extbuf),
reinterpret_cast<char_type*>(_M_extbuf) + _M_ebs,
reinterpret_cast<char_type*>(_M_extbuf) + _M_ebs);
else
this->setg(_M_intbuf, _M_intbuf + _M_ibs, _M_intbuf + _M_ibs);
_M_cm = std::ios_base::in;
return true;
}
return false;
}
///////////////////////////////////////////////////////////////////////////////
template <class CharT, class Traits>
void
basic_filebuf<CharT, Traits>::_M_write_mode() {
if (!(_M_cm & std::ios_base::out)) {
this->setg(0, 0, 0);
if (_M_ebs > sizeof(_M_extbuf_min)) {
if (_M_always_noconv)
this->setp(reinterpret_cast<char_type*>(_M_extbuf),
reinterpret_cast<char_type*>(_M_extbuf) +
(_M_ebs - 1));
else
this->setp(_M_intbuf, _M_intbuf + (_M_ibs - 1));
} else {
this->setp(0, 0);
}
_M_cm = std::ios_base::out;
}
}
///////////////////////////////////////////////////////////////////////////////
}
///////////////////////////////////////////////////////////////////////////////
#endif // KALDI_UTIL_BASIC_FILEBUF_H_
///////////////////////////////////////////////////////////////////////////////
/*
* ============================================================================
* libc++ License
* ============================================================================
*
* The libc++ library is dual licensed under both the University of Illinois
* "BSD-Like" license and the MIT license. As a user of this code you may
* choose to use it under either license. As a contributor, you agree to allow
* your code to be used under both.
*
* Full text of the relevant licenses is included below.
*
* ============================================================================
*
* University of Illinois/NCSA
* Open Source License
*
* Copyright (c) 2009-2014 by the contributors listed in CREDITS.TXT (included below)
*
* All rights reserved.
*
* Developed by:
*
* LLVM Team
*
* University of Illinois at Urbana-Champaign
*
* http://llvm.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal with
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimers.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimers in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the names of the LLVM Team, University of Illinois at
* Urbana-Champaign, nor the names of its contributors may be used to
* endorse or promote products derived from this Software without specific
* prior written permission.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE
* SOFTWARE.
*
* ==============================================================================
*
* Copyright (c) 2009-2014 by the contributors listed in CREDITS.TXT (included below)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* ==============================================================================
*
* This file is a partial list of people who have contributed to the LLVM/libc++
* project. If you have contributed a patch or made some other contribution to
* LLVM/libc++, please submit a patch to this file to add yourself, and it will be
* done!
*
* The list is sorted by surname and formatted to allow easy grepping and
* beautification by scripts. The fields are: name (N), email (E), web-address
* (W), PGP key ID and fingerprint (P), description (D), and snail-mail address
* (S).
*
* N: Saleem Abdulrasool
* E: compnerd@compnerd.org
* D: Minor patches and Linux fixes.
*
* N: Dimitry Andric
* E: dimitry@andric.com
* D: Visibility fixes, minor FreeBSD portability patches.
*
* N: Holger Arnold
* E: holgerar@gmail.com
* D: Minor fix.
*
* N: Ruben Van Boxem
* E: vanboxem dot ruben at gmail dot com
* D: Initial Windows patches.
*
* N: David Chisnall
* E: theraven at theravensnest dot org
* D: FreeBSD and Solaris ports, libcxxrt support, some atomics work.
*
* N: Marshall Clow
* E: mclow.lists@gmail.com
* E: marshall@idio.com
* D: C++14 support, patches and bug fixes.
*
* N: Bill Fisher
* E: william.w.fisher@gmail.com
* D: Regex bug fixes.
*
* N: Matthew Dempsky
* E: matthew@dempsky.org
* D: Minor patches and bug fixes.
*
* N: Google Inc.
* D: Copyright owner and contributor of the CityHash algorithm
*
* N: Howard Hinnant
* E: hhinnant@apple.com
* D: Architect and primary author of libc++
*
* N: Hyeon-bin Jeong
* E: tuhertz@gmail.com
* D: Minor patches and bug fixes.
*
* N: Argyrios Kyrtzidis
* E: kyrtzidis@apple.com
* D: Bug fixes.
*
* N: Bruce Mitchener, Jr.
* E: bruce.mitchener@gmail.com
* D: Emscripten-related changes.
*
* N: Michel Morin
* E: mimomorin@gmail.com
* D: Minor patches to is_convertible.
*
* N: Andrew Morrow
* E: andrew.c.morrow@gmail.com
* D: Minor patches and Linux fixes.
*
* N: Arvid Picciani
* E: aep at exys dot org
* D: Minor patches and musl port.
*
* N: Bjorn Reese
* E: breese@users.sourceforge.net
* D: Initial regex prototype
*
* N: Nico Rieck
* E: nico.rieck@gmail.com
* D: Windows fixes
*
* N: Jonathan Sauer
* D: Minor patches, mostly related to constexpr
*
* N: Craig Silverstein
* E: csilvers@google.com
* D: Implemented Cityhash as the string hash function on 64-bit machines
*
* N: Richard Smith
* D: Minor patches.
*
* N: Joerg Sonnenberger
* E: joerg@NetBSD.org
* D: NetBSD port.
*
* N: Stephan Tolksdorf
* E: st@quanttec.com
* D: Minor <atomic> fix
*
* N: Michael van der Westhuizen
* E: r1mikey at gmail dot com
*
* N: Klaas de Vries
* E: klaas at klaasgaaf dot nl
* D: Minor bug fix.
*
* N: Zhang Xiongpang
* E: zhangxiongpang@gmail.com
* D: Minor patches and bug fixes.
*
* N: Xing Xue
* E: xingxue@ca.ibm.com
* D: AIX port
*
* N: Zhihao Yuan
* E: lichray@gmail.com
* D: Standard compatibility fixes.
*
* N: Jeffrey Yasskin
* E: jyasskin@gmail.com
* E: jyasskin@google.com
* D: Linux fixes.
*/
@@ -0,0 +1,91 @@
// util/const-integer-set-inl.h
// Copyright 2009-2011 Microsoft Corporation
// See ../../COPYING for clarification regarding multiple authors
//
// 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
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#ifndef KALDI_UTIL_CONST_INTEGER_SET_INL_H_
#define KALDI_UTIL_CONST_INTEGER_SET_INL_H_
// Do not include this file directly. It is included by const-integer-set.h
namespace kaldi {
template<class I>
void ConstIntegerSet<I>::InitInternal() {
KALDI_ASSERT_IS_INTEGER_TYPE(I);
quick_set_.clear(); // just in case we previously had data.
if (slow_set_.size() == 0) {
lowest_member_=(I) 1;
highest_member_=(I) 0;
contiguous_ = false;
quick_ = false;
} else {
lowest_member_ = slow_set_.front();
highest_member_ = slow_set_.back();
size_t range = highest_member_ + 1 - lowest_member_;
if (range == slow_set_.size()) {
contiguous_ = true;
quick_= false;
} else {
contiguous_ = false;
// If it would be more compact to store as bool
if (range < slow_set_.size() * 8 * sizeof(I)) {
// (assuming 1 bit per element)...
quick_set_.resize(range, false);
for (size_t i = 0;i < slow_set_.size();i++)
quick_set_[slow_set_[i] - lowest_member_] = true;
quick_ = true;
} else {
quick_ = false;
}
}
}
}
template<class I>
int ConstIntegerSet<I>::count(I i) const {
if (i < lowest_member_ || i > highest_member_) {
return 0;
} else {
if (contiguous_) return true;
if (quick_) {
return (quick_set_[i-lowest_member_] ? 1 : 0);
} else {
bool ans = std::binary_search(slow_set_.begin(), slow_set_.end(), i);
return (ans ? 1 : 0);
}
}
}
template<class I>
void ConstIntegerSet<I>::Write(std::ostream &os, bool binary) const {
WriteIntegerVector(os, binary, slow_set_);
}
template<class I>
void ConstIntegerSet<I>::Read(std::istream &is, bool binary) {
ReadIntegerVector(is, binary, &slow_set_);
InitInternal();
}
} // end namespace kaldi
#endif // KALDI_UTIL_CONST_INTEGER_SET_INL_H_
@@ -0,0 +1,145 @@
// util/const-integer-set-test.cc
// Copyright 2009-2011 Microsoft Corporation
// See ../../COPYING for clarification regarding multiple authors
//
// 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
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#include "util/const-integer-set.h"
#include <set> // for baseline.
#include <cassert>
#include <cstdlib>
#include <iostream>
#include "util/kaldi-io.h"
namespace kaldi {
template<class Int> void TestSetOfNumbers(bool binary) {
std::set<Int> baseline_set;
size_t n_in_set = (Rand() % 3) * 50 + (Rand() % 4); // may be less than this.
size_t max = (Int) (Rand() % 100) + 1;
for (size_t i = 0; i < n_in_set; i++) {
Int to_add((Int) (Rand() % max));
baseline_set.insert(to_add);
}
std::vector<Int> vector_set;
for (typename std::set<Int>::iterator iter = baseline_set.begin();
iter!= baseline_set.end();iter++)
vector_set.push_back(*iter);
if (vector_set.size() != 0) {
for (size_t i = 0;i < 10;i++) // randomize order.
std::swap(vector_set[Rand()%vector_set.size()],
vector_set[Rand()%vector_set.size()]);
}
ConstIntegerSet<Int> my_set1(baseline_set);
ConstIntegerSet<Int> my_set2(vector_set);
ConstIntegerSet<Int> my_set3;
my_set3.Init(baseline_set);
ConstIntegerSet<Int> my_set4;
my_set4.Init(vector_set);
{
my_set4.Write(Output("tmpf", binary).Stream(), binary);
}
ConstIntegerSet<Int> my_set5;
{
bool binary_in;
Input ki("tmpf", &binary_in);
my_set5.Read(ki.Stream(), binary_in);
}
// if (enable_iterators) {
size_t sz = baseline_set.size(), sz1 = my_set1.size(), sz2 = my_set2.size(),
sz3 = my_set3.size(), sz4 = my_set4.size(), sz5 = my_set5.size();
KALDI_ASSERT(sz == sz1 && sz == sz2 && sz == sz3 && sz == sz4 && sz == sz5);
// }
for (size_t i = 0;i < 100;i++) {
Int some_int;
if (i%2 == 0 && vector_set.size() != 0)
some_int = vector_set[Rand()%vector_set.size()];
else
some_int = Rand() % max;
bool in_baseline = (baseline_set.count(some_int) != 0);
bool in_my_set1 = (my_set1.count(some_int) != 0);
bool in_my_set2 = (my_set2.count(some_int) != 0);
bool in_my_set3 = (my_set3.count(some_int) != 0);
bool in_my_set4 = (my_set4.count(some_int) != 0);
bool in_my_set5 = (my_set5.count(some_int) != 0);
if (in_baseline) {
KALDI_ASSERT(in_my_set1&&in_my_set2&&in_my_set3&&in_my_set4&&in_my_set5);
} else {
KALDI_ASSERT(!in_my_set1&&!in_my_set2&&!in_my_set3&&!in_my_set4&&
!in_my_set5);
}
}
// if (enable_iterators) {
typename std::set<Int>::iterator baseline_iter = baseline_set.begin();
typename ConstIntegerSet<Int>::iterator my_iter1 = my_set1.begin();
typename ConstIntegerSet<Int>::iterator my_iter2 = my_set2.begin();
typename ConstIntegerSet<Int>::iterator my_iter3 = my_set3.begin();
typename ConstIntegerSet<Int>::iterator my_iter4 = my_set4.begin();
typename ConstIntegerSet<Int>::iterator my_iter5 = my_set5.begin();
while (baseline_iter != baseline_set.end()) {
KALDI_ASSERT(my_iter1 != my_set1.end());
KALDI_ASSERT(my_iter2 != my_set2.end());
KALDI_ASSERT(my_iter3 != my_set3.end());
KALDI_ASSERT(my_iter4 != my_set4.end());
KALDI_ASSERT(my_iter5 != my_set5.end());
KALDI_ASSERT(*baseline_iter == *my_iter1);
KALDI_ASSERT(*baseline_iter == *my_iter2);
KALDI_ASSERT(*baseline_iter == *my_iter3);
KALDI_ASSERT(*baseline_iter == *my_iter4);
KALDI_ASSERT(*baseline_iter == *my_iter5);
baseline_iter++;
my_iter1++;
my_iter2++;
my_iter3++;
my_iter4++;
my_iter5++;
}
KALDI_ASSERT(my_iter1 == my_set1.end());
KALDI_ASSERT(my_iter2 == my_set2.end());
KALDI_ASSERT(my_iter3 == my_set3.end());
KALDI_ASSERT(my_iter4 == my_set4.end());
KALDI_ASSERT(my_iter5 == my_set5.end());
// }
}
} // end namespace kaldi
int main() {
using namespace kaldi;
for (size_t i = 0;i < 10;i++) {
TestSetOfNumbers<int>(Rand()%2);
TestSetOfNumbers<unsigned int>(Rand()%2);
TestSetOfNumbers<int16>(Rand()%2);
TestSetOfNumbers<int16>(Rand()%2);
TestSetOfNumbers<char>(Rand()%2);
TestSetOfNumbers<unsigned char>(Rand()%2);
}
std::cout << "Test OK.\n";
}
@@ -0,0 +1,96 @@
// util/const-integer-set.h
// Copyright 2009-2011 Microsoft Corporation
// See ../../COPYING for clarification regarding multiple authors
//
// 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
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#ifndef KALDI_UTIL_CONST_INTEGER_SET_H_
#define KALDI_UTIL_CONST_INTEGER_SET_H_
#include <vector>
#include <set>
#include <algorithm>
#include <limits>
#include <cassert>
#include "util/stl-utils.h"
/* ConstIntegerSet is a way to efficiently test whether something is in a
supplied set of integers. It can be initialized from a vector or set, but
never changed after that. It either uses a sorted vector or an array of
bool, depending on the input. It behaves like a const version of an STL set, with
only a subset of the functionality, except all the member functions are
upper-case.
Note that we could get rid of the member slow_set_, but we'd have to
do more work to implement an iterator type. This would save memory.
*/
namespace kaldi {
template<class I> class ConstIntegerSet {
public:
ConstIntegerSet(): lowest_member_(1), highest_member_(0) { }
void Init(const std::vector<I> &input) {
slow_set_ = input;
SortAndUniq(&slow_set_);
InitInternal();
}
void Init(const std::set<I> &input) {
CopySetToVector(input, &slow_set_);
InitInternal();
}
explicit ConstIntegerSet(const std::vector<I> &input): slow_set_(input) {
SortAndUniq(&slow_set_);
InitInternal();
}
explicit ConstIntegerSet(const std::set<I> &input) {
CopySetToVector(input, &slow_set_);
InitInternal();
}
explicit ConstIntegerSet(const ConstIntegerSet<I> &other):
slow_set_(other.slow_set_) {
InitInternal();
}
int count(I i) const; // returns 1 or 0.
typedef typename std::vector<I>::const_iterator iterator;
iterator begin() const { return slow_set_.begin(); }
iterator end() const { return slow_set_.end(); }
size_t size() const { return slow_set_.size(); }
bool empty() const { return slow_set_.empty(); }
void Write(std::ostream &os, bool binary) const;
void Read(std::istream &is, bool binary);
private:
I lowest_member_;
I highest_member_;
bool contiguous_;
bool quick_;
std::vector<bool> quick_set_;
std::vector<I> slow_set_;
void InitInternal();
};
} // end namespace kaldi
#include "util/const-integer-set-inl.h"
#endif // KALDI_UTIL_CONST_INTEGER_SET_H_
@@ -0,0 +1,200 @@
// util/edit-distance-inl.h
// Copyright 2009-2011 Microsoft Corporation; Haihua Xu; Yanmin Qian
// See ../../COPYING for clarification regarding multiple authors
//
// 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
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#ifndef KALDI_UTIL_EDIT_DISTANCE_INL_H_
#define KALDI_UTIL_EDIT_DISTANCE_INL_H_
#include <algorithm>
#include <utility>
#include <vector>
#include "util/stl-utils.h"
namespace kaldi {
template<class T>
int32 LevenshteinEditDistance(const std::vector<T> &a,
const std::vector<T> &b) {
// Algorithm:
// write A and B for the sequences, with elements a_0 ..
// let |A| = M and |B| = N be the lengths, and have
// elements a_0 ... a_{M-1} and b_0 ... b_{N-1}.
// We are computing the recursion
// E(m, n) = min( E(m-1, n-1) + (1-delta(a_{m-1}, b_{n-1})),
// E(m-1, n) + 1,
// E(m, n-1) + 1).
// where E(m, n) is defined for m = 0..M and n = 0..N and out-of-
// bounds quantities are considered to be infinity (i.e. the
// recursion does not visit them).
// We do this computation using a vector e of size N+1.
// The outer iterations range over m = 0..M.
int M = a.size(), N = b.size();
std::vector<int32> e(N+1);
std::vector<int32> e_tmp(N+1);
// initialize e.
for (size_t i = 0; i < e.size(); i++)
e[i] = i;
for (int32 m = 1; m <= M; m++) {
// computing E(m, .) from E(m-1, .)
// handle special case n = 0:
e_tmp[0] = e[0] + 1;
for (int32 n = 1; n <= N; n++) {
int32 term1 = e[n-1] + (a[m-1] == b[n-1] ? 0 : 1);
int32 term2 = e[n] + 1;
int32 term3 = e_tmp[n-1] + 1;
e_tmp[n] = std::min(term1, std::min(term2, term3));
}
e = e_tmp;
}
return e.back();
}
//
struct error_stats {
int32 ins_num;
int32 del_num;
int32 sub_num;
int32 total_cost; // minimum total cost to the current alignment.
};
// Note that both hyp and ref should not contain noise word in
// the following implementation.
template<class T>
int32 LevenshteinEditDistance(const std::vector<T> &ref,
const std::vector<T> &hyp,
int32 *ins, int32 *del, int32 *sub) {
// temp sequence to remember error type and stats.
std::vector<error_stats> e(ref.size()+1);
std::vector<error_stats> cur_e(ref.size()+1);
// initialize the first hypothesis aligned to the reference at each
// position:[hyp_index =0][ref_index]
for (size_t i =0; i < e.size(); i ++) {
e[i].ins_num = 0;
e[i].sub_num = 0;
e[i].del_num = i;
e[i].total_cost = i;
}
// for other alignments
for (size_t hyp_index = 1; hyp_index <= hyp.size(); hyp_index ++) {
cur_e[0] = e[0];
cur_e[0].ins_num++;
cur_e[0].total_cost++;
for (size_t ref_index = 1; ref_index <= ref.size(); ref_index ++) {
int32 ins_err = e[ref_index].total_cost + 1;
int32 del_err = cur_e[ref_index-1].total_cost + 1;
int32 sub_err = e[ref_index-1].total_cost;
if (hyp[hyp_index-1] != ref[ref_index-1])
sub_err++;
if (sub_err < ins_err && sub_err < del_err) {
cur_e[ref_index] =e[ref_index-1];
if (hyp[hyp_index-1] != ref[ref_index-1])
cur_e[ref_index].sub_num++; // substitution error should be increased
cur_e[ref_index].total_cost = sub_err;
} else if (del_err < ins_err) {
cur_e[ref_index] = cur_e[ref_index-1];
cur_e[ref_index].total_cost = del_err;
cur_e[ref_index].del_num++; // deletion number is increased.
} else {
cur_e[ref_index] = e[ref_index];
cur_e[ref_index].total_cost = ins_err;
cur_e[ref_index].ins_num++; // insertion number is increased.
}
}
e = cur_e; // alternate for the next recursion.
}
size_t ref_index = e.size()-1;
*ins = e[ref_index].ins_num, *del =
e[ref_index].del_num, *sub = e[ref_index].sub_num;
return e[ref_index].total_cost;
}
template<class T>
int32 LevenshteinAlignment(const std::vector<T> &a,
const std::vector<T> &b,
T eps_symbol,
std::vector<std::pair<T, T> > *output) {
// Check inputs:
{
KALDI_ASSERT(output != NULL);
for (size_t i = 0; i < a.size(); i++) KALDI_ASSERT(a[i] != eps_symbol);
for (size_t i = 0; i < b.size(); i++) KALDI_ASSERT(b[i] != eps_symbol);
}
output->clear();
// This is very memory-inefficiently implemented using a vector of vectors.
size_t M = a.size(), N = b.size();
size_t m, n;
std::vector<std::vector<int32> > e(M+1);
for (m = 0; m <=M; m++) e[m].resize(N+1);
for (n = 0; n <= N; n++)
e[0][n] = n;
for (m = 1; m <= M; m++) {
e[m][0] = e[m-1][0] + 1;
for (n = 1; n <= N; n++) {
int32 sub_or_ok = e[m-1][n-1] + (a[m-1] == b[n-1] ? 0 : 1);
int32 del = e[m-1][n] + 1; // assumes a == ref, b == hyp.
int32 ins = e[m][n-1] + 1;
e[m][n] = std::min(sub_or_ok, std::min(del, ins));
}
}
// get time-reversed output first: trace back.
m = M;
n = N;
while (m != 0 || n != 0) {
size_t last_m, last_n;
if (m == 0) {
last_m = m;
last_n = n-1;
} else if (n == 0) {
last_m = m-1;
last_n = n;
} else {
int32 sub_or_ok = e[m-1][n-1] + (a[m-1] == b[n-1] ? 0 : 1);
int32 del = e[m-1][n] + 1; // assumes a == ref, b == hyp.
int32 ins = e[m][n-1] + 1;
// choose sub_or_ok if all else equal.
if (sub_or_ok <= std::min(del, ins)) {
last_m = m-1;
last_n = n-1;
} else {
if (del <= ins) { // choose del over ins if equal.
last_m = m-1;
last_n = n;
} else {
last_m = m;
last_n = n-1;
}
}
}
T a_sym, b_sym;
a_sym = (last_m == m ? eps_symbol : a[last_m]);
b_sym = (last_n == n ? eps_symbol : b[last_n]);
output->push_back(std::make_pair(a_sym, b_sym));
m = last_m;
n = last_n;
}
ReverseVector(output);
return e[M][N];
}
} // end namespace kaldi
#endif // KALDI_UTIL_EDIT_DISTANCE_INL_H_
@@ -0,0 +1,267 @@
// util/edit-distance-test.cc
// Copyright 2009-2011 Microsoft Corporation; Haihua Xu
// See ../../COPYING for clarification regarding multiple authors
//
// 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
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#include "base/kaldi-common.h"
#include "util/edit-distance.h"
namespace kaldi {
void TestEditDistance() {
std::vector<int32> a;
std::vector<int32> b;
KALDI_ASSERT(LevenshteinEditDistance(a, b) == 0);
a.push_back(1);
KALDI_ASSERT(LevenshteinEditDistance(a, b) == 1);
b.push_back(1);
KALDI_ASSERT(LevenshteinEditDistance(a, b) == 0);
b.push_back(2);
KALDI_ASSERT(LevenshteinEditDistance(a, b) == 1);
a.push_back(2);
KALDI_ASSERT(LevenshteinEditDistance(a, b) == 0);
a.push_back(3);
a.push_back(4);
b.push_back(4);
KALDI_ASSERT(LevenshteinEditDistance(a, b) == 1);
a.push_back(5);
KALDI_ASSERT(LevenshteinEditDistance(a, b) == 2);
b.push_back(6);
KALDI_ASSERT(LevenshteinEditDistance(a, b) == 2);
a.push_back(1);
b.push_back(1);
KALDI_ASSERT(LevenshteinEditDistance(a, b) == 2);
b.push_back(10);
KALDI_ASSERT(LevenshteinEditDistance(a, b) == 3);
}
void TestEditDistanceString() {
std::vector<std::string> a;
std::vector<std::string> b;
KALDI_ASSERT(LevenshteinEditDistance(a, b) == 0);
a.push_back("1");
KALDI_ASSERT(LevenshteinEditDistance(a, b) == 1);
b.push_back("1");
KALDI_ASSERT(LevenshteinEditDistance(a, b) == 0);
b.push_back("2");
KALDI_ASSERT(LevenshteinEditDistance(a, b) == 1);
a.push_back("2");
KALDI_ASSERT(LevenshteinEditDistance(a, b) == 0);
a.push_back("3");
a.push_back("4");
b.push_back("4");
KALDI_ASSERT(LevenshteinEditDistance(a, b) == 1);
a.push_back("5");
KALDI_ASSERT(LevenshteinEditDistance(a, b) == 2);
b.push_back("6");
KALDI_ASSERT(LevenshteinEditDistance(a, b) == 2);
a.push_back("1");
b.push_back("1");
KALDI_ASSERT(LevenshteinEditDistance(a, b) == 2);
b.push_back("10");
KALDI_ASSERT(LevenshteinEditDistance(a, b) == 3);
}
// edit distance calculate
void TestEditDistance2() {
std::vector<int32> hyp;
std::vector<int32> ref;
int32 ins, del, sub, total_cost;
// initialize hypothesis
hyp.push_back(1);
hyp.push_back(3);
hyp.push_back(4);
hyp.push_back(5);
// initialize reference
ref.push_back(2);
ref.push_back(3);
ref.push_back(4);
ref.push_back(5);
ref.push_back(6);
ref.push_back(7);
total_cost = LevenshteinEditDistance(ref, hyp, &ins, &del, &sub);
KALDI_ASSERT(total_cost == 3 && ins == 0 && del == 2 && sub == 1);
std::swap(hyp, ref);
total_cost = LevenshteinEditDistance(ref, hyp, &ins, &del, &sub);
KALDI_ASSERT(total_cost == 3 && ins == 2 && del == 0 && sub == 1);
hyp.clear();
ref.clear();
hyp.push_back(1);
ref.push_back(1);
total_cost = LevenshteinEditDistance(ref, hyp, &ins, &del, &sub);
KALDI_ASSERT(total_cost == 0 && ins+del+sub == 0);
hyp.push_back(2);
ref.push_back(3);
total_cost = LevenshteinEditDistance(ref, hyp, &ins, &del, &sub);
KALDI_ASSERT(total_cost == 1 && ins == 0 && del == 0 && sub == 1);
// randomized test
size_t num = 0;
for (; num < 1000; num ++) {
int32 hyp_len = Rand()%11;
int32 ref_len = Rand()%3;
hyp.resize(hyp_len);
ref.resize(ref_len);
int32 index = 0;
for (; index < hyp_len; index ++)
hyp[index] = Rand()%4;
for (index = 0; index < ref_len; index ++)
ref[index] = Rand()%4;
// current version
total_cost = LevenshteinEditDistance(ref, hyp, &ins, &del, &sub);
// previous version
int32 total_cost2 = LevenshteinEditDistance(hyp, ref);
// verify both are the same
KALDI_ASSERT(total_cost == total_cost2);
KALDI_ASSERT(ins+del+sub == total_cost);
KALDI_ASSERT(del-ins == static_cast<int32>(ref.size() -hyp.size()));
}
return;
}
// edit distance calculate
void TestEditDistance2String() {
std::vector<std::string> hyp;
std::vector<std::string> ref;
int32 ins, del, sub, total_cost;
// initialize hypothesis
hyp.push_back("1");
hyp.push_back("3");
hyp.push_back("4");
hyp.push_back("5");
// initialize reference
ref.push_back("2");
ref.push_back("3");
ref.push_back("4");
ref.push_back("5");
ref.push_back("6");
ref.push_back("7");
total_cost = LevenshteinEditDistance(ref, hyp, &ins, &del, &sub);
KALDI_ASSERT(total_cost == 3 && ins == 0 && del == 2 && sub == 1);
std::swap(hyp, ref);
total_cost = LevenshteinEditDistance(ref, hyp, &ins, &del, &sub);
KALDI_ASSERT(total_cost == 3 && ins == 2 && del == 0 && sub == 1);
hyp.clear();
ref.clear();
hyp.push_back("1");
ref.push_back("1");
total_cost = LevenshteinEditDistance(ref, hyp, &ins, &del, &sub);
KALDI_ASSERT(total_cost == 0 && ins+del+sub == 0);
hyp.push_back("2");
ref.push_back("3");
total_cost = LevenshteinEditDistance(ref, hyp, &ins, &del, &sub);
KALDI_ASSERT(total_cost == 1 && ins == 0 && del == 0 && sub == 1);
// randomized test
size_t num = 0;
for (; num < 1000; num ++) {
int32 hyp_len = Rand()%11;
int32 ref_len = Rand()%3;
hyp.resize(hyp_len);
ref.resize(ref_len);
int32 index = 0;
for (; index < hyp_len; index ++)
hyp[index] = Rand()%4;
for (index = 0; index < ref_len; index ++)
ref[index] = Rand()%4;
// current version
total_cost = LevenshteinEditDistance(ref, hyp, &ins, &del, &sub);
// previous version
int32 total_cost2 = LevenshteinEditDistance(hyp, ref);
// verify both are the same
KALDI_ASSERT(total_cost == total_cost2);
KALDI_ASSERT(ins+del+sub == total_cost);
KALDI_ASSERT(del-ins == static_cast<int32>(ref.size() -hyp.size()));
}
return;
}
void TestLevenshteinAlignment() {
for (size_t i = 0; i < 100; i++) {
size_t a_sz = Rand() % 5, b_sz = Rand() % 5;
std::vector<int32> a, b;
for (size_t j = 0; j < a_sz; j++) a.push_back(Rand() % 10);
for (size_t j = 0; j < b_sz; j++) b.push_back(Rand() % 10);
int32 eps_sym = -1;
std::vector<std::pair<int32, int32> > ans;
int32 e1 = LevenshteinEditDistance(a, b),
e2 = LevenshteinAlignment(a, b, eps_sym, &ans);
KALDI_ASSERT(e1 == e2);
std::vector<int32> a2, b2;
for (size_t i = 0; i < ans.size(); i++) {
if (ans[i].first != eps_sym) a2.push_back(ans[i].first);
if (ans[i].second != eps_sym) b2.push_back(ans[i].second);
}
KALDI_ASSERT(a == a2);
KALDI_ASSERT(b == b2);
}
}
} // end namespace kaldi
int main() {
using namespace kaldi;
TestEditDistance();
TestEditDistanceString();
TestEditDistance2();
TestEditDistance2String();
TestLevenshteinAlignment();
std::cout << "Test OK\n";
}
@@ -0,0 +1,64 @@
// util/edit-distance.h
// Copyright 2009-2011 Microsoft Corporation; Haihua Xu
// See ../../COPYING for clarification regarding multiple authors
//
// 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
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#ifndef KALDI_UTIL_EDIT_DISTANCE_H_
#define KALDI_UTIL_EDIT_DISTANCE_H_
#include <vector>
#include <set>
#include <algorithm>
#include <limits>
#include <cassert>
#include <utility>
#include "util/edit-distance-inl.h"
#include "base/kaldi-types.h"
namespace kaldi {
// Compute the edit-distance between two strings.
template<class T>
int32 LevenshteinEditDistance(const std::vector<T> &a,
const std::vector<T> &b);
// edit distance calculation with conventional method.
// note: noise word must be filtered out from the hypothesis and
// reference sequence
// before the following procedure conducted.
template<class T>
int32 LevenshteinEditDistance(const std::vector<T> &ref,
const std::vector<T> &hyp,
int32 *ins, int32 *del, int32 *sub);
// This version of the edit-distance computation outputs the alignment
// between the two. This is a vector of pairs of (symbol a, symbol b).
// The epsilon symbol (eps_symbol) must not occur in sequences a or b.
// Where one aligned to no symbol in the other (insertion or deletion),
// epsilon will be the corresponding member of the pair.
// It returns the edit-distance between the two strings.
template<class T>
int32 LevenshteinAlignment(const std::vector<T> &a,
const std::vector<T> &b,
T eps_symbol,
std::vector<std::pair<T, T> > *output);
} // end namespace kaldi
#endif // KALDI_UTIL_EDIT_DISTANCE_H_
@@ -0,0 +1,194 @@
// util/hash-list-inl.h
// Copyright 2009-2011 Microsoft Corporation
// 2013 Johns Hopkins University (author: Daniel Povey)
// See ../../COPYING for clarification regarding multiple authors
//
// 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
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#ifndef KALDI_UTIL_HASH_LIST_INL_H_
#define KALDI_UTIL_HASH_LIST_INL_H_
// Do not include this file directly. It is included by fast-hash.h
namespace kaldi {
template<class I, class T> HashList<I, T>::HashList() {
list_head_ = NULL;
bucket_list_tail_ = static_cast<size_t>(-1); // invalid.
hash_size_ = 0;
freed_head_ = NULL;
}
template<class I, class T> void HashList<I, T>::SetSize(size_t size) {
hash_size_ = size;
KALDI_ASSERT(list_head_ == NULL &&
bucket_list_tail_ == static_cast<size_t>(-1)); // make sure empty.
if (size > buckets_.size())
buckets_.resize(size, HashBucket(0, NULL));
}
template<class I, class T>
typename HashList<I, T>::Elem* HashList<I, T>::Clear() {
// Clears the hashtable and gives ownership of the currently contained list
// to the user.
for (size_t cur_bucket = bucket_list_tail_;
cur_bucket != static_cast<size_t>(-1);
cur_bucket = buckets_[cur_bucket].prev_bucket) {
buckets_[cur_bucket].last_elem = NULL; // this is how we indicate "empty".
}
bucket_list_tail_ = static_cast<size_t>(-1);
Elem *ans = list_head_;
list_head_ = NULL;
return ans;
}
template<class I, class T>
const typename HashList<I, T>::Elem* HashList<I, T>::GetList() const {
return list_head_;
}
template<class I, class T>
inline void HashList<I, T>::Delete(Elem *e) {
e->tail = freed_head_;
freed_head_ = e;
}
template<class I, class T>
inline typename HashList<I, T>::Elem* HashList<I, T>::Find(I key) {
size_t index = (static_cast<size_t>(key) % hash_size_);
HashBucket &bucket = buckets_[index];
if (bucket.last_elem == NULL) {
return NULL; // empty bucket.
} else {
Elem *head = (bucket.prev_bucket == static_cast<size_t>(-1) ?
list_head_ :
buckets_[bucket.prev_bucket].last_elem->tail),
*tail = bucket.last_elem->tail;
for (Elem *e = head; e != tail; e = e->tail)
if (e->key == key) return e;
return NULL; // Not found.
}
}
template<class I, class T>
inline typename HashList<I, T>::Elem* HashList<I, T>::New() {
if (freed_head_) {
Elem *ans = freed_head_;
freed_head_ = freed_head_->tail;
return ans;
} else {
Elem *tmp = new Elem[allocate_block_size_];
for (size_t i = 0; i+1 < allocate_block_size_; i++)
tmp[i].tail = tmp+i+1;
tmp[allocate_block_size_-1].tail = NULL;
freed_head_ = tmp;
allocated_.push_back(tmp);
return this->New();
}
}
template<class I, class T>
HashList<I, T>::~HashList() {
// First test whether we had any memory leak within the
// HashList, i.e. things for which the user did not call Delete().
size_t num_in_list = 0, num_allocated = 0;
for (Elem *e = freed_head_; e != NULL; e = e->tail)
num_in_list++;
for (size_t i = 0; i < allocated_.size(); i++) {
num_allocated += allocate_block_size_;
delete[] allocated_[i];
}
if (num_in_list != num_allocated) {
KALDI_WARN << "Possible memory leak: " << num_in_list
<< " != " << num_allocated
<< ": you might have forgotten to call Delete on "
<< "some Elems";
}
}
template<class I, class T>
inline typename HashList<I, T>::Elem* HashList<I, T>::Insert(I key, T val) {
size_t index = (static_cast<size_t>(key) % hash_size_);
HashBucket &bucket = buckets_[index];
// Check the element is existing or not.
if (bucket.last_elem != NULL) {
Elem *head = (bucket.prev_bucket == static_cast<size_t>(-1) ?
list_head_ :
buckets_[bucket.prev_bucket].last_elem->tail),
*tail = bucket.last_elem->tail;
for (Elem *e = head; e != tail; e = e->tail)
if (e->key == key) return e;
}
// This is a new element. Insert it.
Elem *elem = New();
elem->key = key;
elem->val = val;
if (bucket.last_elem == NULL) { // Unoccupied bucket. Insert at
// head of bucket list (which is tail of regular list, they go in
// opposite directions).
if (bucket_list_tail_ == static_cast<size_t>(-1)) {
// list was empty so this is the first elem.
KALDI_ASSERT(list_head_ == NULL);
list_head_ = elem;
} else {
// link in to the chain of Elems
buckets_[bucket_list_tail_].last_elem->tail = elem;
}
elem->tail = NULL;
bucket.last_elem = elem;
bucket.prev_bucket = bucket_list_tail_;
bucket_list_tail_ = index;
} else {
// Already-occupied bucket. Insert at tail of list of elements within
// the bucket.
elem->tail = bucket.last_elem->tail;
bucket.last_elem->tail = elem;
bucket.last_elem = elem;
}
return elem;
}
template<class I, class T>
void HashList<I, T>::InsertMore(I key, T val) {
size_t index = (static_cast<size_t>(key) % hash_size_);
HashBucket &bucket = buckets_[index];
Elem *elem = New();
elem->key = key;
elem->val = val;
KALDI_ASSERT(bucket.last_elem != NULL); // assume one element is already here
if (bucket.last_elem->key == key) { // standard behavior: add as last element
elem->tail = bucket.last_elem->tail;
bucket.last_elem->tail = elem;
bucket.last_elem = elem;
return;
}
Elem *e = (bucket.prev_bucket == static_cast<size_t>(-1) ?
list_head_ : buckets_[bucket.prev_bucket].last_elem->tail);
// find place to insert in linked list
while (e != bucket.last_elem->tail && e->key != key) e = e->tail;
KALDI_ASSERT(e->key == key); // not found? - should not happen
elem->tail = e->tail;
e->tail = elem;
}
} // end namespace kaldi
#endif // KALDI_UTIL_HASH_LIST_INL_H_
@@ -0,0 +1,107 @@
// util/hash-list-test.cc
// Copyright 2009-2011 Microsoft Corporation
// 2013 Johns Hopkins University (author: Daniel Povey)
// See ../../COPYING for clarification regarding multiple authors
//
// 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
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#include "util/hash-list.h"
#include <map> // for baseline.
#include <cstdlib>
#include <iostream>
namespace kaldi {
template<class Int, class T> void TestHashList() {
typedef typename HashList<Int, T>::Elem Elem;
HashList<Int, T> hash;
hash.SetSize(200); // must be called before use.
std::map<Int, T> m1;
for (size_t j = 0; j < 50; j++) {
Int key = Rand() % 200;
T val = Rand() % 50;
m1[key] = val;
Elem *e = hash.Find(key);
if (e) e->val = val;
else hash.Insert(key, val);
}
std::map<Int, T> m2;
for (int i = 0; i < 100; i++) {
m2.clear();
for (typename std::map<Int, T>::const_iterator iter = m1.begin();
iter != m1.end();
iter++) {
m2[iter->first + 1] = iter->second;
}
std::swap(m1, m2);
Elem *h = hash.Clear(), *tmp;
hash.SetSize(100 + Rand() % 100); // note, SetSize is relatively cheap
// operation as long as we are not increasing the size more than it's ever
// previously been increased to.
for (; h != NULL; h = tmp) {
hash.Insert(h->key + 1, h->val);
tmp = h->tail;
hash.Delete(h); // think of this like calling delete.
}
// Now make sure h and m2 are the same.
const Elem *list = hash.GetList();
size_t count = 0;
for (; list != NULL; list = list->tail, count++) {
KALDI_ASSERT(m1[list->key] == list->val);
}
for (size_t j = 0; j < 10; j++) {
Int key = Rand() % 200;
bool found_m1 = (m1.find(key) != m1.end());
if (found_m1) m1[key];
Elem *e = hash.Find(key);
KALDI_ASSERT((e != NULL) == found_m1);
if (found_m1)
KALDI_ASSERT(m1[key] == e->val);
}
KALDI_ASSERT(m1.size() == count);
}
}
} // end namespace kaldi
int main() {
using namespace kaldi;
for (size_t i = 0;i < 3;i++) {
TestHashList<int, unsigned int>();
TestHashList<unsigned int, int>();
TestHashList<int16, int32>();
TestHashList<int16, int32>();
TestHashList<char, unsigned char>();
TestHashList<unsigned char, int>();
}
std::cout << "Test OK.\n";
}
+147
View File
@@ -0,0 +1,147 @@
// util/hash-list.h
// Copyright 2009-2011 Microsoft Corporation
// 2013 Johns Hopkins University (author: Daniel Povey)
// See ../../COPYING for clarification regarding multiple authors
//
// 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
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#ifndef KALDI_UTIL_HASH_LIST_H_
#define KALDI_UTIL_HASH_LIST_H_
#include <vector>
#include <set>
#include <algorithm>
#include <limits>
#include <cassert>
#include "util/stl-utils.h"
/* This header provides utilities for a structure that's used in a decoder (but
is quite generic in nature so we implement and test it separately).
Basically it's a singly-linked list, but implemented in such a way that we
can quickly search for elements in the list. We give it a slightly richer
interface than just a hash and a list. The idea is that we want to separate
the hash part and the list part: basically, in the decoder, we want to have a
single hash for the current frame and the next frame, because by the time we
need to access the hash for the next frame we no longer need the hash for the
previous frame. So we have an operation that clears the hash but leaves the
list structure intact. We also control memory management inside this object,
to avoid repeated new's/deletes.
See hash-list-test.cc for an example of how to use this object.
*/
namespace kaldi {
template<class I, class T> class HashList {
public:
struct Elem {
I key;
T val;
Elem *tail;
};
/// Constructor takes no arguments.
/// Call SetSize to inform it of the likely size.
HashList();
/// Clears the hash and gives the head of the current list to the user;
/// ownership is transferred to the user (the user must call Delete()
/// for each element in the list, at his/her leisure).
Elem *Clear();
/// Gives the head of the current list to the user. Ownership retained in the
/// class. Caution: in December 2013 the return type was changed to const
/// Elem* and this function was made const. You may need to change some types
/// of local Elem* variables to const if this produces compilation errors.
const Elem *GetList() const;
/// Think of this like delete(). It is to be called for each Elem in turn
/// after you "obtained ownership" by doing Clear(). This is not the opposite
/// of. Insert, it is the opposite of New. It's really a memory operation.
inline void Delete(Elem *e);
/// This should probably not be needed to be called directly by the user.
/// Think of it as opposite
/// to Delete();
inline Elem *New();
/// Find tries to find this element in the current list using the hashtable.
/// It returns NULL if not present. The Elem it returns is not owned by the
/// user, it is part of the internal list owned by this object, but the user
/// is free to modify the "val" element.
inline Elem *Find(I key);
/// Insert inserts a new element into the hashtable/stored list.
/// Because element keys in a hashtable are unique, this operation checks
/// whether each inserted element has a key equivalent to the one of an
/// element already in the hashtable. If so, the element is not inserted,
/// returning an pointer to this existing element.
inline Elem *Insert(I key, T val);
/// Insert inserts another element with same key into the hashtable/
/// stored list.
/// By calling this, the user asserts that one element with that key is
/// already present.
/// We insert it that way, that all elements with the same key
/// follow each other.
/// Find() will return the first one of the elements with the same key.
inline void InsertMore(I key, T val);
/// SetSize tells the object how many hash buckets to allocate (should
/// typically be at least twice the number of objects we expect to go in the
/// structure, for fastest performance). It must be called while the hash
/// is empty (e.g. after Clear() or after initializing the object, but before
/// adding anything to the hash.
void SetSize(size_t sz);
/// Returns current number of hash buckets.
inline size_t Size() { return hash_size_; }
~HashList();
private:
struct HashBucket {
size_t prev_bucket; // index to next bucket (-1 if list tail). Note:
// list of buckets goes in opposite direction to list of Elems.
Elem *last_elem; // pointer to last element in this bucket (NULL if empty)
inline HashBucket(size_t i, Elem *e): prev_bucket(i), last_elem(e) {}
};
Elem *list_head_; // head of currently stored list.
size_t bucket_list_tail_; // tail of list of active hash buckets.
size_t hash_size_; // number of hash buckets.
std::vector<HashBucket> buckets_;
Elem *freed_head_; // head of list of currently freed elements. [ready for
// allocation]
std::vector<Elem*> allocated_; // list of allocated blocks.
static const size_t allocate_block_size_ = 1024; // Number of Elements to
// allocate in one block. Must be largish so storing allocated_ doesn't
// become a problem.
};
} // end namespace kaldi
#include "util/hash-list-inl.h"
#endif // KALDI_UTIL_HASH_LIST_H_
@@ -0,0 +1,129 @@
// util/kaldi-cygwin-io-inl.h
// Copyright 2015 Smart Action Company LLC (author: Kirill Katsnelson)
// See ../../COPYING for clarification regarding multiple authors
//
// 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
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#ifndef KALDI_UTIL_KALDI_CYGWIN_IO_INL_H_
#define KALDI_UTIL_KALDI_CYGWIN_IO_INL_H_
#ifndef _MSC_VER
#error This is a Windows-compatibility file. Something went wery wrong.
#endif
#include <string>
// This file is included only into kaldi-io.cc, and only if
// KALDI_CYGWIN_COMPAT is enabled.
//
// The routines map unix-ey paths passed to Windows programs from shell
// scripts in egs. Since shell scripts run under cygwin, they use cygwin's
// own mount table and a mapping to the file system. It is quite possible to
// create quite an intricate mapping that only own cygwin API would be able
// to untangle. Unfortunately, the API to map between filenames is not
// available to non-cygwin programs. Running cygpath for every file operation
// would as well be cumbersome. So this is only a simplistic path resolution,
// assuming that the default cygwin prefix /cygdrive is used, and that all
// resolved unix-style full paths end up prefixed with /cygdrive. This is
// quite a sensible approach. We'll also try to map /dev/null and /tmp/**,
// die on all other /dev/** and warn about all other rooted paths.
namespace kaldi {
static bool prefixp(const std::string& pfx, const std::string& str) {
return pfx.length() <= str.length() &&
std::equal(pfx.begin(), pfx.end(), str.begin());
}
static std::string cygprefix("/cygdrive/");
static std::string MapCygwinPathNoTmp(const std::string &filename) {
// UNC(?), relative, native Windows and empty paths are ok already.
if (prefixp("//", filename) || !prefixp("/", filename))
return filename;
// /dev/...
if (filename == "/dev/null")
return "\\\\.\\nul";
if (prefixp("/dev/", filename)) {
KALDI_ERR << "Unable to resolve path '" << filename
<< "' - only have /dev/null here.";
return "\\\\.\\invalid";
}
// /cygdrive/?[/....]
int preflen = cygprefix.size();
if (prefixp(cygprefix, filename)
&& filename.size() >= preflen + 1 && isalpha(filename[preflen])
&& (filename.size() == preflen + 1 || filename[preflen + 1] == '/')) {
return std::string() + filename[preflen] + ':' +
(filename.size() > preflen + 1 ? filename.substr(preflen + 1) : "/");
}
KALDI_WARN << "Unable to resolve path '" << filename
<< "' - cannot map unix prefix. "
<< "Will go on, but breakage will likely ensue.";
return filename;
}
// extern for unit testing.
std::string MapCygwinPath(const std::string &filename) {
// /tmp[/....]
if (filename != "/tmp" && !prefixp("/tmp/", filename)) {
return MapCygwinPathNoTmp(filename);
}
char *tmpdir = std::getenv("TMP");
if (tmpdir == nullptr)
tmpdir = std::getenv("TEMP");
if (tmpdir == nullptr) {
KALDI_ERR << "Unable to resolve path '" << filename
<< "' - unable to find temporary directory. Set TMP.";
return filename;
}
// Map the value of tmpdir again, as cygwin environment actually may contain
// unix-style paths.
return MapCygwinPathNoTmp(std::string(tmpdir) + filename.substr(4));
}
// A popen implementation that passes the command line through cygwin
// bash.exe. This is necessary since some piped commands are cygwin links
// (e. g. fgrep is a soft link to grep), and some are #!-files, such as
// gunzip which is a shell script that invokes gzip, or kaldi's own run.pl
// which is a perl script.
//
// _popen uses cmd.exe or whatever shell is specified via the COMSPEC
// variable. Unfortunately, it adds a hardcoded " /c " to it, so we cannot
// just substitute the environment variable COMSPEC to point to bash.exe.
// Instead, quote the command and pass it to bash via its -c switch.
static FILE *CygwinCompatPopen(const char* command, const char* mode) {
// To speed up command launch marginally, optionally accept full path
// to bash.exe. This will not work if the path contains spaces, but
// no sane person would install cygwin into a space-ridden path.
const char* bash_exe = std::getenv("BASH_EXE");
std::string qcmd(bash_exe != nullptr ? bash_exe : "bash.exe");
qcmd += " -c \"";
for (; *command; ++command) {
if (*command == '\"')
qcmd += '\"';
qcmd += *command;
}
qcmd += '\"';
return _popen(qcmd.c_str(), mode);
}
} // namespace kaldi
#endif // KALDI_UTIL_KALDI_CYGWIN_IO_INL_H_
@@ -0,0 +1,46 @@
// util/kaldi-io-inl.h
// Copyright 2009-2011 Microsoft Corporation
// See ../../COPYING for clarification regarding multiple authors
//
// 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
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#ifndef KALDI_UTIL_KALDI_IO_INL_H_
#define KALDI_UTIL_KALDI_IO_INL_H_
#include<string>
namespace kaldi {
bool Input::Open(const std::string &rxfilename, bool *binary) {
return OpenInternal(rxfilename, true, binary);
}
bool Input::OpenTextMode(const std::string &rxfilename) {
return OpenInternal(rxfilename, false, NULL);
}
bool Input::IsOpen() {
return impl_ != NULL;
}
bool Output::IsOpen() {
return impl_ != NULL;
}
} // end namespace kaldi.
#endif // KALDI_UTIL_KALDI_IO_INL_H_
@@ -0,0 +1,370 @@
// util/kaldi-io-test.cc
// Copyright 2009-2011 Microsoft Corporation
// See ../../COPYING for clarification regarding multiple authors
//
// 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
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#ifndef _MSC_VER
#include <unistd.h>
#endif
#include "base/io-funcs.h"
#include "util/kaldi-io.h"
#include "base/kaldi-math.h"
#include "base/kaldi-utils.h"
namespace kaldi {
void UnitTestClassifyRxfilename() {
KALDI_ASSERT(ClassifyRxfilename("") == kStandardInput);
KALDI_ASSERT(ClassifyRxfilename(" ") == kNoInput);
KALDI_ASSERT(ClassifyRxfilename(" a ") == kNoInput);
KALDI_ASSERT(ClassifyRxfilename("a ") == kNoInput);
KALDI_ASSERT(ClassifyRxfilename("a") == kFileInput);
KALDI_ASSERT(ClassifyRxfilename("-") == kStandardInput);
KALDI_ASSERT(ClassifyRxfilename("b|") == kPipeInput);
KALDI_ASSERT(ClassifyRxfilename("|b") == kNoInput);
KALDI_ASSERT(ClassifyRxfilename("b c|") == kPipeInput);
KALDI_ASSERT(ClassifyRxfilename(" b c|") == kPipeInput);
KALDI_ASSERT(ClassifyRxfilename("a b c:123") == kOffsetFileInput);
KALDI_ASSERT(ClassifyRxfilename("a b c:3") == kOffsetFileInput);
KALDI_ASSERT(ClassifyRxfilename("a b c:") == kFileInput);
KALDI_ASSERT(ClassifyRxfilename("a b c/3") == kFileInput);
KALDI_ASSERT(ClassifyRxfilename("ark,s,cs:a b c") == kNoInput);
KALDI_ASSERT(ClassifyRxfilename("scp:a b c") == kNoInput);
}
void UnitTestClassifyWxfilename() {
KALDI_ASSERT(ClassifyWxfilename("") == kStandardOutput);
KALDI_ASSERT(ClassifyWxfilename(" ") == kNoOutput);
KALDI_ASSERT(ClassifyWxfilename(" a ") == kNoOutput);
KALDI_ASSERT(ClassifyWxfilename("a ") == kNoOutput);
KALDI_ASSERT(ClassifyWxfilename("a") == kFileOutput);
KALDI_ASSERT(ClassifyWxfilename("-") == kStandardOutput);
KALDI_ASSERT(ClassifyWxfilename("b|") == kNoOutput);
KALDI_ASSERT(ClassifyWxfilename("|b") == kPipeOutput);
KALDI_ASSERT(ClassifyWxfilename("| b ") == kPipeOutput);
KALDI_ASSERT(ClassifyWxfilename("b c|") == kNoOutput);
KALDI_ASSERT(ClassifyWxfilename("a b c:123") == kNoOutput);
KALDI_ASSERT(ClassifyWxfilename("ark,s,cs:a b c") == kNoOutput);
KALDI_ASSERT(ClassifyWxfilename("scp:a b c") == kNoOutput);
KALDI_ASSERT(ClassifyWxfilename("a b c:3") == kNoOutput);
KALDI_ASSERT(ClassifyWxfilename("a b c:") == kFileOutput);
KALDI_ASSERT(ClassifyWxfilename("a b c/3") == kFileOutput);
}
void UnitTestIoNew(bool binary) {
{
const char *filename = "tmpf";
Output ko(filename, binary);
std::ostream &outfile = ko.Stream();
if (!binary) outfile << "\t";
int64 i1 = Rand() % 10000;
WriteBasicType(outfile, binary, i1);
uint16 i2 = Rand() % 10000;
WriteBasicType(outfile, binary, i2);
if (!binary) outfile << "\t";
char c = Rand();
WriteBasicType(outfile, binary, c);
if (!binary && Rand()%2 == 0) outfile << " \n";
std::vector<int32> vec1;
WriteIntegerVector(outfile, binary, vec1);
if (!binary && Rand()%2 == 0) outfile << " \n";
std::vector<uint16> vec2;
for (size_t i = 0; i < 10; i++) vec2.push_back(Rand()%100 - 10);
WriteIntegerVector(outfile, binary, vec2);
if (!binary) outfile << " \n";
std::vector<char> vec3;
for (size_t i = 0; i < 10; i++) vec3.push_back(Rand()%100);
WriteIntegerVector(outfile, binary, vec3);
if (!binary && Rand()%2 == 0) outfile << " \n";
const char *token1 = "Hi";
WriteToken(outfile, binary, token1);
if (!binary) outfile << " \n";
std::string token2 = "There.";
WriteToken(outfile, binary, token2);
if (!binary && Rand()%2 == 0) outfile << " \n";
std::string token3 = "You.";
WriteToken(outfile, binary, token3);
if (!binary && Rand()%2 == 0) outfile << " ";
float f1 = RandUniform();
WriteBasicType(outfile, binary, f1);
if (!binary && Rand()%2 == 0) outfile << "\t";
float f2 = RandUniform();
WriteBasicType(outfile, binary, f2);
double d1 = RandUniform();
WriteBasicType(outfile, binary, d1);
if (!binary && Rand()%2 == 0) outfile << "\t";
double d2 = RandUniform();
WriteBasicType(outfile, binary, d2);
if (!binary && Rand()%2 == 0) outfile << "\t";
ko.Close();
{
bool binary_in;
Input ki(filename, &binary_in);
std::istream &infile = ki.Stream();
int64 i1_in;
ReadBasicType(infile, binary_in, &i1_in);
KALDI_ASSERT(i1_in == i1);
uint16 i2_in;
ReadBasicType(infile, binary_in, &i2_in);
KALDI_ASSERT(i2_in == i2);
char c_in;
ReadBasicType(infile, binary_in, &c_in);
KALDI_ASSERT(c_in == c);
std::vector<int32> vec1_in;
ReadIntegerVector(infile, binary_in, &vec1_in);
KALDI_ASSERT(vec1_in == vec1);
std::vector<uint16> vec2_in;
ReadIntegerVector(infile, binary_in, &vec2_in);
KALDI_ASSERT(vec2_in == vec2);
std::vector<char> vec3_in;
ReadIntegerVector(infile, binary_in, &vec3_in);
KALDI_ASSERT(vec3_in == vec3);
std::string token1_in, token2_in;
KALDI_ASSERT(Peek(infile, binary_in) == static_cast<int>(*token1));
ReadToken(infile, binary_in, &token1_in);
KALDI_ASSERT(token1_in == (std::string)token1);
ReadToken(infile, binary_in, &token2_in);
KALDI_ASSERT(token2_in == token2);
if (Rand() % 2 == 0)
ExpectToken(infile, binary_in, token3.c_str());
else
ExpectToken(infile, binary_in, token3);
float f1_in; // same type.
ReadBasicType(infile, binary_in, &f1_in);
AssertEqual(f1_in, f1);
double f2_in; // wrong type.
ReadBasicType(infile, binary_in, &f2_in);
AssertEqual(f2_in, f2);
double d1_in; // same type.
ReadBasicType(infile, binary_in, &d1_in);
AssertEqual(d1_in, d1);
float d2_in; // wrong type.
ReadBasicType(infile, binary_in, &d2_in);
AssertEqual(d2_in, d2);
KALDI_ASSERT(Peek(infile, binary_in) == -1);
}
unlink(filename);
}
}
void UnitTestIoPipe(bool binary) {
// This is as UnitTestIoNew except with different filenames.
{
#if defined(_MSC_VER) && !defined(KALDI_CYGWIN_COMPAT)
// self-invocation on Windows that emulates cat(1)
const char *filename_out = "|kaldi-io-test cat > tmpf.gz",
*filename_in = "kaldi-io-test cat tmpf.gz|";
#else
const char *filename_out = "|gzip -c > tmpf.gz",
*filename_in = "gunzip -c tmpf.gz |";
#endif
Output ko(filename_out, binary);
std::ostream &outfile = ko.Stream();
if (!binary) outfile << "\t";
int64 i1 = Rand() % 10000;
WriteBasicType(outfile, binary, i1);
uint16 i2 = Rand() % 10000;
WriteBasicType(outfile, binary, i2);
if (!binary) outfile << "\t";
char c = Rand();
WriteBasicType(outfile, binary, c);
if (!binary && Rand()%2 == 0) outfile << " \n";
std::vector<int32> vec1;
WriteIntegerVector(outfile, binary, vec1);
if (!binary && Rand()%2 == 0) outfile << " \n";
std::vector<uint16> vec2;
for (size_t i = 0; i < 10; i++) vec2.push_back(Rand()%100 - 10);
WriteIntegerVector(outfile, binary, vec2);
if (!binary) outfile << " \n";
WriteToken(outfile, binary, "<foo>");
std::vector<char> vec3;
for (size_t i = 0; i < 10; i++) vec3.push_back(Rand()%100);
WriteIntegerVector(outfile, binary, vec3);
if (!binary && Rand()%2 == 0) outfile << " \n";
const char *token1 = "Hi";
WriteToken(outfile, binary, token1);
if (!binary) outfile << " \n";
std::string token2 = "There.";
WriteToken(outfile, binary, token2);
if (!binary && Rand()%2 == 0) outfile << " \n";
std::string token3 = "You.";
WriteToken(outfile, binary, token3);
if (!binary && Rand()%2 == 0) outfile << " ";
float f1 = RandUniform();
WriteBasicType(outfile, binary, f1);
if (!binary && Rand()%2 == 0) outfile << "\t";
float f2 = RandUniform();
WriteBasicType(outfile, binary, f2);
double d1 = RandUniform();
WriteBasicType(outfile, binary, d1);
if (!binary && Rand()%2 == 0) outfile << "\t";
double d2 = RandUniform();
WriteBasicType(outfile, binary, d2);
if (!binary && Rand()%2 == 0) outfile << "\t";
bool ans = ko.Close();
KALDI_ASSERT(ans);
#ifndef _MSC_VER
Sleep(1); // This test does not work without this sleep:
// seems to be some kind of file-system latency.
#endif
{
bool binary_in;
Input ki(filename_in, &binary_in);
std::istream &infile = ki.Stream();
int64 i1_in;
ReadBasicType(infile, binary_in, &i1_in);
KALDI_ASSERT(i1_in == i1);
uint16 i2_in;
ReadBasicType(infile, binary_in, &i2_in);
KALDI_ASSERT(i2_in == i2);
char c_in;
ReadBasicType(infile, binary_in, &c_in);
KALDI_ASSERT(c_in == c);
std::vector<int32> vec1_in;
ReadIntegerVector(infile, binary_in, &vec1_in);
KALDI_ASSERT(vec1_in == vec1);
std::vector<uint16> vec2_in;
ReadIntegerVector(infile, binary_in, &vec2_in);
KALDI_ASSERT(vec2_in == vec2);
std::vector<char> vec3_in;
KALDI_ASSERT(PeekToken(infile, binary_in) == static_cast<int>('f'));
ExpectToken(infile, binary_in, "<foo>");
ReadIntegerVector(infile, binary_in, &vec3_in);
KALDI_ASSERT(vec3_in == vec3);
std::string token1_in, token2_in;
KALDI_ASSERT(Peek(infile, binary_in) == static_cast<int>(*token1));
ReadToken(infile, binary_in, &token1_in);
KALDI_ASSERT(token1_in == (std::string)token1);
ReadToken(infile, binary_in, &token2_in);
KALDI_ASSERT(token2_in == token2);
if (Rand() % 2 == 0)
ExpectToken(infile, binary_in, token3.c_str());
else
ExpectToken(infile, binary_in, token3);
float f1_in; // same type.
ReadBasicType(infile, binary_in, &f1_in);
AssertEqual(f1_in, f1);
double f2_in; // wrong type.
ReadBasicType(infile, binary_in, &f2_in);
AssertEqual(f2_in, f2);
double d1_in; // same type.
ReadBasicType(infile, binary_in, &d1_in);
AssertEqual(d1_in, d1);
float d2_in; // wrong type.
ReadBasicType(infile, binary_in, &d2_in);
AssertEqual(d2_in, d2);
KALDI_ASSERT(Peek(infile, binary_in) == -1);
}
}
unlink("tmpf.txt");
unlink("tmpf.gz");
}
void UnitTestIoStandard() {
/*
Don't do the the following part because it requires
to pipe from an empty file, for it to not hang.
{
Input inp("", NULL); // standard input.
KALDI_ASSERT(inp.Stream().get() == -1);
}
{
Input inp("-", NULL); // standard input.
KALDI_ASSERT(inp.Stream().get() == -1);
}*/
{
std::cout << "Should see: foo\n";
Output out("", false);
out.Stream() << "foo\n";
}
{
std::cout << "Should see: bar\n";
Output out("-", false);
out.Stream() << "bar\n";
}
}
// This is Windows-specific.
void UnitTestNativeFilename() {
#ifdef KALDI_CYGWIN_COMPAT
extern std::string MapCygwinPath(const std::string &filename);
KALDI_ASSERT(MapCygwinPath("") == "");
KALDI_ASSERT(MapCygwinPath(".") == ".");
KALDI_ASSERT(MapCygwinPath("..") == "..");
KALDI_ASSERT(MapCygwinPath("/dev/null")[0] != '/');
KALDI_ASSERT(MapCygwinPath("/tmp")[1] == ':');
KALDI_ASSERT(MapCygwinPath("/tmp/")[1] == ':');
KALDI_ASSERT(MapCygwinPath("/tmp/foo")[1] == ':');
KALDI_ASSERT(MapCygwinPath("/cygdrive/c") == "c:/");
KALDI_ASSERT(MapCygwinPath("/cygdrive/c/") == "c:/");
KALDI_ASSERT(MapCygwinPath("/cygdrive/c/foo") == "c:/foo");
#endif
}
} // end namespace kaldi.
#if defined(_MSC_VER) && !defined(KALDI_CYGWIN_COMPAT)
// Windows has no cat! There is probably no suitable tool to test popen I/O on
// Windows, so we emulate a lame version of cat(1).
static int TinyCat(int argc, const char** argv) {
const char* name_in = argc > 0 && strcmp(argv[0], "-") ? argv[0] : NULL;
int fd_in = name_in ? _open(name_in, _O_RDONLY) : _fileno(stdin);
if (fd_in < 0)
return 1;
int fd_out = _fileno(stdout);
_setmode(fd_in, _O_BINARY);
_setmode(fd_out, _O_BINARY);
char buffer[100];
int last_read;
while ((last_read = _read(fd_in, buffer, sizeof(buffer))) > 0)
_write(fd_out, buffer, last_read);
if (name_in) _close(fd_in);
return 0;
}
#endif
int main(int argc, const char** argv) {
using namespace kaldi;
#if defined(_MSC_VER) && !defined(KALDI_CYGWIN_COMPAT)
if (argc > 1 && strcmp(argv[1], "cat") == 0)
return TinyCat(argc - 2, argv + 2);
#endif
UnitTestNativeFilename();
UnitTestIoNew(false);
UnitTestIoNew(true);
UnitTestIoPipe(true);
UnitTestIoPipe(false);
UnitTestIoStandard();
UnitTestClassifyRxfilename();
UnitTestClassifyWxfilename();
KALDI_ASSERT(1); // just wanted to check that KALDI_ASSERT does not fail
// for 1.
return 0;
}
+880
View File
@@ -0,0 +1,880 @@
// util/kaldi-io.cc
// Copyright 2009-2011 Microsoft Corporation; Jan Silovsky
// 2016 Xiaohui Zhang
// See ../../COPYING for clarification regarding multiple authors
//
// 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
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#include "util/kaldi-io.h"
#include <errno.h>
#include <cstdlib>
#include "base/kaldi-math.h"
#include "util/text-utils.h"
#include "util/parse-options.h"
#include "util/kaldi-pipebuf.h"
#include <stdio.h>
#include <stdlib.h>
#ifdef KALDI_CYGWIN_COMPAT
#include "util/kaldi-cygwin-io-inl.h"
#define MapOsPath(x) MapCygwinPath(x)
#else // KALDI_CYGWIN_COMPAT
#define MapOsPath(x) x
#endif // KALDI_CYGWIN_COMPAT
#if defined(_MSC_VER)
static FILE *popen(const char* command, const char* mode) {
#ifdef KALDI_CYGWIN_COMPAT
return kaldi::CygwinCompatPopen(command, mode);
#else // KALDI_CYGWIN_COMPAT
return _popen(command, mode);
#endif // KALDI_CYGWIN_COMPAT
}
#endif // _MSC_VER
namespace kaldi {
#ifndef _MSC_VER // on VS, we don't need this type.
// could replace basic_pipebuf<char> with stdio_filebuf<char> on some platforms.
// Would mean we could use less of our own code.
typedef basic_pipebuf<char> PipebufType;
#endif
}
namespace kaldi {
std::string PrintableRxfilename(const std::string &rxfilename) {
if (rxfilename == "" || rxfilename == "-") {
return "standard input";
} else {
// If this call to Escape later causes compilation issues,
// just replace it with "return rxfilename"; it's only a
// pretty-printing issue.
return ParseOptions::Escape(rxfilename);
}
}
std::string PrintableWxfilename(const std::string &wxfilename) {
if (wxfilename == "" || wxfilename == "-") {
return "standard output";
} else {
// If this call to Escape later causes compilation issues,
// just replace it with "return wxfilename"; it's only a
// pretty-printing issue.
return ParseOptions::Escape(wxfilename);
}
}
OutputType ClassifyWxfilename(const std::string &filename) {
const char *c = filename.c_str();
size_t length = filename.length();
char first_char = c[0],
last_char = (length == 0 ? '\0' : c[filename.length()-1]);
// if 'filename' is "" or "-", return kStandardOutput.
if (length == 0 || (length == 1 && first_char == '-'))
return kStandardOutput;
else if (first_char == '|') return kPipeOutput; // An output pipe like "|blah".
else if (isspace(first_char) || isspace(last_char) || last_char == '|') {
return kNoOutput; // Leading or trailing space: can't interpret this.
// Final '|' would represent an input pipe, not an
// output pipe.
//} else if ((first_char == 'a' || first_char == 's') &&
// strchr(c, ':') != NULL &&
// (ClassifyWspecifier(filename, NULL, NULL, NULL) != kNoWspecifier ||
// ClassifyRspecifier(filename, NULL, NULL) != kNoRspecifier)) {
// // e.g. ark:something or scp:something... this is almost certainly a
// // scripting error, so call it an error rather than treating it as a file.
// // In practice in modern kaldi scripts all (r,w)filenames begin with "ark"
// // or "scp", even though technically speaking options like "b", "t", "s" or
// // "cs" can appear before the ark or scp, like "b,ark". For efficiency,
// // and because this code is really just a nicety to catch errors earlier
// // than they would otherwise be caught, we only call those extra functions
// // for filenames beginning with 'a' or 's'.
// return kNoOutput;
} else if (isdigit(last_char)) {
// This could be a file, but we have to see if it's an offset into a file
// (like foo.ark:4314328), which is not allowed for writing (but is
// allowed for reaching). This eliminates some things which would be
// valid UNIX filenames but are not allowed by Kaldi. (Even if we allowed
// such filenames for writing, we woudln't be able to correctly read them).
const char *d = c + length - 1;
while (isdigit(*d) && d > c) d--;
if (*d == ':') return kNoOutput;
// else it could still be a filename; continue to the next check.
}
// At this point it matched no other pattern so we assume a filename, but we
// check for internal '|' as it's a common source of errors to have pipe
// commands without the pipe in the right place. Say that it can't be
// classified.
if (strchr(c, '|') != NULL) {
KALDI_WARN << "Trying to classify wxfilename with pipe symbol in the"
" wrong place (pipe without | at the beginning?): " <<
filename;
return kNoOutput;
}
return kFileOutput; // It matched no other pattern: assume it's a filename.
}
InputType ClassifyRxfilename(const std::string &filename) {
const char *c = filename.c_str();
size_t length = filename.length();
char first_char = c[0],
last_char = (length == 0 ? '\0' : c[filename.length()-1]);
// if 'filename' is "" or "-", return kStandardInput.
if (length == 0 || (length == 1 && first_char == '-')) {
return kStandardInput;
} else if (first_char == '|') {
return kNoInput; // An output pipe like "|blah": not
// valid for input.
} else if (last_char == '|') {
return kPipeInput;
} else if (isspace(first_char) || isspace(last_char)) {
return kNoInput; // We don't allow leading or trailing space in a filename.
//} else if ((first_char == 'a' || first_char == 's') &&
// strchr(c, ':') != NULL &&
// (ClassifyWspecifier(filename, NULL, NULL, NULL) != kNoWspecifier ||
// ClassifyRspecifier(filename, NULL, NULL) != kNoRspecifier)) {
// // e.g. ark:something or scp:something... this is almost certainly a
// // scripting error, so call it an error rather than treating it as a file.
// // In practice in modern kaldi scripts all (r,w)filenames begin with "ark"
// // or "scp", even though technically speaking options like "b", "t", "s" or
// // "cs" can appear before the ark or scp, like "b,ark". For efficiency,
// // and because this code is really just a nicety to catch errors earlier
// // than they would otherwise be caught, we only call those extra functions
// // for filenames beginning with 'a' or 's'.
// return kNoInput;
} else if (isdigit(last_char)) {
const char *d = c + length - 1;
while (isdigit(*d) && d > c) d--;
if (*d == ':') return kOffsetFileInput; // Filename is like
// some_file:12345
// otherwise it could still be a filename; continue to the next check.
}
// At this point it matched no other pattern so we assume a filename, but
// we check for '|' as it's a common source of errors to have pipe
// commands without the pipe in the right place. Say that it can't be
// classified in this case.
if (strchr(c, '|') != NULL) {
KALDI_WARN << "Trying to classify rxfilename with pipe symbol in the"
" wrong place (pipe without | at the end?): " << filename;
return kNoInput;
}
return kFileInput; // It matched no other pattern: assume it's a filename.
}
class OutputImplBase {
public:
// Open will open it as a file (no header), and return true
// on success. It cannot be called on an already open stream.
virtual bool Open(const std::string &filename, bool binary) = 0;
virtual std::ostream &Stream() = 0;
virtual bool Close() = 0;
virtual ~OutputImplBase() { }
};
class FileOutputImpl: public OutputImplBase {
public:
virtual bool Open(const std::string &filename, bool binary) {
if (os_.is_open()) KALDI_ERR << "FileOutputImpl::Open(), "
<< "open called on already open file.";
filename_ = filename;
os_.open(MapOsPath(filename_).c_str(),
binary ? std::ios_base::out | std::ios_base::binary
: std::ios_base::out);
return os_.is_open();
}
virtual std::ostream &Stream() {
if (!os_.is_open())
KALDI_ERR << "FileOutputImpl::Stream(), file is not open.";
// I believe this error can only arise from coding error.
return os_;
}
virtual bool Close() {
if (!os_.is_open())
KALDI_ERR << "FileOutputImpl::Close(), file is not open.";
// I believe this error can only arise from coding error.
os_.close();
return !(os_.fail());
}
virtual ~FileOutputImpl() {
if (os_.is_open()) {
os_.close();
if (os_.fail())
KALDI_ERR << "Error closing output file " << filename_;
}
}
private:
std::string filename_;
std::ofstream os_;
};
class StandardOutputImpl: public OutputImplBase {
public:
StandardOutputImpl(): is_open_(false) { }
virtual bool Open(const std::string &filename, bool binary) {
if (is_open_) KALDI_ERR << "StandardOutputImpl::Open(), "
"open called on already open file.";
#ifdef _MSC_VER
_setmode(_fileno(stdout), binary ? _O_BINARY : _O_TEXT);
#endif
is_open_ = std::cout.good();
return is_open_;
}
virtual std::ostream &Stream() {
if (!is_open_)
KALDI_ERR << "StandardOutputImpl::Stream(), object not initialized.";
// I believe this error can only arise from coding error.
return std::cout;
}
virtual bool Close() {
if (!is_open_)
KALDI_ERR << "StandardOutputImpl::Close(), file is not open.";
is_open_ = false;
std::cout << std::flush;
return !(std::cout.fail());
}
virtual ~StandardOutputImpl() {
if (is_open_) {
std::cout << std::flush;
if (std::cout.fail())
KALDI_ERR << "Error writing to standard output";
}
}
private:
bool is_open_;
};
class PipeOutputImpl: public OutputImplBase {
public:
PipeOutputImpl(): f_(NULL), os_(NULL) { }
virtual bool Open(const std::string &wxfilename, bool binary) {
filename_ = wxfilename;
KALDI_ASSERT(f_ == NULL); // Make sure closed.
KALDI_ASSERT(wxfilename.length() != 0 && wxfilename[0] == '|'); // should
// start with '|'
std::string cmd_name(wxfilename, 1);
#if defined(_MSC_VER) || defined(__CYGWIN__)
f_ = popen(cmd_name.c_str(), (binary ? "wb" : "w"));
#else
f_ = popen(cmd_name.c_str(), "w");
#endif
if (!f_) { // Failure.
KALDI_WARN << "Failed opening pipe for writing, command is: "
<< cmd_name << ", errno is " << strerror(errno);
return false;
} else {
#ifndef _MSC_VER
fb_ = new PipebufType(f_, // Using this constructor won't make the
// destructor try to close the stream when
// we're done.
(binary ? std::ios_base::out|
std::ios_base::binary
:std::ios_base::out));
KALDI_ASSERT(fb_ != NULL); // or would be alloc error.
os_ = new std::ostream(fb_);
#else
os_ = new std::ofstream(f_);
#endif
return os_->good();
}
}
virtual std::ostream &Stream() {
if (os_ == NULL) KALDI_ERR << "PipeOutputImpl::Stream(),"
" object not initialized.";
// I believe this error can only arise from coding error.
return *os_;
}
virtual bool Close() {
if (os_ == NULL) KALDI_ERR << "PipeOutputImpl::Close(), file is not open.";
bool ok = true;
os_->flush();
if (os_->fail()) ok = false;
delete os_;
os_ = NULL;
int status;
#ifdef _MSC_VER
status = _pclose(f_);
#else
status = pclose(f_);
#endif
if (status)
KALDI_WARN << "Pipe " << filename_ << " had nonzero return status "
<< status;
f_ = NULL;
#ifndef _MSC_VER
delete fb_;
fb_ = NULL;
#endif
return ok;
}
virtual ~PipeOutputImpl() {
if (os_) {
if (!Close())
KALDI_ERR << "Error writing to pipe " << PrintableWxfilename(filename_);
}
}
private:
std::string filename_;
FILE *f_;
#ifndef _MSC_VER
PipebufType *fb_;
#endif
std::ostream *os_;
};
class InputImplBase {
public:
// Open will open it as a file, and return true on success.
// May be called twice only for kOffsetFileInput (otherwise,
// if called twice, we just create a new Input object, to avoid
// having to deal with the extra hassle of reopening with the
// same object.
// Note that we will to call Open with true (binary) for
// for text-mode Kaldi files; the only actual text-mode input
// is for non-Kaldi files.
virtual bool Open(const std::string &filename, bool binary) = 0;
virtual std::istream &Stream() = 0;
virtual int32 Close() = 0; // We only need to check failure in the case of
// kPipeInput.
// on close for input streams.
virtual InputType MyType() = 0; // Because if it's kOffsetFileInput, we may
// call Open twice
// (has efficiency benefits).
virtual ~InputImplBase() { }
};
class FileInputImpl: public InputImplBase {
public:
virtual bool Open(const std::string &filename, bool binary) {
if (is_.is_open()) KALDI_ERR << "FileInputImpl::Open(), "
<< "open called on already open file.";
is_.open(MapOsPath(filename).c_str(),
binary ? std::ios_base::in | std::ios_base::binary
: std::ios_base::in);
return is_.is_open();
}
virtual std::istream &Stream() {
if (!is_.is_open())
KALDI_ERR << "FileInputImpl::Stream(), file is not open.";
// I believe this error can only arise from coding error.
return is_;
}
virtual int32 Close() {
if (!is_.is_open())
KALDI_ERR << "FileInputImpl::Close(), file is not open.";
// I believe this error can only arise from coding error.
is_.close();
// Don't check status.
return 0;
}
virtual InputType MyType() { return kFileInput; }
virtual ~FileInputImpl() {
// Stream will automatically be closed, and we don't care about
// whether it fails.
}
private:
std::ifstream is_;
};
class StandardInputImpl: public InputImplBase {
public:
StandardInputImpl(): is_open_(false) { }
virtual bool Open(const std::string &filename, bool binary) {
if (is_open_) KALDI_ERR << "StandardInputImpl::Open(), "
"open called on already open file.";
is_open_ = true;
#ifdef _MSC_VER
_setmode(_fileno(stdin), binary ? _O_BINARY : _O_TEXT);
#endif
return true; // Don't check good() because would be false if
// eof, which may be valid input.
}
virtual std::istream &Stream() {
if (!is_open_)
KALDI_ERR << "StandardInputImpl::Stream(), object not initialized.";
// I believe this error can only arise from coding error.
return std::cin;
}
virtual InputType MyType() { return kStandardInput; }
virtual int32 Close() {
if (!is_open_) KALDI_ERR << "StandardInputImpl::Close(), file is not open.";
is_open_ = false;
return 0;
}
virtual ~StandardInputImpl() { }
private:
bool is_open_;
};
class PipeInputImpl: public InputImplBase {
public:
PipeInputImpl(): f_(NULL), is_(NULL) { }
virtual bool Open(const std::string &rxfilename, bool binary) {
filename_ = rxfilename;
KALDI_ASSERT(f_ == NULL); // Make sure closed.
KALDI_ASSERT(rxfilename.length() != 0 &&
rxfilename[rxfilename.length()-1] == '|'); // should end with '|'
std::string cmd_name(rxfilename, 0, rxfilename.length()-1);
#if defined(_MSC_VER) || defined(__CYGWIN__)
f_ = popen(cmd_name.c_str(), (binary ? "rb" : "r"));
#else
f_ = popen(cmd_name.c_str(), "r");
#endif
if (!f_) { // Failure.
KALDI_WARN << "Failed opening pipe for reading, command is: "
<< cmd_name << ", errno is " << strerror(errno);
return false;
} else {
#ifndef _MSC_VER
fb_ = new PipebufType(f_, // Using this constructor won't lead the
// destructor to close the stream.
(binary ? std::ios_base::in|
std::ios_base::binary
:std::ios_base::in));
KALDI_ASSERT(fb_ != NULL); // or would be alloc error.
is_ = new std::istream(fb_);
#else
is_ = new std::ifstream(f_);
#endif
if (is_->fail() || is_->bad()) return false;
if (is_->eof()) {
KALDI_WARN << "Pipe opened with command "
<< PrintableRxfilename(rxfilename)
<< " is empty.";
// don't return false: empty may be valid.
}
return true;
}
}
virtual std::istream &Stream() {
if (is_ == NULL)
KALDI_ERR << "PipeInputImpl::Stream(), object not initialized.";
// I believe this error can only arise from coding error.
return *is_;
}
virtual int32 Close() {
if (is_ == NULL)
KALDI_ERR << "PipeInputImpl::Close(), file is not open.";
delete is_;
is_ = NULL;
int32 status;
#ifdef _MSC_VER
status = _pclose(f_);
#else
status = pclose(f_);
#endif
if (status)
KALDI_WARN << "Pipe " << filename_ << " had nonzero return status "
<< status;
f_ = NULL;
#ifndef _MSC_VER
delete fb_;
fb_ = NULL;
#endif
return status;
}
virtual ~PipeInputImpl() {
if (is_)
Close();
}
virtual InputType MyType() { return kPipeInput; }
private:
std::string filename_;
FILE *f_;
#ifndef _MSC_VER
PipebufType *fb_;
#endif
std::istream *is_;
};
/*
#else
// Just have an empty implementation of the pipe input that crashes if
// called.
class PipeInputImpl: public InputImplBase {
public:
PipeInputImpl() { KALDI_ASSERT(0 && "Pipe input not yet supported on this
platform."); }
virtual bool Open(const std::string, bool) { return 0; }
virtual std::istream &Stream() const { return NULL; }
virtual void Close() {}
virtual InputType MyType() { return kPipeInput; }
};
#endif
*/
class OffsetFileInputImpl: public InputImplBase {
// This class is a bit more complicated than the
public:
// splits a filename like /my/file:123 into /my/file and the
// number 123. Crashes if not this format.
static void SplitFilename(const std::string &rxfilename,
std::string *filename,
size_t *offset) {
size_t pos = rxfilename.find_last_of(':');
KALDI_ASSERT(pos != std::string::npos); // would indicate error in calling
// code, as the filename is supposed to be of the correct form at this
// point.
*filename = std::string(rxfilename, 0, pos);
std::string number(rxfilename, pos+1);
bool ans = ConvertStringToInteger(number, offset);
if (!ans)
KALDI_ERR << "Cannot get offset from filename " << rxfilename
<< " (possibly you compiled in 32-bit and have a >32-bit"
<< " byte offset into a file; you'll have to compile 64-bit.";
}
bool Seek(size_t offset) {
size_t cur_pos = is_.tellg();
if (cur_pos == offset) return true;
else if (cur_pos<offset && cur_pos+100 > offset) {
// We're close enough that it may be faster to just
// read that data, rather than seek.
for (size_t i = cur_pos; i < offset; i++)
is_.get();
return (is_.tellg() == std::streampos(offset));
}
// Try to actually seek.
is_.seekg(offset, std::ios_base::beg);
if (is_.fail()) { // failbit or badbit is set [error happened]
is_.close();
return false; // failure.
} else {
is_.clear(); // Clear any failure bits (e.g. eof).
return true; // success.
}
}
// This Open routine is unusual in that it is designed to work even
// if it was already open. This for efficiency when seeking multiple
// times.
virtual bool Open(const std::string &rxfilename, bool binary) {
if (is_.is_open()) {
// We are opening when we have an already-open file.
// We may have to seek within this file, or else close it and
// open a different one.
std::string tmp_filename;
size_t offset;
SplitFilename(rxfilename, &tmp_filename, &offset);
if (tmp_filename == filename_ && binary == binary_) { // Just seek
is_.clear(); // clear fail bit, etc.
return Seek(offset);
} else {
is_.close(); // don't bother checking error status of is_.
filename_ = tmp_filename;
is_.open(MapOsPath(filename_).c_str(),
binary ? std::ios_base::in | std::ios_base::binary
: std::ios_base::in);
if (!is_.is_open()) return false;
else
return Seek(offset);
}
} else {
size_t offset;
SplitFilename(rxfilename, &filename_, &offset);
binary_ = binary;
is_.open(MapOsPath(filename_).c_str(),
binary ? std::ios_base::in | std::ios_base::binary
: std::ios_base::in);
if (!is_.is_open()) return false;
else
return Seek(offset);
}
}
virtual std::istream &Stream() {
if (!is_.is_open())
KALDI_ERR << "FileInputImpl::Stream(), file is not open.";
// I believe this error can only arise from coding error.
return is_;
}
virtual int32 Close() {
if (!is_.is_open())
KALDI_ERR << "FileInputImpl::Close(), file is not open.";
// I believe this error can only arise from coding error.
is_.close();
// Don't check status.
return 0;
}
virtual InputType MyType() { return kOffsetFileInput; }
virtual ~OffsetFileInputImpl() {
// Stream will automatically be closed, and we don't care about
// whether it fails.
}
private:
std::string filename_; // the actual filename
bool binary_; // true if was opened in binary mode.
std::ifstream is_;
};
Output::Output(const std::string &wxfilename, bool binary,
bool write_header):impl_(NULL) {
if (!Open(wxfilename, binary, write_header)) {
if (impl_) {
delete impl_;
impl_ = NULL;
}
KALDI_ERR << "Error opening output stream " <<
PrintableWxfilename(wxfilename);
}
}
bool Output::Close() {
if (!impl_) {
return false; // error to call Close if not open.
} else {
bool ans = impl_->Close();
delete impl_;
impl_ = NULL;
return ans;
}
}
Output::~Output() {
if (impl_) {
bool ok = impl_->Close();
delete impl_;
impl_ = NULL;
if (!ok)
KALDI_ERR << "Error closing output file "
<< PrintableWxfilename(filename_)
<< (ClassifyWxfilename(filename_) == kFileOutput ?
" (disk full?)" : "");
}
}
std::ostream &Output::Stream() { // will throw if not open; else returns
// stream.
if (!impl_) KALDI_ERR << "Output::Stream() called but not open.";
return impl_->Stream();
}
bool Output::Open(const std::string &wxfn, bool binary, bool header) {
if (IsOpen()) {
if (!Close()) { // Throw here rather than return status, as it's an error
// about something else: if the user wanted to avoid the exception he/she
// could have called Close().
KALDI_ERR << "Output::Open(), failed to close output stream: "
<< PrintableWxfilename(filename_);
}
}
filename_ = wxfn;
OutputType type = ClassifyWxfilename(wxfn);
KALDI_ASSERT(impl_ == NULL);
if (type == kFileOutput) {
impl_ = new FileOutputImpl();
} else if (type == kStandardOutput) {
impl_ = new StandardOutputImpl();
} else if (type == kPipeOutput) {
impl_ = new PipeOutputImpl();
} else { // type == kNoOutput
KALDI_WARN << "Invalid output filename format "<<
PrintableWxfilename(wxfn);
return false;
}
if (!impl_->Open(wxfn, binary)) {
delete impl_;
impl_ = NULL;
return false; // failed to open.
} else { // successfully opened it.
if (header) {
InitKaldiOutputStream(impl_->Stream(), binary);
bool ok = impl_->Stream().good(); // still OK?
if (!ok) {
delete impl_;
impl_ = NULL;
return false;
}
return true;
} else {
return true;
}
}
}
Input::Input(const std::string &rxfilename, bool *binary): impl_(NULL) {
if (!Open(rxfilename, binary)) {
KALDI_ERR << "Error opening input stream "
<< PrintableRxfilename(rxfilename);
}
}
int32 Input::Close() {
if (impl_) {
int32 ans = impl_->Close();
delete impl_;
impl_ = NULL;
return ans;
} else {
return 0;
}
}
bool Input::OpenInternal(const std::string &rxfilename,
bool file_binary,
bool *contents_binary) {
InputType type = ClassifyRxfilename(rxfilename);
if (IsOpen()) {
// May have to close the stream first.
if (type == kOffsetFileInput && impl_->MyType() == kOffsetFileInput) {
// We want to use the same object to Open... this is in case
// the files are the same, so we can just seek.
if (!impl_->Open(rxfilename, file_binary)) { // true is binary mode--
// always open in binary.
delete impl_;
impl_ = NULL;
return false;
}
// read the binary header, if requested.
if (contents_binary != NULL)
return InitKaldiInputStream(impl_->Stream(), contents_binary);
else
return true;
} else {
Close();
// and fall through to code below which actually opens the file.
}
}
if (type == kFileInput) {
impl_ = new FileInputImpl();
} else if (type == kStandardInput) {
impl_ = new StandardInputImpl();
} else if (type == kPipeInput) {
impl_ = new PipeInputImpl();
} else if (type == kOffsetFileInput) {
impl_ = new OffsetFileInputImpl();
} else { // type == kNoInput
KALDI_WARN << "Invalid input filename format "<<
PrintableRxfilename(rxfilename);
return false;
}
if (!impl_->Open(rxfilename, file_binary)) { // true is binary mode--
// always read in binary.
delete impl_;
impl_ = NULL;
return false;
}
if (contents_binary != NULL)
return InitKaldiInputStream(impl_->Stream(), contents_binary);
else
return true;
}
Input::~Input() { if (impl_) Close(); }
std::istream &Input::Stream() {
if (!IsOpen()) KALDI_ERR << "Input::Stream(), not open.";
return impl_->Stream();
}
//template <> void ReadKaldiObject(const std::string &filename,
// Matrix<float> *m) {
// if (!filename.empty() && filename[filename.size() - 1] == ']') {
// // This filename seems to have a 'range'... like foo.ark:4312423[20:30].
// // (the bit in square brackets is the range).
// std::string rxfilename, range;
// if (!ExtractRangeSpecifier(filename, &rxfilename, &range)) {
// KALDI_ERR << "Could not make sense of possible range specifier in filename "
// << "while reading matrix: " << filename;
// }
// Matrix<float> temp;
// bool binary_in;
// Input ki(rxfilename, &binary_in);
// temp.Read(ki.Stream(), binary_in);
// if (!ExtractObjectRange(temp, range, m)) {
// KALDI_ERR << "Error extracting range of object: " << filename;
// }
// } else {
// // The normal case, there is no range.
// bool binary_in;
// Input ki(filename, &binary_in);
// m->Read(ki.Stream(), binary_in);
// }
//}
//
//template <> void ReadKaldiObject(const std::string &filename,
// Matrix<double> *m) {
// if (!filename.empty() && filename[filename.size() - 1] == ']') {
// // This filename seems to have a 'range'... like foo.ark:4312423[20:30].
// // (the bit in square brackets is the range).
// std::string rxfilename, range;
// if (!ExtractRangeSpecifier(filename, &rxfilename, &range)) {
// KALDI_ERR << "Could not make sense of possible range specifier in filename "
// << "while reading matrix: " << filename;
// }
// Matrix<double> temp;
// bool binary_in;
// Input ki(rxfilename, &binary_in);
// temp.Read(ki.Stream(), binary_in);
// if (!ExtractObjectRange(temp, range, m)) {
// KALDI_ERR << "Error extracting range of object: " << filename;
// }
// } else {
// // The normal case, there is no range.
// bool binary_in;
// Input ki(filename, &binary_in);
// m->Read(ki.Stream(), binary_in);
// }
//}
} // end namespace kaldi
+280
View File
@@ -0,0 +1,280 @@
// util/kaldi-io.h
// Copyright 2009-2011 Microsoft Corporation; Jan Silovsky
// 2016 Xiaohui Zhang
// See ../../COPYING for clarification regarding multiple authors
//
// 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
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#ifndef KALDI_UTIL_KALDI_IO_H_
#define KALDI_UTIL_KALDI_IO_H_
#ifdef _MSC_VER
# include <fcntl.h>
# include <io.h>
#endif
#include <cctype> // For isspace.
#include <limits>
#include <string>
#include "base/kaldi-common.h"
//#include "matrix/kaldi-matrix.h"
namespace kaldi {
class OutputImplBase; // Forward decl; defined in a .cc file
class InputImplBase; // Forward decl; defined in a .cc file
/// \addtogroup io_group
/// @{
// The Output and Input classes handle stream-opening for "extended" filenames
// that include actual files, standard-input/standard-output, pipes, and
// offsets into actual files. They also handle reading and writing the
// binary-mode headers for Kaldi files, where applicable. The classes have
// versions of the Open routines that throw and do not throw, depending whether
// the calling code wants to catch the errors or not; there are also versions
// that write (or do not write) the Kaldi binary-mode header that says if it's
// binary mode. Generally files that contain Kaldi objects will have the header
// on, so we know upon reading them whether they have the header. So you would
// use the OpenWithHeader routines for these (or the constructor); but other
// types of objects (e.g. FSTs) would have files without a header so you would
// use OpenNoHeader.
// We now document the types of extended filenames that we use.
//
// A "wxfilename" is an extended filename for writing. It can take three forms:
// (1) Filename: e.g. "/some/filename", "./a/b/c", "c:\Users\dpovey\My
// Documents\\boo"
// (whatever the actual file-system interprets)
// (2) Standard output: "" or "-"
// (3) A pipe: e.g. "| gzip -c > /tmp/abc.gz"
//
//
// A "rxfilename" is an extended filename for reading. It can take four forms:
// (1) An actual filename, whatever the file-system can read, e.g. "/my/file".
// (2) Standard input: "" or "-"
// (3) A pipe: e.g. "gunzip -c /tmp/abc.gz |"
// (4) An offset into a file, e.g.: "/mnt/blah/data/1.ark:24871"
// [these are created by the Table and TableWriter classes; I may also write
// a program that creates them for arbitrary files]
//
// Typical usage:
// ...
// bool binary;
// MyObject.Write(Output(some_filename, binary).Stream(), binary);
//
// ... more extensive example:
// {
// Output ko(some_filename, binary);
// MyObject1.Write(ko.Stream(), binary);
// MyObject2.Write(ko.Stream(), binary);
// }
enum OutputType {
kNoOutput,
kFileOutput,
kStandardOutput,
kPipeOutput
};
/// ClassifyWxfilename interprets filenames as follows:
/// - kNoOutput: invalid filenames (leading or trailing space, things that look
/// like wspecifiers and rspecifiers or like pipes to read from with leading
/// |.
/// - kFileOutput: Normal filenames
/// - kStandardOutput: The empty string or "-", interpreted as standard output
/// - kPipeOutput: pipes, e.g. "| gzip -c > /tmp/abc.gz"
OutputType ClassifyWxfilename(const std::string &wxfilename);
enum InputType {
kNoInput,
kFileInput,
kStandardInput,
kOffsetFileInput,
kPipeInput
};
/// ClassifyRxfilenames interprets filenames for reading as follows:
/// - kNoInput: invalid filenames (leading or trailing space, things that
/// look like wspecifiers and rspecifiers or pipes to write to
/// with trailing |.
/// - kFileInput: normal filenames
/// - kStandardInput: the empty string or "-"
/// - kPipeInput: e.g. "gunzip -c /tmp/abc.gz |"
/// - kOffsetFileInput: offsets into files, e.g. /some/filename:12970
InputType ClassifyRxfilename(const std::string &rxfilename);
class Output {
public:
// The normal constructor, provided for convenience.
// Equivalent to calling with default constructor then Open()
// with these arguments.
Output(const std::string &filename, bool binary, bool write_header = true);
Output(): impl_(NULL) {}
/// This opens the stream, with the given mode (binary or text). It returns
/// true on success and false on failure. However, it will throw if something
/// was already open and could not be closed (to avoid this, call Close()
/// first. if write_header == true and binary == true, it writes the Kaldi
/// binary-mode header ('\0' then 'B'). You may call Open even if it is
/// already open; it will close the existing stream and reopen (however if
/// closing the old stream failed it will throw).
bool Open(const std::string &wxfilename, bool binary, bool write_header);
inline bool IsOpen(); // return true if we have an open stream. Does not
// imply stream is good for writing.
std::ostream &Stream(); // will throw if not open; else returns stream.
// Close closes the stream. Calling Close is never necessary unless you
// want to avoid exceptions being thrown. There are times when calling
// Close will hurt efficiency (basically, when using offsets into files,
// and using the same Input object),
// but most of the time the user won't be doing this directly, it will
// be done in kaldi-table.{h, cc}, so you don't have to worry about it.
bool Close();
// This will throw if stream could not be closed (to check error status,
// call Close()).
~Output();
private:
OutputImplBase *impl_; // non-NULL if open.
std::string filename_;
KALDI_DISALLOW_COPY_AND_ASSIGN(Output);
};
// bool binary_in;
// Input ki(some_filename, &binary_in);
// MyObject.Read(ki.Stream(), binary_in);
//
// ... more extensive example:
//
// {
// bool binary_in;
// Input ki(some_filename, &binary_in);
// MyObject1.Read(ki.Stream(), &binary_in);
// MyObject2.Write(ki.Stream(), &binary_in);
// }
// Note that to catch errors you need to use try.. catch.
// Input communicates errors by throwing exceptions.
// Input interprets four kinds of filenames:
// (1) Normal filenames
// (2) The empty string or "-", interpreted as standard output
// (3) A pipe: e.g. "gunzip -c /tmp/abc.gz |"
// (4) Offsets into [real] files, e.g. "/my/filename:12049"
// The last one has no correspondence in Output.
class Input {
public:
/// The normal constructor. Opens the stream in binary mode.
/// Equivalent to calling the default constructor followed by Open(); then, if
/// binary != NULL, it calls ReadHeader(), putting the output in "binary"; it
/// throws on error.
Input(const std::string &rxfilename, bool *contents_binary = NULL);
Input(): impl_(NULL) {}
// Open opens the stream for reading (the mode, where relevant, is binary; use
// OpenTextMode for text-mode, we made this a separate function rather than a
// boolean argument, to avoid confusion with Kaldi's text/binary distinction,
// since reading in the file system's text mode is unusual.) If
// contents_binary != NULL, it reads the binary-mode header and puts it in the
// "binary" variable. Returns true on success. If it returns false it will
// not be open. You may call Open even if it is already open; it will close
// the existing stream and reopen (however if closing the old stream failed it
// will throw).
inline bool Open(const std::string &rxfilename, bool *contents_binary = NULL);
// As Open but (if the file system has text/binary modes) opens in text mode;
// you shouldn't ever have to use this as in Kaldi we read even text files in
// binary mode (and ignore the \r).
inline bool OpenTextMode(const std::string &rxfilename);
// Return true if currently open for reading and Stream() will
// succeed. Does not guarantee that the stream is good.
inline bool IsOpen();
// It is never necessary or helpful to call Close, except if
// you are concerned about to many filehandles being open.
// Close does not throw. It returns the exit code as int32
// in the case of a pipe [kPipeInput], and always zero otherwise.
int32 Close();
// Returns the underlying stream. Throws if !IsOpen()
std::istream &Stream();
// Destructor does not throw: input streams may legitimately fail so we
// don't worry about the status when we close them.
~Input();
private:
bool OpenInternal(const std::string &rxfilename, bool file_binary,
bool *contents_binary);
InputImplBase *impl_;
KALDI_DISALLOW_COPY_AND_ASSIGN(Input);
};
template <class C> void ReadKaldiObject(const std::string &filename,
C *c) {
bool binary_in;
Input ki(filename, &binary_in);
c->Read(ki.Stream(), binary_in);
}
// Specialize the template for reading matrices, because we want to be able to
// support reading 'ranges' (row and column ranges), like foo.mat[10:20].
//template <> void ReadKaldiObject(const std::string &filename,
// Matrix<float> *m);
//
//
//template <> void ReadKaldiObject(const std::string &filename,
// Matrix<double> *m);
template <class C> inline void WriteKaldiObject(const C &c,
const std::string &filename,
bool binary) {
Output ko(filename, binary);
c.Write(ko.Stream(), binary);
}
/// PrintableRxfilename turns the rxfilename into a more human-readable
/// form for error reporting, i.e. it does quoting and escaping and
/// replaces "" or "-" with "standard input".
std::string PrintableRxfilename(const std::string &rxfilename);
/// PrintableWxfilename turns the wxfilename into a more human-readable
/// form for error reporting, i.e. it does quoting and escaping and
/// replaces "" or "-" with "standard output".
std::string PrintableWxfilename(const std::string &wxfilename);
/// @}
} // end namespace kaldi.
#include "util/kaldi-io-inl.h"
#endif // KALDI_UTIL_KALDI_IO_H_
@@ -0,0 +1,87 @@
// util/kaldi-pipebuf.h
// Copyright 2009-2011 Ondrej Glembek
// See ../../COPYING for clarification regarding multiple authors
//
// 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
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
/** @file kaldi-pipebuf.h
* This is an Kaldi C++ Library header.
*/
#ifndef KALDI_UTIL_KALDI_PIPEBUF_H_
#define KALDI_UTIL_KALDI_PIPEBUF_H_
#include<string>
#if !defined(_LIBCPP_VERSION) // libc++
#include <fstream>
#else
#include "util/basic-filebuf.h"
#endif
namespace kaldi {
// This class provides a way to initialize a filebuf with a FILE* pointer
// directly; it will not close the file pointer when it is deleted.
// The C++ standard does not allow implementations of C++ to provide
// this constructor within basic_filebuf, which makes it hard to deal
// with pipes using completely native C++. This is a workaround
#ifdef _MSC_VER
#elif defined(_LIBCPP_VERSION) // libc++
template<class CharType, class Traits = std::char_traits<CharType> >
class basic_pipebuf : public basic_filebuf<CharType, Traits> {
public:
typedef basic_pipebuf<CharType, Traits> ThisType;
public:
basic_pipebuf(FILE *fptr, std::ios_base::openmode mode)
: basic_filebuf<CharType, Traits>() {
this->open(fptr, mode);
if (!this->is_open()) {
KALDI_WARN << "Error initializing pipebuf"; // probably indicates
// code error, if the fptr was good.
return;
}
}
}; // class basic_pipebuf
#else
template<class CharType, class Traits = std::char_traits<CharType> >
class basic_pipebuf : public std::basic_filebuf<CharType, Traits> {
public:
typedef basic_pipebuf<CharType, Traits> ThisType;
public:
basic_pipebuf(FILE *fptr, std::ios_base::openmode mode)
: std::basic_filebuf<CharType, Traits>() {
this->_M_file.sys_open(fptr, mode);
if (!this->is_open()) {
KALDI_WARN << "Error initializing pipebuf"; // probably indicates
// code error, if the fptr was good.
return;
}
this->_M_mode = mode;
this->_M_buf_size = BUFSIZ;
this->_M_allocate_internal_buffer();
this->_M_reading = false;
this->_M_writing = false;
this->_M_set_buffer(-1);
}
}; // class basic_pipebuf
#endif // _MSC_VER
} // namespace kaldi
#endif // KALDI_UTIL_KALDI_PIPEBUF_H_
@@ -0,0 +1,57 @@
// util/kaldi-semaphore.cc
// Copyright 2012 Karel Vesely (Brno University of Technology)
// 2017 Dogan Can (University of Southern California)
// See ../../COPYING for clarification regarding multiple authors
//
// 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
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#include "base/kaldi-error.h"
#include "util/kaldi-semaphore.h"
namespace kaldi {
Semaphore::Semaphore(int32 count) {
KALDI_ASSERT(count >= 0);
count_ = count;
}
Semaphore::~Semaphore() {}
bool Semaphore::TryWait() {
std::unique_lock<std::mutex> lock(mutex_);
if(count_) {
count_--;
return true;
}
return false;
}
void Semaphore::Wait() {
std::unique_lock<std::mutex> lock(mutex_);
while(!count_)
condition_variable_.wait(lock);
count_--;
}
void Semaphore::Signal() {
std::unique_lock<std::mutex> lock(mutex_);
count_++;
condition_variable_.notify_one();
}
} // namespace kaldi
@@ -0,0 +1,50 @@
// util/kaldi-semaphore.h
// Copyright 2012 Karel Vesely (Brno University of Technology)
// 2017 Dogan Can (University of Southern California)
// See ../../COPYING for clarification regarding multiple authors
//
// 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
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#ifndef KALDI_THREAD_KALDI_SEMAPHORE_H_
#define KALDI_THREAD_KALDI_SEMAPHORE_H_ 1
#include <mutex>
#include <condition_variable>
namespace kaldi {
class Semaphore {
public:
Semaphore(int32 count = 0);
~Semaphore();
bool TryWait(); ///< Returns true if Wait() goes through
void Wait(); ///< decrease the counter
void Signal(); ///< increase the counter
private:
int32 count_; ///< the semaphore counter, 0 means block on Wait()
std::mutex mutex_;
std::condition_variable condition_variable_;
KALDI_DISALLOW_COPY_AND_ASSIGN(Semaphore);
};
} //namespace
#endif // KALDI_THREAD_KALDI_SEMAPHORE_H_
@@ -0,0 +1,133 @@
// util/kaldi-thread-test.cc
// Copyright 2012 Johns Hopkins University (Author: Daniel Povey)
// Frantisek Skala
// 2017 University of Southern California (Author: Dogan Can)
// See ../../COPYING for clarification regarding multiple authors
//
// 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
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#include <algorithm>
#include "base/kaldi-common.h"
#include "util/kaldi-thread.h"
namespace kaldi {
// Sums up integers from 0 to max_to_count-1.
class MyThreadClass : public MultiThreadable {
public:
MyThreadClass(int32 max_to_count, int32 *i):
max_to_count_(max_to_count), iptr_(i), private_counter_(0) { }
// We are defining a copy constructor to ensure that whenever an instance of
// this class is copied, the default *copy* constructor for MultiThreadable
// is called instead the default constructor for MultiThreadable.
MyThreadClass(const MyThreadClass &other):
MultiThreadable(other),
max_to_count_(other.max_to_count_), iptr_(other.iptr_),
private_counter_(0) { }
void operator() () {
int32 block_size = (max_to_count_+ (num_threads_-1) ) / num_threads_;
int32 start = block_size * thread_id_,
end = std::min(max_to_count_, start + block_size);
for (int32 j = start; j < end; j++)
private_counter_ += j;
}
~MyThreadClass() {
*iptr_ += private_counter_;
}
private:
MyThreadClass() { } // Disallow empty constructor.
int32 max_to_count_;
int32 *iptr_;
int32 private_counter_;
};
void TestThreads() {
g_num_threads = 8;
// run method with temporary threads on 8 threads
// Note: uncomment following line for the possibility of simple benchmarking
// for(int i=0; i<100000; i++)
{
int32 max_to_count = 10000, tot = 0;
MyThreadClass c(max_to_count, &tot);
RunMultiThreaded(c);
KALDI_ASSERT(tot == (10000*(10000-1))/2);
}
g_num_threads = 1;
// let's try the same, but with only one thread
{
int32 max_to_count = 10000, tot = 0;
MyThreadClass c(max_to_count, &tot);
RunMultiThreaded(c);
KALDI_ASSERT(tot == (10000*(10000-1))/2);
}
}
class MyTaskClass { // spins for a while, then outputs a pre-given integer.
public:
MyTaskClass(int32 i, std::vector<int32> *vec):
done_(false), i_(i), vec_(vec) { }
void operator() () {
int32 spin = 1000000 * Rand() % 100;
for (int32 i = 0; i < spin; i++);
done_ = true;
}
~MyTaskClass() {
KALDI_ASSERT(done_);
vec_->push_back(i_);
}
private:
bool done_;
int32 i_;
std::vector<int32> *vec_;
};
void TestTaskSequencer() {
TaskSequencerConfig config;
config.num_threads = 1 + Rand() % 20;
if (Rand() % 2 == 1 )
config.num_threads_total = config.num_threads + Rand() % config.num_threads;
int32 num_tasks = Rand() % 100;
std::vector<int32> task_output;
{
TaskSequencer<MyTaskClass> sequencer(config);
for (int32 i = 0; i < num_tasks; i++) {
sequencer.Run(new MyTaskClass(i, &task_output));
}
} // and let "sequencer" be destroyed, which waits for the last threads.
KALDI_ASSERT(task_output.size() == static_cast<size_t>(num_tasks));
for (int32 i = 0; i < num_tasks; i++)
KALDI_ASSERT(task_output[i] == i);
}
} // end namespace kaldi.
int main() {
using namespace kaldi;
TestThreads();
for (int32 i = 0; i < 10; i++)
TestTaskSequencer();
}
@@ -0,0 +1,33 @@
// util/kaldi-thread.cc
// Copyright 2012 Johns Hopkins University (Author: Daniel Povey)
// Frantisek Skala
// See ../../COPYING for clarification regarding multiple authors
//
// 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
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#include "base/kaldi-common.h"
#include "util/kaldi-thread.h"
namespace kaldi {
int32 g_num_threads = 8; // Initialize this global variable.
MultiThreadable::~MultiThreadable() {
// default implementation does nothing
}
} // end namespace kaldi
@@ -0,0 +1,290 @@
// util/kaldi-thread.h
// Copyright 2012 Johns Hopkins University (Author: Daniel Povey)
// Frantisek Skala
// 2017 University of Southern California (Author: Dogan Can)
// See ../../COPYING for clarification regarding multiple authors
//
// 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
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#ifndef KALDI_THREAD_KALDI_THREAD_H_
#define KALDI_THREAD_KALDI_THREAD_H_ 1
#include <thread>
#include <algorithm>
#include "itf/options-itf.h"
#include "util/kaldi-semaphore.h"
// This header provides convenient mechanisms for parallelization.
//
// The class MultiThreader, and the function RunMultiThreaded provide a
// mechanism to run a specified number of jobs in parellel and wait for them
// all to finish. They accept objects of some class C that derives from the
// base class MultiThreadable. C needs to define the operator () that takes
// no arguments. See ExampleClass below.
//
// The class TaskSequencer addresses a different problem typically encountered
// in Kaldi command-line programs that process a sequence of items. The items
// to be processed are coming in. They are all of different sizes, e.g.
// utterances with different numbers of frames. We would like them to be
// processed in parallel to make good use of the threads available but they
// must be output in the same order they came in. Here, we again accept objects
// of some class C with an operator () that takes no arguments. C may also have
// a destructor with side effects (typically some kind of output).
// TaskSequencer is responsible for running the jobs in parallel. It has a
// function Run() that will accept a new object of class C; this will block
// until a thread is free, at which time it will spawn a thread that starts
// running the operator () of the object. When threads are finished running,
// the objects will be deleted. TaskSequencer guarantees that the destructors
// will be called sequentially (not in parallel) and in the same order the
// objects were given to the Run() function, so that it is safe for the
// destructor to have side effects such as outputting data.
// Note: the destructor of TaskSequencer will wait for any remaining jobs that
// are still running and will call the destructors.
namespace kaldi {
extern int32 g_num_threads; // Maximum number of threads (for programs that
// use threads, which is not many of them, e.g. the SGMM update program does.
// This is 8 by default. You can change this on the command line, where
// used, with --num-threads. Programs that think they will use threads
// should register it with their ParseOptions, as something like:
// po.Register("num-threads", &g_num_threads, "Number of threads to use.");
class MultiThreadable {
// To create a function object that does part of the job, inherit from this
// class, implement a copy constructor calling the default copy constructor
// of this base class (so that thread_id_ and num_threads_ are copied to new
// instances), and finally implement the operator() that does part of the job
// based on thread_id_ and num_threads_ variables.
// Note: example implementations are in util/kaldi-thread-test.cc
public:
virtual void operator() () = 0;
// Does the main function of the class
// Subclasses have to redefine this
virtual ~MultiThreadable();
// Optional destructor. Note: the destructor of the object passed by the user
// will also be called, so watch out.
public:
// Do not redeclare thread_id_ and num_threads_ in derived classes.
int32 thread_id_; // 0 <= thread_id_ < num_threads_
int32 num_threads_;
private:
// Have additional member variables as needed.
};
class ExampleClass: public MultiThreadable {
public:
ExampleClass(int32 *foo); // Typically there will be an initializer that
// takes arguments.
ExampleClass(const ExampleClass &other); // A copy constructor is also needed;
// some example classes use the default version of this.
void operator() () {
// Does the main function of the class. This
// function will typically want to look at the values of the
// member variables thread_id_ and num_threads_, inherited
// from MultiThreadable.
}
~ExampleClass() {
// Optional destructor. Sometimes useful things happen here,
// for example summing up of certain quantities. See code
// that uses RunMultiThreaded for examples.
}
private:
// Have additional member variables as needed.
};
template<class C>
class MultiThreader {
public:
MultiThreader(int32 num_threads, const C &c_in) :
threads_(std::max<int32>(1, num_threads)),
cvec_(std::max<int32>(1, num_threads), c_in) {
if (num_threads == 0) {
// This is a special case with num_threads == 0, which behaves like with
// num_threads == 1 but without creating extra threads. This can be
// useful in GPU computations where threads cannot be used.
cvec_[0].thread_id_ = 0;
cvec_[0].num_threads_ = 1;
(cvec_[0])();
} else {
for (int32 i = 0; i < threads_.size(); i++) {
cvec_[i].thread_id_ = i;
cvec_[i].num_threads_ = threads_.size();
threads_[i] = std::thread(std::ref(cvec_[i]));
}
}
}
~MultiThreader() {
for (size_t i = 0; i < threads_.size(); i++)
if (threads_[i].joinable())
threads_[i].join();
}
private:
std::vector<std::thread> threads_;
std::vector<C> cvec_;
};
/// Here, class C should inherit from MultiThreadable. Note: if you want to
/// control the number of threads yourself, or need to do something in the main
/// thread of the program while the objects exist, just initialize the
/// MultiThreader<C> object yourself.
template<class C> void RunMultiThreaded(const C &c_in) {
MultiThreader<C> m(g_num_threads, c_in);
}
struct TaskSequencerConfig {
int32 num_threads;
int32 num_threads_total;
TaskSequencerConfig(): num_threads(1), num_threads_total(0) { }
void Register(OptionsItf *opts) {
opts->Register("num-threads", &num_threads, "Number of actively processing "
"threads to run in parallel");
opts->Register("num-threads-total", &num_threads_total, "Total number of "
"threads, including those that are waiting on other threads "
"to produce their output. Controls memory use. If <= 0, "
"defaults to --num-threads plus 20. Otherwise, must "
"be >= num-threads.");
}
};
// C should have an operator () taking no arguments, that does some kind
// of computation, and a destructor that produces some kind of output (the
// destructors will be run sequentially in the same order Run as called.
template<class C>
class TaskSequencer {
public:
TaskSequencer(const TaskSequencerConfig &config):
num_threads_(config.num_threads),
threads_avail_(config.num_threads),
tot_threads_avail_(config.num_threads_total > 0 ? config.num_threads_total :
config.num_threads + 20),
thread_list_(NULL) {
KALDI_ASSERT((config.num_threads_total <= 0 ||
config.num_threads_total >= config.num_threads) &&
"num-threads-total, if specified, must be >= num-threads");
}
/// This function takes ownership of the pointer "c", and will delete it
/// in the same sequence as Run was called on the jobs.
void Run(C *c) {
// run in main thread
if (num_threads_ == 0) {
(*c)();
delete c;
return;
}
threads_avail_.Wait(); // wait till we have a thread for computation free.
tot_threads_avail_.Wait(); // this ensures we don't have too many threads
// waiting on I/O, and consume too much memory.
// put the new RunTaskArgsList object at head of the singly
// linked list thread_list_.
thread_list_ = new RunTaskArgsList(this, c, thread_list_);
thread_list_->thread = std::thread(TaskSequencer<C>::RunTask,
thread_list_);
}
void Wait() { // You call this at the end if it's more convenient
// than waiting for the destructor. It waits for all tasks to finish.
if (thread_list_ != NULL) {
while (!thread_list_->thread.joinable()) {
Sleep(1);
}
thread_list_->thread.join();
KALDI_ASSERT(thread_list_->tail == NULL); // thread would not
// have exited without setting tail to NULL.
delete thread_list_;
thread_list_ = NULL;
}
}
/// The destructor waits for the last thread to exit.
~TaskSequencer() {
Wait();
}
private:
struct RunTaskArgsList {
TaskSequencer *me; // Think of this as a "this" pointer.
C *c; // Clist element of the task we're expected
std::thread thread;
RunTaskArgsList *tail;
RunTaskArgsList(TaskSequencer *me, C *c, RunTaskArgsList *tail):
me(me), c(c), tail(tail) {}
};
// This static function gets run in the threads that we create.
static void RunTask(RunTaskArgsList *args) {
// (1) run the job.
(*(args->c))(); // call operator () on args->c, which does the computation.
args->me->threads_avail_.Signal(); // Signal that the compute-intensive
// part of the thread is done (we want to run no more than
// config_.num_threads of these.)
// (2) we want to destroy the object "c" now, by deleting it. But for
// correct sequencing (this is the whole point of this class, it
// is intended to ensure the output of the program is in correct order),
// we first wait till the previous thread, whose details will be in "tail",
// is finished.
if (args->tail != NULL) {
while (!args->tail->thread.joinable()){
Sleep(1);
}
args->tail->thread.join();
}
delete args->c; // delete the object "c". This may cause some output,
// e.g. to a stream. We don't need to worry about concurrent access to
// the output stream, because each thread waits for the previous thread
// to be done, before doing this. So there is no risk of concurrent
// access.
args->c = NULL;
if (args->tail != NULL) {
KALDI_ASSERT(args->tail->tail == NULL); // Because we already
// did join on args->tail->thread, which means that
// thread was done, and before it exited, it would have
// deleted and set to NULL its tail (which is the next line of code).
delete args->tail;
args->tail = NULL;
}
// At this point we are exiting from the thread. Signal the
// "tot_threads_avail_" semaphore which is used to limit the total number of threads that are alive, including
// not onlhy those that are in active computation in c->operator (), but those
// that are waiting on I/O or other threads.
args->me->tot_threads_avail_.Signal();
}
int32 num_threads_; // copy of config.num_threads (since Semaphore doesn't store original count)
Semaphore threads_avail_; // Initialized to the number of threads we are
// supposed to run with; the function Run() waits on this.
Semaphore tot_threads_avail_; // We use this semaphore to ensure we don't
// consume too much memory...
RunTaskArgsList *thread_list_;
};
} // namespace kaldi
#endif // KALDI_THREAD_KALDI_THREAD_H_
@@ -0,0 +1,319 @@
// util/parse-options-test.cc
// Copyright 2009-2011 Microsoft Corporation
// Copyright 2012-2013 Frantisek Skala; Arnab Ghoshal
// Copyright 2013 Tanel Alumae
// See ../../COPYING for clarification regarding multiple authors
//
// 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
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#include "util/parse-options.h"
namespace kaldi {
struct DummyOptions {
int32 my_int;
bool my_bool;
std::string my_string;
DummyOptions() {
my_int = 0;
my_bool = true;
my_string = "default dummy string";
}
void Register(ParseOptions *po) {
po->Register("my-int", &my_int,
"An int32 variable in DummyOptions.");
po->Register("my-bool", &my_bool,
"A Boolean varaible in DummyOptions.");
po->Register("my-str", &my_string,
"A string varaible in DummyOptions.");
}
};
void UnitTestParseOptions() {
int argc = 7;
std::string str="default_for_str";
int32 num = 1;
uint32 unum = 2;
const char *argv[7] = { "program_name", "--unum=5", "--num=3", "--i=boo",
"a", "b", "c" };
ParseOptions po("my usage msg");
po.Register("i", &str, "My variable");
po.Register("num", &num, "My int32 variable");
po.Register("unum", &unum, "My uint32 variable");
po.Read(argc, argv);
KALDI_ASSERT(po.NumArgs() == 3);
KALDI_ASSERT(po.GetArg(1) == "a");
KALDI_ASSERT(po.GetArg(2) == "b");
KALDI_ASSERT(po.GetArg(3) == "c");
KALDI_ASSERT(unum == 5);
KALDI_ASSERT(num == 3);
KALDI_ASSERT(str == "boo");
ParseOptions po2("my another msg");
int argc2 = 4;
const char *argv2[4] = { "program_name", "--i=foo",
"--to-be-NORMALIZED=test", "c" };
std::string str2 = "default_for_str2";
po2.Register("To_Be_Normalized", &str2,
"My variable (name has to be normalized)");
po2.Register("i", &str, "My variable");
po2.Read(argc2, argv2);
KALDI_ASSERT(po2.NumArgs() == 1);
KALDI_ASSERT(po2.GetArg(1) == "c");
KALDI_ASSERT(str2 == "test");
KALDI_ASSERT(str == "foo");
ParseOptions po3("now checking options with prefix");
ParseOptions ro3("prefix", &po3); // to register with prefix
ParseOptions so3("prefix2", &ro3); // to register with prefix, recursively.
DummyOptions dummy_opts;
po3.Register("str", &str, "My string variable");
po3.Register("num", &num, "My int32 variable");
// Now register with prefix
ro3.Register("unum", &unum, "My uint32 variable");
ro3.Register("str", &str2, "My other string variable");
uint32 unum2 = 0;
so3.Register("unum", &unum2, "Another uint32 variable");
int argc3 = 10;
const char *argv3[10] = {
"program_name", "--prefix.unum=5", "--num=3",
"--prefix.str=foo", "--str=bar", "--prefix.my-bool=false",
"--prefix.my-str=baz", "--prefix.prefix2.unum=42", "a", "b" };
dummy_opts.Register(&ro3);
po3.PrintUsage(false);
po3.Read(argc3, argv3);
KALDI_ASSERT(po3.NumArgs() == 2);
KALDI_ASSERT(po3.GetArg(1) == "a");
KALDI_ASSERT(po3.GetArg(2) == "b");
KALDI_ASSERT(unum == 5);
KALDI_ASSERT(unum2 == 42);
KALDI_ASSERT(num == 3);
KALDI_ASSERT(str2 == "foo");
KALDI_ASSERT(str == "bar");
KALDI_ASSERT(dummy_opts.my_bool == false);
KALDI_ASSERT(dummy_opts.my_string == "baz");
try { // test error with --option=, which is not a valid way to set
// boolean options.
int argc4 = 2;
const char *argv4[2] = { "program_name", "--option="};
ParseOptions po4("my usage msg");
bool val = false;
po4.Register("option", &val, "My boolean");
po4.Read(argc4, argv4);
assert(false); // Should not reach this part of code.
} catch(std::exception e) {
KALDI_LOG << "Failed to read option (this is expected).";
}
{ // test that --option sets "option" to true, if bool.
int argc4 = 2;
const char *argv4[2] = { "program_name", "--option"};
ParseOptions po4("my usage msg");
bool val = false;
po4.Register("option", &val, "My boolean");
po4.Read(argc4, argv4);
KALDI_ASSERT(val == true);
}
try { // test error with --option, which is not a valid way to set
// string-valued options.
int argc4 = 2;
const char *argv4[2] = { "program_name", "--option"};
ParseOptions po4("my usage msg");
std::string val;
po4.Register("option", &val, "My string");
po4.Read(argc4, argv4);
assert(false); // Should not reach this part of code.
} catch(std::exception e) {
KALDI_LOG << "Failed to read option (this is expected).";
}
{ // test that --option= sets "option" to empty, if string.
int argc4 = 2;
const char *argv4[2] = { "program_name", "--option="};
ParseOptions po4("my usage msg");
std::string val = "foo";
po4.Register("option", &val, "My boolean");
po4.Read(argc4, argv4);
KALDI_ASSERT(val.empty());
}
{ // integer options test
int argc4 = 2;
const char *argv4[2] = { "program_name", "--option=8"};
ParseOptions po4("my usage msg");
int32 val = 32;
po4.Register("option", &val, "My int");
po4.Read(argc4, argv4);
KALDI_ASSERT(val == 8);
}
{ // float
int argc4 = 2;
const char *argv4[2] = { "program_name", "--option=8.5"};
ParseOptions po4("my usage msg");
BaseFloat val = 32.0;
po4.Register("option", &val, "My float");
po4.Read(argc4, argv4);
KALDI_ASSERT(val == 8.5);
}
{ // string options test
int argc4 = 2;
const char *argv4[2] = { "program_name", "--option=bar"};
ParseOptions po4("my usage msg");
std::string val = "foo";
po4.Register("option", &val, "My string");
po4.Read(argc4, argv4);
KALDI_ASSERT(val == "bar");
}
try { // test error with --float=string
int argc4 = 2;
const char *argv4[2] = { "program_name", "--option=foo"};
ParseOptions po4("my usage msg");
BaseFloat val = 32.0;
po4.Register("option", &val, "My float");
po4.Read(argc4, argv4);
assert(false); // Should not reach this part of code.
} catch(std::exception e) {
KALDI_LOG << "Failed to read option (this is expected).";
}
try { // test error with --int=string
int argc4 = 2;
const char *argv4[2] = { "program_name", "--option=foo"};
ParseOptions po4("my usage msg");
int32 val = 32;
po4.Register("option", &val, "My int");
po4.Read(argc4, argv4);
assert(false); // Should not reach this part of code.
} catch(std::exception e) {
KALDI_LOG << "Failed to read option (this is expected).";
}
try { // test error with --int=int+garbage
int argc4 = 2;
const char *argv4[2] = { "program_name", "--option=12xyz"};
ParseOptions po4("my usage msg");
int32 val = 32;
po4.Register("option", &val, "My int");
po4.Read(argc4, argv4);
assert(false); // Should not reach this part of code.
} catch(std::exception e) {
KALDI_LOG << "Failed to read option (this is expected).";
}
try { // test error with --unsigned-int=negative-number.
int argc4 = 2;
const char *argv4[2] = { "program_name", "--option=-13"};
ParseOptions po4("my usage msg");
uint32 val = 32;
po4.Register("option", &val, "My int");
po4.Read(argc4, argv4);
assert(false); // Should not reach this part of code.
} catch(std::exception e) {
KALDI_LOG << "Failed to read option (this is expected)xxx.";
}
try { // test error with --bool=string
int argc4 = 2;
const char *argv4[2] = { "program_name", "--option=foo"};
ParseOptions po4("my usage msg");
bool val = false;
po4.Register("option", &val, "My bool");
po4.Read(argc4, argv4);
assert(false); // Should not reach this part of code.
} catch(std::exception e) {
KALDI_LOG << "Failed to read option (this is expected).";
}
// test error with --=
try {
int argc4 = 2;
const char *argv4[2] = { "program_name", "--=8"};
int32 num = 0;
ParseOptions po4("my usage msg");
po4.Register("num", &num, "My int32 variable");
po4.Read(argc4, argv4);
KALDI_ASSERT(num == 0);
} catch(std::exception e) {
KALDI_LOG << "Failed to read option (this is expected).";
}
// test "--" (no more options)
int argc4 = 5;
unum = 2;
const char *argv4[5] = { "program_name", "--unum=6", "--", "a", "b" };
ParseOptions po4("my usage msg");
po4.Register("unum", &unum, "My uint32 variable");
po4.Read(argc4, argv4);
KALDI_ASSERT(po4.NumArgs() == 2);
KALDI_ASSERT(po4.GetArg(1) == "a");
KALDI_ASSERT(po4.GetArg(2) == "b");
KALDI_ASSERT(unum == 6);
// test obsolete "--" (no more options)
int argc5 = 3;
unum = 2;
const char *argv5[3] = { "program_name", "--unum=7", "--" };
ParseOptions po5("my usage msg");
po5.Register("unum", &unum, "My uint32 variable");
po5.Read(argc5, argv5);
KALDI_ASSERT(po5.NumArgs() == 0);
KALDI_ASSERT(unum == 7);
// test that "--foo=bar" after "--" is interpreted as argument
int argc6 = 4;
unum = 2;
const char *argv6[5] = { "program_name", "--unum=8", "--", "--foo=8" };
ParseOptions po6("my usage msg");
po6.Register("unum", &unum, "My uint32 variable");
po6.Read(argc6, argv6);
KALDI_ASSERT(po6.NumArgs() == 1);
KALDI_ASSERT(po6.GetArg(1) == "--foo=8");
// test that a second registration is ignored
int argc7 = 2;
const char *const argv7[] = {"program_name", "--i=8"};
ParseOptions po7("my usage msg");
int i7 = 10, k7 = 20;
po7.Register("i", &i7, "My int32 variable");
po7.Register("i", &k7, "My int32 variable");
po7.Read(argc7, argv7);
KALDI_ASSERT(i7 == 8);
KALDI_ASSERT(k7 == 20);
}
} // end namespace kaldi.
int main() {
using namespace kaldi;
UnitTestParseOptions();
return 0;
}
@@ -0,0 +1,669 @@
// util/parse-options.cc
// Copyright 2009-2011 Karel Vesely; Microsoft Corporation;
// Saarland University (Author: Arnab Ghoshal);
// Copyright 2012-2013 Johns Hopkins University (Author: Daniel Povey);
// Frantisek Skala; Arnab Ghoshal
// Copyright 2013 Tanel Alumae
//
// See ../../COPYING for clarification regarding multiple authors
//
// 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
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#include <iostream>
#include <iomanip>
#include <fstream>
#include <algorithm>
#include <cstdlib>
#include <cassert>
#include <cstring>
#include "util/parse-options.h"
#include "util/text-utils.h"
#include "base/kaldi-common.h"
namespace kaldi {
ParseOptions::ParseOptions(const std::string &prefix,
OptionsItf *other):
print_args_(false), help_(false), usage_(""), argc_(0), argv_(NULL) {
ParseOptions *po = dynamic_cast<ParseOptions*>(other);
if (po != NULL && po->other_parser_ != NULL) {
// we get here if this constructor is used twice, recursively.
other_parser_ = po->other_parser_;
} else {
other_parser_ = other;
}
if (po != NULL && po->prefix_ != "") {
prefix_ = po->prefix_ + std::string(".") + prefix;
} else {
prefix_ = prefix;
}
}
void ParseOptions::Register(const std::string &name,
bool *ptr, const std::string &doc) {
RegisterTmpl(name, ptr, doc);
}
void ParseOptions::Register(const std::string &name,
int32 *ptr, const std::string &doc) {
RegisterTmpl(name, ptr, doc);
}
void ParseOptions::Register(const std::string &name,
uint32 *ptr, const std::string &doc) {
RegisterTmpl(name, ptr, doc);
}
void ParseOptions::Register(const std::string &name,
float *ptr, const std::string &doc) {
RegisterTmpl(name, ptr, doc);
}
void ParseOptions::Register(const std::string &name,
double *ptr, const std::string &doc) {
RegisterTmpl(name, ptr, doc);
}
void ParseOptions::Register(const std::string &name,
std::string *ptr, const std::string &doc) {
RegisterTmpl(name, ptr, doc);
}
// old-style, used for registering application-specific parameters
template<typename T>
void ParseOptions::RegisterTmpl(const std::string &name, T *ptr,
const std::string &doc) {
if (other_parser_ == NULL) {
this->RegisterCommon(name, ptr, doc, false);
} else {
KALDI_ASSERT(prefix_ != "" &&
"Cannot use empty prefix when registering with prefix.");
std::string new_name = prefix_ + '.' + name; // name becomes prefix.name
other_parser_->Register(new_name, ptr, doc);
}
}
// does the common part of the job of registering a parameter
template<typename T>
void ParseOptions::RegisterCommon(const std::string &name, T *ptr,
const std::string &doc, bool is_standard) {
KALDI_ASSERT(ptr != NULL);
std::string idx = name;
NormalizeArgName(&idx);
if (doc_map_.find(idx) != doc_map_.end())
KALDI_WARN << "Registering option twice, ignoring second time: " << name;
else
this->RegisterSpecific(name, idx, ptr, doc, is_standard);
}
// used to register standard parameters (those that are present in all of the
// applications)
template<typename T>
void ParseOptions::RegisterStandard(const std::string &name, T *ptr,
const std::string &doc) {
this->RegisterCommon(name, ptr, doc, true);
}
void ParseOptions::RegisterSpecific(const std::string &name,
const std::string &idx,
bool *b,
const std::string &doc,
bool is_standard) {
bool_map_[idx] = b;
doc_map_[idx] = DocInfo(name, doc + " (bool, default = "
+ ((*b)? "true)" : "false)"), is_standard);
}
void ParseOptions::RegisterSpecific(const std::string &name,
const std::string &idx,
int32 *i,
const std::string &doc,
bool is_standard) {
int_map_[idx] = i;
std::ostringstream ss;
ss << doc << " (int, default = " << *i << ")";
doc_map_[idx] = DocInfo(name, ss.str(), is_standard);
}
void ParseOptions::RegisterSpecific(const std::string &name,
const std::string &idx,
uint32 *u,
const std::string &doc,
bool is_standard) {
uint_map_[idx] = u;
std::ostringstream ss;
ss << doc << " (uint, default = " << *u << ")";
doc_map_[idx] = DocInfo(name, ss.str(), is_standard);
}
void ParseOptions::RegisterSpecific(const std::string &name,
const std::string &idx,
float *f,
const std::string &doc,
bool is_standard) {
float_map_[idx] = f;
std::ostringstream ss;
ss << doc << " (float, default = " << *f << ")";
doc_map_[idx] = DocInfo(name, ss.str(), is_standard);
}
void ParseOptions::RegisterSpecific(const std::string &name,
const std::string &idx,
double *f,
const std::string &doc,
bool is_standard) {
double_map_[idx] = f;
std::ostringstream ss;
ss << doc << " (double, default = " << *f << ")";
doc_map_[idx] = DocInfo(name, ss.str(), is_standard);
}
void ParseOptions::RegisterSpecific(const std::string &name,
const std::string &idx,
std::string *s,
const std::string &doc,
bool is_standard) {
string_map_[idx] = s;
doc_map_[idx] = DocInfo(name, doc + " (string, default = \"" + *s + "\")",
is_standard);
}
void ParseOptions::DisableOption(const std::string &name) {
if (argv_ != NULL)
KALDI_ERR << "DisableOption must not be called after calling Read().";
if (doc_map_.erase(name) == 0)
KALDI_ERR << "Option " << name
<< " was not registered so cannot be disabled: ";
bool_map_.erase(name);
int_map_.erase(name);
uint_map_.erase(name);
float_map_.erase(name);
double_map_.erase(name);
string_map_.erase(name);
}
int ParseOptions::NumArgs() const {
return positional_args_.size();
}
std::string ParseOptions::GetArg(int i) const {
// use KALDI_ERR if code error
if (i < 1 || i > static_cast<int>(positional_args_.size()))
KALDI_ERR << "ParseOptions::GetArg, invalid index " << i;
return positional_args_[i - 1];
}
// We currently do not support any other options.
enum ShellType { kBash = 0 };
// This can be changed in the code if it ever does need to be changed (as it's
// unlikely that one compilation of this tool-set would use both shells).
static ShellType kShellType = kBash;
// Returns true if we need to escape a string before putting it into
// a shell (mainly thinking of bash shell, but should work for others)
// This is for the convenience of the user so command-lines that are
// printed out by ParseOptions::Read (with --print-args=true) are
// paste-able into the shell and will run. If you use a different type of
// shell, it might be necessary to change this function.
// But it's mostly a cosmetic issue as it basically affects how
// the program echoes its command-line arguments to the screen.
static bool MustBeQuoted(const std::string &str, ShellType st) {
// Only Bash is supported (for the moment).
KALDI_ASSERT(st == kBash && "Invalid shell type.");
const char *c = str.c_str();
if (*c == '\0') {
return true; // Must quote empty string
} else {
const char *ok_chars[2];
// These seem not to be interpreted as long as there are no other "bad"
// characters involved (e.g. "," would be interpreted as part of something
// like a{b,c}, but not on its own.
ok_chars[kBash] = "[]~#^_-+=:.,/";
// Just want to make sure that a space character doesn't get automatically
// inserted here via an automated style-checking script, like it did before.
KALDI_ASSERT(!strchr(ok_chars[kBash], ' '));
for (; *c != '\0'; c++) {
// For non-alphanumeric characters we have a list of characters which
// are OK. All others are forbidden (this is easier since the shell
// interprets most non-alphanumeric characters).
if (!isalnum(*c)) {
const char *d;
for (d = ok_chars[st]; *d != '\0'; d++) if (*c == *d) break;
// If not alphanumeric or one of the "ok_chars", it must be escaped.
if (*d == '\0') return true;
}
}
return false; // The string was OK. No quoting or escaping.
}
}
// Returns a quoted and escaped version of "str"
// which has previously been determined to need escaping.
// Our aim is to print out the command line in such a way that if it's
// pasted into a shell of ShellType "st" (only bash for now), it
// will get passed to the program in the same way.
static std::string QuoteAndEscape(const std::string &str, ShellType st) {
// Only Bash is supported (for the moment).
KALDI_ASSERT(st == kBash && "Invalid shell type.");
// For now we use the following rules:
// In the normal case, we quote with single-quote "'", and to escape
// a single-quote we use the string: '\'' (interpreted as closing the
// single-quote, putting an escaped single-quote from the shell, and
// then reopening the single quote).
char quote_char = '\'';
const char *escape_str = "'\\''"; // e.g. echo 'a'\''b' returns a'b
// If the string contains single-quotes that would need escaping this
// way, and we determine that the string could be safely double-quoted
// without requiring any escaping, then we double-quote the string.
// This is the case if the characters "`$\ do not appear in the string.
// e.g. see http://www.redhat.com/mirrors/LDP/LDP/abs/html/quotingvar.html
const char *c_str = str.c_str();
if (strchr(c_str, '\'') && !strpbrk(c_str, "\"`$\\")) {
quote_char = '"';
escape_str = "\\\""; // should never be accessed.
}
char buf[2];
buf[1] = '\0';
buf[0] = quote_char;
std::string ans = buf;
const char *c = str.c_str();
for (;*c != '\0'; c++) {
if (*c == quote_char) {
ans += escape_str;
} else {
buf[0] = *c;
ans += buf;
}
}
buf[0] = quote_char;
ans += buf;
return ans;
}
// static function
std::string ParseOptions::Escape(const std::string &str) {
return MustBeQuoted(str, kShellType) ? QuoteAndEscape(str, kShellType) : str;
}
int ParseOptions::Read(int argc, const char *const argv[]) {
argc_ = argc;
argv_ = argv;
std::string key, value;
int i;
if (argc > 0) {
// set global "const char*" g_program_name (name of the program)
// so it can be printed out in error messages;
// it's useful because often the stderr of different programs will
// be mixed together in the same log file.
#ifdef _MSC_VER
const char *c = strrchr(argv[0], '\\');
#else
const char *c = strrchr(argv[0], '/');
#endif
SetProgramName(c == NULL ? argv[0] : c + 1);
}
// first pass: look for config parameter, look for priority
for (i = 1; i < argc; i++) {
if (std::strncmp(argv[i], "--", 2) == 0) {
if (std::strcmp(argv[i], "--") == 0) {
// a lone "--" marks the end of named options
break;
}
bool has_equal_sign;
SplitLongArg(argv[i], &key, &value, &has_equal_sign);
NormalizeArgName(&key);
Trim(&value);
if (key.compare("config") == 0) {
ReadConfigFile(value);
}
if (key.compare("help") == 0) {
PrintUsage();
exit(0);
}
}
}
bool double_dash_seen = false;
// second pass: add the command line options
for (i = 1; i < argc; i++) {
if (std::strncmp(argv[i], "--", 2) == 0) {
if (std::strcmp(argv[i], "--") == 0) {
// A lone "--" marks the end of named options.
// Skip that option and break the processing of named options
i += 1;
double_dash_seen = true;
break;
}
bool has_equal_sign;
SplitLongArg(argv[i], &key, &value, &has_equal_sign);
NormalizeArgName(&key);
Trim(&value);
if (!SetOption(key, value, has_equal_sign)) {
PrintUsage(true);
KALDI_ERR << "Invalid option " << argv[i];
}
} else {
break;
}
}
// process remaining arguments as positional
for (; i < argc; i++) {
if ((std::strcmp(argv[i], "--") == 0) && !double_dash_seen) {
double_dash_seen = true;
} else {
positional_args_.push_back(std::string(argv[i]));
}
}
// if the user did not suppress this with --print-args = false....
if (print_args_) {
std::ostringstream strm;
for (int j = 0; j < argc; j++)
strm << Escape(argv[j]) << " ";
strm << '\n';
std::cerr << strm.str() << std::flush;
}
return i;
}
void ParseOptions::PrintUsage(bool print_command_line) {
std::cerr << '\n' << usage_ << '\n';
DocMapType::iterator it;
// first we print application-specific options
bool app_specific_header_printed = false;
for (it = doc_map_.begin(); it != doc_map_.end(); ++it) {
if (it->second.is_standard_ == false) { // application-specific option
if (app_specific_header_printed == false) { // header was not yet printed
std::cerr << "Options:" << '\n';
app_specific_header_printed = true;
}
std::cerr << " --" << std::setw(25) << std::left << it->second.name_
<< " : " << it->second.use_msg_ << '\n';
}
}
if (app_specific_header_printed == true) {
std::cerr << '\n';
}
// then the standard options
std::cerr << "Standard options:" << '\n';
for (it = doc_map_.begin(); it != doc_map_.end(); ++it) {
if (it->second.is_standard_ == true) { // we have standard option
std::cerr << " --" << std::setw(25) << std::left << it->second.name_
<< " : " << it->second.use_msg_ << '\n';
}
}
std::cerr << '\n';
if (print_command_line) {
std::ostringstream strm;
strm << "Command line was: ";
for (int j = 0; j < argc_; j++)
strm << Escape(argv_[j]) << " ";
strm << '\n';
std::cerr << strm.str() << std::flush;
}
}
void ParseOptions::PrintConfig(std::ostream &os) {
os << '\n' << "[[ Configuration of UI-Registered options ]]" << '\n';
std::string key;
DocMapType::iterator it;
for (it = doc_map_.begin(); it != doc_map_.end(); ++it) {
key = it->first;
os << it->second.name_ << " = ";
if (bool_map_.end() != bool_map_.find(key)) {
os << (*bool_map_[key] ? "true" : "false");
} else if (int_map_.end() != int_map_.find(key)) {
os << (*int_map_[key]);
} else if (uint_map_.end() != uint_map_.find(key)) {
os << (*uint_map_[key]);
} else if (float_map_.end() != float_map_.find(key)) {
os << (*float_map_[key]);
} else if (double_map_.end() != double_map_.find(key)) {
os << (*double_map_[key]);
} else if (string_map_.end() != string_map_.find(key)) {
os << "'" << *string_map_[key] << "'";
} else {
KALDI_ERR << "PrintConfig: unrecognized option " << key << "[code error]";
}
os << '\n';
}
os << '\n';
}
void ParseOptions::ReadConfigFile(const std::string &filename) {
std::ifstream is(filename.c_str(), std::ifstream::in);
if (!is.good()) {
KALDI_ERR << "Cannot open config file: " << filename;
}
std::string line, key, value;
int32 line_number = 0;
while (std::getline(is, line)) {
line_number++;
// trim out the comments
size_t pos;
if ((pos = line.find_first_of('#')) != std::string::npos) {
line.erase(pos);
}
// skip empty lines
Trim(&line);
if (line.length() == 0) continue;
if (line.substr(0, 2) != "--") {
KALDI_ERR << "Reading config file " << filename
<< ": line " << line_number << " does not look like a line "
<< "from a Kaldi command-line program's config file: should "
<< "be of the form --x=y. Note: config files intended to "
<< "be sourced by shell scripts lack the '--'.";
}
// parse option
bool has_equal_sign;
SplitLongArg(line, &key, &value, &has_equal_sign);
NormalizeArgName(&key);
Trim(&value);
if (!SetOption(key, value, has_equal_sign)) {
PrintUsage(true);
KALDI_ERR << "Invalid option " << line << " in config file " << filename;
}
}
}
void ParseOptions::SplitLongArg(const std::string &in,
std::string *key,
std::string *value,
bool *has_equal_sign) {
KALDI_ASSERT(in.substr(0, 2) == "--"); // precondition.
size_t pos = in.find_first_of('=', 0);
if (pos == std::string::npos) { // we allow --option for bools
// defaults to empty. We handle this differently in different cases.
*key = in.substr(2, in.size()-2); // 2 because starts with --.
*value = "";
*has_equal_sign = false;
} else if (pos == 2) { // we also don't allow empty keys: --=value
PrintUsage(true);
KALDI_ERR << "Invalid option (no key): " << in;
} else { // normal case: --option=value
*key = in.substr(2, pos-2); // 2 because starts with --.
*value = in.substr(pos + 1);
*has_equal_sign = true;
}
}
void ParseOptions::NormalizeArgName(std::string *str) {
std::string out;
std::string::iterator it;
for (it = str->begin(); it != str->end(); ++it) {
if (*it == '_')
out += '-'; // convert _ to -
else
out += std::tolower(*it);
}
*str = out;
KALDI_ASSERT(str->length() > 0);
}
bool ParseOptions::SetOption(const std::string &key,
const std::string &value,
bool has_equal_sign) {
if (bool_map_.end() != bool_map_.find(key)) {
if (has_equal_sign && value == "")
KALDI_ERR << "Invalid option --" << key << "=";
*(bool_map_[key]) = ToBool(value);
} else if (int_map_.end() != int_map_.find(key)) {
*(int_map_[key]) = ToInt(value);
} else if (uint_map_.end() != uint_map_.find(key)) {
*(uint_map_[key]) = ToUint(value);
} else if (float_map_.end() != float_map_.find(key)) {
*(float_map_[key]) = ToFloat(value);
} else if (double_map_.end() != double_map_.find(key)) {
*(double_map_[key]) = ToDouble(value);
} else if (string_map_.end() != string_map_.find(key)) {
if (!has_equal_sign)
KALDI_ERR << "Invalid option --" << key
<< " (option format is --x=y).";
*(string_map_[key]) = value;
} else {
return false;
}
return true;
}
bool ParseOptions::ToBool(std::string str) {
std::transform(str.begin(), str.end(), str.begin(), ::tolower);
// allow "" as a valid option for "true", so that --x is the same as --x=true
if ((str.compare("true") == 0) || (str.compare("t") == 0)
|| (str.compare("1") == 0) || (str.compare("") == 0)) {
return true;
}
if ((str.compare("false") == 0) || (str.compare("f") == 0)
|| (str.compare("0") == 0)) {
return false;
}
// if it is neither true nor false:
PrintUsage(true);
KALDI_ERR << "Invalid format for boolean argument [expected true or false]: "
<< str;
return false; // never reached
}
int32 ParseOptions::ToInt(const std::string &str) {
int32 ret;
if (!ConvertStringToInteger(str, &ret))
KALDI_ERR << "Invalid integer option \"" << str << "\"";
return ret;
}
uint32 ParseOptions::ToUint(const std::string &str) {
uint32 ret;
if (!ConvertStringToInteger(str, &ret))
KALDI_ERR << "Invalid integer option \"" << str << "\"";
return ret;
}
float ParseOptions::ToFloat(const std::string &str) {
float ret;
if (!ConvertStringToReal(str, &ret))
KALDI_ERR << "Invalid floating-point option \"" << str << "\"";
return ret;
}
double ParseOptions::ToDouble(const std::string &str) {
double ret;
if (!ConvertStringToReal(str, &ret))
KALDI_ERR << "Invalid floating-point option \"" << str << "\"";
return ret;
}
// instantiate templates
template void ParseOptions::RegisterTmpl(const std::string &name, bool *ptr,
const std::string &doc);
template void ParseOptions::RegisterTmpl(const std::string &name, int32 *ptr,
const std::string &doc);
template void ParseOptions::RegisterTmpl(const std::string &name, uint32 *ptr,
const std::string &doc);
template void ParseOptions::RegisterTmpl(const std::string &name, float *ptr,
const std::string &doc);
template void ParseOptions::RegisterTmpl(const std::string &name, double *ptr,
const std::string &doc);
template void ParseOptions::RegisterTmpl(const std::string &name,
std::string *ptr, const std::string &doc);
template void ParseOptions::RegisterStandard(const std::string &name,
bool *ptr,
const std::string &doc);
template void ParseOptions::RegisterStandard(const std::string &name,
int32 *ptr,
const std::string &doc);
template void ParseOptions::RegisterStandard(const std::string &name,
uint32 *ptr,
const std::string &doc);
template void ParseOptions::RegisterStandard(const std::string &name,
float *ptr,
const std::string &doc);
template void ParseOptions::RegisterStandard(const std::string &name,
double *ptr,
const std::string &doc);
template void ParseOptions::RegisterStandard(const std::string &name,
std::string *ptr,
const std::string &doc);
template void ParseOptions::RegisterCommon(const std::string &name,
bool *ptr,
const std::string &doc, bool is_standard);
template void ParseOptions::RegisterCommon(const std::string &name,
int32 *ptr,
const std::string &doc, bool is_standard);
template void ParseOptions::RegisterCommon(const std::string &name,
uint32 *ptr,
const std::string &doc, bool is_standard);
template void ParseOptions::RegisterCommon(const std::string &name,
float *ptr,
const std::string &doc, bool is_standard);
template void ParseOptions::RegisterCommon(const std::string &name,
double *ptr,
const std::string &doc, bool is_standard);
template void ParseOptions::RegisterCommon(const std::string &name,
std::string *ptr,
const std::string &doc, bool is_standard);
} // namespace kaldi
@@ -0,0 +1,264 @@
// util/parse-options.h
// Copyright 2009-2011 Karel Vesely; Microsoft Corporation;
// Saarland University (Author: Arnab Ghoshal);
// Copyright 2012-2013 Frantisek Skala; Arnab Ghoshal
// See ../../COPYING for clarification regarding multiple authors
//
// 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
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#ifndef KALDI_UTIL_PARSE_OPTIONS_H_
#define KALDI_UTIL_PARSE_OPTIONS_H_
#include <map>
#include <string>
#include <vector>
#include "base/kaldi-common.h"
#include "itf/options-itf.h"
namespace kaldi {
/// The class ParseOptions is for parsing command-line options; see
/// \ref parse_options for more documentation.
class ParseOptions : public OptionsItf {
public:
explicit ParseOptions(const char *usage) :
print_args_(true), help_(false), usage_(usage), argc_(0), argv_(NULL),
prefix_(""), other_parser_(NULL) {
#if !defined(_MSC_VER) && !defined(__CYGWIN__) // This is just a convenient place to set the stderr to line
setlinebuf(stderr); // buffering mode, since it's called at program start.
#endif // This helps ensure different programs' output is not mixed up.
RegisterStandard("config", &config_, "Configuration file to read (this "
"option may be repeated)");
RegisterStandard("print-args", &print_args_,
"Print the command line arguments (to stderr)");
RegisterStandard("help", &help_, "Print out usage message");
RegisterStandard("verbose", &g_kaldi_verbose_level,
"Verbose level (higher->more logging)");
}
/**
This is a constructor for the special case where some options are
registered with a prefix to avoid conflicts. The object thus created will
only be used temporarily to register an options class with the original
options parser (which is passed as the *other pointer) using the given
prefix. It should not be used for any other purpose, and the prefix must
not be the empty string. It seems to be the least bad way of implementing
options with prefixes at this point.
Example of usage is:
ParseOptions po; // original ParseOptions object
ParseOptions po_mfcc("mfcc", &po); // object with prefix.
MfccOptions mfcc_opts;
mfcc_opts.Register(&po_mfcc);
The options will now get registered as, e.g., --mfcc.frame-shift=10.0
instead of just --frame-shift=10.0
*/
ParseOptions(const std::string &prefix, OptionsItf *other);
~ParseOptions() {}
// Methods from the interface
void Register(const std::string &name,
bool *ptr, const std::string &doc);
void Register(const std::string &name,
int32 *ptr, const std::string &doc);
void Register(const std::string &name,
uint32 *ptr, const std::string &doc);
void Register(const std::string &name,
float *ptr, const std::string &doc);
void Register(const std::string &name,
double *ptr, const std::string &doc);
void Register(const std::string &name,
std::string *ptr, const std::string &doc);
/// If called after registering an option and before calling
/// Read(), disables that option from being used. Will crash
/// at runtime if that option had not been registered.
void DisableOption(const std::string &name);
/// This one is used for registering standard parameters of all the programs
template<typename T>
void RegisterStandard(const std::string &name,
T *ptr, const std::string &doc);
/**
Parses the command line options and fills the ParseOptions-registered
variables. This must be called after all the variables were registered!!!
Initially the variables have implicit values,
then the config file values are set-up,
finally the command line values given.
Returns the first position in argv that was not used.
[typically not useful: use NumParams() and GetParam(). ]
*/
int Read(int argc, const char *const *argv);
/// Prints the usage documentation [provided in the constructor].
void PrintUsage(bool print_command_line = false);
/// Prints the actual configuration of all the registered variables
void PrintConfig(std::ostream &os);
/// Reads the options values from a config file. Must be called after
/// registering all options. This is usually used internally after the
/// standard --config option is used, but it may also be called from a
/// program.
void ReadConfigFile(const std::string &filename);
/// Number of positional parameters (c.f. argc-1).
int NumArgs() const;
/// Returns one of the positional parameters; 1-based indexing for argc/argv
/// compatibility. Will crash if param is not >=1 and <=NumArgs().
std::string GetArg(int param) const;
std::string GetOptArg(int param) const {
return (param <= NumArgs() ? GetArg(param) : "");
}
/// The following function will return a possibly quoted and escaped
/// version of "str", according to the current shell. Currently
/// this is just hardwired to bash. It's useful for debug output.
static std::string Escape(const std::string &str);
private:
/// Template to register various variable types,
/// used for program-specific parameters
template<typename T>
void RegisterTmpl(const std::string &name, T *ptr, const std::string &doc);
// Following functions do just the datatype-specific part of the job
/// Register boolean variable
void RegisterSpecific(const std::string &name, const std::string &idx,
bool *b, const std::string &doc, bool is_standard);
/// Register int32 variable
void RegisterSpecific(const std::string &name, const std::string &idx,
int32 *i, const std::string &doc, bool is_standard);
/// Register unsinged int32 variable
void RegisterSpecific(const std::string &name, const std::string &idx,
uint32 *u,
const std::string &doc, bool is_standard);
/// Register float variable
void RegisterSpecific(const std::string &name, const std::string &idx,
float *f, const std::string &doc, bool is_standard);
/// Register double variable [useful as we change BaseFloat type].
void RegisterSpecific(const std::string &name, const std::string &idx,
double *f, const std::string &doc, bool is_standard);
/// Register string variable
void RegisterSpecific(const std::string &name, const std::string &idx,
std::string *s, const std::string &doc,
bool is_standard);
/// Does the actual job for both kinds of parameters
/// Does the common part of the job for all datatypes,
/// then calls RegisterSpecific
template<typename T>
void RegisterCommon(const std::string &name,
T *ptr, const std::string &doc, bool is_standard);
/// Set option with name "key" to "value"; will crash if can't do it.
/// "has_equal_sign" is used to allow --x for a boolean option x,
/// and --y=, for a string option y.
bool SetOption(const std::string &key, const std::string &value,
bool has_equal_sign);
bool ToBool(std::string str);
int32 ToInt(const std::string &str);
uint32 ToUint(const std::string &str);
float ToFloat(const std::string &str);
double ToDouble(const std::string &str);
// maps for option variables
std::map<std::string, bool*> bool_map_;
std::map<std::string, int32*> int_map_;
std::map<std::string, uint32*> uint_map_;
std::map<std::string, float*> float_map_;
std::map<std::string, double*> double_map_;
std::map<std::string, std::string*> string_map_;
/**
Structure for options' documentation
*/
struct DocInfo {
DocInfo() {}
DocInfo(const std::string &name, const std::string &usemsg)
: name_(name), use_msg_(usemsg), is_standard_(false) {}
DocInfo(const std::string &name, const std::string &usemsg,
bool is_standard)
: name_(name), use_msg_(usemsg), is_standard_(is_standard) {}
std::string name_;
std::string use_msg_;
bool is_standard_;
};
typedef std::map<std::string, DocInfo> DocMapType;
DocMapType doc_map_; ///< map for the documentation
bool print_args_; ///< variable for the implicit --print-args parameter
bool help_; ///< variable for the implicit --help parameter
std::string config_; ///< variable for the implicit --config parameter
std::vector<std::string> positional_args_;
const char *usage_;
int argc_;
const char *const *argv_;
/// These members are not normally used. They are only used when the object
/// is constructed with a prefix
std::string prefix_;
OptionsItf *other_parser_;
protected:
/// SplitLongArg parses an argument of the form --a=b, --a=, or --a,
/// and sets "has_equal_sign" to true if an equals-sign was parsed..
/// this is needed in order to correctly allow --x for a boolean option
/// x, and --y= for a string option y, and to disallow --x= and --y.
void SplitLongArg(const std::string &in, std::string *key,
std::string *value, bool *has_equal_sign);
void NormalizeArgName(std::string *str);
};
/// This template is provided for convenience in reading config classes from
/// files; this is not the standard way to read configuration options, but may
/// occasionally be needed. This function assumes the config has a function
/// "void Register(OptionsItf *opts)" which it can call to register the
/// ParseOptions object.
template<class C> void ReadConfigFromFile(const std::string &config_filename,
C *c) {
std::ostringstream usage_str;
usage_str << "Parsing config from "
<< "from '" << config_filename << "'";
ParseOptions po(usage_str.str().c_str());
c->Register(&po);
po.ReadConfigFile(config_filename);
}
/// This variant of the template ReadConfigFromFile is for if you need to read
/// two config classes from the same file.
template<class C1, class C2> void ReadConfigsFromFile(const std::string &conf,
C1 *c1, C2 *c2) {
std::ostringstream usage_str;
usage_str << "Parsing config from "
<< "from '" << conf << "'";
ParseOptions po(usage_str.str().c_str());
c1->Register(&po);
c2->Register(&po);
po.ReadConfigFile(conf);
}
} // namespace kaldi
#endif // KALDI_UTIL_PARSE_OPTIONS_H_
@@ -0,0 +1,81 @@
// util/simple-io-funcs.cc
// Copyright 2009-2011 Microsoft Corporation
// See ../../COPYING for clarification regarding multiple authors
//
// 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
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#include "util/simple-io-funcs.h"
#include "util/text-utils.h"
namespace kaldi {
bool WriteIntegerVectorSimple(const std::string &wxfilename,
const std::vector<int32> &list) {
kaldi::Output ko;
// false, false is: text-mode, no Kaldi header.
if (!ko.Open(wxfilename, false, false)) return false;
for (size_t i = 0; i < list.size(); i++) ko.Stream() << list[i] << '\n';
return ko.Close();
}
bool ReadIntegerVectorSimple(const std::string &rxfilename,
std::vector<int32> *list) {
kaldi::Input ki;
if (!ki.OpenTextMode(rxfilename)) return false;
std::istream &is = ki.Stream();
int32 i;
list->clear();
while ( !(is >> i).fail() )
list->push_back(i);
is >> std::ws;
return is.eof(); // should be eof, or junk at end of file.
}
bool WriteIntegerVectorVectorSimple(const std::string &wxfilename,
const std::vector<std::vector<int32> > &list) {
kaldi::Output ko;
// false, false is: text-mode, no Kaldi header.
if (!ko.Open(wxfilename, false, false)) return false;
std::ostream &os = ko.Stream();
for (size_t i = 0; i < list.size(); i++) {
for (size_t j = 0; j < list[i].size(); j++) {
os << list[i][j];
if (j+1 < list[i].size()) os << ' ';
}
os << '\n';
}
return ko.Close();
}
bool ReadIntegerVectorVectorSimple(const std::string &rxfilename,
std::vector<std::vector<int32> > *list) {
kaldi::Input ki;
if (!ki.OpenTextMode(rxfilename)) return false;
std::istream &is = ki.Stream();
list->clear();
std::string line;
while (std::getline(is, line)) {
std::vector<int32> v;
if (!SplitStringToIntegers(line, " \t\r", true, &v)) {
list->clear();
return false;
}
list->push_back(v);
}
return is.eof(); // if we're not at EOF, something weird happened.
}
} // end namespace kaldi
@@ -0,0 +1,63 @@
// util/simple-io-funcs.h
// Copyright 2009-2011 Microsoft Corporation; Jan Silovsky
// See ../../COPYING for clarification regarding multiple authors
//
// 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
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#ifndef KALDI_UTIL_SIMPLE_IO_FUNCS_H_
#define KALDI_UTIL_SIMPLE_IO_FUNCS_H_
#include <string>
#include <vector>
#include "util/kaldi-io.h"
// This header contains some utilities for reading some common, simple text
// formats:integers in files, one per line, and integers in files, possibly
// multiple per line. these are not really fully native Kaldi formats; they are
// mostly for small files that might be generated by scripts, and can be read
// all at one time. for longer files of this type, we would probably use the
// Table code.
namespace kaldi {
/// WriteToList attempts to write this list of integers, one per line,
/// to the given file, in text format.
/// returns true if succeeded.
bool WriteIntegerVectorSimple(const std::string &wxfilename,
const std::vector<int32> &v);
/// ReadFromList attempts to read this list of integers, one per line,
/// from the given file, in text format.
/// returns true if succeeded.
bool ReadIntegerVectorSimple(const std::string &rxfilename,
std::vector<int32> *v);
// This is a file format like:
// 1 2
// 3
//
// 4 5 6
// etc.
bool WriteIntegerVectorVectorSimple(const std::string &wxfilename,
const std::vector<std::vector<int32> > &v);
bool ReadIntegerVectorVectorSimple(const std::string &rxfilename,
std::vector<std::vector<int32> > *v);
} // end namespace kaldi.
#endif // KALDI_UTIL_SIMPLE_IO_FUNCS_H_
@@ -0,0 +1,86 @@
// util/simple-options-test.cc
// Copyright 2013 Tanel Alumae, Tallinn University of Technology
// See ../../COPYING for clarification regarding multiple authors
//
// 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
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#include "util/simple-options.h"
namespace kaldi {
void UnitTestSimpleOptions() {
std::string str="default_for_str";
int32 num = 1;
uint32 unum = 2;
float realnum = 0.1;
bool flag = false;
bool rval;
SimpleOptions so;
so.Register("num", &num, "Description of num");
so.Register("unum", &unum, "Description of unum");
so.Register("str", &str, "Description of str");
so.Register("flag", &flag, "Description of flag");
so.Register("realnum", &realnum, "Description of realnum");
rval = so.SetOption("num", 42);
KALDI_ASSERT(rval);
so.SetOption("unum", static_cast<uint32>(43));
KALDI_ASSERT(rval);
rval = so.SetOption("str", (std::string)"foo");
KALDI_ASSERT(rval);
rval = so.SetOption("flag", false);
KALDI_ASSERT(rval);
KALDI_ASSERT(num == 42);
KALDI_ASSERT(unum == 43);
KALDI_ASSERT(str == "foo");
KALDI_ASSERT(flag == false);
rval = so.SetOption("str", "foo2");
KALDI_ASSERT(rval);
KALDI_ASSERT(str == "foo2");
// test automatic conversion between int and uint
rval = so.SetOption("unum", 44);
KALDI_ASSERT(rval);
KALDI_ASSERT(unum == 44);
// test automatic conversion between float and double
rval = so.SetOption("realnum", static_cast<float>(0.2));
KALDI_ASSERT(rval);
KALDI_ASSERT(realnum - 0.2 < 0.000001);
rval = so.SetOption("realnum", static_cast<double>(0.3));
KALDI_ASSERT(rval);
KALDI_ASSERT(realnum - 0.3 < 0.000001);
SimpleOptions::OptionType type;
rval = so.GetOptionType("num", &type);
KALDI_ASSERT(rval);
KALDI_ASSERT(type == SimpleOptions::kInt32);
rval = so.GetOptionType("xxxx", &type);
KALDI_ASSERT(rval == false);
}
} // end namespace kaldi.
int main() {
using namespace kaldi;
UnitTestSimpleOptions();
return 0;
}
@@ -0,0 +1,184 @@
// util/simple-options.cc
// Copyright 2013 Tanel Alumae, Tallinn University of Technology
// See ../../COPYING for clarification regarding multiple authors
//
// 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
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#include "util/simple-options.h"
namespace kaldi {
void SimpleOptions::Register(const std::string &name,
bool *value,
const std::string &doc) {
bool_map_[name] = value;
option_info_list_.push_back(std::make_pair(name, OptionInfo(doc, kBool)));
}
void SimpleOptions::Register(const std::string &name,
int32 *value,
const std::string &doc) {
int_map_[name] = value;
option_info_list_.push_back(std::make_pair(name, OptionInfo(doc, kInt32)));
}
void SimpleOptions::Register(const std::string &name,
uint32 *value,
const std::string &doc) {
uint_map_[name] = value;
option_info_list_.push_back(std::make_pair(name, OptionInfo(doc, kUint32)));
}
void SimpleOptions::Register(const std::string &name,
float *value,
const std::string &doc) {
float_map_[name] = value;
option_info_list_.push_back(std::make_pair(name, OptionInfo(doc, kFloat)));
}
void SimpleOptions::Register(const std::string &name,
double *value,
const std::string &doc) {
double_map_[name] = value;
option_info_list_.push_back(std::make_pair(name, OptionInfo(doc, kDouble)));
}
void SimpleOptions::Register(const std::string &name,
std::string *value,
const std::string &doc) {
string_map_[name] = value;
option_info_list_.push_back(std::make_pair(name, OptionInfo(doc, kString)));
}
template<typename T>
static bool SetOptionImpl(const std::string &key, const T &value,
std::map<std::string, T*> &some_map) {
if (some_map.end() != some_map.find(key)) {
*(some_map[key]) = value;
return true;
}
return false;
}
bool SimpleOptions::SetOption(const std::string &key, const bool &value) {
return SetOptionImpl(key, value, bool_map_);
}
bool SimpleOptions::SetOption(const std::string &key, const int32 &value) {
if (!SetOptionImpl(key, value, int_map_)) {
if (!SetOptionImpl(key, static_cast<uint32>(value), uint_map_)) {
return false;
}
}
return true;
}
bool SimpleOptions::SetOption(const std::string &key, const uint32 &value) {
if (!SetOptionImpl(key, value, uint_map_)) {
if (!SetOptionImpl(key, static_cast<int32>(value), int_map_)) {
return false;
}
}
return true;
}
bool SimpleOptions::SetOption(const std::string &key, const float &value) {
if (!SetOptionImpl(key, value, float_map_)) {
if (!SetOptionImpl(key, static_cast<double>(value), double_map_)) {
return false;
}
}
return true;
}
bool SimpleOptions::SetOption(const std::string &key, const double &value) {
if (!SetOptionImpl(key, value, double_map_)) {
if (!SetOptionImpl(key, static_cast<float>(value), float_map_)) {
return false;
}
}
return true;
}
bool SimpleOptions::SetOption(const std::string &key,
const std::string &value) {
return SetOptionImpl(key, value, string_map_);
}
bool SimpleOptions::SetOption(const std::string &key, const char *value) {
std::string str_value = std::string(value);
return SetOptionImpl(key, str_value, string_map_);
}
template<typename T>
static bool GetOptionImpl(const std::string &key, T *value,
std::map<std::string, T*> &some_map) {
typename std::map<std::string, T*>::iterator it = some_map.find(key);
if (it != some_map.end()) {
*value = *(it->second);
return true;
}
return false;
}
bool SimpleOptions::GetOption(const std::string &key, bool *value) {
return GetOptionImpl(key, value, bool_map_);
}
bool SimpleOptions::GetOption(const std::string &key, int32 *value) {
return GetOptionImpl(key, value, int_map_);
}
bool SimpleOptions::GetOption(const std::string &key, uint32 *value) {
return GetOptionImpl(key, value, uint_map_);
}
bool SimpleOptions::GetOption(const std::string &key, float *value) {
return GetOptionImpl(key, value, float_map_);
}
bool SimpleOptions::GetOption(const std::string &key, double *value) {
return GetOptionImpl(key, value, double_map_);
}
bool SimpleOptions::GetOption(const std::string &key, std::string *value) {
return GetOptionImpl(key, value, string_map_);
}
std::vector<std::pair<std::string, SimpleOptions::OptionInfo> >
SimpleOptions::GetOptionInfoList() {
return option_info_list_;
}
bool SimpleOptions::GetOptionType(const std::string &key, OptionType *type) {
for (std::vector <std::pair<std::string,
OptionInfo> >::iterator dx = option_info_list_.begin();
dx != option_info_list_.end(); dx++) {
std::pair<std::string, SimpleOptions::OptionInfo> info_pair = (*dx);
if (info_pair.first == key) {
*type = info_pair.second.type;
return true;
}
}
return false;
}
} // namespace kaldi
@@ -0,0 +1,113 @@
// util/simple-options.h
// Copyright 2013 Tanel Alumae, Tallinn University of Technology
// See ../../COPYING for clarification regarding multiple authors
//
// 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
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#ifndef KALDI_UTIL_SIMPLE_OPTIONS_H_
#define KALDI_UTIL_SIMPLE_OPTIONS_H_
#include <map>
#include <string>
#include <vector>
#include "base/kaldi-common.h"
#include "itf/options-itf.h"
namespace kaldi {
/// The class SimpleOptions is an implementation of OptionsItf that allows
/// setting and getting option values programmatically, i.e., via getter
/// and setter methods. It doesn't provide any command line parsing
/// functionality.
/// The class ParseOptions should be used for command-line options.
class SimpleOptions : public OptionsItf {
public:
SimpleOptions() {
}
virtual ~SimpleOptions() {
}
// Methods from the interface
void Register(const std::string &name, bool *ptr, const std::string &doc);
void Register(const std::string &name, int32 *ptr, const std::string &doc);
void Register(const std::string &name, uint32 *ptr, const std::string &doc);
void Register(const std::string &name, float *ptr, const std::string &doc);
void Register(const std::string &name, double *ptr, const std::string &doc);
void Register(const std::string &name, std::string *ptr,
const std::string &doc);
// set option with the specified key, return true if successful
bool SetOption(const std::string &key, const bool &value);
bool SetOption(const std::string &key, const int32 &value);
bool SetOption(const std::string &key, const uint32 &value);
bool SetOption(const std::string &key, const float &value);
bool SetOption(const std::string &key, const double &value);
bool SetOption(const std::string &key, const std::string &value);
bool SetOption(const std::string &key, const char* value);
// get option with the specified key and put to 'value',
// return true if successful
bool GetOption(const std::string &key, bool *value);
bool GetOption(const std::string &key, int32 *value);
bool GetOption(const std::string &key, uint32 *value);
bool GetOption(const std::string &key, float *value);
bool GetOption(const std::string &key, double *value);
bool GetOption(const std::string &key, std::string *value);
enum OptionType {
kBool,
kInt32,
kUint32,
kFloat,
kDouble,
kString
};
struct OptionInfo {
OptionInfo(const std::string &doc, OptionType type) :
doc(doc), type(type) {
}
std::string doc;
OptionType type;
};
std::vector<std::pair<std::string, OptionInfo> > GetOptionInfoList();
/*
* Puts the type of the option with name 'key' in the argument 'type'.
* Return true if such option is found, false otherwise.
*/
bool GetOptionType(const std::string &key, OptionType *type);
private:
std::vector<std::pair<std::string, OptionInfo> > option_info_list_;
// maps for option variables
std::map<std::string, bool*> bool_map_;
std::map<std::string, int32*> int_map_;
std::map<std::string, uint32*> uint_map_;
std::map<std::string, float*> float_map_;
std::map<std::string, double*> double_map_;
std::map<std::string, std::string*> string_map_;
};
} // namespace kaldi
#endif // KALDI_UTIL_SIMPLE_OPTIONS_H_
@@ -0,0 +1,275 @@
// util/stl-utils-test.cc
// Copyright 2009-2012 Microsoft Corporation; Saarland University
// Johns Hopkins University (Author: Daniel Povey)
// See ../../COPYING for clarification regarding multiple authors
//
// 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
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#include "base/kaldi-common.h"
#include "util/stl-utils.h"
namespace kaldi {
static void TestIsSorted() {
for (int i = 0;i < 100;i++) {
std::vector<int> vec, vec2;
int len = Rand()%5;
for (int i = 0;i < len;i++)
vec.push_back(Rand() % 10);
vec2 = vec;
std::sort(vec2.begin(), vec2.end());
KALDI_ASSERT(IsSorted(vec) == (vec == vec2));
}
}
static void TestIsSortedAndUniq() {
for (int i = 0;i < 100;i++) {
std::vector<int> vec, vec2;
int len = Rand()%5;
for (int i = 0;i < len;i++)
vec.push_back(Rand() % 10);
if (!IsSortedAndUniq(vec)) {
bool ok = false;
for (size_t i = 0; i+1 < (size_t)len; i++)
if (vec[i] >= vec[i+1]) ok = true; // found out-of-order or dup.
KALDI_ASSERT(ok);
} else { // is sorted + uniq.
for (size_t i = 0; i+1 < (size_t)len; i++)
KALDI_ASSERT(vec[i] < vec[i+1]);
}
}
}
static void TestUniq() {
for (int i = 0;i < 100;i++) {
std::vector<int> vec;
int cur = 1; // sorted order.
int len = Rand()%5;
for (int i = 0;i < len;i++) {
cur += 1 + (Rand() % 100);
vec.push_back(cur);
}
std::vector<int> vec2;
for (int i = 0;i < len;i++) {
int count = 1 + Rand()%5;
for (int j = 0;j < count;j++) vec2.push_back(vec[i]);
}
Uniq(&vec2);
KALDI_ASSERT(vec2 == vec);
}
}
static void TestSortAndUniq() {
for (int i = 0;i < 100;i++) {
std::vector<int> vec;
int len = Rand()%5;
for (int i = 0;i < len;i++) {
int n = Rand() % 100;
bool ok = true;
for (size_t j = 0;j < vec.size();j++) if (vec[j] == n) ok = false;
if (ok) vec.push_back(n);
}
// don't sort.
std::vector<int> vec2(vec); // make sure all things in "vec" represented
// in vec2.
int len2 = Rand()%10;
if (vec.size() > 0) // add more, randomly.
for (int i = 0;i < len2;i++)
vec2.push_back(vec[Rand()%vec.size()]);
SortAndUniq(&vec2);
std::sort(vec.begin(), vec.end());
KALDI_ASSERT(vec == vec2);
}
}
void TestCopySetToVector() {
for (int p = 0; p < 100; p++) {
std::set<int> st;
int sz = Rand() % 20;
for (int i = 0;i < sz;i++) st.insert(Rand() % 10);
std::vector<int> v;
CopySetToVector(st, &v);
KALDI_ASSERT(st.size() == v.size());
for (size_t i = 0;i < v.size();i++) KALDI_ASSERT(st.count(v[i]) != 0);
}
}
void TestCopyMapToVector() {
for (int p = 0; p < 100; p++) {
std::map<int, int> mp;
int sz = Rand() % 20;
for (int i = 0;i < sz;i++) mp[Rand() % 10] = Rand() % 20;
std::vector<std::pair<int, int> > v;
CopyMapToVector(mp, &v);
KALDI_ASSERT(mp.size() == v.size());
for (size_t i = 0;i < v.size();i++)
KALDI_ASSERT(mp[v[i].first] == v[i].second);
}
}
void TestCopyMapKeysToVector() {
for (int p = 0; p < 100; p++) {
std::map<int, int> mp;
int sz = Rand() % 20;
for (int i = 0;i < sz;i++) mp[Rand() % 10] = Rand() % 20;
std::vector<int> v;
CopyMapKeysToVector(mp, &v);
KALDI_ASSERT(mp.size() == v.size());
for (size_t i = 0;i < v.size();i++) KALDI_ASSERT(mp.count(v[i]) == 1);
}
}
void TestCopyMapValuesToVector() {
for (int p = 0; p < 100; p++) {
std::map<int, int> mp;
int sz = Rand() % 20;
for (int i = 0;i < sz;i++) mp[Rand() % 10] = Rand() % 20;
std::vector<int> v;
CopyMapValuesToVector(mp, &v);
KALDI_ASSERT(mp.size() == v.size());
int i = 0;
for (std::map<int, int>::iterator iter = mp.begin(); iter != mp.end();
iter++) {
KALDI_ASSERT(v[i++] == iter->second);
}
}
}
void TestCopyMapKeysToSet() {
for (int p = 0; p < 100; p++) {
std::map<int, int> mp;
int sz = Rand() % 20;
for (int i = 0;i < sz;i++) mp[Rand() % 10] = Rand() % 20;
std::vector<int> v;
std::set<int> s;
CopyMapKeysToVector(mp, &v);
CopyMapKeysToSet(mp, &s);
std::set<int> s2;
CopyVectorToSet(v, &s2);
KALDI_ASSERT(s == s2);
}
}
void TestCopyMapValuesToSet() {
for (int p = 0; p < 100; p++) {
std::map<int, int> mp;
int sz = Rand() % 20;
for (int i = 0;i < sz;i++) mp[Rand() % 10] = Rand() % 20;
std::vector<int> v;
std::set<int> s;
CopyMapValuesToVector(mp, &v);
CopyMapValuesToSet(mp, &s);
std::set<int> s2;
CopyVectorToSet(v, &s2);
KALDI_ASSERT(s == s2);
}
}
void TestContainsNullPointers() {
for (int p = 0; p < 100; p++) {
std::vector<char*> vec;
int sz = Rand() % 3;
bool is_null = false;
for (int i = 0;i < sz;i++) {
vec.push_back(reinterpret_cast<char*>(static_cast<intptr_t>(Rand() % 2)));
if (vec.back() == NULL)
is_null = true;
}
KALDI_ASSERT(is_null == ContainsNullPointers(vec));
}
}
void TestReverseVector() {
for (int p = 0; p < 100; p++) {
std::vector<int> vec;
int sz = Rand() % 5;
for (int i = 0;i < sz;i++)
vec.push_back(Rand() % 4);
std::vector<int> vec2(vec), vec3(vec);
ReverseVector(&vec2);
ReverseVector(&vec2);
KALDI_ASSERT(vec2 == vec);
ReverseVector(&vec3);
for (size_t i = 0; i < vec.size(); i++)
KALDI_ASSERT(vec[i] == vec3[vec.size()-1-i]);
}
}
void TestMergePairVectorSumming() {
for (int p = 0; p < 100; p++) {
std::vector<std::pair<int32, int16> > v;
std::map<int32, int16> m;
int sz = Rand() % 10;
for (size_t i = 0; i < sz; i++) {
int32 key = Rand() % 10;
int16 val = (Rand() % 5) - 2;
v.push_back(std::make_pair(key, val));
if (m.count(key) == 0) m[key] = val;
else
m[key] += val;
}
MergePairVectorSumming(&v);
KALDI_ASSERT(IsSorted(v));
for (size_t i = 0; i < v.size(); i++) {
KALDI_ASSERT(v[i].second == m[v[i].first]);
KALDI_ASSERT(v[i].second != 0.0);
if (i > 0) KALDI_ASSERT(v[i].first > v[i-1].first);
}
for (std::map<int32, int16>::const_iterator iter = m.begin();
iter != m.end(); ++iter) {
if (iter->second != 0) {
size_t i;
for (i = 0; i < v.size(); i++)
if (v[i].first == iter->first) break;
KALDI_ASSERT(i != v.size()); // Or we didn't find this
// key in v.
}
}
}
}
} // end namespace kaldi
int main() {
using namespace kaldi;
TestIsSorted();
TestIsSortedAndUniq();
TestUniq();
TestSortAndUniq();
TestCopySetToVector();
TestCopyMapToVector();
TestCopyMapKeysToVector();
TestCopyMapValuesToVector();
TestCopyMapKeysToSet();
TestCopyMapValuesToSet();
TestContainsNullPointers();
TestReverseVector();
TestMergePairVectorSumming();
// CopyVectorToSet implicitly tested by last 2.
std::cout << "Test OK\n";
}
+317
View File
@@ -0,0 +1,317 @@
// util/stl-utils.h
// Copyright 2009-2011 Microsoft Corporation; Saarland University
// See ../../COPYING for clarification regarding multiple authors
//
// 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
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#ifndef KALDI_UTIL_STL_UTILS_H_
#define KALDI_UTIL_STL_UTILS_H_
#include <unordered_map>
#include <unordered_set>
using std::unordered_map;
using std::unordered_set;
#include <algorithm>
#include <map>
#include <set>
#include <string>
#include <vector>
#include "base/kaldi-common.h"
namespace kaldi {
/// Sorts and uniq's (removes duplicates) from a vector.
template<typename T>
inline void SortAndUniq(std::vector<T> *vec) {
std::sort(vec->begin(), vec->end());
vec->erase(std::unique(vec->begin(), vec->end()), vec->end());
}
/// Returns true if the vector is sorted.
template<typename T>
inline bool IsSorted(const std::vector<T> &vec) {
typename std::vector<T>::const_iterator iter = vec.begin(), end = vec.end();
if (iter == end) return true;
while (1) {
typename std::vector<T>::const_iterator next_iter = iter;
++next_iter;
if (next_iter == end) return true; // end of loop and nothing out of order
if (*next_iter < *iter) return false;
iter = next_iter;
}
}
/// Returns true if the vector is sorted and contains each element
/// only once.
template<typename T>
inline bool IsSortedAndUniq(const std::vector<T> &vec) {
typename std::vector<T>::const_iterator iter = vec.begin(), end = vec.end();
if (iter == end) return true;
while (1) {
typename std::vector<T>::const_iterator next_iter = iter;
++next_iter;
if (next_iter == end) return true; // end of loop and nothing out of order
if (*next_iter <= *iter) return false;
iter = next_iter;
}
}
/// Removes duplicate elements from a sorted list.
template<typename T>
inline void Uniq(std::vector<T> *vec) { // must be already sorted.
KALDI_PARANOID_ASSERT(IsSorted(*vec));
KALDI_ASSERT(vec);
vec->erase(std::unique(vec->begin(), vec->end()), vec->end());
}
/// Copies the elements of a set to a vector.
template<class T>
void CopySetToVector(const std::set<T> &s, std::vector<T> *v) {
// copies members of s into v, in sorted order from lowest to highest
// (because the set was in sorted order).
KALDI_ASSERT(v != NULL);
v->resize(s.size());
typename std::set<T>::const_iterator siter = s.begin(), send = s.end();
typename std::vector<T>::iterator viter = v->begin();
for (; siter != send; ++siter, ++viter) {
*viter = *siter;
}
}
template<class T>
void CopySetToVector(const unordered_set<T> &s, std::vector<T> *v) {
KALDI_ASSERT(v != NULL);
v->resize(s.size());
typename unordered_set<T>::const_iterator siter = s.begin(), send = s.end();
typename std::vector<T>::iterator viter = v->begin();
for (; siter != send; ++siter, ++viter) {
*viter = *siter;
}
}
/// Copies the (key, value) pairs in a map to a vector of pairs.
template<class A, class B>
void CopyMapToVector(const std::map<A, B> &m,
std::vector<std::pair<A, B> > *v) {
KALDI_ASSERT(v != NULL);
v->resize(m.size());
typename std::map<A, B>::const_iterator miter = m.begin(), mend = m.end();
typename std::vector<std::pair<A, B> >::iterator viter = v->begin();
for (; miter != mend; ++miter, ++viter) {
*viter = std::make_pair(miter->first, miter->second);
// do it like this because of const casting.
}
}
/// Copies the keys in a map to a vector.
template<class A, class B>
void CopyMapKeysToVector(const std::map<A, B> &m, std::vector<A> *v) {
KALDI_ASSERT(v != NULL);
v->resize(m.size());
typename std::map<A, B>::const_iterator miter = m.begin(), mend = m.end();
typename std::vector<A>::iterator viter = v->begin();
for (; miter != mend; ++miter, ++viter) {
*viter = miter->first;
}
}
/// Copies the values in a map to a vector.
template<class A, class B>
void CopyMapValuesToVector(const std::map<A, B> &m, std::vector<B> *v) {
KALDI_ASSERT(v != NULL);
v->resize(m.size());
typename std::map<A, B>::const_iterator miter = m.begin(), mend = m.end();
typename std::vector<B>::iterator viter = v->begin();
for (; miter != mend; ++miter, ++viter) {
*viter = miter->second;
}
}
/// Copies the keys in a map to a set.
template<class A, class B>
void CopyMapKeysToSet(const std::map<A, B> &m, std::set<A> *s) {
KALDI_ASSERT(s != NULL);
s->clear();
typename std::map<A, B>::const_iterator miter = m.begin(), mend = m.end();
for (; miter != mend; ++miter) {
s->insert(s->end(), miter->first);
}
}
/// Copies the values in a map to a set.
template<class A, class B>
void CopyMapValuesToSet(const std::map<A, B> &m, std::set<B> *s) {
KALDI_ASSERT(s != NULL);
s->clear();
typename std::map<A, B>::const_iterator miter = m.begin(), mend = m.end();
for (; miter != mend; ++miter)
s->insert(s->end(), miter->second);
}
/// Copies the contents of a vector to a set.
template<class A>
void CopyVectorToSet(const std::vector<A> &v, std::set<A> *s) {
KALDI_ASSERT(s != NULL);
s->clear();
typename std::vector<A>::const_iterator iter = v.begin(), end = v.end();
for (; iter != end; ++iter)
s->insert(s->end(), *iter);
// s->end() is a hint in case v was sorted. will work regardless.
}
/// Deletes any non-NULL pointers in the vector v, and sets
/// the corresponding entries of v to NULL
template<class A>
void DeletePointers(std::vector<A*> *v) {
KALDI_ASSERT(v != NULL);
typename std::vector<A*>::iterator iter = v->begin(), end = v->end();
for (; iter != end; ++iter) {
if (*iter != NULL) {
delete *iter;
*iter = NULL; // set to NULL for extra safety.
}
}
}
/// Returns true if the vector of pointers contains NULL pointers.
template<class A>
bool ContainsNullPointers(const std::vector<A*> &v) {
typename std::vector<A*>::const_iterator iter = v.begin(), end = v.end();
for (; iter != end; ++iter)
if (*iter == static_cast<A*> (NULL)) return true;
return false;
}
/// Copies the contents a vector of one type to a vector
/// of another type.
template<typename A, typename B>
void CopyVectorToVector(const std::vector<A> &vec_in, std::vector<B> *vec_out) {
KALDI_ASSERT(vec_out != NULL);
vec_out->resize(vec_in.size());
for (size_t i = 0; i < vec_in.size(); i++)
(*vec_out)[i] = static_cast<B> (vec_in[i]);
}
/// A hashing function-object for vectors.
template<typename Int>
struct VectorHasher { // hashing function for vector<Int>.
size_t operator()(const std::vector<Int> &x) const noexcept {
size_t ans = 0;
typename std::vector<Int>::const_iterator iter = x.begin(), end = x.end();
for (; iter != end; ++iter) {
ans *= kPrime;
ans += *iter;
}
return ans;
}
VectorHasher() { // Check we're instantiated with an integer type.
KALDI_ASSERT_IS_INTEGER_TYPE(Int);
}
private:
static const int kPrime = 7853;
};
/// A hashing function-object for pairs of ints
template<typename Int1, typename Int2 = Int1>
struct PairHasher { // hashing function for pair<int>
size_t operator()(const std::pair<Int1, Int2> &x) const noexcept {
// 7853 was chosen at random from a list of primes.
return x.first + x.second * 7853;
}
PairHasher() { // Check we're instantiated with an integer type.
KALDI_ASSERT_IS_INTEGER_TYPE(Int1);
KALDI_ASSERT_IS_INTEGER_TYPE(Int2);
}
};
/// A hashing function object for strings.
struct StringHasher { // hashing function for std::string
size_t operator()(const std::string &str) const noexcept {
size_t ans = 0, len = str.length();
const char *c = str.c_str(), *end = c + len;
for (; c != end; c++) {
ans *= kPrime;
ans += *c;
}
return ans;
}
private:
static const int kPrime = 7853;
};
/// Reverses the contents of a vector.
template<typename T>
inline void ReverseVector(std::vector<T> *vec) {
KALDI_ASSERT(vec != NULL);
size_t sz = vec->size();
for (size_t i = 0; i < sz/2; i++)
std::swap( (*vec)[i], (*vec)[sz-1-i]);
}
/// Comparator object for pairs that compares only the first pair.
template<class A, class B>
struct CompareFirstMemberOfPair {
inline bool operator() (const std::pair<A, B> &p1,
const std::pair<A, B> &p2) {
return p1.first < p2.first;
}
};
/// For a vector of pair<I, F> where I is an integer and F a floating-point or
/// integer type, this function sorts a vector of type vector<pair<I, F> > on
/// the I value and then merges elements with equal I values, summing these over
/// the F component and then removing any F component with zero value. This
/// is for where the vector of pairs represents a map from the integer to float
/// component, with an "adding" type of semantics for combining the elements.
template<typename I, typename F>
inline void MergePairVectorSumming(std::vector<std::pair<I, F> > *vec) {
KALDI_ASSERT_IS_INTEGER_TYPE(I);
CompareFirstMemberOfPair<I, F> c;
std::sort(vec->begin(), vec->end(), c); // sort on 1st element.
typename std::vector<std::pair<I, F> >::iterator out = vec->begin(),
in = vec->begin(), end = vec->end();
// special case: while there is nothing to be changed, skip over
// initial input (avoids unnecessary copying).
while (in + 1 < end && in[0].first != in[1].first && in[0].second != 0.0) {
in++;
out++;
}
while (in < end) {
// We reach this point only at the first element of
// each stretch of identical .first elements.
*out = *in;
++in;
while (in < end && in->first == out->first) {
out->second += in->second; // this is the merge operation.
++in;
}
if (out->second != static_cast<F>(0)) // Don't keep zero elements.
out++;
}
vec->erase(out, end);
}
} // namespace kaldi
#endif // KALDI_UTIL_STL_UTILS_H_
+207
View File
@@ -0,0 +1,207 @@
// util/table-types.h
// Copyright 2009-2011 Microsoft Corporation
// 2020 Mobvoi AI Lab, Beijing, China (author: Fangjun Kuang)
// See ../../COPYING for clarification regarding multiple authors
//
// 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
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#ifndef KALDI_UTIL_TABLE_TYPES_H_
#define KALDI_UTIL_TABLE_TYPES_H_
#include "base/kaldi-common.h"
#include "util/kaldi-table.h"
#include "util/kaldi-holder.h"
#include "matrix/matrix-lib.h"
namespace kaldi {
// This header defines typedefs that are specific instantiations of
// the Table types.
/// \addtogroup table_types
/// @{
typedef TableWriter<KaldiObjectHolder<MatrixBase<BaseFloat> > >
BaseFloatMatrixWriter;
typedef SequentialTableReader<KaldiObjectHolder<Matrix<BaseFloat> > >
SequentialBaseFloatMatrixReader;
typedef RandomAccessTableReader<KaldiObjectHolder<Matrix<BaseFloat> > >
RandomAccessBaseFloatMatrixReader;
typedef RandomAccessTableReaderMapped<KaldiObjectHolder<Matrix<BaseFloat> > >
RandomAccessBaseFloatMatrixReaderMapped;
typedef TableWriter<KaldiObjectHolder<MatrixBase<double> > >
DoubleMatrixWriter;
typedef SequentialTableReader<KaldiObjectHolder<Matrix<double> > >
SequentialDoubleMatrixReader;
typedef RandomAccessTableReader<KaldiObjectHolder<Matrix<double> > >
RandomAccessDoubleMatrixReader;
typedef RandomAccessTableReaderMapped<KaldiObjectHolder<Matrix<double> > >
RandomAccessDoubleMatrixReaderMapped;
typedef TableWriter<KaldiObjectHolder<CompressedMatrix> >
CompressedMatrixWriter;
typedef TableWriter<KaldiObjectHolder<VectorBase<BaseFloat> > >
BaseFloatVectorWriter;
typedef SequentialTableReader<KaldiObjectHolder<Vector<BaseFloat> > >
SequentialBaseFloatVectorReader;
typedef RandomAccessTableReader<KaldiObjectHolder<Vector<BaseFloat> > >
RandomAccessBaseFloatVectorReader;
typedef RandomAccessTableReaderMapped<KaldiObjectHolder<Vector<BaseFloat> > >
RandomAccessBaseFloatVectorReaderMapped;
typedef TableWriter<KaldiObjectHolder<VectorBase<double> > >
DoubleVectorWriter;
typedef SequentialTableReader<KaldiObjectHolder<Vector<double> > >
SequentialDoubleVectorReader;
typedef RandomAccessTableReader<KaldiObjectHolder<Vector<double> > >
RandomAccessDoubleVectorReader;
typedef TableWriter<KaldiObjectHolder<NumpyArray<BaseFloat>>>
BaseFloatNumpyArrayWriter;
typedef SequentialTableReader<KaldiObjectHolder<NumpyArray<BaseFloat>>>
SequentialBaseFloatNumpyArrayReader;
typedef RandomAccessTableReader<KaldiObjectHolder<NumpyArray<BaseFloat>>>
RandomAccessBaseFloatNumpyArrayReader;
typedef TableWriter<KaldiObjectHolder<NumpyArray<double>>>
DoubleNumpyArrayWriter;
typedef SequentialTableReader<KaldiObjectHolder<NumpyArray<double>>>
SequentialDoubleNumpyArrayReader;
typedef RandomAccessTableReader<KaldiObjectHolder<NumpyArray<double>>>
RandomAccessDoubleNumpyArrayReader;
typedef TableWriter<KaldiObjectHolder<CuMatrix<BaseFloat> > >
BaseFloatCuMatrixWriter;
typedef SequentialTableReader<KaldiObjectHolder<CuMatrix<BaseFloat> > >
SequentialBaseFloatCuMatrixReader;
typedef RandomAccessTableReader<KaldiObjectHolder<CuMatrix<BaseFloat> > >
RandomAccessBaseFloatCuMatrixReader;
typedef RandomAccessTableReaderMapped<KaldiObjectHolder<CuMatrix<BaseFloat> > >
RandomAccessBaseFloatCuMatrixReaderMapped;
typedef TableWriter<KaldiObjectHolder<CuMatrix<double> > >
DoubleCuMatrixWriter;
typedef SequentialTableReader<KaldiObjectHolder<CuMatrix<double> > >
SequentialDoubleCuMatrixReader;
typedef RandomAccessTableReader<KaldiObjectHolder<CuMatrix<double> > >
RandomAccessDoubleCuMatrixReader;
typedef RandomAccessTableReaderMapped<KaldiObjectHolder<CuMatrix<double> > >
RandomAccessDoubleCuMatrixReaderMapped;
typedef TableWriter<KaldiObjectHolder<CuVector<BaseFloat> > >
BaseFloatCuVectorWriter;
typedef SequentialTableReader<KaldiObjectHolder<CuVector<BaseFloat> > >
SequentialBaseFloatCuVectorReader;
typedef RandomAccessTableReader<KaldiObjectHolder<CuVector<BaseFloat> > >
RandomAccessBaseFloatCuVectorReader;
typedef RandomAccessTableReaderMapped<KaldiObjectHolder<CuVector<BaseFloat> > >
RandomAccessBaseFloatCuVectorReaderMapped;
typedef TableWriter<KaldiObjectHolder<CuVector<double> > >
DoubleCuVectorWriter;
typedef SequentialTableReader<KaldiObjectHolder<CuVector<double> > >
SequentialDoubleCuVectorReader;
typedef RandomAccessTableReader<KaldiObjectHolder<CuVector<double> > >
RandomAccessDoubleCuVectorReader;
typedef TableWriter<BasicHolder<int32> > Int32Writer;
typedef SequentialTableReader<BasicHolder<int32> > SequentialInt32Reader;
typedef RandomAccessTableReader<BasicHolder<int32> > RandomAccessInt32Reader;
typedef TableWriter<BasicVectorHolder<int32> > Int32VectorWriter;
typedef SequentialTableReader<BasicVectorHolder<int32> >
SequentialInt32VectorReader;
typedef RandomAccessTableReader<BasicVectorHolder<int32> >
RandomAccessInt32VectorReader;
typedef TableWriter<BasicVectorVectorHolder<int32> > Int32VectorVectorWriter;
typedef SequentialTableReader<BasicVectorVectorHolder<int32> >
SequentialInt32VectorVectorReader;
typedef RandomAccessTableReader<BasicVectorVectorHolder<int32> >
RandomAccessInt32VectorVectorReader;
typedef TableWriter<BasicPairVectorHolder<int32> > Int32PairVectorWriter;
typedef SequentialTableReader<BasicPairVectorHolder<int32> >
SequentialInt32PairVectorReader;
typedef RandomAccessTableReader<BasicPairVectorHolder<int32> >
RandomAccessInt32PairVectorReader;
typedef TableWriter<BasicPairVectorHolder<BaseFloat> >
BaseFloatPairVectorWriter;
typedef SequentialTableReader<BasicPairVectorHolder<BaseFloat> >
SequentialBaseFloatPairVectorReader;
typedef RandomAccessTableReader<BasicPairVectorHolder<BaseFloat> >
RandomAccessBaseFloatPairVectorReader;
typedef TableWriter<BasicHolder<BaseFloat> > BaseFloatWriter;
typedef SequentialTableReader<BasicHolder<BaseFloat> >
SequentialBaseFloatReader;
typedef RandomAccessTableReader<BasicHolder<BaseFloat> >
RandomAccessBaseFloatReader;
typedef RandomAccessTableReaderMapped<BasicHolder<BaseFloat> >
RandomAccessBaseFloatReaderMapped;
typedef TableWriter<BasicHolder<double> > DoubleWriter;
typedef SequentialTableReader<BasicHolder<double> > SequentialDoubleReader;
typedef RandomAccessTableReader<BasicHolder<double> > RandomAccessDoubleReader;
typedef TableWriter<BasicHolder<bool> > BoolWriter;
typedef SequentialTableReader<BasicHolder<bool> > SequentialBoolReader;
typedef RandomAccessTableReader<BasicHolder<bool> > RandomAccessBoolReader;
/// TokenWriter is a writer specialized for std::string where the strings
/// are nonempty and whitespace-free. T == std::string
typedef TableWriter<TokenHolder> TokenWriter;
typedef SequentialTableReader<TokenHolder> SequentialTokenReader;
typedef RandomAccessTableReader<TokenHolder> RandomAccessTokenReader;
/// TokenVectorWriter is a writer specialized for sequences of
/// std::string where the strings are nonempty and whitespace-free.
/// T == std::vector<std::string>
typedef TableWriter<TokenVectorHolder> TokenVectorWriter;
// Ditto for SequentialTokenVectorReader.
typedef SequentialTableReader<TokenVectorHolder> SequentialTokenVectorReader;
typedef RandomAccessTableReader<TokenVectorHolder>
RandomAccessTokenVectorReader;
typedef TableWriter<KaldiObjectHolder<GeneralMatrix> >
GeneralMatrixWriter;
typedef SequentialTableReader<KaldiObjectHolder<GeneralMatrix> >
SequentialGeneralMatrixReader;
typedef RandomAccessTableReader<KaldiObjectHolder<GeneralMatrix> >
RandomAccessGeneralMatrixReader;
typedef RandomAccessTableReaderMapped<KaldiObjectHolder<GeneralMatrix> >
RandomAccessGeneralMatrixReaderMapped;
/// @}
// Note: for FST reader/writer, see ../fstext/fstext-utils.h
// [not done yet].
} // end namespace kaldi
#endif // KALDI_UTIL_TABLE_TYPES_H_
@@ -0,0 +1,538 @@
// util/text-utils-test.cc
// Copyright 2009-2011 Microsoft Corporation
// 2017 Johns Hopkins University (author: Daniel Povey)
// 2015 Vimal Manohar (Johns Hopkins University)
// See ../../COPYING for clarification regarding multiple authors
//
// 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
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#include "base/kaldi-common.h"
#include "util/text-utils.h"
namespace kaldi {
char GetRandChar() {
return static_cast<char>(32 + Rand() % 95); // between ' ' and '~'
}
const char *ws_delim = " \t\n\r";
char GetRandDelim() {
if (Rand() % 2 == 0)
return static_cast<char>(33 + Rand() % 94); // between '!' and '~';
else
return ws_delim[Rand() % 4];
}
void TestSplitStringToVector() {
// srand((unsigned int)time(NULL));
// didn't compile on cygwin.
{
std::vector<std::string> str_vec;
SplitStringToVector("", " ", false, &str_vec);
KALDI_ASSERT(str_vec.size() == 1); // If this fails it may just mean
// that someone changed the
// semantics of SplitStringToVector in a reasonable way.
SplitStringToVector("", " ", true, &str_vec);
KALDI_ASSERT(str_vec.empty());
}
for (int j = 0; j < 100; j++) {
std::vector<std::string> str_vec;
int sz = Rand() % 73;
std::string full;
for (int i = 0; i < sz-1; i++) {
full.push_back((Rand() % 7 == 0)? GetRandDelim() : GetRandChar());
}
std::string delim;
delim.push_back(GetRandDelim());
bool omit_empty_strings = (Rand() %2 == 0)? true : false;
SplitStringToVector(full, delim.c_str(), omit_empty_strings, &str_vec);
std::string new_full;
for (size_t i = 0; i < str_vec.size(); i++) {
if (omit_empty_strings) KALDI_ASSERT(str_vec[i] != "");
new_full.append(str_vec[i]);
if (i < str_vec.size() -1) new_full.append(delim);
}
std::string new_full2;
JoinVectorToString(str_vec, delim.c_str(), omit_empty_strings, &new_full2);
if (omit_empty_strings) { // sequences of delimiters cannot be matched
size_t start = full.find_first_not_of(delim),
end = full.find_last_not_of(delim);
if (start == std::string::npos) { // only delimiters
KALDI_ASSERT(end == std::string::npos);
} else {
std::string full_test;
char last = '\0';
for (size_t i = start; i <= end; i++) {
if (full[i] != last || last != *delim.c_str())
full_test.push_back(full[i]);
last = full[i];
}
if (!full.empty()) {
KALDI_ASSERT(new_full.compare(full_test) == 0);
KALDI_ASSERT(new_full2.compare(full_test) == 0);
}
}
} else if (!full.empty()) {
KALDI_ASSERT(new_full.compare(full) == 0);
KALDI_ASSERT(new_full2.compare(full) == 0);
}
}
}
void TestSplitStringToIntegers() {
{
std::vector<int32> v;
KALDI_ASSERT(SplitStringToIntegers("-1:2:4", ":", false, &v) == true
&& v.size() == 3 && v[0] == -1 && v[1] == 2 && v[2] == 4);
KALDI_ASSERT(SplitStringToIntegers("-1:2:4:", ":", false, &v) == false);
KALDI_ASSERT(SplitStringToIntegers(":-1::2:4:", ":", true, &v) == true
&& v.size() == 3 && v[0] == -1 && v[1] == 2 && v[2] == 4);
KALDI_ASSERT(SplitStringToIntegers("-1\n2\t4", " \n\t\r", false, &v) == true
&& v.size() == 3 && v[0] == -1 && v[1] == 2 && v[2] == 4);
KALDI_ASSERT(SplitStringToIntegers(" ", " \n\t\r", true, &v) == true
&& v.size() == 0);
KALDI_ASSERT(SplitStringToIntegers("", " \n\t\r", false, &v) == true
&& v.size() == 0);
}
{
std::vector<uint32> v;
KALDI_ASSERT(SplitStringToIntegers("-1:2:4", ":", false, &v) == false);
// cannot put negative number in uint32.
}
}
void TestSplitStringToFloats() {
{
std::vector<float> v;
KALDI_ASSERT(SplitStringToFloats("-1:2.5:4", ":", false, &v) == true
&& v.size() == 3 && v[0] == -1 && v[1] == 2.5 && v[2] == 4);
KALDI_ASSERT(SplitStringToFloats("-1:2.5:4:", ":", false, &v) == false);
KALDI_ASSERT(SplitStringToFloats(":-1::2:4:", ":", true, &v) == true
&& v.size() == 3 && v[0] == -1 && v[1] == 2 && v[2] == 4);
KALDI_ASSERT(SplitStringToFloats("-1\n2.5\t4", " \n\t\r", false, &v) == true
&& v.size() == 3 && v[0] == -1 && v[1] == 2.5 && v[2] == 4);
KALDI_ASSERT(SplitStringToFloats(" ", " \n\t\r", true, &v) == true
&& v.size() == 0);
KALDI_ASSERT(SplitStringToFloats("", " \n\t\r", false, &v) == true
&& v.size() == 0);
}
{
std::vector<double> v;
KALDI_ASSERT(SplitStringToFloats("-1:2:4", ":", false, &v) == true);
}
}
void TestConvertStringToInteger() {
int32 i;
KALDI_ASSERT(ConvertStringToInteger("12345", &i) && i == 12345);
KALDI_ASSERT(ConvertStringToInteger("-12345", &i) && i == -12345);
char j;
KALDI_ASSERT(!ConvertStringToInteger("-12345", &j)); // too big for char.
KALDI_ASSERT(ConvertStringToInteger(" -12345 ", &i)); // whitespace accepted
KALDI_ASSERT(!ConvertStringToInteger("a ", &i)); // non-integers rejected.
KALDI_ASSERT(ConvertStringToInteger("0", &i) && i == 0);
uint64 k;
KALDI_ASSERT(ConvertStringToInteger("12345", &k) && k == 12345);
KALDI_ASSERT(!ConvertStringToInteger("-12345", &k)); // unsigned,
// cannot convert.
}
template<class Real>
void TestConvertStringToReal() {
Real d;
KALDI_ASSERT(ConvertStringToReal("1", &d) && d == 1.0);
KALDI_ASSERT(ConvertStringToReal("-1", &d) && d == -1.0);
KALDI_ASSERT(ConvertStringToReal("-1", &d) && d == -1.0);
KALDI_ASSERT(ConvertStringToReal(" -1 ", &d) && d == -1.0);
KALDI_ASSERT(!ConvertStringToReal("-1 x", &d));
KALDI_ASSERT(!ConvertStringToReal("-1f", &d));
KALDI_ASSERT(ConvertStringToReal("12345.2", &d) && fabs(d-12345.2) < 1.0);
KALDI_ASSERT(ConvertStringToReal("1.0e+08", &d) && fabs(d-1.0e+08) < 100.0);
// it also works for inf or nan.
KALDI_ASSERT(ConvertStringToReal("inf", &d) && d > 0 && d - d != 0);
KALDI_ASSERT(ConvertStringToReal(" inf", &d) && d > 0 && d - d != 0);
KALDI_ASSERT(ConvertStringToReal("inf ", &d) && d > 0 && d - d != 0);
KALDI_ASSERT(ConvertStringToReal(" inf ", &d) && d > 0 && d - d != 0);
KALDI_ASSERT(ConvertStringToReal("+inf", &d) && d > 0 && d - d != 0);
KALDI_ASSERT(ConvertStringToReal("-inf", &d) && d < 0 && d - d != 0);
KALDI_ASSERT(ConvertStringToReal("Inf", &d) && d > 0 && d - d != 0);
KALDI_ASSERT(ConvertStringToReal("INF", &d) && d > 0 && d - d != 0);
KALDI_ASSERT(ConvertStringToReal("InF", &d) && d > 0 && d - d != 0);
KALDI_ASSERT(ConvertStringToReal("infinity", &d) && d > 0 && d - d != 0);
KALDI_ASSERT(ConvertStringToReal("-infinity", &d) && d < 0 && d - d != 0);
KALDI_ASSERT(!ConvertStringToReal("GARBAGE inf", &d));
KALDI_ASSERT(!ConvertStringToReal("GARBAGEinf", &d));
KALDI_ASSERT(!ConvertStringToReal("infGARBAGE", &d));
KALDI_ASSERT(!ConvertStringToReal("inf_GARBAGE", &d));
KALDI_ASSERT(!ConvertStringToReal("inf GARBAGE", &d));
KALDI_ASSERT(!ConvertStringToReal("GARBAGE infinity", &d));
KALDI_ASSERT(!ConvertStringToReal("GARBAGEinfinity", &d));
KALDI_ASSERT(!ConvertStringToReal("infinityGARBAGE", &d));
KALDI_ASSERT(!ConvertStringToReal("infinity_GARBAGE", &d));
KALDI_ASSERT(!ConvertStringToReal("infinity GARBAGE", &d));
KALDI_ASSERT(ConvertStringToReal("1.#INF", &d) && d > 0 && d - d != 0);
KALDI_ASSERT(ConvertStringToReal("-1.#INF", &d) && d < 0 && d - d != 0);
KALDI_ASSERT(ConvertStringToReal("-1.#INF ", &d) && d < 0 && d - d != 0);
KALDI_ASSERT(ConvertStringToReal(" -1.#INF ", &d) && d < 0 && d - d != 0);
KALDI_ASSERT(!ConvertStringToReal("GARBAGE 1.#INF", &d));
KALDI_ASSERT(!ConvertStringToReal("GARBAGE1.#INF", &d));
KALDI_ASSERT(!ConvertStringToReal("2.#INF", &d));
KALDI_ASSERT(!ConvertStringToReal("-2.#INF", &d));
KALDI_ASSERT(!ConvertStringToReal("1.#INFGARBAGE", &d));
KALDI_ASSERT(!ConvertStringToReal("1.#INF_GARBAGE", &d));
KALDI_ASSERT(ConvertStringToReal("nan", &d) && d != d);
KALDI_ASSERT(ConvertStringToReal("+nan", &d) && d != d);
KALDI_ASSERT(ConvertStringToReal("-nan", &d) && d != d);
KALDI_ASSERT(ConvertStringToReal("Nan", &d) && d != d);
KALDI_ASSERT(ConvertStringToReal("NAN", &d) && d != d);
KALDI_ASSERT(ConvertStringToReal("NaN", &d) && d != d);
KALDI_ASSERT(ConvertStringToReal(" NaN", &d) && d != d);
KALDI_ASSERT(ConvertStringToReal("NaN ", &d) && d != d);
KALDI_ASSERT(ConvertStringToReal(" NaN ", &d) && d != d);
KALDI_ASSERT(ConvertStringToReal("1.#QNAN", &d) && d != d);
KALDI_ASSERT(ConvertStringToReal("-1.#QNAN", &d) && d != d);
KALDI_ASSERT(ConvertStringToReal("1.#QNAN ", &d) && d != d);
KALDI_ASSERT(ConvertStringToReal(" 1.#QNAN ", &d) && d != d);
KALDI_ASSERT(!ConvertStringToReal("GARBAGE nan", &d));
KALDI_ASSERT(!ConvertStringToReal("GARBAGEnan", &d));
KALDI_ASSERT(!ConvertStringToReal("nanGARBAGE", &d));
KALDI_ASSERT(!ConvertStringToReal("nan_GARBAGE", &d));
KALDI_ASSERT(!ConvertStringToReal("nan GARBAGE", &d));
KALDI_ASSERT(!ConvertStringToReal("GARBAGE 1.#QNAN", &d));
KALDI_ASSERT(!ConvertStringToReal("GARBAGE1.#QNAN", &d));
KALDI_ASSERT(!ConvertStringToReal("2.#QNAN", &d));
KALDI_ASSERT(!ConvertStringToReal("-2.#QNAN", &d));
KALDI_ASSERT(!ConvertStringToReal("-1.#QNAN_GARBAGE", &d));
KALDI_ASSERT(!ConvertStringToReal("-1.#QNANGARBAGE", &d));
}
template<class Real>
void TestNan() {
Real d;
KALDI_ASSERT(ConvertStringToReal(std::to_string(sqrt(-1)), &d) && d != d);
}
template<class Real>
void TestInf() {
Real d;
KALDI_ASSERT(ConvertStringToReal(std::to_string(exp(10000)), &d) &&
d > 0 && d - d != 0);
KALDI_ASSERT(ConvertStringToReal(std::to_string(-exp(10000)), &d) &&
d < 0 && d - d != 0);
}
std::string TrimTmp(std::string s) {
Trim(&s);
return s;
}
void TestTrim() {
KALDI_ASSERT(TrimTmp(" a ") == "a");
KALDI_ASSERT(TrimTmp(" a b c") == "a b c");
KALDI_ASSERT(TrimTmp("") == "");
KALDI_ASSERT(TrimTmp("X\n") == "X");
KALDI_ASSERT(TrimTmp("X\n\t") == "X");
KALDI_ASSERT(TrimTmp("\n\tX") == "X");
} // end namespace kaldi
void TestSplitStringOnFirstSpace() {
std::string a, b;
SplitStringOnFirstSpace("a b", &a, &b);
KALDI_ASSERT(a == "a" && b == "b");
SplitStringOnFirstSpace("aa bb", &a, &b);
KALDI_ASSERT(a == "aa" && b == "bb");
SplitStringOnFirstSpace("aa", &a, &b);
KALDI_ASSERT(a == "aa" && b == "");
SplitStringOnFirstSpace(" aa \n\t ", &a, &b);
KALDI_ASSERT(a == "aa" && b == "");
SplitStringOnFirstSpace(" \n\t ", &a, &b);
KALDI_ASSERT(a == "" && b == "");
SplitStringOnFirstSpace(" aa bb \n\t ", &a, &b);
KALDI_ASSERT(a == "aa" && b == "bb");
SplitStringOnFirstSpace(" aa bb cc ", &a, &b);
KALDI_ASSERT(a == "aa" && b == "bb cc");
SplitStringOnFirstSpace(" aa bb cc ", &a, &b);
KALDI_ASSERT(a == "aa" && b == "bb cc");
SplitStringOnFirstSpace(" aa bb cc", &a, &b);
KALDI_ASSERT(a == "aa" && b == "bb cc");
}
void TestIsToken() {
KALDI_ASSERT(IsToken("a"));
KALDI_ASSERT(IsToken("ab"));
KALDI_ASSERT(!IsToken("ab "));
KALDI_ASSERT(!IsToken(" ab"));
KALDI_ASSERT(!IsToken("a b"));
KALDI_ASSERT(IsToken("\231")); // typical non-ASCII printable character,
// something with an accent.
KALDI_ASSERT(!IsToken("\377")); // character 255, which is a form of space.
KALDI_ASSERT(IsToken("a-b,c,d=ef"));
KALDI_ASSERT(!IsToken("a\nb"));
KALDI_ASSERT(!IsToken("a\tb"));
KALDI_ASSERT(!IsToken("ab\t"));
KALDI_ASSERT(!IsToken(""));
}
void TestIsLine() {
KALDI_ASSERT(IsLine("a"));
KALDI_ASSERT(IsLine("a b"));
KALDI_ASSERT(!IsLine("a\nb"));
KALDI_ASSERT(!IsLine("a b "));
KALDI_ASSERT(!IsLine(" a b"));
}
void TestStringsApproxEqual() {
// we must test the test.
KALDI_ASSERT(!StringsApproxEqual("a", "b"));
KALDI_ASSERT(!StringsApproxEqual("1", "2"));
KALDI_ASSERT(StringsApproxEqual("1.234", "1.235", 2));
KALDI_ASSERT(!StringsApproxEqual("1.234", "1.235", 3));
KALDI_ASSERT(StringsApproxEqual("x 1.234 y", "x 1.2345 y", 3));
KALDI_ASSERT(!StringsApproxEqual("x 1.234 y", "x 1.2345 y", 4));
KALDI_ASSERT(StringsApproxEqual("x 1.234 y 6.41", "x 1.235 y 6.49", 1));
KALDI_ASSERT(!StringsApproxEqual("x 1.234 y 6.41", "x 1.235 y 6.49", 2));
KALDI_ASSERT(StringsApproxEqual("x 1.234 y 6.41", "x 1.235 y 6.411", 2));
KALDI_ASSERT(StringsApproxEqual("x 1.0 y", "x 1.0001 y", 3));
KALDI_ASSERT(!StringsApproxEqual("x 1.0 y", "x 1.0001 y", 4));
}
void UnitTestConfigLineParse() {
std::string str;
{
ConfigLine cfl;
str = "a-b xx=yyy foo=bar baz=123 ba=1:2";
bool status = cfl.ParseLine(str);
KALDI_ASSERT(status && cfl.FirstToken() == "a-b");
KALDI_ASSERT(cfl.HasUnusedValues());
std::string str_value;
KALDI_ASSERT(cfl.GetValue("xx", &str_value));
KALDI_ASSERT(str_value == "yyy");
KALDI_ASSERT(cfl.HasUnusedValues());
KALDI_ASSERT(cfl.GetValue("foo", &str_value));
KALDI_ASSERT(str_value == "bar");
KALDI_ASSERT(cfl.HasUnusedValues());
KALDI_ASSERT(!cfl.GetValue("xy", &str_value));
KALDI_ASSERT(cfl.GetValue("baz", &str_value));
KALDI_ASSERT(str_value == "123");
std::vector<int32> int_values;
KALDI_ASSERT(!cfl.GetValue("xx", &int_values));
KALDI_ASSERT(cfl.GetValue("baz", &int_values));
KALDI_ASSERT(cfl.HasUnusedValues());
KALDI_ASSERT(int_values.size() == 1 && int_values[0] == 123);
KALDI_ASSERT(cfl.GetValue("ba", &int_values));
KALDI_ASSERT(int_values.size() == 2 && int_values[0] == 1 && int_values[1] == 2);
KALDI_ASSERT(!cfl.HasUnusedValues());
}
{
ConfigLine cfl;
str = "a-b baz=x y z pp = qq ab =cd ac= bd";
KALDI_ASSERT(!cfl.ParseLine(str));
}
{
ConfigLine cfl;
str = "a-b baz=x y z pp = qq ab=cd ac=bd";
KALDI_ASSERT(!cfl.ParseLine(str));
}
{
ConfigLine cfl;
str = "foo-bar";
KALDI_ASSERT(cfl.ParseLine(str));
}
{
ConfigLine cfl;
str = "foo-bar a=b c d f=g";
std::string value;
KALDI_ASSERT(cfl.ParseLine(str) && cfl.FirstToken() == "foo-bar" &&
cfl.GetValue("a", &value) && value == "b c d" &&
cfl.GetValue("f", &value) && value == "g" &&
!cfl.HasUnusedValues());
}
{
ConfigLine cfl;
str = "zzz a=b baz";
KALDI_ASSERT(cfl.ParseLine(str) && cfl.FirstToken() == "zzz" &&
cfl.UnusedValues() == "a=b baz");
}
{
ConfigLine cfl;
str = "xxx a=b baz ";
KALDI_ASSERT(cfl.ParseLine(str) && cfl.UnusedValues() == "a=b baz");
}
{
ConfigLine cfl;
str = "xxx a=b =c";
KALDI_ASSERT(!cfl.ParseLine(str));
}
{
ConfigLine cfl;
str = "xxx baz='x y z' pp=qq ab=cd ac=bd";
KALDI_ASSERT(cfl.ParseLine(str) && cfl.FirstToken() == "xxx");
std::string str_value;
KALDI_ASSERT(cfl.GetValue("baz", &str_value));
KALDI_ASSERT(str_value == "x y z");
KALDI_ASSERT(cfl.GetValue("pp", &str_value));
KALDI_ASSERT(str_value == "qq");
KALDI_ASSERT(cfl.UnusedValues() == "ab=cd ac=bd");
KALDI_ASSERT(cfl.GetValue("ab", &str_value));
KALDI_ASSERT(str_value == "cd");
KALDI_ASSERT(cfl.UnusedValues() == "ac=bd");
KALDI_ASSERT(cfl.HasUnusedValues());
KALDI_ASSERT(cfl.GetValue("ac", &str_value));
KALDI_ASSERT(str_value == "bd");
KALDI_ASSERT(!cfl.HasUnusedValues());
}
{
ConfigLine cfl;
str = "x baz= pp = qq flag=t ";
KALDI_ASSERT(!cfl.ParseLine(str));
}
{
ConfigLine cfl;
str = " x baz= pp=qq flag=t ";
KALDI_ASSERT(cfl.ParseLine(str) && cfl.FirstToken() == "x");
std::string str_value;
KALDI_ASSERT(cfl.GetValue("baz", &str_value));
KALDI_ASSERT(str_value == "");
KALDI_ASSERT(cfl.GetValue("pp", &str_value));
KALDI_ASSERT(str_value == "qq");
KALDI_ASSERT(cfl.HasUnusedValues());
KALDI_ASSERT(cfl.GetValue("flag", &str_value));
KALDI_ASSERT(str_value == "t");
KALDI_ASSERT(!cfl.HasUnusedValues());
bool bool_value = false;
KALDI_ASSERT(cfl.GetValue("flag", &bool_value));
KALDI_ASSERT(bool_value);
}
{
ConfigLine cfl;
str = "xx _baz=a -pp=qq";
KALDI_ASSERT(!cfl.ParseLine(str));
}
{
ConfigLine cfl;
str = "xx 0baz=a pp=qq";
KALDI_ASSERT(!cfl.ParseLine(str));
}
{
ConfigLine cfl;
str = "xx -baz=a pp=qq";
KALDI_ASSERT(!cfl.ParseLine(str));
}
{
ConfigLine cfl;
str = "xx _baz'=a pp=qq";
KALDI_ASSERT(!cfl.ParseLine(str));
}
{
ConfigLine cfl;
str = " baz=g";
KALDI_ASSERT(cfl.ParseLine(str) && cfl.FirstToken() == "");
bool flag;
KALDI_ASSERT(!cfl.GetValue("baz", &flag));
}
{
ConfigLine cfl;
str = "xx _baz1=a pp=qq";
KALDI_ASSERT(cfl.ParseLine(str));
std::string str_value;
KALDI_ASSERT(cfl.GetValue("_baz1", &str_value));
}
}
void UnitTestReadConfig() {
std::string str = "a-b alpha=aa beta=\"b b\"# String test\n"
"a-b beta2='b c' beta3=bd # \n"
"a-b gamma=1:2:3:4 # Int Vector test\n"
" a-b de1ta=f # Bool + Integer in key Comment test delta=t \n"
"a-b _epsilon=-1 # Int Vector test _epsilon=1 \n"
"a-b zet-_a=0.15 theta=1.1# Float, -, _ test\n"
"a-b quoted='a b c' # quoted string\n"
"a-b quoted2=\"d e 'a b=c' f\" # string quoted with double quotes";
std::istringstream is(str);
std::vector<std::string> lines;
ReadConfigLines(is, &lines);
KALDI_ASSERT(lines.size() == 8);
ConfigLine cfl;
for (size_t i = 0; i < lines.size(); i++) {
KALDI_ASSERT(cfl.ParseLine(lines[i]) && cfl.FirstToken() == "a-b");
if (i == 1) {
KALDI_ASSERT(cfl.GetValue("beta2", &str) && str == "b c");
}
if (i == 4) {
KALDI_ASSERT(cfl.GetValue("_epsilon", &str) && str == "-1");
}
if (i == 5) {
BaseFloat float_val = 0;
KALDI_ASSERT(cfl.GetValue("zet-_a", &float_val) && ApproxEqual(float_val, 0.15));
}
if (i == 6) {
KALDI_ASSERT(cfl.GetValue("quoted", &str) && str == "a b c");
}
if (i == 7) {
KALDI_ASSERT(cfl.GetValue("quoted2", &str) && str == "d e 'a b=c' f");
}
}
}
} // end namespace kaldi
int main() {
using namespace kaldi;
TestSplitStringToVector();
TestSplitStringToIntegers();
TestSplitStringToFloats();
TestConvertStringToInteger();
TestConvertStringToReal<float>();
TestConvertStringToReal<double>();
TestTrim();
TestSplitStringOnFirstSpace();
TestIsToken();
TestIsLine();
TestStringsApproxEqual();
TestNan<float>();
TestNan<double>();
TestInf<float>();
TestInf<double>();
UnitTestConfigLineParse();
UnitTestReadConfig();
std::cout << "Test OK\n";
}
+591
View File
@@ -0,0 +1,591 @@
// util/text-utils.cc
// Copyright 2009-2011 Saarland University; Microsoft Corporation
// See ../../COPYING for clarification regarding multiple authors
//
// 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
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#include "util/text-utils.h"
#include <limits>
#include <map>
#include <algorithm>
#include "base/kaldi-common.h"
namespace kaldi {
template<class F>
bool SplitStringToFloats(const std::string &full,
const char *delim,
bool omit_empty_strings, // typically false
std::vector<F> *out) {
KALDI_ASSERT(out != NULL);
if (*(full.c_str()) == '\0') {
out->clear();
return true;
}
std::vector<std::string> split;
SplitStringToVector(full, delim, omit_empty_strings, &split);
out->resize(split.size());
for (size_t i = 0; i < split.size(); i++) {
F f = 0;
if (!ConvertStringToReal(split[i], &f))
return false;
(*out)[i] = f;
}
return true;
}
// Instantiate the template above for float and double.
template
bool SplitStringToFloats(const std::string &full,
const char *delim,
bool omit_empty_strings,
std::vector<float> *out);
template
bool SplitStringToFloats(const std::string &full,
const char *delim,
bool omit_empty_strings,
std::vector<double> *out);
void SplitStringToVector(const std::string &full, const char *delim,
bool omit_empty_strings,
std::vector<std::string> *out) {
size_t start = 0, found = 0, end = full.size();
out->clear();
while (found != std::string::npos) {
found = full.find_first_of(delim, start);
// start != end condition is for when the delimiter is at the end
if (!omit_empty_strings || (found != start && start != end))
out->push_back(full.substr(start, found - start));
start = found + 1;
}
}
void JoinVectorToString(const std::vector<std::string> &vec_in,
const char *delim, bool omit_empty_strings,
std::string *str_out) {
std::string tmp_str;
for (size_t i = 0; i < vec_in.size(); i++) {
if (!omit_empty_strings || !vec_in[i].empty()) {
tmp_str.append(vec_in[i]);
if (i < vec_in.size() - 1)
if (!omit_empty_strings || !vec_in[i+1].empty())
tmp_str.append(delim);
}
}
str_out->swap(tmp_str);
}
void Trim(std::string *str) {
const char *white_chars = " \t\n\r\f\v";
std::string::size_type pos = str->find_last_not_of(white_chars);
if (pos != std::string::npos) {
str->erase(pos + 1);
pos = str->find_first_not_of(white_chars);
if (pos != std::string::npos) str->erase(0, pos);
} else {
str->erase(str->begin(), str->end());
}
}
bool IsToken(const std::string &token) {
size_t l = token.length();
if (l == 0) return false;
for (size_t i = 0; i < l; i++) {
unsigned char c = token[i];
if ((!isprint(c) || isspace(c)) && (isascii(c) || c == (unsigned char)255))
return false;
// The "&& (isascii(c) || c == 255)" was added so that we won't reject
// non-ASCII characters such as French characters with accents [except for
// 255 which is "nbsp", a form of space].
}
return true;
}
void SplitStringOnFirstSpace(const std::string &str,
std::string *first,
std::string *rest) {
const char *white_chars = " \t\n\r\f\v";
typedef std::string::size_type I;
const I npos = std::string::npos;
I first_nonwhite = str.find_first_not_of(white_chars);
if (first_nonwhite == npos) {
first->clear();
rest->clear();
return;
}
// next_white is first whitespace after first nonwhitespace.
I next_white = str.find_first_of(white_chars, first_nonwhite);
if (next_white == npos) { // no more whitespace...
*first = std::string(str, first_nonwhite);
rest->clear();
return;
}
I next_nonwhite = str.find_first_not_of(white_chars, next_white);
if (next_nonwhite == npos) {
*first = std::string(str, first_nonwhite, next_white-first_nonwhite);
rest->clear();
return;
}
I last_nonwhite = str.find_last_not_of(white_chars);
KALDI_ASSERT(last_nonwhite != npos); // or coding error.
*first = std::string(str, first_nonwhite, next_white-first_nonwhite);
*rest = std::string(str, next_nonwhite, last_nonwhite+1-next_nonwhite);
}
bool IsLine(const std::string &line) {
if (line.find('\n') != std::string::npos) return false;
if (line.empty()) return true;
if (isspace(*(line.begin()))) return false;
if (isspace(*(line.rbegin()))) return false;
std::string::const_iterator iter = line.begin(), end = line.end();
for (; iter != end; iter++)
if (!isprint(*iter)) return false;
return true;
}
template <class T>
class NumberIstream{
public:
explicit NumberIstream(std::istream &i) : in_(i) {}
NumberIstream & operator >> (T &x) {
if (!in_.good()) return *this;
in_ >> x;
if (!in_.fail() && RemainderIsOnlySpaces()) return *this;
return ParseOnFail(&x);
}
private:
std::istream &in_;
bool RemainderIsOnlySpaces() {
if (in_.tellg() != std::istream::pos_type(-1)) {
std::string rem;
in_ >> rem;
if (rem.find_first_not_of(' ') != std::string::npos) {
// there is not only spaces
return false;
}
}
in_.clear();
return true;
}
NumberIstream & ParseOnFail(T *x) {
std::string str;
in_.clear();
in_.seekg(0);
// If the stream is broken even before trying
// to read from it or if there are many tokens,
// it's pointless to try.
if (!(in_ >> str) || !RemainderIsOnlySpaces()) {
in_.setstate(std::ios_base::failbit);
return *this;
}
std::map<std::string, T> inf_nan_map;
// we'll keep just uppercase values.
inf_nan_map["INF"] = std::numeric_limits<T>::infinity();
inf_nan_map["+INF"] = std::numeric_limits<T>::infinity();
inf_nan_map["-INF"] = - std::numeric_limits<T>::infinity();
inf_nan_map["INFINITY"] = std::numeric_limits<T>::infinity();
inf_nan_map["+INFINITY"] = std::numeric_limits<T>::infinity();
inf_nan_map["-INFINITY"] = - std::numeric_limits<T>::infinity();
inf_nan_map["NAN"] = std::numeric_limits<T>::quiet_NaN();
inf_nan_map["+NAN"] = std::numeric_limits<T>::quiet_NaN();
inf_nan_map["-NAN"] = - std::numeric_limits<T>::quiet_NaN();
// MSVC
inf_nan_map["1.#INF"] = std::numeric_limits<T>::infinity();
inf_nan_map["-1.#INF"] = - std::numeric_limits<T>::infinity();
inf_nan_map["1.#QNAN"] = std::numeric_limits<T>::quiet_NaN();
inf_nan_map["-1.#QNAN"] = - std::numeric_limits<T>::quiet_NaN();
std::transform(str.begin(), str.end(), str.begin(), ::toupper);
if (inf_nan_map.find(str) != inf_nan_map.end()) {
*x = inf_nan_map[str];
} else {
in_.setstate(std::ios_base::failbit);
}
return *this;
}
};
template <typename T>
bool ConvertStringToReal(const std::string &str,
T *out) {
std::istringstream iss(str);
NumberIstream<T> i(iss);
i >> *out;
if (iss.fail()) {
// Number conversion failed.
return false;
}
return true;
}
template
bool ConvertStringToReal(const std::string &str,
float *out);
template
bool ConvertStringToReal(const std::string &str,
double *out);
/*
This function is a helper function of StringsApproxEqual. It should be
thought of as a recursive function-- it was designed that way-- but rather
than actually recursing (which would cause problems with stack overflow), we
just set the args and return to the start.
The 'decimal_places_tolerance' argument is just passed in from outside,
see the documentation for StringsApproxEqual in text-utils.h to see an
explanation. The argument 'places_into_number' provides some information
about the strings 'a' and 'b' that precedes the current pointers.
For purposes of this comment, let's define the 'decimal' of a number
as the part that comes after the decimal point, e.g. in '99.123',
'123' would be the decimal. If 'places_into_number' is -1, it means
we're not currently inside some place like that (i.e. it's not the
case that we're pointing to the '1' or the '2' or the '3').
If it's 0, then we'd be pointing to the first place after the decimal,
'1' in this case. Note if one of the numbers is shorter than the
other, like '99.123' versus '99.1234' and 'a' points to the first '3'
while 'b' points to the second '4', 'places_into_number' referes to the
shorter of the two, i.e. it would be 2 in this example.
*/
bool StringsApproxEqualInternal(const char *a, const char *b,
int32 decimal_places_tolerance,
int32 places_into_number) {
start:
char ca = *a, cb = *b;
if (ca == cb) {
if (ca == '\0') {
return true;
} else {
if (places_into_number >= 0) {
if (isdigit(ca)) {
places_into_number++;
} else {
places_into_number = -1;
}
} else {
if (ca == '.') {
places_into_number = 0;
}
}
a++;
b++;
goto start;
}
} else {
if (places_into_number >= decimal_places_tolerance &&
(isdigit(ca) || isdigit(cb))) {
// we're potentially willing to accept this difference between the
// strings.
if (isdigit(ca)) a++;
if (isdigit(cb)) b++;
// we'll have advanced at least one of the two strings.
goto start;
} else if (places_into_number >= 0 &&
((ca == '0' && !isdigit(cb)) || (cb == '0' && !isdigit(ca)))) {
// this clause is designed to ensure that, for example,
// "0.1" would count the same as "0.100001".
if (ca == '0') a++;
else b++;
places_into_number++;
goto start;
} else {
return false;
}
}
}
bool StringsApproxEqual(const std::string &a,
const std::string &b,
int32 decimal_places_tolerance) {
return StringsApproxEqualInternal(a.c_str(), b.c_str(),
decimal_places_tolerance, -1);
}
bool ConfigLine::ParseLine(const std::string &line) {
data_.clear();
whole_line_ = line;
if (line.size() == 0) return false; // Empty line
size_t pos = 0, size = line.size();
while (isspace(line[pos]) && pos < size) pos++;
if (pos == size)
return false; // whitespace-only line
size_t first_token_start_pos = pos;
// first get first_token_.
while (!isspace(line[pos]) && pos < size) {
if (line[pos] == '=') {
// If the first block of non-whitespace looks like "foo-bar=...",
// then we ignore it: there is no initial token, and FirstToken()
// is empty.
pos = first_token_start_pos;
break;
}
pos++;
}
first_token_ = std::string(line, first_token_start_pos, pos - first_token_start_pos);
// first_token_ is expected to be either empty or something like
// "component-node", which actually is a slightly more restrictive set of
// strings than IsValidName() checks for this is a convenient way to check it.
if (!first_token_.empty() && !IsValidName(first_token_))
return false;
while (pos < size) {
if (isspace(line[pos])) {
pos++;
continue;
}
// OK, at this point we know that we are pointing at nonspace.
size_t next_equals_sign = line.find_first_of("=", pos);
if (next_equals_sign == pos || next_equals_sign == std::string::npos) {
// we're looking for something like 'key=value'. If there is no equals sign,
// or it's not preceded by something, it's a parsing failure.
return false;
}
std::string key(line, pos, next_equals_sign - pos);
if (!IsValidName(key)) return false;
// handle any quotes. we support key='blah blah' or key="foo bar".
// no escaping is supported.
if (line[next_equals_sign+1] == '\'' || line[next_equals_sign+1] == '"') {
char my_quote = line[next_equals_sign+1];
size_t next_quote = line.find_first_of(my_quote, next_equals_sign + 2);
if (next_quote == std::string::npos) { // no matching quote was found.
KALDI_WARN << "No matching quote for " << my_quote << " in config line '"
<< line << "'";
return false;
} else {
std::string value(line, next_equals_sign + 2,
next_quote - next_equals_sign - 2);
data_.insert(std::make_pair(key, std::make_pair(value, false)));
pos = next_quote + 1;
continue;
}
} else {
// we want to be able to parse something like "... input=Offset(a, -1) foo=bar":
// in general, config values with spaces in them, even without quoting.
size_t next_next_equals_sign = line.find_first_of("=", next_equals_sign + 1),
terminating_space = size;
if (next_next_equals_sign != std::string::npos) { // found a later equals sign.
size_t preceding_space = line.find_last_of(" \t", next_next_equals_sign);
if (preceding_space != std::string::npos &&
preceding_space > next_equals_sign)
terminating_space = preceding_space;
}
while (isspace(line[terminating_space - 1]) && terminating_space > 0)
terminating_space--;
std::string value(line, next_equals_sign + 1,
terminating_space - (next_equals_sign + 1));
data_.insert(std::make_pair(key, std::make_pair(value, false)));
pos = terminating_space;
}
}
return true;
}
bool ConfigLine::GetValue(const std::string &key, std::string *value) {
KALDI_ASSERT(value != NULL);
std::map<std::string, std::pair<std::string, bool> >::iterator it = data_.begin();
for (; it != data_.end(); ++it) {
if (it->first == key) {
*value = (it->second).first;
(it->second).second = true;
return true;
}
}
return false;
}
bool ConfigLine::GetValue(const std::string &key, BaseFloat *value) {
KALDI_ASSERT(value != NULL);
std::map<std::string, std::pair<std::string, bool> >::iterator it = data_.begin();
for (; it != data_.end(); ++it) {
if (it->first == key) {
if (!ConvertStringToReal((it->second).first, value))
return false;
(it->second).second = true;
return true;
}
}
return false;
}
bool ConfigLine::GetValue(const std::string &key, int32 *value) {
KALDI_ASSERT(value != NULL);
std::map<std::string, std::pair<std::string, bool> >::iterator it = data_.begin();
for (; it != data_.end(); ++it) {
if (it->first == key) {
if (!ConvertStringToInteger((it->second).first, value))
return false;
(it->second).second = true;
return true;
}
}
return false;
}
bool ConfigLine::GetValue(const std::string &key, std::vector<int32> *value) {
KALDI_ASSERT(value != NULL);
value->clear();
std::map<std::string, std::pair<std::string, bool> >::iterator it = data_.begin();
for (; it != data_.end(); ++it) {
if (it->first == key) {
if (!SplitStringToIntegers((it->second).first, ":,", true, value)) {
// KALDI_WARN << "Bad option " << (it->second).first;
return false;
}
(it->second).second = true;
return true;
}
}
return false;
}
bool ConfigLine::GetValue(const std::string &key, bool *value) {
KALDI_ASSERT(value != NULL);
std::map<std::string, std::pair<std::string, bool> >::iterator it = data_.begin();
for (; it != data_.end(); ++it) {
if (it->first == key) {
if ((it->second).first.size() == 0) return false;
switch (((it->second).first)[0]) {
case 'F':
case 'f':
*value = false;
break;
case 'T':
case 't':
*value = true;
break;
default:
return false;
}
(it->second).second = true;
return true;
}
}
return false;
}
bool ConfigLine::HasUnusedValues() const {
std::map<std::string, std::pair<std::string, bool> >::const_iterator it = data_.begin();
for (; it != data_.end(); ++it) {
if (!(it->second).second) return true;
}
return false;
}
std::string ConfigLine::UnusedValues() const {
std::string unused_str;
std::map<std::string, std::pair<std::string, bool> >::const_iterator it = data_.begin();
for (; it != data_.end(); ++it) {
if (!(it->second).second) {
if (unused_str == "")
unused_str = it->first + "=" + (it->second).first;
else
unused_str += " " + it->first + "=" + (it->second).first;
}
}
return unused_str;
}
//// This is like ExpectToken but for two tokens, and it
//// will either accept token1 and then token2, or just token2.
//// This is useful in Read functions where the first token
//// may already have been consumed.
//void ExpectOneOrTwoTokens(std::istream &is, bool binary,
// const std::string &token1,
// const std::string &token2) {
// KALDI_ASSERT(token1 != token2);
// std::string temp;
// ReadToken(is, binary, &temp);
// if (temp == token1) {
// ExpectToken(is, binary, token2);
// } else {
// if (temp != token2) {
// KALDI_ERR << "Expecting token " << token1 << " or " << token2
// << " but got " << temp;
// }
// }
//}
bool IsValidName(const std::string &name) {
if (name.size() == 0) return false;
for (size_t i = 0; i < name.size(); i++) {
if (i == 0 && !isalpha(name[i]) && name[i] != '_')
return false;
if (!isalnum(name[i]) && name[i] != '_' && name[i] != '-' && name[i] != '.')
return false;
}
return true;
}
void ReadConfigLines(std::istream &is,
std::vector<std::string> *lines) {
KALDI_ASSERT(lines != NULL);
std::string line;
while (std::getline(is, line)) {
if (line.size() == 0) continue;
size_t start = line.find_first_not_of(" \t");
size_t end = line.find_first_of('#');
if (start == std::string::npos || start == end) continue;
end = line.find_last_not_of(" \t", end - 1);
KALDI_ASSERT(end >= start);
lines->push_back(line.substr(start, end - start + 1));
}
}
void ParseConfigLines(const std::vector<std::string> &lines,
std::vector<ConfigLine> *config_lines) {
config_lines->resize(lines.size());
for (size_t i = 0; i < lines.size(); i++) {
bool ret = (*config_lines)[i].ParseLine(lines[i]);
if (!ret) {
KALDI_ERR << "Error parsing config line: " << lines[i];
}
}
}
} // end namespace kaldi
+281
View File
@@ -0,0 +1,281 @@
// util/text-utils.h
// Copyright 2009-2011 Saarland University; Microsoft Corporation
// See ../../COPYING for clarification regarding multiple authors
//
// 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
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#ifndef KALDI_UTIL_TEXT_UTILS_H_
#define KALDI_UTIL_TEXT_UTILS_H_
#include <errno.h>
#include <string>
#include <algorithm>
#include <map>
#include <set>
#include <vector>
#include <limits>
#include "base/kaldi-common.h"
namespace kaldi {
/// Split a string using any of the single character delimiters.
/// If omit_empty_strings == true, the output will contain any
/// nonempty strings after splitting on any of the
/// characters in the delimiter. If omit_empty_strings == false,
/// the output will contain n+1 strings if there are n characters
/// in the set "delim" within the input string. In this case
/// the empty string is split to a single empty string.
void SplitStringToVector(const std::string &full, const char *delim,
bool omit_empty_strings,
std::vector<std::string> *out);
/// Joins the elements of a vector of strings into a single string using
/// "delim" as the delimiter. If omit_empty_strings == true, any empty strings
/// in the vector are skipped. A vector of empty strings results in an empty
/// string on the output.
void JoinVectorToString(const std::vector<std::string> &vec_in,
const char *delim, bool omit_empty_strings,
std::string *str_out);
/**
\brief Split a string (e.g. 1:2:3) into a vector of integers.
\param [in] delim String containing a list of characters, any of which
is allowed as a delimiter.
\param [in] omit_empty_strings If true, empty strings between delimiters are
allowed and will not produce an output integer; if false,
instances of characters in 'delim' that are consecutive or
at the start or end of the string would be an error.
You'll normally want this to be true if 'delim' consists
of spaces, and false otherwise.
\param [out] out The output list of integers.
*/
template<class I>
bool SplitStringToIntegers(const std::string &full,
const char *delim,
bool omit_empty_strings, // typically false [but
// should probably be true
// if "delim" is spaces].
std::vector<I> *out) {
KALDI_ASSERT(out != NULL);
KALDI_ASSERT_IS_INTEGER_TYPE(I);
if (*(full.c_str()) == '\0') {
out->clear();
return true;
}
std::vector<std::string> split;
SplitStringToVector(full, delim, omit_empty_strings, &split);
out->resize(split.size());
for (size_t i = 0; i < split.size(); i++) {
const char *this_str = split[i].c_str();
char *end = NULL;
int64 j = 0;
j = KALDI_STRTOLL(this_str, &end);
if (end == this_str || *end != '\0') {
out->clear();
return false;
} else {
I jI = static_cast<I>(j);
if (static_cast<int64>(jI) != j) {
// output type cannot fit this integer.
out->clear();
return false;
}
(*out)[i] = jI;
}
}
return true;
}
// This is defined for F = float and double.
template<class F>
bool SplitStringToFloats(const std::string &full,
const char *delim,
bool omit_empty_strings, // typically false
std::vector<F> *out);
/// Converts a string into an integer via strtoll and returns false if there was
/// any kind of problem (i.e. the string was not an integer or contained extra
/// non-whitespace junk, or the integer was too large to fit into the type it is
/// being converted into). Only sets *out if everything was OK and it returns
/// true.
template<class Int>
bool ConvertStringToInteger(const std::string &str,
Int *out) {
KALDI_ASSERT_IS_INTEGER_TYPE(Int);
const char *this_str = str.c_str();
char *end = NULL;
errno = 0;
int64 i = KALDI_STRTOLL(this_str, &end);
if (end != this_str)
while (isspace(*end)) end++;
if (end == this_str || *end != '\0' || errno != 0)
return false;
Int iInt = static_cast<Int>(i);
if (static_cast<int64>(iInt) != i ||
(i < 0 && !std::numeric_limits<Int>::is_signed)) {
return false;
}
*out = iInt;
return true;
}
/// ConvertStringToReal converts a string into either float or double
/// and returns false if there was any kind of problem (i.e. the string
/// was not a floating point number or contained extra non-whitespace junk).
/// Be careful- this function will successfully read inf's or nan's.
template <typename T>
bool ConvertStringToReal(const std::string &str,
T *out);
/// Removes the beginning and trailing whitespaces from a string
void Trim(std::string *str);
/// Removes leading and trailing white space from the string, then splits on the
/// first section of whitespace found (if present), putting the part before the
/// whitespace in "first" and the rest in "rest". If there is no such space,
/// everything that remains after removing leading and trailing whitespace goes
/// in "first".
void SplitStringOnFirstSpace(const std::string &line,
std::string *first,
std::string *rest);
/// Returns true if "token" is nonempty, and all characters are
/// printable and whitespace-free.
bool IsToken(const std::string &token);
/// Returns true if "line" is free of \n characters and unprintable
/// characters, and does not contain leading or trailing whitespace.
bool IsLine(const std::string &line);
/**
This function returns true when two text strings are approximately equal, and
false when they are not. The definition of 'equal' is normal string
equality, except that two substrings like "0.31134" and "0.311341" would be
considered equal. 'decimal_places_tolerance' controls how many digits after
the '.' have to match up.
E.g. StringsApproxEqual("hello 0.23 there", "hello 0.24 there", 2) would
return false because there is a difference in the 2nd decimal, but with
an argument of 1 it would return true.
*/
bool StringsApproxEqual(const std::string &a,
const std::string &b,
int32 decimal_places_check = 2);
/**
This class is responsible for parsing input like
hi-there xx=yyy a=b c empty= f-oo=Append(bar, sss) ba_z=123 bing='a b c' baz="a b c d='a b' e"
and giving you access to the fields, in this case
FirstToken() == "hi-there", and key->value pairs:
xx->yyy, a->"b c", empty->"", f-oo->"Append(bar, sss)", ba_z->"123",
bing->"a b c", baz->"a b c d='a b' e"
The first token is optional, if the line started with a key-value pair then
FirstValue() will be empty.
Note: it can parse value fields with space inside them only if they are free of the '='
character. If values are going to contain the '=' character, you need to quote them
with either single or double quotes.
Key values may contain -_a-zA-Z0-9, but must begin with a-zA-Z_.
*/
class ConfigLine {
public:
// Tries to parse the line as a config-file line. Returns false
// if it could not for some reason, e.g. parsing failure. In most cases
// prints no warnings; the user should do this. Does not expect comments.
bool ParseLine(const std::string &line);
// the GetValue functions are overloaded for various types. They return true
// if the key exists with value that can be converted to that type, and false
// otherwise. They also mark the key-value pair as having been read. It is
// not an error to read values twice.
bool GetValue(const std::string &key, std::string *value);
bool GetValue(const std::string &key, BaseFloat *value);
bool GetValue(const std::string &key, int32 *value);
// Values may be separated by ":" or by ",".
bool GetValue(const std::string &key, std::vector<int32> *value);
bool GetValue(const std::string &key, bool *value);
bool HasUnusedValues() const;
/// returns e.g. foo=bar xxx=yyy if foo and xxx were not consumed by one
/// of the GetValue() functions.
std::string UnusedValues() const;
const std::string &FirstToken() const { return first_token_; }
const std::string WholeLine() { return whole_line_; }
// use default assignment operator and copy constructor.
private:
std::string whole_line_;
// the first token of the line, e.g. if line is
// foo-bar baz=bing
// then first_token_ would be "foo-bar".
std::string first_token_;
// data_ maps from key to (value, is-this-value-consumed?).
std::map<std::string, std::pair<std::string, bool> > data_;
};
/// This function is like ExpectToken but for two tokens, and it will either
/// accept token1 and then token2, or just token2. This is useful in Read
/// functions where the first token may already have been consumed.
void ExpectOneOrTwoTokens(std::istream &is, bool binary,
const std::string &token1,
const std::string &token2);
/**
This function reads in a config file and *appends* its contents to a vector of
lines; it is responsible for removing comments (anything after '#') and
stripping out any lines that contain only whitespace after comment removal.
*/
void ReadConfigLines(std::istream &is,
std::vector<std::string> *lines);
/**
This function converts config-lines from a simple sequence of strings
as output by ReadConfigLines(), into a sequence of first-tokens and
name-value pairs. The general format is:
"command-type bar=baz xx=yyy"
etc., although there are subtleties as to what exactly is allowed, see
documentation for class ConfigLine for details.
This function will die if there was a parsing failure.
*/
void ParseConfigLines(const std::vector<std::string> &lines,
std::vector<ConfigLine> *config_lines);
/// Returns true if 'name' would be a valid name for a component or node in a
/// nnet3Nnet. This is a nonempty string beginning with A-Za-z_, and containing only
/// '-', '_', '.', A-Z, a-z, or 0-9.
bool IsValidName(const std::string &name);
} // namespace kaldi
#endif // KALDI_UTIL_TEXT_UTILS_H_