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

wurstscript / WurstScript / 271

29 Sep 2025 12:12PM UTC coverage: 64.649% (+2.4%) from 62.222%
271

Pull #1096

circleci

Frotty
Merge branch 'perf-improvements' of https://github.com/wurstscript/WurstScript into perf-improvements
Pull Request #1096: Perf improvements

18202 of 28155 relevant lines covered (64.65%)

0.65 hits per line

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

60.86
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.ImmutableList;
6
import com.google.common.collect.Lists;
7
import com.google.common.collect.Maps;
8
import com.google.common.collect.Sets;
9
import com.google.common.io.Files;
10
import config.WurstProjectConfigData;
11
import de.peeeq.wurstio.languageserver.requests.RequestFailedException;
12
import de.peeeq.wurstio.map.importer.ImportFile;
13
import de.peeeq.wurstio.mpq.MpqEditor;
14
import de.peeeq.wurstio.utils.FileReading;
15
import de.peeeq.wurstio.utils.FileUtils;
16
import de.peeeq.wurstscript.*;
17
import de.peeeq.wurstscript.ast.*;
18
import de.peeeq.wurstscript.ast.Element;
19
import de.peeeq.wurstscript.attributes.CompilationUnitInfo;
20
import de.peeeq.wurstscript.attributes.CompileError;
21
import de.peeeq.wurstscript.attributes.ErrorHandler;
22
import de.peeeq.wurstscript.gui.WurstGui;
23
import de.peeeq.wurstscript.jassAst.JassProg;
24
import de.peeeq.wurstscript.jassIm.*;
25
import de.peeeq.wurstscript.jassprinter.JassPrinter;
26
import de.peeeq.wurstscript.luaAst.LuaCompilationUnit;
27
import de.peeeq.wurstscript.parser.WPos;
28
import de.peeeq.wurstscript.translation.imoptimizer.ImOptimizer;
29
import de.peeeq.wurstscript.translation.imtojass.ImAttrType;
30
import de.peeeq.wurstscript.translation.imtojass.ImToJassTranslator;
31
import de.peeeq.wurstscript.translation.imtranslation.*;
32
import de.peeeq.wurstscript.translation.lua.translation.LuaTranslator;
33
import de.peeeq.wurstscript.types.TypesHelper;
34
import de.peeeq.wurstscript.utils.LineOffsets;
35
import de.peeeq.wurstscript.utils.NotNullList;
36
import de.peeeq.wurstscript.utils.TempDir;
37
import de.peeeq.wurstscript.utils.Utils;
38
import org.eclipse.jdt.annotation.Nullable;
39
import org.eclipse.lsp4j.MessageType;
40
import org.jetbrains.annotations.NotNull;
41

42
import java.io.*;
43
import java.lang.ref.WeakReference;
44
import java.nio.charset.StandardCharsets;
45
import java.util.*;
46
import java.util.Map.Entry;
47
import java.util.function.Function;
48

49
import static de.peeeq.wurstio.CompiletimeFunctionRunner.FunctionFlagToRun.CompiletimeFunctions;
50
import static de.peeeq.wurstscript.WurstOperator.PLUS;
51
import static de.peeeq.wurstscript.translation.imtranslation.FunctionFlagEnum.IS_EXTERN;
52

