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

wurstscript / WurstScript / 203

18 Oct 2023 02:20PM UTC coverage: 63.758% (+0.3%) from 63.447%
203

push

circleci

web-flow
Update deps, improve performance, JHCR fixes (#1080)

- update dependencies
- update stdlib verison for unit tests
- only apply nullsetting when `-opt` is enabled to save some build time for testing
- minor performance improvements
- make build deterministic
- apply build map config before compilation
- hot code reload now more reliable

17246 of 27049 relevant lines covered (63.76%)

0.64 hits per line

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

56.3
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

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

432
        new VarargEliminator(imProg2).run();
1✔
433
        printDebugImProg("./test-output/im " + stage++ + "_varargEliminated.im");
1✔
434
        imTranslator2.assertProperties();
1✔
435
        if (runArgs.isNoDebugMessages()) {
1✔
436
            beginPhase(3, "remove debug messages");
×
437
            DebugMessageRemover.removeDebugMessages(imProg2);
×
438
        } else {
439
            // debug: add stacktraces
440
            if (runArgs.isIncludeStacktraces()) {
1✔
441
                beginPhase(4, "add stack traces");
1✔
442
                new StackTraceInjector2(imProg2, imTranslator2).transform(timeTaker);
1✔
443
            }
444
        }
445
        imTranslator2.assertProperties();
1✔
446

447
        ImOptimizer optimizer = new ImOptimizer(timeTaker, imTranslator2);
1✔
448

449
        // inliner
450
        if (runArgs.isInline()) {
1✔
451
            beginPhase(5, "inlining");
1✔
452
            optimizer.doInlining();
1✔
453
            imTranslator2.assertProperties();
1✔
454

455
            printDebugImProg("./test-output/im " + stage++ + "_afterinline.im");
1✔
456
        }
457

458
        // eliminate tuples
459
        beginPhase(6, "eliminate tuples");
1✔
460
        getImProg().flatten(imTranslator2);
1✔
461
        EliminateTuples.eliminateTuplesProg(getImProg(), imTranslator2);
1✔
462
        getImTranslator().assertProperties(AssertProperty.NOTUPLES);
1✔
463

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

466
        new MultiArrayEliminator(imProg2, imTranslator2, runArgs.isIncludeStacktraces() && !runArgs.isNoDebugMessages()).run();
1✔
467
        printDebugImProg("./test-output/im " + stage++ + "_withoutmultiarrays.im");
1✔
468
        imTranslator2.assertProperties();
1✔
469

470
        beginPhase(7, "remove func refs");
1✔
471
        new FuncRefRemover(imProg2, imTranslator2).run();
1✔
472

473
        // remove cycles:
474
        beginPhase(8, "remove cyclic functions");
1✔
475
        new CyclicFunctionRemover(imTranslator2, imProg2, timeTaker).work();
1✔
476

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

479
        // flatten
480
        beginPhase(9, "flatten");
1✔
481
        getImProg().flatten(imTranslator2);
1✔
482
        getImTranslator().assertProperties(AssertProperty.NOTUPLES, AssertProperty.FLAT);
1✔
483

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

486
        if (runArgs.isLocalOptimizations()) {
1✔
487
            beginPhase(10, "local optimizations");
1✔
488
            optimizer.localOptimizations();
1✔
489
        }
490

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

493
        if (runArgs.isNullsetting()) {
1✔
494
            beginPhase(11, "null setting");
×
495
            optimizer.doNullsetting();
×
496
            printDebugImProg("./test-output/im " + stage++ + "_afternullsetting.im");
×
497
        }
498

499
        optimizer.removeGarbage();
1✔
500
        imProg.flatten(imTranslator);
1✔
501

502
        // Re-run to avoid #883
503
        optimizer.removeGarbage();
1✔
504
        imProg.flatten(imTranslator);
1✔
505

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

508
        if (runArgs.isHotStartmap() || runArgs.isHotReload()) {
1✔
509
            addJassHotCodeReloadCode();
×
510
        }
511
        if (runArgs.isOptimize()) {
1✔
512
            beginPhase(12, "froptimize");
×
513
            optimizer.optimize();
×
514

515
            optimizer.removeGarbage();
×
516
            imProg.flatten(imTranslator);
×
517
            printDebugImProg("./test-output/im " + stage++ + "_afteroptimize.im");
×
518
        }
519

520

521

522
        // translate flattened intermediate lang to jass:
523

524
        beginPhase(13, "translate to jass");
1✔
525
        getImTranslator().calculateCallRelationsAndUsedVariables();
1✔
526
        ImToJassTranslator translator =
1✔
527
                new ImToJassTranslator(getImProg(), getImTranslator().getCalledFunctions(), getImTranslator().getMainFunc(), getImTranslator().getConfFunc());
1✔
528
        prog = translator.translate();
1✔
529
        if (errorHandler.getErrorCount() > 0) {
1✔
530
            prog = null;
×
531
        }
532
        timeTaker.endPhase();
1✔
533
        return prog;
1✔
534
    }
535

536
    private void addJassHotCodeReloadCode() {
537
        Preconditions.checkNotNull(imTranslator);
×
538
        Preconditions.checkNotNull(imProg);
×
539
        ImFunction mainFunc = imTranslator.getMainFunc();
×
540
        Preconditions.checkNotNull(mainFunc);
×
541
        Element trace = imProg.getTrace();
×
542

543
        List<ImStmt> stmts = new ArrayList<>();
×
544

545
        // add call to JHCR_Init_init in main
546
        stmts.add(callExtern(trace, CallType.EXECUTE, "JHCR_Init_init"));
×
547

548

549
        // add reload trigger for pressing escape
550
        ImStmts reloadBody = JassIm.ImStmts(
×
551
                callExtern(trace, CallType.EXECUTE, "JHCR_Init_parse"),
×
552
                callExtern(trace, CallType.NORMAL, "BJDebugMsg", JassIm.ImStringVal("Code reloaded!"))
×
553
        );
554
        ImFunction jhcr_reload = JassIm.ImFunction(trace, "jhcr_reload_on_escape", JassIm.ImTypeVars(), JassIm.ImVars(), JassIm.ImVoid(), JassIm.ImVars(), reloadBody, Collections.emptyList());
×
555

556

557
        ImVar trig = JassIm.ImVar(trace, TypesHelper.imTrigger(), "trig", false);
×
558
        mainFunc.getLocals().add(trig);
×
559
        // TriggerRegisterPlayerEventEndCinematic(trig, Player(0))
560
        stmts.add(JassIm.ImSet(trace, JassIm.ImVarAccess(trig), callExtern(trace, CallType.NORMAL, "CreateTrigger")));
×
561
        stmts.add(callExtern(trace, CallType.NORMAL, "TriggerRegisterPlayerEventEndCinematic", JassIm.ImVarAccess(trig),
×
562
                callExtern(trace, CallType.NORMAL, "Player", JassIm.ImIntVal(0))));
×
563
        stmts.add(callExtern(trace, CallType.NORMAL, "TriggerAddAction", JassIm.ImVarAccess(trig),
×
564
                JassIm.ImFuncRef(trace, jhcr_reload)));
×
565

566
        mainFunc.getBody().addAll(0, stmts);
×
567
    }
×
568

569
    @NotNull
570
    private ImFunctionCall callExtern(Element trace, CallType callType, String functionName, ImExpr... arguments) {
571
        ImFunction jhcrinit = JassIm.ImFunction(trace, functionName, JassIm.ImTypeVars(), JassIm.ImVars(), JassIm.ImVoid(), JassIm.ImVars(), JassIm.ImStmts(), Collections.singletonList(FunctionFlagEnum.IS_EXTERN));
×
572
        return JassIm.ImFunctionCall(trace, jhcrinit, JassIm.ImTypeArguments(), JassIm.ImExprs(arguments), true, callType);
×
573
    }
574

575
    public void checkNoCompiletimeExpr(ImProg prog) {
576
        prog.accept(new ImProg.DefaultVisitor() {
1✔
577
            @Override
578
            public void visit(ImCompiletimeExpr e) {
579
                super.visit(e);
×
580
                throw new CompileError(e.attrTrace().attrSource(), "Compiletime expressions require compilation with '-runcompiletimefunctions' option.");
×
581
            }
582
        });
583
    }
1✔
584

