Skip to content

Commit ed1f0c8

Browse files
authored
allow overloading the index operator (#1166)
1 parent 0ca5f73 commit ed1f0c8

File tree

12 files changed

+563
-45
lines changed

12 files changed

+563
-45
lines changed

de.peeeq.wurstscript/src/main/antlr/de/peeeq/wurstscript/antlr/Wurst.g4

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jassGlobalsBlock:
2525
;
2626

2727
jassGlobalDecl:
28-
constant='constant'? typeExpr name=ID ('=' initial=expr)? NL
28+
constant='constant'? typeExpr name=(ID|CONTINUE|SKIP_|BREAK) ('=' initial=expr)? NL
2929
;
3030

3131
jassFuncDef:
@@ -35,7 +35,7 @@ jassFuncDef:
3535
'endfunction' NL
3636
;
3737

38-
jassLocal: 'local' typeExpr name=ID ('=' initial=expr)? NL;
38+
jassLocal: 'local' typeExpr name=(ID|CONTINUE|SKIP_|BREAK) ('=' initial=expr)? NL;
3939

4040
jassStatements: stmts+=jassStatement*;
4141

@@ -224,7 +224,7 @@ funcSignature:
224224
formalParameters: '(' (params+=formalParameter (',' params+=formalParameter)*)? ')';
225225

226226
formalParameter:
227-
vararg=VARARG? typeExpr name=ID
227+
vararg=VARARG? typeExpr name=(ID|CONTINUE|SKIP_|BREAK)
228228
;
229229

230230
typeExpr:
@@ -238,7 +238,7 @@ typeExpr:
238238
varDef:
239239
modifiersWithDoc
240240
('var'|constant='constant' varType=typeExpr?|constant='let'|varType=typeExpr)
241-
name=ID ('=' variableInit)? NL
241+
name=(ID|CONTINUE|SKIP_|BREAK) ('=' variableInit)? NL
242242
;
243243

244244
variableInit: (arrayInit | initial=expr);
@@ -312,12 +312,12 @@ stmtWhile:
312312

313313
localVarDef:
314314
(var='var'|let='let'|type=typeExpr)
315-
name=ID ('=' variableInit)?
315+
name=(ID|CONTINUE|SKIP_|BREAK) ('=' variableInit)?
316316
;
317317

318318

319319
localVarDefInline:
320-
typeExpr? name=ID
320+
typeExpr? name=(ID|CONTINUE|SKIP_|BREAK)
321321
;
322322

323323
stmtSet:
@@ -335,12 +335,12 @@ exprAssignable:
335335
;
336336

337337
exprMemberVar:
338-
expr dots=('.'|'..') varname=ID? indexes?
338+
expr dots=('.'|'..') varname=(ID|CONTINUE|SKIP_|BREAK)? indexes?
339339
;
340340

341341

342342
exprVarAccess:
343-
varname=(ID|IT) indexes?
343+
varname=(ID|CONTINUE|SKIP_|BREAK|IT) indexes?
344344
;
345345

346346

@@ -354,7 +354,7 @@ expr:
354354
| left=expr 'castTo' castToType=typeExpr
355355
| left=expr 'instanceof' instaneofType=typeExpr
356356
| receiver=expr dotsCall=('.'|'..') funcName=ID? typeArgs argumentList
357-
| receiver=expr dotsVar=('.'|'..') varName=ID? indexes?
357+
| receiver=expr dotsVar=('.'|'..') varName=(ID|CONTINUE|SKIP_|BREAK)? indexes?
358358
| left=expr op=('*'|'/'|'%'|'div'|'mod') right=expr
359359
| op='-' right=expr // TODO move unary minus one up to be compatible with Java etc.
360360
// currently it is here to be backwards compatible with the old wurst parser
@@ -375,7 +375,7 @@ exprPrimary:
375375
| exprClosure
376376
| exprStatementsBlock
377377
| exprDestroy
378-
| varname=(ID|IT) indexes?
378+
| varname=(ID|CONTINUE|SKIP_|BREAK|IT) indexes?
379379
| atom=(INT
380380
| REAL
381381
| STRING
@@ -412,7 +412,7 @@ shortFormalParameters:
412412
| /* empty */
413413
;
414414

415-
shortFormalParameter: typeExpr? name=ID;
415+
shortFormalParameter: typeExpr? name=(ID|CONTINUE|SKIP_|BREAK);
416416

417417
typeParams: ('<' (params+=typeParam (',' params+=typeParam)*)? '>')?;
418418

@@ -447,6 +447,7 @@ exprList : exprs+=expr (',' exprs+=expr)*;
447447

448448

449449

450+
450451
nativeType: 'nativetype' name=ID ('extends' extended=ID)? NL;
451452
initBlock: 'init' NL statementsBlock;
452453
nativeDef: modifiersWithDoc 'native' funcSignature NL;

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

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@
66
import de.peeeq.wurstio.languageserver.WFile;
77
import de.peeeq.wurstscript.WLogger;
88
import de.peeeq.wurstscript.ast.*;
9+
import de.peeeq.wurstscript.attributes.AttrFuncDef;
910
import de.peeeq.wurstscript.attributes.CofigOverridePackages;
11+
import de.peeeq.wurstscript.attributes.names.FuncLink;
1012
import de.peeeq.wurstscript.attributes.names.NameLink;
1113
import de.peeeq.wurstscript.parser.WPos;
14+
import de.peeeq.wurstscript.types.WurstTypeArray;
1215
import de.peeeq.wurstscript.types.WurstType;
1316
import de.peeeq.wurstscript.types.WurstTypeNamedScope;
1417
import de.peeeq.wurstscript.utils.Utils;
@@ -77,6 +80,10 @@ private List<? extends Location> execute2(ModelManager modelManager) {
7780
return linkTo(originalDecl);
7881
}
7982
}
83+
FunctionDefinition indexOpDecl = getIndexOperatorDeclarationAtPos(e);
84+
if (indexOpDecl != null) {
85+
return linkTo(indexOpDecl);
86+
}
8087
if (e instanceof FuncRef) {
8188
FuncRef funcRef = (FuncRef) e;
8289
FunctionDefinition decl = funcRef.attrFuncDef();
@@ -117,6 +124,37 @@ private List<? extends Location> execute2(ModelManager modelManager) {
117124
return Collections.emptyList();
118125
}
119126

127+
private FunctionDefinition getIndexOperatorDeclarationAtPos(Element e) {
128+
Element target = e;
129+
if (target instanceof Indexes && target.getParent() instanceof NameRef) {
130+
target = target.getParent();
131+
}
132+
if (!(target instanceof NameRef) || !(target instanceof AstElementWithIndexes)) {
133+
return null;
134+
}
135+
136+
NameRef nr = (NameRef) target;
137+
AstElementWithIndexes withIndexes = (AstElementWithIndexes) target;
138+
if (withIndexes.getIndexes().size() != 1) {
139+
return null;
140+
}
141+
NameLink link = nr.attrNameLink();
142+
if (link == null || link.getTyp() instanceof WurstTypeArray) {
143+
return null;
144+
}
145+
146+
WurstType receiverType = link.getTyp();
147+
WurstType indexType = withIndexes.getIndexes().get(0).attrTyp();
148+
if (nr.getParent() instanceof StmtSet && ((StmtSet) nr.getParent()).getUpdatedExpr() == nr) {
149+
StmtSet set = (StmtSet) nr.getParent();
150+
FuncLink f = AttrFuncDef.getIndexSetOperator(nr, receiverType, indexType, set.getRight().attrTyp());
151+
return f == null ? null : f.getDef();
152+
}
153+
154+
FuncLink f = AttrFuncDef.getIndexGetOperator(nr, receiverType, indexType);
155+
return f == null ? null : f.getDef();
156+
}
157+
120158
private List<? extends Location> typeDefinitionFor(Element e) {
121159
if (e instanceof TypeExpr) {
122160
TypeExpr typeExpr = (TypeExpr) e;

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

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,23 @@ public static WurstType calculate(ExprVarArrayAccess term) {
101101
WurstType varDefType = varDef.getTyp();
102102
if (varDefType instanceof WurstTypeArray) {
103103
return ((WurstTypeArray) varDefType).getBaseType();
104-
} else {
105-
term.addError("Variable " + varDef.getName() + " is of type " + varDefType + ", should be an array variable.");
106104
}
105+
if (term.getIndexes().size() == 1) {
106+
WurstType indexType = term.getIndexes().get(0).attrTyp();
107+
FuncLink getOverload = AttrFuncDef.getIndexGetOperator(term, varDefType, indexType);
108+
if (getOverload != null) {
109+
return getOverload.getReturnType();
110+
}
111+
if (isWriteAccess(term)) {
112+
FuncLink setOverload = AttrFuncDef.getIndexSetOperatorByIndex(term, varDefType, indexType);
113+
if (setOverload != null) {
114+
return setOverload.getParameterType(1);
115+
}
116+
return WurstTypeUnknown.instance();
117+
}
118+
}
119+
term.addError("Variable " + varDef.getName() + " is of type " + varDefType +
120+
", should be an array variable or define operator overloading function " + AttrFuncDef.overloadingIndexGet + ".");
107121
return WurstTypeUnknown.instance();
108122
}
109123

@@ -412,7 +426,21 @@ public static WurstType calculate(ExprMemberArrayVarDot term) {
412426
WurstTypeArray ar = (WurstTypeArray) typ;
413427
return ar.getBaseType();
414428
}
415-
term.addError("Variable " + term.getVarName() + " is not an array.");
429+
if (term.getIndexes().size() == 1) {
430+
WurstType indexType = term.getIndexes().get(0).attrTyp();
431+
FuncLink getOverload = AttrFuncDef.getIndexGetOperator(term, typ, indexType);
432+
if (getOverload != null) {
433+
return getOverload.getReturnType();
434+
}
435+
if (isWriteAccess(term)) {
436+
FuncLink setOverload = AttrFuncDef.getIndexSetOperatorByIndex(term, typ, indexType);
437+
if (setOverload != null) {
438+
return setOverload.getParameterType(1);
439+
}
440+
return WurstTypeUnknown.instance();
441+
}
442+
}
443+
term.addError("Variable " + term.getVarName() + " is not an array and has no " + AttrFuncDef.overloadingIndexGet + " overload.");
416444
return typ;
417445
}
418446

@@ -592,4 +620,12 @@ public static WurstType calculate(ExprArrayLength exprArrayLength) {
592620
exprArrayLength.addError(".length is only valid on arrays.");
593621
return de.peeeq.wurstscript.types.WurstTypeUnknown.instance();
594622
}
623+
624+
private static boolean isWriteAccess(NameRef node) {
625+
if (node.getParent() instanceof StmtSet) {
626+
StmtSet stmtSet = (StmtSet) node.getParent();
627+
return stmtSet.getUpdatedExpr() == node;
628+
}
629+
return false;
630+
}
595631
}

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

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ public class AttrFuncDef {
3434
public final static String overloadingMinus = "op_minus";
3535
public final static String overloadingMult = "op_mult";
3636
public final static String overloadingDiv = "op_divReal";
37+
public final static String overloadingIndexGet = "op_index";
38+
public final static String overloadingIndexSet = "op_indexAssign";
3739

3840
public static FuncLink calculate(final ExprFuncRef node) {
3941

@@ -206,6 +208,76 @@ private static boolean isConstructorThisCall(ExprFunctionCall node) {
206208
return searchMemberFunc(left, left.attrTyp(), funcName, Collections.singletonList(right.attrTyp()));
207209
}
208210

211+
public static @Nullable FuncLink getIndexGetOperator(Expr node, WurstType receiverType, WurstType indexType) {
212+
List<WurstType> argTypes = Collections.singletonList(indexType);
213+
FuncLink candidate = searchMemberFunc(node, receiverType, overloadingIndexGet, argTypes);
214+
if (candidate == null || !matchesArguments(node, candidate, argTypes)) {
215+
return null;
216+
}
217+
return candidate;
218+
}
219+
220+
public static @Nullable FuncLink getIndexSetOperator(Expr node, WurstType receiverType, WurstType indexType, WurstType valueType) {
221+
List<WurstType> argTypes = Lists.newArrayList(indexType, valueType);
222+
FuncLink candidate = searchMemberFunc(node, receiverType, overloadingIndexSet, argTypes);
223+
if (candidate == null || !matchesArguments(node, candidate, argTypes)) {
224+
return null;
225+
}
226+
return candidate;
227+
}
228+
229+
public static @Nullable FuncLink getIndexSetOperatorByIndex(Expr node, WurstType receiverType, WurstType indexType) {
230+
Collection<FuncLink> funcs1 = node.lookupMemberFuncs(receiverType, overloadingIndexSet);
231+
if (funcs1.isEmpty()) {
232+
return null;
233+
}
234+
List<FuncLink> funcs = filterInvisible(overloadingIndexSet, node, funcs1);
235+
funcs = filterByReceiverType(node, overloadingIndexSet, funcs);
236+
List<FuncLink> byParamCount = Lists.newArrayList();
237+
for (FuncLink f : funcs) {
238+
if (f.getParameterTypes().size() == 2) {
239+
byParamCount.add(f);
240+
}
241+
}
242+
if (byParamCount.isEmpty()) {
243+
return null;
244+
}
245+
List<FuncLink> byIndexType = Lists.newArrayList();
246+
for (FuncLink f : byParamCount) {
247+
VariableBinding mapping = f.getVariableBinding();
248+
WurstType expectedIndexType = f.getParameterType(0);
249+
VariableBinding m2 = indexType.matchAgainstSupertype(expectedIndexType, node, mapping, VariablePosition.RIGHT);
250+
if (m2 != null) {
251+
byIndexType.add(f);
252+
}
253+
}
254+
if (byIndexType.isEmpty()) {
255+
return null;
256+
}
257+
if (byIndexType.size() == 1) {
258+
return byIndexType.get(0);
259+
}
260+
// ambiguous write-only expected type context: pick deterministic first
261+
return byIndexType.get(0);
262+
}
263+
264+
private static boolean matchesArguments(Element node, FuncLink f, List<WurstType> argumentTypes) {
265+
if (f.getParameterTypes().size() != argumentTypes.size()) {
266+
return false;
267+
}
268+
VariableBinding mapping = f.getVariableBinding();
269+
for (int i = 0; i < argumentTypes.size(); i++) {
270+
WurstType at = argumentTypes.get(i);
271+
WurstType pt = f.getParameterType(i);
272+
VariableBinding m2 = at.matchAgainstSupertype(pt, node, mapping, VariablePosition.RIGHT);
273+
if (m2 == null) {
274+
return false;
275+
}
276+
mapping = m2;
277+
}
278+
return true;
279+
}
280+
209281

210282
/**
211283
* checks if operator is a native operator like for 1+2

0 commit comments

Comments
 (0)