-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathheap.dtx
More file actions
2527 lines (2524 loc) · 85 KB
/
heap.dtx
File metadata and controls
2527 lines (2524 loc) · 85 KB
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
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
%
% \iffalse (driver)
%<*driver>
\documentclass{tclldoc}
\newcommand{\Tcl}{\Tcllogo}
\newenvironment{procmethod}{%
\tclsubcommand{subcommand}{subsubcommand}%
}{\endtclsubcommand}
\newenvironment{ttdescription}{%
\description
\def\makelabel##1{\hspace\labelsep\normalfont\ttfamily ##1}%
}{\enddescription}
\begin{document}
\DocInput{heap.dtx}
\end{document}
%</driver>
% \fi
%
% \title{Heaps as \Tcl\ lists}
% \author{Lars Hellstr\"om}
% \date{2006-10-26--}
% \maketitle
%
% \begin{abstract}
% This package implements a heap (as for dynamic allocation of
% memory) as a \Tcl\ variable. Fundamental operations are carried
% out in constant time, just as for traditional pointer-based
% languages.
% \end{abstract}
%
%
% \section{Implementation}
%
% A heap is practically implemented as a \Tcl\ list. Pointers are
% indices in this list. `|no|' is the nil\slash null pointer.
%
% Element $0$ in the list is used for keeping track of free items
% in the heap. There are two mechanisms for this:
% \begin{itemize}
% \item
% Heap element |{0 0}| points to the head of the \emph{free
% list}, which is a singly-linked list (heap element is a pointer
% to the next item in the list) of all free items. This is
% used to make allocation a constant time operation (constant
% amortized time if the heap needs to grow).
% \item
% Heap element |{0 |$p$|}| for \(p>0\) is $0$ if heap element $p$
% is free and $1$ if it is allocated. This \emph{state vector} is
% needed to detect double deallocation errors. By using literal
% constants $0$ and $1$ here, the overhead can be kept down to
% $1$ machine pointer per heap element.
% \end{itemize}
% \changes{0}{2008/07/08}{Rewrite to also maintain a list of
% `allocated' statuses for the blocks. (LH)}
%
% \changes{1.1}{2016/09/07}{Added docstrip catalogue, for both
% packages. (LH)}
%
% \begin{tcl}
%<*docstrip.tcl::catalogue>
pkgProvide heap 1.0 {pkg noprovide}
%</docstrip.tcl::catalogue>
%<*pkg>
package require Tcl 8.4
%<!noprovide>package provide heap 1.0
namespace eval heap {}
% \end{tcl}
% \setnamespace{heap}
%
% \begin{proc}{core}
% The |core| procedure is what actually implements the operations
% on a heap.
% \changes{0}{2008/08/20}{Generalised to allow specifying a level.
% Used to be called \texttt{heap}, but that is now an alias
% to \texttt{core}. (LH)}
% The call syntax is
% \begin{quote}
% |core| \word{heap-level} \word{heap-variable} \word{subcommand}
% \word{argument}\regstar
% \end{quote}
% where \word{heap-variable} is the (name of the variable in which
% is stored the) heap to operate on and \word{subcommand} determines
% what operation to perform. The \word{heap-level} is the level at
% which this variable is found; in the case of the calling context,
% that should be |1|.
% The return value depends on which operation is performed.
%
% In descriptions of subcommands below, the
% \begin{displaysyntax}
% |core| \word{heap-level} \word{heap-variable}
% \end{displaysyntax}
% part is denoted by \meta{cmdbase}, since the package also creates
% other commands (as aliases to |core|) which sport the same set of
% subcommands.
% \begin{tcl}
proc heap::core {heaplevel heapvar subcmd args} {
upvar $heaplevel $heapvar heap
switch -- $subcmd {
% \end{tcl}
% \begin{procmethod}{get}
% The |get| subcommand retrieves data from an item in the heap.
% It has the syntax
% \begin{quote}
% \meta{cmdbase} |get| \word{pointer} \word{index}\regstar
% \end{quote}
% \word{pointer} is the pointer into the heap. If there are
% \word{index} arguments, then the pointed-to item is treated as
% a list and an element is retrieved in the same way as with |lindex|.
% \begin{tcl}
"get" {
return [lindex $heap $args]
}
% \end{tcl}
% \end{procmethod}
%
% \begin{procmethod}{set}
% The |set| subcommand sets an item or a part of an item. The
% syntax is
% \begin{quote}
% \meta{cmdbase} |set| \word{pointer} \word{index}\regstar\
% \word{value}
% \end{quote}
% \word{pointer} is the pointer into the heap and \word{value} is
% the value to store in the heap. If there are \word{index}
% arguments, then the pointed-to item is treated as a list which
% is modified in place in the manner of |lset|.
% The return value is an empty string.
% \begin{tcl}
"set" {
eval [list lset heap] $args
return
}
% \end{tcl}
% \end{procmethod}
%
% \begin{procmethod}{new}
% The |new| subcommand allocates a new item on the heap. The
% syntax is
% \begin{quote}
% \meta{cmdbase} |new| \word{value}\regopt
% \end{quote}
% and the return value is the pointer to the new item. If a
% \word{value} is supplied then the new item is set to that value,
% otherwise the new item will be an empty string.
% \begin{tcl}
"new" {
if {[lindex $heap 0 0]} then {
set ptr [lindex $heap 0 0]
lset heap 0 0 [lindex $heap $ptr]
lset heap 0 $ptr 1
lset heap $ptr [lindex $args 0]
} else {
set ptr [llength $heap]
set alloc [lindex $heap 0]
lset heap 0 {}
lappend alloc 1
lset heap 0 $alloc
lappend heap [lindex $args 0]
}
return $ptr
}
% \end{tcl}
% \end{procmethod}
%
% \begin{procmethod}{dispose}
% The |dispose| subcommand frees an item on the heap. The
% syntax is
% \begin{quote}
% \meta{cmdbase} |dispose| \word{pointer}
% \end{quote}
% and the return value is the contents of the item freed. An error
% is thrown if the \word{pointer} is not a pointer to an allocated
% element.
% \begin{tcl}
"dispose" {
set ptr [lindex $args 0]
if {![string is integer $ptr] || $ptr<=0} then {
return -code error "Not a pointer: $ptr"
} elseif {[lindex $heap 0 $ptr] != 1} then {
return -code error "Item not allocated: $ptr"
}
set val [lindex $heap $ptr]
lset heap $ptr [lindex $heap 0 0]
lset heap 0 0 $ptr
lset heap 0 $ptr 0
return $val
}
% \end{tcl}
% \end{procmethod}
%
% \begin{procmethod}{nil}
% \begin{procmethod}{isnil}
% The |nil| subcommand returns the string |no| and the |isnil|
% subcommand tests whether something is a pointer or |no|. Their
% syntaxes are
% \begin{displaysyntax}
% \meta{cmdbase} nil\par
% \meta{cmdbase} isnil \word{pointer or nil}
% \end{displaysyntax}
% \begin{tcl}
"nil" {
return no
}
"isnil" {
if {[lindex $args 0] eq "no"} then {return 1}
if {![string is integer [lindex $args 0]] ||\
[lindex $args 0]<=0 ||\
[lindex $args 0]>=[llength $heap]} then {
return -code error "Neither pointer nor nil:\
[lindex $args 0]"
}
return 0
}
% \end{tcl}
% The |nil| and |isnil| subcommands are provided for users who
% want a higher level of abstraction in their code than an
% explicit |no| would provide. Using `|no|' as representation for
% \textbf{nil} has the following advantages:
% \begin{itemize}
% \item
% Unlike a numeric $0$, it is not a valid list index, which
% protects against \textbf{nil}-dereferencing in the |get| and
% |set| subcommands. Such dereferencing could otherwise corrupt
% the heap.
% \item
% Unlike strings such as |nil| and |null|, it \emph{is} a valid
% boolean and will thus permit the idiom
% \begin{quote}
% |if {|\word{pointer or nil}|}| \dots
% \end{quote}
% as an alternative to
% \begin{quote}
% |if {![heap |\word{heap-variable}| isnil\
% |\word{pointer or nil}|]}| \dots
% \end{quote}
% although the latter checks the value also for being
% out-of-bounds.
% \end{itemize}
% \end{procmethod}\end{procmethod}
%
% \begin{procmethod}{create}
% The |create| subcommand sets the heap variable to a new, empty
% heap, discarding any old heap contents. The syntax is
% \begin{quote}
% \meta{cmdbase} |create|
% \end{quote}
% There is no particular return value.
% \begin{tcl}
"create" {
set heap [list [list no]]
}
% \end{tcl}
% \end{procmethod}
%
% \begin{procmethod}{destroy}
% The |destroy| subcommand unsets the heap variable, thus
% discarding anything stored in it. The syntax is
% \begin{quote}
% \meta{cmdbase} |destroy|
% \end{quote}
% There is no particular return value.
% \begin{tcl}
"destroy" {
unset heap
}
% \end{tcl}
% \end{procmethod}
%
% \begin{procmethod}{statistics}
% The |statistics| subcommand calculates the number of allocated
% and freed respectively items in the heap. The syntax is
% \begin{quote}
% \meta{cmdbase} |statistics|
% \end{quote}
% and the return value is a list\slash dictionary
% \begin{quote}
% |inuse| \word{no.~items} |free| \word{no.~items}
% \end{quote}
% This subcommand detects if a heap has been corrupted (which can
% happen if you |set| a free entry), and will then throw an error,
% trying to give a hint about what has gone wrong.
%
% Strictly speaking, this is a $\Theta(n)$ operation where $n$ is
% the number of elements in the heap, but the $n$-proportional
% parts are inside core \Tcl\ commands, so it is more likely that
% experienced performance would be proportional to the number of
% free entries.
% \begin{tcl}
"statistics" {
set visitedL [lindex $heap 0]
if {[llength $visitedL] != [llength $heap]} then {
return -code error "Corrupted heap; state vector and heap\
don't match"
}
set ptr [lindex $heap 0 0]
set free 0
while {$ptr} {
switch -- [lindex $visitedL $ptr] "1" {
return -code error "Corrupted heap; free list\
contains allocated address $ptr"
} "2" {
return -code error "Corrupted heap; free list\
goes to $ptr twice"
}
lset visitedL $ptr 2
incr free
set last $ptr
set ptr [lindex $heap $ptr]
if {[string is integer $ptr] ?
$ptr <= 0 || $ptr >= [llength $heap] :
$ptr != "no"
} then {
return -code error "Corrupted heap; item $last is free\
but has been set"
}
}
if {[lsearch -exact [lrange $visitedL 1 end] 0] != -1} then {
return -code error "Corrupted heap; some free address(es)\
not in free list."
}
return [list inuse [expr {[llength $heap] - $free - 1}]\
free $free]
}
% \end{tcl}
% \end{procmethod}
%
% \begin{procmethod}{repair}
% The |repair| subcommand reconstructs the free list from data in
% the state vector; the resulting free list is ascending, which has
% the effect that low addresses are preferred for allocation of new
% elements. The syntax is
% \begin{quote}
% \meta{cmdbase} |repair|
% \end{quote}
% and the return value is a list\slash dictionary
% \begin{quote}
% |inuse| \word{no.~items} |free| \word{no.~items}
% |suspect| \word{dict}
% \end{quote}
% where the |suspect| part records old contents of items that
% were free according to the state vector but whose contents were
% not consistent with the format of the free list. If the
% \word{dict} list\slash dictionary (mapping pointers to item
% contents) is not empty then there is probably an error in your
% program.
%
% \begin{tcl}
"repair" {
if {[llength $heap] != [llength [lindex $heap 0]]} then {
return -code error "Corrupted heap; state vector and heap\
don't match"
}
set suspect {}
set last {0 0}
set free 0
set ptr 0
foreach state [lrange [lindex $heap 0] 1 end] {incr ptr
if {$state} then {continue}
incr free
lset heap $last $ptr
set last $ptr
set item [lindex $heap $ptr]
if {[string is integer $item] ?
$item<0 || $item>=[llength $heap] ||\
[lindex $heap 0 $item] :
$item != no
} then {lappend suspect $ptr $entry}
}
lset heap $last no
return [list inuse [expr {[llength $heap] - $free - 1}]\
free $free suspect $suspect]
}
% \end{tcl}
% \end{procmethod}
%
% The |repair| subcommand frees items whenever there is a doubt,
% although its main function is to preserve what is allocated. The
% |garbagecollect| subcommand conversely has as main function to
% free allocated (although unreachable) items, but chooses to
% consider items allocated whenever there is a doubt.
%
% \begin{procmethod}{garbagecollect}
% The |garbagecollect| subcommand performs a garbage collection in
% the heap: disposing of all items which can no longer be reached.
% The operation requires that the user can provide a
% \word{scan-cmd} which will find all pointers stored inside a heap
% item, as the heap package itself has no way of knowing whether
% some piece of data is a pointer or not. The call syntax is
% \begin{quote}
% \meta{cmdbase} |garbagecollect| \word{scan-cmd}
% \word{pointer}\regstar
% \end{quote}
% where the \word{pointer}s are the pointers to heap items stored
% outside the heap and \word{scan-cmd} is employed to find pointers
% within the heap. Similarly to the last two subcommands,
% the return value is a list\slash dictionary
% \begin{displaysyntax}
% inuse \word{no.~items} free \word{no.~items}
% inuse0 \word{no.~items} free0 \word{no.~items}
% forced \word{pointer-list}
% \end{displaysyntax}
% where the |inuse| and |free| refer to the state of the heap after
% garbage collection, and |inuse0| and |free0| refer to the state of
% it before.
%
% The |forced| list is the list of pointers to items that were listed
% as free in the state vector, but nontheless pointed to. Having this
% list nonempty indicates there is an error in your program.
% The state of these items will be set to allocated by the garbage
% collector, but they might have been overwritten by previous heap
% operations.
%
% The call syntax for the \word{scan-cmd} is
% \begin{quote}
% \meta{scan-cmd} \word{item}
% \end{quote}
% where \word{item} is an item on the stack. The return value is
% the list of pointers in the \word{item}. Non-pointers (e.g.~|no|)
% are ignored by the garbage collector.
%
% \begin{tcl}
"garbagecollect" {
set call [lrange [lindex $args 0] 0 end]
set queue [lrange $args 1 end]
for {set n 0} {$n<[llength $queue]} {incr n} {
set ptr [lindex $queue $n]
if {[info exists mark($ptr)]} then {continue}
set mark($ptr) {}
if {![string is integer $ptr]} then {continue}
if {$ptr<=0 || $ptr>=[llength $heap]} then {continue}
eval [list lappend queue]\
[uplevel 1 $call [lrange $heap $ptr $ptr]]
}
unset queue
foreach n {inuse inuse0 free free0} {set $n 0}
set forced {}
set ptr -1; foreach state [lindex $heap 0] {incr ptr
if {!$ptr} then {set last {0 0}; continue}
if {$state} then {incr inuse0} else {incr free0}
if {[info exists mark($ptr)]} then {
incr inuse
if {!$state} then {
lappend forced $ptr
lset heap 0 $ptr 1
}
} else {
incr free
lset heap 0 $ptr 0
lset heap $last $ptr
set last $ptr
}
}
lset heap $last no
set res {}
foreach n {inuse inuse0 free free0 forced} {
lappend res $n [set $n]
}
return $res
}
% \end{tcl}
% Runtime for |garbagecollect| is $O(n)+O(m)$, where $n$ is the
% size of the heap and $m$ is the number of pointers in the heap.
% \end{procmethod}
%
% \begin{procmethod}{compact}
% The |compact| subcommand reduces the size of the heap by removing
% all free items from it. This generally requires moving items
% around, and consequently anyone who calls this subcommand is
% expected to be able to update all pointers to items in the heap.
%
% The call syntax is
% \begin{quote}
% \meta{cmdbase} |compact| \word{update-cmd}
% \end{quote}
% where the \word{update-cmd} is responsible for updating pointers
% stored in the heap itself. It is a command prefix which will be
% called as
% \begin{quote}
% \meta{update-cmd} \word{redirections} \word{item}
% \end{quote}
% where \word{item} is an item on the heap, which the command
% should return an updated value for. The \word{redirections} is a
% list which when indexed using a pointer will return the new value
% for that pointer. Elements for free heap items are set to
% |false| (not |no|, although changing dangling pointers to
% \textbf{nil} could sometimes repair damaged data structures).
%
% The return value of the |compact| subcommand is again the
% \word{redirections} list, which must be used to update any
% pointers outside the heap.
% \begin{tcl}
"compact" {
% \end{tcl}
% The first step is to build the redirections list. The approach
% used is to walk through the heap with two pointers, one
% starting at |low| addresses and going upwards, the other
% starting at |high| addresses and going downwards. The |low|
% pointer leaves allocated items where they are but stops at free
% items, whereas the |high| pointer steps over free items and
% exchanges allocated items with the free one at which the |low|
% pointer has stopped. There is also a |pickL| list which lists
% old addresses for the heap items, in the order in which they
% will appear in the new heap.
% \begin{tcl}
set redirL [lreplace [lindex $heap 0] 0 0 false]
set pickL {}
set low 1
set high [expr {[llength $heap]-1}]
while {$low <= $high} {
if {[lindex $heap 0 $low]} then {
lset redirL $low $low
lappend pickL $low
incr low
} elseif {![lindex $heap 0 $high]} then {
lset redirL $high false
incr high -1
} else {
lset redirL $high $low
lappend pickL $high
lset redirL $low false
incr low
incr high -1
}
}
% \end{tcl}
% The second step builds the new heap.
% \begin{tcl}
set call [lindex $args 0]
lappend call $redirL
set newheap [list {}]
set states [list no]
foreach ptr $pickL {
lappend states 1
lappend newheap [uplevel 1 $call [lrange $heap $ptr $ptr]]
}
lset newheap 0 $states
set heap $newheap
return $redirL
}
% \end{tcl}
% \end{procmethod}
% \begin{tcl}
default {
return -code error "unknown subcommand \"$subcmd\":\
must be compact, create, destroy, dispose, garbagecollect,\
get, isnil, new, nil, repair, set, or statistics"
}
}
}
% \end{tcl}
% \end{proc}
%
%
% \begin{proc}{heap}
% The basic interface to the heap operations is not the |core|
% command, but the |heap| command, which has hardwired |1| as value
% for the heap-level. It is implemented as an alias and exported
% from the |heap| namespace.
% \begin{tcl}
namespace eval heap {
interp alias {} [namespace current]::heap {}\
[namespace which core] 1
namespace export heap
}
% \end{tcl}
% \end{proc}
%
%
% \begin{proc}{makecmd}
% This command creates custom heap commands, which can be useful
% (but is not essential) if you need to pass the heap by reference
% to someone. The call syntax is
% \begin{displaysyntax}
% heap::makecmd \word{heap-variable} \begin{regblock}[\regstar]
% \word{option} \word{value} \end{regblock}
% \end{displaysyntax}
% and the return value is the fully qualified name of the new
% command (which will be an alias to |heap::core| with the two
% parameters fixed). The command lifetime is (via a trace) tied to
% that of the heap variable, so when the heap is |destroy|ed or
% otherwise unset, then the command is deleted as well.
%
% The \word{heap-variable} is the name of the variable in which the
% heap will be stored. The supported options are:
% \begin{ttdescription}
% \item[-command]
% Value is the requested name for the command to create. If
% this is not specified, then a unique name in the |heap|
% namespace will be generated.
% \item[-local]
% The value is a boolean; it defaults to false. Controls
% whether the \word{heap-variable} is considered to be
% local~(true) or global\slash namespace~(false).
% \item[-prefix]
% The value is a boolean; it defaults to false. When true,
% |makecmd| doesn't actually create a command or associated heap,
% it just returns the command prefix the command would have
% been an alias to.
% \end{ttdescription}
% Nonlocal \word{heap-variable} names are assumed to be relative to
% the calling namespace, if they are not fully qualified. The same is
% true for specified |-command| names.
%
% \begin{tcl}
proc heap::makecmd {varname args} {
array set O {-local 0 -prefix 0}
array set O $args
set ns [uplevel 1 {::namespace current}]
if {![info exists O(-command)]} then {
while 1 {
set cmdname [namespace current]::h[info cmdcount]
if {[namespace which -command $cmdname] eq ""} then {break}
}
} elseif {[string match ::* $O(-command)]} then {
set cmdname $O(-command)
} else {
set cmdname ${ns}::$O(-command)
}
set prefix [list [namespace which core]]
if {$O(-local)} then {
lappend prefix \#[expr {[info level]-1}] $varname
} else {
if {![string match ::* $varname]} then {
set varname ${ns}::${varname}
}
lappend prefix #0 $varname
}
if {$O(-prefix)} then {return $prefix}
set token [eval [list interp alias {} $cmdname {}] $prefix]
if {[catch {
$cmdname create
} res]} then {
interp alias {} $token {}
return -code error -errorinfo $::errorInfo\
-errorcode $:errorCode $res
}
uplevel 1 [list ::trace add variable $varname unset\
"[list ::interp alias {} $token {}]; #"]
return $cmdname
}
% \end{tcl}
% \end{proc}
%
% \begin{tcl}
%</pkg>
% \end{tcl}
%
%
%
% \section{Cubic tree test}
%
% \begin{tcl}
%<*cubicTreeTest>
% \end{tcl}
% \setnamespace{}
%
% The basic idea for this test is merely to do a lot of things in a
% heap, so that all the commands are exercised.
%
% What is put in the heap is a plane cubic tree (hence the name of
% the test), i.e., pretty much an ordinary binary tree with parent
% pointer, except that no big distinction is made between the parent
% and the children---they're simply all neighbours of the node. What
% is done in this tree is that a cursor is running around in it along
% the exterior ``facet'' (hence plane), and every once in a while it
% changes the tree: there is steady growth, internal rotations, and
% occational deletions of subtrees. There are also more seldomly
% integrity checks, repairs, garbage collecting, and
% compactifications.
%
% The basic structure of a node in the tree is
% \begin{displaysyntax}
% \word{neighbour 0} \word{neighbour 1} \word{neighbour 2}
% \end{displaysyntax}
% where the three neighbours are pointers to neighbours (or nil, if
% we're at the edge of the tree). Traversal of the tree requires
% keeping track not only of the current node, but also a current
% direction in that node.
%
%
% \subsection{Drawing trees}
%
% For verifying that the test is doing what it is supposed to, it is
% useful to be able to draw the trees on a Tk |canvas|.
%
% \begin{proc}{binary_branch}
% This procedure serialises a branch of the big cubic tree as a
% nested list binary tree, with coordinates. The call syntax is
% \begin{displaysyntax}
% |binary_branch| \word{heap-cmd} \word{pointer}
% \word{parent-index} \word{arc-min} \word{arc-max}
% \word{depth}
% \end{displaysyntax}
% where \word{heap-cmd} is the command for accessing the heap in
% which the tree is stored, \word{pointer} is the pointer to the
% node to convert, \word{parent-index} is the index (|0|, |1|, or
% |2|) of the neighbour of this node that should be regarded as
% parent, \word{arc-min} and \word{arc-max} are the angles (in
% radians) that delimit the sector in which the tree should be put,
% and \word{depth} is the distance to the root of the node to
% convert (this is used to determine the radius of the circle on
% which it is placed).
%
% The return value is a list
% \begin{displaysyntax}
% node \word{pointer} \word{parent-index} \word{x} \word{y}
% \word{left} \word{right}
% \end{displaysyntax}
% where \word{left} and \word{right} are the left and right
% children---either such values themselves, or |nil| for ``no
% child''---\word{pointer} is the pointer, and \word{x} and
% \word{y} are coordinates for the node.
% \begin{tcl}
proc binary_branch {heap ptr index min max depth} {
set res [list node $ptr $index]
set r [binary_branch_radius $depth]
set mid [expr {0.5*($max+$min)}]
lappend res [expr {$r*cos($mid)}] [expr {$r*sin($mid)}]
incr depth
set cP [$heap get $ptr [expr {($index+1)%3}]]
if {!$cP} then {
lappend res nil
} else {
set i [lsearch -exact [$heap get $cP] $ptr]
lappend res [binary_branch $heap $cP $i $mid $max $depth]
}
set cP [$heap get $ptr [expr {($index+2)%3}]]
if {!$cP} then {
lappend res nil
} else {
set i [lsearch -exact [$heap get $cP] $ptr]
lappend res [binary_branch $heap $cP $i $min $mid $depth]
}
return $res
}
% \end{tcl}
% \end{proc}
%
% \begin{proc}{binary_branch_radius}
% This procedure computes a radius suitable for nodes at a
% particular depth in a plane cubic tree. The call syntax is
% \begin{displaysyntax}
% |binary_branch_radius| \word{depth}
% \end{displaysyntax}
% and the return value is the radius. It relies heavily on
% memoization to avoid recomputing depths.
% \begin{tcl}
proc binary_branch_radius {d} {
variable binary_branch_radius
if {[info exists binary_branch_radius($d)]} then {
return $binary_branch_radius($d)
} elseif {$d<2} then {
error "This shouldn't happen: depth is $d."
}
% \end{tcl}
% The idea for the computed radius is that it should put child
% nodes at unit distance from their parent. Hence the child, the
% parent, and the origin form a triangle with sides $r_0$
% (previous radius), $r_1$ (sought radius), and $1$. Furthermore
% the angle at the origin (i.e., between the $r_0$ and $r_1$
% sides). This lets one compute the triangle height perpendicular
% to the $r_1$ side, and since that height also splits the triangle
% in to right trangles with two sides known, $r_1$ is easy to
% compute.
% \begin{tcl}
set r0 [binary_branch_radius [expr {$d-1}]]
set alpha [expr {acos(-1)/3*pow(2,1-$d)}]
set h [expr {$r0*sin($alpha)}]
set r1 [expr {$r0*cos($alpha) + sqrt(1-$h*$h)}]
set binary_branch_radius($d) $r1
return $r1
}
% \end{tcl}
% \end{proc}
%
% \begin{arrayvar}{binary_branch_radius}
% The it is necessary to initialise entry |1| in the array.
% \begin{tcl}
set binary_branch_radius(1) 1.0
% \end{tcl}
% \end{arrayvar}
%
% \begin{tcl}
namespace eval draw_binary_branch {}
% \end{tcl}
% The |draw_binary_branch| namespace houses procedures for drawing
% trees such as those returned by |binary_branch|, by evaluating them
% with four extra arguments:
% \begin{displaysyntax}
% \meta{binary branch} \word{canvas} \word{parent-x} \word{parent-y}
% \word{scale}
% \end{displaysyntax}
% The \word{canvas} is the widget to draw in.
% The \word{parent-x} and \word{parent-y} are the coordinates of the
% parent node, which should be connected to the branch by a |line|
% item. The \word{scale} is a scale factor by which all coordinates
% are multiplied before they are drawn. The return value is the
% bounding box (in canvas coordinates, i.e., with $y$ axis flipped
% and \word{scale} applied) of the items drawn by the command.
%
% \begin{proc}[draw_binary_branch]{nil}
% The |nil| procedure does nothing other than produces a sensible
% bounding box.
% \begin{tcl}
proc draw_binary_branch::nil {canvas x y s} {
set p [list [expr {$x*$s}] [expr {-$y*$s}]]
return [concat $p $p]
}
% \end{tcl}
% \end{proc}
%
% \begin{proc}[draw_binary_branch]{node}
% The |node| procedure does all the actual drawing.
% \begin{tcl}
proc draw_binary_branch::node {ptr index x1 y1 left right canvas x0 y0 s} {
set xL [list [expr {$x0*$s}] [expr {$x1*$s}]]
set yL [list [expr {-$y0*$s}] [expr {-$y1*$s}]]
$canvas create line [lindex $xL 0] [lindex $yL 0] [lindex $xL 1]\
[lindex $yL 1]
foreach {x y} [eval $left [list $canvas $x1 $y1 $s]] {
lappend xL $x ; lappend yL $y
}
foreach {x y} [eval $right [list $canvas $x1 $y1 $s]] {
lappend xL $x ; lappend yL $y
}
set xL [lsort -real $xL] ; set yL [lsort -real $yL]
return [list [lindex $xL 0] [lindex $yL 0]\
[lindex $xL end] [lindex $yL end]]
}
% \end{tcl}
% \end{proc}
%
%
% \begin{proc}{draw_cubic_tree}
% This procedure draws a cubic tree on a canvas, reconfiguring its
% |-scrollregion| to match the bounding box of the tree drawing.
% The call syntax is
% \begin{displaysyntax}
% |draw_cubic_tree| \word{canvas} \word{heap-cmd}
% \word{center-ptr} \word{scale}
% \end{displaysyntax}
% \begin{tcl}
proc draw_cubic_tree {c heap cptr scale} {
set xL [list 0]; set yL [list 0]
foreach index {0 1 2} {
set bP [$heap get $cptr $index]
if {!$bP} then {continue}
set max [expr {(3-$index)*acos(-0.5)}]
set min [expr {(2-$index)*acos(-0.5)}]
set i [lsearch -exact [$heap get $bP] $cptr]
foreach {x y} [
namespace inscope draw_binary_branch [
binary_branch $heap $bP $i $min $max 1
] $c 0 0 $scale
] {lappend xL $x; lappend yL $y}
}
set xL [lsort -real $xL]
set yL [lsort -real $yL]
$c configure -scrollregion [
list [expr {round([lindex $xL 0]) - 2}]\
[expr {round([lindex $yL 0]) - 2}]\
[expr {round([lindex $xL end]) + 2}]\
[expr {round([lindex $yL end]) + 2}]
]
}
% \end{tcl}
% \end{proc}
%
%
% \begin{proc}{make_treewindow}
% This procedure creates a |toplevel| window |.tree| with a
% scrollable canvas |.tree.c|, and suitable bindings. There are no
% arguments, nor any particular return value.
% \begin{tcl}
proc make_treewindow {} {
toplevel .tree
grid [label .tree.msg] -row 0 -column 0 -columnspan 2 -sticky wns
grid rowconfigure .tree 0 -weight 0
grid [canvas .tree.c] -row 1 -column 0 -sticky nsew
grid [scrollbar .tree.v -orient vertical -command {.tree.c yview}]\
-row 1 -column 1 -sticky ns
grid rowconfigure .tree 1 -weight 1
grid columnconfigure .tree 0 -weight 1
grid columnconfigure .tree 1 -weight 0
grid [scrollbar .tree.h -orient horizontal -command {.tree.c xview}]\
-row 2 -column 0 -sticky ew
grid rowconfigure .tree 2 -weight 0
.tree.c configure -width 200 -height 200 -scrollregion {0 0 400 400}\
-xscrollcommand {.tree.h set} -yscrollcommand {.tree.v set}
}
% \end{tcl}
% \end{proc}
%
%
% \subsection{The actual test}
%
%
% \begin{proc}{move_endpoint}
% This is a utility procedure to help keep the tree consistent when
% moving edge endpoints. The call syntax is
% \begin{displaysyntax}
% |move_endpoint| \word{heap} \word{from-ptr} \word{from-index}
% \word{to-pos} \word{to-index}
% \end{displaysyntax}
% and this moves whatever is at
% \textit{from-ptr}[\textit{from-index}] to
% \textit{to-ptr}[\textit{to-index}].
% \begin{tcl}
proc move_endpoint {heap fP fi tP ti} {
set oP [$heap get $fP $fi]
if {$oP} then {
set oi [lsearch -exact [$heap get $oP] $fP]
if {$oi<0 || $oi>2} then {error "Broken edge"}
$heap set $oP $oi $tP
}
$heap set $tP $ti $oP
$heap set $fP $fi no
}
% \end{tcl}
% \end{proc}
%
%
% \begin{proc}{rotate_edge}
% This procedure rotates an edge in the tree.
% \begin{displaysyntax}
% |rotate_edge| \word{heap} \word{ptr} \word{index}
% \end{displaysyntax}
% \begin{tcl}
proc rotate_edge {heap aP ai} {
set bP [$heap get $aP $ai]
if {!$bP} then {return}
set bi [lsearch -exact [$heap get $bP] $aP]
if {$bi<0 || $bi>2} then {error "Broken edge"}
move_endpoint $heap $bP [expr {($bi+1)%3}] $aP $ai
move_endpoint $heap $aP [expr {($ai+1)%3}] $bP $bi
$heap set $aP [expr {($ai+1)%3}] $bP
$heap set $bP [expr {($bi+1)%3}] $aP
}
% \end{tcl}
% \end{proc}
%
%
% \begin{proc}{prune_branch}
% This procedure prunes a branch, by deleting all subbranches not
% exceeding a particular depth. It has the call syntax
% \begin{displaysyntax}
% |prune_branch| \word{heap} \word{ptr} \word{index} \word{depth}
% \end{displaysyntax}
% where \word{ptr} and \word{index} identify the edge on which the
% other side of which the branch to prune lies, and \word{depth} is
% the number of leves to prune. The return value is a pair
% \begin{displaysyntax}
% \word{ptr} \word{index}
% \end{displaysyntax}
% which identify the edge back to the rest of the tree.
% \begin{tcl}
proc prune_branch {heap ptr index depth} {
set bP [$heap get $ptr $index]
if {!$bP} then {return [list no ""]}
set bi [lsearch -exact [$heap get $bP] $ptr]
if {$depth<=0} then {return [list $bP $bi]}
incr depth -1
set L {}
foreach i {1 2} {
set res [prune_branch $heap $bP [expr {($bi+$i)%3}] $depth]
if {[lindex $res 0]} then {lappend L $res}
}
if {[llength $L]==2} then {
foreach res $L {
foreach {cP i} $res break
$heap set $cP $i $bP
}
$heap set $bP [list no [lindex $L 0 0] [lindex $L 1 0] 0]
return [list $bP 0]
}
$heap dispose $bP
if {[llength $L]} then {
return [lindex $L 0]
} else {
return [list no ""]
}
}
% \end{tcl}
% \end{proc}
%
% \begin{proc}{sloppy_prune_branch}
% This procedure prunes a branch, by deleting all subbranches not
% exceeding a particular depth, but is ``sloppy'' and drops
% branches every now and then (so that we can get something to
% |garbagecollect|). The call syntax is the same as for
% |prune_branch|, i.e.,
% \begin{displaysyntax}
% |sloppy_prune_branch| \word{heap} \word{ptr} \word{index}
% \word{depth}
% \end{displaysyntax}
% where \word{ptr} and \word{index} identify the edge on which the
% other side of which the branch to prune lies, and \word{depth} is