• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

wurstscript / WurstScript / 215

08 Nov 2023 06:49PM UTC coverage: 62.561% (-0.04%) from 62.602%
215

Pull #1081

circleci

Frotty
fixes and master merge
Pull Request #1081: More performance improvements

17307 of 27664 relevant lines covered (62.56%)

0.63 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

56.85
de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/WurstCompilerJassImpl.java
1
package de.peeeq.wurstio;
2

3
import com.google.common.base.Charsets;
4
import com.google.common.base.Preconditions;
5
import com.google.common.collect.*;
6
import com.google.common.io.Files;
7
import config.WurstProjectConfigData;
8
import de.peeeq.wurstio.languageserver.requests.RequestFailedException;
9
import de.peeeq.wurstio.map.importer.ImportFile;
10
import de.peeeq.wurstio.mpq.MpqEditor;
11
import de.peeeq.wurstio.utils.FileReading;
12
import de.peeeq.wurstio.utils.FileUtils;
13
import de.peeeq.wurstscript.*;
14
import de.peeeq.wurstscript.ast.*;
15
import de.peeeq.wurstscript.ast.Element;
16
import de.peeeq.wurstscript.attributes.CompilationUnitInfo;
17
import de.peeeq.wurstscript.attributes.CompileError;
18
import de.peeeq.wurstscript.attributes.ErrorHandler;
19
import de.peeeq.wurstscript.gui.WurstGui;
20
import de.peeeq.wurstscript.jassAst.JassProg;
21
import de.peeeq.wurstscript.jassIm.*;
22
import de.peeeq.wurstscript.jassprinter.JassPrinter;
23
import de.peeeq.wurstscript.luaAst.LuaCompilationUnit;
24
import de.peeeq.wurstscript.parser.WPos;
25
import de.peeeq.wurstscript.translation.imoptimizer.ImOptimizer;
26
import de.peeeq.wurstscript.translation.imtojass.ImAttrType;
27
import de.peeeq.wurstscript.translation.imtojass.ImToJassTranslator;
28
import de.peeeq.wurstscript.translation.imtranslation.*;
29
import de.peeeq.wurstscript.translation.lua.translation.LuaTranslator;
30
import de.peeeq.wurstscript.types.TypesHelper;
31
import de.peeeq.wurstscript.utils.*;
32
import org.eclipse.jdt.annotation.Nullable;
33
import org.eclipse.lsp4j.MessageType;
34
import org.jetbrains.annotations.NotNull;
35

36
import java.io.*;
37
import java.lang.ref.WeakReference;
38
import java.nio.charset.StandardCharsets;
39
import java.util.*;
40
import java.util.Map.Entry;
41
import java.util.function.Function;
42

43
import static de.peeeq.wurstio.CompiletimeFunctionRunner.FunctionFlagToRun.CompiletimeFunctions;
44

