Skip to content

Commit 31d097f

Browse files
Jampi0nFrotty
andauthored
[WIP] Lua typecasting (#997)
* first attempt uses typeEnsure functions to ensure correct types at all times the intermediate language is not the same as in JASS, so compiletime execution is not correct * restore fromIndex and toIndex functions for intermediate language The typeEnsure functions have no effect on compiletime, so lua compiletime should now be exactly like JASS compiletime. index type is ensured with intEnsure this effectively makes generics integers like in JASS casting type parameters to/from int still uses lua functions, even though they are already integers, so it would not be necessary * restore fromIndex and toIndex for overrides * remove unnecessary typeEnsure function * reactivate jass testing * replace non numeric zero compiletime results with null/nil in lua compiletime results are initially evaluated to 0 for objects that are null in lua the 0 must be replaced with nil * fix type filtering use entry type for arrays and member type for member variables * fix failing tests use regular expressions to only check the functions of interest and ignore other parts of the output * Add parentheses around tables in lua Indexing table literals only works with parentheses around them. Co-authored-by: Frotty <[email protected]>
1 parent 9c94fb2 commit 31d097f

File tree

10 files changed

+372
-17
lines changed

10 files changed

+372
-17
lines changed

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

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import de.peeeq.wurstscript.utils.Utils;
3232
import org.eclipse.lsp4j.jsonrpc.messages.Either;
3333
import org.jetbrains.annotations.NotNull;
34+
import org.jetbrains.annotations.Nullable;
3435

3536
import java.io.File;
3637
import java.io.PrintStream;
@@ -226,6 +227,33 @@ private void executeCompiletimeExpr(ImCompiletimeExpr cte) {
226227
LocalState localState = new LocalState();
227228
ILconst value = cte.evaluate(globalState, localState);
228229
ImExpr newExpr = constantToExpr(cte.getTrace(), value);
230+
if(translator.isLuaTarget() && value.toString().equals("0")) {
231+
// convert 0 to null/nil, if the value is 0 and not a numeric type
232+
ImExpr expr = cte.getExpr();
233+
234+
if(expr instanceof ImNull) {
235+
newExpr = ImHelper.nullExpr();
236+
} else {
237+
@Nullable ImType exprType = null;
238+
if(expr instanceof ImFunctionCall) {
239+
exprType = ((ImFunctionCall) expr).getFunc().getReturnType();
240+
} else if(expr instanceof ImVarAccess) {
241+
exprType = ((ImVarAccess)expr).getVar().getType();
242+
} else if(expr instanceof ImVarArrayAccess) {
243+
ImType type = ((ImVarArrayAccess)expr).getVar().getType();
244+
if(type instanceof ImArrayLikeType) {
245+
exprType = ((ImArrayLikeType) type).getEntryType();
246+
}
247+
}
248+
if(exprType != null && !TypesHelper.isIntType(exprType) && !TypesHelper.isRealType(exprType)) {
249+
newExpr = ImHelper.nullExpr();
250+
}
251+
}
252+
// TODO is this complete? Are there more cases where 0 must be replaced?
253+
// A function can return null
254+
// null can be a literal
255+
// null can be a variable
256+
}
229257
cte.replaceBy(newExpr);
230258
} catch (InterpreterException e) {
231259
String msg = ILInterpreter.buildStacktrace(globalState, e);
@@ -259,7 +287,17 @@ public ImVar initFor(ILconstObject obj) {
259287
ImExprs indexesT = indexes.stream()
260288
.map(i -> constantToExpr(trace, ILconstInt.create(i)))
261289
.collect(Collectors.toCollection(JassIm::ImExprs));
262-
addCompiletimeStateInit(JassIm.ImSet(trace, JassIm.ImMemberAccess(trace, JassIm.ImVarAccess(res), JassIm.ImTypeArguments(), var, indexesT), constantToExpr(trace, attrValue)));
290+
ImExpr value2 = constantToExpr(trace, attrValue);
291+
if(translator.isLuaTarget() && value2.toString().equals("0")) {
292+
ImType varType = var.getType();
293+
if(varType instanceof ImArrayLikeType) {
294+
varType = ((ImArrayLikeType) varType).getEntryType();
295+
}
296+
if (!TypesHelper.isIntType(varType) && !TypesHelper.isRealType(varType)) {
297+
value2 = ImHelper.nullExpr();
298+
}
299+
}
300+
addCompiletimeStateInit(JassIm.ImSet(trace, JassIm.ImMemberAccess(trace, JassIm.ImVarAccess(res), JassIm.ImTypeArguments(), var, indexesT), value2));
263301
}
264302
}
265303
});

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/jassinterpreter/ReflectionNativeProvider.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public ReflectionNativeProvider(AbstractInterpreter interpreter) {
4242
addProvider(new ImageProvider(interpreter));
4343
addProvider(new IntegerProvider(interpreter));
4444
addProvider(new FrameProvider(interpreter));
45+
addProvider(new LuaEnsureTypeProvider(interpreter));
4546
}
4647

