Package com.redhat.ceylon.compiler.js

Source Code of com.redhat.ceylon.compiler.js.JsIdentifierNames

package com.redhat.ceylon.compiler.js;

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import com.redhat.ceylon.compiler.typechecker.model.ClassOrInterface;
import com.redhat.ceylon.compiler.typechecker.model.Declaration;
import com.redhat.ceylon.compiler.typechecker.model.Method;
import com.redhat.ceylon.compiler.typechecker.model.MethodOrValue;
import com.redhat.ceylon.compiler.typechecker.model.Module;
import com.redhat.ceylon.compiler.typechecker.model.Parameter;
import com.redhat.ceylon.compiler.typechecker.model.Scope;
import com.redhat.ceylon.compiler.typechecker.model.TypeAlias;
import com.redhat.ceylon.compiler.typechecker.model.TypeDeclaration;

/**
* Manages the identifier names in the JavaScript code generated for a Ceylon
* compilation unit.
*
* @author Ivo Kasiuk
*/
public class JsIdentifierNames {

    private static long uniqueID = 0;
    private static long nextUID() {
        if (++uniqueID <= 0) {
            uniqueID = 1;
        }
        return uniqueID;
    }

    private static Set<String> reservedWords = new HashSet<String>();

    static {
        // Identifiers that have to be escaped because they are keywords in
        // JavaScript. We don't have to include identifiers that are also
        // keywords in Ceylon because no such identifiers can occur in Ceylon
        // source code anyway.
        //Language
        reservedWords.addAll(Arrays.asList("undefined", "boolean", "byte", "char", "const",
                "debugger", "default", "delete", "do", "double", "enum", "export", "false",
                "final", "float", "goto", "implements", "instanceof", "int", "long",
                "native", "new", "null", "private", "protected", "public", "short", "static",
                "synchronized", "throws", "transient", "true", "typeof", "var", "volatile",
                "with", "abstract"));
        //Types
        reservedWords.addAll(Arrays.asList("Date", "Object", "Boolean", "Error", "Number"));
        //JS Object
        reservedWords.addAll(Arrays.asList("hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable"));
        //JS Function
        reservedWords.add("Function");
        reservedWords.addAll(Arrays.asList("call", "arguments", "caller", "apply", "bind")); //name?
        //JS Number
        reservedWords.addAll(Arrays.asList("toFixed", "valueOf", "toPrecision", "toExponential"));
        //JS String
        reservedWords.add("String");
        reservedWords.addAll(Arrays.asList("charAt", "strike", "fixed", "sub", "charCodeAt",
                "trimLeft", "toLocaleUpperCase", "toUpperCase", "fontsize", "search",
                "toLocaleLowerCase", "small", "big", "fontcolor", "blink", "trim",
                "bold", "match", "substr", "trimRight", "replace", "split", "sup", "link",
                "localeCompare", "valueOf", "substring", "toLowerCase", "italics", "anchor"));
        //JS Array
        reservedWords.add("Array");
        reservedWords.addAll(Arrays.asList("toLocaleString", "splice", "map", "forEach", "reverse",
                "join", "push", "shift", "pop", "sort", "unshift", "reduceRight", "reduce",
                "every", "filter"));
        //String, Array, etc
        reservedWords.addAll(Arrays.asList("length", "toString", "constructor", "prototype",
                "concat", "indexOf", "lastIndexOf", "slice", "get"));
    }

    public static boolean isReservedWord(String token) {
        return reservedWords.contains(token);
    }

    /**
     * Determine the identifier name to be used in the generated JavaScript code
     * to represent the given declaration.
     */
    public String name(Declaration decl) {
        return getName(decl, false, false);
    }