585
    public ImTranslator getImTranslator() {
586
        final ImTranslator t = imTranslator;
1✔
587
        if (t != null) {
1✔
588
            return t;
1✔
589
        } else {
590
            throw new Error("translator not initialized");
×
591
        }
592
    }
593

594
    public @Nullable ImProg translateProgToIm(WurstModel root) {
595
        beginPhase(1, "to intermediate lang");
1✔
596
        // translate wurst to intermediate lang:
597
        imTranslator = new ImTranslator(root, errorHandler.isUnitTestMode(), runArgs);
1✔
598
        imProg = getImTranslator().translateProg();
1✔
599
        int stage = 1;
1✔
600
        printDebugImProg("./test-output/im " + stage++ + ".im");
1✔
601
        timeTaker.endPhase();
1✔
602
        return imProg;
1✔
603
    }
604

605
    private void beginPhase(int phase, String description) {
606
        errorHandler.setProgress("Translating wurst. Phase " + phase + ": " + description, 0.6 + 0.01 * phase);
1✔
607
        timeTaker.beginPhase(description);
1✔
608
    }
1✔
609

610
    private void printDebugImProg(String debugFile) {
611
        if (!errorHandler.isUnitTestMode() ) {
1✔
612
            // output only in unit test mode
613
            return;
×
614
        }
615
        try {
616
            // TODO remove test output
617
            File file = new File(debugFile);
1✔
618
            file.getParentFile().mkdirs();
1✔
619
            try (Writer w = Files.newWriter(file, Charsets.UTF_8)) {
1✔
620
                getImProg().print(w, 0);
1✔
621
            }
622
        } catch (IOException e) {
×
623
            ErrorReporting.instance.handleSevere(e, getCompleteSourcecode());
×
624
        }
1✔
625
    }
1✔
626

627
    private WurstModel mergeCompilationUnits(List<CompilationUnit> compilationUnits) {
628
        gui.sendProgress("Merging Files");
1✔
629
        WurstModel result = Ast.WurstModel();
1✔
630
        for (CompilationUnit compilationUnit : compilationUnits) {
1✔
631
            // remove from old parent
632
            compilationUnit.setParent(null);
1✔
633
            result.add(compilationUnit);
1✔
634
        }
1✔
635
        return result;
1✔
636
    }
637

638
    private CompilationUnit processMap(File file) {
639
        gui.sendProgress("Processing Map " + file.getName());
×
640
        if (!mapFile.isPresent() || !file.equals(mapFile.get())) {
×
641
            // TODO check if file != mapFile is possible, would be strange
642
            // so this should definitely be done differently
643
            throw new Error("file: " + file + " is not the mapfile: " + mapFile);
×
644
        }
645

646
        MpqEditor mapMpq = mapFileMpq;
×
647
        if (mapMpq == null) {
×
648
            throw new RuntimeException("map mpq is null");
×
649
        }
650

651
        if (runArgs.isNoExtractMapScript()) {
×
652
            return null;
×
653
        }
654

655
        // extract mapscript:
656
        try {
657
            byte[] tempBytes = mapMpq.extractFile("war3map.j");
×
658
            File tempFile = File.createTempFile("war3map", ".j", TempDir.get()); // TODO work directly with bytes without temp file
×
659
            tempFile.deleteOnExit();
×
660
            Files.write(tempBytes, tempFile);
×
661

662
            if (isWurstGenerated(tempFile)) {
×
663
                // the war3map.j file was generated by wurst
664
                // this should not be the case, as we will get duplicate function errors in this case
665
                throw new AbortCompilationException(
×
666
                        "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"
667
                                + "if you have used the test-map-button without saving the map first.");
668
            }
669

670
            // move file to wurst directory
671
            File wurstFolder = new File(file.getParentFile(), "wurst");
×
672
            wurstFolder.mkdirs();
×
673
            if (!wurstFolder.isDirectory()) {
×
674
                throw new AbortCompilationException("Could not create Wurst folder at " + wurstFolder + ".");
×
675
            }
676
            File wurstwar3map = new File(wurstFolder, "war3map.j");
×
677
            wurstwar3map.delete();
×
678
            if (tempFile.renameTo(wurstwar3map)) {
×
679
                return parseFile(wurstwar3map);
×
680
            } else {
681
                throw new Error("Could not move war3map.j from " + tempFile + " to " + wurstwar3map);
×
682
            }
683
        } catch (RuntimeException e) {
×
684
            throw e;
×
685
        } catch (Exception e) {
×
686
            throw new Error(e);
×
687
        }
688

689
    }
