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,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 () - 2 ; 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