File Coverage

XS.xs
Criterion Covered Total %
statement 336 355 94.6
branch 325 420 77.3
condition n/a
subroutine n/a
pod n/a
total 661 775 85.2


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