2828import ch .njol .skript .lang .ExpressionType ;
2929import ch .njol .skript .lang .Literal ;
3030import ch .njol .skript .lang .SkriptParser .ParseResult ;
31+ import ch .njol .skript .lang .UnparsedLiteral ;
3132import ch .njol .skript .lang .util .SimpleExpression ;
3233import ch .njol .skript .lang .util .SimpleLiteral ;
3334import ch .njol .skript .registrations .Classes ;
@@ -117,43 +118,151 @@ public PatternInfo(Operator operator, boolean leftGrouped, boolean rightGrouped)
117118 private boolean leftGrouped , rightGrouped ;
118119
119120 @ Override
120- @ SuppressWarnings ({"ConstantConditions" , "unchecked" })
121+ @ SuppressWarnings ({"ConstantConditions" , "rawtypes" , " unchecked" })
121122 public boolean init (Expression <?>[] exprs , int matchedPattern , Kleenean isDelayed , ParseResult parseResult ) {
122- first = LiteralUtils .defendExpression (exprs [0 ]);
123- second = LiteralUtils .defendExpression (exprs [1 ]);
124-
125- if (!LiteralUtils .canInitSafely (first , second ))
126- return false ;
127-
128- Class <? extends L > firstClass = first .getReturnType ();
129- Class <? extends R > secondClass = second .getReturnType ();
123+ first = (Expression <L >) exprs [0 ];
124+ second = (Expression <R >) exprs [1 ];
130125
131126 PatternInfo patternInfo = patterns .getInfo (matchedPattern );
132127 leftGrouped = patternInfo .leftGrouped ;
133128 rightGrouped = patternInfo .rightGrouped ;
134129 operator = patternInfo .operator ;
135130
136- if (firstClass != Object .class && secondClass == Object .class && Arithmetics .getOperations (operator , firstClass ).isEmpty ()) {
137- // If the first class is known but doesn't have any operations, then we fail
138- return error (firstClass , secondClass );
139- } else if (firstClass == Object .class && secondClass != Object .class && Arithmetics .getOperations (operator ).stream ()
140- .noneMatch (info -> info .getRight ().isAssignableFrom (secondClass ))) {
141- // If the second class is known but doesn't have any operations, then we fail
142- return error (firstClass , secondClass );
131+ /*
132+ * Step 1: UnparsedLiteral Resolving
133+ *
134+ * Since Arithmetic may be performed on a variety of types, it is possible that 'first' or 'second'
135+ * will represent unparsed literals. That is, the parser could not determine what their literal contents represent.
136+ * Thus, it is now up to this expression to determine what they mean.
137+ *
138+ * If there are no unparsed literals, nothing happens at this step.
139+ * If there are unparsed literals, one of three possible execution flows will occur:
140+ *
141+ * Case 1. 'first' and 'second' are unparsed literals
142+ * In this case, there is not a lot of information to work with.
143+ * 'first' and 'second' are attempted to be converted to fit one of all operations using 'operator'.
144+ * If they cannot be matched with the types of a known operation, init will fail.
145+ *
146+ * Case 2. 'first' is an unparsed literal, 'second' is not
147+ * In this case, 'first' needs to be converted into the "left" type of
148+ * any operation using 'operator' with the type of 'second' as the right type.
149+ * If 'first' cannot be converted, init will fail.
150+ * If no operations are found for converting 'first', init will fail, UNLESS the type of 'second' is object,
151+ * where operations will be searched again later with the context of the type of first.
152+ * TODO When 'first' can represent multiple literals, it might be worth checking which of those can work with 'operator' and 'second'
153+ *
154+ * Case 3. 'second' is an unparsed literal, 'first' is not
155+ * In this case, 'second' needs to be converted into the "right" type of
156+ * any operation using 'operator' with the type of 'first' as the "left" type.
157+ * If 'second' cannot be converted, init will fail.
158+ * If no operations are found for converting 'second', init will fail, UNLESS the type of 'first' is object,
159+ * where operations will be searched again later with the context of the type of second.
160+ * TODO When 'second' can represent multiple literals, it might be worth checking which of those can work with 'first' and 'operator'
161+ */
162+
163+ if (first instanceof UnparsedLiteral ) {
164+ if (second instanceof UnparsedLiteral ) { // first and second need converting
165+ for (OperationInfo <?, ?, ?> operation : Arithmetics .getOperations (operator )) {
166+ // match left type with 'first'
167+ Expression <?> convertedFirst = first .getConvertedExpression (operation .getLeft ());
168+ if (convertedFirst == null )
169+ continue ;
170+ // match right type with 'second'
171+ Expression <?> convertedSecond = second .getConvertedExpression (operation .getRight ());
172+ if (convertedSecond == null )
173+ continue ;
174+ // success, set the values
175+ first = (Expression <L >) convertedFirst ;
176+ second = (Expression <R >) convertedSecond ;
177+ returnType = (Class <? extends T >) operation .getReturnType ();
178+ }
179+ } else { // first needs converting
180+ // attempt to convert <first> to types that make valid operations with <second>
181+ Class <?> secondClass = second .getReturnType ();
182+ Class [] leftTypes = Arithmetics .getOperations (operator ).stream ()
183+ .filter (info -> info .getRight ().isAssignableFrom (secondClass ))
184+ .map (OperationInfo ::getLeft )
185+ .toArray (Class []::new );
186+ if (leftTypes .length == 0 ) { // no known operations with second's type
187+ if (secondClass != Object .class ) // there won't be any operations
188+ return error (first .getReturnType (), secondClass );
189+ first = (Expression <L >) first .getConvertedExpression (Object .class );
190+ } else {
191+ first = (Expression <L >) first .getConvertedExpression (leftTypes );
192+ }
193+ }
194+ } else if (second instanceof UnparsedLiteral ) { // second needs converting
195+ // attempt to convert <second> to types that make valid operations with <first>
196+ Class <?> firstClass = first .getReturnType ();
197+ List <? extends OperationInfo <?, ?, ?>> operations = Arithmetics .getOperations (operator , firstClass );
198+ if (operations .isEmpty ()) { // no known operations with first's type
199+ if (firstClass != Object .class ) // there won't be any operations
200+ return error (firstClass , second .getReturnType ());
201+ second = (Expression <R >) second .getConvertedExpression (Object .class );
202+ } else {
203+ second = (Expression <R >) second .getConvertedExpression (operations .stream ()
204+ .map (OperationInfo ::getRight )
205+ .toArray (Class []::new )
206+ );
207+ }
143208 }
144209
145- OperationInfo <L , R , T > operationInfo ;
210+ if (!LiteralUtils .canInitSafely (first , second )) // checks if there are still unparsed literals present
211+ return false ;
212+
213+ /*
214+ * Step 2: Return Type Calculation
215+ *
216+ * After the first step, everything that can be known about 'first' and 'second' during parsing is known.
217+ * As a result, it is time to determine the return type of the operation.
218+ *
219+ * If the types of 'first' or 'second' are object, it is possible that multiple operations with different return types
220+ * will be found. If that is the case, the supertype of these operations will be the return type (can be object).
221+ * If the types of both are object (e.g. variables), the return type will be object (have to wait until runtime and hope it works).
222+ * Of course, if no operations are found, init will fail.
223+ *
224+ * After these checks, it is safe to assume returnType has a value, as init should have failed by now if not.
225+ * One final check is performed specifically for numerical types.
226+ * Any numerical operation involving division or exponents have a return type of Double.
227+ * Other operations will also return Double, UNLESS 'first' and 'second' are of integer types, in which case the return type will be Long.
228+ *
229+ * If the types of both are something meaningful, the search for a registered operation commences.
230+ * If no operation can be found, init will fail.
231+ */
232+
233+ Class <? extends L > firstClass = first .getReturnType ();
234+ Class <? extends R > secondClass = second .getReturnType ();
235+
146236 if (firstClass == Object .class || secondClass == Object .class ) {
147- // If either of the types is unknown, then we resolve the operation at runtime
148- operationInfo = null ;
149- } else {
150- operationInfo = (OperationInfo <L , R , T >) Arithmetics .lookupOperationInfo (operator , firstClass , secondClass );
151- if (operationInfo == null ) // We error if we couldn't find an operation between the two types
237+ // if either of the types is unknown, then we resolve the operation at runtime
238+ Class <?>[] returnTypes = null ;
239+ if (!(firstClass == Object .class && secondClass == Object .class )) { // both aren't object
240+ if (firstClass == Object .class ) {
241+ returnTypes = Arithmetics .getOperations (operator ).stream ()
242+ .filter (info -> info .getRight ().isAssignableFrom (secondClass ))
243+ .map (OperationInfo ::getReturnType )
244+ .toArray (Class []::new );
245+ } else { // secondClass is Object
246+ returnTypes = Arithmetics .getOperations (operator , firstClass ).stream ()
247+ .map (OperationInfo ::getReturnType )
248+ .toArray (Class []::new );
249+ }
250+ }
251+ if (returnTypes == null ) { // both are object; can't determine anything
252+ returnType = (Class <? extends T >) Object .class ;
253+ } else if (returnTypes .length == 0 ) { // one of the classes is known but doesn't have any operations
254+ return error (firstClass , secondClass );
255+ } else {
256+ returnType = (Class <? extends T >) Classes .getSuperClassInfo (returnTypes ).getC ();
257+ }
258+ } else if (returnType == null ) { // lookup
259+ OperationInfo <L , R , T > operationInfo = (OperationInfo <L , R , T >) Arithmetics .lookupOperationInfo (operator , firstClass , secondClass );
260+ if (operationInfo == null ) // we error if we couldn't find an operation between the two types
152261 return error (firstClass , secondClass );
262+ returnType = operationInfo .getReturnType ();
153263 }
154264
155- returnType = operationInfo == null ? (Class <? extends T >) Object .class : operationInfo .getReturnType ();
156-
265+ // ensure proper return types for numerical operations
157266 if (Number .class .isAssignableFrom (returnType )) {
158267 if (operator == Operator .DIVISION || operator == Operator .EXPONENTIATION ) {
159268 returnType = (Class <? extends T >) Double .class ;
@@ -164,19 +273,31 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye
164273 firstIsInt |= i .isAssignableFrom (first .getReturnType ());
165274 secondIsInt |= i .isAssignableFrom (second .getReturnType ());
166275 }
167-
168276 returnType = (Class <? extends T >) (firstIsInt && secondIsInt ? Long .class : Double .class );
169277 }
170278 }
171279
172- // Chaining
173- if (first instanceof ExprArithmetic && !leftGrouped ) {
280+ /*
281+ * Step 3: Chaining and Parsing
282+ *
283+ * This step builds the arithmetic chain that will be parsed into an ordered operation to be executed at runtime.
284+ * With larger operations, it is possible that 'first' or 'second' will be instances of ExprArithmetic.
285+ * As a result, their chains need to be incorporated into this instance's chain.
286+ * This is to ensure that, during parsing, a "gettable" that follows the order of operations is built.
287+ * However, in the case of parentheses, the chains will not be combined as the
288+ * order of operations dictates that the result of that chain be determined first.
289+ *
290+ * The chain (a list of values and operators) will then be parsed into a "gettable" that
291+ * can be evaluated during runtime for a final result.
292+ */
293+
294+ if (first instanceof ExprArithmetic && !leftGrouped ) { // combine chain of 'first' if we do not have parentheses
174295 chain .addAll (((ExprArithmetic <?, ?, L >) first ).chain );
175296 } else {
176297 chain .add (first );
177298 }
178299 chain .add (operator );
179- if (second instanceof ExprArithmetic && !rightGrouped ) {
300+ if (second instanceof ExprArithmetic && !rightGrouped ) { // combine chain of 'second' if we do not have parentheses
180301 chain .addAll (((ExprArithmetic <?, ?, R >) second ).chain );
181302 } else {
182303 chain .add (second );
@@ -197,7 +318,8 @@ protected T[] get(Event event) {
197318
198319 private boolean error (Class <?> firstClass , Class <?> secondClass ) {
199320 ClassInfo <?> first = Classes .getSuperClassInfo (firstClass ), second = Classes .getSuperClassInfo (secondClass );
200- Skript .error (operator .getName () + " can't be performed on " + first .getName ().withIndefiniteArticle () + " and " + second .getName ().withIndefiniteArticle ());
321+ if (first .getC () != Object .class && second .getC () != Object .class ) // errors with "object" are not very useful and often misleading
322+ Skript .error (operator .getName () + " can't be performed on " + first .getName ().withIndefiniteArticle () + " and " + second .getName ().withIndefiniteArticle ());
201323 return false ;
202324 }
203325
0 commit comments