Index: trunk/phase3/includes/libs/JSMin.php |
— | — | @@ -69,7 +69,7 @@ |
70 | 70 | // -- Public Static Methods -------------------------------------------------- |
71 | 71 | |
72 | 72 | public static function minify( $js ) { |
73 | | - $jsmin = new JSMin( $js ); |
| 73 | + $jsmin = new self( $js ); |
74 | 74 | $ret = $jsmin->min(); |
75 | 75 | return $ret; |
76 | 76 | } |
— | — | @@ -77,7 +77,10 @@ |
78 | 78 | // -- Public Instance Methods ------------------------------------------------ |
79 | 79 | |
80 | 80 | public function __construct( $input ) { |
| 81 | + // Fix line endings |
81 | 82 | $this->input = str_replace( "\r\n", "\n", $input ); |
| 83 | + // Replace tabs and other control characters (except LF) with spaces |
| 84 | + $this->input = preg_replace( '/[\x00-\x09\x0b-\x1f]/', ' ', $this->input ); |
82 | 85 | $this->inputLength = strlen( $this->input ); |
83 | 86 | } |
84 | 87 | |
— | — | @@ -94,35 +97,35 @@ |
95 | 98 | protected function action( $d ) { |
96 | 99 | switch( $d ) { |
97 | 100 | case self::OUTPUT: |
98 | | - // Output A. Copy B to A. Get the next B. |
99 | 101 | $this->output .= $this->a; |
100 | 102 | |
101 | 103 | case self::DELETE_A: |
102 | | - // Copy B to A. Get the next B. (Delete A). |
103 | 104 | $this->a = $this->b; |
104 | 105 | |
105 | 106 | if ( $this->a === "'" || $this->a === '"' ) { |
| 107 | + $interestingChars = $this->a . "\\\n"; |
| 108 | + $this->output .= $this->a; |
106 | 109 | for ( ; ; ) { |
107 | | - $this->output .= $this->a; |
108 | | - $this->a = $this->get(); |
| 110 | + $runLength = strcspn( $this->input, $interestingChars, $this->inputIndex ); |
| 111 | + $this->output .= substr( $this->input, $this->inputIndex, $runLength ); |
| 112 | + $this->inputIndex += $runLength; |
| 113 | + $this->a = $this->get(); |
109 | 114 | |
110 | 115 | if ( $this->a === $this->b ) { |
111 | 116 | break; |
112 | 117 | } |
113 | 118 | |
114 | | - if ( ord( $this->a ) <= self::ORD_LF ) { |
| 119 | + if ( $this->a === "\n" || $this->a === null ) { |
115 | 120 | throw new JSMinException( 'Unterminated string literal.' ); |
116 | 121 | } |
117 | 122 | |
118 | 123 | if ( $this->a === '\\' ) { |
119 | | - $this->output .= $this->a; |
120 | | - $this->a = $this->get(); |
| 124 | + $this->output .= $this->a . $this->get(); |
121 | 125 | } |
122 | 126 | } |
123 | 127 | } |
124 | 128 | |
125 | 129 | case self::DELETE_B: |
126 | | - // Get the next B. (Delete B). |
127 | 130 | $this->b = $this->next(); |
128 | 131 | |
129 | 132 | if ( $this->b === '/' && ( |
— | — | @@ -133,6 +136,9 @@ |
134 | 137 | $this->output .= $this->a . $this->b; |
135 | 138 | |
136 | 139 | for ( ; ; ) { |
| 140 | + $runLength = strcspn( $this->input, "/\\\n", $this->inputIndex ); |
| 141 | + $this->output .= substr( $this->input, $this->inputIndex, $runLength ); |
| 142 | + $this->inputIndex += $runLength; |
137 | 143 | $this->a = $this->get(); |
138 | 144 | |
139 | 145 | if ( $this->a === '/' ) { |
— | — | @@ -140,7 +146,7 @@ |
141 | 147 | } elseif ( $this->a === '\\' ) { |
142 | 148 | $this->output .= $this->a; |
143 | 149 | $this->a = $this->get(); |
144 | | - } elseif ( ord( $this->a ) <= self::ORD_LF ) { |
| 150 | + } elseif ( $this->a === "\n" || $this->a === null ) { |
145 | 151 | throw new JSMinException( 'Unterminated regular expression ' . |
146 | 152 | 'literal.' ); |
147 | 153 | } |
— | — | @@ -159,27 +165,11 @@ |
160 | 166 | * linefeed. |
161 | 167 | */ |
162 | 168 | protected function get() { |
163 | | - $c = $this->lookAhead; |
164 | | - $this->lookAhead = null; |
165 | | - |
166 | | - if ( $c === null ) { |
167 | | - if ( $this->inputIndex < $this->inputLength ) { |
168 | | - $c = substr( $this->input, $this->inputIndex, 1 ); |
169 | | - $this->inputIndex += 1; |
170 | | - } else { |
171 | | - $c = null; |
172 | | - } |
| 169 | + if ( $this->inputIndex < $this->inputLength ) { |
| 170 | + return $this->input[$this->inputIndex++]; |
| 171 | + } else { |
| 172 | + return null; |
173 | 173 | } |
174 | | - |
175 | | - if ( $c === "\r" ) { |
176 | | - return "\n"; |
177 | | - } |
178 | | - |
179 | | - if ( $c === null || $c === "\n" || ord( $c ) >= self::ORD_SPACE ) { |
180 | | - return $c; |
181 | | - } |
182 | | - |
183 | | - return ' '; |
184 | 174 | } |
185 | 175 | |
186 | 176 | /** |
— | — | @@ -212,25 +202,12 @@ |
213 | 203 | |
214 | 204 | case "\n": |
215 | 205 | switch ( $this->b ) { |
216 | | - case '{': |
217 | | - case '[': |
218 | | - case '(': |
219 | | - case '+': |
220 | | - case '-': |
221 | | - $this->action( self::OUTPUT ); |
222 | | - break; |
223 | | - |
224 | 206 | case ' ': |
225 | 207 | $this->action( self::DELETE_B ); |
226 | 208 | break; |
227 | 209 | |
228 | 210 | default: |
229 | | - if ( $this->isAlphaNum( $this->b ) ) { |
230 | | - $this->action( self::OUTPUT ); |
231 | | - } |
232 | | - else { |
233 | | - $this->action( self::DELETE_A ); |
234 | | - } |
| 211 | + $this->action( self::OUTPUT ); |
235 | 212 | } |
236 | 213 | break; |
237 | 214 | |
— | — | @@ -244,29 +221,6 @@ |
245 | 222 | |
246 | 223 | $this->action( self::DELETE_B ); |
247 | 224 | break; |
248 | | - |
249 | | - case "\n": |
250 | | - switch ( $this->a ) { |
251 | | - case '}': |
252 | | - case ']': |
253 | | - case ')': |
254 | | - case '+': |
255 | | - case '-': |
256 | | - case '"': |
257 | | - case "'": |
258 | | - $this->action( self::OUTPUT ); |
259 | | - break; |
260 | | - |
261 | | - default: |
262 | | - if ( $this->isAlphaNum( $this->a ) ) { |
263 | | - $this->action( self::OUTPUT ); |
264 | | - } |
265 | | - else { |
266 | | - $this->action( self::DELETE_B ); |
267 | | - } |
268 | | - } |
269 | | - break; |
270 | | - |
271 | 225 | default: |
272 | 226 | $this->action( self::OUTPUT ); |
273 | 227 | break; |
— | — | @@ -274,44 +228,48 @@ |
275 | 229 | } |
276 | 230 | } |
277 | 231 | |
278 | | - return $this->output; |
| 232 | + // Remove initial line break |
| 233 | + if ( $this->output[0] !== "\n" ) { |
| 234 | + throw new JSMinException( 'Unexpected lack of line break.' ); |
| 235 | + } |
| 236 | + if ( $this->output === "\n" ) { |
| 237 | + return ''; |
| 238 | + } else { |
| 239 | + return substr( $this->output, 1 ); |
| 240 | + } |
279 | 241 | } |
280 | 242 | |
281 | 243 | /** |
282 | | - * Get the next character, excluding comments. peek() is used to see |
283 | | - * if a '/' is followed by a '/' or '*'. |
| 244 | + * Get the next character, excluding comments. |
284 | 245 | */ |
285 | 246 | protected function next() { |
286 | | - $c = $this->get(); |
| 247 | + if ( $this->inputIndex >= $this->inputLength ) { |
| 248 | + return null; |
| 249 | + } |
| 250 | + $c = $this->input[$this->inputIndex++]; |
287 | 251 | |
| 252 | + if ( $this->inputIndex >= $this->inputLength ) { |
| 253 | + return $c; |
| 254 | + } |
| 255 | + |
288 | 256 | if ( $c === '/' ) { |
289 | | - switch( $this->peek() ) { |
| 257 | + switch( $this->input[$this->inputIndex] ) { |
290 | 258 | case '/': |
291 | | - for ( ; ; ) { |
292 | | - $c = $this->get(); |
293 | | - |
294 | | - if ( ord( $c ) <= self::ORD_LF ) { |
295 | | - return $c; |
296 | | - } |
297 | | - } |
298 | | - |
| 259 | + $this->inputIndex += strcspn( $this->input, "\n", $this->inputIndex ) + 1; |
| 260 | + return "\n"; |
299 | 261 | case '*': |
300 | | - $this->get(); |
301 | | - |
302 | | - for ( ; ; ) { |
303 | | - switch( $this->get() ) { |
304 | | - case '*': |
305 | | - if ( $this->peek() === '/' ) { |
306 | | - $this->get(); |
307 | | - return ' '; |
308 | | - } |
309 | | - break; |
310 | | - |
311 | | - case null: |
312 | | - throw new JSMinException( 'Unterminated comment.' ); |
313 | | - } |
| 262 | + $endPos = strpos( $this->input, '*/', $this->inputIndex + 1 ); |
| 263 | + if ( $endPos === false ) { |
| 264 | + throw new JSMinException( 'Unterminated comment.' ); |
314 | 265 | } |
315 | | - |
| 266 | + $numLines = substr_count( $this->input, "\n", $this->inputIndex, |
| 267 | + $endPos - $this->inputIndex ); |
| 268 | + $this->inputIndex = $endPos + 2; |
| 269 | + if ( $numLines ) { |
| 270 | + return str_repeat( "\n", $numLines ); |
| 271 | + } else { |
| 272 | + return ' '; |
| 273 | + } |
316 | 274 | default: |
317 | 275 | return $c; |
318 | 276 | } |
— | — | @@ -319,14 +277,6 @@ |
320 | 278 | |
321 | 279 | return $c; |
322 | 280 | } |
323 | | - |
324 | | - /** |
325 | | - * Get the next character without getting it. |
326 | | - */ |
327 | | - protected function peek() { |
328 | | - $this->lookAhead = $this->get(); |
329 | | - return $this->lookAhead; |
330 | | - } |
331 | 281 | } |
332 | 282 | |
333 | 283 | // -- Exceptions --------------------------------------------------------------- |