Skip to content

Commit a513c86

Browse files
committed
Improve SCC processing, run all tests and fix them
Also "an other" typo fix and use Arrays.hashCode
1 parent bed57ac commit a513c86

34 files changed

Lines changed: 348 additions & 407 deletions

de.peeeq.wurstscript/build.gradle

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -223,9 +223,7 @@ test {
223223
// set minimal heap size required to run tests:
224224
jvmArgs = ['-Xms256m']
225225

226-
useTestNG() {
227-
suites 'src/test/resources/AllTestsSuite.xml'
228-
}
226+
useTestNG()
229227
}
230228

231229
// delete the generated sources on clean

de.peeeq.wurstscript/src/main/java/de/peeeq/datastructures/GraphInterpreter.java

Lines changed: 65 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -77,77 +77,80 @@ public List<T> getResult() {
7777
* <p>
7878
* See https://en.wikipedia.org/wiki/Path-based_strong_component_algorithm
7979
*/
80-
public Set<Set<T>> findStronglyConnectedComponents(List<T> nodes) {
81-
Deque<T> s = new ArrayDeque<>();
82-
Deque<T> p = new ArrayDeque<>();
83-
AtomicInteger c = new AtomicInteger();
84-
AtomicInteger componentCount = new AtomicInteger();
85-
Object2IntLinkedOpenHashMap<T> preorderNumber = new Object2IntLinkedOpenHashMap<>();
86-
Object2IntLinkedOpenHashMap<T> component = new Object2IntLinkedOpenHashMap<>();
87-
88-
// This stack simulates the recursive calls
89-
Deque<T> traversalStack = new ArrayDeque<>();
90-
// This map holds iterators for the children of each node
91-
Map<T, Iterator<T>> iterators = new HashMap<>();
92-
93-
for (T startNode : nodes) {
94-
if (!preorderNumber.containsKey(startNode)) {
95-
traversalStack.push(startNode);
96-
97-
while (!traversalStack.isEmpty()) {
98-
T v = traversalStack.peek();
99-
100-
// Pre-order processing (first time visiting node v)
101-
if (!preorderNumber.containsKey(v)) {
102-
preorderNumber.put(v, c.getAndIncrement());
103-
s.push(v);
104-
p.push(v);
105-
iterators.put(v, getIncidentNodes(v).iterator());
106-
}
80+
public List<List<T>> findStronglyConnectedComponents(List<T> nodes) {
81+
Deque<T> s = new ArrayDeque<>(); // S stack
82+
Deque<T> p = new ArrayDeque<>(); // P stack
83+
84+
int preorderCounter = 0;
85+
int componentCounter = 0;
86+
87+
Object2IntLinkedOpenHashMap<T> preorder = new Object2IntLinkedOpenHashMap<>();
88+
preorder.defaultReturnValue(-1);
89+
Object2IntLinkedOpenHashMap<T> compId = new Object2IntLinkedOpenHashMap<>();
90+
compId.defaultReturnValue(-1);
91+
92+
Deque<T> frameStack = new ArrayDeque<>(); // simulated recursion
93+
Map<T, Iterator<T>> childIters = new HashMap<>(); // per-node child iterators
10794

108-
boolean foundNewChild = false;
109-
Iterator<T> children = iterators.get(v);
110-
111-
// Iterate over children to find the next one to visit
112-
while (children.hasNext()) {
113-
T w = children.next();
114-
if (!preorderNumber.containsKey(w)) {
115-
// Found an unvisited child, push to stack to simulate recursive call
116-
traversalStack.push(w);
117-
foundNewChild = true;
118-
break;
119-
} else if (!component.containsKey(w)) {
120-
// Child w has been visited but is not yet in a component
121-
while (!p.isEmpty() && preorderNumber.getOrDefault(p.peek(), -1) > preorderNumber.get(w)) {
122-
p.pop();
123-
}
95+
List<List<T>> sccs = new ArrayList<>();
96+
97+
for (T start : nodes) {
98+
if (preorder.getInt(start) != -1) continue;
99+
100+
frameStack.push(start);
101+
while (!frameStack.isEmpty()) {
102+
T v = frameStack.peek();
103+
104+
// First time at v
105+
if (preorder.getInt(v) == -1) {
106+
preorder.put(v, preorderCounter++);
107+
s.push(v);
108+
p.push(v);
109+
childIters.put(v, getIncidentNodes(v).iterator());
110+
}
111+
112+
boolean descended = false;
113+
Iterator<T> it = childIters.get(v);
114+
115+
while (it.hasNext()) {
116+
T w = it.next();
117+
int preW = preorder.getInt(w);
118+
if (preW == -1) {
119+
frameStack.push(w);
120+
descended = true;
121+
break;
122+
} else if (compId.getInt(w) == -1) {
123+
// w discovered but not assigned; shrink P
124+
while (!p.isEmpty() && preorder.getInt(p.peek()) > preW) {
125+
p.pop();
124126
}
125127
}
128+
}
126129

127-
if (foundNewChild) {
128-
// Continue the loop to process the new child on top of the stack
129-
continue;
130-
}
130+
if (descended) continue;
131131

132-
// Post-order processing (all children of v have been visited)
133-
traversalStack.pop(); // Finished with v, pop it
134-
iterators.remove(v); // Clean up iterator
135-
136-
if (!p.isEmpty() && p.peek() == v) {
137-
Integer newComponent = componentCount.incrementAndGet();
138-
while (true) {
139-
T popped = s.pop();
140-
component.put(popped, newComponent);
141-
if (popped == v) {
142-
break;
143-
}
144-
}
145-
p.pop();
132+
// Post-order for v
133+
frameStack.pop();
134+
childIters.remove(v);
135+
136+
if (!p.isEmpty() && p.peek() == v) {
137+
int newCid = componentCounter++;
138+
ArrayList<T> cur = new ArrayList<>();
139+
while (true) {
140+
T x = s.pop();
141+
compId.put(x, newCid);
142+
cur.add(x);
143+
if (x == v) break;
146144
}
145+
p.pop();
146+
147+
// Gabow emits SCCs in reverse-topological order
148+
sccs.add(cur);
147149
}
148150
}
149151
}
150-
return ImmutableSet.copyOf(Utils.inverseMapToSet(component).values());
152+
153+
return sccs;
151154
}
152155

153156
public String generateDotFile(List<T> nodes) {

de.peeeq.wurstscript/src/main/java/de/peeeq/immutablecollections/ImmutableList.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public abstract class ImmutableList<T> implements Iterable<T> {
1717
abstract public ImmutableList<T> appFront(T elem);
1818

1919
/**
20-
* adds an other ImmutableList to the end
20+
* adds another ImmutableList to the end
2121
*/
2222
abstract public <R extends T> ImmutableList<T> cons(ImmutableList<R> other);
2323

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,7 @@ public List<Either<String, MarkedString>> case_ArrayInitializer(ArrayInitializer
357357

358358
@Override
359359
public List<Either<String, MarkedString>> case_ModOverride(ModOverride modOverride) {
360-
return string("override: This function overrides an other function from a module or superclass");
360+
return string("override: This function overrides another function from a module or superclass");
361361
}
362362

363363
@Override

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/DescriptionHtml.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ public static String description(ModConstant modConstant) {
238238

239239
public static String description(ModOverride m) {
240240
// TODO add info about which function is overridden
241-
return "override: This function overrides an other function from a module or superclass";
241+
return "override: This function overrides another function from a module or superclass";
242242
}
243243

244244
public static String description(ModStatic modStatic) {

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/ErrorHandler.java

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010

1111
public class ErrorHandler {
1212

13-
// Public-facing lists (unchanged)
1413
private final List<CompileError> errors = new NotNullList<>();
1514
private final List<CompileError> warnings = new NotNullList<>();
1615

@@ -65,8 +64,6 @@ public boolean isUnitTestMode() {
6564
return unitTestMode;
6665
}
6766

68-
// ---------- package-private helpers for ErrorHandling ----------
69-
7067
List<CompileError> getBucketForFile(String file, ErrorType type) {
7168
return (type == ErrorType.ERROR) ? errorsByFile.get(file) : warningsByFile.get(file);
7269
}
@@ -82,8 +79,6 @@ void removeFromGlobal(CompileError err) {
8279
}
8380
}
8481

85-
// ---------- internal helpers ----------
86-
8782
private static void addToBucket(Map<String, List<CompileError>> byFile, CompileError err) {
8883
final String file = err.getSource().getFile();
8984
byFile.computeIfAbsent(file, f -> new NotNullList<>()).add(err);

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/names/NameResolution.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,8 @@ public static ImmutableCollection<FuncLink> lookupFuncsNoConfig(Element node, St
4646
}
4747

4848
public static ImmutableCollection<FuncLink> lookupFuncs(Element e, String name, boolean showErrors) {
49-
// Pull once
5049
final ImmutableCollection<FuncLink> raw = e.lookupFuncsNoConfig(name, showErrors);
5150

52-
// If we know the size, we can fast-path 0/1 and pre-size the builder.
5351
if (raw != null) {
5452
final java.util.Collection<FuncLink> c = raw;
5553
final int n = c.size();

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/prettyPrint/PrettyPrinter.java

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package de.peeeq.wurstscript.attributes.prettyPrint;
22

3+
import de.peeeq.wurstscript.WurstOperator;
34
import de.peeeq.wurstscript.ast.*;
45
import de.peeeq.wurstscript.ast.Element;
56
import de.peeeq.wurstscript.jassAst.*;
@@ -129,7 +130,7 @@ public static void prettyPrint(Annotation e, Spacer spacer, StringBuilder sb, in
129130
sb.append(e.getAnnotationType());
130131
if (e.getAnnotationMessage() != null && e.getAnnotationMessage().length() >= 1) {
131132
sb.append("(");
132-
sb.append(e.getAnnotationMessage());
133+
sb.append(Utils.escapeString(e.getAnnotationMessage()));
133134
sb.append(")");
134135
}
135136
}
@@ -220,13 +221,55 @@ public static void prettyPrint(EnumMembers e, Spacer spacer, StringBuilder sb, i
220221
}
221222
}
222223

224+
public static int precedence(WurstOperator op) {
225+
// Higher number = binds tighter
226+
// 5: unary (not, unary minus)
227+
// 4: * / % (mod)
228+
// 3: + -
229+
// 2: comparisons (<= >= > < == !=)
230+
// 1: or
231+
// 0: and
232+
switch (op) {
233+
case NOT:
234+
case UNARY_MINUS:
235+
return 5;
236+
237+
case MULT:
238+
case DIV_INT:
239+
case DIV_REAL:
240+
case MOD_INT:
241+
case MOD_REAL:
242+
return 4;
243+
244+
case PLUS:
245+
case MINUS:
246+
return 3;
247+
248+
case LESS:
249+
case LESS_EQ:
250+
case GREATER:
251+
case GREATER_EQ:
252+
case EQ:
253+
case NOTEQ:
254+
return 2;
255+
256+
case OR:
257+
return 1;
258+
259+
case AND:
260+
return 0;
261+
}
262+
// Fallback if new ops appear
263+
return 0;
264+
}
265+
223266

224267
public static void prettyPrint(ExprBinary e, Spacer spacer, StringBuilder sb, int indent) {
225268
boolean useParanthesesLeft = false;
226269
boolean useParanthesesRight = false;
227270
if (e.getLeft() instanceof ExprBinary) {
228271
ExprBinary left = (ExprBinary) e.getLeft();
229-
if (precedence(left.getOp().jassTranslateBinary()) < precedence(e.getOp().jassTranslateBinary())) {
272+
if (precedence(left.getOp()) < precedence(e.getOp())) {
230273
// if the precedence level on the left is _smaller_ we have to use parentheses
231274
useParanthesesLeft = true;
232275
}
@@ -237,8 +280,8 @@ public static void prettyPrint(ExprBinary e, Spacer spacer, StringBuilder sb, in
237280
}
238281
if (e.getRight() instanceof ExprBinary) {
239282
ExprBinary right = (ExprBinary) e.getRight();
240-
JassOpBinary op = right.getOp().jassTranslateBinary();
241-
JassOpBinary op2 = e.getOp().jassTranslateBinary();
283+
WurstOperator op = right.getOp();
284+
WurstOperator op2 = e.getOp();
242285
if (precedence(op) < precedence(op2)) {
243286
// if the precedence level on the right is smaller we have to use parentheses
244287
useParanthesesRight = true;
@@ -247,10 +290,10 @@ public static void prettyPrint(ExprBinary e, Spacer spacer, StringBuilder sb, in
247290
// left associative but for commutative operators (+, *, and, or) we do not
248291
// need parentheses
249292

250-
if (!((op instanceof JassOpPlus && op2 instanceof JassOpPlus)
251-
|| (op instanceof JassOpMult && op2 instanceof JassOpMult)
252-
|| (op instanceof JassOpOr && op2 instanceof JassOpOr)
253-
|| (op instanceof JassOpAnd && op2 instanceof JassOpAnd))) {
293+
if (!((op == WurstOperator.PLUS && op2 == WurstOperator.PLUS)
294+
|| (op == WurstOperator.MULT && op2 == WurstOperator.MULT)
295+
|| (op == WurstOperator.OR && op2 == WurstOperator.OR)
296+
|| (op == WurstOperator.AND && op2 == WurstOperator.AND))) {
254297
// in other cases use parentheses
255298
// for example
256299
useParanthesesRight = true;

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/intermediatelang/ILconstArray.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ public String print() {
5252

5353
@Override
5454
public boolean isEqualTo(ILconst other) {
55-
// Preserve previous semantics: identity equality only.
5655
return other == this;
5756
}
5857

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/intermediatelang/interpreter/ILInterpreter.java

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -161,20 +161,16 @@ private static boolean isTypeReal(ImType t) {
161161
private static final Object2ObjectOpenHashMap<ImFunction, Int2ObjectLinkedOpenHashMap<LocalState>> localStateCache =
162162
new Object2ObjectOpenHashMap<>();
163163

164-
// Cap per-function cache size to avoid unbounded growth (tune as needed)
164+
// Cap per-function cache size to avoid unbounded growth
165165
private static final int MAX_CACHE_PER_FUNC = 2048;
166166

167-
// If LocalState is immutable-or-treated-as-readonly when used as "no return":
168-
// Prefer a TRUE singleton to avoid allocating huge internal maps for "void" cases.
169167
private static final LocalState EMPTY_LOCAL_STATE = new LocalState();
170168

171-
// -------------- public entry with varargs kept for API compatibility --------------
172169
private static LocalState runBuiltinFunction(ProgramState globalState, ImFunction f, ILconst... args) {
173170
// Delegate to the array overload to avoid double-allocations.
174171
return runBuiltinFunction(globalState, f, args, /*isVarargs*/ true);
175172
}
176173

177-
// -------------- internal overload that can be called with an existing ILconst[] --------------
178174
private static LocalState runBuiltinFunction(ProgramState globalState, ImFunction f, ILconst[] args, boolean isVarargs) {
179175
// Cache purity + name once
180176
final String fname = f.getName();
@@ -199,7 +195,7 @@ private static LocalState runBuiltinFunction(ProgramState globalState, ImFunctio
199195

200196
for (NativesProvider natives : globalState.getNativeProviders()) {
201197
try {
202-
// Invoke native; ideally you cache method handles per name elsewhere.
198+
// Invoke native; TODO: cache method handles per name elsewhere.
203199
final LocalState localState = new LocalState(natives.invoke(fname, args));
204200

205201
if (pure) {
@@ -236,23 +232,14 @@ private static LocalState runBuiltinFunction(ProgramState globalState, ImFunctio
236232
if (f.getReturnType() instanceof ImVoid) {
237233
return EMPTY_LOCAL_STATE;
238234
}
239-
// If you can, pass a lightweight state to evaluate default (avoid allocating a heavy LocalState)
240235
final ILconst returnValue = ImHelper.defaultValueForComplexType(f.getReturnType())
241236
.evaluate(globalState, EMPTY_LOCAL_STATE);
242237
return new LocalState(returnValue);
243238
}
244239

245240
/** Zero-allocation combined hash for ILconst[] (order-sensitive). */
246241
private static int fastHashArgs(ILconst[] args) {
247-
int h = 1;
248-
for (final ILconst a : args) {
249-
// If ILconst has a stable, cheap hash (recommended), rely on it.
250-
// If not, consider a dedicated method (e.g., a.fastHash()).
251-
h = 31 * h + (a == null ? 0 : a.hashCode());
252-
}
253-
// Spread bits a little to reduce clustering (optional)
254-
h ^= (h >>> 16);
255-
return h;
242+
return Arrays.hashCode(args);
256243
}
257244

258245

0 commit comments

Comments
 (0)