4748
public NativeJassFunction getFunctionPair(String funcName) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package de.peeeq.wurstio.jassinterpreter.providers;
2+
3+
import de.peeeq.wurstscript.intermediatelang.*;
4+
import de.peeeq.wurstscript.intermediatelang.interpreter.AbstractInterpreter;
5+
6+
public class LuaEnsureTypeProvider extends Provider {
7+
public LuaEnsureTypeProvider(AbstractInterpreter interpreter) {
8+
super(interpreter);
9+
}
10+
11+
public ILconstInt intEnsure(ILconstInt x) {
12+
return x;
13+
}
14+
15+
public ILconstString stringEnsure(ILconstString x) {
16+
return x;
17+
}
18+
19+
public ILconstBool boolEnsure(ILconstBool x) {
20+
return x;
21+
}
22+
23+
public ILconstReal realEnsure(ILconstReal x) {
24+
return x;
25+
}
26+
}

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/ExprTranslation.java

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import de.peeeq.wurstscript.utils.Utils;
1919
import io.vavr.control.Either;
2020
import io.vavr.control.Option;
21+
import org.eclipse.jdt.annotation.Nullable;
2122

2223
import java.util.HashMap;
2324
import java.util.List;
@@ -98,13 +99,35 @@ private static ImExpr wrapTranslation(Expr e, ImTranslator t, ImExpr translated)
9899
return wrapTranslation(e, t, translated, actualType, expectedTypRaw);
99100
}
100101

101-
static ImExpr wrapTranslation(Element trace, ImTranslator t, ImExpr translated, WurstType actualType, WurstType expectedTypRaw) {
102-
if (t.isLuaTarget()) {
103-
// for lua we do not need fromIndex/toIndex
104-
return translated;
105-
}
102+
static ImExpr wrapLua(Element trace, ImTranslator t, ImExpr translated, WurstType actualType) {
103+
// use ensureType functions for lua
104+
// these functions convert nil to the default value for primitive types (int, string, bool, real)
105+
if (t.isLuaTarget() && actualType instanceof WurstTypeBoundTypeParam) {
106+
WurstTypeBoundTypeParam wtb = (WurstTypeBoundTypeParam) actualType;
106107

108+
@Nullable ImFunction ensureType = null;
109+
switch (wtb.getName()) {
110+
case "integer":
111+
ensureType = t.ensureIntFunc;
112+
break;
113+
case "string":
114+
ensureType = t.ensureStrFunc;
115+
break;
116+
case "boolean":
117+
ensureType = t.ensureBoolFunc;
118+
break;
119+
case "real":
120+
ensureType = t.ensureRealFunc;
121+
break;
122+
}
123+
if(ensureType != null) {
124+
return ImFunctionCall(trace, ensureType, ImTypeArguments(), JassIm.ImExprs(translated), false, CallType.NORMAL);
125+
}
126+
}
127+
return translated;
128+
}
107129

130+
static ImExpr wrapTranslation(Element trace, ImTranslator t, ImExpr translated, WurstType actualType, WurstType expectedTypRaw) {
108131
ImFunction toIndex = null;
109132
ImFunction fromIndex = null;
110133
if (actualType instanceof WurstTypeBoundTypeParam) {
@@ -129,15 +152,19 @@ static ImExpr wrapTranslation(Element trace, ImTranslator t, ImExpr translated,
129152
if (toIndex != null && fromIndex != null) {
130153
// System.out.println(" --> cancel");
131154
// the two conversions cancel each other out
132-
return translated;
155+
return wrapLua(trace, t, translated, actualType);
133156
} else if (fromIndex != null) {
134157
// System.out.println(" --> fromIndex");
158+
if(t.isLuaTarget()) {
159+
translated = ImFunctionCall(trace, t.ensureIntFunc, ImTypeArguments(), JassIm.ImExprs(translated), false, CallType.NORMAL);
160+
}
161+
// no ensure type necessary here, because the fromIndex function is already type safe
135162
return ImFunctionCall(trace, fromIndex, ImTypeArguments(), JassIm.ImExprs(translated), false, CallType.NORMAL);
136163
} else if (toIndex != null) {
137164
// System.out.println(" --> toIndex");
138-
return ImFunctionCall(trace, toIndex, ImTypeArguments(), JassIm.ImExprs(translated), false, CallType.NORMAL);
165+
return wrapLua(trace, t, ImFunctionCall(trace, toIndex, ImTypeArguments(), JassIm.ImExprs(translated), false, CallType.NORMAL), actualType);
139166
}
140-
return translated;
167+
return wrapLua(trace, t, translated, actualType);
141168
}
142169

143170
public static ImExpr translateIntern(ExprBinary e, ImTranslator t, ImFunction f) {

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/ImTranslator.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@ public class ImTranslator {
8989

9090
private @Nullable ImFunction configFunc = null;
9191

92+
@Nullable public ImFunction ensureIntFunc = null;
93+
@Nullable public ImFunction ensureBoolFunc = null;
94+
@Nullable public ImFunction ensureRealFunc = null;
95+
@Nullable public ImFunction ensureStrFunc = null;
96+
9297
private final Map<ImVar, VarsForTupleResult> varsForTupleVar = new LinkedHashMap<>();
9398

9499
private boolean isUnitTestMode;
@@ -122,6 +127,17 @@ public ImProg translateProg() {
122127
debugPrintFunction = ImFunction(emptyTrace, $DEBUG_PRINT, ImTypeVars(), ImVars(JassIm.ImVar(wurstProg, WurstTypeString.instance().imTranslateType(this), "msg",
123128
false)), ImVoid(), ImVars(), ImStmts(), flags(IS_NATIVE, IS_BJ));
124129

130+
if(isLuaTarget()) {
131+
ensureIntFunc = JassIm.ImFunction(emptyTrace, "intEnsure", ImTypeVars(), ImVars(JassIm.ImVar(wurstProg, WurstTypeInt.instance().imTranslateType(this), "x", false)), WurstTypeInt.instance().imTranslateType(this), ImVars(), ImStmts(), flags(IS_NATIVE, IS_BJ));
132+
ensureBoolFunc = JassIm.ImFunction(emptyTrace, "boolEnsure", ImTypeVars(), ImVars(JassIm.ImVar(wurstProg, WurstTypeBool.instance().imTranslateType(this), "x", false)), WurstTypeBool.instance().imTranslateType(this), ImVars(), ImStmts(), flags(IS_NATIVE, IS_BJ));
133+
ensureRealFunc = JassIm.ImFunction(emptyTrace, "realEnsure", ImTypeVars(), ImVars(JassIm.ImVar(wurstProg, WurstTypeReal.instance().imTranslateType(this), "x", false)), WurstTypeReal.instance().imTranslateType(this), ImVars(), ImStmts(), flags(IS_NATIVE, IS_BJ));
134+
ensureStrFunc = JassIm.ImFunction(emptyTrace, "stringEnsure", ImTypeVars(), ImVars(JassIm.ImVar(wurstProg, WurstTypeString.instance().imTranslateType(this), "x", false)), WurstTypeString.instance().imTranslateType(this), ImVars(), ImStmts(), flags(IS_NATIVE, IS_BJ));
135+
addFunction(ensureIntFunc);
136+
addFunction(ensureBoolFunc);
137+
addFunction(ensureRealFunc);
138+
addFunction(ensureStrFunc);
139+
}
140+
125141
calculateCompiletimeOrder();
126142

127143
for (CompilationUnit cu : wurstProg) {
@@ -136,6 +152,7 @@ public ImProg translateProg() {
136152
configFunc = ImFunction(emptyTrace, "config", ImTypeVars(), ImVars(), ImVoid(), ImVars(), ImStmts(), flags());
137153
addFunction(configFunc);
138154
}
155+
139156
finishInitFunctions();
140157
EliminateCallFunctionsWithAnnotation.process(imProg);
141158
removeDuplicateNatives(imProg);

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/lua/printing/LuaPrinter.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -290,9 +290,9 @@ public static void print(LuaStatements stmts, StringBuilder sb, int indent) {
290290
}
291291

292292
public static void print(LuaTableConstructor e, StringBuilder sb, int indent) {
293-
sb.append("{");
293+
sb.append("({");
294294
e.getTableFields().print(sb, indent);
295-
sb.append("}");
295+
sb.append("})");
296296
}
297297

298298
public static void print(LuaTableExprField e, StringBuilder sb, int indent) {

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/lua/translation/LuaTranslator.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,11 @@ public LuaMethod initFor(ImClass a) {
127127

128128
LuaFunction instanceOfFunction = LuaAst.LuaFunction(uniqueName("isInstanceOf"), LuaAst.LuaParams(), LuaAst.LuaStatements());
129129

130+
LuaFunction ensureIntFunction = LuaAst.LuaFunction(uniqueName("intEnsure"), LuaAst.LuaParams(), LuaAst.LuaStatements());
131+
LuaFunction ensureStrFunction = LuaAst.LuaFunction(uniqueName("stringEnsure"), LuaAst.LuaParams(), LuaAst.LuaStatements());
132+
LuaFunction ensureBoolFunction = LuaAst.LuaFunction(uniqueName("boolEnsure"), LuaAst.LuaParams(), LuaAst.LuaStatements());
133+
LuaFunction ensureRealFunction = LuaAst.LuaFunction(uniqueName("realEnsure"), LuaAst.LuaParams(), LuaAst.LuaStatements());
134+
130135
private final Lazy<LuaFunction> errorFunc = Lazy.create(() ->
131136
this.getProg().getFunctions().stream()
132137
.flatMap(f -> {
@@ -178,6 +183,7 @@ public LuaCompilationUnit translate() {
178183
createStringConcatFunction();
179184
createInstanceOfFunction();
180185
createObjectIndexFunctions();
186+
createEnsureTypeFunctions();
181187

182188
for (ImVar v : prog.getGlobals()) {
183189
translateGlobal(v);
@@ -379,6 +385,26 @@ function defaultArray(d)
379385
luaModel.add(arrayInitFunction);
380386
}
381387

388+
private void createEnsureTypeFunctions() {
389+
LuaFunction[] ensureTypeFunctions = {ensureIntFunction, ensureBoolFunction, ensureRealFunction, ensureStrFunction};
390+
String[] defaultValue = {"0", "false", "0.0", "\"\""};
391+
for(int i = 0; i < ensureTypeFunctions.length; ++i) {
392+
String[] code = {
393+
"if x == nil then",
394+
" return " + defaultValue[i],
395+
"else",
396+
" return x",
397+
"end"
398+
};
399+
400+
ensureTypeFunctions[i].getParams().add(LuaAst.LuaVariable("x", LuaAst.LuaNoExpr()));
401+
for (String c : code) {
402+
ensureTypeFunctions[i].getBody().add(LuaAst.LuaLiteral(c));
403+
}
404+
luaModel.add(ensureTypeFunctions[i]);
405+
}
406+
}
407+
382408
private void cleanStatements() {
383409
luaModel.accept(new LuaModel.DefaultVisitor() {
384410
@Override

de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/LuaTranslationTests.java

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,37 @@
88
import java.io.File;
99
import java.io.IOException;
1010

11+
import java.util.regex.Matcher;
12+
import java.util.regex.Pattern;
13+
1114
import static org.testng.AssertJUnit.*;
1215

1316

1417
public class LuaTranslationTests extends WurstScriptTest {
1518

19+
private void assertFunctionReturns(String output, String functionName, String returnValue) {
20+
/*
21+
The function functionName must only return returnValue and do nothing else.
22+
*/
23+
Pattern pattern = Pattern.compile("function\\s+" + functionName + "\\(.*?\\)\\s+return\\s+" + returnValue + "\\s+end");
24+
Matcher matcher = pattern.matcher(output);
25+
assertTrue("Function " + functionName + " with return value " + returnValue + " was not found.", matcher.find());
26+
}
27+
28+
private void assertFunctionCall(String output, String functionName, String arguments) {
29+
/*
30+
The function declaration is ignored by using negative lookbehind.
31+
All function calls must use the specified arguments.
32+
*/
33+
Pattern pattern = Pattern.compile("(?<!\\sfunction\\s)" + functionName + "\\((.*)\\)");
34+
Matcher matcher = pattern.matcher(output);
35+
boolean findAtLeastOne = false;
36+
while (matcher.find()) {
37+
assertEquals(arguments, matcher.group(1));
38+
findAtLeastOne = true;
39+
}
40+
assertTrue("Function call to function " + functionName + " with arguments (" + arguments + ") was not found.", findAtLeastOne);
41+
}
1642

1743
@Test
1844
public void testStdLib() throws IOException {
@@ -47,7 +73,7 @@ public void nullString1() throws IOException {
4773
" nullString()"
4874
);
4975
String compiled = Files.toString(new File("test-output/lua/LuaTranslationTests_nullString1.lua"), Charsets.UTF_8);
50-
assertTrue(compiled.contains("return \"\"") && !compiled.contains("return nil"));
76+
assertFunctionReturns(compiled, "nullString", "\"\"");
5177
}
5278

5379
@Test
@@ -59,7 +85,7 @@ public void nullString2() throws IOException {
5985
" takesString(null)"
6086
);
6187
String compiled = Files.toString(new File("test-output/lua/LuaTranslationTests_nullString2.lua"), Charsets.UTF_8);
62-
assertTrue(compiled.contains("takesString(\"\")") && !compiled.contains("takesString(nil)"));
88+
assertFunctionCall(compiled, "takesString", "\"\"");
6389
}
6490

6591
@Ignore
@@ -91,7 +117,7 @@ public void nullObject1() throws IOException {
91117
" nullObject()"
92118
);
93119
String compiled = Files.toString(new File("test-output/lua/LuaTranslationTests_nullObject1.lua"), Charsets.UTF_8);
94-
assertTrue(compiled.contains("return nil") && !compiled.contains("return \"\""));
120+
assertFunctionReturns(compiled, "nullObject", "nil");
95121
}
96122

97123
@Test
@@ -104,7 +130,7 @@ public void nullObject2() throws IOException {
104130
" takesObject(null)"
105131
);
106132
String compiled = Files.toString(new File("test-output/lua/LuaTranslationTests_nullObject2.lua"), Charsets.UTF_8);
107-
assertTrue(compiled.contains("takesObject(nil)") && !compiled.contains("takesObject(\"\")"));
133+
assertFunctionCall(compiled, "takesObject", "nil");
108134
}
109135

110136
@Test
@@ -117,7 +143,7 @@ public void nullUnit1() throws IOException {
117143
" nullUnit()"
118144
);
119145
String compiled = Files.toString(new File("test-output/lua/LuaTranslationTests_nullUnit1.lua"), Charsets.UTF_8);
120-
assertTrue(compiled.contains("return nil") && !compiled.contains("return \"\""));
146+
assertFunctionReturns(compiled, "nullUnit", "nil");
121147
}
122148

123149
@Test
@@ -129,7 +155,7 @@ public void nullUnit2() throws IOException {
129155
" takesUnit(null)"
130156
);
131157
String compiled = Files.toString(new File("test-output/lua/LuaTranslationTests_nullUnit2.lua"), Charsets.UTF_8);
132-
assertTrue(compiled.contains("takesUnit(nil)") && !compiled.contains("takesUnit(\"\")"));
158+
assertFunctionCall(compiled, "takesUnit", "nil");
133159
}
134160
}
135161

0 commit comments

Comments
 (0)