GlobalFunctions.java revision 1036:f0b5e3900a10
1/*
2 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package jdk.nashorn.internal.runtime;
27
28import static jdk.nashorn.internal.lookup.Lookup.MH;
29
30import java.lang.invoke.MethodHandle;
31import java.lang.invoke.MethodHandles;
32import java.util.Locale;
33
34/**
35 * Utilities used by Global class.
36 */
37public final class GlobalFunctions {
38
39    /** Methodhandle to implementation of ECMA 15.1.2.2, parseInt */
40    public static final MethodHandle PARSEINT = findOwnMH("parseInt", double.class, Object.class, Object.class, Object.class);
41
42    /** Methodhandle (specialized) to implementation of ECMA 15.1.2.2, parseInt */
43    public static final MethodHandle PARSEINT_OI = findOwnMH("parseInt", double.class, Object.class, Object.class, int.class);
44
45    /** ParseInt - NaN for booleans (thru string conversion to number conversion) */
46    public static final MethodHandle PARSEINT_Z = MH.dropArguments(MH.dropArguments(MH.constant(double.class, Double.NaN), 0, boolean.class), 0, Object.class);
47
48    /** ParseInt - identity for ints */
49    public static final MethodHandle PARSEINT_I = MH.dropArguments(MH.identity(int.class), 0, Object.class);
50
51    /** ParseInt - identity for longs */
52    public static final MethodHandle PARSEINT_J = MH.dropArguments(MH.identity(long.class), 0, Object.class);
53
54    /** Methodhandle (specialized) to implementation of ECMA 15.1.2.2, parseInt */
55    public static final MethodHandle PARSEINT_O = findOwnMH("parseInt", double.class, Object.class, Object.class);
56
57    /** Methodhandle to implementation of ECMA 15.1.2.3, parseFloat */
58    public static final MethodHandle PARSEFLOAT = findOwnMH("parseFloat", double.class, Object.class, Object.class);
59
60    /** isNan for integers - always false */
61    public static final MethodHandle IS_NAN_I = MH.dropArguments(MH.constant(boolean.class, false), 0, Object.class);
62
63    /** isNan for longs - always false */
64    public static final MethodHandle IS_NAN_J = MH.dropArguments(MH.constant(boolean.class, false), 0, Object.class);
65
66    /** IsNan for doubles - use Double.isNaN */
67    public static final MethodHandle IS_NAN_D = MH.dropArguments(MH.findStatic(MethodHandles.lookup(), Double.class, "isNaN", MH.type(boolean.class, double.class)), 0, Object.class);
68
69    /** Methodhandle to implementation of ECMA 15.1.2.4, isNaN */
70    public static final MethodHandle IS_NAN = findOwnMH("isNaN",      boolean.class, Object.class, Object.class);
71
72    /** Methodhandle to implementation of ECMA 15.1.2.5, isFinite */
73    public static final MethodHandle IS_FINITE = findOwnMH("isFinite",   boolean.class, Object.class, Object.class);
74
75    /** Methodhandle to implementation of ECMA 15.1.3.3, encodeURI */
76    public static final MethodHandle ENCODE_URI = findOwnMH("encodeURI",  Object.class, Object.class, Object.class);
77
78    /** Methodhandle to implementation of ECMA 15.1.3.4, encodeURIComponent */
79    public static final MethodHandle ENCODE_URICOMPONENT = findOwnMH("encodeURIComponent", Object.class, Object.class, Object.class);
80
81    /** Methodhandle to implementation of ECMA 15.1.3.1, decodeURI */
82    public static final MethodHandle DECODE_URI = findOwnMH("decodeURI", Object.class, Object.class, Object.class);
83
84    /** Methodhandle to implementation of ECMA 15.1.3.2, decodeURIComponent */
85    public static final MethodHandle DECODE_URICOMPONENT = findOwnMH("decodeURIComponent", Object.class, Object.class, Object.class);
86
87    /** Methodhandle to implementation of ECMA B.2.1, escape */
88    public static final MethodHandle ESCAPE = findOwnMH("escape",    String.class, Object.class, Object.class);
89
90    /** Methodhandle to implementation of ECMA B.2.2, unescape */
91    public static final MethodHandle UNESCAPE = findOwnMH("unescape",  String.class, Object.class, Object.class);
92
93    /** Methodhandle to implementation of ECMA 15.3.4, "anonymous" - Properties of the Function Prototype Object. */
94    public static final MethodHandle ANONYMOUS = findOwnMH("anonymous", Object.class, Object.class);
95
96    private static final String UNESCAPED = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@*_+-./";
97
98    private GlobalFunctions() {
99    }
100
101    /**
102     * ECMA 15.1.2.2 parseInt implementation
103     *
104     * @param self   self reference
105     * @param string string to parse
106     * @param rad    radix
107     *
108     * @return numeric type representing string contents as an int
109     */
110    public static double parseInt(final Object self, final Object string, final Object rad) {
111        return parseIntInternal(JSType.trimLeft(JSType.toString(string)), JSType.toInt32(rad));
112    }
113
114    /**
115     * ECMA 15.1.2.2 parseInt implementation specialized for int radix
116     *
117     * @param self   self reference
118     * @param string string to parse
119     * @param rad    radix
120     *
121     * @return numeric type representing string contents as an int
122     */
123    public static double parseInt(final Object self, final Object string, final int rad) {
124        return parseIntInternal(JSType.trimLeft(JSType.toString(string)), rad);
125    }
126
127    /**
128     * ECMA 15.1.2.2 parseInt implementation specialized for no radix argument
129     *
130     * @param self   self reference
131     * @param string string to parse
132     *
133     * @return numeric type representing string contents as an int
134     */
135    public static double parseInt(final Object self, final Object string) {
136        return parseIntInternal(JSType.trimLeft(JSType.toString(string)), 0);
137    }
138
139    private static double parseIntInternal(final String str, final int rad) {
140        final int length = str.length();
141        int radix = rad;
142
143        // empty string is not valid
144        if (length == 0) {
145            return Double.NaN;
146        }
147
148        boolean negative = false;
149        int idx = 0;
150
151        // checking for the sign character
152        final char firstChar = str.charAt(idx);
153        if (firstChar < '0') {
154            // Possible leading "+" or "-"
155            if (firstChar == '-') {
156                negative = true;
157            } else if (firstChar != '+') {
158                return Double.NaN;
159            }
160            // skip the sign character
161            idx++;
162        }
163
164        boolean stripPrefix = true;
165
166        if (radix != 0) {
167            if (radix < 2 || radix > 36) {
168                return Double.NaN;
169            }
170            if (radix != 16) {
171                stripPrefix = false;
172            }
173        } else {
174            // default radix
175            radix = 10;
176        }
177        // strip "0x" or "0X" and treat radix as 16
178        if (stripPrefix && ((idx + 1) < length)) {
179            final char c1 = str.charAt(idx);
180            final char c2 = str.charAt(idx + 1);
181            if (c1 == '0' && (c2 == 'x' || c2 == 'X')) {
182                radix = 16;
183                // skip "0x" or "0X"
184                idx += 2;
185            }
186        }
187
188        double result = 0.0;
189        int digit;
190        // we should see atleast one valid digit
191        boolean entered = false;
192        while (idx < length) {
193            digit = fastDigit(str.charAt(idx++), radix);
194            if (digit < 0) {
195                break;
196            }
197            // we have seen atleast one valid digit in the specified radix
198            entered = true;
199            result *= radix;
200            result += digit;
201        }
202
203        return entered ? (negative ? -result : result) : Double.NaN;
204    }
205
206    /**
207     * ECMA 15.1.2.3 parseFloat implementation
208     *
209     * @param self   self reference
210     * @param string string to parse
211     *
212     * @return numeric type representing string contents
213     */
214    public static double parseFloat(final Object self, final Object string) {
215        final String str    = JSType.trimLeft(JSType.toString(string));
216        final int    length = str.length();
217
218        // empty string is not valid
219        if (length == 0) {
220            return Double.NaN;
221        }
222
223        int     start    = 0;
224        boolean negative = false;
225        char    ch       = str.charAt(0);
226
227        if (ch == '-') {
228            start++;
229            negative = true;
230        } else if (ch == '+') {
231            start++;
232        } else if (ch == 'N') {
233            if (str.startsWith("NaN")) {
234                return Double.NaN;
235            }
236        }
237
238        if (start == length) {
239            // just the sign character
240            return Double.NaN;
241        }
242
243        ch = str.charAt(start);
244        if (ch == 'I') {
245            if (str.substring(start).startsWith("Infinity")) {
246                return negative? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY;
247            }
248        }
249
250        boolean dotSeen    = false;
251        boolean exponentOk = false;
252        int exponentOffset = -1;
253        int end;
254
255loop:
256        for (end = start; end < length; end++) {
257            ch = str.charAt(end);
258
259            switch (ch) {
260            case '.':
261                // dot allowed only once
262                if (exponentOffset != -1 || dotSeen) {
263                    break loop;
264                }
265                dotSeen = true;
266                break;
267
268            case 'e':
269            case 'E':
270                // 'e'/'E' allow only once
271                if (exponentOffset != -1) {
272                    break loop;
273                }
274                exponentOffset = end;
275                break;
276
277            case '+':
278            case '-':
279                // Sign of the exponent. But allowed only if the
280                // previous char in the string was 'e' or 'E'.
281                if (exponentOffset != end - 1) {
282                    break loop;
283                }
284                break;
285
286            case '0':
287            case '1':
288            case '2':
289            case '3':
290            case '4':
291            case '5':
292            case '6':
293            case '7':
294            case '8':
295            case '9':
296                if (exponentOffset != -1) {
297                    // seeing digit after 'e' or 'E'
298                    exponentOk = true;
299                }
300                break;
301
302            default: // ignore garbage at the end
303                break loop;
304            }
305        }
306
307        // ignore 'e'/'E' followed by '+/-' if not real exponent found
308        if (exponentOffset != -1 && !exponentOk) {
309            end = exponentOffset;
310        }
311
312        if (start == end) {
313            return Double.NaN;
314        }
315
316        try {
317            final double result = Double.valueOf(str.substring(start, end));
318            return negative ? -result : result;
319        } catch (final NumberFormatException e) {
320            return Double.NaN;
321        }
322    }
323
324    /**
325     * ECMA 15.1.2.4, isNaN implementation
326     *
327     * @param self    self reference
328     * @param number  number to check
329     *
330     * @return true if number is NaN
331     */
332    public static boolean isNaN(final Object self, final Object number) {
333        return Double.isNaN(JSType.toNumber(number));
334    }
335
336    /**
337     * ECMA 15.1.2.5, isFinite implementation
338     *
339     * @param self   self reference
340     * @param number number to check
341     *
342     * @return true if number is infinite
343     */
344    public static boolean isFinite(final Object self, final Object number) {
345        final double value = JSType.toNumber(number);
346        return ! (Double.isInfinite(value) || Double.isNaN(value));
347    }
348
349
350    /**
351     * ECMA 15.1.3.3, encodeURI implementation
352     *
353     * @param self  self reference
354     * @param uri   URI to encode
355     *
356     * @return encoded URI
357     */
358    public static Object encodeURI(final Object self, final Object uri) {
359        return URIUtils.encodeURI(self, JSType.toString(uri));
360    }
361
362    /**
363     * ECMA 15.1.3.4, encodeURIComponent implementation
364     *
365     * @param self  self reference
366     * @param uri   URI component to encode
367     *
368     * @return encoded URIComponent
369     */
370    public static Object encodeURIComponent(final Object self, final Object uri) {
371        return URIUtils.encodeURIComponent(self, JSType.toString(uri));
372    }
373
374    /**
375     * ECMA 15.1.3.1, decodeURI implementation
376     *
377     * @param self  self reference
378     * @param uri   URI to decode
379     *
380     * @return decoded URI
381     */
382    public static Object decodeURI(final Object self, final Object uri) {
383        return URIUtils.decodeURI(self, JSType.toString(uri));
384    }
385
386    /**
387     * ECMA 15.1.3.2, decodeURIComponent implementation
388     *
389     * @param self  self reference
390     * @param uri   URI component to encode
391     *
392     * @return decoded URI
393     */
394    public static Object decodeURIComponent(final Object self, final Object uri) {
395        return URIUtils.decodeURIComponent(self, JSType.toString(uri));
396    }
397
398    /**
399     * ECMA B.2.1, escape implementation
400     *
401     * @param self    self reference
402     * @param string  string to escape
403     *
404     * @return escaped string
405     */
406    public static String escape(final Object self, final Object string) {
407        final String str = JSType.toString(string);
408        final int length = str.length();
409
410        if (length == 0) {
411            return str;
412        }
413
414        final StringBuilder sb = new StringBuilder();
415        for (int k = 0; k < length; k++) {
416            final char ch = str.charAt(k);
417            if (UNESCAPED.indexOf(ch) != -1) {
418                sb.append(ch);
419            } else if (ch < 256) {
420                sb.append('%');
421                if (ch < 16) {
422                    sb.append('0');
423                }
424                sb.append(Integer.toHexString(ch).toUpperCase(Locale.ENGLISH));
425            } else {
426                sb.append("%u");
427                if (ch < 4096) {
428                    sb.append('0');
429                }
430                sb.append(Integer.toHexString(ch).toUpperCase(Locale.ENGLISH));
431            }
432        }
433
434        return sb.toString();
435    }
436
437    /**
438     * ECMA B.2.2, unescape implementation
439     *
440     * @param self    self reference
441     * @param string  string to unescape
442     *
443     * @return unescaped string
444     */
445    public static String unescape(final Object self, final Object string) {
446        final String str    = JSType.toString(string);
447        final int    length = str.length();
448
449        if (length == 0) {
450            return str;
451        }
452
453        final StringBuilder sb = new StringBuilder();
454        for (int k = 0; k < length; k++) {
455            char ch = str.charAt(k);
456            if (ch != '%') {
457                sb.append(ch);
458            } else {
459                if (k < (length - 5)) {
460                   if (str.charAt(k + 1) == 'u') {
461                       try {
462                           ch = (char) Integer.parseInt(str.substring(k + 2, k + 6), 16);
463                           sb.append(ch);
464                           k += 5;
465                           continue;
466                       } catch (final NumberFormatException e) {
467                           //ignored
468                       }
469                   }
470                }
471
472                if (k < (length - 2)) {
473                    try {
474                        ch = (char) Integer.parseInt(str.substring(k + 1, k + 3), 16);
475                        sb.append(ch);
476                        k += 2;
477                        continue;
478                    } catch (final NumberFormatException e) {
479                        //ignored
480                    }
481                }
482
483                // everything fails
484                sb.append(ch);
485            }
486        }
487
488        return sb.toString();
489    }
490
491
492    /**
493     * ECMA 15.3.4 Properties of the Function Prototype Object.
494     * The Function prototype object is itself a Function object
495     * (its [[Class]] is "Function") that, when invoked, accepts
496     * any arguments and returns undefined. This method is used to
497     * implement that anonymous function.
498     *
499     * @param self  self reference
500     *
501     * @return undefined
502     */
503    public static Object anonymous(final Object self) {
504        return ScriptRuntime.UNDEFINED;
505    }
506
507    private static int fastDigit(final int ch, final int radix) {
508        int n = -1;
509        if (ch >= '0' && ch <= '9') {
510            n = ch - '0';
511        } else if (radix > 10) {
512            if (ch >= 'a' && ch <= 'z') {
513                n = ch - 'a' + 10;
514            } else if (ch >= 'A' && ch <= 'Z') {
515                n = ch - 'A' + 10;
516            }
517        }
518        return n < radix ? n : -1;
519    }
520
521    private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
522        return MH.findStatic(MethodHandles.lookup(), GlobalFunctions.class, name, MH.type(rtype, types));
523    }
524}
525