53
public class WurstCompilerJassImpl implements WurstCompiler {
54

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

74
    public WurstCompilerJassImpl(@Nullable File projectFolder, WurstGui gui, @Nullable MpqEditor mapFileMpq, RunArgs runArgs) {
75
        this(new TimeTaker.Default(), projectFolder, gui, mapFileMpq, runArgs);
1✔
76
    }
1✔
77

78
    public WurstCompilerJassImpl(TimeTaker timeTaker, @Nullable File projectFolder, WurstGui gui, @Nullable MpqEditor mapFileMpq, RunArgs runArgs) {
1✔
79
        this.timeTaker = timeTaker;
1✔
80
        this.projectFolder = projectFolder;
1✔
81
        this.gui = gui;
1✔
82
        this.runArgs = runArgs;
1✔
83
        this.errorHandler = new ErrorHandler(gui);
1✔
84
        this.parser = new WurstParser(errorHandler, gui);
1✔
85
        this.checker = new WurstChecker(gui, errorHandler);
1✔
86
        this.mapFileMpq = mapFileMpq;
1✔
87
    }
1✔
88

89
    @Override
90
    public void loadFiles(String... filenames) {
91
        gui.sendProgress("Loading Files");
×
92
        for (String filename : filenames) {
×
93
            File file = new File(filename);
×
94
            if (!file.exists()) {
×
95
                throw new Error("File " + filename + " does not exist.");
×
96
            }
97
            files.add(file);
×
98
        }
99
    }
×
100

101
    @Override
102
    public void loadFiles(File... files) {
103
        gui.sendProgress("Loading Files");
1✔
104
        for (File file : files) {
1✔
105
            loadFile(file);
1✔
106
        }
107
    }
1✔
108

109
    @Override
110
    public void runCompiletime(WurstProjectConfigData projectConfigData, boolean isProd, boolean cache) {
111
        if (runArgs.runCompiletimeFunctions()) {
1✔
112
            // compile & inject object-editor data
113
            // TODO run optimizations later?
114
            gui.sendProgress("Running compiletime functions");
1✔
115
            CompiletimeFunctionRunner ctr = new CompiletimeFunctionRunner(imTranslator, getImProg(), getMapFile(), getMapfileMpqEditor(), gui,
1✔
116
                CompiletimeFunctions, projectConfigData, isProd, cache);
117
            ctr.setInjectObjects(runArgs.isInjectObjects());
1✔
118
            ctr.setOutputStream(new PrintStream(System.err));
1✔
119
            ctr.run();
1✔
120
        }
121

122
        if (gui.getErrorCount() > 0) {
1✔
123
            CompileError compileError = gui
×
124
                .getErrorList().get(0);
×
125
            throw new RequestFailedException(MessageType.Error, "Could not compile project (error in running compiletime functions/expressions): ", compileError);
×
126
        }
127

128

129
        if (runArgs.isInjectObjects()) {
1✔
130
            Preconditions.checkNotNull(mapFileMpq);
×
131
            Preconditions.checkNotNull(projectFolder);
×
132
            // add the imports
133
            ImportFile.importFilesFromImports(projectFolder, mapFileMpq);
×
134
        }
135
    }
1✔
136

137
    private void loadFile(File file) throws Error {
138
        Preconditions.checkNotNull(file);
1✔
139
        if (!file.exists()) {
1✔
140
            throw new Error("File " + file + " does not exist.");
×
141
        }
142
        this.files.add(file);
1✔
143
    }
1✔
144

145
    public void loadWurstFilesInDir(File dir) {
146
        for (File f : dir.listFiles()) {
1✔
147
            if (f.isDirectory()) {
1✔
148
                loadWurstFilesInDir(f);
1✔
149
            } else if (Utils.isWurstFile(f)) {
1✔
150
                loadFile(f);
×
151
            } else if (f.getName().equals("wurst.dependencies")) {
1✔
152
                dependencies.addAll(checkDependencyFile(f, gui));
1✔
153
            } else if ((!mapFile.isPresent() || runArgs.isNoExtractMapScript()) && f.getName().equals("war3map.j")) {
×
154
                loadFile(f);
×
155
            }
156
        }
157
    }
1✔
158

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

194
    @Override
195
    public @Nullable WurstModel parseFiles() {
196

197
        // search mapFile
198
        for (File file : files) {
1✔
199
            if (file.getName().endsWith(".w3x") || file.getName().endsWith(".w3m")) {
1✔
200
                mapFile = Optional.ofNullable(file);
×
201
            } else if (file.isDirectory()) {
1✔
202
                if (projectFolder != null && !file.getParent().equals(projectFolder.getAbsolutePath())) {
×
203
                    throw new RuntimeException("Cannot set projectFolder to " + file + " because it is already set to non parent " + projectFolder);
×
204
                }
205
                projectFolder = file;
×
206
            }
207
        }
1✔
208

209

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

230
        // add directories:
231
        List<File> dirs = Lists.newArrayList();
1✔
232
        for (File file : files) {
1✔
233
            if (file.isDirectory()) {
1✔
234
                dirs.add(file);
×
235
            }
236
        }
1✔
237
        for (File dir : dirs) {
1✔
238
            loadWurstFilesInDir(dir);
×
239
        }
×
240

241
        gui.sendProgress("Parsing Files");
1✔
242
        // parse all the files:
243
        List<CompilationUnit> compilationUnits = new NotNullList<>();
1✔
244

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

264
        try {
265
            addImportedLibs(compilationUnits);
1✔
266
        } catch (CompileError e) {
×
267
            gui.sendError(e);
×
268
            return null;
×
269
        }
1✔
270

271
        if (errorHandler.getErrorCount() > 0)
1✔
272
            return null;
1✔
273

274
        // merge the compilationUnits:
275
        WurstModel merged = mergeCompilationUnits(compilationUnits);
1✔
276
        StringBuilder sb = new StringBuilder();
1✔
277
        for (CompilationUnit cu : merged) {
1✔
278
            sb.append(cu.getCuInfo().getFile()).append(", ");
1✔
279
        }
1✔
280
        WLogger.info("Compiling compilation units: " + sb);
1✔
281

282
        return merged;
1✔
283
    }
284

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

301
    private void addImportedLibs(List<CompilationUnit> compilationUnits) {
302
        addImportedLibs(compilationUnits, file -> {
1✔
303
            CompilationUnit lib = parseFile(file);
1✔
304
            lib.getCuInfo().setFile(file.getAbsolutePath());
1✔
305
            compilationUnits.add(lib);
1✔
306
            return lib;
1✔
307
        });
308
    }
1✔
309

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

324
        for (WImport imp : imports) {
1✔
325
            resolveImport(addCompilationUnit, packages, imp);
1✔
326
        }
1✔
327

328
    }
