1 module des.arch.tree;
2 
3 import std.traits;
4 import std..string;
5 import std.algorithm;
6 import std.exception;
7 
8 import des.ts;
9 
10 ///
11 class TNodeException : Exception
12 {
13     this( string msg, string file=__FILE__, size_t line=__LINE__ )
14     { super( msg, file, line ); }
15 }
16 
17 ///
18 class TNodeCycleException : TNodeException
19 {
20     this( string file=__FILE__, size_t lien=__LINE__ )
21     { super( "cycle detect", file, line ); }
22 }
23 
24 ///
25 class TNodeNullChildException : TNodeException
26 {
27     this( string file=__FILE__, size_t lien=__LINE__ )
28     { super( "can't append null child", file, line ); }
29 }
30 
31 ///
32 template TNode(T,string prefix="",string suffix="")
33 {
34 interface TNode
35 {
36     mixin( replaceWords!(wordWrapFunc!(prefix,suffix))(q{
37     public
38     {
39         alias T %NodeType;
40 
41         @property
42         {
43             %NodeType %parent();
44             const(%NodeType) %parent() const;
45             %NodeType %parent( %NodeType p );
46 
47             %NodeType[] %childs();
48             const(%NodeType[]) %childs() const;
49         }
50 
51         final bool %findInChilds( const(%NodeType) obj ) const
52         {
53             foreach( ch; %childs )
54             {
55                 if( obj == ch ) return true;
56                 if( ch.%findInChilds(obj) ) return true;
57             }
58             return false;
59         }
60 
61         void %attachChilds( %NodeType[] att... );
62         void %detachChilds( %NodeType[] att... );
63     }
64 
65     protected
66     {
67         void %attachCallback( %NodeType[] att );
68         void %detachCallback( %NodeType[] det );
69     }
70 
71     void __%simpleSetParent( %NodeType p );
72 
73     mixin template %TNodeHelper(bool __%release_cycle_check=false,
74                                 bool __%release_nullchild_check=false )
75     {
76         protected
77         {
78             import std.traits;
79             override
80             {
81                 static if( isAbstractFunction!%attachCallback )
82                     void %attachCallback( %NodeType[] att ){}
83                 static if( isAbstractFunction!%detachCallback )
84                     void %detachCallback( %NodeType[] det ){}
85             }
86 
87             %NodeType __%parent_node;
88             %NodeType[] __%childs_list;
89         }
90 
91         void __%simpleSetParent( %NodeType p ) { __%parent_node = p; }
92 
93         public @property
94         {
95             %NodeType %parent() { return __%parent_node; }
96             const(%NodeType) %parent() const { return __%parent_node; }
97 
98             %NodeType %parent( %NodeType p )
99             {
100                 if( __%parent_node !is null )
101                     __%parent_node.%detachChilds( this );
102                 __%parent_node = p;
103                 if( __%parent_node !is null )
104                     __%parent_node.%attachChilds( this );
105                 return p;
106             }
107 
108             %NodeType[] %childs() { return __%childs_list; }
109             const(%NodeType[]) %childs() const { return __%childs_list; }
110 
111             %NodeType[] %childs( %NodeType[] nch )
112             {
113                 __%childs_list = nch;
114                 return nch;
115             }
116         }
117 
118         final
119         {
120             public void %attachChilds( %NodeType[] att... )
121             in { assert( att.length > 0 ); } body
122             {
123                 import std.exception;
124                 import des.arch.tree;
125                 debug enum DEBUG = true; else enum DEBUG = false;
126 
127                 static if( __%release_nullchild_check || DEBUG )
128                     foreach( el; att )
129                         enforce( el !is null, new TNodeNullChildException );
130 
131                 static if( __%release_cycle_check || DEBUG )
132                     enforce( %cycleCheck( att ), new TNodeCycleException );
133 
134                 foreach( el; att )
135                 {
136                     if( %findInChilds( el ) ) continue;
137                     if( el.%parent !is null )
138                         el.%parent.%detachChilds( el );
139                     __%childs_list ~= el;
140                     el.__%simpleSetParent( this );
141                 }
142                 %attachCallback( att );
143             }
144 
145             public void %detachChilds( %NodeType[] det... )
146             {
147                 %NodeType[] buf;
148                 foreach( ch; %childs )
149                     foreach( d; det )
150                         if( ch != d )
151                             buf ~= ch;
152                 foreach( d; det )
153                     d.__%simpleSetParent( null );
154                 %childs = buf;
155                 %detachCallback( det );
156             }
157 
158             protected bool %cycleCheck( const(%NodeType)[] list... ) const
159             {
160                 const(%NodeType)[] plist = [this];
161 
162                 while( plist[$-1].%parent )
163                     plist ~= plist[$-1].%parent;
164 
165                 foreach( p; plist )
166                     foreach( elem; list )
167                         if( elem.%findInChilds(p) )
168                             return false;
169 
170                 return true;
171             }
172         }
173     }
174 }));
175 }
176 }
177 
178 ///
179 unittest
180 {
181     static class Test : TNode!(Test,"","XX")
182     {
183         mixin TNodeHelperXX!(true,true);
184 
185         string name;
186         this( string name ) { this.name = name; }
187 
188         void append( string kk )
189         {
190             name ~= kk;
191             foreach( ch; childsXX )
192                 ch.append( kk );
193         }
194     }
195 
196     auto e0 = new Test("a");
197     auto e1 = new Test("b");
198     auto e2 = new Test("c");
199     auto e3 = new Test("d");
200 
201     e1.parentXX = e0;
202     e2.parentXX = e1;
203 
204     e0.append( "ok" );
205 
206     e1.attachChildsXX( e3 );
207 
208     assertEq( e0.childsXX, [ e1 ] );
209     assertEq( e0.name, "aok" );
210     assertEq( e1.name, "bok" );
211     assertEq( e3.name, "d" );
212     assertEq( e3.parentXX, e1 );
213 }
214 
215 ///
216 unittest
217 {
218     static class Test : TNode!(Test,"a_"), TNode!(Test,"b_")
219     {
220         mixin a_TNodeHelper!(true,true);
221         mixin b_TNodeHelper!(true,true);
222 
223         string name;
224         this( string name ) { this.name = name; }
225 
226         void append( string kk )
227         {
228             name ~= kk;
229             foreach( ch; b_childs )
230                 ch.append( kk );
231         }
232     }
233 
234     auto e0 = new Test("e0");
235     auto e1 = new Test("e1");
236     auto e2 = new Test("e2");
237     auto e3 = new Test("e3");
238 
239     e2.a_parent = e0;
240     e2.b_parent = e1;
241 
242     e0.append( "ok" );
243 
244     assertEq( e0.name, "e0ok" );
245     assertEq( e2.name, "e2" );
246 }
247 
248 ///
249 unittest
250 {
251     static interface Node : TNode!Node
252     {
253         mixin template NodeHelper() { mixin TNodeHelper!true; }
254         void inc_self();
255 
256         final void inc()
257         {
258             inc_self();
259             foreach( ch; childs )
260                 ch.inc();
261         }
262     }
263 
264     static class Test : Node
265     {
266         mixin NodeHelper;
267 
268         int i;
269         this( int i ) { this.i = i; }
270         void inc_self() { i++; }
271     }
272 
273     auto a0 = new Test(10);
274     auto a1 = new Test(15);
275     a0.attachChilds( a1 );
276 
277     a0.inc();
278 
279     assertEq( a0.i, 11 );
280     assertEq( a1.i, 16 );
281 }
282 
283 ///
284 unittest
285 {
286     static class Test : TNode!Test { mixin TNodeHelper!(true, true); }
287 
288     auto a0 = new Test;
289     auto a1 = new Test;
290     auto a2 = new Test;
291 
292     assertNull( a0.parent );
293     assertNull( a1.parent );
294     assertNull( a2.parent );
295 
296     a0.attachChilds( a1 );
297     a1.attachChilds( a2 );
298 
299     assertEq( a0.childs.length, 1 );
300     assertEq( a1.childs.length, 1 );
301 
302     assertExcept!TNodeCycleException({ a2.attachChilds(a0); });
303     assertExcept!TNodeNullChildException({ a2.attachChilds(null); });
304 }
305 
306 unittest
307 {
308     static class Test : TNode!(Test,"a") { mixin aTNodeHelper!(true, true); }
309     auto a0 = new Test;
310     assert( a0.aParent is null );
311 }
312 
313 private
314 {
315     string replaceWords(alias fun)( string s ) pure
316     {
317         string ret;
318         string buf;
319         size_t p0 = 0, p1 = 0;
320         while( p1 < s.length )
321         {
322             if( s[p1] == '%' )
323             {
324                 ret ~= s[p0..p1];
325                 p0 = ++p1;
326                 while( p1 < s.length && identitySymbol(s[p1]) )
327                     p1++;
328                 ret ~= fun(s[p0..p1]);
329                 p0 = p1;
330             }
331             else if( p1 == s.length - 1 )
332                 ret ~= s[p0..p1];
333             p1++;
334         }
335         return ret;
336     }
337 
338     pure bool identitySymbol( char c )
339     {
340         switch(c)
341         {
342             case 'a': .. case 'z': case 'A': .. case 'Z':
343             case '_': case '0': .. case '9': return true;
344             default: return false;
345         }
346     }
347 
348     pure bool identityString( string s )
349     {
350         foreach( c; s )
351             if( !identitySymbol(c) ) return false;
352         return true;
353     }
354 
355     template wordWrapFunc(string pre, string suf)
356     {
357         import std.exception;
358         static assert( identityString(pre) );
359         static assert( identityString(suf) );
360         string wordWrapFunc( string a )
361         {
362             enforce( a.length );
363             if( pre.length && pre[$-1] != '_' )
364                 a = (""~a[0]).toUpper ~ a[1..$];
365             return pre ~ a ~ suf;
366         }
367     }
368 }