-
Notifications
You must be signed in to change notification settings - Fork 1
/
switch_control.js
149 lines (131 loc) · 4.26 KB
/
switch_control.js
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
var morphic = require("morphic");
var either = require("./matchers.js").either;
var matchArray = require("./matchers.js").matchArray;
var flow = require("./utils.js").flow;
var List = require("immutable").List;
var Map = require("immutable").Map;
/**
* flowProgram is the method that actually generates the flows for a specific
* node. This code handles flows specific to switch statements:
*/
var flowProgram = new morphic();
flowProgram.with(
morphic.Object("list"),
morphic.number("nodeId"),
{
"type": "SwitchStatement",
"discriminant": morphic.number("discriminant"),
"cases": matchArray("cases")
}
).then(r => {
var cases = List(r.cases).map(n => r.list.get(n));
var nonDefaultCases = cases.filter(option => option.test != null);
// first we generate a flow for the test statement,
// then we generate flows between the cases - the break statement handling
// will sort out any case statements that don't flow between one another
// then we generate the flows for each possible case including default (even
// if there isn't a default statement)
// switch(s1) {
// case s2: s3;
// case s4: s5;
// default: s6;
// }
// s1 -> s2 -> s4 -> s5 -> s6
// s1 -> s2 -> s3 -> s5 -> s6
// s1 -> s2 -> s4 -> s6
var consequents = cases.flatMap(option => option.consequent);
// 1. JOIN consequents
var flows = List([
flow(
{node: r.nodeId, type: "start"},
{node: r.discriminant, type: "start"})
]).concat(consequents.zip(consequents.rest()).map((consequents) => (
flow(
{node: consequents[0], type: "end"},
{node: consequents[1], type: "start"})
)));
if (consequents.size) {
// if there are cases then push the flow from consequents to the exit of
// the switch statement:
flows = flows.push(
flow(
{node: consequents.last(), type: "end"},
{node: r.nodeId, type: "end"}));
}
// join our tests together
flows = flows.concat(nonDefaultCases.zip(nonDefaultCases.rest()).map((cases) => (
flow(
{node: cases[0].test, type: "end"},
{node: cases[1].test, type: "start"})
)));
var parsedCases = cases.map((option) => {
var consequent = cases
.skipWhile(option2 => option2 != option)
.flatMap(option => option.consequent)
.map(consequent => ({node: consequent, type: "start"}))
.push({node: r.nodeId, type: "end"})
.first();
return {
test: option.test,
consequent: consequent,
};
});
// join our tests to consequents:
flows = flows.concat(
parsedCases
.filter((option) => option.test != null)
.map((option) => (
flow(
{node: option.test, type: "end"},
option.consequent)
))
);
var defaultCase = parsedCases.find(option => option.test == null);
// if there's at least one test join it up:
if (nonDefaultCases.size) {
// there's at least one test - let's join it to our discriminant:
flows = flows.push(
flow(
{node: r.discriminant, type: "end"},
{node: nonDefaultCases.first().test, type: "start"}));
} else if(defaultCase) {
// otherwise because there's a default statement join it to that:
flows = flows.push(
flow(
{node: r.discriminant, type: "end"},
defaultCase.consequent));
} else {
// there's nothing just join it to the end:
flows = flows.push(
flow(
{node: r.discriminant, type: "end"},
{node: r.nodeId, type: "end"}));
}
// join the last test to either the default or to the end:
if (nonDefaultCases.size) {
if (defaultCase) {
flows = flows.push(
flow(
{node: nonDefaultCases.last().test, type: "end"},
defaultCase.consequent));
} else {
flows = flows.push(
flow(
{node: nonDefaultCases.last().test, type: "end"},
{node: r.nodeId, type: "end"}));
}
}
// remove duplicate flows before returning:
return flows.filter((e1, i, a) => (
a.slice(0, i).every((e2) => (
e1.start.node != e2.start.node
|| e1.start.type != e2.start.type
|| e1.end.node != e2.end.node
|| e1.end.type != e2.end.type
))
));
});
flowProgram.otherwise().return([]);
module.exports = (nodeList) => (
nodeList.flatMap((element, id) => flowProgram(nodeList, id, element))
);