1✔
329

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

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

374
    public Map<String, File> getLibs() {
375
        Map<String, File> lc = libCache;
1✔
376
        if (lc == null) {
1✔
377
            lc = Maps.newLinkedHashMap();
1✔
378
            libCache = lc;
1✔
379
            for (File libDir : runArgs.getAdditionalLibDirs()) {
1✔
380
                addLibDir(libDir);
1✔
381
            }
1✔
382
            for (File libDir : dependencies) {
1✔
383
                addLibDir(libDir);
×
384
            }
×
385
        }
386
        return lc;
1✔
387
    }
388

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

405
    public void checkProg(WurstModel model) {
406
        checkProg(model, model);
1✔
407
    }
1✔
408

409
    public void checkProg(WurstModel model, Collection<CompilationUnit> toCheck) {
410
        for (CompilationUnit cu : toCheck) {
1✔
411
            Preconditions.checkNotNull(cu);
1✔
412
            if (!model.contains(cu)) {
1✔
413
                // model has changed since then, no need to do 'toCheck'
414
                throw new ModelChangedException();
×
415
            }
416
        }
1✔
417

418
        checker.checkProg(model, toCheck);
1✔
419
    }
1✔
420

421
    public JassProg transformProgToJass() {
422
        ImTranslator imTranslator2 = getImTranslator();
1✔
423
        ImProg imProg2 = getImProg();
1✔
424
        imTranslator2.assertProperties();
1✔
425
        checkNoCompiletimeExpr(imProg2);
1✔
426
        int stage = 2;
1✔
427
        // eliminate
428
        beginPhase(2, "Eliminate generics");
1✔
429
        new EliminateGenerics(imTranslator2, imProg2).transform();
1✔
430
        printDebugImProg("./test-output/im " + stage++ + "_genericsEliminated.im");
1✔
431
        timeTaker.endPhase();
1✔
432
        // eliminate classes
433
        beginPhase(2, "translate classes");
1✔
434

435
        new EliminateClasses(imTranslator2, imProg2, !runArgs.isUncheckedDispatch()).eliminateClasses();
1✔
436
        imTranslator2.assertProperties();
1✔
437
        printDebugImProg("./test-output/im " + stage++ + "_classesEliminated.im");
1✔
438
        timeTaker.endPhase();
1✔
439

440
        new VarargEliminator(imProg2).run();
1✔
441
        printDebugImProg("./test-output/im " + stage++ + "_varargEliminated.im");
1✔
442
        imTranslator2.assertProperties();
1✔
443

444
        timeTaker.endPhase();
1✔
445

446
        if (runArgs.isNoDebugMessages()) {
1✔
447
            beginPhase(3, "remove debug messages");
×
448
            DebugMessageRemover.removeDebugMessages(imProg2);
×
449
            timeTaker.endPhase();
×
450
        } else {
451
            // debug: add stacktraces
452
            if (runArgs.isIncludeStacktraces()) {
1✔
453
                beginPhase(4, "add stack traces");
1✔
454
                new StackTraceInjector2(imProg2, imTranslator2).transform(timeTaker);
1✔
455
                timeTaker.endPhase();
1✔
456
            }
457
        }
458
        imTranslator2.assertProperties();
1✔
459

460
        ImOptimizer optimizer = new ImOptimizer(timeTaker, imTranslator2);
1✔
461

462
        // inliner
463
        if (runArgs.isInline()) {
1✔
464
            beginPhase(5, "inlining");
1✔
465
            optimizer.doInlining();
1✔
466
            imTranslator2.assertProperties();
1✔
467

468
            printDebugImProg("./test-output/im " + stage++ + "_afterinline.im");
1✔
469
            timeTaker.endPhase();
1✔
470
        }
471

472
        // eliminate tuples
473
        beginPhase(6, "eliminate tuples");
1✔
474
        timeTaker.beginPhase("flatten");
1✔
475
        getImProg().flatten(imTranslator2);
1✔
476
        timeTaker.endPhase();
1✔
477
        timeTaker.beginPhase("kill tuples");
1✔
478
        EliminateTuples.eliminateTuplesProg(getImProg(), imTranslator2);
1✔
479
        timeTaker.endPhase();
1✔
480

481
        getImTranslator().assertProperties(AssertProperty.NOTUPLES);
1✔
482

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

485
        timeTaker.endPhase();
1✔
486

487
        beginPhase(7, "eliminate multi arrays");
1✔
488
        new MultiArrayEliminator(imProg2, imTranslator2, runArgs.isIncludeStacktraces() && !runArgs.isNoDebugMessages()).run();
1✔
489
        printDebugImProg("./test-output/im " + stage++ + "_withoutmultiarrays.im");
1✔
490
        imTranslator2.assertProperties();
1✔
491
        timeTaker.endPhase();
1✔
492

493
        beginPhase(8, "remove func refs");
1✔
494
        new FuncRefRemover(imProg2, imTranslator2).run();
1✔
495
        timeTaker.endPhase();
1✔
496

497
        // remove cycles:
498
        beginPhase(9, "remove cyclic functions");
1✔
499
        new CyclicFunctionRemover(imTranslator2, imProg2, timeTaker).work();
1✔
500

501
        printDebugImProg("./test-output/im " + stage++ + "_nocyc.im");
1✔
502
        timeTaker.endPhase();
1✔
503

504
        // flatten
505
        beginPhase(10, "flatten");
1✔
506
        getImProg().flatten(imTranslator2);
1✔
507
        getImTranslator().assertProperties(AssertProperty.NOTUPLES, AssertProperty.FLAT);
1✔
508

509
        printDebugImProg("./test-output/im " + stage++ + "_flat.im");
1✔
510
        timeTaker.endPhase();
1✔
511

512
        if (runArgs.isLocalOptimizations()) {
1✔
513
            beginPhase(11, "local optimizations");
1✔
514
            optimizer.localOptimizations();
1✔
515
            timeTaker.endPhase();
1✔
516
        }
517

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

520
        if (runArgs.isNullsetting()) {
1✔
521
            beginPhase(12, "null setting");
×
522
            optimizer.doNullsetting();
×
523
            printDebugImProg("./test-output/im " + stage++ + "_afternullsetting.im");
×
524
            timeTaker.endPhase();
×
525
        }
526

527
        beginPhase(13, "flatten");
1✔
528
        optimizer.removeGarbage();
1✔
529
        imProg.flatten(imTranslator);
1✔
530

531
        // Re-run to avoid #883
532
        optimizer.removeGarbage();
1✔
533
        imProg.flatten(imTranslator);
1✔
534

535
        printDebugImProg("./test-output/im " + stage++ + "_afterremoveGarbage1.im");
1✔
536
        timeTaker.endPhase();
1✔
537

538
        if (runArgs.isHotStartmap() || runArgs.isHotReload()) {
1✔
539
            addJassHotCodeReloadCode();
×
540
        }
541
        if (runArgs.isOptimize()) {
1✔
542
            beginPhase(13, "froptimize");
×
543
            optimizer.optimize();
×
544

545
            optimizer.removeGarbage();
×
546
            imProg.flatten(imTranslator);
×
547
            printDebugImProg("./test-output/im " + stage++ + "_afteroptimize.im");
×
548
        }
549

550

551
        // translate flattened intermediate lang to jass:
552

553
        beginPhase(14, "translate to jass");
1✔
554
        getImTranslator().calculateCallRelationsAndUsedVariables();
1✔
555
        ImToJassTranslator translator =
1✔
556
            new ImToJassTranslator(getImProg(), getImTranslator().getCalledFunctions(), getImTranslator().getMainFunc(), getImTranslator().getConfFunc());
1✔
557
        prog = translator.translate();
1✔
558
        if (errorHandler.getErrorCount() > 0) {
1✔
559
            prog = null;
×
560
        }
561
        timeTaker.endPhase();
1✔
562
        return prog;
1✔
563
    }
