Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions de.peeeq.wurstscript/parserspec/jass.parseq
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ JassOpBinary =
| JassOpMinus()
| JassOpMult()
| JassOpDiv()
| JassOpMod()

JassOpUnary =
JassOpNot()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ PLUS: '+';
MINUS: '-';
MULT: '*';
DIV_REAL: '/';
MOD_REAL: '%';
MOD_INT: '%';
PAREN_LEFT: '(';
PAREN_RIGHT: ')';
BRACKET_LEFT: '[';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.google.common.collect.Maps;
import de.peeeq.wurstscript.WLogger;
import de.peeeq.wurstscript.WurstOperator;
import de.peeeq.wurstscript.intermediatelang.*;
import de.peeeq.wurstscript.intermediatelang.interpreter.AbstractInterpreter;
import de.peeeq.wurstscript.intermediatelang.interpreter.TimerMockHandler;
Expand Down Expand Up @@ -310,6 +311,12 @@ public ILconst case_JassOpDiv(JassOpDiv jassOpDiv) {
return getLeftNum().div(getRightNum());
}

@Override
public ILconst case_JassOpMod(JassOpMod jassOpMod) {
return new ILconstInt(WurstOperator.jassModuloInteger(
((ILconstInt) getLeft()).getVal(), ((ILconstInt) getRight()).getVal()));
}

@Override
public ILconst case_JassOpLess(JassOpLess jassOpLess) {
return getLeftNum().less(getRightNum());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package de.peeeq.wurstio.jassinterpreter.providers;

import de.peeeq.wurstscript.WurstOperator;
import de.peeeq.wurstscript.intermediatelang.ILconstInt;
import de.peeeq.wurstscript.intermediatelang.ILconstReal;
import de.peeeq.wurstscript.intermediatelang.ILconstString;
Expand Down Expand Up @@ -50,7 +51,7 @@ public ILconstInt __wurst_rawFloorDivInt(ILconstInt a, ILconstInt b) {
}

public ILconstInt __wurst_rawFmodInt(ILconstInt a, ILconstInt b) {
return ILconstInt.create(a.getVal() % b.getVal());
return ILconstInt.create(WurstOperator.jassModuloInteger(a.getVal(), b.getVal()));
}

public ILconstReal __wurst_rawFmodReal(ILconstReal a, ILconstReal b) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public enum WurstOperator {
DIV_INT("div", 2),
MOD_REAL("%", 2),
MOD_INT("mod", 2),
JASS_MOD_INT("%", 2),
NOT("not", 1),
UNARY_MINUS("-", 1);

Expand Down Expand Up @@ -75,6 +76,8 @@ public JassOpBinary jassTranslateBinary() {
case MOD_INT:
case MOD_REAL:
throw new Error("Cannot translate modulo");
case JASS_MOD_INT:
return JassAst.JassOpMod();
case MULT:
return JassAst.JassOpMult();
case NOTEQ:
Expand Down Expand Up @@ -110,6 +113,7 @@ public LuaOpBinary luaTranslateBinary() {
case MOD_REAL:
return LuaAst.LuaOpMod();
case MOD_INT:
case JASS_MOD_INT:
throw new Error("Cannot translate modulo int");
case MULT:
return LuaAst.LuaOpMult();
Expand Down Expand Up @@ -150,6 +154,8 @@ public ILconst evaluateBinaryOperator(ILconst left,
return ((ILconstNum) left).sub((ILconstNum) right.get());
case MOD_INT:
return new ILconstInt(moduloInteger(((ILconstInt) left).getVal(), ((ILconstInt) right.get()).getVal()));
case JASS_MOD_INT:
return new ILconstInt(jassModuloInteger(((ILconstInt) left).getVal(), ((ILconstInt) right.get()).getVal()));
case MOD_REAL:
return new ILconstReal(moduloReal(getReal(left), getReal(right.get())));
case MULT:
Expand Down Expand Up @@ -180,6 +186,11 @@ public static int moduloInteger(int a, int b) {
return r;
}

/** Native Jass {@code %}: integer-only remainder truncated toward zero. */
public static int jassModuloInteger(int a, int b) {
return a % b;
}

/** Reference semantics for Wurst's real {@code mod}; see {@link #moduloInteger}. */
public static float moduloReal(float a, float b) {
float r = a % b;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ public static WurstType calculate(final ExprBinary term) {
"operands " + leftType + " and " + rightType);
return WurstTypeUnknown.instance();
case MOD_INT:
case JASS_MOD_INT:
case DIV_INT:
if (leftType.isSubtypeOf(WurstTypeInt.instance(), term) && rightType.isSubtypeOf(WurstTypeInt.instance(), term)) {
return leftType;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ public static int precedence(WurstOperator op) {
case DIV_REAL:
case MOD_INT:
case MOD_REAL:
case JASS_MOD_INT:
return 4;

case PLUS:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,7 @@ private void analyzeComponent(List<Node> scc, Map<Node, Knowledge> knowledge) {
case MULT: return JassIm.ImIntVal(l * r);
case DIV_INT: if (r != 0) return JassIm.ImIntVal(l / r); break;
case MOD_INT: if (r != 0) return JassIm.ImIntVal(WurstOperator.moduloInteger(l, r)); break;
case JASS_MOD_INT: if (r != 0) return JassIm.ImIntVal(WurstOperator.jassModuloInteger(l, r)); break;
// IMPORTANT: Return ImBoolVal for comparisons, not ImIntVal!
case EQ: return JassIm.ImBoolVal(l == r);
case NOTEQ: return JassIm.ImBoolVal(l != r);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,12 @@ private boolean optimizeIntInt(ImOperatorCall opc, boolean wasViable, ImIntVal l
isArithmetic = true;
}
break;
case JASS_MOD_INT:
if (i2 != 0) {
resultVal = WurstOperator.jassModuloInteger(i1, i2);
isArithmetic = true;
}
break;
case MOD_REAL: {
float f1 = i1;
float f2 = i2;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -368,8 +368,8 @@ private WurstOperator transformOp(Token op) {
return WurstOperator.MULT;
case JassParser.DIV_REAL:
return WurstOperator.DIV_REAL;
case JassParser.MOD_REAL:
return WurstOperator.MOD_REAL;
case JassParser.MOD_INT:
return WurstOperator.JASS_MOD_INT;
case JassParser.NOT:
return WurstOperator.NOT;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,11 @@ public Integer case_JassOpDiv(@SuppressWarnings("null") JassOpDiv jassOpDiv) {
return 4;
}

@Override
public Integer case_JassOpMod(@SuppressWarnings("null") JassOpMod jassOpMod) {
return 4;
}

@Override
public Integer case_JassOpLess(@SuppressWarnings("null") JassOpLess jassOpLess) {
return 2;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ public static String asString(JassOpDiv jassOpDiv) {
return "/";
}

public static String asString(JassOpMod jassOpMod) {
return "%";
}


public static String asString(JassOpAnd jassOpAnd) {
return "and";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,8 @@ private boolean mayTrapAtRuntime(Element elem, Map<ImFunction, Boolean> function
if (elem instanceof ImOperatorCall) {
ImOperatorCall opCall = (ImOperatorCall) elem;
WurstOperator op = opCall.getOp();
if ((op == WurstOperator.DIV_INT || op == WurstOperator.MOD_INT) && opCall.getArguments().size() >= 2) {
if ((op == WurstOperator.DIV_INT || op == WurstOperator.MOD_INT || op == WurstOperator.JASS_MOD_INT)
&& opCall.getArguments().size() >= 2) {
ImExpr denominator = opCall.getArguments().get(1);
// Preserve integer div/mod unless denominator is provably non-zero.
if (!(denominator instanceof ImIntVal) || ((ImIntVal) denominator).getValI() == 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ public static ImType getType(ImOperatorCall e) {
return WurstTypeReal.instance().imTranslateType();
case DIV_INT:
case MOD_INT:
case JASS_MOD_INT:
return WurstTypeInt.instance().imTranslateType();
case AND:
case OR:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ public void visit(ImOperatorCall call) {
private static final de.peeeq.wurstscript.ast.Element SYNTHETIC_TRACE = de.peeeq.wurstscript.ast.Ast.NoExpr();

/**
* Rewrites {@code DIV_INT}/{@code MOD_INT}/{@code MOD_REAL} operator calls
* Rewrites {@code DIV_INT}/{@code MOD_INT}/{@code MOD_REAL}/{@code JASS_MOD_INT} operator calls
* into calls against small, portable IM functions (not natives), instead
* of them being lowered directly to opaque, always-emitted Lua helper
* functions at Lua-emission time (after {@code ImOptimizer} has already
Expand Down Expand Up @@ -287,6 +287,8 @@ public void visit(ImOperatorCall call) {
target = funcs.modInt();
} else if (call.getOp() == WurstOperator.MOD_REAL) {
target = funcs.modReal();
} else if (call.getOp() == WurstOperator.JASS_MOD_INT) {
target = funcs.jassModInt();
} else {
return;
}
Expand Down Expand Up @@ -439,6 +441,10 @@ ImFunction modReal() {
return modReal;
}

ImFunction jassModInt() {
return rawFmodInt();
}

private ImFunction rawFloorDivInt() {
if (rawFloorDivInt == null) {
rawFloorDivInt = rawNative("__wurst_rawFloorDivInt", TypesHelper.imInt());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,8 +219,9 @@ public static LuaExpr translate(ImOperatorCall e, LuaTranslator tr) {
} else if (e.getOp() == WurstOperator.NOTEQ) {
return LuaAst.LuaExprUnary(LuaAst.LuaOpNot(), translateEquals(left, right, tr));
}
if (e.getOp() == WurstOperator.MOD_INT || e.getOp() == WurstOperator.MOD_REAL || e.getOp() == WurstOperator.DIV_INT) {
// LuaNativeLowering.lowerDivMod rewrites every DIV_INT/MOD_INT/MOD_REAL
if (e.getOp() == WurstOperator.MOD_INT || e.getOp() == WurstOperator.MOD_REAL
|| e.getOp() == WurstOperator.JASS_MOD_INT || e.getOp() == WurstOperator.DIV_INT) {
// LuaNativeLowering.lowerDivMod rewrites every DIV_INT/MOD_INT/MOD_REAL/JASS_MOD_INT
// into a call against a portable IM function before the optimizer runs
// (so it can be inlined/constant-folded there). It should never survive
// to here - falling through to the default binary-op path below would
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,54 @@ public void test_inline_jass_div() {
"endpackage");
}

@Test
public void jassModuloUsesTruncatingIntegerRemainder() throws IOException {
test().executeProg().compilationUnits(
compilationUnit("input.j",
"function jassModulo takes integer a, integer b returns integer",
"\treturn a % b",
"endfunction"),
compilationUnit("test.wurst",
"package test",
"\tnative testSuccess()",
"\tinit",
"\t\tif jassModulo(5, 3) == 2 and jassModulo(-5, 3) == -2 and jassModulo(5, -3) == 2 and jassModulo(-5, -3) == -2",
"\t\t\ttestSuccess()",
"endpackage"));
String compiled = Files.toString(new File(TEST_OUTPUT_PATH
+ "BugTests_jassModuloUsesTruncatingIntegerRemainder_no_opts.j"), Charsets.UTF_8);
Assert.assertTrue(compiled.contains("return a % b"), compiled);
}

@Test
public void jassModuloUsesTruncatingIntegerRemainderInLua() throws IOException {
test().testLua(true).executeProg().compilationUnits(
compilationUnit("input.j",
"function jassModulo takes integer a, integer b returns integer",
"\treturn a % b",
"endfunction"),
compilationUnit("test.wurst",
"package test",
"\tnative testSuccess()",
"\tinit",
"\t\tif jassModulo(5, 3) == 2 and jassModulo(-5, 3) == -2 and jassModulo(5, -3) == 2 and jassModulo(-5, -3) == -2",
"\t\t\ttestSuccess()",
"endpackage"));
String compiled = Files.toString(new File(TEST_OUTPUT_PATH
+ "lua/BugTests_jassModuloUsesTruncatingIntegerRemainderInLua.lua"), Charsets.UTF_8);
Assert.assertTrue(compiled.contains("return math.fmod(a, b)"), compiled);
}

@Test
public void jassModuloRejectsRealOperands() {
test().executeProg(false)
.expectError("Operator % is not defined for operands real and real")
.compilationUnits(compilationUnit("input.j",
"function jassModulo takes real a, real b returns real",
"\treturn a % b",
"endfunction"));
}

@Test
public void test_init_order_jass_warning() {
testAssertErrorsLines(false, "Variable b may not have been initialized",
Expand Down
Loading