Skip to content

Commit 3686bd3

Browse files
authored
Support 'continue' & safer lua code emission (#1161)
* fix functions with more than 200 locals in lua * completely replace hashtable usage with lua tables during compilation * support continue statement
1 parent 7e0e4ad commit 3686bd3

38 files changed

Lines changed: 2084 additions & 130 deletions

.circleci/config.yml

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@ jobs:
1717
working_directory: ~/repo/de.peeeq.wurstscript
1818

1919
environment:
20-
# Customize the JVM maximum heap limit
21-
JVM_OPTS: -Xmx3200m
20+
GRADLE_OPTS: -Dorg.gradle.parallel=false -Dorg.gradle.workers.max=2
2221
TERM: dumb
2322

2423
steps:
@@ -28,20 +27,18 @@ jobs:
2827
# Download and cache dependencies
2928
- restore_cache:
3029
keys:
31-
- v1-dependencies-{{ checksum "build.gradle" }}
30+
- v2-gradle-{{ checksum "build.gradle" }}-{{ checksum "gradle.properties" }}-{{ checksum "gradle/wrapper/gradle-wrapper.properties" }}
3231
# fallback to using the latest cache if no exact match is found
33-
- v1-dependencies-
34-
35-
- run: ./gradlew dependencies
32+
- v2-gradle-
3633

3734
- save_cache:
3835
paths:
3936
- ~/.gradle
40-
key: v1-dependencies-{{ checksum "build.gradle" }}
41-
42-
# run tests
43-
- run: ./gradlew test --info
37+
key: v2-gradle-{{ checksum "build.gradle" }}-{{ checksum "gradle.properties" }}-{{ checksum "gradle/wrapper/gradle-wrapper.properties" }}
4438

45-
# report tests results
46-
- run: ./gradlew jacocoTestReport coveralls
39+
# run tests and coverage in one invocation to avoid duplicate config/startup cost
40+
- run:
41+
name: Run tests and coverage
42+
command: ./gradlew --no-daemon --stacktrace test jacocoTestReport coveralls
43+
no_output_timeout: 30m
4744

de.peeeq.wurstscript/gradle.properties

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ org.gradle.configuration-cache=true
33
org.gradle.parallel=true
44
org.gradle.daemon=true
55
org.gradle.java.installations.auto-download=true
6-
org.gradle.java.installations.auto-detect=true
6+
org.gradle.java.installations.auto-detect=true
7+
org.gradle.jvmargs=-Xmx3g -XX:MaxMetaspaceSize=768m -Dfile.encoding=UTF-8

de.peeeq.wurstscript/parserspec/wurstscript.parseq

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ ControlflowStatement =
147147
CompoundStatement
148148
| StmtReturn(@ignoreForEquality de.peeeq.wurstscript.parser.WPos source, OptExpr returnedObj)
149149
| StmtExitwhen(@ignoreForEquality de.peeeq.wurstscript.parser.WPos source, Expr cond)
150+
| StmtContinue(de.peeeq.wurstscript.parser.WPos source)
150151

151152
CompoundStatement =
152153
StmtIf(@ignoreForEquality de.peeeq.wurstscript.parser.WPos source, Expr cond, WStatements thenBlock, WStatements elseBlock, boolean hasElse)
@@ -1073,4 +1074,4 @@ Annotation.getAnnotationType()
10731074

10741075
Annotation.getAnnotationMessage()
10751076
returns String
1076-
implemented by de.peeeq.wurstscript.attributes.Annotations.annotationMessage
1077+
implemented by de.peeeq.wurstscript.attributes.Annotations.annotationMessage

de.peeeq.wurstscript/src/main/antlr/de/peeeq/wurstscript/antlr/Wurst.g4

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@ statement:
260260
| stmtSet (externalLambda|NL)
261261
| stmtReturn (externalLambda|NL)
262262
| stmtBreak NL
263+
| stmtContinue NL
263264
| stmtSkip NL
264265
| expr (externalLambda|NL)
265266
| stmtIf
@@ -435,6 +436,7 @@ forIteratorLoop:
435436

436437

437438
stmtBreak:'break';
439+
stmtContinue:'continue';
438440
stmtSkip:'skip';
439441

440442

@@ -461,6 +463,7 @@ WHILE: 'while';
461463
FOR: 'for';
462464
IN: 'in';
463465
BREAK: 'break';
466+
CONTINUE: 'continue';
464467
NEW: 'new';
465468
NULL: 'null';
466469
PACKAGE: 'package';

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/WurstCompilerJassImpl.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import de.peeeq.wurstscript.translation.imtojass.ImAttrType;
2929
import de.peeeq.wurstscript.translation.imtojass.ImToJassTranslator;
3030
import de.peeeq.wurstscript.translation.imtranslation.*;
31+
import de.peeeq.wurstscript.translation.lua.translation.RemoveGarbage;
3132
import de.peeeq.wurstscript.translation.lua.translation.LuaTranslator;
3233
import de.peeeq.wurstscript.types.TypesHelper;
3334
import de.peeeq.wurstscript.utils.LineOffsets;
@@ -937,7 +938,12 @@ public LuaCompilationUnit transformProgToLua() {
937938
printDebugImProg("./test-output/lua/im " + stage++ + "_afteroptimize.im");
938939
timeTaker.endPhase();
939940
}
940-
beginPhase(13, "translate to lua");
941+
beginPhase(13, "lua remove garbage");
942+
RemoveGarbage.removeGarbage(imProg);
943+
imProg.flatten(imTranslator);
944+
timeTaker.endPhase();
945+
946+
beginPhase(14, "translate to lua");
941947
LuaTranslator luaTranslator = new LuaTranslator(imProg, imTranslator);
942948
LuaCompilationUnit luaCode = luaTranslator.translate();
943949
ImAttrType.setWurstClassType(TypesHelper.imInt());

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/LanguageWorker.java

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -213,13 +213,8 @@ private Workitem getNextWorkItem() {
213213
FileReconcile fr = (FileReconcile) change;
214214
affected = modelManager.syncCompilationUnitContent(fr.getFilename(), fr.getContents());
215215
} else if (change instanceof FileSystemUpdated || change instanceof FileDeleted) {
216-
// Dependency roots may have changed (e.g. grill install), refresh and sync incrementally.
217-
modelManager.refreshDependencies();
218-
if (change instanceof FileDeleted) {
219-
affected = modelManager.removeCompilationUnit(change.getFilename());
220-
} else {
221-
affected = modelManager.syncCompilationUnit(change.getFilename());
222-
}
216+
// Dependency roots may have changed (e.g. grill install), sync full dependency state.
217+
affected = modelManager.syncDependencyCompilationUnits();
223218
} else {
224219
// Editor-triggered updates (save/close) use the normal incremental path.
225220
affected = modelManager.syncCompilationUnit(change.getFilename());

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/ModelManager.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ public interface ModelManager {
3434
*/
3535
void refreshDependencies();
3636

37+
/**
38+
* refresh and synchronize all dependency compilation units.
39+
* This handles dependency delete/replace/move/rename scenarios robustly.
40+
*/
41+
Changes syncDependencyCompilationUnits();
42+
3743
Changes syncCompilationUnit(WFile changedFilePath);
3844

3945
Changes syncCompilationUnitContent(WFile filename, String contents);

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/ModelManagerImpl.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import java.io.*;
2121
import java.nio.file.Path;
22+
import java.nio.file.Paths;
2223
import java.nio.file.StandardCopyOption;
2324
import java.util.*;
2425
import java.util.function.Consumer;
@@ -184,6 +185,39 @@ public void refreshDependencies() {
184185
readDependencies();
185186
}
186187

188+
@Override
189+
public synchronized Changes syncDependencyCompilationUnits() {
190+
readDependencies();
191+
192+
WurstModel model2 = model;
193+
if (model2 == null) {
194+
return Changes.empty();
195+
}
196+
197+
Set<WFile> expectedDependencyFiles = getDependencyWurstFiles().stream()
198+
.map(WFile::create)
199+
.collect(Collectors.toSet());
200+
201+
List<WFile> loadedDependencyFiles = model2.stream()
202+
.map(this::wFile)
203+
.filter(this::isUnderDependenciesFolder)
204+
.collect(Collectors.toList());
205+
206+
Changes changes = Changes.empty();
207+
208+
for (WFile loadedFile : loadedDependencyFiles) {
209+
if (!expectedDependencyFiles.contains(loadedFile)) {
210+
changes = changes.mergeWith(removeCompilationUnit(loadedFile));
211+
}
212+
}
213+
214+
for (WFile dependencyFile : expectedDependencyFiles) {
215+
changes = changes.mergeWith(syncCompilationUnit(dependencyFile));
216+
}
217+
218+
return changes;
219+
}
220+
187221
private String getCanonicalPath(File f) {
188222
try {
189223
return f.getCanonicalPath();
@@ -861,6 +895,18 @@ private boolean isAlreadyLoaded(WFile file) {
861895
return false;
862896
}
863897

898+
private boolean isUnderDependenciesFolder(WFile file) {
899+
try {
900+
Path filePath = file.getPath().toAbsolutePath().normalize();
901+
Path dependencyRoot = Paths.get(projectPath.getAbsolutePath(), "_build", "dependencies")
902+
.toAbsolutePath()
903+
.normalize();
904+
return filePath.startsWith(dependencyRoot) && Utils.isWurstFile(filePath.toString());
905+
} catch (FileNotFoundException e) {
906+
return false;
907+
}
908+
}
909+
864910

865911
public File getProjectPath() {
866912
return projectPath;

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/HoverInfo.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,11 @@ public List<Either<String, MarkedString>> case_StmtExitwhen(StmtExitwhen stmtExi
422422
return string("exitwhen: exits the current loop when the condition is true.");
423423
}
424424

425+
@Override
426+
public List<Either<String, MarkedString>> case_StmtContinue(StmtContinue stmtContinue) {
427+
return string("continue: skips the rest of the current loop iteration.");
428+
}
429+
425430
@Override
426431
public List<Either<String, MarkedString>> case_ConstructorDef(ConstructorDef constr) {
427432
return description(constr);

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/languageserver/requests/MapRequest.java

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import de.peeeq.wurstscript.jassprinter.JassPrinter;
2828
import de.peeeq.wurstscript.luaAst.LuaCompilationUnit;
2929
import de.peeeq.wurstscript.parser.WPos;
30+
import de.peeeq.wurstscript.translation.lua.translation.LuaTranslator;
3031
import de.peeeq.wurstscript.utils.LineOffsets;
3132
import de.peeeq.wurstscript.utils.Utils;
3233
import net.moonlightflower.wc3libs.bin.app.W3I;
@@ -167,6 +168,7 @@ protected File compileMap(File projectFolder, WurstGui gui, Optional<File> mapCo
167168
luaCode.get().print(sb, 0);
168169

169170
String compiledMapScript = sb.toString();
171+
LuaTranslator.assertNoLeakedHashtableNativeCalls(compiledMapScript);
170172
File buildDir = getBuildDir();
171173
File outFile = new File(buildDir, BUILD_COMPILED_LUA_NAME);
172174
Files.write(compiledMapScript.getBytes(Charsets.UTF_8), outFile);
@@ -418,8 +420,12 @@ private String resolveCachedMapFileName() {
418420
if (!cachedMapFileName.isEmpty()) {
419421
return cachedMapFileName;
420422
}
423+
return resolveCachedMapFileName(runArgs.isLua());
424+
}
425+
426+
private String resolveCachedMapFileName(boolean luaMode) {
421427
if (!map.isPresent()) {
422-
return "cached_map.w3x";
428+
return luaMode ? "cached_map_lua.w3x" : "cached_map_jass.w3x";
423429
}
424430
File inputMap = map.get();
425431
String inputName = inputMap.getName();
@@ -428,7 +434,8 @@ private String resolveCachedMapFileName() {
428434
// Keep only filesystem-safe characters and avoid collisions for same basename from different folders.
429435
String safeBase = baseName.replaceAll("[^a-zA-Z0-9._-]", "_");
430436
String pathHash = Integer.toUnsignedString(inputMap.getAbsolutePath().hashCode(), 36);
431-
return safeBase + "_" + pathHash + "_cached.w3x";
437+
String modeSuffix = luaMode ? "lua" : "jass";
438+
return safeBase + "_" + pathHash + "_" + modeSuffix + "_cached.w3x";
432439
}
433440

434441
protected File ensureWritableTargetFile(File targetFile, String dialogTitle, String lockMessage,
@@ -498,6 +505,7 @@ private boolean isLocked(File targetMap) {
498505
*/
499506
protected File ensureCachedMap(WurstGui gui) throws IOException {
500507
File cachedMap = getCachedMapFile();
508+
cleanupOppositeModeCacheAndOutputs();
501509

502510
if (!map.isPresent()) {
503511
throw new RequestFailedException(MessageType.Error, "No source map provided");
@@ -515,6 +523,29 @@ protected File ensureCachedMap(WurstGui gui) throws IOException {
515523
return cachedMap;
516524
}
517525

526+
private void cleanupOppositeModeCacheAndOutputs() {
527+
if (cachedMapFileName.isEmpty()) {
528+
File cacheDir = new File(getBuildDir(), "cache");
529+
String oppositeModeCacheName = resolveCachedMapFileName(!runArgs.isLua());
530+
java.nio.file.Path oppositeModeCache = new File(cacheDir, oppositeModeCacheName).toPath();
531+
try {
532+
java.nio.file.Files.deleteIfExists(oppositeModeCache);
533+
} catch (IOException e) {
534+
WLogger.warning("Could not delete opposite-mode cached map: " + oppositeModeCache + " (" + e.getMessage() + ")");
535+
}
536+
}
537+
538+
File buildDir = getBuildDir();
539+
File oppositeCompiledOutput = runArgs.isLua()
540+
? new File(buildDir, BUILD_COMPILED_JASS_NAME)
541+
: new File(buildDir, BUILD_COMPILED_LUA_NAME);
542+
try {
543+
java.nio.file.Files.deleteIfExists(oppositeCompiledOutput.toPath());
544+
} catch (IOException e) {
545+
WLogger.warning("Could not delete opposite-mode compiled output: " + oppositeCompiledOutput + " (" + e.getMessage() + ")");
546+
}
547+
}
548+
518549
protected CompilationResult compileScript(ModelManager modelManager, WurstGui gui, Optional<File> testMap,
519550
WurstProjectConfigData projectConfigData, File buildDir,
520551
boolean isProd) throws Exception {

0 commit comments

Comments
 (0)