564

565
    private void addJassHotCodeReloadCode() {
566
        Preconditions.checkNotNull(imTranslator);
×
567
        Preconditions.checkNotNull(imProg);
×
568
        ImFunction mainFunc = imTranslator.getMainFunc();
×
569
        Preconditions.checkNotNull(mainFunc);
×
570
        Element trace = imProg.getTrace();
×
571

572
        List<ImStmt> stmts = new ArrayList<>();
×
573

574
        // add call to JHCR_Init_init in main
575
        stmts.add(callExtern(trace, CallType.EXECUTE, "JHCR_Init_init"));
×
576

577
        ImFunction statusFunction = JassIm.ImFunction(trace, "JHCR_API_GetLastStatus", JassIm.ImTypeVars(),
×
578
            JassIm.ImVars(), JassIm.ImSimpleType("integer"), JassIm.ImVars(), JassIm.ImStmts(JassIm.ImReturn(trace, JassIm.ImIntVal(0))),
×
579
                List.of());
×
580

581
        imProg.getFunctions().add(statusFunction);
×
582

583
        ImFunctionCall jhcrStatusCall = JassIm.ImFunctionCall(trace, statusFunction, JassIm.ImTypeArguments(), JassIm.ImExprs(), false, CallType.NORMAL);
×
584
        ImFunction I2S = findNative("I2S", trace.attrErrorPos());
×
585
        ImFunctionCall statusCall = JassIm.ImFunctionCall(trace, I2S, JassIm.ImTypeArguments(), JassIm.ImExprs(jhcrStatusCall), false, CallType.NORMAL);
×
586

587

588
        // add reload trigger for pressing escape
589
        ImStmts reloadBody = JassIm.ImStmts(
×
590
            callExtern(trace, CallType.EXECUTE, "JHCR_Init_parse"),
×
591
            callExtern(trace, CallType.NORMAL, "BJDebugMsg", JassIm.ImOperatorCall(PLUS, JassIm.ImExprs(JassIm.ImStringVal("Code reloaded, status: "), statusCall)))
×
592
            );
593
        ImFunction jhcr_reload = JassIm.ImFunction(trace, "jhcr_reload_on_escape", JassIm.ImTypeVars(), JassIm.ImVars(), JassIm.ImVoid(), JassIm.ImVars(), reloadBody, Collections.emptyList());
×
594

595

596
        ImVar trig = JassIm.ImVar(trace, TypesHelper.imTrigger(), "trig", false);
×
597
        mainFunc.getLocals().add(trig);
×
598
        // TriggerRegisterPlayerEventEndCinematic(trig, Player(0))
599
        stmts.add(JassIm.ImSet(trace, JassIm.ImVarAccess(trig), callExtern(trace, CallType.NORMAL, "CreateTrigger")));
×
600
        stmts.add(callExtern(trace, CallType.NORMAL, "TriggerRegisterPlayerEventEndCinematic", JassIm.ImVarAccess(trig),
×
601
            callExtern(trace, CallType.NORMAL, "Player", JassIm.ImIntVal(0))));
×
602
        stmts.add(callExtern(trace, CallType.NORMAL, "TriggerAddAction", JassIm.ImVarAccess(trig),
×
603
            JassIm.ImFuncRef(trace, jhcr_reload)));
×
604

605
        mainFunc.getBody().addAll(0, stmts);
×
606
    }
