Skip to content

Commit e6ddbf1

Browse files
committed
[WIP] Add __cast__ function and syntax
Add `__cast__(val, type)` function and syntax. Syntax is `(type) val`.
1 parent 44a89e1 commit e6ddbf1

File tree

1 file changed

+132
-22
lines changed

1 file changed

+132
-22
lines changed

src/main/java/com/laytonsmith/core/functions/Compiler.java

Lines changed: 132 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,16 @@
66
import com.laytonsmith.annotations.hide;
77
import com.laytonsmith.annotations.noboilerplate;
88
import com.laytonsmith.annotations.noprofile;
9+
import com.laytonsmith.core.ArgumentValidation;
910
import com.laytonsmith.core.FullyQualifiedClassName;
1011
import com.laytonsmith.core.MSVersion;
1112
import com.laytonsmith.core.Optimizable;
1213
import com.laytonsmith.core.ParseTree;
1314
import com.laytonsmith.core.Script;
1415
import com.laytonsmith.core.compiler.FileOptions;
1516
import com.laytonsmith.core.compiler.analysis.StaticAnalysis;
17+
import com.laytonsmith.core.compiler.signature.FunctionSignatures;
18+
import com.laytonsmith.core.compiler.signature.SignatureBuilder;
1619
import com.laytonsmith.core.constructs.CBareString;
1720
import com.laytonsmith.core.constructs.CBracket;
1821
import com.laytonsmith.core.constructs.CClassType;
@@ -26,6 +29,7 @@
2629
import com.laytonsmith.core.constructs.CVoid;
2730
import com.laytonsmith.core.constructs.Construct;
2831
import com.laytonsmith.core.constructs.IVariable;
32+
import com.laytonsmith.core.constructs.InstanceofUtil;
2933
import com.laytonsmith.core.constructs.Target;
3034
import com.laytonsmith.core.constructs.Token;
3135
import com.laytonsmith.core.environments.Environment;
@@ -187,27 +191,32 @@ public static ParseTree rewrite(List<ParseTree> list, boolean returnSConcat,
187191
//If any of our nodes are CSymbols, we have different behavior
188192
boolean inSymbolMode = false; //caching this can save Xn
189193

194+
// Rewrite execute operator.
190195
rewriteParenthesis(list);
191196

192-
//Assignment
193-
//Note that we are walking the array in reverse, because multiple assignments,
194-
//say @a = @b = 1 will break if they look like assign(assign(@a, @b), 1),
195-
//they need to be assign(@a, assign(@b, 1)). As a variation, we also have
196-
//to support something like 1 + @a = 2, which will turn into add(1, assign(@a, 2),
197-
//and 1 + @a = @b + 3 would turn into add(1, assign(@a, add(@b, 3))).
197+
// Rewrite assignment operator.
198+
/*
199+
* Note that we are walking the array in reverse, because multiple assignments,
200+
* say @a = @b = 1 will break if they look like assign(assign(@a, @b), 1),
201+
* they need to be assign(@a, assign(@b, 1)). As a variation, we also have
202+
* to support something like 1 + @a = 2, which will turn into add(1, assign(@a, 2),
203+
* and 1 + @a = @b + 3 would turn into add(1, assign(@a, add(@b, 3))).
204+
*/
198205
for(int i = list.size() - 2; i >= 0; i--) {
199206
ParseTree node = list.get(i + 1);
200207
if(node.getData() instanceof CSymbol && ((CSymbol) node.getData()).isAssignment()) {
208+
209+
// Get assign left hand side and autoconcat assign right hand side if necessary.
201210
ParseTree lhs = list.get(i);
202-
ParseTree assignNode = new ParseTree(
203-
new CFunction(assign.NAME, node.getTarget()), node.getFileOptions());
204-
ParseTree rhs;
205211
if(i < list.size() - 3) {
206-
//Need to autoconcat
207212
List<ParseTree> valChildren = new ArrayList<>();
208213
int index = i + 2;
209214
// add all preceding symbols
210-
while(list.size() > index + 1 && list.get(index).getData() instanceof CSymbol) {
215+
while(list.size() > index + 1 && (list.get(index).getData() instanceof CSymbol
216+
|| (list.get(index).getData() instanceof CFunction cf
217+
&& cf.hasFunction() && cf.getFunction() != null
218+
&& cf.getFunction().getName().equals(Compiler.p.NAME)
219+
&& list.get(index).numberOfChildren() == 1))) {
211220
valChildren.add(list.get(index));
212221
list.remove(index);
213222
}
@@ -237,26 +246,31 @@ public static ParseTree rewrite(List<ParseTree> list, boolean returnSConcat,
237246
if(list.size() <= i + 2) {
238247
throw new ConfigCompileException("Unexpected end of statement", list.get(i).getTarget());
239248
}
249+
ParseTree rhs = list.get(i + 2);
240250

241-
// Additive assignment
251+
// Wrap additive assignment in right hand side (e.g. convert @a += 1 to @a = @a + 1).
242252
CSymbol sy = (CSymbol) node.getData();
243253
String conversionFunction = sy.convertAssignment();
244254
if(conversionFunction != null) {
245-
ParseTree conversion = new ParseTree(new CFunction(conversionFunction, node.getTarget()), node.getFileOptions());
246-
conversion.addChild(lhs);
247-
conversion.addChild(list.get(i + 2));
248-
list.set(i + 2, conversion);
255+
ParseTree rhsReplacement = new ParseTree(
256+
new CFunction(conversionFunction, node.getTarget()), node.getFileOptions());
257+
rhsReplacement.addChild(lhs);
258+
rhsReplacement.addChild(rhs);
259+
rhs = rhsReplacement;
249260
}
250261

251-
rhs = list.get(i + 2);
262+
// Rewrite to assign node.
263+
ParseTree assignNode = new ParseTree(
264+
new CFunction(assign.NAME, node.getTarget()), node.getFileOptions());
252265
assignNode.addChild(lhs);
253266
assignNode.addChild(rhs);
254-
list.set(i, assignNode);
255-
list.remove(i + 1);
256-
list.remove(i + 1);
267+
list.set(i, assignNode); // Overwrite lhs with assign node.
268+
list.remove(i + 1); // Remove "=" node.
269+
list.remove(i + 1); // Remove rhs node.
257270
}
258271
}
259-
//postfix
272+
273+
// Rewrite postfix operators.
260274
for(int i = 0; i < list.size(); i++) {
261275
ParseTree node = list.get(i);
262276
if(node.getData() instanceof CSymbol) {
@@ -280,9 +294,10 @@ public static ParseTree rewrite(List<ParseTree> list, boolean returnSConcat,
280294
}
281295
}
282296
}
297+
298+
// Rewrite unary operators.
283299
if(inSymbolMode) {
284300
try {
285-
//look for unary operators
286301
for(int i = 0; i < list.size() - 1; i++) {
287302
ParseTree node = list.get(i);
288303
if(node.getData() instanceof CSymbol && ((CSymbol) node.getData()).isUnary()) {
@@ -326,6 +341,38 @@ public static ParseTree rewrite(List<ParseTree> list, boolean returnSConcat,
326341
conversion.addChild(rewrite(ac, returnSConcat, envs));
327342
}
328343
}
344+
} catch (IndexOutOfBoundsException e) {
345+
throw new ConfigCompileException("Unexpected symbol (" + list.get(list.size() - 1).getData().val() + ")",
346+
list.get(list.size() - 1).getTarget());
347+
}
348+
}
349+
350+
// Rewrite cast operator.
351+
for(int i = 0; i < list.size() - 1; i++) {
352+
ParseTree node = list.get(i);
353+
if(node.getData() instanceof CFunction cf && cf.hasFunction() && cf.getFunction() != null
354+
&& cf.getFunction().getName().equals(Compiler.p.NAME) && node.numberOfChildren() == 1) {
355+
356+
// Convert bare string or concat() to type reference if needed.
357+
ParseTree typeNode = node.getChildAt(0);
358+
ParseTree convertedTypeNode = __type_ref__.createFromBareStringOrConcats(typeNode);
359+
if(convertedTypeNode != null) {
360+
typeNode = convertedTypeNode;
361+
}
362+
363+
// Rewrite p(A) and the next list entry B to __cast__(B, A).
364+
ParseTree castNode = new ParseTree(
365+
new CFunction(__cast__.NAME, node.getTarget()), node.getFileOptions());
366+
castNode.addChild(list.get(i + 1));
367+
castNode.addChild(typeNode);
368+
list.set(i, castNode);
369+
list.remove(i + 1);
370+
}
371+
}
372+
373+
// Rewrite binary operators.
374+
if(inSymbolMode) {
375+
try {
329376

330377
//Exponential
331378
for(int i = 0; i < list.size() - 1; i++) {
@@ -1136,4 +1183,67 @@ public ParseTree postParseRewrite(ParseTree ast, Environment env,
11361183
}
11371184
}
11381185
}
1186+
@api
1187+
@noprofile
1188+
@hide("This is only used internally by the compiler.")
1189+
public static class __cast__ extends DummyFunction {
1190+
1191+
public static final String NAME = "__cast__";
1192+
1193+
@Override
1194+
public String getName() {
1195+
return NAME;
1196+
}
1197+
1198+
@Override
1199+
public FunctionSignatures getSignatures() {
1200+
return new SignatureBuilder(CClassType.AUTO)
1201+
.param(Mixed.TYPE, "value", "The value.")
1202+
.param(CClassType.TYPE, "type", "The type.")
1203+
.throwsEx(CRECastException.class, "When value cannot be cast to type.")
1204+
.build();
1205+
}
1206+
1207+
@SuppressWarnings("unchecked")
1208+
@Override
1209+
public Class<? extends CREThrowable>[] thrown() {
1210+
return new Class[] {CRECastException.class};
1211+
}
1212+
1213+
@Override
1214+
public Integer[] numArgs() {
1215+
return new Integer[] {2};
1216+
}
1217+
1218+
@Override
1219+
public String docs() {
1220+
return "mixed {mixed value, ClassType type} Used internally by the compiler. You shouldn't use it.";
1221+
}
1222+
1223+
@Override
1224+
public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException {
1225+
Mixed value = args[0];
1226+
CClassType type = ArgumentValidation.getClassType(args[1], t);
1227+
if(!InstanceofUtil.isInstanceof(value, type, env)) {
1228+
throw new CRECastException(
1229+
"Cannot cast " + value.typeof().getName() + " to " + type.getName() + ".", t);
1230+
}
1231+
// TODO - Perform runtime conversion to 'type' when necessary (cross-cast handling).
1232+
return value;
1233+
}
1234+
1235+
@Override
1236+
public CClassType typecheck(StaticAnalysis analysis,
1237+
ParseTree ast, Environment env, Set<ConfigCompileException> exceptions) {
1238+
1239+
// Typecheck children and validate function signature through super call.
1240+
super.typecheck(analysis, ast, env, exceptions);
1241+
1242+
// Return type that is being cast to.
1243+
if(ast.numberOfChildren() != 2 || !(ast.getChildAt(1).getData() instanceof CClassType)) {
1244+
return CClassType.AUTO;
1245+
}
1246+
return (CClassType) ast.getChildAt(1).getData();
1247+
}
1248+
}
11391249
}

0 commit comments

Comments
 (0)