001    /*
002     * Copyright 2010-2013 JetBrains s.r.o.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    package org.jetbrains.k2js.facade;
018    
019    import com.google.common.base.Predicates;
020    import com.google.dart.compiler.backend.js.ast.JsNode;
021    import com.google.dart.compiler.backend.js.ast.JsProgram;
022    import com.google.dart.compiler.util.TextOutputImpl;
023    import com.intellij.openapi.project.Project;
024    import com.intellij.openapi.util.text.StringUtil;
025    import com.intellij.openapi.vfs.VfsUtilCore;
026    import com.intellij.openapi.vfs.VirtualFile;
027    import com.intellij.psi.PsiFile;
028    import com.intellij.util.Consumer;
029    import com.intellij.util.Function;
030    import com.intellij.util.SmartList;
031    import com.intellij.util.containers.ContainerUtil;
032    import org.jetbrains.annotations.NotNull;
033    import org.jetbrains.annotations.Nullable;
034    import org.jetbrains.jet.OutputFileCollection;
035    import org.jetbrains.jet.SimpleOutputFile;
036    import org.jetbrains.jet.SimpleOutputFileCollection;
037    import org.jetbrains.jet.analyzer.AnalyzeExhaust;
038    import org.jetbrains.jet.lang.descriptors.ModuleDescriptor;
039    import org.jetbrains.jet.lang.psi.JetFile;
040    import org.jetbrains.jet.lang.resolve.BindingContext;
041    import org.jetbrains.jet.utils.fileUtils.FileUtilsPackage;
042    import org.jetbrains.js.compiler.JsSourceGenerationVisitor;
043    import org.jetbrains.js.compiler.sourcemap.SourceMap3Builder;
044    import org.jetbrains.js.compiler.sourcemap.SourceMapBuilder;
045    import org.jetbrains.k2js.analyze.TopDownAnalyzerFacadeForJS;
046    import org.jetbrains.k2js.config.Config;
047    import org.jetbrains.k2js.facade.exceptions.TranslationException;
048    import org.jetbrains.k2js.translate.general.Translation;
049    
050    import java.io.File;
051    import java.io.IOException;
052    import java.util.Collections;
053    import java.util.List;
054    
055    import static org.jetbrains.jet.lang.psi.PsiPackage.JetPsiFactory;
056    import static org.jetbrains.k2js.facade.FacadeUtils.parseString;
057    
058    /**
059     * An entry point of translator.
060     */
061    public final class K2JSTranslator {
062    
063        public static final String FLUSH_SYSTEM_OUT = "Kotlin.out.flush();\n";
064        public static final String GET_SYSTEM_OUT = "Kotlin.out.buffer;\n";
065    
066        public static OutputFileCollection translateWithMainCallParameters(
067                @NotNull MainCallParameters mainCall,
068                @NotNull List<JetFile> files,
069                @NotNull File outputFile,
070                @Nullable File outputPrefixFile,
071                @Nullable File outputPostfixFile,
072                @NotNull Config config,
073                @NotNull Consumer<JsNode> astConsumer // hack for tests
074        ) throws TranslationException, IOException {
075            K2JSTranslator translator = new K2JSTranslator(config);
076            TextOutputImpl output = new TextOutputImpl();
077            SourceMapBuilder sourceMapBuilder = config.isSourcemap() ? new SourceMap3Builder(outputFile, output, new SourceMapBuilderConsumer()) : null;
078            String programCode = translator.generateProgramCode(files, mainCall, output, sourceMapBuilder, astConsumer);
079    
080            String prefix = FileUtilsPackage.readTextOrEmpty(outputPrefixFile);
081            String postfix = FileUtilsPackage.readTextOrEmpty(outputPostfixFile);
082    
083            StringBuilder outBuilder = new StringBuilder(programCode.length() + prefix.length() + postfix.length());
084            outBuilder.append(prefix).append(programCode).append(postfix);
085    
086            List<File> sourceFiles = ContainerUtil.map(files, new Function<JetFile, File>() {
087                @Override
088                public File fun(JetFile file) {
089                    VirtualFile virtualFile = file.getOriginalFile().getVirtualFile();
090                    if (virtualFile == null) return new File(file.getName());
091                    return VfsUtilCore.virtualToIoFile(virtualFile);
092                }
093            });
094    
095            SimpleOutputFile jsFile = new SimpleOutputFile(sourceFiles, outputFile.getName(), outBuilder.toString());
096            List<SimpleOutputFile> outputFiles = new SmartList<SimpleOutputFile>(jsFile);
097    
098            if (sourceMapBuilder != null) {
099                sourceMapBuilder.skipLinesAtBeginning(StringUtil.getLineBreakCount(prefix));
100                SimpleOutputFile sourcemapFile = new SimpleOutputFile(sourceFiles, sourceMapBuilder.getOutFile().getName(), sourceMapBuilder.build());
101                outputFiles.add(sourcemapFile);
102            }
103    
104            return new SimpleOutputFileCollection(outputFiles);
105        }
106    
107        @NotNull
108        private final Config config;
109    
110        public K2JSTranslator(@NotNull Config config) {
111            this.config = config;
112        }
113    
114        //NOTE: web demo related method
115        @SuppressWarnings("UnusedDeclaration")
116        @NotNull
117        public String translateStringWithCallToMain(@NotNull String programText, @NotNull String argumentsString) throws TranslationException {
118            JetFile file = JetPsiFactory(getProject()).createFile("test", programText);
119            String programCode = generateProgramCode(file, MainCallParameters.mainWithArguments(parseString(argumentsString))) + "\n";
120            return FLUSH_SYSTEM_OUT + programCode + GET_SYSTEM_OUT;
121        }
122    
123        @NotNull
124        public String generateProgramCode(@NotNull JetFile file, @NotNull MainCallParameters mainCallParameters) throws TranslationException {
125            return generateProgramCode(Collections.singletonList(file), mainCallParameters);
126        }
127    
128        @NotNull
129        public String generateProgramCode(@NotNull List<JetFile> files, @NotNull MainCallParameters mainCallParameters)
130                throws TranslationException {
131            //noinspection unchecked
132            return generateProgramCode(files, mainCallParameters, new TextOutputImpl(), null, Consumer.EMPTY_CONSUMER);
133        }
134    
135        @NotNull
136        public String generateProgramCode(
137                @NotNull List<JetFile> files,
138                @NotNull MainCallParameters mainCallParameters,
139                @NotNull TextOutputImpl output,
140                @Nullable SourceMapBuilder sourceMapBuilder,
141                @NotNull Consumer<JsNode> astConsumer
142        ) throws TranslationException {
143            JsProgram program = generateProgram(files, mainCallParameters);
144    
145            JsSourceGenerationVisitor sourceGenerator = new JsSourceGenerationVisitor(output, sourceMapBuilder);
146            program.accept(sourceGenerator);
147    
148            astConsumer.consume(program);
149    
150            return output.toString();
151        }
152    
153        @NotNull
154        public JsProgram generateProgram(@NotNull List<JetFile> filesToTranslate,
155                @NotNull MainCallParameters mainCallParameters)
156                throws TranslationException {
157            AnalyzeExhaust analyzeExhaust = TopDownAnalyzerFacadeForJS.analyzeFiles(filesToTranslate, Predicates.<PsiFile>alwaysTrue(), config);
158            BindingContext bindingContext = analyzeExhaust.getBindingContext();
159            TopDownAnalyzerFacadeForJS.checkForErrors(Config.withJsLibAdded(filesToTranslate, config), bindingContext);
160            ModuleDescriptor moduleDescriptor = analyzeExhaust.getModuleDescriptor();
161            return Translation.generateAst(bindingContext, filesToTranslate, mainCallParameters, moduleDescriptor, config);
162        }
163    
164        @NotNull
165        private Project getProject() {
166            return config.getProject();
167        }
168    }