File Coverage

XS.xs
Criterion Covered Total %
statement 334 359 93.0
branch 313 414 75.6
condition n/a
subroutine n/a
pod n/a
total 647 773 83.7


line stmt bran cond sub pod time code
1             #include
2             #include
3             #include
4              
5             #include
6             #include
7             #include
8             #include
9              
10             /* uncomment to enable debugging output */
11             /* #define DEBUG 1 */
12              
13             /* ****************************************************************************
14             * CHARACTER CLASS METHODS
15             * ****************************************************************************
16             */
17 647           int charIsSpace(char ch) {
18 647 100         if (ch == ' ') return 1;
19 414 100         if (ch == '\t') return 1;
20 410           return 0;
21             }
22 1033           int charIsEndspace(char ch) {
23 1033 100         if (ch == '\n') return 1;
24 849 50         if (ch == '\r') return 1;
25 849 50         if (ch == '\f') return 1;
26 849           return 0;
27             }
28 647           int charIsWhitespace(char ch) {
29 647 100         return charIsSpace(ch) || charIsEndspace(ch);
    100          
30             }
31 576           int charIsIdentifier(char ch) {
32 576 100         if ((ch >= 'a') && (ch <= 'z')) return 1;
    100          
33 193 100         if ((ch >= 'A') && (ch <= 'Z')) return 1;
    100          
34 192 100         if ((ch >= '0') && (ch <= '9')) return 1;
    100          
35 176 100         if (ch == '_') return 1;
36 169 100         if (ch == '$') return 1;
37 167 50         if (ch == '\\') return 1;
38 167 50         if (ch > 126) return 1;
39 167           return 0;
40             }
41 217           int charIsInfix(char ch) {
42             /* EOL characters before+after these characters can be removed */
43 217 100         if (ch == ',') return 1;
44 205 100         if (ch == ';') return 1;
45 143 50         if (ch == ':') return 1;
46 143 100         if (ch == '=') return 1;
47 91 50         if (ch == '&') return 1;
48 91 50         if (ch == '%') return 1;
49 91 50         if (ch == '*') return 1;
50 91 50         if (ch == '<') return 1;
51 91 50         if (ch == '>') return 1;
52 91 50         if (ch == '?') return 1;
53 91 50         if (ch == '|') return 1;
54 91 50         if (ch == '\n') return 1;
55 91           return 0;
56             }
57 160           int charIsPrefix(char ch) {
58             /* EOL characters after these characters can be removed */
59 160 100         if (ch == '{') return 1;
60 148 100         if (ch == '(') return 1;
61 132 100         if (ch == '[') return 1;
62 127 50         if (ch == '!') return 1;
63 127           return charIsInfix(ch);
64             }
65 116           int charIsPostfix(char ch) {
66             /* EOL characters before these characters can be removed */
67 116 100         if (ch == '}') return 1;
68 110 100         if (ch == ')') return 1;
69 93 100         if (ch == ']') return 1;
70 90           return charIsInfix(ch);
71             }
72              
73             /* ****************************************************************************
74             * TYPE DEFINITIONS
75             * ****************************************************************************
76             */
77             typedef enum {
78             NODE_EMPTY,
79             NODE_WHITESPACE,
80             NODE_BLOCKCOMMENT,
81             NODE_LINECOMMENT,
82             NODE_IDENTIFIER,
83             NODE_LITERAL,
84             NODE_SIGIL
85             } NodeType;
86             #ifdef DEBUG
87             static char* strNodeTypes[] = {
88             "empty",
89             "whitespace",
90             "block comment",
91             "line comment",
92             "identifier",
93             "literal",
94             "sigil"
95             };
96             #endif
97              
98             struct _Node;
99             typedef struct _Node Node;
100             struct _Node {
101             /* linked list pointers */
102             Node* prev;
103             Node* next;
104             /* node internals */
105             char* contents;
106             size_t length;
107             NodeType type;
108             };
109              
110             typedef struct {
111             /* linked list pointers */
112             Node* head;
113             Node* tail;
114             /* doc internals */
115             const char* buffer;
116             size_t length;
117             size_t offset;
118             } JsDoc;
119              
120              
121             /* ****************************************************************************
122             * NODE CHECKING MACROS/FUNCTIONS
123             * ****************************************************************************
124             */
125              
126             /* checks to see if the node is the given string, case INSENSITIVELY */
127 2           int nodeEquals(Node* node, const char* string) {
128 2           return (strcasecmp(node->contents, string) == 0);
129             }
130              
131             /* checks to see if the node contains the given string, case INSENSITIVELY */
132 33           int nodeContains(Node* node, const char* string) {
133 33           const char* haystack = node->contents;
134 33           size_t len = strlen(string);
135 33           char ul_start[2] = { tolower(*string), toupper(*string) };
136              
137             /* if node is shorter we know we're not going to have a match */
138 33 100         if (len > node->length)
139 2           return 0;
140              
141             /* find the needle in the haystack */
142 112 50         while (haystack && *haystack) {
    50          
143             /* find first char of needle */
144 112           haystack = strpbrk( haystack, ul_start );
145 112 100         if (haystack == NULL)
146 29           return 0;
147             /* check if the rest matches */
148 83 100         if (strncasecmp(haystack, string, len) == 0)
149 2           return 1;
150             /* nope, move onto next character in the haystack */
151 81           haystack ++;
152             }
153              
154             /* no match */
155 33           return 0;
156             }
157             /* checks to see if the node begins with the given string, case INSENSITIVELY
158             */
159 58           int nodeBeginsWith(Node* node, const char* string) {
160 58           size_t len = strlen(string);
161 58 50         if (len > node->length)
162 0           return 0;
163 58           return (strncasecmp(node->contents, string, len) == 0);
164             }
165              
166             /* checks to see if the node ends with the given string, case INSENSITIVELY */
167 4           int nodeEndsWith(Node* node, const char* string) {
168 4           size_t len = strlen(string);
169 4           size_t off = node->length - len;
170 4 50         if (len > node->length)
171 0           return 0;
172 4           return (strncasecmp(node->contents+off, string, len) == 0);
173             }
174              
175             /* macros to help see what kind of node we've got */
176             #define nodeIsWHITESPACE(node) ((node->type == NODE_WHITESPACE))
177             #define nodeIsBLOCKCOMMENT(node) ((node->type == NODE_BLOCKCOMMENT))
178             #define nodeIsLINECOMMENT(node) ((node->type == NODE_LINECOMMENT))
179             #define nodeIsIDENTIFIER(node) ((node->type == NODE_IDENTIFIER))
180             #define nodeIsLITERAL(node) ((node->type == NODE_LITERAL))
181             #define nodeIsSIGIL(node) ((node->type == NODE_SIGIL))
182              
183             #define nodeIsEMPTY(node) ((node->type == NODE_EMPTY) || (node->length==0) || (node->contents=NULL))
184             #define nodeIsCOMMENT(node) (nodeIsBLOCKCOMMENT(node) || nodeIsLINECOMMENT(node))
185             #define nodeIsIECONDITIONALBLOCKCOMMENT(node) (nodeIsBLOCKCOMMENT(node) && nodeBeginsWith(node,"/*@") && nodeEndsWith(node,"@*/"))
186             #define nodeIsIECONDITIONALLINECOMMENT(node) (nodeIsLINECOMMENT(node) && nodeBeginsWith(node,"//@"))
187             #define nodeIsIECONDITIONALCOMMENT(node) (nodeIsIECONDITIONALBLOCKCOMMENT(node) || nodeIsIECONDITIONALLINECOMMENT(node))
188             #define nodeIsPREFIXSIGIL(node) (nodeIsSIGIL(node) && charIsPrefix(node->contents[0]))
189             #define nodeIsPOSTFIXSIGIL(node) (nodeIsSIGIL(node) && charIsPostfix(node->contents[0]))
190             #define nodeIsENDSPACE(node) (nodeIsWHITESPACE(node) && charIsEndspace(node->contents[0]))
191             #define nodeIsCHAR(node,ch) ((node->contents[0]==ch) && (node->length==1))
192              
193             /* ****************************************************************************
194             * NODE MANIPULATION FUNCTIONS
195             * ****************************************************************************
196             */
197             /* allocates a new node */
198 344           Node* JsAllocNode() {
199             Node* node;
200 344           Newz(0, node, 1, Node);
201 344           node->prev = NULL;
202 344           node->next = NULL;
203 344           node->contents = NULL;
204 344           node->length = 0;
205 344           node->type = NODE_EMPTY;
206 344           return node;
207             }
208              
209             /* frees the memory used by a node */
210 344           void JsFreeNode(Node* node) {
211 344 50         if (node->contents)
212 344           Safefree(node->contents);
213 344           Safefree(node);
214 344           }
215 17           void JsFreeNodeList(Node* head) {
216 231 100         while (head) {
217 214           Node* tmp = head->next;
218 214           JsFreeNode(head);
219 214           head = tmp;
220             }
221 17           }
222              
223             /* clears the contents of a node */
224 476           void JsClearNodeContents(Node* node) {
225 476 100         if (node->contents)
226 132           Safefree(node->contents);
227 476           node->contents = NULL;
228 476           node->length = 0;
229 476           }
230              
231             /* sets the contents of a node */
232 476           void JsSetNodeContents(Node* node, const char* string, size_t len) {
233 476           size_t bufSize = len + 1;
234             /* clear node, set new length */
235 476           JsClearNodeContents(node);
236 476           node->length = len;
237             /* allocate string, fill with NULLs, and copy */
238 476           Newz(0, node->contents, bufSize, char);
239 476           strncpy( node->contents, string, len );
240 476           }
241              
242             /* removes the node from the list and discards it entirely */
243 130           void JsDiscardNode(Node* node) {
244 130 100         if (node->prev)
245 98           node->prev->next = node->next;
246 130 100         if (node->next)
247 127           node->next->prev = node->prev;
248 130           JsFreeNode(node);
249 130           }
250              
251             /* appends the node to the given element */
252 325           void JsAppendNode(Node* element, Node* node) {
253 325 50         if (element->next)
254 0           element->next->prev = node;
255 325           node->next = element->next;
256 325           node->prev = element;
257 325           element->next = node;
258 325           }
259              
260             /* collapses a node to a single whitespace character. If the node contains any
261             * endspace characters, that is what we're collapsed to.
262             */
263 130           void JsCollapseNodeToWhitespace(Node* node) {
264 130 50         if (node->contents) {
265 130           char ws = node->contents[0];
266             size_t idx;
267 222 100         for (idx=0; idxlength; idx++) {
268 144 100         if (charIsEndspace(node->contents[idx])) {
269 52           ws = node->contents[idx];
270 52           break;
271             }
272             }
273 130           JsSetNodeContents(node, &ws, 1);
274             }
275 130           }
276              
277             /* collapses a node to a single endspace character. If the node doesn't
278             * contain any endspace characters, the node is collapsed to an empty string.
279             */
280 0           void JsCollapseNodeToEndspace(Node* node) {
281 0 0         if (node->contents) {
282 0           char ws = 0;
283             size_t idx;
284 0 0         for (idx=0; idxlength; idx++) {
285 0 0         if (charIsEndspace(node->contents[idx])) {
286 0           ws = node->contents[idx];
287 0           break;
288             }
289             }
290 0           JsClearNodeContents(node);
291 0 0         if (ws)
292 0           JsSetNodeContents(node, &ws, 1);
293             }
294 0           }
295              
296              
297             /* ****************************************************************************
298             * TOKENIZING FUNCTIONS
299             * ****************************************************************************
300             */
301              
302             /* extracts a quoted literal string */
303 14           void _JsExtractLiteral(JsDoc* doc, Node* node) {
304 14           const char* buf = doc->buffer;
305 14           size_t offset = doc->offset;
306 14           char delimiter = buf[offset];
307             /* skip start of literal */
308 14           offset ++;
309             /* search for end of literal */
310 321 50         while (offset < doc->length) {
311 321 100         if (buf[offset] == '\\') {
312             /* escaped character; skip */
313 2           offset ++;
314             }
315 319 100         else if (buf[offset] == delimiter) {
316 14           const char* start = buf + doc->offset;
317 14           size_t length = offset - doc->offset + 1;
318 14           JsSetNodeContents(node, start, length);
319 14           node->type = NODE_LITERAL;
320 14           return;
321             }
322             /* move onto next character */
323 307           offset ++;
324             }
325 0           croak( "unterminated quoted string literal" );
326             }
327              
328             /* extracts a block comment */
329 26           void _JsExtractBlockComment(JsDoc* doc, Node* node) {
330 26           const char* buf = doc->buffer;
331 26           size_t offset = doc->offset;
332              
333             /* skip start of comment */
334 26           offset ++; /* skip "/" */
335 26           offset ++; /* skip "*" */
336              
337             /* search for end of comment block */
338 1430 50         while (offset < doc->length) {
339 1430 100         if (buf[offset] == '*') {
340 35 100         if (buf[offset+1] == '/') {
341 26           const char* start = buf + doc->offset;
342 26           size_t length = offset - doc->offset + 2;
343 26           JsSetNodeContents(node, start, length);
344 26           node->type = NODE_BLOCKCOMMENT;
345 26           return;
346             }
347             }
348             /* move onto next character */
349 1404           offset ++;
350             }
351              
352 0           croak( "unterminated block comment" );
353             }
354              
355             /* extracts a line comment */
356 9           void _JsExtractLineComment(JsDoc* doc, Node* node) {
357 9           const char* buf = doc->buffer;
358 9           size_t offset = doc->offset;
359              
360             /* skip start of comment */
361 9           offset ++; /* skip "/" */
362 9           offset ++; /* skip "/" */
363              
364             /* search for end of line */
365 402 100         while ((offset < doc->length) && !charIsEndspace(buf[offset]))
    100          
366 393           offset ++;
367              
368             /* found it ! */
369             {
370 9           const char* start = buf + doc->offset;
371 9           size_t length = offset - doc->offset;
372 9           JsSetNodeContents(node, start, length);
373 9           node->type = NODE_LINECOMMENT;
374             }
375 9           }
376              
377             /* extracts a run of whitespace characters */
378 130           void _JsExtractWhitespace(JsDoc* doc, Node* node) {
379 130           const char* buf = doc->buffer;
380 130           size_t offset = doc->offset;
381 355 100         while ((offset < doc->length) && charIsWhitespace(buf[offset]))
    100          
382 225           offset ++;
383 130           JsSetNodeContents(node, doc->buffer+doc->offset, offset-doc->offset);
384 130           node->type = NODE_WHITESPACE;
385 130           }
386              
387             /* extracts an identifier */
388 73           void _JsExtractIdentifier(JsDoc* doc, Node* node) {
389 73           const char* buf = doc->buffer;
390 73           size_t offset = doc->offset;
391 408 100         while ((offset < doc->length) && charIsIdentifier(buf[offset]))
    100          
392 335           offset ++;
393 73           JsSetNodeContents(node, doc->buffer+doc->offset, offset-doc->offset);
394 73           node->type = NODE_IDENTIFIER;
395 73           }
396              
397             /* extracts a -single- symbol/sigil */
398 92           void _JsExtractSigil(JsDoc* doc, Node* node) {
399 92           JsSetNodeContents(node, doc->buffer+doc->offset, 1);
400 92           node->type = NODE_SIGIL;
401 92           }
402              
403             /* tokenizes the given string and returns the list of nodes */
404 20           Node* JsTokenizeString(const char* string) {
405             JsDoc doc;
406              
407             /* initialize our JS document object */
408 20           doc.head = NULL;
409 20           doc.tail = NULL;
410 20           doc.buffer = string;
411 20           doc.length = strlen(string);
412 20           doc.offset = 0;
413              
414             /* parse the JS */
415 364 100         while ((doc.offset < doc.length) && (doc.buffer[doc.offset])) {
    50          
416             /* allocate a new node */
417 344           Node* node = JsAllocNode();
418 344 100         if (!doc.head)
419 19           doc.head = node;
420 344 100         if (!doc.tail)
421 19           doc.tail = node;
422              
423             /* parse the next node out of the JS */
424 344 100         if (doc.buffer[doc.offset] == '/') {
425 43 100         if (doc.buffer[doc.offset+1] == '*')
426 26           _JsExtractBlockComment(&doc, node);
427 17 100         else if (doc.buffer[doc.offset+1] == '/')
428 9           _JsExtractLineComment(&doc, node);
429             else {
430             /* could be "division" or "regexp", but need to know more about
431             * our context...
432             */
433 8           Node* last = doc.tail;
434 8           char ch = 0;
435              
436             /* find last non-whitespace, non-comment node */
437 20 100         while (nodeIsWHITESPACE(last) || nodeIsCOMMENT(last))
    50          
    100          
438 12           last = last->prev;
439              
440 8           ch = last->contents[last->length-1];
441              
442             /* see if we're "division" or "regexp" */
443 8 100         if (nodeIsIDENTIFIER(last) && nodeEquals(last, "return")) {
    100          
444             /* returning a regexp from a function */
445 1           _JsExtractLiteral(&doc, node);
446             }
447 7 50         else if (ch && ((ch == ')') || (ch == '.') || (ch == ']') || (charIsIdentifier(ch)))) {
    50          
    50          
    100          
    100          
448             /* looks like an identifier; guess its division */
449 2           _JsExtractSigil(&doc, node);
450             }
451             else {
452             /* presume its a regexp */
453 43           _JsExtractLiteral(&doc, node);
454             }
455             }
456             }
457 301 100         else if ((doc.buffer[doc.offset] == '"') || (doc.buffer[doc.offset] == '\'') || (doc.buffer[doc.offset] == '`'))
    100          
    100          
458 8           _JsExtractLiteral(&doc, node);
459 293 100         else if (charIsWhitespace(doc.buffer[doc.offset]))
460 130           _JsExtractWhitespace(&doc, node);
461 163 100         else if (charIsIdentifier(doc.buffer[doc.offset]))
462 73           _JsExtractIdentifier(&doc, node);
463             else
464 90           _JsExtractSigil(&doc, node);
465              
466             /* move ahead to the end of the parsed node */
467 344           doc.offset += node->length;
468              
469             /* add the node to our list of nodes */
470 344 100         if (node != doc.tail)
471 325           JsAppendNode(doc.tail, node);
472 344           doc.tail = node;
473              
474             /* some debugging info */
475             #ifdef DEBUG
476             {
477             int idx;
478             printf("----------------------------------------------------------------\n");
479             printf("%s: %s\n", strNodeTypes[node->type], node->contents);
480             printf("next: '");
481             for (idx=0; idx<=10; idx++) {
482             if ((doc.offset+idx) >= doc.length) break;
483             if (!doc.buffer[doc.offset+idx]) break;
484             printf("%c", doc.buffer[doc.offset+idx]);
485             }
486             printf("'\n");
487             }
488             #endif
489             }
490              
491             /* return the node list */
492 20           return doc.head;
493             }
494              
495             /* ****************************************************************************
496             * MINIFICATION FUNCTIONS
497             * ****************************************************************************
498             */
499              
500             /* collapses all of the nodes to their shortest possible representation */
501 19           void JsCollapseNodes(Node* curr) {
502 363 100         while (curr) {
503 344           Node* next = curr->next;
504 344           switch (curr->type) {
505             case NODE_WHITESPACE:
506             /* all WS gets collapsed */
507 130           JsCollapseNodeToWhitespace(curr);
508 130           break;
509             case NODE_BLOCKCOMMENT:
510             /* block comments get collapsed to WS if that's a side-affect
511             * of their placement in the JS document.
512             */
513 26 50         if (!nodeIsIECONDITIONALBLOCKCOMMENT(curr)) {
    100          
    100          
514 25           int convert_to_ws = 0;
515             /* find surrounding non-WS nodes */
516 25           Node* nonws_prev = curr->prev;
517 25           Node* nonws_next = curr->next;
518 36 100         while (nonws_prev && nodeIsWHITESPACE(nonws_prev))
    100          
519 11           nonws_prev = nonws_prev->prev;
520 49 100         while (nonws_next && nodeIsWHITESPACE(nonws_next))
    100          
521 24           nonws_next = nonws_next->next;
522             /* check what we're between... */
523 25 100         if (nonws_prev && nonws_next) {
    50          
524             /* between identifiers? convert to WS */
525 11 100         if (nodeIsIDENTIFIER(nonws_prev) && nodeIsIDENTIFIER(nonws_next))
    50          
526 0           convert_to_ws = 1;
527             /* between possible pre/post increment? convert to WS */
528 11 100         if (nodeIsCHAR(nonws_prev,'-') && nodeIsCHAR(nonws_next,'-'))
    50          
    100          
    50          
529 1           convert_to_ws = 1;
530 11 100         if (nodeIsCHAR(nonws_prev,'+') && nodeIsCHAR(nonws_next,'+'))
    50          
    100          
    50          
531 1           convert_to_ws = 1;
532             }
533             /* convert to WS */
534 25 100         if (convert_to_ws) {
535 2           JsSetNodeContents(curr," ",1);
536 2           curr->type = NODE_WHITESPACE;
537             }
538             }
539 26           break;
540             default:
541 188           break;
542             }
543 344           curr = next;
544             }
545 19           }
546              
547             /* checks to see whether we can prune the given node from the list.
548             *
549             * THIS is the function that controls the bulk of the minification process.
550             */
551             enum {
552             PRUNE_NO,
553             PRUNE_PREVIOUS,
554             PRUNE_CURRENT,
555             PRUNE_NEXT
556             };
557 364           int JsCanPrune(Node* node) {
558 364           Node* prev = node->prev;
559 364           Node* next = node->next;
560              
561 364           switch (node->type) {
562             case NODE_EMPTY:
563             /* prune empty nodes */
564 0           return PRUNE_CURRENT;
565             case NODE_WHITESPACE:
566             /* multiple whitespace gets pruned to preserve endspace */
567 62 100         if (prev && nodeIsENDSPACE(prev))
    100          
    50          
568 4           return PRUNE_CURRENT;
569 58 100         if (prev && nodeIsWHITESPACE(prev))
    50          
570 0           return PRUNE_PREVIOUS;
571             /* leading whitespace gets pruned */
572 58 100         if (!prev)
573 16           return PRUNE_CURRENT;
574             /* trailing whitespace gets pruned */
575 42 50         if (!next)
576 0           return PRUNE_CURRENT;
577             /* keep all other whitespace */
578 42           return PRUNE_NO;
579             case NODE_BLOCKCOMMENT:
580             /* keep comments that contain the word "copyright" */
581 24 100         if (nodeContains(node, "copyright"))
582 1           return PRUNE_NO;
583             /* keep comments that are for IE Conditional Compilation */
584 23 50         if (nodeIsIECONDITIONALBLOCKCOMMENT(node))
    100          
    100          
585 1           return PRUNE_NO;
586             /* block comments get pruned */
587 22           return PRUNE_CURRENT;
588             case NODE_LINECOMMENT:
589             /* keep comments that contain the word "copyright" */
590 9 100         if (nodeContains(node, "copyright"))
591 1           return PRUNE_NO;
592             /* keep comments that are for IE Conditional Compilation */
593 8 50         if (nodeIsIECONDITIONALLINECOMMENT(node))
    100          
594 1           return PRUNE_NO;
595             /* line comments get pruned */
596 7           return PRUNE_CURRENT;
597             case NODE_IDENTIFIER:
598             /* remove whitespace (but NOT endspace) after identifiers, provided
599             * that next thing is -NOT- another identifier
600             */
601 95 100         if (next && nodeIsWHITESPACE(next) && !nodeIsENDSPACE(next) && next->next && !nodeIsIDENTIFIER(next->next))
    100          
    50          
    50          
    50          
    100          
602 21           return PRUNE_NEXT;
603             /* keep all identifiers */
604 74           return PRUNE_NO;
605             case NODE_LITERAL:
606             /* keep all literals */
607 14           return PRUNE_NO;
608             case NODE_SIGIL:
609             /* remove whitespace after "prefix" sigils */
610 160 50         if (nodeIsPREFIXSIGIL(node) && next && nodeIsWHITESPACE(next))
    100          
    100          
    100          
611 44           return PRUNE_NEXT;
612             /* remove whitespace before "postfix" sigils */
613 116 50         if (nodeIsPOSTFIXSIGIL(node) && prev && nodeIsWHITESPACE(prev))
    100          
    50          
    100          
614 3           return PRUNE_PREVIOUS;
615             /* remove whitespace (but NOT endspace) after closing brackets */
616 113 100         if (next && nodeIsWHITESPACE(next) && !nodeIsENDSPACE(next) && (nodeIsCHAR(node,')') || nodeIsCHAR(node,'}') || nodeIsCHAR(node,']')))
    100          
    50          
    100          
    100          
    50          
    50          
    0          
    50          
    0          
617 4           return PRUNE_NEXT;
618             /* remove whitespace surrounding "/", EXCEPT where it'd cause "//" */
619 109 100         if (nodeIsCHAR(node,'/') && prev && nodeIsWHITESPACE(prev) && prev->prev && !nodeEndsWith(prev->prev,"/"))
    50          
    50          
    50          
    0          
    0          
620 0           return PRUNE_PREVIOUS;
621 109 100         if (nodeIsCHAR(node,'/') && next && nodeIsWHITESPACE(next) && next->next && !nodeBeginsWith(next->next,"/"))
    50          
    50          
    100          
    50          
    50          
622 1           return PRUNE_NEXT;
623             /* remove whitespace (but NOT endspace) surrounding "-", EXCEPT where it'd cause "--" */
624 108 100         if (nodeIsCHAR(node,'-') && prev && nodeIsWHITESPACE(prev) && !nodeIsENDSPACE(prev) && prev->prev && !nodeIsCHAR(prev->prev,'-'))
    50          
    50          
    100          
    50          
    50          
    50          
    50          
    50          
625 0           return PRUNE_PREVIOUS;
626 108 100         if (nodeIsCHAR(node,'-') && next && nodeIsWHITESPACE(next) && !nodeIsENDSPACE(next) && next->next && !nodeIsCHAR(next->next,'-'))
    50          
    50          
    100          
    50          
    50          
    50          
    100          
    50          
627 4           return PRUNE_NEXT;
628             /* remove whitespace (but NOT endspace) surrounding "+", EXCEPT where it'd cause "++" */
629 104 100         if (nodeIsCHAR(node,'+') && prev && nodeIsWHITESPACE(prev) && !nodeIsENDSPACE(prev) && prev->prev && !nodeIsCHAR(prev->prev,'+'))
    50          
    50          
    100          
    50          
    50          
    50          
    50          
    50          
630 0           return PRUNE_PREVIOUS;
631 104 100         if (nodeIsCHAR(node,'+') && next && nodeIsWHITESPACE(next) && !nodeIsENDSPACE(next) && next->next && !nodeIsCHAR(next->next,'+'))
    50          
    50          
    100          
    50          
    50          
    50          
    100          
    50          
632 4           return PRUNE_NEXT;
633             /* keep all other sigils */
634 100           return PRUNE_NO;
635             }
636             /* keep anything else */
637 0           return PRUNE_NO;
638             }
639              
640             /* prune nodes from the list */
641 19           Node* JsPruneNodes(Node *head) {
642 19           Node* curr = head;
643 383 100         while (curr) {
644             /* see if/howe we can prune this node */
645 364           int prune = JsCanPrune(curr);
646             /* prune. each block is responsible for moving onto the next node */
647 364           Node* prev = curr->prev;
648 364           Node* next = curr->next;
649 364           switch (prune) {
650             case PRUNE_PREVIOUS:
651             /* discard previous node */
652 3           JsDiscardNode(prev);
653             /* reset "head" if that's what got pruned */
654 3 50         if (prev == head)
655 0           prev = curr;
656 3           break;
657             case PRUNE_CURRENT:
658             /* discard current node */
659 49           JsDiscardNode(curr);
660             /* reset "head" if that's what got pruned */
661 49 100         if (curr == head)
662 32 50         head = prev ? prev : next;
663             /* backup and try again if possible */
664 49 100         curr = prev ? prev : next;
665 49           break;
666             case PRUNE_NEXT:
667             /* discard next node */
668 78           JsDiscardNode(next);
669             /* stay on current node, and try again */
670 78           break;
671             default:
672             /* move ahead to next node */
673 234           curr = next;
674 234           break;
675             }
676             }
677              
678             /* return the (possibly new) head node back to the caller */
679 19           return head;
680             }
681              
682             /* ****************************************************************************
683             * Minifies the given JavaScript, returning a newly allocated string back to
684             * the caller (YOU'RE responsible for freeing its memory).
685             * ****************************************************************************
686             */
687 20           char* JsMinify(const char* string) {
688             char* results;
689             /* PASS 1: tokenize JS into a list of nodes */
690 20           Node* head = JsTokenizeString(string);
691 20 100         if (!head) return NULL;
692             /* PASS 2: collapse nodes */
693 19           JsCollapseNodes(head);
694             /* PASS 3: prune nodes */
695 19           head = JsPruneNodes(head);
696 19 100         if (!head) return NULL;
697             /* PASS 4: re-assemble JS into single string */
698             {
699             Node* curr;
700             char* ptr;
701             /* allocate the result buffer to the same size as the original JS; in a
702             * worst case scenario that's how much memory we'll need for it.
703             */
704 17           Newz(0, results, (strlen(string)+1), char);
705 17           ptr = results;
706             /* copy node contents into result buffer */
707 17           curr = head;
708 231 100         while (curr) {
709 214           memcpy(ptr, curr->contents, curr->length);
710 214           ptr += curr->length;
711 214           curr = curr->next;
712             }
713 17           *ptr = 0;
714             }
715             /* free memory used by node list */
716 17           JsFreeNodeList(head);
717             /* return resulting minified JS back to caller */
718 17           return results;
719             }
720              
721              
722              
723             MODULE = JavaScript::Minifier::XS PACKAGE = JavaScript::Minifier::XS
724              
725             PROTOTYPES: disable
726              
727             SV*
728             minify(string)
729             SV* string
730             INIT:
731 20           char* buffer = NULL;
732 20           RETVAL = &PL_sv_undef;
733             CODE:
734             /* minify the JavaScript */
735 20           buffer = JsMinify( SvPVX(string) );
736             /* hand back the minified JS (if we had any) */
737 20 100         if (buffer != NULL) {
738 17           RETVAL = newSVpv(buffer, 0);
739 17           Safefree( buffer );
740             }
741             OUTPUT:
742             RETVAL