66import com .laytonsmith .annotations .hide ;
77import com .laytonsmith .annotations .noboilerplate ;
88import com .laytonsmith .annotations .noprofile ;
9+ import com .laytonsmith .core .ArgumentValidation ;
910import com .laytonsmith .core .FullyQualifiedClassName ;
1011import com .laytonsmith .core .MSVersion ;
1112import com .laytonsmith .core .Optimizable ;
1213import com .laytonsmith .core .ParseTree ;
1314import com .laytonsmith .core .Script ;
1415import com .laytonsmith .core .compiler .FileOptions ;
1516import com .laytonsmith .core .compiler .analysis .StaticAnalysis ;
17+ import com .laytonsmith .core .compiler .signature .FunctionSignatures ;
18+ import com .laytonsmith .core .compiler .signature .SignatureBuilder ;
1619import com .laytonsmith .core .constructs .CBareString ;
1720import com .laytonsmith .core .constructs .CBracket ;
1821import com .laytonsmith .core .constructs .CClassType ;
2629import com .laytonsmith .core .constructs .CVoid ;
2730import com .laytonsmith .core .constructs .Construct ;
2831import com .laytonsmith .core .constructs .IVariable ;
32+ import com .laytonsmith .core .constructs .InstanceofUtil ;
2933import com .laytonsmith .core .constructs .Target ;
3034import com .laytonsmith .core .constructs .Token ;
3135import 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