Package com.redhat.ceylon.compiler.js

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

package com.redhat.ceylon.compiler.js;

import static com.redhat.ceylon.compiler.typechecker.analyzer.Util.eliminateParensAndWidening;

import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;

import org.antlr.runtime.CommonToken;

import com.redhat.ceylon.compiler.Options;
import com.redhat.ceylon.compiler.js.TypeUtils.RuntimeMetamodelAnnotationGenerator;
import com.redhat.ceylon.compiler.typechecker.model.Class;
import com.redhat.ceylon.compiler.typechecker.model.ClassAlias;
import com.redhat.ceylon.compiler.typechecker.model.ClassOrInterface;
import com.redhat.ceylon.compiler.typechecker.model.Declaration;
import com.redhat.ceylon.compiler.typechecker.model.Functional;
import com.redhat.ceylon.compiler.typechecker.model.Interface;
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.Package;
import com.redhat.ceylon.compiler.typechecker.model.ProducedType;
import com.redhat.ceylon.compiler.typechecker.model.Scope;
import com.redhat.ceylon.compiler.typechecker.model.Setter;
import com.redhat.ceylon.compiler.typechecker.model.TypeAlias;
import com.redhat.ceylon.compiler.typechecker.model.TypeDeclaration;
import com.redhat.ceylon.compiler.typechecker.model.TypeParameter;
import com.redhat.ceylon.compiler.typechecker.model.UnknownType;
import com.redhat.ceylon.compiler.typechecker.model.Util;
import com.redhat.ceylon.compiler.typechecker.model.Value;
import com.redhat.ceylon.compiler.typechecker.tree.*;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.*;