×
607

608
    @NotNull
609
    private ImFunction findNative(String funcName, WPos trace) {
610
        return imProg.getFunctions()
×
611
            .stream()
×
612
            .filter(ImFunction::isNative)
×
613
            .filter(func -> func.getName().equals(funcName))
×
614
            .findFirst()
×
615
            .orElseGet(() -> {
×
616
                throw new CompileError(trace, "Could not find native " + funcName);
×
617
            });
618
    }
619

620
    @NotNull
621
    private ImFunction findFunction(String funcName, WPos trace) {
622
        return imProg.getFunctions()
×
623
            .stream()
×
624
            .filter(func -> func.getName().equals(funcName))
×
625
            .findFirst()
×
626
            .orElseGet(() -> {
×
627
                throw new CompileError(trace, "Could not find native " + funcName);
×
628
            });
629
    }
630

631
    @NotNull
632
    private ImFunctionCall callExtern(Element trace, CallType callType, String functionName, ImExpr... arguments) {
633
        ImFunction jhcrinit = JassIm.ImFunction(trace, functionName, JassIm.ImTypeVars(), JassIm.ImVars(), JassIm.ImVoid(), JassIm.ImVars(), JassIm.ImStmts(), Collections.singletonList(IS_EXTERN));
×
634
        return JassIm.ImFunctionCall(trace, jhcrinit, JassIm.ImTypeArguments(), JassIm.ImExprs(arguments), true, callType);
×
635
    }
636

637
    public void checkNoCompiletimeExpr(ImProg prog) {
638
        prog.accept(new ImProg.DefaultVisitor() {
1✔
639
            @Override
640
            public void visit(ImCompiletimeExpr e) {
641
                super.visit(e);
×
642
                throw new CompileError(e.attrTrace().attrSource(), "Compiletime expressions require compilation with '-runcompiletimefunctions' option.");
×
643
            }
644
        });
645
    }
1✔
646

647
    public ImTranslator getImTranslator() {
648
        final ImTranslator t = imTranslator;
1✔
649
        if (t != null) {
1✔
650
            return t;
1✔
651
        } else {
652
            throw new Error("translator not initialized");
×
653
        }
654
    }
655

656
    public @Nullable ImProg translateProgToIm(WurstModel root) {
657
        beginPhase(1, "to intermediate lang");
1✔
658
        // translate wurst to intermediate lang:
659
        imTranslator = new ImTranslator(root, errorHandler.isUnitTestMode(), runArgs);
1✔
660
        imProg = getImTranslator().translateProg();
1✔
661
        int stage = 1;
1✔
662
        printDebugImProg("./test-output/im " + stage++ + ".im");
1✔
663
        timeTaker.endPhase();
1✔
664
        return imProg;
1✔
665
    }
666

667
    private void beginPhase(int phase, String description) {
668
        errorHandler.setProgress("Translating wurst. Phase " + phase + ": " + description, 0.6 + 0.01 * phase);
1✔
669
        timeTaker.beginPhase(description);
1✔
670
    }
1✔
671

672
    private void printDebugImProg(String debugFile) {
673
        if (!errorHandler.isUnitTestMode()) {
1✔
674
            // output only in unit test mode
675
            return;
×
676
        }
677
        try {
678
            // TODO remove test output
679
            File file = new File(debugFile);
1✔
680
            file.getParentFile().mkdirs();
1✔
681
            try (Writer w = Files.newWriter(file, Charsets.UTF_8)) {
1✔
682
                getImProg().print(w, 0);
1✔
683
            }
684
        } catch (IOException e) {
×
685
            ErrorReporting.instance.handleSevere(e, getCompleteSourcecode());
×
686
        }
1✔
687
    }
1✔
688

689
    private WurstModel mergeCompilationUnits(List<CompilationUnit> compilationUnits) {
690
        gui.sendProgress("Merging Files");
1✔
691
        WurstModel result = Ast.WurstModel();
1✔
692
        for (CompilationUnit compilationUnit : compilationUnits) {
1✔
693
            // remove from old parent
694
            compilationUnit.setParent(null);
1✔
695
            result.add(compilationUnit);
1✔
696
        }
1✔
697
        return result;
1✔
698
    }
699

700
    private CompilationUnit processMap(File file) {
701
        gui.sendProgress("Processing Map " + file.getName());
×
702
        if (!mapFile.isPresent() || !file.equals(mapFile.get())) {
×
703
            // TODO check if file != mapFile is possible, would be strange
704
            // so this should definitely be done differently
705
            throw new Error("file: " + file + " is not the mapfile: " + mapFile);
×
706
        }
707

708
        MpqEditor mapMpq = mapFileMpq;
×
709
        if (mapMpq == null) {
×
710
            throw new RuntimeException("map mpq is null");
×
711
        }
712

713
        if (runArgs.isNoExtractMapScript()) {
×
714
            return null;
×
715
        }
716

717
        // extract mapscript:
718
        try {
719
            byte[] tempBytes = mapMpq.extractFile("war3map.j");
×
720
            File tempFile = File.createTempFile("war3map", ".j", TempDir.get()); // TODO work directly with bytes without temp file
×
721
            tempFile.deleteOnExit();
×
722
            Files.write(tempBytes, tempFile);
×
723

724
            if (isWurstGenerated(tempFile)) {
×
725
                // the war3map.j file was generated by wurst
726
                // this should not be the case, as we will get duplicate function errors in this case
727
                throw new AbortCompilationException(
×
728
                    "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"
729
                        + "if you have used the test-map-button without saving the map first.");
730
            }
731

732
            // move file to wurst directory
733
            File wurstFolder = new File(file.getParentFile(), "wurst");
×
734
            wurstFolder.mkdirs();
×
735
            if (!wurstFolder.isDirectory()) {
×
736
                throw new AbortCompilationException("Could not create Wurst folder at " + wurstFolder + ".");
×
737
            }
738
            File wurstwar3map = new File(wurstFolder, "war3map.j");
×
739
            wurstwar3map.delete();
×
740
            if (tempFile.renameTo(wurstwar3map)) {
×
741
                return parseFile(wurstwar3map);
×
742
            } else {
743
                throw new Error("Could not move war3map.j from " + tempFile + " to " + wurstwar3map);
×
744
            }
745
        } catch (RuntimeException e) {
×
746
            throw e;
×
747
        } catch (Exception e) {
×
748
            throw new Error(e);
×
749
        }
750

751
    }
