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

wurstscript / WurstScript / 228

29 Nov 2023 05:00PM UTC coverage: 62.48% (-0.09%) from 62.574%
228

push

circleci

web-flow
Show dialog for choosing game path, cleanup (#1083)

* show dialog for choosing game path

* cleanup code

* remove logs and refactor

* remove confusing mpq error, make some mpq loads readonly

17295 of 27681 relevant lines covered (62.48%)

0.62 hits per line

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

55.19
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.Element;
18
import de.peeeq.wurstscript.ast.*;
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

51
public class WurstCompilerJassImpl implements WurstCompiler {
52

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

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

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

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

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

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

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

126

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

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

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

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

192
    @Override
193
    public @Nullable WurstModel parseFiles() {
194

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

207

208

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

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

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

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

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

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

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

281
        return merged;
1✔
282
    }
283

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

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

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

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

327
    }
1✔
328

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

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

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

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

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

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

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

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

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

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

443
        timeTaker.endPhase();
1✔
444

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

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

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

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

471
        // eliminate tuples
472
        beginPhase(6, "eliminate tuples");
1✔
473
        timeTaker.measure("flatten", () -> getImProg().flatten(imTranslator2));
1✔
474
        timeTaker.measure("kill tuples", () -> EliminateTuples.eliminateTuplesProg(getImProg(), imTranslator2));
1✔
475
        getImTranslator().assertProperties(AssertProperty.NOTUPLES);
1✔
476

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

479
        timeTaker.endPhase();
1✔
480

481
        beginPhase(7, "eliminate multi arrays");
1✔
482
        new MultiArrayEliminator(imProg2, imTranslator2, runArgs.isIncludeStacktraces() && !runArgs.isNoDebugMessages()).run();
1✔
483
        printDebugImProg("./test-output/im " + stage++ + "_withoutmultiarrays.im");
1✔
484
        imTranslator2.assertProperties();
1✔
485
        timeTaker.endPhase();
1✔
486

487
        beginPhase(8, "remove func refs");
1✔
488
        new FuncRefRemover(imProg2, imTranslator2).run();
1✔
489
        timeTaker.endPhase();
1✔
490

491
        // remove cycles:
492
        beginPhase(9, "remove cyclic functions");
1✔
493
        new CyclicFunctionRemover(imTranslator2, imProg2, timeTaker).work();
1✔
494

495
        printDebugImProg("./test-output/im " + stage++ + "_nocyc.im");
1✔
496
        timeTaker.endPhase();
1✔
497

498
        // flatten
499
        beginPhase(10, "flatten");
1✔
500
        getImProg().flatten(imTranslator2);
1✔
501
        getImTranslator().assertProperties(AssertProperty.NOTUPLES, AssertProperty.FLAT);
1✔
502

503
        printDebugImProg("./test-output/im " + stage++ + "_flat.im");
1✔
504
        timeTaker.endPhase();
1✔
505

506
        if (runArgs.isLocalOptimizations()) {
1✔
507
            beginPhase(11, "local optimizations");
1✔
508
            optimizer.localOptimizations();
1✔
509
            timeTaker.endPhase();
1✔
510
        }
511

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

514
        if (runArgs.isNullsetting()) {
1✔
515
            beginPhase(12, "null setting");
×
516
            optimizer.doNullsetting();
×
517
            printDebugImProg("./test-output/im " + stage++ + "_afternullsetting.im");
×
518
            timeTaker.endPhase();
×
519
        }
520

521
        beginPhase(13, "flatten");
1✔
522
        optimizer.removeGarbage();
1✔
523
        imProg.flatten(imTranslator);
1✔
524

525
        // Re-run to avoid #883
526
        optimizer.removeGarbage();
1✔
527
        imProg.flatten(imTranslator);
1✔
528

529
        printDebugImProg("./test-output/im " + stage++ + "_afterremoveGarbage1.im");
1✔
530
        timeTaker.endPhase();
1✔
531

532
        if (runArgs.isHotStartmap() || runArgs.isHotReload()) {
1✔
533
            addJassHotCodeReloadCode();
×
534
        }
535
        if (runArgs.isOptimize()) {
1✔
536
            beginPhase(13, "froptimize");
×
537
            optimizer.optimize();
×
538

539
            optimizer.removeGarbage();
×
540
            imProg.flatten(imTranslator);
×
541
            printDebugImProg("./test-output/im " + stage++ + "_afteroptimize.im");
×
542
        }
543

544

545

546
        // translate flattened intermediate lang to jass:
547

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

560
    private void addJassHotCodeReloadCode() {
561
        Preconditions.checkNotNull(imTranslator);
×
562
        Preconditions.checkNotNull(imProg);
×
563
        ImFunction mainFunc = imTranslator.getMainFunc();
×
564
        Preconditions.checkNotNull(mainFunc);
×
565
        Element trace = imProg.getTrace();
×
566

567
        List<ImStmt> stmts = new ArrayList<>();
×
568

569
        // add call to JHCR_Init_init in main
570
        stmts.add(callExtern(trace, CallType.EXECUTE, "JHCR_Init_init"));
×
571

572
        ImFunction statusFunc = findFunction("JHCR_API_GetLastStatus", trace.attrErrorPos());
×
573
        ImFunctionCall jhcrStatusCall = JassIm.ImFunctionCall(trace, statusFunc, JassIm.ImTypeArguments(), JassIm.ImExprs(), false, CallType.NORMAL);
×
574
        ImFunction I2S = findNative("I2S", trace.attrErrorPos());
×
575
        ImFunctionCall statusCall = JassIm.ImFunctionCall(trace, I2S, JassIm.ImTypeArguments(), JassIm.ImExprs(jhcrStatusCall), false, CallType.NORMAL);
×
576

577

578
        // add reload trigger for pressing escape
579
        ImStmts reloadBody = JassIm.ImStmts(
×
580
                callExtern(trace, CallType.EXECUTE, "JHCR_Init_parse")
×
581
//            callExtern(trace, CallType.NORMAL, "BJDebugMsg", JassIm.ImOperatorCall(PLUS, JassIm.ImExprs(JassIm.ImStringVal("Code reloaded, status: "), statusCall))
582
        );
583
        ImFunction jhcr_reload = JassIm.ImFunction(trace, "jhcr_reload_on_escape", JassIm.ImTypeVars(), JassIm.ImVars(), JassIm.ImVoid(), JassIm.ImVars(), reloadBody, Collections.emptyList());
×
584

585

586
        ImVar trig = JassIm.ImVar(trace, TypesHelper.imTrigger(), "trig", false);
×
587
        mainFunc.getLocals().add(trig);
×
588
        // TriggerRegisterPlayerEventEndCinematic(trig, Player(0))
589
        stmts.add(JassIm.ImSet(trace, JassIm.ImVarAccess(trig), callExtern(trace, CallType.NORMAL, "CreateTrigger")));
×
590
        stmts.add(callExtern(trace, CallType.NORMAL, "TriggerRegisterPlayerEventEndCinematic", JassIm.ImVarAccess(trig),
×
591
                callExtern(trace, CallType.NORMAL, "Player", JassIm.ImIntVal(0))));
×
592
        stmts.add(callExtern(trace, CallType.NORMAL, "TriggerAddAction", JassIm.ImVarAccess(trig),
×
593
                JassIm.ImFuncRef(trace, jhcr_reload)));
×
594

595
        mainFunc.getBody().addAll(0, stmts);
×
596
    }
×
597

598
    @NotNull
599
    private ImFunction findNative(String funcName, WPos trace) {
600
        return imProg.getFunctions()
×
601
            .stream()
×
602
            .filter(ImFunction::isNative)
×
603
            .filter(func -> func.getName().equals(funcName))
×
604
            .findFirst()
×
605
            .orElseGet(() -> {
×
606
                throw new CompileError(trace, "Could not find native " + funcName);
×
607
            });
608
    }
609

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

621
    @NotNull
622
    private ImFunctionCall callExtern(Element trace, CallType callType, String functionName, ImExpr... arguments) {
623
        ImFunction jhcrinit = JassIm.ImFunction(trace, functionName, JassIm.ImTypeVars(), JassIm.ImVars(), JassIm.ImVoid(), JassIm.ImVars(), JassIm.ImStmts(), Collections.singletonList(FunctionFlagEnum.IS_EXTERN));
×
624
        return JassIm.ImFunctionCall(trace, jhcrinit, JassIm.ImTypeArguments(), JassIm.ImExprs(arguments), true, callType);
×
625
    }
626

627
    public void checkNoCompiletimeExpr(ImProg prog) {
628
        prog.accept(new ImProg.DefaultVisitor() {
1✔
629
            @Override
630
            public void visit(ImCompiletimeExpr e) {
631
                super.visit(e);
×
632
                throw new CompileError(e.attrTrace().attrSource(), "Compiletime expressions require compilation with '-runcompiletimefunctions' option.");
×
633
            }
634
        });
635
    }
1✔
636

637
    public ImTranslator getImTranslator() {
638
        final ImTranslator t = imTranslator;
1✔
639
        if (t != null) {
1✔
640
            return t;
1✔
641
        } else {
642
            throw new Error("translator not initialized");
×
643
        }
644
    }
645

646
    public @Nullable ImProg translateProgToIm(WurstModel root) {
647
        beginPhase(1, "to intermediate lang");
1✔
648
        // translate wurst to intermediate lang:
649
        imTranslator = new ImTranslator(root, errorHandler.isUnitTestMode(), runArgs);
1✔
650
        imProg = getImTranslator().translateProg();
1✔
651
        int stage = 1;
1✔
652
        printDebugImProg("./test-output/im " + stage++ + ".im");
1✔
653
        timeTaker.endPhase();
1✔
654
        return imProg;
1✔
655
    }
656

657
    private void beginPhase(int phase, String description) {
658
        errorHandler.setProgress("Translating wurst. Phase " + phase + ": " + description, 0.6 + 0.01 * phase);
1✔
659
        timeTaker.beginPhase(description);
1✔
660
    }
1✔
661

662
    private void printDebugImProg(String debugFile) {
663
        if (!errorHandler.isUnitTestMode() ) {
1✔
664
            // output only in unit test mode
665
            return;
×
666
        }
667
        try {
668
            // TODO remove test output
669
            File file = new File(debugFile);
1✔
670
            file.getParentFile().mkdirs();
1✔
671
            try (Writer w = Files.newWriter(file, Charsets.UTF_8)) {
1✔
672
                getImProg().print(w, 0);
1✔
673
            }
674
        } catch (IOException e) {
×
675
            ErrorReporting.instance.handleSevere(e, getCompleteSourcecode());
×
676
        }
1✔
677
    }
1✔
678

679
    private WurstModel mergeCompilationUnits(List<CompilationUnit> compilationUnits) {
680
        gui.sendProgress("Merging Files");
1✔
681
        WurstModel result = Ast.WurstModel();
1✔
682
        for (CompilationUnit compilationUnit : compilationUnits) {
1✔
683
            // remove from old parent
684
            compilationUnit.setParent(null);
1✔
685
            result.add(compilationUnit);
1✔
686
        }
1✔
687
        return result;
1✔
688
    }
689

690
    private CompilationUnit processMap(File file) {
691
        gui.sendProgress("Processing Map " + file.getName());
×
692
        if (!mapFile.isPresent() || !file.equals(mapFile.get())) {
×
693
            // TODO check if file != mapFile is possible, would be strange
694
            // so this should definitely be done differently
695
            throw new Error("file: " + file + " is not the mapfile: " + mapFile);
×
696
        }
697

698
        MpqEditor mapMpq = mapFileMpq;
×
699
        if (mapMpq == null) {
×
700
            throw new RuntimeException("map mpq is null");
×
701
        }
702

703
        if (runArgs.isNoExtractMapScript()) {
×
704
            return null;
×
705
        }
706

707
        // extract mapscript:
708
        try {
709
            byte[] tempBytes = mapMpq.extractFile("war3map.j");
×
710
            File tempFile = File.createTempFile("war3map", ".j", TempDir.get()); // TODO work directly with bytes without temp file
×
711
            tempFile.deleteOnExit();
×
712
            Files.write(tempBytes, tempFile);
×
713

714
            if (isWurstGenerated(tempFile)) {
×
715
                // the war3map.j file was generated by wurst
716
                // this should not be the case, as we will get duplicate function errors in this case
717
                throw new AbortCompilationException(
×
718
                        "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"
719
                                + "if you have used the test-map-button without saving the map first.");
720
            }
721

722
            // move file to wurst directory
723
            File wurstFolder = new File(file.getParentFile(), "wurst");
×
724
            wurstFolder.mkdirs();
×
725
            if (!wurstFolder.isDirectory()) {
×
726
                throw new AbortCompilationException("Could not create Wurst folder at " + wurstFolder + ".");
×
727
            }
728
            File wurstwar3map = new File(wurstFolder, "war3map.j");
×
729
            wurstwar3map.delete();
×
730
            if (tempFile.renameTo(wurstwar3map)) {
×
731
                return parseFile(wurstwar3map);
×
732
            } else {
733
                throw new Error("Could not move war3map.j from " + tempFile + " to " + wurstwar3map);
×
734
            }
735
        } catch (RuntimeException e) {
×
736
            throw e;
×
737
        } catch (Exception e) {
×
738
            throw new Error(e);
×
739
        }
740

741
    }
742

743
    private boolean isWurstGenerated(File tempFile) {
744
        try (FileReader fr = new FileReader(tempFile); BufferedReader in = new BufferedReader(fr)) {
×
745
            String firstLine = in.readLine();
×
746
            WLogger.info("firstLine = '" + firstLine + "'");
×
747
            return firstLine.equals(JassPrinter.WURST_COMMENT);
×
748
        } catch (IOException e) {
×
749
            WLogger.severe(e);
×
750
        }
751
        return false;
×
752
    }
753

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

757
    private CompilationUnit parseFile(File file) {
758
        if (errorHandler.isUnitTestMode()) {
1✔
759
            // in unit test mode, we use a cache
760
            WeakReference<CompilationUnit> wr = fileCompilationUnitCache.get(file);
1✔
761
            CompilationUnit res = wr == null ? null : wr.get();
1✔
762
            if (res == null) {
1✔
763
                res = parseFile2(file);
1✔
764
                fileCompilationUnitCache.put(file, new WeakReference<>(res));
1✔
765
            } else {
766
                res = res.copy();
1✔
767
            }
768
            return res;
1✔
769
        } else {
770
            return parseFile2(file);
×
771
        }
772
    }
773

774
    private CompilationUnit parseFile2(File file) {
775
        if (file.isDirectory()) {
1✔
776
            throw new Error("Is a directory: " + file);
×
777
        }
778
        parsedFiles.add(file);
1✔
779

780
        gui.sendProgress("Parsing File " + file.getName());
1✔
781
        String source = file.getAbsolutePath();
1✔
782
        try (Reader reader = FileReading.getFileReader(file)) {
1✔
783
            // scanning
784
            return parse(source, reader);
1✔
785

786
        } catch (CompileError e) {
×
787
            gui.sendError(e);
×
788
            return emptyCompilationUnit();
×
789
        } catch (FileNotFoundException e) {
×
790
            gui.sendError(new CompileError(new WPos(source, LineOffsets.dummy, 0, 0), "File not found."));
×
791
            return emptyCompilationUnit();
×
792
        } catch (IOException e) {
×
793
            gui.sendError(new CompileError(new WPos(source, LineOffsets.dummy, 0, 0), "Could not read file."));
×
794
            return emptyCompilationUnit();
×
795
        }
796
    }
797

798
    public CompilationUnit parse(String fileName, Reader reader) {
799
        if (fileName.endsWith(".j")) {
1✔
800
            return parser.parseJass(reader, fileName, hasCommonJ);
1✔
801
        }
802
        if (fileName.endsWith(".jurst")) {
1✔
803
            return parser.parseJurst(reader, fileName, hasCommonJ);
1✔
804
        }
805
        if (runArgs.isPrettyPrint()) {
1✔
806
            parser.setRemoveSugar(false);
×
807
        }
808
        return parser.parse(reader, fileName, hasCommonJ);
1✔
809
    }
810

811
    private CompilationUnit emptyCompilationUnit() {
812
        return parser.emptyCompilationUnit();
×
813
    }
814

815
    public @Nullable JassProg getProg() {
816
        return prog;
×
817
    }
818

819
    public void loadReader(String name, Reader input) {
820
        otherInputs.put(name, input);
1✔
821
    }
1✔
822

823
    public void setHasCommonJ(boolean hasCommonJ) {
824
        this.hasCommonJ = hasCommonJ;
1✔
825
    }
1✔
826

827
    public ImProg getImProg() {
828
        final ImProg imProg2 = imProg;
1✔
829
        if (imProg2 != null) {
1✔
830
            return imProg2;
1✔
831
        } else {
832
            throw new Error("imProg is null");
×
833
        }
834
    }
835

836
    public Optional<File> getMapFile() {
837
        return mapFile;
1✔
838
    }
839

840
    public ErrorHandler getErrorHandler() {
841
        return errorHandler;
1✔
842
    }
843

844
    public String getCompleteSourcecode() {
845

846
        StringBuilder sb = new StringBuilder();
×
847
        try {
848
            for (File f : parsedFiles) {
×
849
                sb.append(" //######################################################\n");
×
850
                sb.append(" // File ").append(f.getAbsolutePath()).append("\n");
×
851
                sb.append(" //######################################################\n");
×
852
                sb.append(Files.toString(f, Charsets.UTF_8));
×
853
            }
×
854

855
            for (Entry<String, Reader> entry : otherInputs.entrySet()) {
×
856
                sb.append(" //######################################################\n");
×
857
                sb.append(" // Input ").append(entry.getKey()).append("\n");
×
858
                sb.append(" //######################################################\n");
×
859
                try (Reader reader = entry.getValue()) {
×
860
                    char[] buffer = new char[1024];
×
861
                    while (true) {
862
                        int len = reader.read(buffer);
×
863
                        if (len < 0) {
×
864
                            break;
×
865
                        }
866
                        sb.append(buffer, 0, len);
×
867
                    }
×
868
                }
869
            }
×
870
        } catch (Throwable t) {
×
871
            sb.append(Utils.printExceptionWithStackTrace(t));
×
872
            WLogger.severe(t);
×
873
        }
×
874
        return sb.toString();
×
875
    }
876

877
    public void setRunArgs(RunArgs runArgs) {
878
        this.runArgs = runArgs;
1✔
879
    }
1✔
880

881
    public void setMapFile(Optional<File> mapFile) {
882
        this.mapFile = mapFile;
×
883
    }
×
884

885
    public @Nullable MpqEditor getMapfileMpqEditor() {
886
        return mapFileMpq;
1✔
887
    }
888

889
    public LuaCompilationUnit transformProgToLua() {
890

891
        ImAttrType.setWurstClassType(null);
1✔
892
        int stage;
893
        if (runArgs.isNoDebugMessages()) {
1✔
894
            beginPhase(3, "remove debug messages");
×
895
            DebugMessageRemover.removeDebugMessages(imProg);
×
896
            timeTaker.endPhase();
×
897
        } else {
898
            // debug: add stacktraces
899
            if (runArgs.isIncludeStacktraces()) {
1✔
900
                beginPhase(4, "add stack traces");
×
901
                new StackTraceInjector2(imProg, imTranslator).transform(timeTaker);
×
902
                timeTaker.endPhase();
×
903
            }
904
        }
905
        ImTranslator imTranslator2 = getImTranslator();
1✔
906
        ImOptimizer optimizer = new ImOptimizer(timeTaker, imTranslator2);
1✔
907
        // inliner
908
        stage = 5;
1✔
909
        if (runArgs.isInline()) {
1✔
910
            beginPhase(5, "inlining");
×
911
            optimizer.doInlining();
×
912
            imTranslator2.assertProperties();
×
913

914
            printDebugImProg("./test-output/lua/im " + stage++ + "_afterinline.im");
×
915
            timeTaker.endPhase();
×
916
        }
917

918
        // eliminate local types
919
        beginPhase(6, "eliminate local type");
1✔
920
        getImProg().flatten(imTranslator2);
1✔
921
        EliminateLocalTypes.eliminateLocalTypesProg(getImProg(), imTranslator2);
1✔
922

923
        optimizer.removeGarbage();
1✔
924
        imProg.flatten(imTranslator);
1✔
925
        timeTaker.endPhase();
1✔
926
        stage = 10;
1✔
927
        if (runArgs.isLocalOptimizations()) {
1✔
928
            beginPhase(10, "local optimizations");
×
929
            optimizer.localOptimizations();
×
930
            timeTaker.endPhase();
×
931
        }
932

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

935
        optimizer.removeGarbage();
1✔
936
        imProg.flatten(imTranslator);
1✔
937

938
        // Re-run to avoid #883
939
        optimizer.removeGarbage();
1✔
940
        imProg.flatten(imTranslator);
1✔
941

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

944
        stage = 12;
1✔
945
        if (runArgs.isOptimize()) {
1✔
946
            beginPhase(12, "froptimize");
×
947
            optimizer.optimize();
×
948

949
            optimizer.removeGarbage();
×
950
            imProg.flatten(imTranslator);
×
951
            printDebugImProg("./test-output/lua/im " + stage++ + "_afteroptimize.im");
×
952
            timeTaker.endPhase();
×
953
        }
954
        beginPhase(13, "translate to lua");
1✔
955
        LuaTranslator luaTranslator = new LuaTranslator(imProg, imTranslator);
1✔
956
        LuaCompilationUnit luaCode = luaTranslator.translate();
1✔
957
        ImAttrType.setWurstClassType(TypesHelper.imInt());
1✔
958
        timeTaker.endPhase();
1✔
959
        return luaCode;
1✔
960
    }
961
}
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