File Coverage

lib/Text/KnuthPlass.xs
Criterion Covered Total %
statement 144 161 89.4
branch 129 178 72.4
condition n/a
subroutine n/a
pod n/a
total 273 339 80.5


line stmt bran cond sub pod time code
1             #include "EXTERN.h"
2             #include "perl.h"
3             #include "XSUB.h"
4              
5             #include "ppport.h"
6              
7             #define isPenalty(sv) (SvROK(sv) && sv_derived_from(sv, "Text::KnuthPlass::Penalty"))
8             #define ivHash(hv, key) (IV)SvIV(*hv_fetch((HV*)hv, key, strlen(key), TRUE))
9             #define nvHash(hv, key) (NV)SvNVx(*hv_fetch((HV*)hv, key, strlen(key), TRUE))
10             #define debug(x)
11              
12             typedef SV * Text_KnuthPlass;
13              
14             struct Breakpoint_s {
15             struct Breakpoint_s * prev;
16             struct Breakpoint_s * next;
17             struct Breakpoint_s * previous;
18             struct Breakpoint_s * active; /* Just for candidates */
19             NV demerits;
20             NV ratio;
21             IV line;
22             IV position;
23             IV fitness_class;
24             HV* totals;
25             };
26              
27             typedef struct Breakpoint_s Breakpoint;
28              
29             typedef struct LinkedList_s {
30             Breakpoint* head;
31             Breakpoint* tail;
32             IV list_size;
33             AV* to_free;
34             } LinkedList;
35              
36             // overrides _computeCost() in Perl (note name difference!)
37             // a = $active, current_line = $currentLine in Perl
38 5384           NV _compute_cost(Text_KnuthPlass self, IV start, IV end, Breakpoint* a,
39             IV current_line, AV* nodes) {
40 5384 50         IV infinity = ivHash(self, "infinity"); // $self->{'infinity'}
41 5384           HV* sum = (HV*)SvRV(*hv_fetch((HV*)self, "sum", 3, FALSE));
42 5384           HV* totals = a->totals;
43 5384 100         NV width = nvHash(sum, "width") - nvHash(totals,"width");
    50          
44 5384           AV* linelengths = (AV*)SvRV(*hv_fetch((HV*)self, "linelengths", 11, FALSE));
45             /* ll is index of LAST element of linelengths, NOT the size (length) */
46 5384           I32 ll = av_len(linelengths);
47             NV stretch = 0;
48             NV shrink = 0;
49 5384 100         NV linelength = SvNV(*av_fetch(linelengths, current_line <= ll ? current_line-1 : ll, 0));
    100          
    100          
    100          
50              
51             debug(warn("Computing cost from %i to %i\n", start, end));
52             debug(warn("Sum width: %f\n", nvHash(sum, "width")));
53             debug(warn("Total width: %f\n", nvHash(totals, "width")));
54              
55 5384 50         if (isPenalty(*av_fetch(nodes, end, 0))) {
    100          
56             debug(warn("Adding penalty width\n"));
57 1839 100         width += nvHash(SvRV(*av_fetch(nodes,end, 0)),"width");
58             }
59             debug(warn("Width %f, linelength %f\n", width, linelength));
60              
61 5384 100         if (width < linelength) {
62 4075 100         stretch = nvHash(sum, "stretch") - nvHash(totals, "stretch");
    50          
63             debug(warn("Stretch %f\n", stretch));
64 4075 100         if (stretch > 0) {
65 3264           return (linelength - width) / stretch;
66             } else {
67 811           return infinity;
68             }
69 1309 100         } else if (width > linelength) {
70             debug(warn("Shrink %f\n", shrink));
71 1243 100         shrink = nvHash(sum, "shrink") - nvHash(totals, "shrink");
    50          
72 1243 100         if (shrink > 0) {
73 36           return (linelength - width) / shrink;
74             } else {
75 1207           return infinity;
76             }
77             } else { return 0; }
78             }
79              
80             // overrides _computeSum() in Perl (note name difference!)
81 586           HV* _compute_sum(Text_KnuthPlass self, IV index, AV* nodes) {
82 586           HV* result = newHV();
83 586           HV* sum = (HV*)SvRV(*hv_fetch((HV*)self, "sum", 3, FALSE));
84 586 50         IV infinity = ivHash(self, "infinity");
85 586 50         NV width = nvHash(sum, "width");
86 586 50         NV stretch = nvHash(sum, "stretch");
87 586 100         NV shrink = nvHash(sum, "shrink");
88 586           I32 len = av_len(nodes);
89 586           I32 i = index;
90              
91 1151 100         while (i < len) {
92 1106           SV* e = *av_fetch(nodes, i, 0);
93 1106 100         if (sv_derived_from(e, "Text::KnuthPlass::Glue")) {
94 411 100         width += nvHash(SvRV(e), "width");
95 411 100         stretch += nvHash(SvRV(e), "stretch");
96 411 100         shrink += nvHash(SvRV(e), "shrink");
97 695 100         } else if (sv_derived_from(e, "Text::KnuthPlass::Box") ||
    50          
98 154 50         (isPenalty(e) && ivHash(SvRV(e), "penalty") == -infinity
    50          
    50          
99 0 0         && i > index)) {
100             break;
101             }
102 565           i++;
103             }
104              
105 586           hv_stores(result, "width", newSVnv(width));
106 586           hv_stores(result, "stretch", newSVnv(stretch));
107 586           hv_stores(result, "shrink", newSVnv(shrink));
108 586           return result;
109             }
110              
111 1176           Breakpoint* _new_breakpoint (void) {
112             Breakpoint* dummy;
113 1176           HV* totals = newHV();
114 1176           Newxz(dummy, 1, Breakpoint);
115 1176           dummy->prev = dummy->next = dummy->previous = dummy->active = NULL;
116 1176           dummy->demerits = dummy->ratio = 0;
117 1176           dummy->line = dummy->position = dummy->fitness_class = 0;
118 1176           hv_stores(totals, "width", newSVnv(0));
119 1176           hv_stores(totals, "stretch", newSVnv(0));
120 1176           hv_stores(totals, "shrink", newSVnv(0));
121 1176           dummy->totals = totals;
122 1176           return dummy;
123             }
124              
125 0           void free_breakpoint(Breakpoint* b) {
126 0 0         while (b) {
127 0           Breakpoint* p = b->previous;
128 0 0         if (SvROK(b->totals)) {
129 0           sv_free((SV*)b->totals);
130 0           Safefree(b);
131             }
132             b = p;
133             }
134 0 0         if (b && b->totals) sv_free((SV*)b->totals);
    0          
135 0 0         if (b) Safefree(b);
136 0           }
137              
138 569           void _unlinkKP(LinkedList* list, Breakpoint* a) {
139 569 100         if (!a->prev) { list->head = a->next; } else { a->prev->next = a->next; }
140 569 100         if (!a->next) { list->tail = a->prev; } else { a->next->prev = a->prev; }
141 569           list->list_size--;
142 569           av_push(list->to_free, newSViv((IV)a));
143 569           }
144              
145             MODULE = Text::KnuthPlass PACKAGE = Text::KnuthPlass
146              
147             void
148             _init_nodelist(self)
149             Text_KnuthPlass self
150              
151             CODE:
152             // overrides _init_nodelist() in Perl
153             LinkedList* activelist;
154 4           Newxz(activelist, 1, LinkedList);
155 4           activelist->head = activelist->tail = _new_breakpoint();
156 4           activelist->list_size = 1;
157 4           activelist->to_free = newAV();
158 4           hv_stores((HV*)self, "activeNodes", ((SV *)activelist));
159              
160             void _active_to_breaks(self)
161             Text_KnuthPlass self
162              
163             PREINIT:
164             LinkedList* activelist;
165             Breakpoint* b;
166             Breakpoint* best = NULL;
167              
168             PPCODE:
169             // overrides _active_to_breaks() in Perl
170 4           activelist = (LinkedList*)(*hv_fetch((HV*)self, "activeNodes", 11, FALSE));
171              
172 25 100         for (b = activelist->head; b; b = b->next) {
173 21 100         if (!best || b->demerits < best->demerits) best = b;
    50          
174             }
175 30 100         while (best) {
176 26           HV* posnode = newHV();
177 26           hv_stores(posnode, "position", newSViv(best->position));
178 26           hv_stores(posnode, "ratio", newSVnv(best->ratio));
179 26 50         XPUSHs(sv_2mortal(newRV((SV*)posnode)));
180 26           best = best->previous;
181             }
182              
183             void _cleanup(self)
184             Text_KnuthPlass self
185              
186             CODE:
187             // overrides _cleanup() in Perl (which is a dummy stub)
188             Breakpoint* b;
189             LinkedList* activelist;
190 4           activelist = (LinkedList*)(*hv_fetch((HV*)self, "activeNodes", 11, FALSE));
191             return;
192             /* Free nodes on the activelist */
193             b = activelist->head;
194             while (b) {
195             Breakpoint* n = b->next;
196             free_breakpoint(b);
197             b = n;
198             }
199             /* Shut down the activelist itself */
200             while (av_len(activelist->to_free)) {
201             SV* pointer = av_shift(activelist->to_free);
202             if ((Breakpoint*)SvIV(pointer))
203             free_breakpoint((Breakpoint*)SvIV(pointer));
204             sv_free(pointer);
205             }
206             sv_free((SV*)activelist->to_free);
207             // Safefree(activelist);
208              
209             void
210             _mainloop(self, node, index, nodes)
211             Text_KnuthPlass self
212             SV* node
213             IV index
214             AV* nodes
215              
216             CODE:
217             // overrides _mainloop() in Perl
218 147           LinkedList* activelist = (LinkedList*)(*hv_fetch((HV*)self, "activeNodes", 11, FALSE));
219 147 50         IV tolerance = ivHash(self, "tolerance");
220 147 50         IV infinity = ivHash(self, "infinity");
221 147           SV* demerits_r = *hv_fetch((HV*)self, "demerits", 8, FALSE);
222             NV ratio = 0;
223             IV nodepenalty = 0;
224             NV demerits = 0;
225             IV linedemerits = 0, flaggeddemerits = 0, fitnessdemerits = 0;
226             Breakpoint* candidates[4];
227             NV badness;
228             IV current_line = 0;
229             HV* tmpsum;
230             IV current_class = 0;
231 147           Breakpoint* active = activelist->head;
232             Breakpoint* next;
233              
234 147 50         if (demerits_r && SvRV(demerits_r)) {
    50          
235 147 50         linedemerits = ivHash(SvRV(demerits_r), "line");
236 147 50         flaggeddemerits = ivHash(SvRV(demerits_r), "flagged");
237 147 50         fitnessdemerits = ivHash(SvRV(demerits_r), "fitness");
238             } else {
239 0           croak("Demerits hash not properly set!");
240             }
241              
242 147 50         if (isPenalty(node)) {
    100          
243 32 50         nodepenalty = SvIV(*hv_fetch((HV*)SvRV(node), "penalty", 7, TRUE));
244             }
245              
246 844 100         while (active) {
247             int t;
248 697           candidates[0] = NULL; candidates[1] = NULL;
249 697           candidates[2] = NULL; candidates[3] = NULL;
250             debug(warn("Outer\n"));
251 5384 50         while (active) {
252              
253 5384           next = active->next;
254 5384           IV position = active->position;
255              
256             debug(warn("Inner loop\n"));
257              
258 5384           current_line = 1+ active->line;
259             /*warn("_mainloop, current_line=%i\n", current_line);*/
260 5384           ratio = _compute_cost(self, position, index, active, current_line, nodes);
261             debug(warn("Got a ratio of %f\n", ratio));
262              
263 5384 100         if (ratio < 1 || (isPenalty(node) && nodepenalty == -infinity)) {
    50          
    100          
    100          
264             debug(warn("Dropping a node\n"));
265 569           _unlinkKP(activelist, active);
266             }
267              
268 5384 100         if (-1 <= ratio && ratio <= tolerance) {
    100          
269 2439           SV* nodeAtPos = *av_fetch(nodes, position, FALSE);
270 2439           badness = 100 * ratio * ratio * ratio;
271             debug(warn("Badness is %f\n", badness));
272 2439 50         if (isPenalty(node) && nodepenalty > 0) {
    100          
    100          
273 688           demerits = linedemerits + badness + nodepenalty;
274 1751 50         } else if (isPenalty(node) && nodepenalty != -infinity) {
    100          
    50          
275 0           demerits = linedemerits + badness - nodepenalty;
276             } else {
277 1751           demerits = linedemerits + badness;
278             }
279 2439           demerits = demerits * demerits;
280 2439 50         if (isPenalty(node) && isPenalty(SvRV(nodeAtPos))) {
    100          
    50          
    0          
281 0           demerits = demerits + (flaggeddemerits *
282 0 0         ivHash(node, "flagged") *
283 0 0         ivHash(SvRV(nodeAtPos), "flagged"));
284             }
285              
286 2439 100         if (ratio < -0.5) current_class = 0; // tight
287 2437 100         else if (ratio <= 0.5) current_class = 1; // normal
288 2073 100         else if (ratio <= 1) current_class = 2; // loose
289             else current_class = 3; // very loose
290              
291 2439 100         if (abs(current_class - active->fitness_class) > 1)
292 594           demerits += fitnessdemerits;
293              
294 2439           demerits += active->demerits;
295              
296 2439 100         if (!candidates[current_class] ||
    100          
297 1853           demerits < candidates[current_class]->demerits) {
298             debug(warn("Setting c %i\n", current_class));
299 1394 100         if (!candidates[current_class])
300 586           candidates[current_class] = _new_breakpoint();
301 1394           candidates[current_class]->active = active;
302 1394           candidates[current_class]->demerits = demerits;
303 1394           candidates[current_class]->ratio = ratio;
304             }
305             }
306             active = next;
307 5384 100         if (!active || active->line >= current_line)
    100          
308             break;
309             }
310             debug(warn("Post inner loop\n"));
311              
312              
313 3632 100         for (t = 0; t <= 3; t++) {
314 2788 100         if (candidates[t]) {
315 586           Breakpoint* newnode = _new_breakpoint();
316 586           HV* tmpsum = _compute_sum(self, index, nodes);
317 586           newnode->position = index;
318 586           newnode->demerits = candidates[t]->demerits;
319 586           newnode->ratio = candidates[t]->ratio;
320 586           newnode->line = candidates[t]->active->line + 1;
321 586           newnode->fitness_class = t;
322 586           newnode->totals = tmpsum;
323             debug(warn("Setting previous to %p\n", candidates[t]->active));
324 586           newnode->previous = candidates[t]->active;
325 586 100         if (active) {
326             debug(warn("Before\n"));
327 547           newnode->prev = active->prev;
328 547           newnode->next = active;
329 547 100         if (!active->prev) { activelist->head = newnode; }
330 533           else { active->prev->next = newnode; }
331 547           active->prev = newnode;
332 547           activelist->list_size++;
333             } else {
334             debug(warn("After\n"));
335 39 50         if (!activelist->head) {
336 0           activelist->head = activelist->tail = newnode;
337 0           newnode->prev = newnode->next = NULL;
338             } else {
339 39           newnode->prev = activelist->tail;
340 39           newnode->next = NULL;
341 39           activelist->tail->next = newnode;
342 39           activelist->tail = newnode;
343 39           activelist->list_size++;
344             }
345             }
346 586           sv_free((SV*)candidates[t]->totals);
347 586           Safefree(candidates[t]);
348             } // demerits check (candidates[fitness class] > 0)
349             } // fitness class (t) 0..3 loop
350             } // while active loop
351