refactor and reuse Automatas. TODO : add test for automatas
This commit is contained in:
parent
184f8dd1fe
commit
8f38095bec
3 changed files with 166 additions and 318 deletions
|
|
@ -317,12 +317,13 @@ namespace tp {
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
void append(Arg data) {
|
tType& append(Arg data) {
|
||||||
if (mLoad == mSize) {
|
if (mLoad == mSize) {
|
||||||
resizeBuffer(tResizePolicy(mSize));
|
resizeBuffer(tResizePolicy(mSize));
|
||||||
}
|
}
|
||||||
new (&mBuff[mLoad]) tType(data);
|
new (&mBuff[mLoad]) tType(data);
|
||||||
mLoad++;
|
mLoad++;
|
||||||
|
return mBuff[mLoad - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
void append(const Buffer& in) {
|
void append(const Buffer& in) {
|
||||||
|
|
|
||||||
|
|
@ -299,6 +299,14 @@ namespace tp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void transferNodes(List in) {
|
||||||
|
removeAll();
|
||||||
|
for (auto node : in) {
|
||||||
|
attach(node);
|
||||||
|
}
|
||||||
|
in.detachAll();
|
||||||
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
template <class tArchiver>
|
template <class tArchiver>
|
||||||
void archiveWrite(tArchiver& ar) const {
|
void archiveWrite(tArchiver& ar) const {
|
||||||
|
|
|
||||||
|
|
@ -4,78 +4,90 @@
|
||||||
#include "Utils.hpp"
|
#include "Utils.hpp"
|
||||||
#include "List.hpp"
|
#include "List.hpp"
|
||||||
#include "Map.hpp"
|
#include "Map.hpp"
|
||||||
|
#include "Tree.hpp"
|
||||||
|
|
||||||
namespace tp {
|
namespace tp {
|
||||||
|
|
||||||
// Non-Deterministic Finite-State Automata
|
// Non-Deterministic Finite-State Automata
|
||||||
template <typename tAlphabetType, typename tStateType>
|
template <typename tAlphabetType, typename tStateType>
|
||||||
class NFA {
|
class FiniteStateAutomation {
|
||||||
|
public:
|
||||||
|
struct State;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
struct Vertex;
|
class Transition {
|
||||||
|
friend FiniteStateAutomation;
|
||||||
|
|
||||||
struct Edge {
|
public:
|
||||||
Vertex* mVertex = nullptr;
|
enum Type { ANY, EPSILON, SYMBOL };
|
||||||
bool mConsumesSymbol = false;
|
|
||||||
Range<tAlphabetType> mAcceptingRange;
|
|
||||||
bool mAcceptsAll = false;
|
|
||||||
bool mExclude = false;
|
|
||||||
|
|
||||||
bool isTransition(const tAlphabetType& symbol) {
|
public:
|
||||||
if (symbol == 0) return false;
|
Transition(Type type, State* state, tAlphabetType symbol = tAlphabetType()) {
|
||||||
if (!mConsumesSymbol || mAcceptsAll) return true;
|
mState = state;
|
||||||
bool const in_range = (symbol >= mAcceptingRange.mBegin && symbol <= mAcceptingRange.mEnd);
|
mType = type;
|
||||||
return in_range != mExclude;
|
mSymbol = symbol;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool isTransition(const tAlphabetType& symbol) const {
|
||||||
|
return (mType == ANY || mType == EPSILON) || (mSymbol == symbol);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool doesConsumes(const tAlphabetType& symbol) const {
|
||||||
|
return (mType == ANY || (mType == SYMBOL && mSymbol == symbol));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool isEpsilon() const { return mType == EPSILON; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
State* mState = nullptr;
|
||||||
|
Type mType;
|
||||||
|
tAlphabetType mSymbol;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Vertex {
|
class State {
|
||||||
List<Edge> edges;
|
friend FiniteStateAutomation;
|
||||||
tStateType state{};
|
|
||||||
bool isTermination = false;
|
State() = default;
|
||||||
ualni debug_idx = 0;
|
|
||||||
ualni flag = 0;
|
public:
|
||||||
|
void setValue(const tStateType& stateValue) { mStateVal = stateValue; }
|
||||||
|
void setAcceptance(bool isAccepting) { mIsAccepting = isAccepting; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
Buffer<Transition> mTransitions;
|
||||||
|
tStateType mStateVal = tStateType();
|
||||||
|
bool mIsAccepting = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
private:
|
||||||
List<Vertex> mVertices;
|
List<State> mStates;
|
||||||
Vertex* mStart = nullptr;
|
const State* mStartState = nullptr;
|
||||||
Buffer<tAlphabetType> mAllSymbols;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
NFA() = default;
|
FiniteStateAutomation() = default;
|
||||||
|
|
||||||
Vertex* addVertex(const tStateType& state, bool termination) {
|
State* addState(const tStateType& state, bool accepting) {
|
||||||
auto node = mVertices.newNode();
|
auto node = mStates.newNode();
|
||||||
node->data.isTermination = termination;
|
node->data.mIsAccepting = accepting;
|
||||||
node->data.state = state;
|
node->data.mState = state;
|
||||||
node->data.debug_idx = mVertices.length() + 1;
|
mStates.pushBack(node);
|
||||||
mVertices.pushBack(node);
|
|
||||||
return &node->data;
|
return &node->data;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void addTransition(State* from, State* to, const tAlphabetType& symbol) {
|
||||||
addTransition(Vertex* from, Vertex* to, Range<tAlphabetType> range, bool consumes, bool accepts_all, bool exclude) {
|
from->edges.pushBack(Transition(Transition::SYMBOL, to, symbol));
|
||||||
Edge edge;
|
|
||||||
edge.mVertex = to;
|
|
||||||
edge.mConsumesSymbol = consumes;
|
|
||||||
edge.mAcceptingRange = range;
|
|
||||||
edge.mExclude = exclude;
|
|
||||||
edge.mAcceptsAll = accepts_all;
|
|
||||||
from->edges.pushBack(edge);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void setStartVertex(Vertex* start) { mStart = start; }
|
void addEpsilonTransition(State* from, State* to) { from->edges.pushBack(Transition(Transition::SYMBOL, to)); }
|
||||||
|
|
||||||
[[nodiscard]] Vertex* getStartVertex() const { return mStart; }
|
void addAnyTransition(State* from, State* to) { from->edges.pushBack(Transition(Transition::ANY, to)); }
|
||||||
|
|
||||||
void setVertexState(Vertex* vertex, const tStateType& state, bool terminated) {
|
void setStartVertex(State* start) { mStartState = start; }
|
||||||
vertex->state = state;
|
|
||||||
vertex->isTermination = terminated;
|
[[nodiscard]] State* getStartState() const { return mStartState; }
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] bool isValid() const {
|
[[nodiscard]] bool isValid() const {
|
||||||
if (!mStart) {
|
if (!mStartState) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -83,285 +95,112 @@ namespace tp {
|
||||||
|
|
||||||
// vertices that are reachable from initial set with no input consumption (E-transitions)
|
// vertices that are reachable from initial set with no input consumption (E-transitions)
|
||||||
// does not include initial set
|
// does not include initial set
|
||||||
void closure(const List<Vertex*>& set, List<Vertex*>& closure, ualni unique_call_id) {
|
void findClosureSet(const Buffer<State*>& from, Buffer<State*>& closureSet) const {
|
||||||
List<Vertex*> marked;
|
Map<State*, bool> lookup;
|
||||||
marked = set;
|
List<State*> workingSet;
|
||||||
|
|
||||||
while (marked.length()) {
|
for (auto item : from) {
|
||||||
auto first = marked.first()->data;
|
workingSet.pushBack(item.data());
|
||||||
if (first->flag != unique_call_id) {
|
}
|
||||||
first->flag = unique_call_id;
|
|
||||||
closure.pushBack(first);
|
while (workingSet.length()) {
|
||||||
|
auto first = workingSet.first()->data;
|
||||||
|
closureSet.append(first);
|
||||||
|
|
||||||
|
for (auto edge : first->mTransitions) {
|
||||||
|
if (!edge.data().isEpsilon()) continue;
|
||||||
|
if (lookup.presents(edge.data().mState)) continue;
|
||||||
|
workingSet.pushBack(edge.data().mState);
|
||||||
}
|
}
|
||||||
for (auto edge : first->edges) {
|
|
||||||
if (!edge.data().mConsumesSymbol && edge.data().mVertex->flag != unique_call_id) {
|
workingSet.popFront();
|
||||||
marked.pushBack(edge.data().mVertex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
marked.popFront();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// vertices that are reachable from initial set with symbol transition
|
// vertices that are reachable from initial set with symbol transition
|
||||||
void move(const List<Vertex*>& set, List<Vertex*>& reachable, tAlphabetType symbol, ualni unique_call_id) {
|
void findMoveSet(const Buffer<State*>& from, Buffer<State*>& moveSet, tAlphabetType symbol) const {
|
||||||
for (auto vertex : set) {
|
Map<State*, bool> lookup;
|
||||||
for (auto edge : vertex->edges) {
|
|
||||||
if (!edge.data().mConsumesSymbol) continue;
|
for (auto vertex : from) {
|
||||||
bool transition = edge.data().isTransition(symbol);
|
for (auto edge : vertex.mTransitions) {
|
||||||
if (transition && edge.data().mVertex->flag != unique_call_id) {
|
if (edge.data().isepsilon()) continue;
|
||||||
edge.data().mVertex->flag = unique_call_id;
|
if (!edge.data().isTransition(symbol)) continue;
|
||||||
reachable.pushBack(edge.data().mVertex);
|
if (lookup.presents(edge.data().mState)) continue;
|
||||||
|
moveSet.append(edge.data().mState);
|
||||||
|
lookup.put(edge.data().mState, {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename tAlphabetIterator>
|
||||||
|
bool makeDeterministic(const tAlphabetIterator& allSymbols) {
|
||||||
|
if (!isValid()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Group {
|
||||||
|
Buffer<State*> states;
|
||||||
|
AvlTree<Group*, tAlphabetType> transitions;
|
||||||
|
State* newState = nullptr;
|
||||||
|
bool accepting = false;
|
||||||
|
tStateType stateVal = tStateType();
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GroupKey {
|
||||||
|
const Group* group;
|
||||||
|
};
|
||||||
|
|
||||||
|
Buffer<Group> newStates = { {} };
|
||||||
|
|
||||||
|
// 1) find new states
|
||||||
|
Map<GroupKey, bool> lookup;
|
||||||
|
List<Group*> workingSet;
|
||||||
|
|
||||||
|
findClosureSet({ getStartState() }, newStates.first().states);
|
||||||
|
|
||||||
|
workingSet.pushBack(&newStates.first());
|
||||||
|
while (workingSet.length()) {
|
||||||
|
auto group = workingSet.first();
|
||||||
|
|
||||||
|
for (auto symbol : allSymbols) {
|
||||||
|
|
||||||
|
// calculate new possible state
|
||||||
|
Group potentialGroup;
|
||||||
|
findMoveSet(group.states, potentialGroup.states, symbol);
|
||||||
|
if (!potentialGroup.states.size()) continue;
|
||||||
|
|
||||||
|
// find existing or create group
|
||||||
|
Group* targetGroup = nullptr;
|
||||||
|
auto iter = lookup.presents({ &potentialGroup });
|
||||||
|
if (iter) {
|
||||||
|
targetGroup = lookup.getSlotVal(iter);
|
||||||
|
} else {
|
||||||
|
targetGroup = newStates.append({});
|
||||||
|
lookup.put({ targetGroup }, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// add transition
|
||||||
|
group.transitions.insert(targetGroup, symbol);
|
||||||
|
}
|
||||||
|
|
||||||
|
workingSet.popFront();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) find new states termination values
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// 3) transfer
|
||||||
|
mStates.removeAll();
|
||||||
|
|
||||||
|
for (auto group : newStates) {
|
||||||
|
group.newState = addState();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto group : newStates) {
|
||||||
|
for (auto transition : group.transitions) {
|
||||||
|
addTransition(/* ... */);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
}
|
||||||
// Deterministic Finite-State Automata
|
|
||||||
template <typename tAlphabetType, typename tStateType>
|
|
||||||
class DFA {
|
|
||||||
public:
|
|
||||||
struct Vertex {
|
|
||||||
struct Edge {
|
|
||||||
Vertex* vertex = nullptr;
|
|
||||||
tAlphabetType transition_code = nullptr;
|
|
||||||
};
|
|
||||||
|
|
||||||
List<Edge> edges;
|
|
||||||
tStateType state{};
|
|
||||||
bool termination = false;
|
|
||||||
bool marked = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
List<Vertex> mVertices;
|
|
||||||
Vertex* mStart = nullptr;
|
|
||||||
const Vertex* mIter = nullptr;
|
|
||||||
Buffer<tAlphabetType> mAlphabetRange;
|
|
||||||
bool mTrapState = false;
|
|
||||||
|
|
||||||
typedef typename NFA<tAlphabetType, tStateType>::Vertex NState;
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit DFA(NFA<tAlphabetType, tStateType>& nfa) {
|
|
||||||
if (!nfa.isValid()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mAlphabetRange = nfa.mAllSymbols;
|
|
||||||
|
|
||||||
struct DStateKey {
|
|
||||||
const List<NState*>* nStates;
|
|
||||||
bool operator==(const DStateKey& in) const {
|
|
||||||
if (nStates->length() != in.nStates->length()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// FIXME : make linear time
|
|
||||||
for (auto state : *nStates) {
|
|
||||||
bool found = false;
|
|
||||||
for (auto in_state : *in.nStates) {
|
|
||||||
if (state.data() == in_state.data()) {
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!found) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static ualni dStateHashFunc(DStateKey key) {
|
|
||||||
alni out = 0;
|
|
||||||
for (auto state : *key.nStates) {
|
|
||||||
out += alni(state.data());
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// all NFA states that are reachable from initial DFA State for specific symbol in alphabet
|
|
||||||
struct DState {
|
|
||||||
struct DTransition {
|
|
||||||
DState* state = nullptr;
|
|
||||||
tAlphabetType accepting_code;
|
|
||||||
};
|
|
||||||
|
|
||||||
List<NState*> nStates;
|
|
||||||
List<DTransition> transitions;
|
|
||||||
|
|
||||||
Vertex* dVertex = nullptr; // relevant DFA vertex
|
|
||||||
|
|
||||||
ualni debug_idx = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
// includes closure of NFA start state by definition
|
|
||||||
auto start_state = new DState();
|
|
||||||
nfa.closure({ nfa.getStartVertex() }, start_state->nStates, ualni(start_state));
|
|
||||||
|
|
||||||
Map<DStateKey, DState*, DefaultAllocator, DStateKey::dStateHashFunc, 256> dStates;
|
|
||||||
|
|
||||||
dStates.put({ &start_state->nStates }, start_state);
|
|
||||||
|
|
||||||
List<DState*> working_set = { start_state };
|
|
||||||
|
|
||||||
// while there is items to work with
|
|
||||||
auto currentDState = working_set.first();
|
|
||||||
while (currentDState) {
|
|
||||||
|
|
||||||
// check all possible transitions for any symbol
|
|
||||||
for (auto symbol : mAlphabetRange) {
|
|
||||||
|
|
||||||
List<NState*> reachableNStates;
|
|
||||||
|
|
||||||
nfa.move(currentDState->data->nStates, reachableNStates, symbol, ualni(currentDState + symbol));
|
|
||||||
nfa.closure(reachableNStates, reachableNStates, ualni(currentDState + symbol));
|
|
||||||
|
|
||||||
if (!reachableNStates.length()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
DState* targetDState = nullptr;
|
|
||||||
|
|
||||||
// check if set of all reachable NFA states already forms existing DFA state
|
|
||||||
auto idx = dStates.presents({ &reachableNStates });
|
|
||||||
if (idx) {
|
|
||||||
targetDState = dStates.getSlotVal(idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!targetDState) {
|
|
||||||
// register new DFA state
|
|
||||||
targetDState = new DState();
|
|
||||||
|
|
||||||
targetDState->debug_idx = dStates.size();
|
|
||||||
|
|
||||||
targetDState->nStates = reachableNStates;
|
|
||||||
|
|
||||||
// append to working stack
|
|
||||||
working_set.pushBack(targetDState);
|
|
||||||
dStates.put({ &targetDState->nStates }, targetDState);
|
|
||||||
}
|
|
||||||
|
|
||||||
// add transition to DFA state
|
|
||||||
currentDState->data->transitions.pushBack({ targetDState, symbol });
|
|
||||||
}
|
|
||||||
|
|
||||||
working_set.popFront();
|
|
||||||
currentDState = working_set.first();
|
|
||||||
}
|
|
||||||
|
|
||||||
// create own vertices
|
|
||||||
for (auto node : dStates) {
|
|
||||||
const tStateType* state = nullptr;
|
|
||||||
ualni numFound = 0;
|
|
||||||
for (auto iter : node->val->nStates) {
|
|
||||||
if (iter->isTermination) {
|
|
||||||
state = &iter->state;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (numFound != 1) {
|
|
||||||
printf("Error constructing dfa - invalid number of termination states in final node.");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
node->val->dVertex = addVertex(*state);
|
|
||||||
}
|
|
||||||
|
|
||||||
// connect all vertices
|
|
||||||
for (auto node : dStates) {
|
|
||||||
for (auto edge : node->val->transitions) {
|
|
||||||
addTransition(node->val->dVertex, edge.data().state->dVertex, edge.data().accepting_code);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// set the starting vertex
|
|
||||||
mStart = start_state->dVertex;
|
|
||||||
|
|
||||||
// cleanup
|
|
||||||
for (auto node : dStates) {
|
|
||||||
delete node->val;
|
|
||||||
}
|
|
||||||
|
|
||||||
collapseEquivalentVertices();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
tStateType move(tAlphabetType symbol) {
|
|
||||||
if (mTrapState || !mIter) {
|
|
||||||
return tNoStateVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto edge : mIter->edges) {
|
|
||||||
if (edge.data().transition_code == symbol) {
|
|
||||||
mIter = edge.data().vertex;
|
|
||||||
return mIter->termination_state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mTrapState = true;
|
|
||||||
return tNoStateVal;
|
|
||||||
}
|
|
||||||
|
|
||||||
void start() {
|
|
||||||
mIter = mStart;
|
|
||||||
mTrapState = false;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
[[nodiscard]] uhalni nVertices() const { return (uhalni) mVertices.length(); }
|
|
||||||
|
|
||||||
[[nodiscard]] const Buffer<tAlphabetType>& getRange() const { return mAlphabetRange; }
|
|
||||||
|
|
||||||
public:
|
|
||||||
[[nodiscard]] Range<tAlphabetType> getAlphabetRange() const {
|
|
||||||
Range<tAlphabetType> out;
|
|
||||||
for (auto vertex : mVertices) {
|
|
||||||
vertex.data().marked = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool first = true;
|
|
||||||
getAlphabetRangeUtil(mStart, &out, first);
|
|
||||||
out.mEnd++;
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
void getAlphabetRangeUtil(Vertex* vert, Range<tAlphabetType>* out, bool& first) const {
|
|
||||||
vert->marked = true;
|
|
||||||
for (auto edge : vert->edges) {
|
|
||||||
auto const code = edge.data().transition_code;
|
|
||||||
|
|
||||||
if (first) {
|
|
||||||
*out = { code, code };
|
|
||||||
first = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (code < out->mBegin) {
|
|
||||||
out->mBegin = code;
|
|
||||||
}
|
|
||||||
if (code > out->mEnd) {
|
|
||||||
out->mEnd = code;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!edge.data().vertex->marked) {
|
|
||||||
getAlphabetRangeUtil(edge.data().vertex, out, first);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void collapseEquivalentVertices() {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
Vertex* addVertex(const tStateType& state) {
|
|
||||||
auto node = mVertices.addNodeBack();
|
|
||||||
node->data.state = state;
|
|
||||||
return &node->data;
|
|
||||||
}
|
|
||||||
|
|
||||||
void addTransition(Vertex* from, Vertex* to, tAlphabetType transition_symbol) {
|
|
||||||
from->edges.pushBack({ to, transition_symbol });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue