-
Notifications
You must be signed in to change notification settings - Fork 1
/
graph.cpp
226 lines (186 loc) · 7.29 KB
/
graph.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
#include <iostream>
#include <vector>
#include <string>
#include <unordered_map>
#include <memory>
#include <algorithm>
#include <cassert>
struct Branch;
// Nodes of the parse graph.
struct State {
State(const std::string& name) : m_name(name) {}
std::string m_name;
std::vector<std::shared_ptr<Branch>> m_branches;
};
struct Branch {
Branch(const std::shared_ptr<State>& state) : m_nextState(state) {}
std::shared_ptr<State> m_nextState;
};
// Encodes a parse graph in the readable form that is easy to modify.
typedef std::unordered_map<std::string, std::vector<std::string>> StateToBranchedStates;
StateToBranchedStates ParseGraph = {
{"start", {"start_loop", "s1"}},
{"start_loop", {"loop_1", "s1"}},
{"loop_1", {"loop_2"}},
{"loop_2", {"start_loop", "accept"}},
{"s1", {"accept"}},
{"accept", {}}
};
// For testing a dead loop without an exit.
/*StateToBranchedStates ParseGraph = {
{"start", {"start_loop", "s1"}},
{"start_loop", {"loop_1"}},
{"loop_1", {"loop_2"}},
{"loop_2", {"start_loop"}},
{"s1", {"accept"}},
{"accept", {}}
};*/
// For testing crossing loops.
/*StateToBranchedStates ParseGraph = {
{"start", {"s1", "s2"}},
{"s1", {"s3"}},
{"s2", {"s3"}},
{"s3", {"s1", "s2", "accept"}},
{"accept", {}}
};*/
// Builds a parse tree from its readable form.
std::shared_ptr<State> createGraph(const StateToBranchedStates& graph) {
std::unordered_map<std::string, std::shared_ptr<State>> namesToStates;
for (auto& elem : graph) {
const std::string& stateName = elem.first;
auto res = namesToStates.emplace(stateName, std::make_shared<State>(stateName));
if (!res.second) {
std::cerr << stateName << ": Multiple states with same name in parse graph." << std::endl;
return std::shared_ptr<State>();
}
}
for (auto& elem : graph) {
const std::string& stateName = elem.first;
auto it = namesToStates.find(stateName);
assert(it != namesToStates.end());
auto& state = it->second;
for (const std::string& branchedStateName : elem.second) {
auto it = namesToStates.find(branchedStateName);
if (it == namesToStates.end()) {
std::cerr << branchedStateName << ": Failed to find definition for state in parse graph." << std::endl;
return std::shared_ptr<State>();
}
state->m_branches.push_back(std::make_shared<Branch>(it->second));
}
}
auto it = namesToStates.find("start");
if (it == namesToStates.end()) {
std::cerr << "Failed to find state \"start\" in parse graph." << std::endl;
return std::shared_ptr<State>();
}
return it->second;
}
// Represents a single path in a parse graph from the start state to some
// terminate state.
class ParsePath {
public:
// A single element in a path: state in its branch chosen to
// proceed to the next path element.
struct Element {
Element(const std::shared_ptr<State>& state) : m_state(state) {}
std::shared_ptr<State> m_state;
std::shared_ptr<Branch> m_branch;
};
typedef std::vector<Element> Elements;
typedef Elements::const_iterator Iterator;
public:
// Adds a new state to the end of the path.
void pushState(const std::shared_ptr<State>& state) {
m_elements.emplace_back(state);
}
// Removes the last state from the path.
void popState() {
assert(!m_elements.empty());
m_elements.pop_back();
}
// Checks to see if the branch can be followed to proceed building the path
// and, if it can, registers the branch in the path.
// The branch must belong to the last state pushed to the path.
// A branch cannot be followed if:
// - it branches to the branching state itself (the last state loops to itself);
// - it has already been followed by this path (in order not to follow the same loop
// multiple times and hang there).
bool followBranch(const std::shared_ptr<Branch>& branch) {
assert(!m_elements.empty());
// The last registered state is the branching state (the one owning this branch).
const std::shared_ptr<State> lastState = m_elements.back().m_state;
if (branch->m_nextState == lastState)
// State branches to itself. Ignore the branch.
return false;
// Do not follow the branch if it is already a part of this path.
for (auto& elem : m_elements) {
if (elem.m_state == lastState && elem.m_branch == branch)
return false;
}
// The branch can be followed.
m_elements.back().m_branch = branch;
return true;
}
Iterator begin() const { return m_elements.begin(); }
Iterator end() const { return m_elements.end(); }
private:
Elements m_elements;
};
// Outputs the parse path to a given stream.
std::ostream& operator<<(std::ostream& stream, const ParsePath& path) {
for (auto& elem : path)
stream << elem.m_state->m_name << "; ";
return stream;
}
// Paths finding.
void findPathsImpl(std::vector<ParsePath>& paths, ParsePath& currentPath, const std::shared_ptr<State>& state) {
currentPath.pushState(state);
if (state->m_branches.empty()) {
// Terminate state found. Finalize the current path.
paths.push_back(currentPath);
} else {
// There are branches in this state to check.
// Detect if at least one branch of "state" will be used for looking deeper into the parse graph.
// If no state's branch is followed means that all the state's branches are already a part
// of the path being built now. This in turn means either currentPath is a loop and "state" is its
// entry state that was reached again while traversing the loop and that state has no other
// branch not yet added to currentPath.
bool branchFollowed = false;
for (auto& branch : state->m_branches) {
if (currentPath.followBranch(branch)) {
branchFollowed = true;
findPathsImpl(paths, currentPath, branch->m_nextState);
}
}
if (!branchFollowed) {
// No branch was followed.
// This is a loop without exit. Report the looped path.
std::cerr << "Loop without exit or loop whose all states and branches have already been added to the path detected. "
<< std::endl << "Ignoring the parse subtree: "
<< std::endl << "\t" << currentPath << std::endl;
}
// Possible to detect loops and collect the states that compose it here if needed.
// Loop can (and should) be defined as a property of ParsePath.
}
currentPath.popState();
}
void findPaths(std::vector<ParsePath>& paths, const std::shared_ptr<State>& state) {
ParsePath currentPath;
findPathsImpl(paths, currentPath, state);
}
int main() {
// Build a graph.
auto startState = createGraph(ParseGraph);
if (!startState)
return 1;
// Find all paths in the graph.
std::vector<ParsePath> paths;
findPaths(paths, startState);
// Output the paths found.
std::cout << "Paths found:" << std::endl;
for (auto& path : paths) {
std::cout << "\t";
std::cout << path << std::endl;
}
return 0;
}