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
44 changes: 35 additions & 9 deletions HelperScripts/generate-obj-mappings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ function resolveUnitBalanceSlk(): string {
}
const OUT_FILE =
"./de.peeeq.wurstscript/src/main/resources/stdlib-obj-mappings.json";
const KB_OUT_FILE = "./HelperScripts/wc3-knowledge-base.json";
const KB_OUT_FILE =
"./de.peeeq.wurstscript/src/main/resources/wc3-knowledge-base.json";
const GAMEDATA_DIR = "./HelperScripts/gamedata";
const UNIT_BALANCE_SLK = resolveUnitBalanceSlk();

Expand Down Expand Up @@ -571,7 +572,7 @@ function parseFuncTxt(content: string): Map<string, Record<string, string>> {
return result;
}

/** Merge multiple func maps; first writer per key wins. */
/** Merge profile/skin maps in WC3 load order; later skin files override earlier defaults. */
function mergeFuncMaps(
...maps: Map<string, Record<string, string>>[]
): Map<string, Record<string, string>> {
Expand All @@ -581,7 +582,7 @@ function mergeFuncMaps(
if (!result.has(id)) result.set(id, {});
const target = result.get(id)!;
for (const [k, v] of Object.entries(fields)) {
if (!(k in target)) target[k] = v;
if (v !== "") target[k] = v;
}
}
}
Expand All @@ -600,7 +601,7 @@ interface KBFieldSchema {
/** Source SLK tag, e.g. "unitData", "unitUI", "Profile", "ItemData" */
slk: string;
/** Level index (-1 = not leveled; ≥ 0 = explicit level base) */
index: number;
index: number | null;
/** 1 = value repeats per level (ability leveled fields) */
repeat: number;
/** Ability data slot pointer */
Expand All @@ -620,6 +621,10 @@ interface KBFieldSchema {
useBuilding: boolean;
useItem: boolean;
useCreep: boolean;
useSpecific: string[];
notSpecific: string[];
canBeEmpty: boolean;
forceNonNeg: boolean;
section: string | null;
}

Expand All @@ -644,9 +649,19 @@ function buildSchemas(
const n = typeof v === "number" ? v : parseFloat(String(v));
return isNaN(n) ? 0 : n;
}
function toOptionalNum(v: string | number | null | undefined): number | null {
if (v === null || v === undefined || v === "") return null;
const n = typeof v === "number" ? v : parseFloat(String(v));
return isNaN(n) ? null : n;
}
function toNullStr(v: string | number | null | undefined): string | null {
return (v !== null && v !== undefined) ? String(v) : null;
}
function toCodes(v: string | number | null | undefined): string[] {
// metadata uses both commas and dots as delimiters (e.g. "ACbl.Afzy"),
// matching GenAbilities.java's split on [,\.]+
return v === null || v === undefined ? [] : String(v).split(/[,.]+/).map((s) => s.trim()).filter(Boolean);
}

for (const row of rows) {
const id = row["ID"] as string;
Expand All @@ -655,7 +670,7 @@ function buildSchemas(
id,
field: resolveStr(row["field"]),
slk: resolveStr(row["slk"]),
index: toNum(row["index"]),
index: toOptionalNum(row["index"]),
repeat: toNum(row["repeat"]),
data: toNum(row["data"]),
category: resolveStr(row["category"]),
Expand All @@ -669,6 +684,10 @@ function buildSchemas(
useBuilding: toBool(row["useBuilding"]),
useItem: toBool(row["useItem"]),
useCreep: toBool(row["useCreep"]),
useSpecific: toCodes(row["useSpecific"]),
notSpecific: toCodes(row["notSpecific"]),
canBeEmpty: toBool(row["canBeEmpty"]),
forceNonNeg: toBool(row["forceNonNeg"]),
section: toNullStr(row["section"]),
});
}
Expand Down Expand Up @@ -705,11 +724,11 @@ function buildObjectMap(
}
}

// func.txt / skin.txt fields
// func.txt / skin.txt fields override SLK defaults, matching WC3 profile merge semantics.
const funcRow = funcMap.get(id);
if (funcRow) {
for (const [k, v] of Object.entries(funcRow)) {
if (!(k in obj) && v !== "") obj[k] = v;
if (v !== "") obj[k] = v;
}
}

Expand All @@ -722,7 +741,11 @@ function buildObjectMap(
// Top-level knowledge base builder
// ---------------------------------------------------------------------------

function generateKnowledgeBase(westrings: Map<string, string>): object {
function generateKnowledgeBase(
westrings: Map<string, string>,
buildingBaseIds: Set<string>,
heroBaseIds: Set<string>,
): object {
function loadSLK(file: string): Map<string, SLKRow> {
const c = tryRead(gdPath(file));
return c ? slkToMap(c) : new Map();
Expand Down Expand Up @@ -805,6 +828,9 @@ function generateKnowledgeBase(westrings: Map<string, string>): object {

// unitmetadata.slk covers units, heroes, buildings AND items; split by use-flags.
return {
schemaVersion: 1,
buildingBaseIds: [...buildingBaseIds].sort(),
heroBaseIds: [...heroBaseIds].sort(),
/**
* Field schemas per object type.
* Each entry describes one editable field: its 4-char ID, human-readable
Expand Down Expand Up @@ -924,7 +950,7 @@ if (!westringsContent) {
const westrings = parseWEStrings(westringsContent);
console.log(`Parsed ${westrings.size} WorldEditStrings entries`);

const kb = generateKnowledgeBase(westrings);
const kb = generateKnowledgeBase(westrings, buildingIds, heroIds);
const kbJson = JSON.stringify(kb, null, 2);
Deno.writeTextFileSync(KB_OUT_FILE, kbJson);
console.log(`Generated: ${KB_OUT_FILE}`);
Expand Down
2 changes: 2 additions & 0 deletions de.peeeq.wurstscript/parserspec/wurstscript.parseq
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ ExprMember =
ExprMemberVar =
ExprMemberVarDot(@ignoreForEquality de.peeeq.wurstscript.parser.WPos source, Expr left, Identifier varNameId)
| ExprMemberVarDotDot(@ignoreForEquality de.peeeq.wurstscript.parser.WPos source, Expr left, Identifier varNameId)
| ExprMemberVarQuestionDot(@ignoreForEquality de.peeeq.wurstscript.parser.WPos source, Expr left, Identifier varNameId)

ExprMemberArrayVar =
ExprMemberArrayVarDot(@ignoreForEquality de.peeeq.wurstscript.parser.WPos source, Expr left, Identifier varNameId, Indexes indexes)
Expand All @@ -209,6 +210,7 @@ ExprMemberArrayVar =
ExprMemberMethod =
ExprMemberMethodDot(@ignoreForEquality de.peeeq.wurstscript.parser.WPos source, Expr left, Identifier funcNameId, TypeExprList typeArgs, Arguments args)
| ExprMemberMethodDotDot(@ignoreForEquality de.peeeq.wurstscript.parser.WPos source, Expr left, Identifier funcNameId, TypeExprList typeArgs, Arguments args)
| ExprMemberMethodQuestionDot(@ignoreForEquality de.peeeq.wurstscript.parser.WPos source, Expr left, Identifier funcNameId, TypeExprList typeArgs, Arguments args)


ExprAtomic =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,10 @@ expr:
| left=expr 'instanceof' instaneofType=typeExpr
| receiver=expr dotsCall=('.'|'..') funcName=ID? typeArgs argumentList
| receiver=expr dotsVar=('.'|'..') varName=(ID|CONTINUE|SKIP_|BREAK)? indexes?
// null-safe member access: '?' and '.' are separate tokens so that
// ternaries with real literals (cond ? .5 : x) keep lexing as before
| receiver=expr qdotCall='?' '.' funcName=ID? typeArgs argumentList
| receiver=expr qdotVar='?' '.' varName=(ID|CONTINUE|SKIP_|BREAK)?
| left=expr op=('*'|'/'|'%'|'div'|'mod') right=expr
| op='-' right=expr // TODO move unary minus one up to be compatible with Java etc.
// currently it is here to be backwards compatible with the old wurst parser
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.interpreter.AbstractInterpreter;
Expand Down Expand Up @@ -58,10 +59,12 @@ public ILconstInt GetRandomInt(ILconstInt a, ILconstInt b) {
}

public ILconstInt ModuloInteger(ILconstInt a, ILconstInt b) {
return new ILconstInt(a.getVal() % b.getVal());
// must match Blizzard.j's ModuloInteger (truncated remainder, plus
// divisor if negative), which is what the game executes at runtime
return new ILconstInt(WurstOperator.moduloInteger(a.getVal(), b.getVal()));
}

public ILconstReal ModuloReal(ILconstReal a, ILconstReal b) {
return new ILconstReal(a.getVal() % b.getVal());
return new ILconstReal(WurstOperator.moduloReal(a.getVal(), b.getVal()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,11 @@ public void case_ExprMemberMethodDot(ExprMemberMethodDot e) {
case_Member(e);
}

@Override
public void case_ExprMemberMethodQuestionDot(ExprMemberMethodQuestionDot e) {
case_Member(e);
}

private void case_Member(ExprMemberMethod e) {
WurstType leftType = e.getLeft().attrTyp();
if (leftType instanceof WurstTypeClassOrInterface) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,16 @@ public List<Either<String, MarkedString>> case_ExprMemberVarDotDot(ExprMemberVar
return description(exprMemberVarDotDot);
}

@Override
public List<Either<String, MarkedString>> case_ExprMemberVarQuestionDot(ExprMemberVarQuestionDot exprMemberVarQuestionDot) {
return description(exprMemberVarQuestionDot);
}

@Override
public List<Either<String, MarkedString>> case_ExprMemberMethodQuestionDot(ExprMemberMethodQuestionDot exprMemberMethodQuestionDot) {
return description(exprMemberMethodQuestionDot);
}

@Override
public List<Either<String, MarkedString>> case_ExprVarArrayAccess(ExprVarArrayAccess exprVarArrayAccess) {
return description(exprVarArrayAccess);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,22 +148,10 @@ public ILconst evaluateBinaryOperator(ILconst left,
return ((ILconstNum) left).lessEq((ILconstNum) right.get());
case MINUS:
return ((ILconstNum) left).sub((ILconstNum) right.get());
case MOD_INT: {
int right2 = ((ILconstInt) right.get()).getVal();
int r = ((ILconstInt) left).getVal() % right2;
if (r < 0) {
r += right2;
}
return new ILconstInt(r);
}
case MOD_REAL: {
float right2 = getReal(right.get());
float r = getReal(left) % right2;
if (r < 0) {
r += right2;
}
return new ILconstReal(r);
}
case MOD_INT:
return new ILconstInt(moduloInteger(((ILconstInt) left).getVal(), ((ILconstInt) right.get()).getVal()));
case MOD_REAL:
return new ILconstReal(moduloReal(getReal(left), getReal(right.get())));
case MULT:
return ((ILconstNum) left).mul((ILconstNum) right.get());
case NOTEQ:
Expand All @@ -178,6 +166,29 @@ public ILconst evaluateBinaryOperator(ILconst left,

}

/**
* Reference semantics for Wurst's integer {@code mod}: matches Blizzard.j's
* ModuloInteger (truncated remainder, plus divisor if the remainder is
* negative). All constant folding, interpretation, and backends must agree
* with this.
*/
public static int moduloInteger(int a, int b) {
int r = a % b;
if (r < 0) {
r += b;
}
return r;
}

/** Reference semantics for Wurst's real {@code mod}; see {@link #moduloInteger}. */
public static float moduloReal(float a, float b) {
float r = a % b;
if (r < 0) {
r += b;
}
return r;
}

private static float getReal(ILconst c) {
if (c instanceof ILconstReal) {
return ((ILconstReal) c).getVal();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,16 @@ public static WurstType calculate(final ExprUnary term) {


public static WurstType calculate(ExprMemberVarDot term) {
return memberVarType(term);
}

public static WurstType calculate(ExprMemberVarQuestionDot term) {
// receiver/result nullability rules are checked in WurstValidator;
// the result type is the same as for a plain '.' access
return memberVarType(term);
}

private static WurstType memberVarType(ExprMemberVar term) {
NameLink varDef = term.attrNameLink();
if (varDef == null) {
return WurstTypeUnknown.instance();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,29 @@ public static void prettyPrint(ExprMemberVarDotDot e, Spacer spacer, StringBuild
sb.append(e.getVarName());
}

public static void prettyPrint(ExprMemberVarQuestionDot e, Spacer spacer, StringBuilder sb, int indent) {
e.getLeft().prettyPrint(spacer, sb, indent);
sb.append("?.");
sb.append(e.getVarName());
}

public static void prettyPrint(ExprMemberMethodQuestionDot e, Spacer spacer, StringBuilder sb, int indent) {
printIndent(sb, indent);
if (e.getLeft() instanceof ExprBinary) {
sb.append("(");
e.getLeft().prettyPrint(spacer, sb, indent);
sb.append(")");
} else {
e.getLeft().prettyPrint(spacer, sb, indent);
}
sb.append("?.");
sb.append(e.getFuncName());
sb.append("(");
e.getArgs().prettyPrint(spacer, sb, indent);
sb.append(")");
printNewline(e, sb, indent);
}

public static void prettyPrint(ExprNewObject e, Spacer spacer, StringBuilder sb, int indent) {
printIndent(sb, indent);
sb.append("new");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,7 @@ private void analyzeComponent(List<Node> scc, Map<Node, Knowledge> knowledge) {
case MINUS: return JassIm.ImIntVal(l - r);
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(l % r); break;
case MOD_INT: if (r != 0) return JassIm.ImIntVal(WurstOperator.moduloInteger(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 @@ -362,7 +362,7 @@ private boolean optimizeRealRealMixed(ImOperatorCall opc, boolean wasViable, ImE
break;
case MOD_REAL:
if (f2 != 0f) {
resultVal = f1 % f2;
resultVal = WurstOperator.moduloReal(f1, f2);
isArithmetic = true;
}
break;
Expand Down Expand Up @@ -547,15 +547,15 @@ private boolean optimizeIntInt(ImOperatorCall opc, boolean wasViable, ImIntVal l
break;
case MOD_INT:
if (i2 != 0) {
resultVal = i1 % i2;
resultVal = WurstOperator.moduloInteger(i1, i2);
isArithmetic = true;
}
break;
case MOD_REAL: {
float f1 = i1;
float f2 = i2;
if (f2 != 0f) {
float resultF = f1 % f2;
float resultF = WurstOperator.moduloReal(f1, f2);
String s = floatToStringWithDecimalDigits(resultF, 4);
if (Float.parseFloat(s) != resultF) {
s = floatToStringWithDecimalDigits(resultF, 9);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -925,7 +925,9 @@ private NameRef transformExprMemberVarAccess2(WPos source,
indexes);
}
} else {
if (e_dots.getType() == WurstParser.DOT) {
if (e_dots.getType() == WurstParser.QUESTION) {
return Ast.ExprMemberVarQuestionDot(source, left, varName);
} else if (e_dots.getType() == WurstParser.DOT) {
return Ast.ExprMemberVarDot(source, left, varName);
} else {
return Ast.ExprMemberVarDotDot(source, left, varName);
Expand Down Expand Up @@ -1033,7 +1035,10 @@ private ExprMemberMethod transformMemberMethodCall2(WPos source,
private ExprMemberMethod transformMemberMethodCall2(WPos source,
Expr left, Token dots, Token funcName,
TypeArgsContext typeArgs, ArgumentListContext args) {
if (dots.getType() == WurstParser.DOT) {
if (dots.getType() == WurstParser.QUESTION) {
return Ast.ExprMemberMethodQuestionDot(source, left, text(funcName),
transformTypeArgs(typeArgs), transformArgumentList(args));
} else if (dots.getType() == WurstParser.DOT) {
return Ast.ExprMemberMethodDot(source, left, text(funcName),
transformTypeArgs(typeArgs), transformArgumentList(args));
} else {
Expand Down Expand Up @@ -1107,6 +1112,12 @@ private Expr transformExpr(ExprContext e) {
} else if (e.dotsCall != null) {
return transformMemberMethodCall2(source, e.receiver, e.dotsCall,
e.funcName, e.typeArgs(), e.argumentList());
} else if (e.qdotVar != null) {
return transformExprMemberVarAccess2(source, e.receiver, e.qdotVar,
e.varName, e.indexes());
} else if (e.qdotCall != null) {
return transformMemberMethodCall2(source, e.receiver, e.qdotCall,
e.funcName, e.typeArgs(), e.argumentList());
} else if (e.instaneofType != null) {
return Ast.ExprInstanceOf(source, transformTypeExpr(e.instaneofType),
transformExpr(e.left));
Expand Down
Loading
Loading