public class GenerateJsVisitor extends Visitor
        implements NaturalVisitor {

    private final Stack<Continuation> continues = new Stack<>();
    private final EnclosingFunctionVisitor encloser = new EnclosingFunctionVisitor();
    private final JsIdentifierNames names;
    private final Set<Declaration> directAccess = new HashSet<>();
    private final Set<Declaration> generatedAttributes = new HashSet<>();
    private final RetainedVars retainedVars = new RetainedVars();
    final ConditionGenerator conds;
    private final InvocationGenerator invoker;
    private final List<CommonToken> tokens;
    private final ErrorVisitor errVisitor = new ErrorVisitor();
    private int dynblock;
    private int exitCode = 0;

    static final class SuperVisitor extends Visitor {
        private final List<Declaration> decs;

        SuperVisitor(List<Declaration> decs) {
            this.decs = decs;
        }

        @Override
        public void visit(QualifiedMemberOrTypeExpression qe) {
            Term primary = eliminateParensAndWidening(qe.getPrimary());
            if (primary instanceof Super) {
                decs.add(qe.getDeclaration());
            }
            super.visit(qe);
        }

        @Override
        public void visit(QualifiedType that) {
            if (that.getOuterType() instanceof SuperType) {
                decs.add(that.getDeclarationModel());
            }
            super.visit(that);
        }

        public void visit(Tree.ClassOrInterface qe) {
            //don't recurse
            if (qe instanceof ClassDefinition) {
                ExtendedType extType = ((ClassDefinition) qe).getExtendedType();
                if (extType != null) { super.visit(extType); }
            }
        }
    }

    private final class OuterVisitor extends Visitor {
        boolean found = false;
        private Declaration dec;

        private OuterVisitor(Declaration dec) {
            this.dec = dec;
        }

        @Override
        public void visit(QualifiedMemberOrTypeExpression qe) {
            if (qe.getPrimary() instanceof Outer ||
                    qe.getPrimary() instanceof This) {
                if ( qe.getDeclaration().equals(dec) ) {
                    found = true;
                }
            }
            super.visit(qe);
        }
    }

    private List<? extends Statement> currentStatements = null;
   
    private final TypeUtils types;
    private Writer out;
    private final Writer originalOut;
    final Options opts;
    private CompilationUnit root;
    static final String function="function ";
    private boolean needIndent = true;
    private int indentLevel = 0;

    Package getCurrentPackage() {
        return root.getUnit().getPackage();
    }

    /** Returns the module name for the language module. */
    String getClAlias() { return jsout.getLanguageModuleAlias(); }

    @Override
    public void handleException(Exception e, Node that) {
        that.addUnexpectedError(that.getMessage(e, this));
    }

    private final JsOutput jsout;

    public GenerateJsVisitor(JsOutput out, Options options, JsIdentifierNames names,
            List<CommonToken> tokens, TypeUtils typeUtils) throws IOException {
        this.jsout = out;
        this.opts = options;
        this.out = out.getWriter();
        originalOut = out.getWriter();
        this.names = names;
        conds = new ConditionGenerator(this, names, directAccess);
        this.tokens = tokens;
        types = typeUtils;
        invoker = new InvocationGenerator(this, names, retainedVars);
    }

    TypeUtils getTypeUtils() { return types; }
    InvocationGenerator getInvoker() { return invoker; }

    /** Returns the helper component to handle naming. */
    JsIdentifierNames getNames() { return names; }

    static interface GenerateCallback {
        public void generateValue();
    }
   
    /** Print generated code to the Writer specified at creation time.
     * Automatically prints indentation first if necessary.
     * @param code The main code
     * @param codez Optional additional strings to print after the main code. */
    void out(String code, String... codez) {
        try {
            if (!opts.isMinify() && opts.isIndent() && needIndent) {
                for (int i=0;i<indentLevel;i++) {
                    out.write("    ");
                }
            }
            needIndent = false;
            out.write(code);
            for (String s : codez) {
                out.write(s);
            }
            if (opts.isVerbose() && out == originalOut) {
                //Print code to console (when printing to REAL output)
                System.out.print(code);
                for (String s : codez) {
                    System.out.print(s);
                }
            }
        }
        catch (IOException ioe) {
            throw new RuntimeException("Generating JS code", ioe);
        }
    }

    /** Prints a newline. Indentation will automatically be printed by {@link #out(String, String...)}
     * when the next line is started. */
    void endLine() {
        endLine(false);
    }
    /** Prints a newline. Indentation will automatically be printed by {@link #out(String, String...)}
     * when the next line is started.
     * @param semicolon  if <code>true</code> then a semicolon is printed at the end
     *                  of the previous line*/
    void endLine(boolean semicolon) {
        if (semicolon) {
            out(";");
            if (opts.isMinify())return;
        }
        out("\n");
        needIndent = opts.isIndent() && !opts.isMinify();
    }
    /** Calls {@link #endLine()} if the current position is not already the beginning
     * of a line. */
    void beginNewLine() {
        if (!needIndent) { endLine(false); }
    }

    /** Increases indentation level, prints opening brace and newline. Indentation will
     * automatically be printed by {@link #out(String, String...)} when the next line is started. */
    void beginBlock() {
        indentLevel++;
        out("{");
        if (!opts.isMinify())endLine(false);
    }

    /** Decreases indentation level, prints a closing brace in new line (using
     * {@link #beginNewLine()}) and calls {@link #endLine()}. */
    void endBlockNewLine() {
        endBlock(false, true);
    }
    /** Decreases indentation level, prints a closing brace in new line (using
     * {@link #beginNewLine()}) and calls {@link #endLine()}.
     * @param semicolon  if <code>true</code> then prints a semicolon after the brace*/
    void endBlockNewLine(boolean semicolon) {
        endBlock(semicolon, true);
    }
    /** Decreases indentation level and prints a closing brace in new line (using
     * {@link #beginNewLine()}). */
    void endBlock() {
        endBlock(false, false);
    }
    /** Decreases indentation level and prints a closing brace in new line (using
     * {@link #beginNewLine()}).
     * @param semicolon  if <code>true</code> then prints a semicolon after the brace
     * @param newline  if <code>true</code> then additionally calls {@link #endLine()} */
    void endBlock(boolean semicolon, boolean newline) {
        indentLevel--;
        if (!opts.isMinify())beginNewLine();
        out(semicolon ? "};" : "}");
        if (semicolon&&opts.isMinify())return;
        if (newline) { endLine(false); }
    }

    /** Prints source code location in the form "at [filename] ([location])" */
    void location(Node node) {
        out(" at ", node.getUnit().getFilename(), " (", node.getLocation(), ")");
    }

    private String generateToString(final GenerateCallback callback) {
        final Writer oldWriter = out;
        out = new StringWriter();
        callback.generateValue();
        final String str = out.toString();
        out = oldWriter;
        return str;
    }
   
    @Override
    public void visit(final Tree.CompilationUnit that) {
        root = that;
        if (!that.getModuleDescriptors().isEmpty()) {
            ModuleDescriptor md = that.getModuleDescriptors().get(0);
            out("ex$.$mod$ans$=");
            TypeUtils.outputAnnotationsFunction(md.getAnnotationList(), null, this);
            endLine(true);
            if (md.getImportModuleList() != null && !md.getImportModuleList().getImportModules().isEmpty()) {
                out("ex$.$mod$imps=function(){return{");
                if (!opts.isMinify())endLine();
                boolean first=true;
                for (ImportModule im : md.getImportModuleList().getImportModules()) {
                    final StringBuilder path=new StringBuilder("'");
                    if (im.getImportPath()==null) {
                        if (im.getQuotedLiteral()==null) {
                            throw new CompilerErrorException("Invalid imported module");
                        } else {
                            final String ql = im.getQuotedLiteral().getText();
                            path.append(ql.substring(1, ql.length()-1));
                        }
                    } else {
                        for (Identifier id : im.getImportPath().getIdentifiers()) {
                            if (path.length()>1)path.append('.');
                            path.append(id.getText());
                        }
                    }
                    final String qv = im.getVersion().getText();
                    path.append('/').append(qv.substring(1, qv.length()-1)).append("'");
                    if (first)first=false;else{out(",");endLine();}
                    out(path.toString(), ":");
                    TypeUtils.outputAnnotationsFunction(im.getAnnotationList(), null, this);
                }
                if (!opts.isMinify())endLine();
                out("};};");
                if (!opts.isMinify())endLine();
            }
        }
        if (!that.getPackageDescriptors().isEmpty()) {
            final String pknm = that.getUnit().getPackage().getNameAsString().replaceAll("\\.", "\\$");
            out("ex$.$pkg$ans$", pknm, "=");
            TypeUtils.outputAnnotationsFunction(that.getPackageDescriptors().get(0).getAnnotationList(), null, this);
            endLine(true);
        }

        for (CompilerAnnotation ca: that.getCompilerAnnotations()) {
            ca.visit(this);
        }
        if (that.getImportList() != null) {
            that.getImportList().visit(this);
        }
        visitStatements(that.getDeclarations());
    }

    public void visit(final Tree.Import that) {
    }

    @Override
    public void visit(final Tree.Parameter that) {
        out(names.name(that.getParameterModel()));
    }

    @Override
    public void visit(final Tree.ParameterList that) {
        out("(");
        boolean first=true;
        boolean ptypes = false;
        //Check if this is the first parameter list
        if (that.getScope() instanceof Method && that.getModel().isFirst()) {
            ptypes = ((Method)that.getScope()).getTypeParameters() != null &&
                    !((Method)that.getScope()).getTypeParameters().isEmpty();
        }
        for (Parameter param: that.getParameters()) {
            if (!first) out(",");
            out(names.name(param.getParameterModel()));
            first = false;
        }
        if (ptypes) {
            if (!first) out(",");
            out("$$$mptypes");
        }
        out(")");
    }

    void visitStatements(List<? extends Statement> statements) {
        List<String> oldRetainedVars = retainedVars.reset(null);
        final List<? extends Statement> prevStatements = currentStatements;
        currentStatements = statements;

        for (int i=0; i<statements.size(); i++) {
            Statement s = statements.get(i);
            s.visit(this);
            if (!opts.isMinify())beginNewLine();
            retainedVars.emitRetainedVars(this);
        }
        retainedVars.reset(oldRetainedVars);
       
        currentStatements = prevStatements;
    }

    @Override
    public void visit(final Tree.Body that) {
        visitStatements(that.getStatements());
    }

    @Override
    public void visit(final Tree.Block that) {
        List<Statement> stmnts = that.getStatements();
        if (stmnts.isEmpty()) {
            out("{}");
        }
        else {
            beginBlock();
            visitStatements(stmnts);
            endBlock();
        }
    }

    void initSelf(Node node) {
        if (new NeedsThisVisitor(node).needsThisReference()) {
            out("var ", names.self(prototypeOwner), "=this");
            endLine(true);
        }
    }

    /** Visitor that determines if a method definition will need the "this" reference. */
    class NeedsThisVisitor extends Visitor {
        private boolean refs=false;
        NeedsThisVisitor(Node n) {
            if (prototypeOwner != null) {
                n.visit(this);
            }
        }
        @Override public void visit(Tree.This that) {
            refs = true;
        }
        @Override public void visit(Tree.Outer that) {
            refs = true;
        }
        @Override public void visit(Tree.Super that) {
            refs = true;
        }
        public void visit(Tree.MemberOrTypeExpression that) {
            if (refs)return;
            if (that.getDeclaration() == null) {
                //Some expressions in dynamic blocks can have null declarations
                super.visit(that);
                return;
            }
            final Scope origScope = that.getDeclaration().getContainer();
            Scope s = origScope;
            while (s != null) {
                if (s == prototypeOwner || (s instanceof TypeDeclaration && prototypeOwner.inherits((TypeDeclaration)s))) {
                    refs = true;
                    return;
                }
                s = s.getContainer();
            }
            //Check the other way around as well
            s = prototypeOwner;
            while (s != null) {
                if (s == origScope ||
                        (s instanceof TypeDeclaration && origScope instanceof TypeDeclaration
                                && ((TypeDeclaration)s).inherits((TypeDeclaration)origScope))) {
                    refs = true;
                    return;
                }
                s = s.getContainer();
            }
            super.visit(that);
        }
        boolean needsThisReference() {
            return refs;
        }
    }

    void comment(Tree.Declaration that) {
        if (!opts.isComment() || opts.isMinify()) return;
        endLine();
        String dname = that.getNodeType();
        if (dname.endsWith("Declaration") || dname.endsWith("Definition")) {
            dname = dname.substring(0, dname.length()-7);
        }
        out("//", dname, " ", that.getDeclarationModel().getName());
        location(that);
        endLine();
    }

    boolean share(Declaration d) {
        return share(d, true);
    }

    private boolean share(Declaration d, boolean excludeProtoMembers) {
        boolean shared = false;
        if (!(excludeProtoMembers && opts.isOptimize() && d.isClassOrInterfaceMember())
                && isCaptured(d)) {
            beginNewLine();
            outerSelf(d);
            String dname=names.name(d);
            if (dname.endsWith("()")){
                dname = dname.substring(0, dname.length()-2);
            }
            out(".", dname, "=", dname);
            endLine(true);
            shared = true;
        }
        return shared;
    }

    @Override
    public void visit(final Tree.ClassDeclaration that) {
        if (opts.isOptimize() && that.getDeclarationModel().isClassOrInterfaceMember()) return;
        classDeclaration(that);
    }

    private void addClassDeclarationToPrototype(TypeDeclaration outer, final Tree.ClassDeclaration that) {
        classDeclaration(that);
        final String tname = names.name(that.getDeclarationModel());
        out(names.self(outer), ".", tname, "=", tname);
        endLine(true);
    }

    private void addAliasDeclarationToPrototype(TypeDeclaration outer, Tree.TypeAliasDeclaration that) {
        comment(that);
        final TypeAlias d = that.getDeclarationModel();
        String path = qualifiedPath(that, d, true);
        if (path.length() > 0) {
            path += '.';
        }
        String tname = names.name(d);
        tname = tname.substring(0, tname.length()-2);
        String _tmp=names.createTempVariable();
        out(names.self(outer), ".", tname, "=function(){var ", _tmp, "=");
        TypeUtils.typeNameOrList(that, that.getTypeSpecifier().getType().getTypeModel(), this, true);
        out(";", _tmp, ".$crtmm$=");
        TypeUtils.encodeForRuntime(that,d,this);
        out(";return ", _tmp, ";}");
        endLine(true);
    }

    @Override
    public void visit(final Tree.InterfaceDeclaration that) {
        if (!(opts.isOptimize() && that.getDeclarationModel().isClassOrInterfaceMember())) {
            interfaceDeclaration(that);
        }
    }

    private void addInterfaceDeclarationToPrototype(TypeDeclaration outer, final Tree.InterfaceDeclaration that) {
        interfaceDeclaration(that);
        final String tname = names.name(that.getDeclarationModel());
        out(names.self(outer), ".", tname, "=", tname);
        endLine(true);
    }

    private void addInterfaceToPrototype(ClassOrInterface type, final Tree.InterfaceDefinition interfaceDef) {
        TypeGenerator.interfaceDefinition(interfaceDef, this);
        Interface d = interfaceDef.getDeclarationModel();
        out(names.self(type), ".", names.name(d), "=", names.name(d));
        endLine(true);
    }

    @Override
    public void visit(final Tree.InterfaceDefinition that) {
        if (!(opts.isOptimize() && that.getDeclarationModel().isClassOrInterfaceMember())) {
            TypeGenerator.interfaceDefinition(that, this);
        }
    }

    private void addClassToPrototype(ClassOrInterface type, final Tree.ClassDefinition classDef) {
        TypeGenerator.classDefinition(classDef, this);
        final String tname = names.name(classDef.getDeclarationModel());
        out(names.self(type), ".", tname, "=", tname);
        endLine(true);
    }

    @Override
    public void visit(final Tree.ClassDefinition that) {
        if (!(opts.isOptimize() && that.getDeclarationModel().isClassOrInterfaceMember())) {
            TypeGenerator.classDefinition(that, this);
        }
    }

    private void interfaceDeclaration(final Tree.InterfaceDeclaration that) {
        //Don't even bother with nodes that have errors
        if (errVisitor.hasErrors(that))return;
        comment(that);
        final Interface d = that.getDeclarationModel();
        final String aname = names.name(d);
        Tree.StaticType ext = that.getTypeSpecifier().getType();
        out(function, aname, "(");
        if (d.getTypeParameters() != null && !d.getTypeParameters().isEmpty()) {
            out("$$targs$$,");
        }
        out(names.self(d), "){");
        final ProducedType pt = ext.getTypeModel();
        final TypeDeclaration aliased = pt.getDeclaration();
        qualify(that,aliased);
        out(names.name(aliased), "(");
        if (!pt.getTypeArguments().isEmpty()) {
            TypeUtils.printTypeArguments(that, pt.getTypeArguments(), this, true,
                    pt.getVarianceOverrides());
            out(",");
        }
        out(names.self(d), ");}");
        endLine();
        out(aname,".$crtmm$=");
        TypeUtils.encodeForRuntime(that, d, this);
        endLine(true);
        share(d);
    }

    private void classDeclaration(final Tree.ClassDeclaration that) {
        //Don't even bother with nodes that have errors
        if (errVisitor.hasErrors(that))return;
        comment(that);
        final Class d = that.getDeclarationModel();
        final String aname = names.name(d);
        final Tree.ClassSpecifier ext = that.getClassSpecifier();
        out(function, aname, "(");
        //Generate each parameter because we need to append one at the end
        for (Parameter p: that.getParameterList().getParameters()) {
            p.visit(this);
            out(", ");
        }
        if (d.getTypeParameters() != null && !d.getTypeParameters().isEmpty()) {
            out("$$targs$$,");
        }
        out(names.self(d), "){return ");
        TypeDeclaration aliased = ext.getType().getDeclarationModel();
        qualify(that, aliased);
        out(names.name(aliased), "(");
        if (ext.getInvocationExpression().getPositionalArgumentList() != null) {
            ext.getInvocationExpression().getPositionalArgumentList().visit(this);
            if (!ext.getInvocationExpression().getPositionalArgumentList().getPositionalArguments().isEmpty()) {
                out(",");
            }
        } else {
            out("/*PENDIENTE NAMED ARG CLASS DECL */");
        }
        Map<TypeParameter, ProducedType> invargs = ext.getType().getTypeModel().getTypeArguments();
        if (invargs != null && !invargs.isEmpty()) {
            TypeUtils.printTypeArguments(that, invargs, this, true,
                    ext.getType().getTypeModel().getVarianceOverrides());
            out(",");
        }
        out(names.self(d), ");}");
        endLine();
        out(aname, ".$$=");
        qualify(that, aliased);
        out(names.name(aliased), ".$$");
        endLine(true);
        out(aname,".$crtmm$=");
        TypeUtils.encodeForRuntime(that, d, this);
        endLine(true);
        share(d);
    }

    @Override
    public void visit(final Tree.TypeAliasDeclaration that) {
        //Don't even bother with nodes that have errors
        if (errVisitor.hasErrors(that))return;
        final TypeAlias d = that.getDeclarationModel();
        if (opts.isOptimize() && d.isClassOrInterfaceMember()) return;
        comment(that);
        final String tname=names.createTempVariable();
        out(function, names.name(d), "{var ", tname, "=");
        TypeUtils.typeNameOrList(that, that.getTypeSpecifier().getType().getTypeModel(), this, false);
        out(";", tname, ".$crtmm$=");
        TypeUtils.encodeForRuntime(that, d, this, new RuntimeMetamodelAnnotationGenerator() {
            @Override public void generateAnnotations() {
                TypeUtils.outputAnnotationsFunction(that.getAnnotationList(), d, GenerateJsVisitor.this);
            }
        });
        out(";return ", tname, ";}");
        endLine();
        share(d);
    }

    void referenceOuter(TypeDeclaration d) {
        if (!d.isToplevel()) {
            final ClassOrInterface coi = Util.getContainingClassOrInterface(d.getContainer());
            if (coi != null) {
                out(names.self(d), ".outer$");
                if (d.isClassOrInterfaceMember()) {
                    out("=this");
                } else {
                    out("=", names.self(coi));
                }
                endLine(true);
            }
        }
    }

    /** Returns the name of the type or its $init$ function if it's local. */
    String typeFunctionName(final Tree.StaticType type, boolean removeAlias) {
        TypeDeclaration d = type.getTypeModel().getDeclaration();
        if (removeAlias && d.isAlias()) {
            d = d.getExtendedTypeDeclaration();
        }
        boolean inProto = opts.isOptimize()
                && (type.getScope().getContainer() instanceof TypeDeclaration);
        String tfn = memberAccessBase(type, d, false, qualifiedPath(type, d, inProto));
        if (removeAlias && !isImported(type.getUnit().getPackage(), d)) {
            int idx = tfn.lastIndexOf('.');
            if (idx > 0) {
                tfn = tfn.substring(0, idx+1) + "$init$" + tfn.substring(idx+1) + "()";
            } else {
                tfn = "$init$" + tfn + "()";
            }
        }
        return tfn;
    }

    void addToPrototype(Node node, ClassOrInterface d, List<Tree.Statement> statements) {
        boolean enter = opts.isOptimize();
        ArrayList<com.redhat.ceylon.compiler.typechecker.model.Parameter> plist = null;
        if (enter) {
            enter = !statements.isEmpty();
            if (d instanceof com.redhat.ceylon.compiler.typechecker.model.Class) {
                com.redhat.ceylon.compiler.typechecker.model.ParameterList _pl =
                        ((com.redhat.ceylon.compiler.typechecker.model.Class)d).getParameterList();
                if (_pl != null) {
                    plist = new ArrayList<>(_pl.getParameters().size());
                    plist.addAll(_pl.getParameters());
                    enter |= !plist.isEmpty();
                }
            }
        }
        if (enter) {
            final List<? extends Statement> prevStatements = currentStatements;
            currentStatements = statements;
           
            out("(function(", names.self(d), ")");
            beginBlock();
            for (Statement s: statements) {
                addToPrototype(d, s, plist);
            }
            //Generated attributes with corresponding parameters will remove them from the list
            if (plist != null) {
                for (com.redhat.ceylon.compiler.typechecker.model.Parameter p : plist) {
                    generateAttributeForParameter(node, (com.redhat.ceylon.compiler.typechecker.model.Class)d, p);
                }
            }
            if (d.isMember()) {
                ClassOrInterface coi = Util.getContainingClassOrInterface(d.getContainer());
                if (coi != null && d.inherits(coi)) {
                    out(names.self(d), ".", names.name(d),"=", names.name(d), ";");
                }
            }
            endBlock();
            out(")(", names.name(d), ".$$.prototype)");
            endLine(true);
           
            currentStatements = prevStatements;
        }
    }

    void generateAttributeForParameter(Node node, com.redhat.ceylon.compiler.typechecker.model.Class d,
            com.redhat.ceylon.compiler.typechecker.model.Parameter p) {
        final String privname = names.name(p) + "_";
        defineAttribute(names.self(d), names.name(p.getModel()));
        out("{");
        if (p.getModel().isLate()) {
            generateUnitializedAttributeReadCheck("this."+privname, names.name(p));
        }
        out("return this.", privname, ";}");
        if (p.getModel().isVariable() || p.getModel().isLate()) {
            final String param = names.createTempVariable();
            out(",function(", param, "){");
            if (p.getModel().isLate() && !p.getModel().isVariable()) {
                generateImmutableAttributeReassignmentCheck("this."+privname, names.name(p));
            }
            out("return this.", privname,
                    "=", param, ";}");
        } else {
            out(",undefined");
        }
        out(",");
        TypeUtils.encodeForRuntime(node, p.getModel(), this);
        out(")");
        endLine(true);
    }

    private ClassOrInterface prototypeOwner;

    private void addToPrototype(ClassOrInterface d, final Tree.Statement s,
            List<com.redhat.ceylon.compiler.typechecker.model.Parameter> params) {
        ClassOrInterface oldPrototypeOwner = prototypeOwner;
        prototypeOwner = d;
        if (s instanceof MethodDefinition) {
            addMethodToPrototype(d, (MethodDefinition)s);
        } else if (s instanceof MethodDeclaration) {
            //Don't even bother with nodes that have errors
            if (errVisitor.hasErrors(s))return;
            FunctionHelper.methodDeclaration(d, (MethodDeclaration) s, this);
        } else if (s instanceof AttributeGetterDefinition) {
            addGetterToPrototype(d, (AttributeGetterDefinition)s);
        } else if (s instanceof AttributeDeclaration) {
            AttributeGenerator.addGetterAndSetterToPrototype(d, (AttributeDeclaration) s, this);
        } else if (s instanceof ClassDefinition) {
            addClassToPrototype(d, (ClassDefinition) s);
        } else if (s instanceof InterfaceDefinition) {
            addInterfaceToPrototype(d, (InterfaceDefinition) s);
        } else if (s instanceof ObjectDefinition) {
            addObjectToPrototype(d, (ObjectDefinition) s);
        } else if (s instanceof ClassDeclaration) {
            addClassDeclarationToPrototype(d, (ClassDeclaration) s);
        } else if (s instanceof InterfaceDeclaration) {
            addInterfaceDeclarationToPrototype(d, (InterfaceDeclaration) s);
        } else if (s instanceof SpecifierStatement) {
            addSpecifierToPrototype(d, (SpecifierStatement) s);
        } else if (s instanceof TypeAliasDeclaration) {
            addAliasDeclarationToPrototype(d, (TypeAliasDeclaration)s);
        }
        //This fixes #231 for prototype style
        if (params != null && s instanceof Tree.Declaration) {
            Declaration m = ((Tree.Declaration)s).getDeclarationModel();
            for (Iterator<com.redhat.ceylon.compiler.typechecker.model.Parameter> iter = params.iterator();
                    iter.hasNext();) {
                com.redhat.ceylon.compiler.typechecker.model.Parameter _p = iter.next();
                if (m.getName() != null && m.getName().equals(_p.getName())) {
                    iter.remove();
                    break;
                }
            }
        }
        prototypeOwner = oldPrototypeOwner;
    }

    void declareSelf(ClassOrInterface d) {
        out("if(", names.self(d), "===undefined)");
        if (d instanceof com.redhat.ceylon.compiler.typechecker.model.Class && d.isAbstract()) {
            out(getClAlias(), "throwexc(", getClAlias(), "InvocationException$meta$model(");
            out("\"Cannot instantiate abstract class ", d.getQualifiedNameString(), "\"),'?','?')");
        } else {
            out(names.self(d), "=new ");
            if (opts.isOptimize() && d.isClassOrInterfaceMember()) {
                out("this.");
            }
            out(names.name(d), ".$$;");
        }
        endLine();
    }

    void instantiateSelf(ClassOrInterface d) {
        out("var ", names.self(d), "=new ");
        if (opts.isOptimize() && d.isClassOrInterfaceMember()) {
            out("this.");
        }
        out(names.name(d), ".$$;");
    }

    private void addObjectToPrototype(ClassOrInterface type, final Tree.ObjectDefinition objDef) {
        objectDefinition(objDef);
        Value d = objDef.getDeclarationModel();
        Class c = (Class) d.getTypeDeclaration();
        out(names.self(type), ".", names.name(c), "=", names.name(c), ";",
                names.self(type), ".", names.name(c), ".$crtmm$=");
        TypeUtils.encodeForRuntime(objDef, d, this);
        endLine(true);
    }

    @Override
    public void visit(final Tree.ObjectDefinition that) {
        Value d = that.getDeclarationModel();
        if (!(opts.isOptimize() && d.isClassOrInterfaceMember())) {
            objectDefinition(that);
        } else {
            //Don't even bother with nodes that have errors
            if (errVisitor.hasErrors(that))return;
            Class c = (Class) d.getTypeDeclaration();
            comment(that);
            outerSelf(d);
            out(".", names.privateName(d), "=");
            outerSelf(d);
            out(".", names.name(c), "()");
            endLine(true);
        }
    }

    private void objectDefinition(final Tree.ObjectDefinition that) {
        //Don't even bother with nodes that have errors
        if (errVisitor.hasErrors(that))return;
        comment(that);
        final Value d = that.getDeclarationModel();
        boolean addToPrototype = opts.isOptimize() && d.isClassOrInterfaceMember();
        final Class c = (Class) d.getTypeDeclaration();

        out(function, names.name(c));
        Map<TypeParameter, ProducedType> targs=new HashMap<TypeParameter, ProducedType>();
        if (that.getSatisfiedTypes() != null) {
            for (StaticType st : that.getSatisfiedTypes().getTypes()) {
                Map<TypeParameter, ProducedType> stargs = st.getTypeModel().getTypeArguments();
                if (stargs != null && !stargs.isEmpty()) {
                    targs.putAll(stargs);
                }
            }
        }
        out(targs.isEmpty()?"()":"($$targs$$)");
        beginBlock();
        instantiateSelf(c);
        referenceOuter(c);
       
        final List<Declaration> superDecs = new ArrayList<Declaration>();
        if (!opts.isOptimize()) {
            new SuperVisitor(superDecs).visit(that.getClassBody());
        }
        if (!targs.isEmpty()) {
            out(names.self(c), ".$$targs$$=$$targs$$"); endLine(true);
        }
        TypeGenerator.callSuperclass(that.getExtendedType(), c, that, superDecs, this);
        TypeGenerator.callInterfaces(that.getSatisfiedTypes(), c, that, superDecs, this);
       
        that.getClassBody().visit(this);
        out("return ", names.self(c), ";");
        indentLevel--;
        endLine();
        out("};", names.name(c), ".$crtmm$=");
        TypeUtils.encodeForRuntime(that, c, this);
        endLine(true);

        TypeGenerator.initializeType(that, this);

        if (!addToPrototype) {
            out("var ", names.name(d));
            //If it's a property, create the object here
            if (defineAsProperty(d)) {
                out("=", names.name(c), "(");
                if (!targs.isEmpty()) {
                    TypeUtils.printTypeArguments(that, targs, this, false, null);
                }
                out(")");
            }
            endLine(true);
        }

        if (!defineAsProperty(d)) {
            final String objvar = (addToPrototype ? "this.":"")+names.name(d);
            out(function, names.getter(d), "()");
            beginBlock();
            //Create the object lazily
            final String oname = names.objectName(c);
            out("if(", objvar, "===", getClAlias(), "INIT$)");
            generateThrow(getClAlias()+"InitializationError",
                    "Cyclic initialization trying to read the value of '" +
                    d.getName() + "' before it was set", that);
            endLine(true);
            out("if(", objvar, "===undefined){", objvar, "=", getClAlias(), "INIT$;",
                    objvar, "=$init$", oname);
            if (!oname.endsWith("()"))out("()");
            out("(");
            if (!targs.isEmpty()) {
                TypeUtils.printTypeArguments(that, targs, this, false, null);
            }
            out(");", objvar, ".$crtmm$=", names.getter(d), ".$crtmm$;}");
            endLine();
            out("return ", objvar, ";");
            endBlockNewLine();           
           
            if (addToPrototype || d.isShared()) {
                outerSelf(d);
                out(".", names.getter(d), "=", names.getter(d));
                endLine(true);
            }
            if (!d.isToplevel()) {
                if(outerSelf(d))out(".");
            }
            out(names.getter(d), ".$crtmm$=");
            TypeUtils.encodeForRuntime(d, that.getAnnotationList(), this);
            endLine(true);
            if (!d.isToplevel()) {
                if (outerSelf(d))out(".");
            }
            out("$prop$", names.getter(d), "={get:");
            if (!d.isToplevel()) {
                if (outerSelf(d))out(".");
            }
            out(names.getter(d), ",$crtmm$:");
            if (!d.isToplevel()) {
                if (outerSelf(d))out(".");
            }
            out(names.getter(d), ".$crtmm$}");
            endLine(true);
            //make available with the class name as well, for metamodel access
            out(names.getter(c), "=", names.getter(d), ";$prop$", names.getter(c), "=", names.getter(d));
            endLine(true);
            if (d.isToplevel()) {
                out("ex$.$prop$", names.getter(d), "=$prop$", names.getter(d));
                endLine(true);
            }
        }
        else {
            out(getClAlias(), "atr$(");
            outerSelf(d);
            out(",'", names.name(d), "',function(){return ");
            if (addToPrototype) {
                out("this.", names.privateName(d));
            } else {
                out(names.name(d));
            }
            out(";},undefined,");
            TypeUtils.encodeForRuntime(d, that.getAnnotationList(), this);
            out(")");
            endLine(true);
        }
    }

    @Override
    public void visit(final Tree.MethodDeclaration that) {
        //Don't even bother with nodes that have errors
        if (errVisitor.hasErrors(that))return;
        FunctionHelper.methodDeclaration(null, that, this);
    }

    boolean shouldStitch(Declaration d) {
        return JsCompiler.isCompilingLanguageModule() && d.isNative();
    }

    private File getStitchedFilename(final Declaration d, final String suffix) {
        String fqn = d.getQualifiedNameString();
        if (fqn.startsWith("ceylon.language"))fqn = fqn.substring(15);
        if (fqn.startsWith("::"))fqn=fqn.substring(2);
        fqn = fqn.replace('.', '/').replace("::", "/");
        return new File(Stitcher.LANGMOD_JS_SRC, fqn + suffix);
    }

    /** Reads a file with hand-written snippet and outputs it to the current writer. */
    boolean stitchNative(final Declaration d, final Tree.Declaration n) {
        final File f = getStitchedFilename(d, ".js");
        if (f.exists() && f.canRead()) {
            jsout.outputFile(f);
            if (d.isClassOrInterfaceMember()) {
                if (d instanceof Value)return true;//Native values are defined as attributes
                out(names.self((TypeDeclaration)d.getContainer()), ".");
            }
            out(names.name(d), ".$crtmm$=");
            TypeUtils.encodeForRuntime(d, n.getAnnotationList(), this);
            endLine(true);
            return true;
        } else {
            if (d instanceof ClassOrInterface==false) {
                final String err = "REQUIRED NATIVE FILE MISSING FOR "
                        + d.getQualifiedNameString() + " => " + f + ", containing " + names.name(d);
                System.out.println(err);
                out("/*", err, "*/");
            }
            return false;
        }
    }

    /** Stitch a snippet of code to initialize type (usually a call to initTypeProto). */
    boolean stitchInitializer(TypeDeclaration d) {
        final File f = getStitchedFilename(d, "$init.js");
        if (f.exists() && f.canRead()) {
            jsout.outputFile(f);
            return true;
        }
        return false;
    }

    boolean stitchConstructorHelper(final Tree.ClassOrInterface coi, final String partName) {
        final File f;
        if (JsCompiler.isCompilingLanguageModule()) {
            f = getStitchedFilename(coi.getDeclarationModel(), partName + ".js");
        } else {
            f = new File(new File(coi.getUnit().getFullPath()).getParentFile(),
                    String.format("%s%s.js", names.name(coi.getDeclarationModel()), partName));
        }
        if (f.exists() && f.isFile() && f.canRead()) {
            if (opts.isVerbose() || JsCompiler.isCompilingLanguageModule()) {
                System.out.println("Stitching in " + f + ". It must contain an anonymous function "
                        + "which will be invoked with the same arguments as the "
                        + names.name(coi.getDeclarationModel()) + " constructor.");
            }
            out("(");
            jsout.outputFile(f);
            out(")");
            TypeGenerator.generateParameters(coi, this);
            endLine(true);
        }
        return false;
    }

    @Override
    public void visit(final Tree.MethodDefinition that) {
        //Don't even bother with nodes that have errors
        if (errVisitor.hasErrors(that))return;
        final Method d = that.getDeclarationModel();
        if (!((opts.isOptimize() && d.isClassOrInterfaceMember()) || isNative(d))) {
            comment(that);
            initDefaultedParameters(that.getParameterLists().get(0), d);
            FunctionHelper.methodDefinition(that, this, true);
            //Add reference to metamodel
            out(names.name(d), ".$crtmm$=");
            TypeUtils.encodeForRuntime(d, that.getAnnotationList(), this);
            endLine(true);
        }
    }

    /** Get the specifier expression for a Parameter, if one is available. */
    private SpecifierOrInitializerExpression getDefaultExpression(final Tree.Parameter param) {
        final SpecifierOrInitializerExpression expr;
        if (param instanceof ParameterDeclaration || param instanceof InitializerParameter) {
            MethodDeclaration md = null;
            if (param instanceof ParameterDeclaration) {
                TypedDeclaration td = ((ParameterDeclaration) param).getTypedDeclaration();
                if (td instanceof AttributeDeclaration) {
                    expr = ((AttributeDeclaration) td).getSpecifierOrInitializerExpression();
                } else if (td instanceof MethodDeclaration) {
                    md = (MethodDeclaration)td;
                    expr = md.getSpecifierExpression();
                } else {
                    param.addUnexpectedError("Don't know what to do with TypedDeclaration " + td.getClass().getName());
                    expr = null;
                }
            } else {
                expr = ((InitializerParameter) param).getSpecifierExpression();
            }
        } else {
            param.addUnexpectedError("Don't know what to do with defaulted/sequenced param " + param);
            expr = null;
        }
        return expr;
    }

    /** Create special functions with the expressions for defaulted parameters in a parameter list. */
    void initDefaultedParameters(final Tree.ParameterList params, Method container) {
        if (!container.isMember())return;
        for (final Parameter param : params.getParameters()) {
            com.redhat.ceylon.compiler.typechecker.model.Parameter pd = param.getParameterModel();
            if (pd.isDefaulted()) {
                final SpecifierOrInitializerExpression expr = getDefaultExpression(param);
                if (expr == null) {
                    continue;
                }
                qualify(params, container);
                out(names.name(container), "$defs$", pd.getName(), "=function");
                params.visit(this);
                out("{");
                initSelf(expr);
                out("return ");
                if (param instanceof ParameterDeclaration &&
                        ((ParameterDeclaration)param).getTypedDeclaration() instanceof MethodDeclaration) {
                    // function parameter defaulted using "=>"
                    FunctionHelper.singleExprFunction(
                            ((MethodDeclaration)((ParameterDeclaration)param).getTypedDeclaration()).getParameterLists(),
                            expr.getExpression(), null, true, true, this);
                } else if (!isNaturalLiteral(expr.getExpression().getTerm())) {
                    expr.visit(this);
                }
                out(";}");
                endLine(true);
            }
        }
    }

    /** Initialize the sequenced, defaulted and captured parameters in a type declaration. */
    void initParameters(final Tree.ParameterList params, TypeDeclaration typeDecl, Method m) {
        for (final Parameter param : params.getParameters()) {
            com.redhat.ceylon.compiler.typechecker.model.Parameter pd = param.getParameterModel();
            final String paramName = names.name(pd);
            if (pd.isDefaulted() || pd.isSequenced()) {
                out("if(", paramName, "===undefined){", paramName, "=");
                if (pd.isDefaulted()) {
                    if (m !=null && m.isMember()) {
                        qualify(params, m);
                        out(names.name(m), "$defs$", pd.getName(), "(");
                        boolean firstParam=true;
                        for (com.redhat.ceylon.compiler.typechecker.model.Parameter p : m.getParameterLists().get(0).getParameters()) {
                            if (firstParam){firstParam=false;}else out(",");
                            out(names.name(p));
                        }
                        out(")");
                    } else {
                        final SpecifierOrInitializerExpression expr = getDefaultExpression(param);
                        if (expr == null) {
                            param.addUnexpectedError("Default expression missing for " + pd.getName());
                            out("null");
                        } else if (param instanceof ParameterDeclaration &&
                                ((ParameterDeclaration)param).getTypedDeclaration() instanceof MethodDeclaration) {
                            // function parameter defaulted using "=>"
                            FunctionHelper.singleExprFunction(
                                    ((MethodDeclaration)((ParameterDeclaration)param).getTypedDeclaration()).getParameterLists(),
                                    expr.getExpression(), m, true, true, this);
                        } else {
                            expr.visit(this);
                        }
                    }
                } else {
                    out(getClAlias(), "getEmpty()");
                }
                out(";}");
                endLine();
            }
            if ((typeDecl != null) && pd.getModel().isCaptured()) {
                out(names.self(typeDecl), ".", paramName, "_=", paramName);
                if (!opts.isOptimize() && pd.isHidden()) { //belt and suspenders...
                    out(";", names.self(typeDecl), ".", paramName, "=", paramName);
                }
                endLine(true);
            }
        }
    }

    private void addMethodToPrototype(TypeDeclaration outer,
            final Tree.MethodDefinition that) {
        //Don't even bother with nodes that have errors
        if (errVisitor.hasErrors(that))return;
        Method d = that.getDeclarationModel();
        if (!opts.isOptimize()||!d.isClassOrInterfaceMember()) return;
        comment(that);
        initDefaultedParameters(that.getParameterLists().get(0), d);
        out(names.self(outer), ".", names.name(d), "=");
        FunctionHelper.methodDefinition(that, this, false);
        //Add reference to metamodel
        out(names.self(outer), ".", names.name(d), ".$crtmm$=");
        TypeUtils.encodeForRuntime(d, that.getAnnotationList(), this);
        endLine(true);
    }

    @Override
    public void visit(final Tree.AttributeGetterDefinition that) {
        Value d = that.getDeclarationModel();
        if (opts.isOptimize()&&d.isClassOrInterfaceMember()) return;
        comment(that);
        if (defineAsProperty(d)) {
            defineAttribute(names.self((TypeDeclaration)d.getContainer()), names.name(d));
            AttributeGenerator.getter(that, this);
            final AttributeSetterDefinition setterDef = associatedSetterDefinition(d);
            if (setterDef == null) {
                out(",undefined");
            } else {
                out(",function(", names.name(setterDef.getDeclarationModel().getParameter()), ")");
                AttributeGenerator.setter(setterDef, this);
            }
            out(",");
            TypeUtils.encodeForRuntime(d, that.getAnnotationList(), this);
            if (setterDef != null) {
                out(",");
                TypeUtils.encodeForRuntime(setterDef.getDeclarationModel(), setterDef.getAnnotationList(), this);
            }
            out(");");
        }
        else {
            out(function, names.getter(d), "()");
            AttributeGenerator.getter(that, this);
            endLine();
            out(names.getter(d), ".$crtmm$=");
            TypeUtils.encodeForRuntime(that, d, this);
            if (!shareGetter(d)) { out(";"); }
            generateAttributeMetamodel(that, true, false);
        }
    }

    private void addGetterToPrototype(TypeDeclaration outer,
            final Tree.AttributeGetterDefinition that) {
        Value d = that.getDeclarationModel();
        if (!opts.isOptimize()||!d.isClassOrInterfaceMember()) return;
        comment(that);
        defineAttribute(names.self(outer), names.name(d));
        AttributeGenerator.getter(that, this);
        final AttributeSetterDefinition setterDef = associatedSetterDefinition(d);
        if (setterDef == null) {
            out(",undefined");
        } else {
            out(",function(", names.name(setterDef.getDeclarationModel().getParameter()), ")");
            AttributeGenerator.setter(setterDef, this);
        }
        out(",");
        TypeUtils.encodeForRuntime(d, that.getAnnotationList(), this);
        if (setterDef != null) {
            out(",");
            TypeUtils.encodeForRuntime(setterDef.getDeclarationModel(), setterDef.getAnnotationList(), this);
        }
        out(");");
    }
   
    Tree.AttributeSetterDefinition associatedSetterDefinition(
            final Value valueDecl) {
        final Setter setter = valueDecl.getSetter();
        if ((setter != null) && (currentStatements != null)) {
            for (Statement stmt : currentStatements) {
                if (stmt instanceof AttributeSetterDefinition) {
                    final AttributeSetterDefinition setterDef =
                            (AttributeSetterDefinition) stmt;
                    if (setterDef.getDeclarationModel() == setter) {
                        return setterDef;
                    }
                }
            }
        }
        return null;
    }

    /** Exports a getter function; useful in non-prototype style. */
    boolean shareGetter(final MethodOrValue d) {
        boolean shared = false;
        if (isCaptured(d)) {
            beginNewLine();
            outerSelf(d);
            out(".", names.getter(d), "=", names.getter(d));
            endLine(true);
            shared = true;
        }
        return shared;
    }

    @Override
    public void visit(final Tree.AttributeSetterDefinition that) {
        Setter d = that.getDeclarationModel();
        if ((opts.isOptimize()&&d.isClassOrInterfaceMember()) || defineAsProperty(d)) return;
        comment(that);
        out("function ", names.setter(d.getGetter()), "(", names.name(d.getParameter()), ")");
        AttributeGenerator.setter(that, this);
        if (!shareSetter(d)) { out(";"); }
        if (!d.isToplevel())outerSelf(d);
        out(names.setter(d.getGetter()), ".$crtmm$=");
        TypeUtils.encodeForRuntime(d, that.getAnnotationList(), this);
        endLine(true);
        generateAttributeMetamodel(that, false, true);
    }

    boolean isCaptured(Declaration d) {
        if (d.isToplevel()||d.isClassOrInterfaceMember()) { //TODO: what about things nested inside control structures
            if (d.isShared() || d.isCaptured() ) {
                return true;
            }
            else {
                OuterVisitor ov = new OuterVisitor(d);
                ov.visit(root);
                return ov.found;
            }
        }
        else {
            return false;
        }
    }

    boolean shareSetter(final MethodOrValue d) {
        boolean shared = false;
        if (isCaptured(d)) {
            beginNewLine();
            outerSelf(d);
            out(".", names.setter(d), "=", names.setter(d));
            endLine(true);
            shared = true;
        }
        return shared;
    }

    @Override
    public void visit(final Tree.AttributeDeclaration that) {
        final Value d = that.getDeclarationModel();
        //Check if the attribute corresponds to a class parameter
        //This is because of the new initializer syntax
        final com.redhat.ceylon.compiler.typechecker.model.Parameter param = d.isParameter() ?
                ((Functional)d.getContainer()).getParameter(d.getName()) : null;
        if (d.isFormal()) {
            if (!opts.isOptimize())generateAttributeMetamodel(that, false, false);
        } else {
            comment(that);
            SpecifierOrInitializerExpression specInitExpr =
                        that.getSpecifierOrInitializerExpression();
            final boolean addGetter = (specInitExpr != null) || (param != null) || !d.isMember()
                    || d.isVariable() || d.isLate();
            final boolean addSetter = (d.isVariable() || d.isLate()) && !defineAsProperty(d);
            if (opts.isOptimize() && d.isClassOrInterfaceMember()) {
                if ((specInitExpr != null
                        && !(specInitExpr instanceof LazySpecifierExpression)) || d.isLate()) {
                    outerSelf(d);
                    out(".", names.privateName(d), "=");
                    if (d.isLate()) {
                        out("undefined");
                    } else {
                        super.visit(that);
                    }
                    endLine(true);
                }
            }
            else if (specInitExpr instanceof LazySpecifierExpression) {
                final boolean property = defineAsProperty(d);
                if (property) {
                    defineAttribute(names.self((TypeDeclaration)d.getContainer()), names.name(d));
                    out("{");
                } else {
                    out(function, names.getter(d), "(){");
                }
                initSelf(that);
                out("return ");
                if (!isNaturalLiteral(specInitExpr.getExpression().getTerm())) {
                    int boxType = boxStart(specInitExpr.getExpression().getTerm());
                    specInitExpr.getExpression().visit(this);
                    if (boxType == 4) out("/*TODO: callable targs 1*/");
                    boxUnboxEnd(boxType);
                }
                out(";}");
                if (property) {
                    Tree.AttributeSetterDefinition setterDef = null;
                    if (d.isVariable()) {
                        setterDef = associatedSetterDefinition(d);
                        if (setterDef != null) {
                            out(",function(", names.name(setterDef.getDeclarationModel().getParameter()), ")");
                            AttributeGenerator.setter(setterDef, this);
                        }
                    }
                    if (setterDef == null) {
                        out(",undefined");
                    }
                    out(",");
                    TypeUtils.encodeForRuntime(d, that.getAnnotationList(), this);
                    if (setterDef != null) {
                        out(",");
                        TypeUtils.encodeForRuntime(setterDef.getDeclarationModel(), setterDef.getAnnotationList(), this);
                    }
                    out(")");
                    endLine(true);
                } else {
                    endLine(true);
                    shareGetter(d);
                }
            }
            else {
                if (addGetter) {
                    AttributeGenerator.generateAttributeGetter(that, d, specInitExpr,
                            names.name(param), this, directAccess);
                }
                if (addSetter) {
                    AttributeGenerator.generateAttributeSetter(that, d, this);
                }
            }
            generateAttributeMetamodel(that, addGetter, addSetter);
        }
    }

    /** Generate runtime metamodel info for an attribute declaration or definition. */
    void generateAttributeMetamodel(final Tree.TypedDeclaration that, final boolean addGetter, final boolean addSetter) {
        //No need to define all this for local values
        Scope _scope = that.getScope();
        while (_scope != null) {
            //TODO this is bound to change for local decl metamodel
            if (_scope instanceof Declaration) {
                if (_scope instanceof Method)return;
                else break;
            }
            _scope = _scope.getContainer();
        }
        Declaration d = that.getDeclarationModel();
        if (d instanceof Setter) d = ((Setter)d).getGetter();
        final String pname = names.getter(d);
        if (!generatedAttributes.contains(d)) {
            if (d.isToplevel()) {
                out("var ");
            } else if (outerSelf(d)) {
                out(".");
            }
            //issue 297 this is only needed in some cases
            out("$prop$", pname, "={$crtmm$:");
            TypeUtils.encodeForRuntime(d, that.getAnnotationList(), this);
            out("}"); endLine(true);
            if (d.isToplevel()) {
                out("ex$.$prop$", pname, "=$prop$", pname);
                endLine(true);
            }
            generatedAttributes.add(d);
        }
        if (addGetter) {
            if (!d.isToplevel()) {
                if (outerSelf(d))out(".");
            }
            out("$prop$", pname, ".get=");
            if (isCaptured(d) && !defineAsProperty(d)) {
                out(pname);
                endLine(true);
                out(pname, ".$crtmm$=$prop$", pname, ".$crtmm$");
            } else {
                out("function(){return ", names.name(d), "}");
            }
            endLine(true);
        }
        if (addSetter) {
            final String pset = names.setter(d instanceof Setter ? ((Setter)d).getGetter() : d);
            if (!d.isToplevel()) {
                if (outerSelf(d))out(".");
            }
            out("$prop$", pname, ".set=", pset);
            endLine(true);
            out("if(", pset, ".$crtmm$===undefined)", pset, ".$crtmm$=$prop$", pname, ".$crtmm$");
            endLine(true);
        }
    }

    void generateUnitializedAttributeReadCheck(String privname, String pubname) {
        //TODO we can later optimize this, to replace this getter with the plain one
        //once the value has been defined
        out("if(", privname, "===undefined)throw ", getClAlias(),
                "InitializationError('Attempt to read unitialized attribute «", pubname, "»');");
    }
    void generateImmutableAttributeReassignmentCheck(String privname, String pubname) {
        out("if(", privname, "!==undefined)throw ", getClAlias(),
                "InitializationError('Attempt to reassign immutable attribute «", pubname, "»');");
    }

    @Override
    public void visit(final Tree.CharLiteral that) {
        out(getClAlias(), "Character(");
        out(String.valueOf(that.getText().codePointAt(1)));
        out(",true)");
    }

    /** Escapes a StringLiteral (needs to be quoted). */
    String escapeStringLiteral(String s) {
        StringBuilder text = new StringBuilder(s);
        //Escape special chars
        for (int i=0; i < text.length();i++) {
            switch(text.charAt(i)) {
            case 8:text.replace(i, i+1, "\\b"); i++; break;
            case 9:text.replace(i, i+1, "\\t"); i++; break;
            case 10:text.replace(i, i+1, "\\n"); i++; break;
            case 12:text.replace(i, i+1, "\\f"); i++; break;
            case 13:text.replace(i, i+1, "\\r"); i++; break;
            case 34:text.replace(i, i+1, "\\\""); i++; break;
            case 39:text.replace(i, i+1, "\\'"); i++; break;
            case 92:text.replace(i, i+1, "\\\\"); i++; break;
            case 0x2028:text.replace(i, i+1, "\\u2028"); i++; break;
            case 0x2029:text.replace(i, i+1, "\\u2029"); i++; break;
            }
        }
        return text.toString();
    }

    @Override
    public void visit(final Tree.StringLiteral that) {
        out("\"", escapeStringLiteral(that.getText()), "\"");
    }

    @Override
    public void visit(final Tree.StringTemplate that) {
        List<StringLiteral> literals = that.getStringLiterals();
        List<Expression> exprs = that.getExpressions();
        for (int i = 0; i < literals.size(); i++) {
            StringLiteral literal = literals.get(i);
            literal.visit(this);
            if (i>0)out(")");
            if (i < exprs.size()) {
                out(".plus(");
                final Expression expr = exprs.get(i);
                expr.visit(this);
                if (expr.getTypeModel() == null || !"ceylon.language::String".equals(expr.getTypeModel().getProducedTypeQualifiedName())) {
                    out(".string");
                }
                out(").plus(");
            }
        }
    }

    @Override
    public void visit(final Tree.FloatLiteral that) {
        out(getClAlias(), "Float(", that.getText(), ")");
    }

    public long parseNaturalLiteral(Tree.NaturalLiteral that) throws NumberFormatException {
        char prefix = that.getText().charAt(0);
        int radix = 10;
        String nt = that.getText();
        if (prefix == '$' || prefix == '#') {
            radix = prefix == '$' ? 2 : 16;
            nt = nt.substring(1);
        }
        return new java.math.BigInteger(nt, radix).longValue();
    }

    @Override
    public void visit(final Tree.NaturalLiteral that) {
        try {
            out("(", Long.toString(parseNaturalLiteral(that)), ")");
        } catch (NumberFormatException ex) {
            that.addError("Invalid numeric literal " + that.getText());
        }
    }

    @Override
    public void visit(final Tree.This that) {
        out(names.self(Util.getContainingClassOrInterface(that.getScope())));
    }

    @Override
    public void visit(final Tree.Super that) {
        out(names.self(Util.getContainingClassOrInterface(that.getScope())));
    }

    @Override
    public void visit(final Tree.Outer that) {
        boolean outer = false;
        if (opts.isOptimize()) {
            Scope scope = that.getScope();
            while ((scope != null) && !(scope instanceof TypeDeclaration)) {
                scope = scope.getContainer();
            }
            if (scope != null && ((TypeDeclaration)scope).isClassOrInterfaceMember()) {
                out(names.self((TypeDeclaration) scope), ".");
                outer = true;
            }
        }
        if (outer) {
            out("outer$");
        } else {
            out(names.self(that.getTypeModel().getDeclaration()));
        }
    }

    @Override
    public void visit(final Tree.BaseMemberExpression that) {
        BmeGenerator.generateBme(that, this, false);
    }

    boolean accessDirectly(Declaration d) {
        return !accessThroughGetter(d) || directAccess.contains(d) || d.isParameter();
    }

    private boolean accessThroughGetter(Declaration d) {
        return (d instanceof MethodOrValue) && !(d instanceof Method)
                && !defineAsProperty(d);
    }
   
    boolean defineAsProperty(Declaration d) {
        // for now, only define member attributes as properties, not toplevel attributes
        return d.isMember() && d instanceof MethodOrValue && !(d instanceof Method);
    }

    /** Returns true if the top-level declaration for the term is annotated "nativejs" */
    static boolean isNative(final Tree.Term t) {
        if (t instanceof MemberOrTypeExpression) {
            return isNative(((MemberOrTypeExpression)t).getDeclaration());
        }
        return false;
    }

    /** Returns true if the declaration is annotated "nativejs" */
    static boolean isNative(Declaration d) {
        return hasAnnotationByName(getToplevel(d), "nativejs") || TypeUtils.isUnknown(d);
    }

    private static Declaration getToplevel(Declaration d) {
        while (d != null && !d.isToplevel()) {
            Scope s = d.getContainer();
            // Skip any non-declaration elements
            while (s != null && !(s instanceof Declaration)) {
                s = s.getContainer();
            }
            d = (Declaration) s;
        }
        return d;
    }

    private static boolean hasAnnotationByName(Declaration d, String name){
        if (d != null) {
            for(com.redhat.ceylon.compiler.typechecker.model.Annotation annotation : d.getAnnotations()){
                if(annotation.getName().equals(name))
                    return true;
            }
        }
        return false;
    }

    private void generateSafeOp(final Tree.QualifiedMemberOrTypeExpression that) {
        boolean isMethod = that.getDeclaration() instanceof Method;
        String lhsVar = createRetainedTempVar();
        out("(", lhsVar, "=");
        super.visit(that);
        out(",");
        if (isMethod) {
            out(getClAlias(), "JsCallable(", lhsVar, ",");
        }
        out(getClAlias(),"nn$(", lhsVar, ")?");
        if (isMethod && !((Method)that.getDeclaration()).getTypeParameters().isEmpty()) {
            //Method ref with type parameters
            BmeGenerator.printGenericMethodReference(this, that, lhsVar, memberAccess(that, lhsVar));
        } else {
            out(memberAccess(that, lhsVar));
        }
        out(":null)");
        if (isMethod) {
            out(")");
        }
    }

    void supervisit(final Tree.QualifiedMemberOrTypeExpression that) {
        super.visit(that);
    }

    @Override
    public void visit(final Tree.QualifiedMemberExpression that) {
        //Big TODO: make sure the member is actually
        //          refined by the current class!
        if (that.getMemberOperator() instanceof SafeMemberOp) {
            generateSafeOp(that);
        } else if (that.getMemberOperator() instanceof SpreadOp) {
            SequenceGenerator.generateSpread(that, this);
        } else if (that.getDeclaration() instanceof Method && that.getSignature() == null) {
            //TODO right now this causes that all method invocations are done this way
            //we need to filter somehow to only use this pattern when the result is supposed to be a callable
            //looks like checking for signature is a good way (not THE way though; named arg calls don't have signature)
            generateCallable(that, null);
        } else if (that.getStaticMethodReference() && that.getDeclaration()!=null) {
            out("function($O$) {return ");
            if (that.getDeclaration() instanceof Method) {
                if (BmeGenerator.hasTypeParameters(that)) {
                    BmeGenerator.printGenericMethodReference(this, that, "$O$", "$O$."+names.name(that.getDeclaration()));
                } else {
                    out(getClAlias(), "JsCallable($O$,$O$.", names.name(that.getDeclaration()), ")");
                }
                out(";}");
            } else {
                out("$O$.", names.name(that.getDeclaration()), ";}");
            }
        } else {
            final String lhs = generateToString(new GenerateCallback() {
                @Override public void generateValue() {
                    GenerateJsVisitor.super.visit(that);
                }
            });
            out(memberAccess(that, lhs));
        }
    }

    private void generateCallable(final Tree.QualifiedMemberOrTypeExpression that, String name) {
        if (that.getPrimary() instanceof Tree.BaseTypeExpression) {
            //it's a static method ref
            if (name == null) {
                name = memberAccess(that, "");
            }
            out("function(x){return ");
            if (BmeGenerator.hasTypeParameters(that)) {
                BmeGenerator.printGenericMethodReference(this, that, "x", "x."+name);
            } else {
                out(getClAlias(), "JsCallable(x,x.", name, ")");
            }
            out(";}");
            return;
        }
        final Declaration d = that.getDeclaration();
        if (d.isToplevel() && d instanceof Method) {
            //Just output the name
            out(names.name(d));
            return;
        }
        String primaryVar = createRetainedTempVar();
        out("(", primaryVar, "=");
        that.getPrimary().visit(this);
        out(",");
        final String member = (name == null) ? memberAccess(that, primaryVar) : (primaryVar+"."+name);
        if (that.getDeclaration() instanceof Method
                && !((Method)that.getDeclaration()).getTypeParameters().isEmpty()) {
            //Method ref with type parameters
            BmeGenerator.printGenericMethodReference(this, that, primaryVar, member);
        } else {
            out(getClAlias(), "JsCallable(", primaryVar, ",", getClAlias(), "nn$(", primaryVar, ")?", member, ":null)");
        }
        out(")");
    }
   
    /**
     * Checks if the given node is a MemberOrTypeExpression or QualifiedType which
     * represents an access to a supertype member and returns the scope of that
     * member or null.
     */
    Scope getSuperMemberScope(Node node) {
        Scope scope = null;
        if (node instanceof QualifiedMemberOrTypeExpression) {
            // Check for "super.member"
            QualifiedMemberOrTypeExpression qmte = (QualifiedMemberOrTypeExpression) node;
            final Term primary = eliminateParensAndWidening(qmte.getPrimary());
            if (primary instanceof Super) {
                scope = qmte.getDeclaration().getContainer();
            }
        }
        else if (node instanceof QualifiedType) {
            // Check for super.Membertype
            QualifiedType qtype = (QualifiedType) node;
            if (qtype.getOuterType() instanceof SuperType) {
                scope = qtype.getDeclarationModel().getContainer();
            }
        }
        return scope;
    }

    String getMember(Node node, Declaration decl, String lhs) {
        final StringBuilder sb = new StringBuilder();
        if (lhs != null) {
            if (lhs.length() > 0) {
                sb.append(lhs);
            }
        }
        else if (node instanceof BaseMemberOrTypeExpression) {
            BaseMemberOrTypeExpression bmte = (BaseMemberOrTypeExpression) node;
            Declaration bmd = bmte.getDeclaration();
            if (bmd.isParameter() && bmd.getContainer() instanceof ClassAlias) {
                return names.name(bmd);
            }
            String path = qualifiedPath(node, bmd);
            if (path.length() > 0) {
                sb.append(path);
            }
        }
        return sb.toString();
    }

    String memberAccessBase(Node node, Declaration decl, boolean setter,
                String lhs) {
        final StringBuilder sb = new StringBuilder(getMember(node, decl, lhs));

        if (sb.length() > 0) {
            if (node instanceof BaseMemberOrTypeExpression) {
                Declaration bmd = ((BaseMemberOrTypeExpression)node).getDeclaration();
                if (bmd.isParameter() && bmd.getContainer() instanceof ClassAlias) {
                    return sb.toString();
                }
            }
            sb.append('.');
        }
        Scope scope = getSuperMemberScope(node);
        if (opts.isOptimize() && (scope != null)) {
            sb.append("getT$all()['");
            sb.append(scope.getQualifiedNameString());
            sb.append("']");
            if (defineAsProperty(decl)) {
                return getClAlias() + (setter ? "attrSetter(" : "attrGetter(")
                        + sb.toString() + ",'" + names.name(decl) + "')";
            }
            sb.append(".$$.prototype.");
        }
        final String member = (accessThroughGetter(decl) && !accessDirectly(decl))
                ? (setter ? names.setter(decl) : names.getter(decl)) : names.name(decl);
        sb.append(member);
        if (!opts.isOptimize() && (scope != null)) {
            sb.append(names.scopeSuffix(scope));
        }
        return sb.toString();
    }

    /**
     * Returns a string representing a read access to a member, as represented by
     * the given expression. If lhs==null and the expression is a BaseMemberExpression
     * then the qualified path is prepended.
     */
    String memberAccess(final Tree.StaticMemberOrTypeExpression expr, String lhs) {
        Declaration decl = expr.getDeclaration();
        String plainName = null;
        if (decl == null && dynblock > 0) {
            plainName = expr.getIdentifier().getText();
        }
        else if (isNative(decl)) {
            // direct access to a native element
            plainName = decl.getName();
        }
        if (plainName != null) {
            return ((lhs != null) && (lhs.length() > 0))
                    ? (lhs + "." + plainName) : plainName;           
        }
        boolean protoCall = opts.isOptimize() && (getSuperMemberScope(expr) != null);
        if (accessDirectly(decl) && !(protoCall && defineAsProperty(decl))) {
            // direct access, without getter
            return memberAccessBase(expr, decl, false, lhs);
        }
        // access through getter
        return memberAccessBase(expr, decl, false, lhs)
                + (protoCall ? ".call(this)" : "()");
    }
   
    @Override
    public void visit(final Tree.BaseTypeExpression that) {
        Declaration d = that.getDeclaration();
        if (d == null && isInDynamicBlock()) {
            //It's a native js type but will be wrapped in dyntype() call
            String id = that.getIdentifier().getText();
            out("(typeof ", id, "==='undefined'?");
            generateThrow(null, "Undefined type " + id, that);
            out(":", id, ")");
        } else {
            qualify(that, d);
            out(names.name(d));
        }
    }

    @Override
    public void visit(final Tree.QualifiedTypeExpression that) {
        if (that.getMemberOperator() instanceof SafeMemberOp) {
            generateCallable(that, names.name(that.getDeclaration()));
        } else {
            super.visit(that);
            if (isInDynamicBlock() && that.getDeclaration() == null) {
                out(".", that.getIdentifier().getText());
            } else {
                out(".", names.name(that.getDeclaration()));
            }
        }
    }

    public void visit(final Tree.Dynamic that) {
        invoker.nativeObject(that.getNamedArgumentList());
    }

    @Override
    public void visit(final Tree.InvocationExpression that) {
        invoker.generateInvocation(that);
    }

    @Override
    public void visit(final Tree.PositionalArgumentList that) {
        invoker.generatePositionalArguments(null, that, that.getPositionalArguments(), false, false);
    }

    /** Box a term, visit it, unbox it. */
    void box(final Tree.Term term) {
        final int t = boxStart(term);
        term.visit(this);
        if (t == 4) out("/*TODO: callable targs 4*/");
        boxUnboxEnd(t);
    }

    // Make sure fromTerm is compatible with toTerm by boxing it when necessary
    int boxStart(final Tree.Term fromTerm) {
        return boxUnboxStart(fromTerm, false);
    }

    // Make sure fromTerm is compatible with toTerm by boxing or unboxing it when necessary
    int boxUnboxStart(final Tree.Term fromTerm, final Tree.Term toTerm) {
        return boxUnboxStart(fromTerm, isNative(toTerm));
    }

    // Make sure fromTerm is compatible with toDecl by boxing or unboxing it when necessary
    int boxUnboxStart(final Tree.Term fromTerm, com.redhat.ceylon.compiler.typechecker.model.TypedDeclaration toDecl) {
        return boxUnboxStart(fromTerm, isNative(toDecl));
    }

    int boxUnboxStart(final Tree.Term fromTerm, boolean toNative) {
        // Box the value
        final boolean fromNative = isNative(fromTerm);
        final ProducedType fromType = fromTerm.getTypeModel();
        final String fromTypeName = Util.isTypeUnknown(fromType) ? "UNKNOWN" : fromType.getProducedTypeQualifiedName();
        if (fromNative != toNative || fromTypeName.startsWith("ceylon.language::Callable<")) {
            if (fromNative) {
                // conversion from native value to Ceylon value
                if (fromTypeName.equals("ceylon.language::Integer")) {
                    out("(");
                } else if (fromTypeName.equals("ceylon.language::Float")) {
                    out(getClAlias(), "Float(");
                } else if (fromTypeName.equals("ceylon.language::Boolean")) {
                    out("(");
                } else if (fromTypeName.equals("ceylon.language::Character")) {
                    out(getClAlias(), "Character(");
                } else if (fromTypeName.startsWith("ceylon.language::Callable<")) {
                    out(getClAlias(), "$JsCallable(");
                    return 4;
                } else {
                    return 0;
                }
                return 1;
            } else if ("ceylon.language::Float".equals(fromTypeName)) {
                // conversion from Ceylon Float to native value
                return 2;
            } else if (fromTypeName.startsWith("ceylon.language::Callable<")) {
                Term _t = fromTerm;
                if (_t instanceof Tree.InvocationExpression) {
                    _t = ((Tree.InvocationExpression)_t).getPrimary();
                }
                //Don't box callables if they're not members or anonymous
                if (_t instanceof Tree.MemberOrTypeExpression) {
                    final Declaration d = ((Tree.MemberOrTypeExpression)_t).getDeclaration();
                    if (d != null && !(d.isClassOrInterfaceMember() || d.isAnonymous())) {
                        return 0;
                    }
                }
                out(getClAlias(), "$JsCallable(");
                return 4;
            } else {
                return 3;
            }
        }
        return 0;
    }

    void boxUnboxEnd(int boxType) {
        switch (boxType) {
        case 1: out(")"); break;
        case 2: out(".valueOf()"); break;
        case 4: out(")"); break;
        default: //nothing
        }
    }

    @Override
    public void visit(final Tree.ObjectArgument that) {
        if (errVisitor.hasErrors(that))return;
        FunctionHelper.objectArgument(that, this);
    }

    @Override
    public void visit(final Tree.AttributeArgument that) {
        if (errVisitor.hasErrors(that))return;
        FunctionHelper.attributeArgument(that, this);
    }

    @Override
    public void visit(final Tree.SequencedArgument that) {
        if (errVisitor.hasErrors(that))return;
        SequenceGenerator.sequencedArgument(that, this);
    }

    @Override
    public void visit(final Tree.SequenceEnumeration that) {
        if (errVisitor.hasErrors(that))return;
        SequenceGenerator.sequenceEnumeration(that, this);
    }


    @Override
    public void visit(final Tree.Comprehension that) {
        new ComprehensionGenerator(this, names, directAccess).generateComprehension(that);
    }


    @Override
    public void visit(final Tree.SpecifierStatement that) {
        // A lazy specifier expression in a class/interface should go into the
        // prototype in prototype style, so don't generate them here.
        if (!(opts.isOptimize() && (that.getSpecifierExpression() instanceof LazySpecifierExpression)
                && (that.getScope().getContainer() instanceof TypeDeclaration))) {
            specifierStatement(null, that);
        }
    }
   
    private void specifierStatement(final TypeDeclaration outer,
            final Tree.SpecifierStatement specStmt) {
        final Tree.Expression expr = specStmt.getSpecifierExpression().getExpression();
        final Term term = specStmt.getBaseMemberExpression();
        final BaseMemberExpression bme = term instanceof BaseMemberExpression ? (BaseMemberExpression)term : null;
        if (dynblock > 0 && Util.isTypeUnknown(term.getTypeModel())) {
            if (bme != null && bme.getDeclaration() == null) {
                out(bme.getIdentifier().getText());
            } else {
                term.visit(this);
            }
            out("=");
            int box = boxUnboxStart(expr, term);
            expr.visit(this);
            if (box == 4) out("/*TODO: callable targs 6*/");
            boxUnboxEnd(box);
            out(";");
            return;
        }
        if (bme != null) {
            Declaration bmeDecl = bme.getDeclaration();
            if (specStmt.getSpecifierExpression() instanceof LazySpecifierExpression) {
                // attr => expr;
                final boolean property = defineAsProperty(bmeDecl);
                if (property) {
                    defineAttribute(qualifiedPath(specStmt, bmeDecl), names.name(bmeDecl));
                } else  {
                    if (bmeDecl.isMember()) {
                        qualify(specStmt, bmeDecl);
                    } else {
                        out ("var ");
                    }
                    out(names.getter(bmeDecl), "=function()");
                }
                beginBlock();
                if (outer != null) { initSelf(specStmt); }
                out ("return ");
                if (!isNaturalLiteral(specStmt.getSpecifierExpression().getExpression().getTerm())) {
                    specStmt.getSpecifierExpression().visit(this);
                }
                out(";");
                endBlock();
                if (property) {
                    out(",undefined,");
                    TypeUtils.encodeForRuntime(specStmt, bmeDecl, this);
                    out(")");
                }
                endLine(true);
                directAccess.remove(bmeDecl);
            }
            else if (outer != null) {
                // "attr = expr;" in a prototype definition
                if (bmeDecl.isMember() && (bmeDecl instanceof Value) && bmeDecl.isActual()) {
                    out("delete ", names.self(outer), ".", names.name(bmeDecl));
                    endLine(true);
                }               
            }
            else if (bmeDecl instanceof MethodOrValue) {
                // "attr = expr;" in an initializer or method
                final MethodOrValue moval = (MethodOrValue)bmeDecl;
                if (moval.isVariable()) {
                    // simple assignment to a variable attribute
                    BmeGenerator.generateMemberAccess(bme, new GenerateCallback() {
                        @Override public void generateValue() {
                            int boxType = boxUnboxStart(expr.getTerm(), moval);
                            if (dynblock > 0 && !Util.isTypeUnknown(moval.getType())
                                    && Util.isTypeUnknown(expr.getTypeModel())) {
                                TypeUtils.generateDynamicCheck(expr, moval.getType(), GenerateJsVisitor.this, false,
                                        expr.getTypeModel().getTypeArguments());
                            } else {
                                expr.visit(GenerateJsVisitor.this);
                            }
                            if (boxType == 4) {
                                out(",");
                                if (moval instanceof Method) {
                                    //Add parameters
                                    TypeUtils.encodeParameterListForRuntime(specStmt,
                                            ((Method)moval).getParameterLists().get(0), GenerateJsVisitor.this);
                                    out(",");
                                } else {
                                    //TODO extract parameters from Value
                                    out("[/*VALUE Callable params", moval.getClass().getName(), "*/],");
                                }
                                TypeUtils.printTypeArguments(expr, expr.getTypeModel().getTypeArguments(),
                                        GenerateJsVisitor.this, false, expr.getTypeModel().getVarianceOverrides());
                            }
                            boxUnboxEnd(boxType);
                        }
                    }, null, this);
                    out(";");
                } else if (moval.isMember()) {
                    if (moval instanceof Method) {
                        //same as fat arrow
                        qualify(specStmt, bmeDecl);
                        out(names.name(moval), "=function ", names.name(moval), "(");
                        //Build the parameter list, we'll use it several times
                        final StringBuilder paramNames = new StringBuilder();
                        final List<com.redhat.ceylon.compiler.typechecker.model.Parameter> params =
                                ((Method) moval).getParameterLists().get(0).getParameters();
                        for (com.redhat.ceylon.compiler.typechecker.model.Parameter p : params) {
                            if (paramNames.length() > 0) paramNames.append(",");
                            paramNames.append(names.name(p));
                        }
                        out(paramNames.toString());
                        out("){");
                        for (com.redhat.ceylon.compiler.typechecker.model.Parameter p : params) {
                            if (p.isDefaulted()) {
                                out("if(", names.name(p), "===undefined)", names.name(p),"=");
                                qualify(specStmt, moval);
                                out(names.name(moval), "$defs$", p.getName(), "(", paramNames.toString(), ")");
                                endLine(true);
                            }
                        }
                        out("return ");
                        if (!isNaturalLiteral(specStmt.getSpecifierExpression().getExpression().getTerm())) {
                            specStmt.getSpecifierExpression().visit(this);
                        }
                        out("(", paramNames.toString(), ");}");
                        endLine(true);
                    } else {
                        // Specifier for a member attribute. This actually defines the
                        // member (e.g. in shortcut refinement syntax the attribute
                        // declaration itself can be omitted), so generate the attribute.
                        AttributeGenerator.generateAttributeGetter(null, moval,
                                specStmt.getSpecifierExpression(), null, this, directAccess);
                    }
                } else {
                    // Specifier for some other attribute, or for a method.
                    if (opts.isOptimize()
                            || (bmeDecl.isMember() && (bmeDecl instanceof Method))) {
                        qualify(specStmt, bmeDecl);
                    }
                    out(names.name(bmeDecl), "=");
                    if (dynblock > 0 && Util.isTypeUnknown(expr.getTypeModel())
                            && !Util.isTypeUnknown(((MethodOrValue) bmeDecl).getType())) {
                        TypeUtils.generateDynamicCheck(expr, ((MethodOrValue) bmeDecl).getType(), this, false,
                                expr.getTypeModel().getTypeArguments());
                    } else {
                        specStmt.getSpecifierExpression().visit(this);
                    }
                    out(";");
                }
            }
        }
        else if ((term instanceof ParameterizedExpression)
                && (specStmt.getSpecifierExpression() != null)) {
            final ParameterizedExpression paramExpr = (ParameterizedExpression)term;
            if (paramExpr.getPrimary() instanceof BaseMemberExpression) {
                // func(params) => expr;
                final BaseMemberExpression bme2 = (BaseMemberExpression) paramExpr.getPrimary();
                final Declaration bmeDecl = bme2.getDeclaration();
                if (bmeDecl.isMember()) {
                    qualify(specStmt, bmeDecl);
                } else {
                    out("var ");
                }
                out(names.name(bmeDecl), "=");
                FunctionHelper.singleExprFunction(paramExpr.getParameterLists(), expr,
                        bmeDecl instanceof Scope ? (Scope)bmeDecl : null, true, true, this);
                out(";");
            }
        }
    }
   
    private void addSpecifierToPrototype(final TypeDeclaration outer,
                final Tree.SpecifierStatement specStmt) {
        specifierStatement(outer, specStmt);
    }

    @Override
    public void visit(final AssignOp that) {
        String returnValue = null;
        StaticMemberOrTypeExpression lhsExpr = null;
       
        if (dynblock > 0 && Util.isTypeUnknown(that.getLeftTerm().getTypeModel())) {
            that.getLeftTerm().visit(this);
            out("=");
            int box = boxUnboxStart(that.getRightTerm(), that.getLeftTerm());
            that.getRightTerm().visit(this);
            if (box == 4) out("/*TODO: callable targs 6*/");
            boxUnboxEnd(box);
            return;
        }
        out("(");
        if (that.getLeftTerm() instanceof BaseMemberExpression) {
            BaseMemberExpression bme = (BaseMemberExpression) that.getLeftTerm();
            lhsExpr = bme;
            Declaration bmeDecl = bme.getDeclaration();
            boolean simpleSetter = hasSimpleGetterSetter(bmeDecl);
            if (!simpleSetter) {
                returnValue = memberAccess(bme, null);
            }

        } else if (that.getLeftTerm() instanceof QualifiedMemberExpression) {
            QualifiedMemberExpression qme = (QualifiedMemberExpression)that.getLeftTerm();
            lhsExpr = qme;
            boolean simpleSetter = hasSimpleGetterSetter(qme.getDeclaration());
            String lhsVar = null;
            if (!simpleSetter) {
                lhsVar = createRetainedTempVar();
                out(lhsVar, "=");
                super.visit(qme);
                out(",", lhsVar, ".");
                returnValue = memberAccess(qme, lhsVar);
            } else {
                super.visit(qme);
                out(".");
            }
        }
       
        BmeGenerator.generateMemberAccess(lhsExpr, new GenerateCallback() {
            @Override public void generateValue() {
                if (!isNaturalLiteral(that.getRightTerm())) {
                    int boxType = boxUnboxStart(that.getRightTerm(), that.getLeftTerm());
                    that.getRightTerm().visit(GenerateJsVisitor.this);
                    if (boxType == 4) out("/*TODO: callable targs 7*/");
                    boxUnboxEnd(boxType);
                }
            }
        }, null, this);
       
        if (returnValue != null) { out(",", returnValue); }
        out(")");
    }

    /** Outputs the module name for the specified declaration. Returns true if something was output. */
    boolean qualify(final Node that, final Declaration d) {
        String path = qualifiedPath(that, d);
        if (path.length() > 0) {
            out(path, ".");
        }
        return path.length() > 0;
    }

    private String qualifiedPath(final Node that, final Declaration d) {
        return qualifiedPath(that, d, false);
    }

    String qualifiedPath(final Node that, final Declaration d, final boolean inProto) {
        boolean isMember = d.isClassOrInterfaceMember();
        if (!isMember && isImported(that == null ? null : that.getUnit().getPackage(), d)) {
            return names.moduleAlias(d.getUnit().getPackage().getModule());
        }
        else if (opts.isOptimize() && !inProto) {
            if (isMember && !(d.isParameter() && !d.isCaptured())) {
                TypeDeclaration id = that.getScope().getInheritingDeclaration(d);
                if (id == null) {
                    //a local declaration of some kind,
                    //perhaps in an outer scope
                    id = (TypeDeclaration) d.getContainer();
                }
                String path = "";
                Scope scope = that.getScope();
                if ((scope != null) && ((that instanceof ClassDeclaration)
                                        || (that instanceof InterfaceDeclaration))) {
                    // class/interface aliases have no own "this"
                    scope = scope.getContainer();
                }
                while (scope != null) {
                    if (scope instanceof TypeDeclaration) {
                        if (path.length() > 0) {
                            path += ".outer$";
                        } else {
                            path += names.self((TypeDeclaration) scope);
                        }
                    } else {
                        path = "";
                    }
                    if (scope == id) {
                        break;
                    }
                    scope = scope.getContainer();
                }
                return path;
            }
        }
        else {
            if (d != null && isMember && (d.isShared() || inProto || (!d.isParameter() && defineAsProperty(d)))) {
                TypeDeclaration id = d instanceof TypeAlias ? (TypeDeclaration)d : that.getScope().getInheritingDeclaration(d);
                if (id==null) {
                    //a shared local declaration
                    return names.self((TypeDeclaration)d.getContainer());
                }
                else {
                    //an inherited declaration that might be
                    //inherited by an outer scope
                    return names.self(id);
                }
            }
        }
        return "";
    }

    /** Tells whether a declaration is in the specified package. */
    boolean isImported(final Package p2, final Declaration d) {
        if (d == null) {
            return false;
        }
        Package p1 = d.getUnit().getPackage();
        if (p2 == null)return p1 != null;
        if (p1.getModule()== null)return p2.getModule()!=null;
        return !p1.getModule().equals(p2.getModule());
    }

    @Override
    public void visit(final Tree.ExecutableStatement that) {
        super.visit(that);
        endLine(true);
    }

    /** Creates a new temporary variable which can be used immediately, even
     * inside an expression. The declaration for that temporary variable will be
     * emitted after the current Ceylon statement has been completely processed.
     * The resulting code is valid because JavaScript variables may be used before
     * they are declared. */
    String createRetainedTempVar() {
        String varName = names.createTempVariable();
        retainedVars.add(varName);
        return varName;
    }

    @Override
    public void visit(final Tree.Return that) {
        out("return");
        if (that.getExpression() == null) {
            endLine(true);
            return;
        }
        out(" ");
        if (dynblock > 0 && Util.isTypeUnknown(that.getExpression().getTypeModel())) {
            Scope cont = Util.getRealScope(that.getScope()).getScope();
            if (cont instanceof Declaration) {
                final ProducedType dectype = ((Declaration)cont).getReference().getType();
                if (!Util.isTypeUnknown(dectype)) {
                    TypeUtils.generateDynamicCheck(that.getExpression(), dectype, this, false,
                            that.getExpression().getTypeModel().getTypeArguments());
                    endLine(true);
                    return;
                }
            }
        }
        if (isNaturalLiteral(that.getExpression().getTerm())) {
            out(";");
        } else {
            super.visit(that);
        }
    }

    @Override
    public void visit(final Tree.AnnotationList that) {}

    boolean outerSelf(Declaration d) {
        if (d.isToplevel()) {
            out("ex$");
            return true;
        }
        else if (d.isClassOrInterfaceMember()) {
            out(names.self((TypeDeclaration)d.getContainer()));
            return true;
        }
        return false;
    }

    @Override
    public void visit(final Tree.SumOp that) {
        Operators.simpleBinaryOp(that, null, ".plus(", ")", this);
    }

    @Override
    public void visit(final Tree.ScaleOp that) {
        final String lhs = names.createTempVariable();
        Operators.simpleBinaryOp(that, "function(){var "+lhs+"=", ";return ", ".scale("+lhs+");}()", this);
    }

    @Override
    public void visit(final Tree.DifferenceOp that) {
        Operators.simpleBinaryOp(that, null, ".minus(", ")", this);
    }

    @Override
    public void visit(final Tree.ProductOp that) {
        Operators.simpleBinaryOp(that, null, ".times(", ")", this);
    }

    @Override
    public void visit(final Tree.QuotientOp that) {
        Operators.simpleBinaryOp(that, null, ".divided(", ")", this);
    }

    @Override public void visit(final Tree.RemainderOp that) {
        Operators.simpleBinaryOp(that, null, ".remainder(", ")", this);
    }

    @Override public void visit(final Tree.PowerOp that) {
        Operators.simpleBinaryOp(that, null, ".power(", ")", this);
    }

    @Override public void visit(final Tree.AddAssignOp that) {
        assignOp(that, "plus", null);
    }

    @Override public void visit(final Tree.SubtractAssignOp that) {
        assignOp(that, "minus", null);
    }

    @Override public void visit(final Tree.MultiplyAssignOp that) {
        assignOp(that, "times", null);
    }

    @Override public void visit(final Tree.DivideAssignOp that) {
        assignOp(that, "divided", null);
    }

    @Override public void visit(final Tree.RemainderAssignOp that) {
        assignOp(that, "remainder", null);
    }

    public void visit(Tree.ComplementAssignOp that) {
        assignOp(that, "complement", TypeUtils.mapTypeArgument(that, "complement", "Element", "Other"));
    }
    public void visit(Tree.UnionAssignOp that) {
        assignOp(that, "union", TypeUtils.mapTypeArgument(that, "union", "Element", "Other"));
    }
    public void visit(Tree.IntersectAssignOp that) {
        assignOp(that, "intersection", TypeUtils.mapTypeArgument(that, "intersection", "Element", "Other"));
    }

    public void visit(Tree.AndAssignOp that) {
        assignOp(that, "&&", null);
    }
    public void visit(Tree.OrAssignOp that) {
        assignOp(that, "||", null);
    }

    private void assignOp(final Tree.AssignmentOp that, final String functionName, final Map<TypeParameter, ProducedType> targs) {
        Term lhs = that.getLeftTerm();
        final boolean isNative="||".equals(functionName)||"&&".equals(functionName);
        if (lhs instanceof BaseMemberExpression) {
            BaseMemberExpression lhsBME = (BaseMemberExpression) lhs;
            Declaration lhsDecl = lhsBME.getDeclaration();

            final String getLHS = memberAccess(lhsBME, null);
            out("(");
            BmeGenerator.generateMemberAccess(lhsBME, new GenerateCallback() {
                @Override public void generateValue() {
                    if (isNative) {
                        out(getLHS, functionName);
                    } else {
                        out(getLHS, ".", functionName, "(");
                    }
                    if (!isNaturalLiteral(that.getRightTerm())) {
                        that.getRightTerm().visit(GenerateJsVisitor.this);
                    }
                    if (!isNative) {
                        if (targs != null) {
                            out(",");
                            TypeUtils.printTypeArguments(that, targs, GenerateJsVisitor.this, false, null);
                        }
                        out(")");
                    }
                }
            }, null, this);
            if (!hasSimpleGetterSetter(lhsDecl)) { out(",", getLHS); }
            out(")");

        } else if (lhs instanceof QualifiedMemberExpression) {
            QualifiedMemberExpression lhsQME = (QualifiedMemberExpression) lhs;
            if (isNative(lhsQME)) {
                // ($1.foo = Box($1.foo).operator($2))
                final String tmp = names.createTempVariable();
                final String dec = isInDynamicBlock() && lhsQME.getDeclaration() == null ?
                        lhsQME.getIdentifier().getText() : lhsQME.getDeclaration().getName();
                out("(", tmp, "=");
                lhsQME.getPrimary().visit(this);
                out(",", tmp, ".", dec, "=");
                int boxType = boxStart(lhsQME);
                out(tmp, ".", dec);
                if (boxType == 4) out("/*TODO: callable targs 8*/");
                boxUnboxEnd(boxType);
                out(".", functionName, "(");
                if (!isNaturalLiteral(that.getRightTerm())) {
                    that.getRightTerm().visit(this);
                }
                out("))");
               
            } else {
                final String lhsPrimaryVar = createRetainedTempVar();
                final String getLHS = memberAccess(lhsQME, lhsPrimaryVar);
                out("(", lhsPrimaryVar, "=");
                lhsQME.getPrimary().visit(this);
                out(",");
                BmeGenerator.generateMemberAccess(lhsQME, new GenerateCallback() {
                    @Override public void generateValue() {
                        out(getLHS, ".", functionName, "(");
                        if (!isNaturalLiteral(that.getRightTerm())) {
                            that.getRightTerm().visit(GenerateJsVisitor.this);
                        }
                        out(")");
                    }
                }, lhsPrimaryVar, this);
               
                if (!hasSimpleGetterSetter(lhsQME.getDeclaration())) {
                    out(",", getLHS);
                }
                out(")");
            }
        }
    }

    @Override public void visit(final Tree.NegativeOp that) {
        if (that.getTerm() instanceof Tree.NaturalLiteral) {
            long t = parseNaturalLiteral((Tree.NaturalLiteral)that.getTerm());
            out("(");
            if (t > 0) {
                out("-");
            }
            out(Long.toString(t));
            out(")");
            if (t == 0) {
                //Force -0
                out(".negated");
            }
            return;
        }
        final TypeDeclaration d = that.getTerm().getTypeModel().getDeclaration();
        final boolean isint = d.inherits(that.getUnit().getIntegerDeclaration());
        Operators.unaryOp(that, isint?"(-":null, isint?")":".negated", this);
    }

    @Override public void visit(final Tree.PositiveOp that) {
        final TypeDeclaration d = that.getTerm().getTypeModel().getDeclaration();
        final boolean nat = d.inherits(that.getUnit().getIntegerDeclaration());
        //TODO if it's positive we leave it as is?
        Operators.unaryOp(that, nat?"(+":null, nat?")":null, this);
    }

    @Override public void visit(final Tree.EqualOp that) {
        if (dynblock > 0 && Util.isTypeUnknown(that.getLeftTerm().getTypeModel())) {
            //Try to use equals() if it exists
            String ltmp = names.createTempVariable();
            String rtmp = names.createTempVariable();
            out("(", ltmp, "=");
            box(that.getLeftTerm());
            out(",", rtmp, "=");
            box(that.getRightTerm());
            out(",(", ltmp, ".equals&&", ltmp, ".equals(", rtmp, "))||", ltmp, "===", rtmp, ")");
        } else {
            final boolean usenat = canUseNativeComparator(that.getLeftTerm(), that.getRightTerm());
            Operators.simpleBinaryOp(that, usenat?"((":null, usenat?").valueOf()==(":".equals(",
                    usenat?").valueOf())":")", this);
        }
    }

    @Override public void visit(final Tree.NotEqualOp that) {
        if (dynblock > 0 && Util.isTypeUnknown(that.getLeftTerm().getTypeModel())) {
            //Try to use equals() if it exists
            String ltmp = names.createTempVariable();
            String rtmp = names.createTempVariable();
            out("(", ltmp, "=");
            box(that.getLeftTerm());
            out(",", rtmp, "=");
            box(that.getRightTerm());
            out(",(", ltmp, ".equals&&!", ltmp, ".equals(", rtmp, "))||", ltmp, "!==", rtmp, ")");
        } else {
            final boolean usenat = canUseNativeComparator(that.getLeftTerm(), that.getRightTerm());
            Operators.simpleBinaryOp(that, usenat?"!(":"(!", usenat?"==":".equals(", usenat?")":"))", this);
        }
    }

    @Override public void visit(final Tree.NotOp that) {
        final Term t = that.getTerm();
        final boolean omitParens = t instanceof BaseMemberExpression
                || t instanceof QualifiedMemberExpression
                || t instanceof IsOp || t instanceof Exists || t instanceof IdenticalOp
                || t instanceof InOp || t instanceof Nonempty
                || (t instanceof InvocationExpression && ((InvocationExpression)t).getNamedArgumentList() == null);
        if (omitParens) {
            Operators.unaryOp(that, "!", null, this);
        } else {
            Operators.unaryOp(that, "(!", ")", this);
        }
    }

    @Override public void visit(final Tree.IdenticalOp that) {
        Operators.simpleBinaryOp(that, "(", "===", ")", this);
    }

    @Override public void visit(final Tree.CompareOp that) {
        Operators.simpleBinaryOp(that, null, ".compare(", ")", this);
    }

    /** Returns true if both Terms' types is either Integer or Boolean. */
    private boolean canUseNativeComparator(final Tree.Term left, final Tree.Term right) {
        if (left == null || right == null || left.getTypeModel() == null || right.getTypeModel() == null) {
            return false;
        }
        final ProducedType lt = left.getTypeModel();
        final ProducedType rt = right.getTypeModel();
        final TypeDeclaration intdecl = left.getUnit().getIntegerDeclaration();
        final TypeDeclaration booldecl = left.getUnit().getBooleanDeclaration();
        return (intdecl.equals(lt.getDeclaration()) && intdecl.equals(rt.getDeclaration()))
                || (booldecl.equals(lt.getDeclaration()) && booldecl.equals(rt.getDeclaration()));
    }

    @Override public void visit(final Tree.SmallerOp that) {
        if (dynblock > 0 && Util.isTypeUnknown(that.getLeftTerm().getTypeModel())) {
            //Try to use compare() if it exists
            String ltmp = names.createTempVariable();
            String rtmp = names.createTempVariable();
            out("(", ltmp, "=");
            box(that.getLeftTerm());
            out(",", rtmp, "=");
            box(that.getRightTerm());
            out(",(", ltmp, ".compare&&", ltmp, ".compare(", rtmp, ").equals(",
                    getClAlias(), "getSmaller()))||", ltmp, "<", rtmp, ")");
        } else {
            final boolean usenat = canUseNativeComparator(that.getLeftTerm(), that.getRightTerm());
            if (usenat) {
                Operators.simpleBinaryOp(that, "(", "<", ")", this);
            } else {
                Operators.simpleBinaryOp(that, null, ".compare(", ")", this);
                out(".equals(", getClAlias(), "getSmaller())");
            }
        }
    }

    @Override public void visit(final Tree.LargerOp that) {
        if (dynblock > 0 && Util.isTypeUnknown(that.getLeftTerm().getTypeModel())) {
            //Try to use compare() if it exists
            String ltmp = names.createTempVariable();
            String rtmp = names.createTempVariable();
            out("(", ltmp, "=");
            box(that.getLeftTerm());
            out(",", rtmp, "=");
            box(that.getRightTerm());
            out(",(", ltmp, ".compare&&", ltmp, ".compare(", rtmp, ").equals(",
                    getClAlias(), "getLarger()))||", ltmp, ">", rtmp, ")");
        } else {
            final boolean usenat = canUseNativeComparator(that.getLeftTerm(), that.getRightTerm());
            if (usenat) {
                Operators.simpleBinaryOp(that, "(", ">", ")", this);
            } else {
                Operators.simpleBinaryOp(that, null, ".compare(", ")", this);
                out(".equals(", getClAlias(), "getLarger())");
            }
        }
    }

    @Override public void visit(final Tree.SmallAsOp that) {
        if (dynblock > 0 && Util.isTypeUnknown(that.getLeftTerm().getTypeModel())) {
            //Try to use compare() if it exists
            String ltmp = names.createTempVariable();
            String rtmp = names.createTempVariable();
            out("(", ltmp, "=");
            box(that.getLeftTerm());
            out(",", rtmp, "=");
            box(that.getRightTerm());
            out(",(", ltmp, ".compare&&", ltmp, ".compare(", rtmp, ")!==",
                    getClAlias(), "getLarger())||", ltmp, "<=", rtmp, ")");
        } else {
            final boolean usenat = canUseNativeComparator(that.getLeftTerm(), that.getRightTerm());
            if (usenat) {
                Operators.simpleBinaryOp(that, "(", "<=", ")", this);
            } else {
                out("(");
                Operators.simpleBinaryOp(that, null, ".compare(", ")", this);
                out("!==", getClAlias(), "getLarger()");
                out(")");
            }
        }
    }

    @Override public void visit(final Tree.LargeAsOp that) {
        if (dynblock > 0 && Util.isTypeUnknown(that.getLeftTerm().getTypeModel())) {
            //Try to use compare() if it exists
            String ltmp = names.createTempVariable();
            String rtmp = names.createTempVariable();
            out("(", ltmp, "=");
            box(that.getLeftTerm());
            out(",", rtmp, "=");
            box(that.getRightTerm());
            out(",(", ltmp, ".compare&&", ltmp, ".compare(", rtmp, ")!==",
                    getClAlias(), "getSmaller())||", ltmp, ">=", rtmp, ")");
        } else {
            final boolean usenat = canUseNativeComparator(that.getLeftTerm(), that.getRightTerm());
            if (usenat) {
                Operators.simpleBinaryOp(that, "(", ">=", ")", this);
            } else {
                out("(");
                Operators.simpleBinaryOp(that, null, ".compare(", ")", this);
                out("!==", getClAlias(), "getSmaller()");
                out(")");
            }
        }
    }
    public void visit(final Tree.WithinOp that) {
        final String ttmp = names.createTempVariable();
        out("(", ttmp, "=");
        box(that.getTerm());
        out(",");
        if (dynblock > 0 && Util.isTypeUnknown(that.getTerm().getTypeModel())) {
            final String tmpl = names.createTempVariable();
            final String tmpu = names.createTempVariable();
            out(tmpl, "=");
            box(that.getLowerBound().getTerm());
            out(",", tmpu, "=");
            box(that.getUpperBound().getTerm());
            out(",((", ttmp, ".compare&&",ttmp,".compare(", tmpl);
            if (that.getLowerBound() instanceof Tree.OpenBound) {
                out(")===", getClAlias(), "getLarger())||", ttmp, ">", tmpl, ")");
            } else {
                out(")!==", getClAlias(), "getSmaller())||", ttmp, ">=", tmpl, ")");
            }
            out("&&((", ttmp, ".compare&&",ttmp,".compare(", tmpu);
            if (that.getUpperBound() instanceof Tree.OpenBound) {
                out(")===", getClAlias(), "getSmaller())||", ttmp, "<", tmpu, ")");
            } else {
                out(")!==", getClAlias(), "getLarger())||", ttmp, "<=", tmpu, ")");
            }
        } else {
            out(ttmp, ".compare(");
            box(that.getLowerBound().getTerm());
            if (that.getLowerBound() instanceof Tree.OpenBound) {
                out(")===", getClAlias(), "getLarger()");
            } else {
                out(")!==", getClAlias(), "getSmaller()");
            }
            out("&&");
            out(ttmp, ".compare(");
            box(that.getUpperBound().getTerm());
            if (that.getUpperBound() instanceof Tree.OpenBound) {
                out(")===", getClAlias(), "getSmaller()");
            } else {
                out(")!==", getClAlias(), "getLarger()");
            }
        }
        out(")");
    }

   @Override public void visit(final Tree.AndOp that) {
       Operators.simpleBinaryOp(that, "(", "&&", ")", this);
   }

   @Override public void visit(final Tree.OrOp that) {
       Operators.simpleBinaryOp(that, "(", "||", ")", this);
   }

   @Override public void visit(final Tree.EntryOp that) {
       out(getClAlias(), "Entry(");
       Operators.genericBinaryOp(that, ",", that.getTypeModel().getTypeArguments(),
               that.getTypeModel().getVarianceOverrides(), this);
   }

   @Override public void visit(final Tree.RangeOp that) {
       out(getClAlias(), "span(");
       that.getLeftTerm().visit(this);
       out(",");
       that.getRightTerm().visit(this);
       out(",{Element$span:");
       TypeUtils.typeNameOrList(that,
               Util.unionType(that.getLeftTerm().getTypeModel(), that.getRightTerm().getTypeModel(), that.getUnit()),
               this, false);
       out("})");
   }

   @Override
   public void visit(final Tree.SegmentOp that) {
       final Tree.Term left  = that.getLeftTerm();
       final Tree.Term right = that.getRightTerm();
       out(getClAlias(), "measure(");
       left.visit(this);
       out(",");
       right.visit(this);
       out(",{Element$measure:");
       TypeUtils.typeNameOrList(that,
               Util.unionType(left.getTypeModel(), right.getTypeModel(), that.getUnit()),
               this, false);
       out("})");
   }

   @Override public void visit(final Tree.ThenOp that) {
       Operators.simpleBinaryOp(that, "(", "?", ":null)", this);
   }

   @Override public void visit(final Tree.Element that) {
       out(".$_get(");
       if (!isNaturalLiteral(that.getExpression().getTerm())) {
           that.getExpression().visit(this);
       }
       out(")");
   }

   @Override public void visit(final Tree.DefaultOp that) {
       String lhsVar = createRetainedTempVar();
       out("(", lhsVar, "=");
       box(that.getLeftTerm());
       out(",", getClAlias(), "nn$(", lhsVar, ")?", lhsVar, ":");
       box(that.getRightTerm());
       out(")");
   }

   @Override public void visit(final Tree.IncrementOp that) {
       Operators.prefixIncrementOrDecrement(that.getTerm(), "successor", this);
   }

   @Override public void visit(final Tree.DecrementOp that) {
       Operators.prefixIncrementOrDecrement(that.getTerm(), "predecessor", this);
   }

   boolean hasSimpleGetterSetter(Declaration decl) {
       return (dynblock > 0 && TypeUtils.isUnknown(decl)) ||
               !((decl instanceof Value && ((Value)decl).isTransient()) || (decl instanceof Setter) || decl.isFormal());
   }

   @Override public void visit(final Tree.PostfixIncrementOp that) {
       Operators.postfixIncrementOrDecrement(that.getTerm(), "successor", this);
   }

   @Override public void visit(final Tree.PostfixDecrementOp that) {
       Operators.postfixIncrementOrDecrement(that.getTerm(), "predecessor", this);
   }

    @Override
    public void visit(final Tree.UnionOp that) {
        Operators.genericBinaryOp(that, ".union(",
                TypeUtils.mapTypeArgument(that, "union", "Element", "Other"),
                that.getTypeModel().getVarianceOverrides(), this);
    }

    @Override
    public void visit(final Tree.IntersectionOp that) {
        Operators.genericBinaryOp(that, ".intersection(",
                TypeUtils.mapTypeArgument(that, "intersection", "Element", "Other"),
                that.getTypeModel().getVarianceOverrides(), this);
    }

    @Override
    public void visit(final Tree.ComplementOp that) {
        Operators.genericBinaryOp(that, ".complement(",
                TypeUtils.mapTypeArgument(that, "complement", "Element", "Other"),
                that.getTypeModel().getVarianceOverrides(), this);
    }

   @Override public void visit(final Tree.Exists that) {
       Operators.unaryOp(that, getClAlias()+"nn$(", ")", this);
   }
   @Override public void visit(final Tree.Nonempty that) {
       Operators.unaryOp(that, getClAlias()+"ne$(", ")", this);
   }

   //Don't know if we'll ever see this...
   @Override public void visit(final Tree.ConditionList that) {
       System.out.println("ZOMG condition list in the wild! " + that.getLocation()
               + " of " + that.getUnit().getFilename());
       super.visit(that);
   }

   @Override public void visit(final Tree.BooleanCondition that) {
       int boxType = boxStart(that.getExpression().getTerm());
       super.visit(that);
       if (boxType == 4) out("/*TODO: callable targs 10*/");
       boxUnboxEnd(boxType);
   }

   @Override public void visit(final Tree.IfStatement that) {
       conds.generateIf(that);
   }

   @Override public void visit(final Tree.WhileStatement that) {
       conds.generateWhile(that);
   }

    /** Generates js code to check if a term is of a certain type. We solve this in JS by
     * checking against all types that Type satisfies (in the case of union types, matching any
     * type will do, and in case of intersection types, all types must be matched).
     * @param term The term that is to be checked against a type
     * @param termString (optional) a string to be used as the term to be checked
     * @param type The type to check against
     * @param tmpvar (optional) a variable to which the term is assigned
     * @param negate If true, negates the generated condition
     */
    void generateIsOfType(Node term, String termString, Type type, String tmpvar, final boolean negate) {
        if (negate) {
            out("!");
        }
        out(getClAlias(), "is$(");
        if (term instanceof Term) {
            conds.specialConditionRHS((Term)term, tmpvar);
        } else {
            conds.specialConditionRHS(termString, tmpvar);
        }
        out(",");
        if (type!=null) {
            TypeUtils.typeNameOrList(term, type.getTypeModel(), this, false);
        }
        out(")");
    }

    @Override
    public void visit(final Tree.IsOp that) {
        generateIsOfType(that.getTerm(), null, that.getType(), null, false);
    }

    @Override public void visit(final Tree.Break that) {
        if (continues.isEmpty()) {
            out("break;");
        } else {
            Continuation top=continues.peek();
            if (that.getScope()==top.getScope()) {
                top.useBreak();
                out(top.getBreakName(), "=true; return;");
            } else {
                out("break;");
            }
        }
    }
    @Override public void visit(final Tree.Continue that) {
        if (continues.isEmpty()) {
            out("continue;");
        } else {
            Continuation top=continues.peek();
            if (that.getScope()==top.getScope()) {
                top.useContinue();
                out(top.getContinueName(), "=true; return;");
            } else {
                out("continue;");
            }
        }
    }

    @Override public void visit(final Tree.ForStatement that) {
        if (errVisitor.hasErrors(that))return;
        new ForGenerator(this, directAccess).generate(that);
    }

    public void visit(final Tree.InOp that) {
        box(that.getRightTerm());
        out(".contains(");
        if (!isNaturalLiteral(that.getLeftTerm())) {
            box(that.getLeftTerm());
        }
        out(")");
    }

    @Override public void visit(final Tree.TryCatchStatement that) {
        if (errVisitor.hasErrors(that))return;
        new TryCatchGenerator(this, directAccess).generate(that);
    }

    @Override public void visit(final Tree.Throw that) {
        out("throw ", getClAlias(), "wrapexc(");
        if (that.getExpression() == null) {
            out(getClAlias(), "Exception()");
        } else {
            that.getExpression().visit(this);
        }
        that.getUnit().getFullPath();
        out(",'", that.getLocation(), "','", that.getUnit().getRelativePath(), "');");
    }

    private void visitIndex(final Tree.IndexExpression that) {
        that.getPrimary().visit(this);
        ElementOrRange eor = that.getElementOrRange();
        if (eor instanceof Element) {
            final Tree.Expression _elemexpr = ((Tree.Element)eor).getExpression();
            final String _end;
            if (Util.isTypeUnknown(that.getPrimary().getTypeModel()) && dynblock > 0) {
                out("[");
                _end = "]";
            } else {
                out(".$_get(");
                _end = ")";
            }
            if (!isNaturalLiteral(_elemexpr.getTerm())) {
                _elemexpr.visit(this);
            }
            out(_end);
        } else {//range, or spread?
            ElementRange er = (ElementRange)eor;
            Expression sexpr = er.getLength();
            if (sexpr == null) {
                if (er.getLowerBound() == null) {
                    out(".spanTo(");
                } else if (er.getUpperBound() == null) {
                    out(".spanFrom(");
                } else {
                    out(".span(");
                }
            } else {
                out(".measure(");
            }
            if (er.getLowerBound() != null) {
                if (!isNaturalLiteral(er.getLowerBound().getTerm())) {
                    er.getLowerBound().visit(this);
                }
                if (er.getUpperBound() != null || sexpr != null) {
                    out(",");
                }
            }
            if (er.getUpperBound() != null) {
                if (!isNaturalLiteral(er.getUpperBound().getTerm())) {
                    er.getUpperBound().visit(this);
                }
            } else if (sexpr != null) {
                sexpr.visit(this);
            }
            out(")");
        }
    }

    public void visit(final Tree.IndexExpression that) {
        visitIndex(that);
    }

    /** Generates code for a case clause, as part of a switch statement. Each case
     * is rendered as an if. */
    private void caseClause(final Tree.CaseClause cc, String expvar, final Tree.Term switchTerm) {
        out("if(");
        final CaseItem item = cc.getCaseItem();
        if (item instanceof IsCase) {
            IsCase isCaseItem = (IsCase) item;
            generateIsOfType(switchTerm, expvar, isCaseItem.getType(), null, false);
            Variable caseVar = isCaseItem.getVariable();
            if (caseVar != null) {
                directAccess.add(caseVar.getDeclarationModel());
                names.forceName(caseVar.getDeclarationModel(), expvar);
            }
        } else if (item instanceof SatisfiesCase) {
            item.addError("case(satisfies) not yet supported");
            out("true");
        } else if (item instanceof MatchCase) {
            boolean first = true;
            for (Expression exp : ((MatchCase)item).getExpressionList().getExpressions()) {
                if (!first) out(" || ");
                if (exp.getTerm() instanceof Tree.StringLiteral || exp.getTerm() instanceof Tree.NaturalLiteral
                        || switchTerm.getTypeModel().isUnknown()) {
                    out(expvar, "===");
                    if (!isNaturalLiteral(exp.getTerm())) {
                        exp.visit(this);
                    }
                } else if (exp.getTerm() instanceof Tree.Literal) {
                    if (switchTerm.getUnit().isOptionalType(switchTerm.getTypeModel())) {
                        out(expvar,"!==null&&");
                    }
                    out(expvar, ".equals(");
                    exp.visit(this);
                    out(")");
                } else {
                    out(expvar, "===");
                    exp.visit(this);
                }
                first = false;
            }
        } else {
            cc.addUnexpectedError("support for case of type " + cc.getClass().getSimpleName() + " not yet implemented");
        }
        out(") ");
        encloseBlockInFunction(cc.getBlock());
    }

    @Override
    public void visit(final Tree.SwitchStatement that) {
        if (opts.isComment() && !opts.isMinify()) {
            out("//Switch statement at ", that.getUnit().getFilename(), " (", that.getLocation(), ")");
            endLine();
        }
        //Put the expression in a tmp var
        final String expvar = names.createTempVariable();
        out("var ", expvar, "=");
        final Expression expr = that.getSwitchClause().getExpression();
        expr.visit(this);
        endLine(true);
        //For each case, do an if
        boolean first = true;
        for (CaseClause cc : that.getSwitchCaseList().getCaseClauses()) {
            if (!first) out("else ");
            caseClause(cc, expvar, expr.getTerm());
            first = false;
        }
        if (that.getSwitchCaseList().getElseClause() == null) {
            if (dynblock > 0 && expr.getTypeModel().getDeclaration() instanceof UnknownType) {
                out("else throw ", getClAlias(), "Exception('Ceylon switch over unknown type does not cover all cases')");
            }
        } else {
            out("else ");
            that.getSwitchCaseList().getElseClause().visit(this);
        }
        if (opts.isComment() && !opts.isMinify()) {
            out("//End switch statement at ", that.getUnit().getFilename(), " (", that.getLocation(), ")");
            endLine();
        }
    }

    /** Generates the code for an anonymous function defined inside an argument list. */
    @Override
    public void visit(final Tree.FunctionArgument that) {
        if (errVisitor.hasErrors(that))return;
        FunctionHelper.functionArgument(that, this);
    }

    /** Generates the code for a function in a named argument list. */
    @Override
    public void visit(final Tree.MethodArgument that) {
        if (errVisitor.hasErrors(that))return;
        FunctionHelper.methodArgument(that, this);
    }

    /** Encloses the block in a function, IF NEEDED. */
    void encloseBlockInFunction(final Tree.Block block) {
        final boolean wrap=encloser.encloseBlock(block);
        if (wrap) {
            beginBlock();
            Continuation c = new Continuation(block.getScope(), names);
            continues.push(c);
            out("var ", c.getContinueName(), "=false"); endLine(true);
            out("var ", c.getBreakName(), "=false"); endLine(true);
            out("var ", c.getReturnName(), "=(function()");
        }
        block.visit(this);
        if (wrap) {
            Continuation c = continues.pop();
            out("());if(", c.getReturnName(), "!==undefined){return ", c.getReturnName(), ";}");
            if (c.isContinued()) {
                out("else if(", c.getContinueName(),"===true){continue;}");
            }
            if (c.isBreaked()) {
                out("else if(", c.getBreakName(),"===true){break;}");
            }
            endBlockNewLine();
        }
    }

    private static class Continuation {
        private final String cvar;
        private final String rvar;
        private final String bvar;
        private final Scope scope;
        private boolean cused, bused;
        public Continuation(Scope scope, JsIdentifierNames names) {
            this.scope=scope;
            cvar = names.createTempVariable();
            rvar = names.createTempVariable();
            bvar = names.createTempVariable();
        }
        public Scope getScope() { return scope; }
        public String getContinueName() { return cvar; }
        public String getBreakName() { return bvar; }
        public String getReturnName() { return rvar; }
        public void useContinue() { cused = true; }
        public void useBreak() { bused=true; }
        public boolean isContinued() { return cused; }
        public boolean isBreaked() { return bused; } //"isBroken" sounds really really bad in this case
    }

    /** This interface is used inside type initialization method. */
    static interface PrototypeInitCallback {
        void addToPrototypeCallback();
    }

    @Override
    public void visit(final Tree.Tuple that) {
        SequenceGenerator.tuple(that, this);
    }

    @Override
    public void visit(final Tree.Assertion that) {
        if (opts.isComment() && !opts.isMinify()) {
            out("//assert");
            location(that);
            endLine();
        }
        String custom = "Assertion failed";
        //Scan for a "doc" annotation with custom message
        if (that.getAnnotationList() != null && that.getAnnotationList().getAnonymousAnnotation() != null) {
            custom = that.getAnnotationList().getAnonymousAnnotation().getStringLiteral().getText();
        } else {
            for (Annotation ann : that.getAnnotationList().getAnnotations()) {
                BaseMemberExpression bme = (BaseMemberExpression)ann.getPrimary();
                if ("doc".equals(bme.getDeclaration().getName())) {
                    custom = ((Tree.ListedArgument)ann.getPositionalArgumentList().getPositionalArguments().get(0))
                            .getExpression().getTerm().getText();
                }
            }
        }
        StringBuilder sb = new StringBuilder(custom).append(": '");
        for (int i = that.getConditionList().getToken().getTokenIndex()+1;
                i < that.getConditionList().getEndToken().getTokenIndex(); i++) {
            sb.append(tokens.get(i).getText());
        }
        sb.append("' at ").append(that.getUnit().getFilename()).append(" (").append(
                that.getConditionList().getLocation()).append(")");
        conds.specialConditionsAndBlock(that.getConditionList(), null, getClAlias()+"asrt$(");
        //escape
        out(",\"", escapeStringLiteral(sb.toString()), "\",'",that.getLocation(), "','",
                that.getUnit().getFilename(), "');");
        endLine();
    }

    @Override
    public void visit(Tree.DynamicStatement that) {
        dynblock++;
        if (dynblock == 1 && !opts.isMinify()) {
            out("/*BEG dynblock*/");
            endLine();
        }
        for (Tree.Statement stmt : that.getDynamicClause().getBlock().getStatements()) {
            stmt.visit(this);
        }
        if (dynblock == 1 && !opts.isMinify()) {
            out("/*END dynblock*/");
            endLine();
        }
        dynblock--;
    }

    boolean isInDynamicBlock() {
        return dynblock > 0;
    }

    @Override
    public void visit(final Tree.TypeLiteral that) {
        //Can be an alias, class, interface or type parameter
        if (that.getWantsDeclaration()) {
            MetamodelHelper.generateOpenType(that, that.getDeclaration(), this);
        } else {
            MetamodelHelper.generateClosedTypeLiteral(that, this);
        }
    }

    @Override
    public void visit(final Tree.MemberLiteral that) {
        if (that.getWantsDeclaration()) {
            MetamodelHelper.generateOpenType(that, that.getDeclaration(), this);
        } else {
            MetamodelHelper.generateMemberLiteral(that, this);
        }
    }

    @Override
    public void visit(final Tree.PackageLiteral that) {
        com.redhat.ceylon.compiler.typechecker.model.Package pkg =
                (com.redhat.ceylon.compiler.typechecker.model.Package)that.getImportPath().getModel();
        MetamodelHelper.findModule(pkg.getModule(), this);
        out(".findPackage('", pkg.getNameAsString(), "')");
    }

    @Override
    public void visit(Tree.ModuleLiteral that) {
        Module m = (Module)that.getImportPath().getModel();
        MetamodelHelper.findModule(m, this);
    }

    /** Call internal function "throwexc" with the specified message and source location. */
    void generateThrow(String exceptionClass, String msg, Node node) {
        out(getClAlias(), "throwexc(", exceptionClass==null ? getClAlias() + "Exception":exceptionClass, "(");
        out("\"", escapeStringLiteral(msg), "\"),'", node.getLocation(), "','",
                node.getUnit().getFilename(), "')");
    }

    @Override public void visit(Tree.CompilerAnnotation that) {
        //just ignore this
    }

    /** Outputs the initial part of an attribute definition. */
    void defineAttribute(final String owner, final String name) {
        out(getClAlias(), "atr$(", owner, ",'", name, "',function()");
    }

    public int getExitCode() {
        return exitCode;
    }

    @Override public void visit(Tree.ListedArgument that) {
        if (!isNaturalLiteral(that.getExpression().getTerm())) {
            super.visit(that);
        }
    }

    boolean isNaturalLiteral(Tree.Term that) {
        if (that instanceof Tree.NaturalLiteral) {
            out(Long.toString(parseNaturalLiteral((Tree.NaturalLiteral)that)));
            return true;
        }
        return false;
    }
}
TOP

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

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.