1 module tooling.MergedRange;
2 
3 import std.algorithm, std.array, std.range, std.file, std.stdio, std.exception, std.conv, std.typecons;
4 
5 import tooling.Scanner;
6 import tooling.TreeRange;
7 import tooling.Tokenizer;
8 import tooling.TokenRange;
9 
10 auto mergedRange(Token[][] tokensList)
11 {
12   struct Result
13   {
14     this(Token[][] tokensList)
15     {
16       tokenRanges_ = tokensList.map!(f => f.namespaceTokenRange).array;
17       rangeState_.length = tokenRanges_.length;
18       for (auto i=0; i<rangeState_.length; ++i) {
19         nextState(i);
20       }
21       pos_ = 0;
22       prependNewline_ = false;
23     }
24 
25     bool empty() { return tokenRanges_.map!(r => r.empty).reduce!((e, v) => v && e); }
26     Token front()
27     {
28       if (prependNewline_)
29       {
30         auto t = tokenRanges_[pos_].front;
31         t.token_.precedingWhitespace_ = "\n" ~ t.token_.precedingWhitespace_;
32         return t.token_;
33       }
34       else
35       {
36         return tokenRanges_[pos_].front.token_;
37       }
38     }
39 
40     Result save() { return this; }
41 
42     private void nextPosition()
43     {
44       // If this token range (to this entity) is empty, switch to the next
45       // token range on the same namespace level.
46       if (!empty)
47       {
48         for (; tokenRanges_[pos_].empty; pos_ = (pos_+1) % tokenRanges_.length)
49         {
50         }
51       }
52     }
53 
54     private void nextState(ulong pos)
55     {
56       rangeState_[pos] = RangeState();
57       if (!tokenRanges_[pos].empty)
58       {
59         auto t = tokenRanges_[pos].front;
60         if (t.entity_)
61         {
62           if (t.entity_.type_ == "namespace")
63           {
64             if (t.token_.value == "namespace")
65             {
66               rangeState_[pos] = RangeState(t.entity_.name, "enter");
67             }
68             else if (t.token_.value == "}")
69             {
70               rangeState_[pos] = RangeState(t.entity_.name, "exit");
71             }
72           }
73         }
74       }
75     }
76 
77     void popFront()
78     {
79       prependNewline_ = false;
80       if (!tokenRanges_[pos_].empty)
81       {
82         tokenRanges_[pos_].popFront;
83       }
84       nextPosition();
85       if (!tokenRanges_[pos_].empty)
86       {
87         nextState(pos_);
88         if (!rangeState_[pos_].name.empty)
89         {
90           // Cycle over the states of all inputs. We want to find the next
91           // normal input and slice it in.
92           // Make sure to always use the input in given order.
93           auto states = iota(rangeState_.length)
94             .zip(rangeState_)
95             // .cycle(pos_+1)
96             // .take(rangeState_.length)
97             .array;
98           //std.stdio.writeln(states);
99           auto p = states.find!(t => t[1].name.empty);
100           if (!p.empty)
101           {
102             pos_ = p.front[0];
103             prependNewline_ = true;
104             nextPosition();
105           }
106           else
107           {
108             // no normal token waiting any longer
109             // if there is some namespace to enter, do so
110             p = states.find!(t => t[1].action == "enter");
111             if (!p.empty)
112             {
113               pos_ = p.front[0];
114               auto name = p.front[1].name;
115               namespaceStack_ ~= name;
116 
117               // enhance all other inputs that are waiting at the same namespace
118               foreach (e; states)
119               {
120                 if (e[0] != pos_ && e[1].name == name && e[1].action == "enter" && !tokenRanges_[e[0]].empty)
121                 {
122                   tokenRanges_[e[0]].popFront;
123                   // consume the start of the namespace tokens, so we output
124                   // them only once
125                   while (!tokenRanges_[e[0]].empty)
126                   {
127                     auto value = tokenRanges_[e[0]].front.token_.value;
128                     auto done = value == "{" || value == ";";
129                     tokenRanges_[e[0]].popFront;
130                     if (done)
131                       break;
132                   }
133                   nextState(e[0]);
134                 }
135               }
136             }
137             else
138             {
139               // no namespace to enter any more, we need to get out of the namespace
140               auto name = namespaceStack_[$-1];
141               namespaceStack_.popBack;
142               p = states.find!(t => t[1].name == name && t[1].action == "exit");
143               if (p.empty)
144               {
145                 throw new Exception("Missing closing token for namespace '" ~ name ~ "'");
146               }
147               pos_ = p.front[0];
148 
149               // consume the namespace closings that are not needed
150               foreach (e; states)
151               {
152                 if (e[0] != pos_ && e[1].name == name && e[1].action == "exit" && !tokenRanges_[e[0]].empty)
153                 {
154                   tokenRanges_[e[0]].popFront;
155                   nextState(e[0]);
156                 }
157               }
158             }
159           }
160         }
161       }
162     }
163 
164     TokenRangeResult[] tokenRanges_;
165     alias RangeState = Tuple!(string, "name", string, "action");
166     RangeState[] rangeState_;
167     string[] namespaceStack_;
168     ulong pos_;
169     bool prependNewline_;
170   }
171 
172   return Result(tokensList);
173 }
174 
175 auto mergedRange(string[] filenames)
176 {
177   auto tokensList = filenames.map!(f => f.readTokens).array;
178   return mergedRange(tokensList);
179 }