Skip to content

Commit 40256c9

Browse files
committed
Add __cast__ function and syntax
Add `__cast__(val, type)` function and syntax. Syntax is `(type) val`.
1 parent e61eaa6 commit 40256c9

File tree

1 file changed

+156
-28
lines changed

1 file changed

+156
-28
lines changed

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

Lines changed: 156 additions & 28 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,44 @@ 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 = list.size() - 1; i >= 0; 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+
if(!typeNode.getData().isInstanceOf(CClassType.TYPE)) {
359+
ParseTree convertedTypeNode = __type_ref__.createFromBareStringOrConcats(typeNode);
360+
if(convertedTypeNode != null) {
361+
typeNode = convertedTypeNode;
362+
} else {
363+
364+
// This is not a "(classtype)" format. Skip node.
365+
continue;
366+
}
367+
}
368+
369+
// Rewrite p(A) and the next list entry B to __cast__(B, A).
370+
ParseTree castNode = new ParseTree(
371+
new CFunction(__cast__.NAME, node.getTarget()), node.getFileOptions());
372+
castNode.addChild(list.get(i + 1));
373+
castNode.addChild(typeNode);
374+
list.set(i, castNode);
375+
list.remove(i + 1);
376+
}
377+
}
378+
379+
// Rewrite binary operators.
380+
if(inSymbolMode) {
381+
try {
329382

330383
//Exponential
331384
for(int i = 0; i < list.size() - 1; i++) {
@@ -586,18 +639,30 @@ private static void rewriteParenthesis(List<ParseTree> list) throws ConfigCompil
586639
for(int listInd = list.size() - 1; listInd >= 1; listInd--) {
587640
Stack<ParseTree> executes = new Stack<>();
588641
while(listInd > 0) {
589-
ParseTree lastNode = list.get(listInd);
642+
ParseTree node = list.get(listInd);
590643
try {
591-
if(lastNode.getData() instanceof CFunction cf
644+
if(node.getData() instanceof CFunction cf
592645
&& cf.hasFunction()
593646
&& cf.getFunction() != null
594647
&& cf.getFunction().getName().equals(Compiler.p.NAME)) {
595-
Mixed prevNode = list.get(listInd - 1).getData();
596-
if(prevNode instanceof CSymbol || prevNode instanceof CLabel || prevNode instanceof CString) {
597-
// It's just a parenthesis like @a = (1); or key: (value), so we should leave it alone.
648+
ParseTree prevNode = list.get(listInd - 1);
649+
Mixed prevNodeVal = prevNode.getData();
650+
651+
// Do not rewrite parenthesis like "@a = (1);" or "key: (value)" to execute().
652+
if(prevNodeVal instanceof CSymbol
653+
|| prevNodeVal instanceof CLabel || prevNodeVal instanceof CString) {
598654
break;
599655
}
600-
executes.push(lastNode);
656+
657+
// Do not rewrite casts to execute() if the callable is the cast (i.e. "(type) (val)").
658+
if(prevNodeVal instanceof CFunction cfunc && cfunc.hasFunction() && cfunc.getFunction() != null
659+
&& cfunc.getFunction().getName().equals(Compiler.p.NAME) && prevNode.numberOfChildren() == 1
660+
&& (prevNode.getChildAt(0).getData().isInstanceOf(CClassType.TYPE)
661+
|| __type_ref__.createFromBareStringOrConcats(prevNode.getChildAt(0)) != null)) {
662+
break;
663+
}
664+
665+
executes.push(node);
601666
list.remove(listInd--);
602667
} else {
603668
break;
@@ -1136,4 +1201,67 @@ public ParseTree postParseRewrite(ParseTree ast, Environment env,
11361201
}
11371202
}
11381203
}
1204+
@api
1205+
@noprofile
1206+
@hide("This is only used internally by the compiler.")
1207+
public static class __cast__ extends DummyFunction {
1208+
1209+
public static final String NAME = "__cast__";
1210+
1211+
@Override
1212+
public String getName() {
1213+
return NAME;
1214+
}
1215+
1216+
@Override
1217+
public FunctionSignatures getSignatures() {
1218+
return new SignatureBuilder(CClassType.AUTO)
1219+
.param(Mixed.TYPE, "value", "The value.")
1220+
.param(CClassType.TYPE, "type", "The type.")
1221+
.throwsEx(CRECastException.class, "When value cannot be cast to type.")
1222+
.build();
1223+
}
1224+
1225+
@SuppressWarnings("unchecked")
1226+
@Override
1227+
public Class<? extends CREThrowable>[] thrown() {
1228+
return new Class[] {CRECastException.class};
1229+
}
1230+
1231+
@Override
1232+
public Integer[] numArgs() {
1233+
return new Integer[] {2};
1234+
}
1235+
1236+
@Override
1237+
public String docs() {
1238+
return "mixed {mixed value, ClassType type} Used internally by the compiler. You shouldn't use it.";
1239+
}
1240+
1241+
@Override
1242+
public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntimeException {
1243+
Mixed value = args[0];
1244+
CClassType type = ArgumentValidation.getClassType(args[1], t);
1245+
if(!InstanceofUtil.isInstanceof(value, type, env)) {
1246+
throw new CRECastException(
1247+
"Cannot cast from " + value.typeof().getSimpleName() + " to " + type.getSimpleName() + ".", t);
1248+
}
1249+
// TODO - Perform runtime conversion to 'type' when necessary (cross-cast handling).
1250+
return value;
1251+
}
1252+
1253+
@Override
1254+
public CClassType typecheck(StaticAnalysis analysis,
1255+
ParseTree ast, Environment env, Set<ConfigCompileException> exceptions) {
1256+
1257+
// Typecheck children and validate function signature through super call.
1258+
super.typecheck(analysis, ast, env, exceptions);
1259+
1260+
// Return type that is being cast to.
1261+
if(ast.numberOfChildren() != 2 || !(ast.getChildAt(1).getData() instanceof CClassType)) {
1262+
return CClassType.AUTO;
1263+
}
1264+
return (CClassType) ast.getChildAt(1).getData();
1265+
}
1266+
}
11391267
}

0 commit comments

Comments
 (0)