690

691
    private boolean isWurstGenerated(File tempFile) {
692
        try (FileReader fr = new FileReader(tempFile); BufferedReader in = new BufferedReader(fr)) {
×
693
            String firstLine = in.readLine();
×
694
            WLogger.info("firstLine = '" + firstLine + "'");
×
695
            return firstLine.equals(JassPrinter.WURST_COMMENT);
×
696
        } catch (IOException e) {
×
697
            WLogger.severe(e);
×
698
        }
699
        return false;
×
700
    }
701

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

705
    private CompilationUnit parseFile(File file) {
706
        if (errorHandler.isUnitTestMode()) {
1✔
707
            // in unit test mode, we use a cache
708
            WeakReference<CompilationUnit> wr = fileCompilationUnitCache.get(file);
1✔
709
            CompilationUnit res = wr == null ? null : wr.get();
1✔
710
            if (res == null) {
1✔
711
                res = parseFile2(file);
1✔
712
                fileCompilationUnitCache.put(file, new WeakReference<>(res));
1✔
713
            } else {
714
                res = res.copy();
1✔
715
            }
716
            return res;
1✔
717
        } else {
718
            return parseFile2(file);
×
719
        }
720
    }
721

722
    private CompilationUnit parseFile2(File file) {
723
        if (file.isDirectory()) {
1✔
724
            throw new Error("Is a directory: " + file);
×
725
        }
726
        parsedFiles.add(file);
1✔
727

728
        gui.sendProgress("Parsing File " + file.getName());
1✔
729
        String source = file.getAbsolutePath();
1✔
730
        try (Reader reader = FileReading.getFileReader(file)) {
1✔
731
            // scanning
732
            return parse(source, reader);
1✔
733

734
        } catch (CompileError e) {
×
735
            gui.sendError(e);
×
736
            return emptyCompilationUnit();
×
737
        } catch (FileNotFoundException e) {
×
738
            gui.sendError(new CompileError(new WPos(source, LineOffsets.dummy, 0, 0), "File not found."));
×
739
            return emptyCompilationUnit();
×
740
        } catch (IOException e) {
×
741
            gui.sendError(new CompileError(new WPos(source, LineOffsets.dummy, 0, 0), "Could not read file."));
×
742
            return emptyCompilationUnit();
×
743
        }
744
    }
745

746
    public CompilationUnit parse(String fileName, Reader reader) {
747
        if (fileName.endsWith(".j")) {
1✔
748
            return parser.parseJass(reader, fileName, hasCommonJ);
1✔
749
        }
750
        if (fileName.endsWith(".jurst")) {
1✔
751
            return parser.parseJurst(reader, fileName, hasCommonJ);
1✔
752
        }
753
        return parser.parse(reader, fileName, hasCommonJ);
1✔
754
    }
755

756
    private CompilationUnit emptyCompilationUnit() {
757
        return parser.emptyCompilationUnit();
×
758
    }
759

760
    public @Nullable JassProg getProg() {
761
        return prog;
×
762
    }
763

764
    public void loadReader(String name, Reader input) {
765
        otherInputs.put(name, input);
1✔
766
    }
1✔
767

768
    public void setHasCommonJ(boolean hasCommonJ) {
769
        this.hasCommonJ = hasCommonJ;
1✔
770
    }
1✔
771

772
    public ImProg getImProg() {
773
        final ImProg imProg2 = imProg;
1✔
774
        if (imProg2 != null) {
1✔
775
            return imProg2;
1✔
776
        } else {
777
            throw new Error("imProg is null");
×
778
        }
779
    }
780

781
    public Optional<File> getMapFile() {
782
        return mapFile;
1✔
783
    }
784

785
    public ErrorHandler getErrorHandler() {
786
        return errorHandler;
1✔
787
    }
788