752

753
    private boolean isWurstGenerated(File tempFile) {
754
        try (FileReader fr = new FileReader(tempFile); BufferedReader in = new BufferedReader(fr)) {
×
755
            String firstLine = in.readLine();
×
756
            WLogger.info("firstLine = '" + firstLine + "'");
×
757
            return firstLine.equals(JassPrinter.WURST_COMMENT);
×
758
        } catch (IOException e) {
×
759
            WLogger.severe(e);
×
760
        }
761
        return false;
×
762
    }
763

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

767
    private CompilationUnit parseFile(File file) {
768
        if (errorHandler.isUnitTestMode()) {
1✔
769
            // in unit test mode, we use a cache
770
            WeakReference<CompilationUnit> wr = fileCompilationUnitCache.get(file);
1✔
771
            CompilationUnit res = wr == null ? null : wr.get();
1✔
772
            if (res == null) {
1✔
773
                res = parseFile2(file);
1✔
774
                fileCompilationUnitCache.put(file, new WeakReference<>(res));
1✔
775
            } else {
776
                res = res.copy();
1✔
777
            }
778
            return res;
1✔
779
        } else {
780
            return parseFile2(file);
×
781
        }
782
    }
783

784
    private CompilationUnit parseFile2(File file) {
785
        if (file.isDirectory()) {
1✔
786
            throw new Error("Is a directory: " + file);
×
787
        }
788
        parsedFiles.add(file);
1✔
789

790
        gui.sendProgress("Parsing File " + file.getName());
1✔
791
        String source = file.getAbsolutePath();
1✔
792
        try (Reader reader = FileReading.getFileReader(file)) {
1✔
793
            // scanning
794
            return parse(source, reader);
1✔
795

796
        } catch (CompileError e) {
×
797
            gui.sendError(e);
×
798
            return emptyCompilationUnit();
×
799
        } catch (FileNotFoundException e) {
×
800
            gui.sendError(new CompileError(new WPos(source, LineOffsets.dummy, 0, 0), "File not found."));
×
801
            return emptyCompilationUnit();
×
802
        } catch (IOException e) {
×
803
            gui.sendError(new CompileError(new WPos(source, LineOffsets.dummy, 0, 0), "Could not read file."));
×
804
            return emptyCompilationUnit();
×
805
        }
806
    }
807

808
    public CompilationUnit parse(String fileName, Reader reader) {
809
        if (fileName.endsWith(".j")) {
1✔
810
            return parser.parseJass(reader, fileName, hasCommonJ);
1✔
811
        }
812
        if (fileName.endsWith(".jurst")) {
1✔
813
            return parser.parseJurst(reader, fileName, hasCommonJ);
1✔
814
        }
815
        if (runArgs.isPrettyPrint()) {
1✔
816
            parser.setRemoveSugar(false);
1✔
817
        }
818
        return parser.parse(reader, fileName, hasCommonJ);
1✔
819
    }
820

821
    private CompilationUnit emptyCompilationUnit() {
822
        return parser.emptyCompilationUnit();
×
823
    }
824

825
    public @Nullable JassProg getProg() {
826
        return prog;
×
827
    }
828

829
    public void loadReader(String name, Reader input) {
830
        otherInputs.put(name, input);
1✔
831
    }
1✔
832

833
    public void setHasCommonJ(boolean hasCommonJ) {
834
        this.hasCommonJ = hasCommonJ;
1✔
835
    }
1✔
836

837
    public ImProg getImProg() {
838
        final ImProg imProg2 = imProg;
1✔
839
        if (imProg2 != null) {
1✔
840
            return imProg2;
1✔
841
        } else {
842
            throw new Error("imProg is null");
×
843
        }
844
    }
845

846
    public Optional<File> getMapFile() {
847
        return mapFile;
1✔
848
    }
849

850
    public ErrorHandler getErrorHandler() {
851
        return errorHandler;
1✔
852
    }
853

