File Coverage

XS.xs
Criterion Covered Total %
statement 363 382 95.0
branch 344 438 78.5
condition n/a
subroutine n/a
pod n/a
total 707 820 86.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 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 0           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 450           Node* JsAllocNode(JsDoc* doc) {
215             Node* node;
216 450           NodeSet* set = doc->tail_set;
217              
218             /* if our current NodeSet is full, allocate a new NodeSet */
219 450 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 450           node = set->nodes + set->next_node;
229 450           set->next_node ++;
230              
231             /* initialize the node */
232 450           node->prev = NULL;
233 450           node->next = NULL;
234 450           node->contents = NULL;
235 450           node->length = 0;
236 450           node->type = NODE_EMPTY;
237 450           return node;
238             }
239              
240             /* clears the contents of a node */
241 898           void JsClearNodeContents(Node* node) {
242 898 100         if (node->contents)
243 449           Safefree(node->contents);
244 898           node->contents = NULL;
245 898           node->length = 0;
246 898           }
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 21           void _JsExtractLiteral(JsDoc* doc, Node* node) {
312 21           const char* buf = doc->buffer;
313 21           size_t offset = doc->offset;
314 21           char delimiter = buf[offset];
315 21           bool in_char_class = 0;
316             /* skip start of literal */
317 21           offset ++;
318             /* search for end of literal */
319 332 100         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 1           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 36           Node* JsTokenizeString(JsDoc* doc, const char* string) {
426             /* parse the JS */
427 485 100         while ((doc->offset < doc->length) && (doc->buffer[doc->offset])) {
    50          
428             /* allocate a new node */
429 450           Node* node = JsAllocNode(doc);
430 450 100         if (!doc->head)
431 35           doc->head = node;
432 450 100         if (!doc->tail)
433 35           doc->tail = node;
434              
435             /* parse the next node out of the JS */
436 450 100         if (doc->buffer[doc->offset] == '/') {
437 35 100         if (doc->buffer[doc->offset+1] == '*')
438 15           _JsExtractBlockComment(doc, node);
439 20 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 12           Node* last = doc->tail;
446 12           char ch = 0;
447              
448             /* find last non-whitespace, non-comment node */
449 27 50         while (last && (nodeIsWHITESPACE(last) || nodeIsCOMMENT(last)))
    100          
    50          
    100          
450 15           last = last->prev;
451              
452 12 50         if (last && (last->length > 0))
    100          
453 11           ch = last->contents[last->length-1];
454              
455             /* see if we're "division" or "regexp" */
456 12 50         if (last && nodeIsIDENTIFIER(last) && nodeEquals(last, "return")) {
    100          
    100          
457             /* returning a regexp from a function */
458 1           _JsExtractLiteral(doc, node);
459             }
460 11 100         else if (ch && ((ch == ')') || (ch == '.') || (ch == ']') || (charIsIdentifier(ch)))) {
    50          
    50          
    100          
    100          
461             /* looks like an identifier; guess its division */
462 2           _JsExtractSigil(doc, node);
463             }
464             else {
465             /* presume its a regexp */
466 9           _JsExtractLiteral(doc, node);
467             }
468             }
469             }
470 415 100         else if ((doc->buffer[doc->offset] == '"') || (doc->buffer[doc->offset] == '\'') || (doc->buffer[doc->offset] == '`'))
    100          
    100          
471 11           _JsExtractLiteral(doc, node);
472 404 100         else if (charIsWhitespace(doc->buffer[doc->offset]))
473 160           _JsExtractWhitespace(doc, node);
474 244 100         else if (charIsIdentifier(doc->buffer[doc->offset]))
475 111           _JsExtractIdentifier(doc, node);
476             else
477 133           _JsExtractSigil(doc, node);
478              
479             /* move ahead to the end of the parsed node */
480 449           doc->offset += node->length;
481              
482             /* add the node to our list of nodes */
483 449 100         if (node != doc->tail)
484 415           JsAppendNode(doc->tail, node);
485 449           doc->tail = node;
486              
487             /* some debugging info */
488             #ifdef DEBUG
489             {
490             size_t idx;
491             printf("----------------------------------------------------------------\n");
492             printf("%s: [%s]\n", strNodeTypes[node->type], node->contents);
493             printf("next: [");
494             for (idx=0; idx<=10; idx++) {
495             if ((doc->offset+idx) >= doc->length) break;
496             if (!doc->buffer[doc->offset+idx]) break;
497             printf("%c", doc->buffer[doc->offset+idx]);
498             }
499             printf("]\n");
500             }
501             #endif
502             }
503              
504             /* return the node list */
505 35           return doc->head;
506             }
507              
508             /* ****************************************************************************
509             * MINIFICATION FUNCTIONS
510             * ****************************************************************************
511             */
512              
513             /* collapses all of the nodes to their shortest possible representation */
514 34           void JsCollapseNodes(Node* curr) {
515 483 100         while (curr) {
516 449           Node* next = curr->next;
517 449           switch (curr->type) {
518 160           case NODE_WHITESPACE:
519             /* all WS gets collapsed */
520 160           JsCollapseNodeToWhitespace(curr);
521 160           break;
522 15           case NODE_BLOCKCOMMENT:
523             /* IE Conditional Compilation comments do not get collapsed */
524 15 50         if (nodeIsIECONDITIONALBLOCKCOMMENT(curr)) {
    100          
    100          
525 1           break;
526             }
527             /* block comments get collapsed to WS if that's a side-affect
528             * of their placement in the JS document.
529             */
530             {
531 14           bool convert_to_ws = 0;
532             /* find surrounding non-WS nodes */
533 14           Node* nonws_prev = curr->prev;
534 14           Node* nonws_next = curr->next;
535 25 100         while (nonws_prev && nodeIsWHITESPACE(nonws_prev))
    100          
536 11           nonws_prev = nonws_prev->prev;
537 25 100         while (nonws_next && nodeIsWHITESPACE(nonws_next))
    100          
538 11           nonws_next = nonws_next->next;
539             /* check what we're between... */
540 14 100         if (nonws_prev && nonws_next) {
    100          
541             /* between identifiers? convert to WS */
542 9 100         if (nodeIsIDENTIFIER(nonws_prev) && nodeIsIDENTIFIER(nonws_next))
    50          
543 0           convert_to_ws = 1;
544             /* between possible pre/post increment? convert to WS */
545 9 100         if (nodeIsCHAR(nonws_prev,'-') && nodeIsCHAR(nonws_next,'-'))
    50          
    100          
    50          
546 1           convert_to_ws = 1;
547 9 100         if (nodeIsCHAR(nonws_prev,'+') && nodeIsCHAR(nonws_next,'+'))
    50          
    100          
    50          
548 1           convert_to_ws = 1;
549             }
550             /* convert to WS */
551 14 100         if (convert_to_ws) {
552 2           JsSetNodeContents(curr," ",1);
553 2           curr->type = NODE_WHITESPACE;
554             }
555             }
556 14           break;
557 274           default:
558 274           break;
559             }
560 449           curr = next;
561             }
562 34           }
563              
564             /* checks to see whether we can prune the given node from the list.
565             *
566             * THIS is the function that controls the bulk of the minification process.
567             */
568             enum {
569             PRUNE_NO,
570             PRUNE_PREVIOUS,
571             PRUNE_CURRENT,
572             PRUNE_NEXT
573             };
574 474           int JsCanPrune(Node* node) {
575 474           Node* prev = node->prev;
576 474           Node* next = node->next;
577              
578 474           switch (node->type) {
579 0           case NODE_EMPTY:
580             /* prune empty nodes */
581 0           return PRUNE_CURRENT;
582 69           case NODE_WHITESPACE:
583             /* multiple whitespace gets pruned to preserve endspace */
584 69 100         if (prev && nodeIsENDSPACE(prev))
    100          
    50          
585 2           return PRUNE_CURRENT;
586 67 100         if (prev && nodeIsWHITESPACE(prev))
    50          
587 0           return PRUNE_PREVIOUS;
588             /* leading whitespace gets pruned */
589 67 100         if (!prev)
590 14           return PRUNE_CURRENT;
591             /* trailing whitespace gets pruned */
592 53 100         if (!next)
593 7           return PRUNE_CURRENT;
594             /* keep all other whitespace */
595 46           return PRUNE_NO;
596 14           case NODE_BLOCKCOMMENT:
597             /* keep comments that contain the word "copyright" */
598 14 100         if (nodeContains(node, "copyright"))
599 1           return PRUNE_NO;
600             /* keep comments that are for IE Conditional Compilation */
601 13 50         if (nodeIsIECONDITIONALBLOCKCOMMENT(node))
    100          
    100          
602 2           return PRUNE_NO;
603             /* block comments get pruned */
604 11           return PRUNE_CURRENT;
605 8           case NODE_LINECOMMENT:
606             /* keep comments that contain the word "copyright" */
607 8 100         if (nodeContains(node, "copyright"))
608 2           return PRUNE_NO;
609             /* keep comments that are for IE Conditional Compilation */
610 6 50         if (nodeIsIECONDITIONALLINECOMMENT(node))
    50          
611 0           return PRUNE_NO;
612             /* line comments get pruned */
613 6           return PRUNE_CURRENT;
614 139           case NODE_IDENTIFIER:
615             /* remove whitespace (but NOT endspace) after identifiers, provided
616             * that next thing is -NOT- another identifier
617             */
618 139 100         if (next && nodeIsWHITESPACE(next) && !nodeIsENDSPACE(next) && next->next && !nodeIsIDENTIFIER(next->next))
    100          
    50          
    100          
    50          
    100          
619 27           return PRUNE_NEXT;
620             /* keep all identifiers */
621 112           return PRUNE_NO;
622 20           case NODE_LITERAL:
623             /* keep all literals */
624 20           return PRUNE_NO;
625 224           case NODE_SIGIL:
626             /* remove whitespace after "prefix" sigils */
627 224 50         if (nodeIsPREFIXSIGIL(node) && next && nodeIsWHITESPACE(next))
    100          
    100          
    100          
628 56           return PRUNE_NEXT;
629             /* remove whitespace before "postfix" sigils */
630 168 50         if (nodeIsPOSTFIXSIGIL(node) && prev && nodeIsWHITESPACE(prev) && prev->prev && !nodeIsLINECOMMENT(prev->prev))
    100          
    100          
    100          
    50          
    100          
631 3           return PRUNE_PREVIOUS;
632             /* remove whitespace (but NOT endspace) after closing brackets */
633 165 100         if (next && nodeIsWHITESPACE(next) && !nodeIsENDSPACE(next) && (nodeIsCHAR(node,')') || nodeIsCHAR(node,'}') || nodeIsCHAR(node,']')))
    100          
    50          
    100          
    100          
    50          
    50          
    0          
    50          
    0          
634 5           return PRUNE_NEXT;
635             /* remove whitespace surrounding "/", EXCEPT where it'd cause "//" */
636 160 100         if (nodeIsCHAR(node,'/') && prev && nodeIsWHITESPACE(prev) && prev->prev && !nodeEndsWith(prev->prev,"/"))
    50          
    50          
    50          
    0          
    0          
637 0           return PRUNE_PREVIOUS;
638 160 100         if (nodeIsCHAR(node,'/') && next && nodeIsWHITESPACE(next) && next->next && !nodeBeginsWith(next->next,"/"))
    50          
    50          
    100          
    50          
    50          
639 1           return PRUNE_NEXT;
640             /* remove whitespace (but NOT endspace) surrounding "-", EXCEPT where it'd cause "--" */
641 159 100         if (nodeIsCHAR(node,'-') && prev && nodeIsWHITESPACE(prev) && !nodeIsENDSPACE(prev) && prev->prev && !nodeIsCHAR(prev->prev,'-'))
    50          
    50          
    100          
    50          
    50          
    50          
    50          
    50          
642 0           return PRUNE_PREVIOUS;
643 159 100         if (nodeIsCHAR(node,'-') && next && nodeIsWHITESPACE(next) && !nodeIsENDSPACE(next) && next->next && !nodeIsCHAR(next->next,'-'))
    50          
    50          
    100          
    50          
    50          
    50          
    100          
    50          
644 4           return PRUNE_NEXT;
645             /* remove whitespace (but NOT endspace) surrounding "+", EXCEPT where it'd cause "++" */
646 155 100         if (nodeIsCHAR(node,'+') && prev && nodeIsWHITESPACE(prev) && !nodeIsENDSPACE(prev) && prev->prev && !nodeIsCHAR(prev->prev,'+'))
    50          
    50          
    100          
    50          
    50          
    50          
    50          
    50          
647 0           return PRUNE_PREVIOUS;
648 155 100         if (nodeIsCHAR(node,'+') && next && nodeIsWHITESPACE(next) && !nodeIsENDSPACE(next) && next->next && !nodeIsCHAR(next->next,'+'))
    50          
    50          
    100          
    50          
    50          
    50          
    100          
    50          
649 4           return PRUNE_NEXT;
650             /* keep all other sigils */
651 151           return PRUNE_NO;
652             }
653             /* keep anything else */
654 0           return PRUNE_NO;
655             }
656              
657             /* prune nodes from the list */
658 34           Node* JsPruneNodes(Node *head) {
659 34           Node* curr = head;
660 508 100         while (curr) {
661             /* see if/howe we can prune this node */
662 474           int prune = JsCanPrune(curr);
663             /* prune. each block is responsible for moving onto the next node */
664 474           Node* prev = curr->prev;
665 474           Node* next = curr->next;
666 474           switch (prune) {
667 3           case PRUNE_PREVIOUS:
668             /* discard previous node */
669 3           JsDiscardNode(prev);
670             /* reset "head" if that's what got pruned */
671 3 50         if (prev == head)
672 0           prev = curr;
673 3           break;
674 40           case PRUNE_CURRENT:
675             /* discard current node */
676 40           JsDiscardNode(curr);
677             /* reset "head" if that's what got pruned */
678 40 100         if (curr == head)
679 18 50         head = prev ? prev : next;
680             /* backup and try again if possible */
681 40 100         curr = prev ? prev : next;
682 40           break;
683 97           case PRUNE_NEXT:
684             /* discard next node */
685 97           JsDiscardNode(next);
686             /* stay on current node, and try again */
687 97           break;
688 334           default:
689             /* move ahead to next node */
690 334           curr = next;
691 334           break;
692             }
693             }
694              
695             /* return the (possibly new) head node back to the caller */
696 34           return head;
697             }
698              
699             /* ****************************************************************************
700             * Minifies the given JavaScript, returning a newly allocated string back to
701             * the caller (YOU'RE responsible for freeing its memory).
702             * ****************************************************************************
703             */
704 36           char* JsMinify(const char* string) {
705 36           char* results = NULL;
706             JsDoc doc;
707              
708             /* initialize our JS document object */
709 36           doc.head = NULL;
710 36           doc.tail = NULL;
711 36           doc.buffer = string;
712 36           doc.length = strlen(string);
713 36           doc.offset = 0;
714 36           Newz(0, doc.head_set, 1, NodeSet);
715 36           doc.tail_set = doc.head_set;
716              
717             /* PASS 1: tokenize JS into a list of nodes */
718 36           Node* head = JsTokenizeString(&doc, string);
719 35 100         if (!head) goto cleanup;
720             /* PASS 2: collapse nodes */
721 34           JsCollapseNodes(head);
722             /* PASS 3: prune nodes */
723 34           head = JsPruneNodes(head);
724 34 100         if (!head) goto cleanup;
725             /* PASS 4: re-assemble JS into single string */
726             {
727             Node* curr;
728             char* ptr;
729             /* allocate the result buffer to the same size as the original JS; in a
730             * worst case scenario that's how much memory we'll need for it.
731             */
732 31           Newz(0, results, (strlen(string)+1), char);
733 31           ptr = results;
734             /* copy node contents into result buffer */
735 31           curr = head;
736 340 100         while (curr) {
737 309           memcpy(ptr, curr->contents, curr->length);
738 309           ptr += curr->length;
739 309           curr = curr->next;
740             }
741 31           *ptr = 0;
742             }
743             /* free memory used by the NodeSets */
744 35           cleanup:
745             {
746 35           NodeSet* curr = doc.head_set;
747 70 100         while (curr) {
748 35           NodeSet* next = curr->next;
749             /* free each node's contents buffer before freeing the set */
750             size_t idx;
751 484 100         for (idx=0; idx < curr->next_node; idx++)
752 449           JsClearNodeContents( &curr->nodes[idx] );
753             /* free the set, now that it's empty */
754 35           Safefree(curr);
755 35           curr = next;
756             }
757             }
758             /* return resulting minified JS back to caller */
759 35           return results;
760             }
761              
762              
763              
764             MODULE = JavaScript::Minifier::XS PACKAGE = JavaScript::Minifier::XS
765              
766             PROTOTYPES: disable
767              
768             SV*
769             minify(string)
770             SV* string
771             INIT:
772 36           char* buffer = NULL;
773 36           RETVAL = &PL_sv_undef;
774             CODE:
775             /* minify the JavaScript */
776 36           buffer = JsMinify( SvPVX(string) );
777             /* hand back the minified JS (if we had any) */
778 35 100         if (buffer != NULL) {
779 31           RETVAL = newSVpv(buffer, 0);
780 31           Safefree( buffer );
781             }
782             OUTPUT:
783             RETVAL