    public String name(Parameter param) {
        if (param == null) { return null; }
        String name = param.getName();
        MethodOrValue decl = param.getModel();
        final boolean nonLocal = (decl.isShared() && decl.isMember())
                || (decl.isToplevel() && decl instanceof Method);
        if (nonLocal) {
            // The identifier might be accessed from other .js files, so it must
            // be reliably reproducible. In most cases simply using the original
            // name is ok because otherwise it would result in a name collision in
            // Ceylon too. We just have to take care of a few exceptions:
            String suffix = nestingSuffix(decl, false);
            if (suffix.length() > 0) {
                // nested type
                name += suffix;
            } else if (reservedWords.contains(name)) {
                // JavaScript keyword
                name = "$_" + name;
            }
        }
        else {
            // The identifier will not be used outside the generated .js file,
            // so we can simply disambiguate it with a numeric ID.
            name = uniqueVarNames.get(decl);
            if (name == null) {
                name = "$" + Long.toString(getUID(decl), 36);
            }
        }
        return name;
    }

    /**
     * Determine a secondary, private identifier name for the given declaration.
     */
    public String privateName(Declaration decl) {
        return getName(decl, false, true);
    }

    public String objectName(Declaration decl) {
        String name = getName(decl, true, false);
        if (decl.isToplevel()) {
            return String.format("get%c%s()", Character.toUpperCase(name.charAt(0)),
                    name.substring(1));
        }
        return name;
    }

    /**
     * Determine the function name to be used in the generated JavaScript code
     * for the getter of the given declaration.
     */
    public String getter(Declaration decl) {
        if (decl == null) { return ""; }
        String name = getName(decl, true, false);
        return String.format("get%c%s", Character.toUpperCase(name.charAt(0)),
                name.substring(1));
    }

    /**
     * Determine the function name to be used in the generated JavaScript code
     * for the setter of the given declaration.
     */
    public String setter(Declaration decl) {
        String name = getName(decl, true, false);
        return String.format("set%c%s", Character.toUpperCase(name.charAt(0)),
                name.substring(1));
    }

    /**
     * Determine the identifier to be used in the generated JavaScript code as
     * an alias for the given package.
     */
    public String moduleAlias(Module pkg) {
        if (JsCompiler.isCompilingLanguageModule() && pkg.getLanguageModule()==pkg) {
            //If we're compiling the language module, omit the package name
            return "";
        }
        StringBuilder sb = new StringBuilder("m$");
        sb.append(Long.toString(getUID(pkg), 36));
        return sb.toString();
    }

    /** Creates a new unique identifier. */
    public String createTempVariable() {
        return "$"+Long.toString(nextUID(),36);
    }

    /**
     * Determine the identifier to be used for the self variable of the given type.
     */
    public String self(TypeDeclaration decl) {
        String name = decl.getName();
        if (decl.isShared() || decl.isToplevel()) {
            name += nestingSuffix(decl, true);
        } else {
            // The identifier will not be used outside the generated .js file,
            // so we can simply disambiguate it with a numeric ID.
            name = "$" + Long.toString(getUID(decl), 36);
        }
        return String.format("%c%s$", Character.toLowerCase(name.charAt(0)),
                    name.substring(1));
    }

    /**
     * Returns a disambiguation suffix for the given scope. It is guaranteed that
     * the suffixes generated for two different scopes are different.
     */
    public String scopeSuffix(Scope scope) {
        return String.format("$%s", scope.getQualifiedNameString().replace("::","$").replace('.', '$'));
    }

    /**
     * Generates a disambiguation suffix if the given declaration is a nested
     * declaration whose name may collide with other declarations.
     * Currently this is required only for types which are nested inside other
     * types.
     */
    private String nestingSuffix(Declaration decl, final boolean forSelf) {
        String suffix = "";
        if (decl instanceof TypeDeclaration && (forSelf || !decl.isAnonymous())) {
            // The generated suffix consists of the names of the enclosing types.
            StringBuilder sb = new StringBuilder();
            // Use the original declaration if it's an overriden class: an overriding
            // member must have the same name as the member it overrides.
            Scope scope = originalDeclaration(decl).getContainer();
            while (scope instanceof TypeDeclaration) {
                sb.append('$');
                sb.append(((TypeDeclaration) scope).getName());
                scope = scope.getContainer();
            }
            suffix = sb.toString();
        }
        return suffix;
    }