854
    public String getCompleteSourcecode() {
855

856
        StringBuilder sb = new StringBuilder();
×
857
        try {
858
            for (File f : parsedFiles) {
×
859
                sb.append(" //######################################################\n");
×
860
                sb.append(" // File ").append(f.getAbsolutePath()).append("\n");
×
861
                sb.append(" //######################################################\n");
×
862
                sb.append(Files.toString(f, Charsets.UTF_8));
×
863
            }
×
864

865
            for (Entry<String, Reader> entry : otherInputs.entrySet()) {
×
866
                sb.append(" //######################################################\n");
×
867
                sb.append(" // Input ").append(entry.getKey()).append("\n");
×
868
                sb.append(" //######################################################\n");
×
869
                try (Reader reader = entry.getValue()) {
×
870
                    char[] buffer = new char[1024];
×
871
                    while (true) {
872
                        int len = reader.read(buffer);
×
873
                        if (len < 0) {
×
874
                            break;
×
875
                        }
876
                        sb.append(buffer, 0, len);
×
877
                    }
×
878
                }
879
            }
×
880
        } catch (Throwable t) {
×
881
            sb.append(Utils.printExceptionWithStackTrace(t));
×
882
            WLogger.severe(t);
×
883
        }
×
884
        return sb.toString();
×
885
    }
886

887
    public void setRunArgs(RunArgs runArgs) {
888
        this.runArgs = runArgs;
1✔
889
    }
1✔
890

891
    public void setMapFile(Optional<File> mapFile) {
892
        this.mapFile = mapFile;
×
893
    }
×
894

895
    public @Nullable MpqEditor getMapfileMpqEditor() {
896
        return mapFileMpq;
1✔
897
    }
898

899
    public LuaCompilationUnit transformProgToLua() {
900

901
        ImAttrType.setWurstClassType(null);
1✔
902
        int stage;
903
        if (runArgs.isNoDebugMessages()) {
1✔
904
            beginPhase(3, "remove debug messages");
×
905
            DebugMessageRemover.removeDebugMessages(imProg);
×
906
            timeTaker.endPhase();
×
907
        } else {
908
            // debug: add stacktraces
909
            if (runArgs.isIncludeStacktraces()) {
1✔
910
                beginPhase(4, "add stack traces");
×
911
                new StackTraceInjector2(imProg, imTranslator).transform(timeTaker);
×
912
                timeTaker.endPhase();
×
913
            }
914
        }
915
        ImTranslator imTranslator2 = getImTranslator();
1✔
916
        ImOptimizer optimizer = new ImOptimizer(timeTaker, imTranslator2);
1✔
917
        // inliner
918
        stage = 5;
1✔
919
        if (runArgs.isInline()) {
1✔
920
            beginPhase(5, "inlining");
×
921
            optimizer.doInlining();
×
922
            imTranslator2.assertProperties();
×
923

924
            printDebugImProg("./test-output/lua/im " + stage++ + "_afterinline.im");
×
925
            timeTaker.endPhase();
×
926
        }
927

928
        // eliminate local types
929
        beginPhase(6, "eliminate local type");
1✔
930
        getImProg().flatten(imTranslator2);
1✔
931
        EliminateLocalTypes.eliminateLocalTypesProg(getImProg(), imTranslator2);
1✔
932

933
        optimizer.removeGarbage();
1✔
934
        imProg.flatten(imTranslator);
1✔
935
        timeTaker.endPhase();
1✔
936
        stage = 10;
1✔
937
        if (runArgs.isLocalOptimizations()) {
1✔
938
            beginPhase(10, "local optimizations");
×
939
            optimizer.localOptimizations();
×
940
            timeTaker.endPhase();
×
941
        }
942

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

945
        optimizer.removeGarbage();
1✔
946
        imProg.flatten(imTranslator);
1✔
947

948
        // Re-run to avoid #883
949
        optimizer.removeGarbage();
1✔
950
        imProg.flatten(imTranslator);
1✔
951

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

954
        stage = 12;
1✔
955
        if (runArgs.isOptimize()) {
1✔
956
            beginPhase(12, "froptimize");
×
957
            optimizer.optimize();
×
958

959
            optimizer.removeGarbage();
×
960
            imProg.flatten(imTranslator);
×
961
            printDebugImProg("./test-output/lua/im " + stage++ + "_afteroptimize.im");
×
962
            timeTaker.endPhase();
×
963
        }
964
        beginPhase(13, "translate to lua");
1✔
965
        LuaTranslator luaTranslator = new LuaTranslator(imProg, imTranslator);
1✔
966
        LuaCompilationUnit luaCode = luaTranslator.translate();
1✔
967
        ImAttrType.setWurstClassType(TypesHelper.imInt());
1✔
968
        timeTaker.endPhase();
1✔
969
        return luaCode;
1✔
970
    }
971
}
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