File Coverage

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