789
    public String getCompleteSourcecode() {
790

791
        StringBuilder sb = new StringBuilder();
×
792
        try {
793
            for (File f : parsedFiles) {
×
794
                sb.append(" //######################################################\n");
×
795
                sb.append(" // File ").append(f.getAbsolutePath()).append("\n");
×
796
                sb.append(" //######################################################\n");
×
797
                sb.append(Files.toString(f, Charsets.UTF_8));
×
798
            }
×
799

800
            for (Entry<String, Reader> entry : otherInputs.entrySet()) {
×
801
                sb.append(" //######################################################\n");
×
802
                sb.append(" // Input ").append(entry.getKey()).append("\n");
×
803
                sb.append(" //######################################################\n");
×
804
                try (Reader reader = entry.getValue()) {
×
805
                    char[] buffer = new char[1024];
×
806
                    while (true) {
807
                        int len = reader.read(buffer);
×
808
                        if (len < 0) {
×
809
                            break;
×
810
                        }
811
                        sb.append(buffer, 0, len);
×
812
                    }
×
813
                }
814
            }
×
815
        } catch (Throwable t) {
×
816
            sb.append(Utils.printExceptionWithStackTrace(t));
×
817
            WLogger.severe(t);
×
818
        }
×
819
        return sb.toString();
×
820
    }
821

822
    public void setRunArgs(RunArgs runArgs) {
823
        this.runArgs = runArgs;
1✔
824
    }
1✔
825

826
    public void setMapFile(Optional<File> mapFile) {
827
        this.mapFile = mapFile;
×
828
    }
×
829

830
    public @Nullable MpqEditor getMapfileMpqEditor() {
831
        return mapFileMpq;
1✔
832
    }
833

834
    public LuaCompilationUnit transformProgToLua() {
835

836
        ImAttrType.setWurstClassType(null);
1✔
837
        int stage;
838
        if (runArgs.isNoDebugMessages()) {
1✔
839
            beginPhase(3, "remove debug messages");
×
840
            DebugMessageRemover.removeDebugMessages(imProg);
×
841
        } else {
842
            // debug: add stacktraces
843
            if (runArgs.isIncludeStacktraces()) {
1✔
844
                beginPhase(4, "add stack traces");
×
845
                new StackTraceInjector2(imProg, imTranslator).transform(timeTaker);
×
846
            }
847
        }
848
        ImTranslator imTranslator2 = getImTranslator();
1✔
849
        ImOptimizer optimizer = new ImOptimizer(timeTaker, imTranslator2);
1✔
850
        // inliner
851
        stage = 5;
1✔
852
        if (runArgs.isInline()) {
1✔
853
            beginPhase(5, "inlining");
×
854
            optimizer.doInlining();
×
855
            imTranslator2.assertProperties();
×
856

857
            printDebugImProg("./test-output/lua/im " + stage++ + "_afterinline.im");
×
858
        }
859

860
        // eliminate local types
861
        beginPhase(6, "eliminate local type");
1✔
862
        getImProg().flatten(imTranslator2);
1✔
863
        EliminateLocalTypes.eliminateLocalTypesProg(getImProg(), imTranslator2);
1✔
864

865
        optimizer.removeGarbage();
1✔
866
        imProg.flatten(imTranslator);
1✔
867

868
        stage = 10;
1✔
869
        if (runArgs.isLocalOptimizations()) {
1✔
870
            beginPhase(10, "local optimizations");
×
871
            optimizer.localOptimizations();
×
872
        }
873

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

876
        optimizer.removeGarbage();
1✔
877
        imProg.flatten(imTranslator);
1✔
878

879
        // Re-run to avoid #883
880
        optimizer.removeGarbage();
1✔
881
        imProg.flatten(imTranslator);
1✔
882

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

885
        stage = 12;
1✔
886
        if (runArgs.isOptimize()) {
1✔
887
            beginPhase(12, "froptimize");
×
888
            optimizer.optimize();
×
889

890
            optimizer.removeGarbage();
×
891
            imProg.flatten(imTranslator);
×
892
            printDebugImProg("./test-output/lua/im " + stage++ + "_afteroptimize.im");
×
893
        }
894
        beginPhase(13, "translate to lua");
1✔
895
        LuaTranslator luaTranslator = new LuaTranslator(imProg, imTranslator);
1✔
896
        LuaCompilationUnit luaCode = luaTranslator.translate();
1✔
897
        ImAttrType.setWurstClassType(TypesHelper.imInt());
1✔
898
        return luaCode;
1✔
899
    }
900
}
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