    public void forceName(Declaration decl, String name) {
        uniqueVarNames.put(decl, name);
    }

    private Map<Module, Long> moduleUIDs = new HashMap<Module, Long>();
    private Map<Declaration, Long> uniqueVarIDs = new HashMap<Declaration, Long>();
    private Map<Declaration, String> uniqueVarNames =
            new HashMap<Declaration, String>();

    private String getName(Declaration decl, boolean forGetterSetter, boolean priv) {
        if (decl == null) { return null; }
        String name = decl.getName();
        boolean nonLocal = !priv;
        if (nonLocal) {
            // check if it's a shared member or a toplevel function
            nonLocal = decl.isMember() ? decl.isShared() || decl instanceof TypeDeclaration :
                decl.isToplevel() && (forGetterSetter || (decl instanceof Method)
                || (decl instanceof ClassOrInterface) || (decl instanceof TypeAlias));
        }
        if (nonLocal && decl instanceof com.redhat.ceylon.compiler.typechecker.model.Class
                && ((com.redhat.ceylon.compiler.typechecker.model.Class)decl).isAnonymous() && !forGetterSetter) {
            // A lower-case class name belongs to an object and is not public.
            nonLocal = false;
        }
        if (nonLocal) {
            // The identifier might be accessed from other .js files, so it must
            // be reliably reproducible. In most cases simply using the original
            // name is ok because otherwise it would result in a name collision in
            // Ceylon too. We just have to take care of a few exceptions:
            String suffix = nestingSuffix(decl, false);
            if (suffix.length() > 0) {
                // nested type
                name += suffix;
            } else if (!forGetterSetter && reservedWords.contains(name)) {
                // JavaScript keyword
                name = "$_" + name;
            }
        }
        else {
            // The identifier will not be used outside the generated .js file,
            // so we can simply disambiguate it with a numeric ID.
            name = uniqueVarNames.get(decl);
            if (name == null) {
                name = String.format(priv ? "$%s_" : "$%s", Long.toString(getUID(decl), 36));
            }
        }
        //Fix #204 - same top-level declarations in different packages
        final com.redhat.ceylon.compiler.typechecker.model.Package declPkg = decl.getUnit().getPackage();
        if (decl.isToplevel() && !declPkg.equals(declPkg.getModule().getRootPackage())) {
            //rootPackage can be null when compiling from IDE
            String rootName = declPkg.getModule().getRootPackage() == null ?
                    "":declPkg.getModule().getRootPackage().getNameAsString();
            String pkgName = declPkg.getNameAsString();
            rootName = pkgName.substring(rootName.length()).replaceAll("\\.", "\\$");
            if (rootName.length()>0 && rootName.charAt(0) != '$') {
                rootName = '$' + rootName;
            }
            name += rootName;
        }
        if (decl instanceof TypeAlias) {
            name+="()";
        }
        return name;
    }
   
    private Declaration originalDeclaration(Declaration decl) {
        Declaration refinedDecl = decl;
        while (true) {
            Declaration d = refinedDecl.getRefinedDeclaration();
            if ((d == null) || (d == refinedDecl)) { break; }
            refinedDecl = d;
        }
        return refinedDecl;
    }

    private long getUID(Declaration decl) {
        Long id = uniqueVarIDs.get(decl);
        if (id == null) {
            id = nextUID();
            uniqueVarIDs.put(decl, id);
        }
        return id;
    }

    private long getUID(Module pkg) {
        Long id = moduleUIDs.get(pkg);
        if (id == null) {
            id = nextUID();
            moduleUIDs.put(pkg, id);
        }
        return id;
    }

}
TOP

Related Classes of com.redhat.ceylon.compiler.js.JsIdentifierNames

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.