45
public class WurstCompilerJassImpl implements WurstCompiler {
46

47
    private List<File> files = Lists.newArrayList();
1✔
48
    private Map<String, Reader> otherInputs = Maps.newLinkedHashMap();
1✔
49
    private @Nullable JassProg prog;
50
    private WurstGui gui;
51
    private boolean hasCommonJ;
52
    private RunArgs runArgs;
53
    private Optional<File> mapFile = Optional.empty();
1✔
54
    private @Nullable File projectFolder;
55
    private ErrorHandler errorHandler;
56
    private @Nullable Map<String, File> libCache = null;
1✔
57
    private @Nullable ImProg imProg;
58
    private List<File> parsedFiles = Lists.newArrayList();
1✔
59
    private final WurstParser parser;
60
    private final WurstChecker checker;
61
    private @Nullable ImTranslator imTranslator;
62
    private List<File> dependencies = Lists.newArrayList();
1✔
63
    private final @Nullable MpqEditor mapFileMpq;
64
    private TimeTaker timeTaker;
65

66
    public WurstCompilerJassImpl(@Nullable File projectFolder, WurstGui gui, @Nullable MpqEditor mapFileMpq, RunArgs runArgs) {
67
        this(new TimeTaker.Default(), projectFolder, gui, mapFileMpq, runArgs);
1✔
68
    }
1✔
69

70
    public WurstCompilerJassImpl(TimeTaker timeTaker, @Nullable File projectFolder, WurstGui gui, @Nullable MpqEditor mapFileMpq, RunArgs runArgs) {
1✔
71
        this.timeTaker = timeTaker;
1✔
72
        this.projectFolder = projectFolder;
1✔
73
        this.gui = gui;
1✔
74
        this.runArgs = runArgs;
1✔
75
        this.errorHandler = new ErrorHandler(gui);
1✔
76
        this.parser = new WurstParser(errorHandler, gui);
1✔
77
        this.checker = new WurstChecker(gui, errorHandler);
1✔
78
        this.mapFileMpq = mapFileMpq;
1✔
79
    }
1✔
80

81
    @Override
82
    public void loadFiles(String... filenames) {
83
        gui.sendProgress("Loading Files");
×
84
        for (String filename : filenames) {
×
85
            File file = new File(filename);
×
86
            if (!file.exists()) {
×
87
                throw new Error("File " + filename + " does not exist.");
×
88
            }
89
            files.add(file);
×
90
        }
91
    }
×
92

93
    @Override
94
    public void loadFiles(File... files) {
95
        gui.sendProgress("Loading Files");
1✔
96
        for (File file : files) {
1✔
97
            loadFile(file);
1✔
98
        }
99
    }
1✔
100

101
    @Override
102
    public void runCompiletime(WurstProjectConfigData projectConfigData, boolean isProd, boolean cache) {
103
        if (runArgs.runCompiletimeFunctions()) {
1✔
104
            // compile & inject object-editor data
105
            // TODO run optimizations later?
106
            gui.sendProgress("Running compiletime functions");
1✔
107
            CompiletimeFunctionRunner ctr = new CompiletimeFunctionRunner(imTranslator, getImProg(), getMapFile(), getMapfileMpqEditor(), gui,
1✔
108
                    CompiletimeFunctions, projectConfigData, isProd, cache);
109
            ctr.setInjectObjects(runArgs.isInjectObjects());
1✔
110
            ctr.setOutputStream(new PrintStream(System.err));
1✔
111
            ctr.run();
1✔
112
        }
113

114
        if (gui.getErrorCount() > 0) {
1✔
115
            CompileError compileError = gui
×
116
                    .getErrorList().get(0);
×
117
            throw new RequestFailedException(MessageType.Error, "Could not compile project (error in running compiletime functions/expressions): ", compileError);
×
118
        }
119

120

121
        if (runArgs.isInjectObjects()) {
1✔
122
            Preconditions.checkNotNull(mapFileMpq);
×
123
            Preconditions.checkNotNull(projectFolder);
×
124
            // add the imports
125
            ImportFile.importFilesFromImports(projectFolder, mapFileMpq);
×
126
        }
127
    }
1✔
128

129
    private void loadFile(File file) throws Error {
130
        Preconditions.checkNotNull(file);
1✔
131
        if (!file.exists()) {
1✔
132
            throw new Error("File " + file + " does not exist.");
×
133
        }
134
        this.files.add(file);
1✔
135
    }
1✔
136

137
    public void loadWurstFilesInDir(File dir) {
138
        for (File f : dir.listFiles()) {
×
139
            if (f.isDirectory()) {
×
140
                loadWurstFilesInDir(f);
×
141
            } else if (Utils.isWurstFile(f)) {
×
142
                loadFile(f);
×
143
            } else if (f.getName().equals("wurst.dependencies")) {
×
144
                dependencies.addAll(checkDependencyFile(f, gui));
×
145
            } else if ((!mapFile.isPresent() || runArgs.isNoExtractMapScript()) && f.getName().equals("war3map.j")) {
×
146
                loadFile(f);
×
147
            }
148
        }
149
    }
×
150

151
    public static ImmutableList<File> checkDependencyFile(File depFile, WurstGui gui) {
152
        List<String> lines;
153
        try {
154
            lines = Files.readLines(depFile, StandardCharsets.UTF_8);
×
155
        } catch (IOException e) {
×
156
            e.printStackTrace();
×
157
            throw new Error(e);
×
158
        }
×
159
        LineOffsets offsets = new LineOffsets();
×
160
        int lineNr = 0;
×
161
        int offset = 0;
×
162
        for (String line : lines) {
×
163
            offsets.set(lineNr, offset);
×
164
            lineNr++;
×
165
            offset += line.length() + 1;
×
166
        }
×
167
        offsets.set(lineNr, offset);
×
168
        lineNr = 0;
×
169
        ImmutableList.Builder<File> dependencies = ImmutableList.builder();
×
170
        for (String line : lines) {
×
171
            int lineOffset = offsets.get(lineNr);
×
172
            WPos pos = new WPos(depFile.getAbsolutePath(), offsets, lineOffset + 1, lineOffset + line.length() + 1);
×
173
            File folder = new File(line);
×
174
            if (!folder.exists()) {
×
175
                gui.sendError(new CompileError(pos, "Folder " + line + " not found."));
×
176
            } else if (!folder.isDirectory()) {
×
177
                gui.sendError(new CompileError(pos, "" + line + " is not a folder."));
×
178
            } else {
179
                dependencies.add(folder);
×
180
            }
181
            lineNr++;
×
182
        }
×
183
        return dependencies.build();
×
184
    }
185

186
    @Override
187
    public @Nullable WurstModel parseFiles() {
188

189
        // search mapFile
190
        for (File file : files) {
1✔
191
            if (file.getName().endsWith(".w3x") || file.getName().endsWith(".w3m")) {
1✔
192
                mapFile = Optional.ofNullable(file);
×
193
            } else if (file.isDirectory()) {
1✔
194
                if (projectFolder != null && !file.getParent().equals(projectFolder.getAbsolutePath())) {
×
195
                    throw new RuntimeException("Cannot set projectFolder to " + file + " because it is already set to non parent " + projectFolder);
×
196
                }
197
                projectFolder = file;
×
198
            }
199
        }
1✔
200

201

202

203
        // import wurst folder if it exists
204
        Optional<File> l_mapFile = mapFile;
1✔
205
        if (l_mapFile.isPresent()) {
1✔
206
            if (projectFolder == null) {
×
207
                projectFolder = l_mapFile.get().getParentFile();
×
208
            }
209
            File relativeWurstDir = new File(projectFolder, "wurst");
×
210
            if (relativeWurstDir.exists()) {
×
211
                WLogger.info("Importing wurst files from " + relativeWurstDir);
×
212
                loadWurstFilesInDir(relativeWurstDir);
×
213
            } else {
214
                WLogger.info("No wurst folder found in " + relativeWurstDir);
×
215
            }
216
            File dependencyFile = new File(projectFolder, "wurst.dependencies");
×
217
            if (dependencyFile.exists()) {
×
218
                dependencies.addAll(checkDependencyFile(dependencyFile, gui));
×
219
            }
220
            addDependenciesFromFolder(projectFolder, dependencies);
×
221
        }
222

223
        // add directories:
224
        List<File> dirs = Lists.newArrayList();
1✔
225
        for (File file : files) {
1✔
226
            if (file.isDirectory()) {
1✔
227
                dirs.add(file);
×
228
            }
229
        }
1✔
230
        for (File dir : dirs) {
1✔
231
            loadWurstFilesInDir(dir);
×
232
        }
×
233

234
        gui.sendProgress("Parsing Files");
1✔
235
        // parse all the files:
236
        List<CompilationUnit> compilationUnits = new NotNullList<>();
1✔
237

238
        for (File file : files) {
1✔
239
            if (file.isDirectory()) {
1✔
240
                // ignore dirs
241
            } else if (file.getName().endsWith(".w3x") || file.getName().endsWith(".w3m")) {
1✔
242
                CompilationUnit r = processMap(file);
×
243
                if (r != null) {
×
244
                    compilationUnits.add(r);
×
245
                }
246
            } else {
×
247
                if (file.getName().endsWith("common.j")) {
1✔
248
                    hasCommonJ = true;
1✔
249
                }
250
                compilationUnits.add(parseFile(file));
1✔
251
            }
252
        }
1✔
253
        for (Entry<String, Reader> in : otherInputs.entrySet()) {
1✔
254
            compilationUnits.add(parse(in.getKey(), in.getValue()));
1✔
255
        }
1✔
256

257
        try {
258
            addImportedLibs(compilationUnits);
1✔
259
        } catch (CompileError e) {
×
260
            gui.sendError(e);
×
261
            return null;
×
262
        }
1✔
263

264
        if (errorHandler.getErrorCount() > 0)
1✔
265
            return null;
1✔
266

267
        // merge the compilationUnits:
268
        WurstModel merged = mergeCompilationUnits(compilationUnits);
1✔
269
        StringBuilder sb = new StringBuilder();
1✔
270
        for (CompilationUnit cu : merged) {
1✔
271
            sb.append(cu.getCuInfo().getFile()).append(", ");
1✔
272
        }
1✔
273
        WLogger.info("Compiling compilation units: " + sb);
1✔
274

275
        return merged;
1✔
276
    }
277

278
    /**
279
     * Adds dependencies from the _build/dependencies folder
280
     */
281
    public static void addDependenciesFromFolder(File projectFolder, Collection<File> dependencies) {
282
        File dependencyFolder = new File(new File(projectFolder, "_build"), "dependencies");
×
283
        File[] depProjects = dependencyFolder.listFiles();
×
284
        if (depProjects != null) {
×
285
            for (File depFile : depProjects) {
×
286
                if (depFile.isDirectory()
×
287
                        && dependencies.stream().noneMatch(f -> FileUtils.sameFile(f, depFile))) {
×
288
                    dependencies.add(depFile);
×
289
                }
290
            }
291
        }
292
    }
×
293

294
    private void addImportedLibs(List<CompilationUnit> compilationUnits) {
295
        addImportedLibs(compilationUnits, file -> {
1✔
296
            CompilationUnit lib = parseFile(file);
1✔
297
            lib.getCuInfo().setFile(file.getAbsolutePath());
1✔
298
            compilationUnits.add(lib);
1✔
299
            return lib;
1✔
300
        });
301
    }
1✔
302

303
    /**
304
     * this method scans for unsatisfied imports and tries to find them in the lib-path
305
     */
306
    public void addImportedLibs(List<CompilationUnit> compilationUnits, Function<File, CompilationUnit> addCompilationUnit) {
307
        Set<String> packages = Sets.newLinkedHashSet();
1✔
308
        Set<WImport> imports = new LinkedHashSet<>();
1✔
309
        for (CompilationUnit c : compilationUnits) {
1✔
310
            c.getCuInfo().setCuErrorHandler(errorHandler);
1✔
311
            for (WPackage p : c.getPackages()) {
1✔
312
                packages.add(p.getName());
1✔
313
                imports.addAll(p.getImports());
1✔
314
            }
1✔
315
        }
1✔
316

317
        for (WImport imp : imports) {
1✔
318
            resolveImport(addCompilationUnit, packages, imp);
1✔
319
        }
1✔
320

321
    }
1✔
322

323
    private void resolveImport(Function<File, CompilationUnit> addCompilationUnit, Set<String> packages, WImport imp) throws CompileError {
324
        //                WLogger.info("resolving import: " + imp.getPackagename());
325
        if (!packages.contains(imp.getPackagename())) {
1✔
326
            if (getLibs().containsKey(imp.getPackagename())) {
1✔
327
                CompilationUnit lib = loadLibPackage(addCompilationUnit, imp.getPackagename());
1✔
328
                boolean foundPackage = false;
1✔
329
                for (WPackage p : lib.getPackages()) {
1✔
330
                    packages.add(p.getName());
1✔
331
                    if (p.getName().equals(imp.getPackagename())) {
1✔
332
                        foundPackage = true;
1✔
333
                    }
334
                    for (WImport i : p.getImports()) {
1✔
335
                        resolveImport(addCompilationUnit, packages, i);
1✔
336
                    }
1✔
337
                }
1✔
338
                if (!foundPackage) {
1✔
339
                    imp.addError("The import " + imp.getPackagename() + " could not be found in file " + lib.getCuInfo().getFile());
×
340
                }
341
            } else {
1✔
342
                if (imp.getPackagename().equals("Wurst")) {
1✔
343
                    imp.addError("The standard library could not be imported.");
×
344
                }
345
                if (imp.getPackagename().equals("NoWurst")) {
1✔
346
                    // ignore this package
347
                } else {
348
                    imp.addError("The import '" + imp.getPackagename() + "' could not be resolved.\n" + "Available packages: "
1✔
349
                            + Utils.join(getLibs().keySet(), ", "));
1✔
350
                }
351
            }
352
        } else {
353
            //                        WLogger.info("already imported: " + imp.getPackagename());
354
        }
355
    }
1✔
356

357
    private CompilationUnit loadLibPackage(Function<File, CompilationUnit> addCompilationUnit, String imp) {
358
        File file = getLibs().get(imp);
1✔
359
        if (file == null) {
1✔
360
            gui.sendError(new CompileError(new WPos("", null, 0, 0), "Could not find lib-package " + imp + ". Are you missing your wurst.dependencies file?"));
×
361
            return Ast.CompilationUnit(new CompilationUnitInfo(errorHandler), Ast.JassToplevelDeclarations(), Ast.WPackages());
×
362
        } else {
363
            return addCompilationUnit.apply(file);
1✔
364
        }
365
    }
366

367
    public Map<String, File> getLibs() {
368
        Map<String, File> lc = libCache;
1✔
369
        if (lc == null) {
1✔
370
            lc = Maps.newLinkedHashMap();
1✔
371
            libCache = lc;
1✔
372
            for (File libDir : runArgs.getAdditionalLibDirs()) {
1✔
373
                addLibDir(libDir);
1✔
374
            }
1✔
375
            for (File libDir : dependencies) {
1✔
376
                addLibDir(libDir);
×
377
            }
×
378
        }
379
        return lc;
1✔
380
    }
381

382
    private void addLibDir(File libDir) throws Error {
383
        if (!libDir.exists() || !libDir.isDirectory()) {
1✔
384
            throw new Error("Library folder " + libDir + " does not exist.");
×
385
        }
386
        for (File f : libDir.listFiles()) {
1✔
387
            if (f.isDirectory()) {
1✔
388
                // recursively scan directory
389
                addLibDir(f);
1✔
390
            }
391
            if (Utils.isWurstFile(f)) {
1✔
392
                String libName = Utils.getLibName(f);
1✔
393
                getLibs().put(libName, f);
1✔
394
            }
395
        }
396
    }
1✔
397

398
    public void checkProg(WurstModel model) {
399
        checkProg(model, model);
1✔
400
    }
1✔
401

402
    public void checkProg(WurstModel model, Collection<CompilationUnit> toCheck) {
403
        for (CompilationUnit cu : toCheck) {
1✔
404
            Preconditions.checkNotNull(cu);
1✔
405
            if (!model.contains(cu)) {
1✔
406
                // model has changed since then, no need to do 'toCheck'
407
                throw new ModelChangedException();
×
408
            }
409
        }
1✔
410

411
        checker.checkProg(model, toCheck);
1✔
412
    }
1✔
413

414
    public JassProg transformProgToJass() {
415
        ImTranslator imTranslator2 = getImTranslator();
1✔
416
        ImProg imProg2 = getImProg();
1✔
417
        imTranslator2.assertProperties();
1✔
418
        checkNoCompiletimeExpr(imProg2);
1✔
419
        int stage = 2;
1✔
420
        // eliminate
421
        beginPhase(2, "Eliminate generics");
1✔
422
        new EliminateGenerics(imTranslator2, imProg2).transform();
1✔
423
        printDebugImProg("./test-output/im " + stage++ + "_genericsEliminated.im");
1✔
424
        timeTaker.endPhase();
1✔
425
        // eliminate classes
426
        beginPhase(2, "translate classes");
1✔
427

428
        new EliminateClasses(imTranslator2, imProg2, !runArgs.isUncheckedDispatch()).eliminateClasses();
1✔
429
        imTranslator2.assertProperties();
1✔
430
        printDebugImProg("./test-output/im " + stage++ + "_classesEliminated.im");
1✔
431
        timeTaker.endPhase();
1✔
432

433
        new VarargEliminator(imProg2).run();
1✔
434
        printDebugImProg("./test-output/im " + stage++ + "_varargEliminated.im");
1✔
435
        imTranslator2.assertProperties();
1✔
436

437
        timeTaker.endPhase();
1✔
438

439
        if (runArgs.isNoDebugMessages()) {
1✔
440
            beginPhase(3, "remove debug messages");
×
441
            DebugMessageRemover.removeDebugMessages(imProg2);
×
442
            timeTaker.endPhase();
×
443
        } else {
444
            // debug: add stacktraces
445
            if (runArgs.isIncludeStacktraces()) {
1✔
446
                beginPhase(4, "add stack traces");
1✔
447
                new StackTraceInjector2(imProg2, imTranslator2).transform(timeTaker);
1✔
448
                timeTaker.endPhase();
1✔
449
            }
450
        }
451
        imTranslator2.assertProperties();
1✔
452

453
        ImOptimizer optimizer = new ImOptimizer(timeTaker, imTranslator2);
1✔
454

455
        // inliner
456
        if (runArgs.isInline()) {
1✔
457
            beginPhase(5, "inlining");
1✔
458
            optimizer.doInlining();
1✔
459
            imTranslator2.assertProperties();
1✔
460

461
            printDebugImProg("./test-output/im " + stage++ + "_afterinline.im");
1✔
462
            timeTaker.endPhase();
1✔
463
        }
464

465
        // eliminate tuples
466
        beginPhase(6, "eliminate tuples");
1✔
467
        timeTaker.measure("flatten", () -> getImProg().flatten(imTranslator2));
1✔
468
        timeTaker.measure("kill tuples", () -> EliminateTuples.eliminateTuplesProg(getImProg(), imTranslator2));
1✔
469
        getImTranslator().assertProperties(AssertProperty.NOTUPLES);
1✔
470

471
        printDebugImProg("./test-output/im " + stage++ + "_withouttuples.im");
1✔
472

473
        timeTaker.endPhase();
1✔
474

475
        beginPhase(7, "eliminate multi arrays");
1✔
476
        new MultiArrayEliminator(imProg2, imTranslator2, runArgs.isIncludeStacktraces() && !runArgs.isNoDebugMessages()).run();
1✔
477
        printDebugImProg("./test-output/im " + stage++ + "_withoutmultiarrays.im");
1✔
478
        imTranslator2.assertProperties();
1✔
479
        timeTaker.endPhase();
1✔
480

481
        beginPhase(8, "remove func refs");
1✔
482
        new FuncRefRemover(imProg2, imTranslator2).run();
1✔
483
        timeTaker.endPhase();
1✔
484

485
        // remove cycles:
486
        beginPhase(9, "remove cyclic functions");
1✔
487
        new CyclicFunctionRemover(imTranslator2, imProg2, timeTaker).work();
1✔
488

489
        printDebugImProg("./test-output/im " + stage++ + "_nocyc.im");
1✔
490
        timeTaker.endPhase();
1✔
491

492
        // flatten
493
        beginPhase(10, "flatten");
1✔
494
        getImProg().flatten(imTranslator2);
1✔
495
        getImTranslator().assertProperties(AssertProperty.NOTUPLES, AssertProperty.FLAT);
1✔
496

497
        printDebugImProg("./test-output/im " + stage++ + "_flat.im");
1✔
498
        timeTaker.endPhase();
1✔
499

500
        if (runArgs.isLocalOptimizations()) {
1✔
501
            beginPhase(11, "local optimizations");
1✔
502
            optimizer.localOptimizations();
1✔
503
            timeTaker.endPhase();
1✔
504
        }
505

506
        printDebugImProg("./test-output/im " + stage++ + "_afterlocalopts.im");
1✔
507

508
        if (runArgs.isNullsetting()) {
1✔
509
            beginPhase(12, "null setting");
×
510
            optimizer.doNullsetting();
×
511
            printDebugImProg("./test-output/im " + stage++ + "_afternullsetting.im");
×
512
            timeTaker.endPhase();
×
513
        }
514

515
        beginPhase(13, "flatten");
1✔
516
        optimizer.removeGarbage();
1✔
517
        imProg.flatten(imTranslator);
1✔
518

519
        // Re-run to avoid #883
520
        optimizer.removeGarbage();
1✔
521
        imProg.flatten(imTranslator);
1✔
522

523
        printDebugImProg("./test-output/im " + stage++ + "_afterremoveGarbage1.im");
1✔
524
        timeTaker.endPhase();
1✔
525

526
        if (runArgs.isHotStartmap() || runArgs.isHotReload()) {
1✔
527
            addJassHotCodeReloadCode();
×
528
        }
529
        if (runArgs.isOptimize()) {
1✔
530
            beginPhase(13, "froptimize");
×
531
            optimizer.optimize();
×
532

533
            optimizer.removeGarbage();
×
534
            imProg.flatten(imTranslator);
×
535
            printDebugImProg("./test-output/im " + stage++ + "_afteroptimize.im");
×
536
        }
537

538

539

540
        // translate flattened intermediate lang to jass:
541

542
        beginPhase(14, "translate to jass");
1✔
543
        getImTranslator().calculateCallRelationsAndUsedVariables();
1✔
544
        ImToJassTranslator translator =
1✔
545
                new ImToJassTranslator(getImProg(), getImTranslator().getCalledFunctions(), getImTranslator().getMainFunc(), getImTranslator().getConfFunc());
1✔
546
        prog = translator.translate();
1✔
547
        if (errorHandler.getErrorCount() > 0) {
1✔
548
            prog = null;
×
549
        }
550
        timeTaker.endPhase();
1✔
551
        return prog;
1✔
552
    }
553

554
    private void addJassHotCodeReloadCode() {
555
        Preconditions.checkNotNull(imTranslator);
×
556
        Preconditions.checkNotNull(imProg);
×
557
        ImFunction mainFunc = imTranslator.getMainFunc();
×
558
        Preconditions.checkNotNull(mainFunc);
×
559
        Element trace = imProg.getTrace();
×
560

561
        List<ImStmt> stmts = new ArrayList<>();
×
562

563
        // add call to JHCR_Init_init in main
564
        stmts.add(callExtern(trace, CallType.EXECUTE, "JHCR_Init_init"));
×
565

566

567
        // add reload trigger for pressing escape
568
        ImStmts reloadBody = JassIm.ImStmts(
×
569
                callExtern(trace, CallType.EXECUTE, "JHCR_Init_parse"),
×
570
                callExtern(trace, CallType.NORMAL, "BJDebugMsg", JassIm.ImStringVal("Code reloaded!"))
×
571
        );
572
        ImFunction jhcr_reload = JassIm.ImFunction(trace, "jhcr_reload_on_escape", JassIm.ImTypeVars(), JassIm.ImVars(), JassIm.ImVoid(), JassIm.ImVars(), reloadBody, Collections.emptyList());
×
573

574

575
        ImVar trig = JassIm.ImVar(trace, TypesHelper.imTrigger(), "trig", false);
×
576
        mainFunc.getLocals().add(trig);
×
577
        // TriggerRegisterPlayerEventEndCinematic(trig, Player(0))
578
        stmts.add(JassIm.ImSet(trace, JassIm.ImVarAccess(trig), callExtern(trace, CallType.NORMAL, "CreateTrigger")));
×
579
        stmts.add(callExtern(trace, CallType.NORMAL, "TriggerRegisterPlayerEventEndCinematic", JassIm.ImVarAccess(trig),
×
580
                callExtern(trace, CallType.NORMAL, "Player", JassIm.ImIntVal(0))));
×
581
        stmts.add(callExtern(trace, CallType.NORMAL, "TriggerAddAction", JassIm.ImVarAccess(trig),
×
582
                JassIm.ImFuncRef(trace, jhcr_reload)));
×
583

584
        mainFunc.getBody().addAll(0, stmts);
×
585
    }
×
586

587
    @NotNull
588
    private ImFunctionCall callExtern(Element trace, CallType callType, String functionName, ImExpr... arguments) {
589
        ImFunction jhcrinit = JassIm.ImFunction(trace, functionName, JassIm.ImTypeVars(), JassIm.ImVars(), JassIm.ImVoid(), JassIm.ImVars(), JassIm.ImStmts(), Collections.singletonList(FunctionFlagEnum.IS_EXTERN));
×
590
        return JassIm.ImFunctionCall(trace, jhcrinit, JassIm.ImTypeArguments(), JassIm.ImExprs(arguments), true, callType);
×
591
    }
592

593
    public void checkNoCompiletimeExpr(ImProg prog) {
594
        prog.accept(new ImProg.DefaultVisitor() {
1✔
595
            @Override
596
            public void visit(ImCompiletimeExpr e) {
597
                super.visit(e);
×
598
                throw new CompileError(e.attrTrace().attrSource(), "Compiletime expressions require compilation with '-runcompiletimefunctions' option.");
×
599
            }
600
        });
601
    }
1✔
602

603
    public ImTranslator getImTranslator() {
604
        final ImTranslator t = imTranslator;
1✔
605
        if (t != null) {
1✔
606
            return t;
1✔
607
        } else {
608
            throw new Error("translator not initialized");
×
609
        }
610
    }
611

612
    public @Nullable ImProg translateProgToIm(WurstModel root) {
613
        beginPhase(1, "to intermediate lang");
1✔
614
        // translate wurst to intermediate lang:
615
        imTranslator = new ImTranslator(root, errorHandler.isUnitTestMode(), runArgs);
1✔
616
        imProg = getImTranslator().translateProg();
1✔
617
        int stage = 1;
1✔
618
        printDebugImProg("./test-output/im " + stage++ + ".im");
1✔
619
        timeTaker.endPhase();
1✔
620
        return imProg;
1✔
621
    }
622

623
    private void beginPhase(int phase, String description) {
624
        errorHandler.setProgress("Translating wurst. Phase " + phase + ": " + description, 0.6 + 0.01 * phase);
1✔
625
        timeTaker.beginPhase(description);
1✔
626
    }
1✔
627

628
    private void printDebugImProg(String debugFile) {
629
        if (!errorHandler.isUnitTestMode() ) {
1✔
630
            // output only in unit test mode
631
            return;
×
632
        }
633
        try {
634
            // TODO remove test output
635
            File file = new File(debugFile);
1✔
636
            file.getParentFile().mkdirs();
1✔
637
            try (Writer w = Files.newWriter(file, Charsets.UTF_8)) {
1✔
638
                getImProg().print(w, 0);
1✔
639
            }
640
        } catch (IOException e) {
×
641
            ErrorReporting.instance.handleSevere(e, getCompleteSourcecode());
×
642
        }
1✔
643
    }
1✔
644

645
    private WurstModel mergeCompilationUnits(List<CompilationUnit> compilationUnits) {
646
        gui.sendProgress("Merging Files");
1✔
647
        WurstModel result = Ast.WurstModel();
1✔
648
        for (CompilationUnit compilationUnit : compilationUnits) {
1✔
649
            // remove from old parent
650
            compilationUnit.setParent(null);
1✔
651
            result.add(compilationUnit);
1✔
652
        }
1✔
653
        return result;
1✔
654
    }
655

656
    private CompilationUnit processMap(File file) {
657
        gui.sendProgress("Processing Map " + file.getName());
×
658
        if (!mapFile.isPresent() || !file.equals(mapFile.get())) {
×
659
            // TODO check if file != mapFile is possible, would be strange
660
            // so this should definitely be done differently
661
            throw new Error("file: " + file + " is not the mapfile: " + mapFile);
×
662
        }
663

664
        MpqEditor mapMpq = mapFileMpq;
×
665
        if (mapMpq == null) {
×
666
            throw new RuntimeException("map mpq is null");
×
667
        }
668

669
        if (runArgs.isNoExtractMapScript()) {
×
670
            return null;
×
671
        }
672

673
        // extract mapscript:
674
        try {
675
            byte[] tempBytes = mapMpq.extractFile("war3map.j");
×
676
            File tempFile = File.createTempFile("war3map", ".j", TempDir.get()); // TODO work directly with bytes without temp file
×
677
            tempFile.deleteOnExit();
×
678
            Files.write(tempBytes, tempFile);
×
679

680
            if (isWurstGenerated(tempFile)) {
×
681
                // the war3map.j file was generated by wurst
682
                // this should not be the case, as we will get duplicate function errors in this case
683
                throw new AbortCompilationException(
×
684
                        "Map was not saved correctly. Please try saving the map again.\n\n" + "This usually happens if you change the name of the map or \n"
685
                                + "if you have used the test-map-button without saving the map first.");
686
            }
687

688
            // move file to wurst directory
689
            File wurstFolder = new File(file.getParentFile(), "wurst");
×
690
            wurstFolder.mkdirs();
×
691
            if (!wurstFolder.isDirectory()) {
×
692
                throw new AbortCompilationException("Could not create Wurst folder at " + wurstFolder + ".");
×
693
            }
694
            File wurstwar3map = new File(wurstFolder, "war3map.j");
×
695
            wurstwar3map.delete();
×
696
            if (tempFile.renameTo(wurstwar3map)) {
×
697
                return parseFile(wurstwar3map);
×
698
            } else {
699
                throw new Error("Could not move war3map.j from " + tempFile + " to " + wurstwar3map);
×
700
            }
701
        } catch (RuntimeException e) {
×
702
            throw e;
×
703
        } catch (Exception e) {
×
704
            throw new Error(e);
×
705
        }
706

707
    }
708

709
    private boolean isWurstGenerated(File tempFile) {
710
        try (FileReader fr = new FileReader(tempFile); BufferedReader in = new BufferedReader(fr)) {
×
711
            String firstLine = in.readLine();
×
712
            WLogger.info("firstLine = '" + firstLine + "'");
×
713
            return firstLine.equals(JassPrinter.WURST_COMMENT);
×
714
        } catch (IOException e) {
×
715
            WLogger.severe(e);
×
716
        }
717
        return false;
×
718
    }
719

720
    // a cache for compilation units, only used for unit tests to avoid parsing standard library too many times
721
    private static final Map<File, WeakReference<CompilationUnit>> fileCompilationUnitCache = new HashMap<>();
1✔
722

723
    private CompilationUnit parseFile(File file) {
724
        if (errorHandler.isUnitTestMode()) {
1✔
725
            // in unit test mode, we use a cache
726
            WeakReference<CompilationUnit> wr = fileCompilationUnitCache.get(file);
1✔
727
            CompilationUnit res = wr == null ? null : wr.get();
1✔
728
            if (res == null) {
1✔
729
                res = parseFile2(file);
1✔
730
                fileCompilationUnitCache.put(file, new WeakReference<>(res));
1✔
731
            } else {
732
                res = res.copy();
1✔
733
            }
734
            return res;
1✔
735
        } else {
736
            return parseFile2(file);
×
737
        }
738
    }
739

740
    private CompilationUnit parseFile2(File file) {
741
        if (file.isDirectory()) {
1✔
742
            throw new Error("Is a directory: " + file);
×
743
        }
744
        parsedFiles.add(file);
1✔
745

746
        gui.sendProgress("Parsing File " + file.getName());
1✔
747
        String source = file.getAbsolutePath();
1✔
748
        try (Reader reader = FileReading.getFileReader(file)) {
1✔
749
            // scanning
750
            return parse(source, reader);
1✔
751

752
        } catch (CompileError e) {
×
753
            gui.sendError(e);
×
754
            return emptyCompilationUnit();
×
755
        } catch (FileNotFoundException e) {
×
756
            gui.sendError(new CompileError(new WPos(source, LineOffsets.dummy, 0, 0), "File not found."));
×
757
            return emptyCompilationUnit();
×
758
        } catch (IOException e) {
×
759
            gui.sendError(new CompileError(new WPos(source, LineOffsets.dummy, 0, 0), "Could not read file."));
×
760
            return emptyCompilationUnit();
×
761
        }
762
    }
763

764
    public CompilationUnit parse(String fileName, Reader reader) {
765
        if (fileName.endsWith(".j")) {
1✔
766
            return parser.parseJass(reader, fileName, hasCommonJ);
1✔
767
        }
768
        if (fileName.endsWith(".jurst")) {
1✔
769
            return parser.parseJurst(reader, fileName, hasCommonJ);
1✔
770
        }
771
        if (runArgs.isPrettyPrint()) {
1✔
772
            parser.setRemoveSugar(false);
×
773
        }
774
        return parser.parse(reader, fileName, hasCommonJ);
1✔
775
    }
776

777
    private CompilationUnit emptyCompilationUnit() {
778
        return parser.emptyCompilationUnit();
×
779
    }
780

781
    public @Nullable JassProg getProg() {
782
        return prog;
×
783
    }
784

785
    public void loadReader(String name, Reader input) {
786
        otherInputs.put(name, input);
1✔
787
    }
1✔
788

789
    public void setHasCommonJ(boolean hasCommonJ) {
790
        this.hasCommonJ = hasCommonJ;
1✔
791
    }
1✔
792

793
    public ImProg getImProg() {
794
        final ImProg imProg2 = imProg;
1✔
795
        if (imProg2 != null) {
1✔
796
            return imProg2;
1✔
797
        } else {
798
            throw new Error("imProg is null");
×
799
        }
800
    }
801

802
    public Optional<File> getMapFile() {
803
        return mapFile;
1✔
804
    }
805

806
    public ErrorHandler getErrorHandler() {
807
        return errorHandler;
1✔
808
    }
809

810
    public String getCompleteSourcecode() {
811

812
        StringBuilder sb = new StringBuilder();
×
813
        try {
814
            for (File f : parsedFiles) {
×
815
                sb.append(" //######################################################\n");
×
816
                sb.append(" // File ").append(f.getAbsolutePath()).append("\n");
×
817
                sb.append(" //######################################################\n");
×
818
                sb.append(Files.toString(f, Charsets.UTF_8));
×
819
            }
×
820

821
            for (Entry<String, Reader> entry : otherInputs.entrySet()) {
×
822
                sb.append(" //######################################################\n");
×
823
                sb.append(" // Input ").append(entry.getKey()).append("\n");
×
824
                sb.append(" //######################################################\n");
×
825
                try (Reader reader = entry.getValue()) {
×
826
                    char[] buffer = new char[1024];
×
827
                    while (true) {
828
                        int len = reader.read(buffer);
×
829
                        if (len < 0) {
×
830
                            break;
×
831
                        }
832
                        sb.append(buffer, 0, len);
×
833
                    }
×
834
                }
835
            }
×
836
        } catch (Throwable t) {
×
837
            sb.append(Utils.printExceptionWithStackTrace(t));
×
838
            WLogger.severe(t);
×
839
        }
×
840
        return sb.toString();
×
841
    }
842

843
    public void setRunArgs(RunArgs runArgs) {
844
        this.runArgs = runArgs;
1✔
845
    }
1✔
846

847
    public void setMapFile(Optional<File> mapFile) {
848
        this.mapFile = mapFile;
×
849
    }
×
850

851
    public @Nullable MpqEditor getMapfileMpqEditor() {
852
        return mapFileMpq;
1✔
853
    }
854

855
    public LuaCompilationUnit transformProgToLua() {
856

857
        ImAttrType.setWurstClassType(null);
1✔
858
        int stage;
859
        if (runArgs.isNoDebugMessages()) {
1✔
860
            beginPhase(3, "remove debug messages");
×
861
            DebugMessageRemover.removeDebugMessages(imProg);
×
862
            timeTaker.endPhase();
×
863
        } else {
864
            // debug: add stacktraces
865
            if (runArgs.isIncludeStacktraces()) {
1✔
866
                beginPhase(4, "add stack traces");
×
867
                new StackTraceInjector2(imProg, imTranslator).transform(timeTaker);
×
868
                timeTaker.endPhase();
×
869
            }
870
        }
871
        ImTranslator imTranslator2 = getImTranslator();
1✔
872
        ImOptimizer optimizer = new ImOptimizer(timeTaker, imTranslator2);
1✔
873
        // inliner
874
        stage = 5;
1✔
875
        if (runArgs.isInline()) {
1✔
876
            beginPhase(5, "inlining");
×
877
            optimizer.doInlining();
×
878
            imTranslator2.assertProperties();
×
879

880
            printDebugImProg("./test-output/lua/im " + stage++ + "_afterinline.im");
×
881
            timeTaker.endPhase();
×
882
        }
883

884
        // eliminate local types
885
        beginPhase(6, "eliminate local type");
1✔
886
        getImProg().flatten(imTranslator2);
1✔
887
        EliminateLocalTypes.eliminateLocalTypesProg(getImProg(), imTranslator2);
1✔
888

889
        optimizer.removeGarbage();
1✔
890
        imProg.flatten(imTranslator);
1✔
891
        timeTaker.endPhase();
1✔
892
        stage = 10;
1✔
893
        if (runArgs.isLocalOptimizations()) {
1✔
894
            beginPhase(10, "local optimizations");
×
895
            optimizer.localOptimizations();
×
896
            timeTaker.endPhase();
×
897
        }
898

899
        printDebugImProg("./test-output/lua/im " + stage++ + "_afterlocalopts.im");
1✔
900

901
        optimizer.removeGarbage();
1✔
902
        imProg.flatten(imTranslator);
1✔
903

904
        // Re-run to avoid #883
905
        optimizer.removeGarbage();
1✔
906
        imProg.flatten(imTranslator);
1✔
907

908
        printDebugImProg("./test-output/lua/im " + stage++ + "_afterremoveGarbage1.im");
1✔
909

910
        stage = 12;
1✔
911
        if (runArgs.isOptimize()) {
1✔
912
            beginPhase(12, "froptimize");
×
913
            optimizer.optimize();
×
914

915
            optimizer.removeGarbage();
×
916
            imProg.flatten(imTranslator);
×
917
            printDebugImProg("./test-output/lua/im " + stage++ + "_afteroptimize.im");
×
918
            timeTaker.endPhase();
×
919
        }
920
        beginPhase(13, "translate to lua");
1✔
921
        LuaTranslator luaTranslator = new LuaTranslator(imProg, imTranslator);
1✔
922
        LuaCompilationUnit luaCode = luaTranslator.translate();
1✔
923
        ImAttrType.setWurstClassType(TypesHelper.imInt());
1✔
924
        timeTaker.endPhase();
1✔
925
        return luaCode;
1✔
926
    }
927
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc