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
120 changes: 23 additions & 97 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,40 +59,10 @@ Inside `de.peeeq.wurstscript`:

## 2. Agent Expectations

Changes made by agents must follow these principles:

### Compatibility first

* **All existing tests must continue to pass.**
* If behavior changes intentionally, provide **new tests** that define the updated semantics.

### Minimal, well-scoped edits

Agents should:

* Fix concrete bugs with **small, local patches**.
* Add missing null-checks, defensive checks, or diagnostics where appropriate.
* Add tests when resolving issues or implementing requested features.

Agents should avoid:

* Large refactors (renaming packages, structural moves, mass rewrites).
* Modifying deprecated folders unless explicitly instructed.
* Altering public semantics or language rules without tests demonstrating the intended outcome.

### Test-driven

* Any new behavior requires tests showing failure before the change and success after.
* Use existing test style and harnesses.

### Generated code

* Do **not** modify files generated by `:gen`.
* If modifying `.parseq` files or grammars, regenerate via:

```
./gradlew :gen
```
* **All existing tests must continue to pass.** The authoritative behavior is defined by the existing test suite.
* **Test-driven**: new behavior requires tests showing failure before the change and success after, in the existing test style. Bug fixes start with a failing repro.
* **Minimal, well-scoped edits**: small local patches; no large refactors (renames, structural moves, mass rewrites), no changes to deprecated folders, no altered language semantics without tests demonstrating the intended outcome, no new external dependencies unless requested.
* **Generated code**: never modify files generated by `:gen`; change the `.parseq` specs or grammars and regenerate (`./gradlew :gen`). Adding a node type to a `.parseq` sum type breaks every exhaustive matcher at compile time — fix those compile errors, they are the complete checklist.

---

Expand Down Expand Up @@ -122,75 +92,26 @@ Use the conventions already present in the file you edit. Avoid introducing new

---

## 4. Allowed vs. Disallowed Changes

### Allowed

* Fix a crash or incorrect behavior in a specific compiler pass.
* Add a regression test that demonstrates a known issue.
* Improve clarity of error messages.
* Add a small new feature when fully specified by the user and backed by tests.
* Update Gradle/JDK usage only if part of a requested task.

### Disallowed

* Unsolicited rewrites of ANTLR grammars.
* Modifying deprecated folders.
* Changing code generation semantics without explicit tests.
* Changing IM behavior without test coverage.
* Introducing new external dependencies unless requested.

---

## 5. How to Run Tests and Code Generation

Inside
## 4. How to Run Tests and Code Generation

```
de.peeeq.wurstscript/
```

run:

### Run all tests

```
./gradlew test
```

### Run a specific test
The Gradle wrapper lives inside `de.peeeq.wurstscript/` (not the repo root); run all commands from there.

```
./gradlew test --tests "tests.wurstscript.tests.GenericsWithTypeclassesTests.identity"
./gradlew test # all tests
./gradlew test --tests "tests.wurstscript.tests.SomeTestClass.someMethod" # one test
./gradlew :gen # regenerate AST (parseq) + ANTLR
./gradlew build # build the compiler
```

### Generate AST code via ANTLR & abstractsyntaxgen

```
./gradlew :gen
```
### Runtime-executing tests

### Build the compiler

```
./gradlew build
```
* `test().executeProg()` runs the compiled program in the interpreter and requires a `testSuccess()` call.
* `test().testLua(true).executeProg()` additionally syntax-checks the emitted Lua with luac and executes it with a real Lua 5.3 interpreter against the WC3 runtime in `src/test/resources/luaruntime/` (wc3shim + Reforged `common.j.lua`/`blizzard.j.lua` dumps). Interpreter discovery: bundled `src/test/resources/lua.exe` on Windows, `lua53` on Linux, else PATH; tests skip visibly when none is found.
* Use `LuaBackendAuditTests` as the reference style for backend regression repros.

---

## 6. Summary for Agents

* Keep changes **minimal**, **compatible**, and **tested**.
* The authoritative behavior is defined by the **existing test suite**.
* The compiler architecture relies on CST → AST → IM → Backend; treat each stage carefully.
* Never modify generated files; modify the sources that generate them instead.
* New behavior must be documented through tests.



---

## 7. LSP Structure and Build Pipelines
## 5. LSP Structure and Build Pipelines

This repository has multiple entry points that may trigger compilation/build behavior:

Expand Down Expand Up @@ -244,7 +165,7 @@ This pipeline handles:

---

## 8. Shared Project Config, Patch Targets, and Run Behavior
## 6. Shared Project Config, Patch Targets, and Run Behavior

Recent Grill/compiler integration work moved `wurst.build` parsing rules into a tiny shared dependency. Keep compiler behavior aligned with that shared model.

Expand Down Expand Up @@ -287,7 +208,7 @@ For config and run-pipeline changes, prefer these focused checks before broader

---

## 9. Backend Parity and Lua Guardrails
## 7. Backend Parity and Lua Guardrails

Recent fixes established additional rules for backend work. Follow these for all future changes:

Expand All @@ -299,6 +220,11 @@ Recent fixes established additional rules for backend work. Follow these for all
* the reason is backend/runtime-specific, and
* the difference is documented in tests.

### Reference semantics for arithmetic

* Integer/real division and modulo semantics are centralized: `WurstOperator.moduloInteger/moduloReal` implement the Blizzard.j formula (truncated remainder, plus divisor if negative) and Jass `div` truncates toward zero.
* The Lua polyfills (`intDiv`/`wurstMod`), the interpreter's `MathProvider` mocks, and constant folding (`SimpleRewrites`, `ConstantAndCopyPropagation`) must all stay consistent with those helpers — never reimplement div/mod locally.

