#include "buildstate.h"
#include "position.h"
#include "position_dump.h"
#include "position_info.h"
#include "parse_error.h"
#include "hs_internal.h"
#include "ue2common.h"
#include "nfagraph/ng_builder.h"
#include "util/charreach.h"
#include "util/container.h"
#include "util/flat_containers.h"
#include "util/hash.h"
#include "util/make_unique.h"
#include "util/unordered.h"
#include <algorithm>
#include <iterator>
#include <limits>
#include <map>
#include <utility>
#if defined(DEBUG) || defined(DUMP_SUPPORT)
#include <ostream>
#include <sstream>
#endif
using namespace std;
namespace ue2 {
const Position GlushkovBuildState::POS_UNINITIALIZED =
numeric_limits<Position>::max();
const Position GlushkovBuildState::POS_EPSILON =
numeric_limits<Position>::max() - 1;
GlushkovBuildState::~GlushkovBuildState() { }
namespace {
class CheckPositionFlags {
public:
explicit CheckPositionFlags(int fl) : flags(fl) {}
bool operator()(const PositionInfo &p) const {
return (p.flags & flags) == flags;
}
private:
int flags;
};
class CheckUnflaggedEpsilon {
public:
bool operator()(const PositionInfo &p) const {
return p.pos == GlushkovBuildState::POS_EPSILON && p.flags == 0;
}
};
class GlushkovBuildStateImpl : public GlushkovBuildState {
public:
GlushkovBuildStateImpl(NFABuilder &b, bool prefilter);
NFABuilder &getBuilder() override { return builder; }
const NFABuilder &getBuilder() const override { return builder; }
void connectRegions(const vector<PositionInfo> &lasts,
const vector<PositionInfo> &firsts) override;
void connectAccepts(const vector<PositionInfo> &lasts) override;
void connectSuccessors(const PositionInfo &last,
vector<PositionInfo> firsts);
void addSuccessor(Position from, Position to) override;
void cloneFollowSet(Position from, Position to, unsigned offset) override;
void buildEdges() override;
void buildEdge(Position from, const PositionInfo &to);
Position startState;
Position startDotstarState;
Position acceptState;
Position acceptEodState;
Position acceptNlEodState;
Position acceptNlState;
NFABuilder &builder;
bool doPrefilter;
map<Position, flat_set<PositionInfo>> successors;
};
}
GlushkovBuildStateImpl::GlushkovBuildStateImpl(NFABuilder &b,
bool prefilter) :
startState(b.getStart()),
startDotstarState(b.getStartDotStar()),
acceptState(b.getAccept()),
acceptEodState(b.getAcceptEOD()),
acceptNlEodState(POS_UNINITIALIZED),
acceptNlState(POS_UNINITIALIZED),
builder(b),
doPrefilter(prefilter)
{
vector<PositionInfo> lasts, firsts;
lasts.push_back(startState);
lasts.push_back(startDotstarState);
firsts.push_back(startDotstarState);
connectRegions(lasts, firsts);
builder.setNodeReportID(startState, 0);
builder.setNodeReportID(startDotstarState, 0);
}
static
void checkEmbeddedEndAnchor(const PositionInfo &from,
const vector<PositionInfo> &firsts) {
if (!(from.flags & POS_FLAG_ONLY_ENDS)) {
return;
}
for (const auto &first : firsts) {
if (first.pos != GlushkovBuildStateImpl::POS_EPSILON) {
throw ParseError("Embedded end anchors not supported.");
}
}
}
void
GlushkovBuildStateImpl::connectRegions(const vector<PositionInfo> &lasts,
const vector<PositionInfo> &firsts) {
for (const auto &last : lasts) {
checkEmbeddedEndAnchor(last, firsts);
connectSuccessors(last, firsts);
}
}
static
void filterEdges(const GlushkovBuildStateImpl &bs, const PositionInfo &from,
vector<PositionInfo> &tolist) {
if (from.pos == bs.startDotstarState) {
CheckPositionFlags check(POS_FLAG_NOFLOAT);
tolist.erase(remove_if(tolist.begin(), tolist.end(), check),
tolist.end());
if (from.flags & POS_FLAG_NOFLOAT) {
tolist.clear();
}
} else if (from.pos == bs.startState) {
CheckUnflaggedEpsilon check;
tolist.erase(remove_if(tolist.begin(), tolist.end(), check),
tolist.end());
CheckPositionFlags check2(POS_FLAG_MUST_FLOAT | POS_FLAG_NOFLOAT);
tolist.erase(remove_if(tolist.begin(), tolist.end(), check2),
tolist.end());
}
if (bs.builder.getAssertFlag(from.pos) & POS_FLAG_MULTILINE_START) {
DEBUG_PRINTF("multiline start - no eod\n");
tolist.erase(remove(tolist.begin(), tolist.end(), bs.acceptEodState),
tolist.end());
}
}
static
Position makeNewlineAssertPos(GlushkovBuildState &bs) {
NFABuilder &builder = bs.getBuilder();
Position newline = builder.makePositions(1);
builder.addCharReach(newline, CharReach('\n'));
builder.setAssertFlag(newline, POS_FLAG_FIDDLE_ACCEPT);
builder.setNodeReportID(newline, -1);
return newline;
}
static
void generateAccepts(GlushkovBuildStateImpl &bs, const PositionInfo &from,
vector<PositionInfo> *tolist) {
NFABuilder &builder = bs.getBuilder();
u32 flags = from.flags;
bool require_eod = flags & POS_FLAG_WIRE_EOD;
bool require_nl_eod = flags & POS_FLAG_WIRE_NL_EOD
&& !(flags & POS_FLAG_NO_NL_EOD);
bool require_nl_accept = (flags & POS_FLAG_WIRE_NL_ACCEPT)
&& !(flags & POS_FLAG_NO_NL_ACCEPT);
bool require_accept = !(flags & POS_FLAG_ONLY_ENDS);
if (require_eod) {
tolist->push_back(bs.acceptEodState);
}
if (require_nl_accept) {
if (bs.acceptNlState == GlushkovBuildState::POS_UNINITIALIZED) {
Position newline = makeNewlineAssertPos(bs);
bs.addSuccessor(newline, builder.getAccept());
bs.acceptNlState = newline;
}
tolist->push_back(bs.acceptNlState);
}
if (require_nl_eod) {
if (bs.acceptNlEodState == GlushkovBuildState::POS_UNINITIALIZED) {
Position newline = makeNewlineAssertPos(bs);
bs.addSuccessor(newline, builder.getAcceptEOD());
bs.acceptNlEodState = newline;
}
tolist->push_back(bs.acceptNlEodState);
}
if (require_accept) {
tolist->push_back(bs.acceptState);
}
}
void GlushkovBuildStateImpl::connectAccepts(const vector<PositionInfo> &lasts) {
for (const auto &last : lasts) {
vector<PositionInfo> accepts;
generateAccepts(*this, last, &accepts);
connectSuccessors(last, accepts);
}
}
#if defined(DEBUG) || defined(DUMP_SUPPORT)
static UNUSED
string dumpCaptures(const PositionInfo &p) {
ostringstream oss;
if (p.flags & POS_FLAG_NOFLOAT) {
oss << "<nofloat>";
}
if (p.flags & POS_FLAG_MUST_FLOAT) {
oss << "<must_float>";
}
if (p.flags & POS_FLAG_FIDDLE_ACCEPT) {
oss << "<fiddle_accept>";
}
if (p.flags & POS_FLAG_ONLY_ENDS) {
oss << "<only_ends>";
}
if (p.flags & POS_FLAG_NO_NL_EOD) {
oss << "<no_nl_eod>";
}
if (p.flags & POS_FLAG_NO_NL_ACCEPT) {
oss << "<no_nl_acc>";
}
return oss.str();
}
#endif
void GlushkovBuildStateImpl::connectSuccessors(const PositionInfo &from,
vector<PositionInfo> tolist) {
assert(from.pos != POS_EPSILON);
assert(from.pos != POS_UNINITIALIZED);
assert(find(tolist.begin(), tolist.end(), POS_UNINITIALIZED)
== tolist.end());
DEBUG_PRINTF("FROM = %u%s TO = %s\n", from.pos, dumpCaptures(from).c_str(),
dumpPositions(tolist.begin(), tolist.end()).c_str());
filterEdges(*this, from, tolist);
if (from.flags & POS_FLAG_FIDDLE_ACCEPT) {
auto accept = find(tolist.begin(), tolist.end(), acceptState);
if (accept != tolist.end()) {
DEBUG_PRINTF("accept through -1 offset-adjusting dot\n");
Position fakedot = builder.makePositions(1);
builder.addCharReach(fakedot, CharReach(0x00, 0xff));
builder.setNodeReportID(fakedot, -1);
addSuccessor(fakedot, acceptState);
*accept = fakedot;
} else {
builder.setNodeReportID(from.pos, -1);
}
assert(find(tolist.begin(), tolist.end(), acceptState) == tolist.end());
}
auto &succ = successors[from.pos];
DEBUG_PRINTF("connect %u -> %s\n", from.pos,
dumpPositions(tolist.begin(), tolist.end()).c_str());
DEBUG_PRINTF("%u curr succ: %s\n", from.pos,
dumpPositions(begin(succ), end(succ)).c_str());
for (const auto &to : tolist) {
if (to.pos != POS_EPSILON) {
succ.insert(to);
}
}
DEBUG_PRINTF("%u succ: %s\n", from.pos,
dumpPositions(begin(succ), end(succ)).c_str());
}
void GlushkovBuildStateImpl::addSuccessor(Position from, Position to) {
DEBUG_PRINTF("connect %u -> %u\n", from, to);
assert(from != POS_EPSILON && from != POS_UNINITIALIZED);
assert(to != POS_EPSILON && to != POS_UNINITIALIZED);
auto &succ = successors[from];
succ.insert(to);
DEBUG_PRINTF("%u succ: %s\n", from,
dumpPositions(begin(succ), end(succ)).c_str());
}
void GlushkovBuildStateImpl::cloneFollowSet(Position first, Position last,
unsigned offset) {
assert(first <= last);
builder.cloneRegion(first, last, offset);
for (Position i = first; i <= last; i++) {
assert(successors[i + offset].empty());
for (const PositionInfo &to : successors[i]) {
if (to.pos >= first && to.pos <= last) {
PositionInfo clone(to);
clone.pos += offset;
DEBUG_PRINTF("clone: %u -> %u\n", i + offset, clone.pos);
successors[i + offset].insert(clone);
} else {
assert(0);
}
}
}
}
void GlushkovBuildStateImpl::buildEdge(Position from, const PositionInfo &to) {
if (to == startState) {
throw ParseError("Embedded start anchors not supported.");
}
assert(to.pos != POS_UNINITIALIZED);
assert(to.pos != POS_EPSILON);
if (builder.hasEdge(from, to.pos)) {
return;
}
builder.addEdge(from, to.pos);
}
void GlushkovBuildStateImpl::buildEdges() {
for (const auto &m : successors) {
const Position from = m.first;
for (const auto &to : m.second) {
buildEdge(from, to);
}
}
}
unique_ptr<GlushkovBuildState> makeGlushkovBuildState(NFABuilder &b,
bool prefilter) {
return ue2::make_unique<GlushkovBuildStateImpl>(b, prefilter);
}
void cleanupPositions(vector<PositionInfo> &a) {
ue2_unordered_set<pair<Position, int>> seen;
vector<PositionInfo> out;
out.reserve(a.size());
for (const auto &p : a) {
if (seen.emplace(p.pos, p.flags).second) {
out.push_back(p); }
}
DEBUG_PRINTF("in %zu; out %zu\n", a.size(), out.size());
a.swap(out);
}
static
vector<PositionInfo>::iterator
replaceElemWithSequence(vector<PositionInfo> &dest,
vector<PositionInfo>::iterator &victim,
const vector<PositionInfo> &replacement) {
auto past = dest.erase(victim);
size_t d = distance(dest.begin(), past) + replacement.size();
dest.insert(past, replacement.begin(), replacement.end());
return dest.begin() + d;
}
void replaceEpsilons(vector<PositionInfo> &target,
const vector<PositionInfo> &source) {
auto found =
find(target.begin(), target.end(), GlushkovBuildState::POS_EPSILON);
if (found == target.end()) {
target.insert(target.end(), source.begin(), source.end());
return;
}
while (found != target.end()) {
checkEmbeddedEndAnchor(*found, source);
vector<PositionInfo> newsource(source);
for (auto &pos : newsource) {
pos.flags |= found->flags;
}
found = replaceElemWithSequence(target, found, newsource);
found = find(found, target.end(), GlushkovBuildState::POS_EPSILON);
}
cleanupPositions(target);
}
#ifdef DUMP_SUPPORT
void dump(ostream &os, const PositionInfo &p) {
if (p.pos == GlushkovBuildState::POS_EPSILON) {
os << "epsilon";
} else {
os << p.pos;
}
os << dumpCaptures(p);
}
#endif
}