1/*
2 * Copyright (c) 2010, 2015, 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.ir;
27
28import java.util.Arrays;
29import java.util.Collections;
30import java.util.List;
31import jdk.nashorn.internal.codegen.types.Type;
32import jdk.nashorn.internal.ir.annotations.Immutable;
33import jdk.nashorn.internal.ir.visitor.NodeVisitor;
34import jdk.nashorn.internal.parser.TokenType;
35
36/**
37 * IR representation for a runtime call.
38 */
39@Immutable
40public class RuntimeNode extends Expression {
41    private static final long serialVersionUID = 1L;
42
43    /**
44     * Request enum used for meta-information about the runtime request
45     */
46    public enum Request {
47        /** An addition with at least one object */
48        ADD(TokenType.ADD, Type.OBJECT, 2, true),
49        /** Request to enter debugger */
50        DEBUGGER,
51        /** New operator */
52        NEW,
53        /** Typeof operator */
54        TYPEOF,
55        /** Reference error type */
56        REFERENCE_ERROR,
57        /** Delete operator */
58        DELETE(TokenType.DELETE, Type.BOOLEAN, 1),
59        /** Delete operator for slow scopes */
60        SLOW_DELETE(TokenType.DELETE, Type.BOOLEAN, 1, false),
61        /** Delete operator that always fails -- see Lower */
62        FAIL_DELETE(TokenType.DELETE, Type.BOOLEAN, 1, false),
63        /** === operator with at least one object */
64        EQ_STRICT(TokenType.EQ_STRICT, Type.BOOLEAN, 2, true),
65        /** == operator with at least one object */
66        EQ(TokenType.EQ, Type.BOOLEAN, 2, true),
67        /** {@literal >=} operator with at least one object */
68        GE(TokenType.GE, Type.BOOLEAN, 2, true),
69        /** {@literal >} operator with at least one object */
70        GT(TokenType.GT, Type.BOOLEAN, 2, true),
71        /** in operator */
72        IN(TokenType.IN, Type.BOOLEAN, 2),
73        /** instanceof operator */
74        INSTANCEOF(TokenType.INSTANCEOF, Type.BOOLEAN, 2),
75        /** {@literal <=} operator with at least one object */
76        LE(TokenType.LE, Type.BOOLEAN, 2, true),
77        /** {@literal <} operator with at least one object */
78        LT(TokenType.LT, Type.BOOLEAN, 2, true),
79        /** !== operator with at least one object */
80        NE_STRICT(TokenType.NE_STRICT, Type.BOOLEAN, 2, true),
81        /** != operator with at least one object */
82        NE(TokenType.NE, Type.BOOLEAN, 2, true),
83        /** is undefined */
84        IS_UNDEFINED(TokenType.EQ_STRICT, Type.BOOLEAN, 2),
85        /** is not undefined */
86        IS_NOT_UNDEFINED(TokenType.NE_STRICT, Type.BOOLEAN, 2),
87        /** Get template object from raw and cooked string arrays. */
88        GET_TEMPLATE_OBJECT(TokenType.TEMPLATE, Type.SCRIPT_OBJECT, 2);
89
90        /** token type */
91        private final TokenType tokenType;
92
93        /** return type for request */
94        private final Type returnType;
95
96        /** arity of request */
97        private final int arity;
98
99        /** Can the specializer turn this into something that works with 1 or more primitives? */
100        private final boolean canSpecialize;
101
102        private Request() {
103            this(TokenType.VOID, Type.OBJECT, 0);
104        }
105
106        private Request(final TokenType tokenType, final Type returnType, final int arity) {
107            this(tokenType, returnType, arity, false);
108        }
109
110        private Request(final TokenType tokenType, final Type returnType, final int arity, final boolean canSpecialize) {
111            this.tokenType     = tokenType;
112            this.returnType    = returnType;
113            this.arity         = arity;
114            this.canSpecialize = canSpecialize;
115        }
116
117        /**
118         * Can this request type be specialized?
119         *
120         * @return true if request can be specialized
121         */
122        public boolean canSpecialize() {
123            return canSpecialize;
124        }
125
126        /**
127         * Get arity
128         *
129         * @return the arity of the request
130         */
131        public int getArity() {
132            return arity;
133        }
134
135        /**
136         * Get the return type
137         *
138         * @return return type for request
139         */
140        public Type getReturnType() {
141            return returnType;
142        }
143
144        /**
145         * Get token type
146         *
147         * @return token type for request
148         */
149        public TokenType getTokenType() {
150            return tokenType;
151        }
152
153        /**
154         * Get the non-strict name for this request.
155         *
156         * @return the name without _STRICT suffix
157         */
158        public String nonStrictName() {
159            switch(this) {
160            case NE_STRICT:
161                return NE.name();
162            case EQ_STRICT:
163                return EQ.name();
164            default:
165                return name();
166            }
167        }
168
169        /**
170         * Derive a runtime node request type for a node
171         * @param node the node
172         * @return request type
173         */
174        public static Request requestFor(final Expression node) {
175            switch (node.tokenType()) {
176            case TYPEOF:
177                return Request.TYPEOF;
178            case IN:
179                return Request.IN;
180            case INSTANCEOF:
181                return Request.INSTANCEOF;
182            case EQ_STRICT:
183                return Request.EQ_STRICT;
184            case NE_STRICT:
185                return Request.NE_STRICT;
186            case EQ:
187                return Request.EQ;
188            case NE:
189                return Request.NE;
190            case LT:
191                return Request.LT;
192            case LE:
193                return Request.LE;
194            case GT:
195                return Request.GT;
196            case GE:
197                return Request.GE;
198            default:
199                assert false;
200                return null;
201            }
202        }
203
204        /**
205         * Is this an undefined check?
206         *
207         * @param request request
208         *
209         * @return true if undefined check
210         */
211        public static boolean isUndefinedCheck(final Request request) {
212            return request == IS_UNDEFINED || request == IS_NOT_UNDEFINED;
213        }
214
215        /**
216         * Is this an EQ or EQ_STRICT?
217         *
218         * @param request a request
219         *
220         * @return true if EQ or EQ_STRICT
221         */
222        public static boolean isEQ(final Request request) {
223            return request == EQ || request == EQ_STRICT;
224        }
225
226        /**
227         * Is this an NE or NE_STRICT?
228         *
229         * @param request a request
230         *
231         * @return true if NE or NE_STRICT
232         */
233        public static boolean isNE(final Request request) {
234            return request == NE || request == NE_STRICT;
235        }
236
237        /**
238         * Is this strict?
239         *
240         * @param request a request
241         *
242         * @return true if script
243         */
244        public static boolean isStrict(final Request request) {
245            return request == EQ_STRICT || request == NE_STRICT;
246        }
247
248        /**
249         * If this request can be reversed, return the reverse request
250         * Eq EQ {@literal ->} NE.
251         *
252         * @param request request to reverse
253         *
254         * @return reversed request or null if not applicable
255         */
256        public static Request reverse(final Request request) {
257            switch (request) {
258            case EQ:
259            case EQ_STRICT:
260            case NE:
261            case NE_STRICT:
262                return request;
263            case LE:
264                return GE;
265            case LT:
266                return GT;
267            case GE:
268                return LE;
269            case GT:
270                return LT;
271            default:
272                return null;
273            }
274        }
275
276        /**
277         * Invert the request, only for non equals comparisons.
278         *
279         * @param request a request
280         *
281         * @return the inverted request, or null if not applicable
282         */
283        public static Request invert(final Request request) {
284            switch (request) {
285            case EQ:
286                return NE;
287            case EQ_STRICT:
288                return NE_STRICT;
289            case NE:
290                return EQ;
291            case NE_STRICT:
292                return EQ_STRICT;
293            case LE:
294                return GT;
295            case LT:
296                return GE;
297            case GE:
298                return LT;
299            case GT:
300                return LE;
301            default:
302                return null;
303            }
304        }
305
306        /**
307         * Check if this is a comparison
308         *
309         * @param request a request
310         *
311         * @return true if this is a comparison, null otherwise
312         */
313        public static boolean isComparison(final Request request) {
314            switch (request) {
315            case EQ:
316            case EQ_STRICT:
317            case NE:
318            case NE_STRICT:
319            case LE:
320            case LT:
321            case GE:
322            case GT:
323            case IS_UNDEFINED:
324            case IS_NOT_UNDEFINED:
325                return true;
326            default:
327                return false;
328            }
329        }
330    }
331
332    /** Runtime request. */
333    private final Request request;
334
335    /** Call arguments. */
336    private final List<Expression> args;
337
338    /**
339     * Constructor
340     *
341     * @param token   token
342     * @param finish  finish
343     * @param request the request
344     * @param args    arguments to request
345     */
346    public RuntimeNode(final long token, final int finish, final Request request, final List<Expression> args) {
347        super(token, finish);
348
349        this.request      = request;
350        this.args         = args;
351    }
352
353    private RuntimeNode(final RuntimeNode runtimeNode, final Request request, final List<Expression> args) {
354        super(runtimeNode);
355
356        this.request      = request;
357        this.args         = args;
358    }
359
360    /**
361     * Constructor
362     *
363     * @param token   token
364     * @param finish  finish
365     * @param request the request
366     * @param args    arguments to request
367     */
368    public RuntimeNode(final long token, final int finish, final Request request, final Expression... args) {
369        this(token, finish, request, Arrays.asList(args));
370    }
371
372    /**
373     * Constructor
374     *
375     * @param parent  parent node from which to inherit source, token, finish
376     * @param request the request
377     * @param args    arguments to request
378     */
379    public RuntimeNode(final Expression parent, final Request request, final Expression... args) {
380        this(parent, request, Arrays.asList(args));
381    }
382
383    /**
384     * Constructor
385     *
386     * @param parent  parent node from which to inherit source, token, finish
387     * @param request the request
388     * @param args    arguments to request
389     */
390    public RuntimeNode(final Expression parent, final Request request, final List<Expression> args) {
391        super(parent);
392
393        this.request      = request;
394        this.args         = args;
395    }
396
397    /**
398     * Constructor
399     *
400     * @param parent  parent node from which to inherit source, token, finish and arguments
401     * @param request the request
402     */
403    public RuntimeNode(final UnaryNode parent, final Request request) {
404        this(parent, request, parent.getExpression());
405    }
406
407    /**
408     * Constructor used to replace a binary node with a runtime request.
409     *
410     * @param parent  parent node from which to inherit source, token, finish and arguments
411     */
412    public RuntimeNode(final BinaryNode parent) {
413        this(parent, Request.requestFor(parent), parent.lhs(), parent.rhs());
414    }
415
416    /**
417     * Reset the request for this runtime node
418     * @param request request
419     * @return new runtime node or same if same request
420     */
421    public RuntimeNode setRequest(final Request request) {
422        if (this.request == request) {
423            return this;
424        }
425        return new RuntimeNode(this, request, args);
426   }
427
428    /**
429     * Return type for the ReferenceNode
430     */
431    @Override
432    public Type getType() {
433        return request.getReturnType();
434    }
435
436    @Override
437    public Node accept(final NodeVisitor<? extends LexicalContext> visitor) {
438        if (visitor.enterRuntimeNode(this)) {
439            return visitor.leaveRuntimeNode(setArgs(Node.accept(visitor, args)));
440        }
441
442        return this;
443    }
444
445    @Override
446    public void toString(final StringBuilder sb, final boolean printType) {
447        sb.append("ScriptRuntime.");
448        sb.append(request);
449        sb.append('(');
450
451        boolean first = true;
452
453        for (final Node arg : args) {
454            if (!first) {
455                sb.append(", ");
456            } else {
457                first = false;
458            }
459
460            arg.toString(sb, printType);
461        }
462
463        sb.append(')');
464    }
465
466    /**
467     * Get the arguments for this runtime node
468     * @return argument list
469     */
470    public List<Expression> getArgs() {
471        return Collections.unmodifiableList(args);
472    }
473
474    /**
475     * Set the arguments of this runtime node
476     * @param args new arguments
477     * @return new runtime node, or identical if no change
478     */
479    public RuntimeNode setArgs(final List<Expression> args) {
480        if (this.args == args) {
481            return this;
482        }
483        return new RuntimeNode(this, request, args);
484    }
485
486    /**
487     * Get the request that this runtime node implements
488     * @return the request
489     */
490    public Request getRequest() {
491        return request;
492    }
493
494    /**
495     * Is this runtime node, engineered to handle the "at least one object" case of the defined
496     * requests and specialize on demand, really primitive. This can happen e.g. after AccessSpecializer
497     * In that case it can be turned into a simpler primitive form in CodeGenerator
498     *
499     * @return true if all arguments now are primitive
500     */
501    public boolean isPrimitive() {
502        for (final Expression arg : args) {
503            if (arg.getType().isObject()) {
504                return false;
505            }
506        }
507        return true;
508    }
509}
510