### Error behavior parity expectations

* Prefer matching Jass behavior semantically in Lua output.
Expand Down Expand Up @@ -331,7 +257,7 @@ Recent fixes established additional rules for backend work. Follow these for all

---

## 10. Virtual Slot Binding and Determinism (New Generics + Lua)
## 8. Virtual Slot Binding and Determinism (New Generics + Lua)

Recent regressions showed that virtual-slot binding can silently degrade to base/no-op implementations in generated Lua while still compiling. Follow these rules for all related changes:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -891,7 +891,7 @@ public LuaCompilationUnit transformProgToLua() {
// Lower Lua-specific native calls into IM-level wrappers before optimization,
// so the optimizer can inline and eliminate the nil-safety checks and remapped stubs.
beginPhase(4, "lua native lowering");
LuaNativeLowering.transform(imProg);
LuaNativeLowering.transform(imProg, imTranslator2);
timeTaker.endPhase();

// inliner
Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,59 @@
package de.peeeq.wurstio.jassinterpreter.providers;

import de.peeeq.wurstscript.intermediatelang.ILconstBool;
import de.peeeq.wurstscript.intermediatelang.ILconstInt;
import de.peeeq.wurstscript.intermediatelang.ILconstReal;
import de.peeeq.wurstscript.intermediatelang.ILconstString;
import de.peeeq.wurstscript.intermediatelang.interpreter.AbstractInterpreter;

/**
* Interpreter-side implementations of the raw Lua-primitive leaves that the
* portable Lua ensureType/div/mod helpers delegate to (see
* de.peeeq.wurstscript.translation.imtranslation.LuaEnsureFunctions and
* LuaNativeLowering#lowerDivMod). Those helpers are plain, non-native IM
* functions now - the interpreter runs their body directly - so only the
* handful of leaves with no IM equivalent (Lua's {@code //} floor division,
* {@code math.fmod}, {@code tonumber}, {@code math.tointeger}, string
* concatenation) still need a native implementation here.
*
* <p>The interpreter's own type system already guarantees these are only
* ever called with a genuinely-typed, non-null argument (the callers' nil
* checks run as regular interpreted control flow first), so unlike the real
* Lua runtime these can be simple, non-defensive passthroughs.
*/
public class LuaEnsureTypeProvider extends Provider {
public LuaEnsureTypeProvider(AbstractInterpreter interpreter) {
super(interpreter);
}

public ILconstInt intEnsure(ILconstInt x) {
public ILconstInt __wurst_rawToNumberInt(ILconstInt x) {
return x;
}

public ILconstString stringEnsure(ILconstString x) {
public ILconstInt __wurst_rawToInteger(ILconstInt x) {
return x;
}

public ILconstBool boolEnsure(ILconstBool x) {
public ILconstReal __wurst_rawToNumberReal(ILconstReal x) {
return x;
}

public ILconstReal realEnsure(ILconstReal x) {
public ILconstString __wurst_rawToString(ILconstString x) {
return x;
}

public ILconstString stringConcat(ILconstString x, ILconstString y) { return new ILconstString(x.getVal() + y.getVal()); }
public ILconstString __wurst_rawConcat(ILconstString x, ILconstString y) {
return new ILconstString(x.getVal() + y.getVal());
}

public ILconstInt __wurst_rawFloorDivInt(ILconstInt a, ILconstInt b) {
return ILconstInt.create(Math.floorDiv(a.getVal(), b.getVal()));
}

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

public ILconstReal __wurst_rawFmodReal(ILconstReal a, ILconstReal b) {
return ILconstReal.create(a.getVal() % b.getVal());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -200,16 +200,14 @@ public ImProg translateProg() {
false)), ImVoid(), ImVars(), ImStmts(), flags(IS_NATIVE, IS_BJ));

if(isLuaTarget()) {
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));
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));
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));
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));
stringConcatFunc =JassIm.ImFunction(emptyTrace, "stringConcat", ImTypeVars(), ImVars(JassIm.ImVar(wurstProg, WurstTypeString.instance().imTranslateType(this), "x", false),JassIm.ImVar(wurstProg, WurstTypeString.instance().imTranslateType(this), "y", false)), WurstTypeString.instance().imTranslateType(this), ImVars(), ImStmts(), flags(IS_NATIVE, IS_BJ));
addFunction(ensureIntFunc);
addFunction(ensureBoolFunc);
addFunction(ensureRealFunc);
addFunction(ensureStrFunc);
addFunction(stringConcatFunc);
// Portable IM bodies (not IS_NATIVE stubs) - see LuaEnsureFunctions for why.
List<ImFunction> luaHelperFunctions = new ArrayList<>();
ensureIntFunc = LuaEnsureFunctions.buildEnsureInt(luaHelperFunctions);
ensureBoolFunc = LuaEnsureFunctions.buildEnsureBool(luaHelperFunctions);
ensureRealFunc = LuaEnsureFunctions.buildEnsureReal(luaHelperFunctions);
ensureStrFunc = LuaEnsureFunctions.buildEnsureStr(luaHelperFunctions);
stringConcatFunc = LuaEnsureFunctions.buildStringConcat(luaHelperFunctions);
luaHelperFunctions.forEach(this::addFunction);
}

calculateCompiletimeOrder();
Expand Down
Loading
Loading