1 /**
2 Copyright: Copyright (c) 2017, Joakim Brännström. All rights reserved.
3 License: MPL-2
4 Author: Joakim Brännström (joakim.brannstrom@gmx.com)
5 
6 This Source Code Form is subject to the terms of the Mozilla Public License,
7 v.2.0. If a copy of the MPL was not distributed with this file, You can obtain
8 one at http://mozilla.org/MPL/2.0/.
9 */
10 module cpptooling.data.class_merge;
11 
12 import cpptooling.data.representation : CppClass;
13 import cpptooling.data.symbol : Container;
14 import cpptooling.data.symbol.types : FullyQualifiedNameType;
15 
16 import std.typecons : NullableRef;
17 import std.range : only;
18 
19 @safe:
20 
21 alias LookupT = typeof(only(NullableRef!(CppClass).init)) delegate(FullyQualifiedNameType a);
22 
23 /** Create a merged class with methods from all those classes it inherits from.
24  *
25  * Params:
26  *  LookupT = a callback that takes a FullyQualifiedNameType and returns a
27  *      range with zero or one elements of type NullableRef!CppClass.
28  */
29 CppClass mergeClassInherit(ref CppClass class_, ref Container container, LookupT lookup) {
30     import std.algorithm : each;
31 
32     if (class_.inheritRange.length == 0) {
33         return class_;
34     }
35 
36     auto methods = dedup(getMethods(class_, container, lookup));
37 
38     auto c = CppClass(class_.name, class_.inherits, class_.resideInNs);
39     () @trusted { methods.each!(a => c.put(a)); }();
40 
41     return c;
42 }
43 
44 private:
45 
46 bool isMethodOrOperator(T)(T method) @trusted {
47     import std.variant : visit;
48     import cpptooling.data.representation : CppMethod, CppMethodOp, CppCtor, CppDtor;
49 
50     // dfmt off
51     return method.visit!((CppMethod a) => true,
52                          (CppMethodOp a) => true,
53                          (CppCtor a) => false,
54                          (CppDtor a) => false);
55     // dfmt on
56 }
57 
58 CppClass.CppFunc[] getMethods(ref CppClass c, ref Container container, LookupT lookup) @safe {
59     import std.array : array, appender;
60     import std.algorithm : cache, copy, each, filter, joiner, map;
61     import std.range : chain;
62 
63     // dfmt off
64     auto local_methods = c.methodRange
65         .filter!(a => isMethodOrOperator(a));
66 
67     auto inherit_methods = c.inheritRange
68         .map!(a => lookup(a.fullyQualifiedName))
69         // some classes do not exist in AST thus no methods returned
70         .joiner
71         .map!(a => getMethods(a, container, lookup));
72     // dfmt on
73 
74     auto methods = appender!(CppClass.CppFunc[])();
75     () @trusted { local_methods.copy(methods); inherit_methods.copy(methods); }();
76 
77     return methods.data;
78 }
79 
80 //TODO this function is inefficient. So many allocations...
81 auto dedup(CppClass.CppFunc[] methods) @trusted {
82     import std.array : array;
83     import std.algorithm : makeIndex, uniq, map, sort;
84     import cpptooling.utility.dedup : dedup;
85     import cpptooling.data.representation : funcToString;
86 
87     static auto getUniqeId(T)(ref T method) {
88         import std.variant : visit;
89         import cpptooling.data : CppMethod, CppMethodOp, CppCtor, CppDtor;
90 
91         // dfmt off
92         return method.visit!((CppMethod a) => a.id,
93                              (CppMethodOp a) => a.id,
94                              (CppCtor a) => a.id,
95                              (CppDtor a) => a.id);
96         // dfmt on
97     }
98 
99     //TODO inefficient, lots of intermittent arrays and allocations.
100     // Convert to a range based no-allocation.
101 
102     auto arr = methods.map!(a => getUniqeId(a)).array();
103 
104     auto index = new size_t[arr.length];
105     // sorting the indexes
106     makeIndex(arr, index);
107 
108     // dfmt off
109     // contains a list of indexes into methods
110     auto deduped_methods =
111         index
112         // dedup the sorted index
113         .uniq!((a,b) => arr[a] == arr[b])
114         .array();
115 
116     // deterministic sorting by function signature
117     deduped_methods.sort!((a,b) { return methods[a].funcToString < methods[b].funcToString; });
118 
119     return deduped_methods
120         // reconstruct an array from the sorted indexes
121         .map!(a => methods[a])
122         .array();
123     // dfmt on
124 }