/* * $Id$ * * SARL is an general-purpose agent programming language. * More details on http://www.sarl.io * * Copyright (C) 2014-2017 the original authors or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.sarl.lang.compiler; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.google.common.base.Strings; import com.google.common.collect.Lists; import com.google.inject.Inject; import org.eclipse.emf.ecore.EObject; import org.eclipse.xtend.core.compiler.XtendCompiler; import org.eclipse.xtext.common.types.JvmAnnotationAnnotationValue; import org.eclipse.xtext.common.types.JvmAnnotationReference; import org.eclipse.xtext.common.types.JvmAnnotationValue; import org.eclipse.xtext.common.types.JvmBooleanAnnotationValue; import org.eclipse.xtext.common.types.JvmByteAnnotationValue; import org.eclipse.xtext.common.types.JvmCharAnnotationValue; import org.eclipse.xtext.common.types.JvmCustomAnnotationValue; import org.eclipse.xtext.common.types.JvmDoubleAnnotationValue; import org.eclipse.xtext.common.types.JvmEnumAnnotationValue; import org.eclipse.xtext.common.types.JvmExecutable; import org.eclipse.xtext.common.types.JvmFloatAnnotationValue; import org.eclipse.xtext.common.types.JvmFormalParameter; import org.eclipse.xtext.common.types.JvmIdentifiableElement; import org.eclipse.xtext.common.types.JvmIntAnnotationValue; import org.eclipse.xtext.common.types.JvmLongAnnotationValue; import org.eclipse.xtext.common.types.JvmShortAnnotationValue; import org.eclipse.xtext.common.types.JvmStringAnnotationValue; import org.eclipse.xtext.common.types.JvmTypeAnnotationValue; import org.eclipse.xtext.common.types.JvmTypeReference; import org.eclipse.xtext.xbase.XAbstractFeatureCall; import org.eclipse.xtext.xbase.XAssignment; import org.eclipse.xtext.xbase.XBooleanLiteral; import org.eclipse.xtext.xbase.XExpression; import org.eclipse.xtext.xbase.XNumberLiteral; import org.eclipse.xtext.xbase.XStringLiteral; import org.eclipse.xtext.xbase.XTypeLiteral; import org.eclipse.xtext.xbase.compiler.output.ITreeAppendable; import org.eclipse.xtext.xbase.typesystem.IBatchTypeResolver; import org.eclipse.xtext.xbase.typesystem.IResolvedTypes; import org.eclipse.xtext.xbase.typesystem.references.LightweightTypeReference; import org.eclipse.xtext.xbase.util.XExpressionHelper; import io.sarl.lang.jvmmodel.Messages; import io.sarl.lang.sarl.SarlBreakExpression; /** The compiler from SARL to the target language. * * <p>This compiler provide a specific support for inline annotations. Indeed, the Xbase inline evaluation does * not support variadic parameters. This SARL compiler provides a support for variadic feature calls. * * <p>Additionally, this compiler supports the Inline annotation for non-static calls, by skipping the left * operand of a member feature call when the inline expression is constant. * * <p>This compiler supports also the "$0" parameter in inline expression. This parameter represents the * current receiver, e.g. "this.". * * <p>The compiler supports the SARL keywords too: break. * * <p>This compiler catches exceptions when generating statements for expressions in order to let the compiler * to generate as much as possible. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ * @since 0.4 */ public class SarlCompiler extends XtendCompiler { private static final String INLINE_VARIABLE_PREFIX = "$"; //$NON-NLS-1$ private static final String INLINE_VALUE_NAME = "value"; //$NON-NLS-1$ private static final String INLINE_IMPORTED_NAME = "imported"; //$NON-NLS-1$ private static final String CONSTANT_EXPRESSION_NAME = "constantExpression"; //$NON-NLS-1$ private static final Pattern INLINE_VARIABLE_PATTERN = Pattern.compile("\\" + INLINE_VARIABLE_PREFIX //$NON-NLS-1$ + "(\\" + INLINE_VARIABLE_PREFIX + "|[0-9]+)"); //$NON-NLS-1$ //$NON-NLS-2$ @Inject private Logger log; @Inject private XExpressionHelper expressionHelper; @Inject private IBatchTypeResolver batchTypeResolver; @SuppressWarnings({"checkstyle:returncount", "checkstyle:npathcomplexity", "checkstyle:cyclomaticcomplexity"}) private static String getAnnotationStringValue(JvmAnnotationValue value) { if (value instanceof JvmAnnotationAnnotationValue) { return ((JvmAnnotationAnnotationValue) value).getValues().get(0).getAnnotation().getIdentifier(); } if (value instanceof JvmBooleanAnnotationValue) { return ((JvmBooleanAnnotationValue) value).getValues().get(0).toString(); } if (value instanceof JvmByteAnnotationValue) { return ((JvmByteAnnotationValue) value).getValues().get(0).toString(); } if (value instanceof JvmCharAnnotationValue) { return ((JvmCharAnnotationValue) value).getValues().get(0).toString(); } if (value instanceof JvmCustomAnnotationValue) { final EObject evalue = ((JvmCustomAnnotationValue) value).getValues().get(0); if (evalue instanceof XStringLiteral) { return ((XStringLiteral) evalue).getValue(); } if (evalue instanceof XNumberLiteral) { return ((XNumberLiteral) evalue).getValue(); } if (evalue instanceof XBooleanLiteral) { return ((XNumberLiteral) evalue).getValue(); } if (evalue instanceof XTypeLiteral) { return ((XTypeLiteral) evalue).getType().getIdentifier(); } } if (value instanceof JvmDoubleAnnotationValue) { return ((JvmDoubleAnnotationValue) value).getValues().get(0).toString(); } if (value instanceof JvmEnumAnnotationValue) { return ((JvmEnumAnnotationValue) value).getValues().get(0).getSimpleName(); } if (value instanceof JvmFloatAnnotationValue) { return ((JvmFloatAnnotationValue) value).getValues().get(0).toString(); } if (value instanceof JvmIntAnnotationValue) { return ((JvmIntAnnotationValue) value).getValues().get(0).toString(); } if (value instanceof JvmLongAnnotationValue) { return ((JvmLongAnnotationValue) value).getValues().get(0).toString(); } if (value instanceof JvmShortAnnotationValue) { return ((JvmShortAnnotationValue) value).getValues().get(0).toString(); } if (value instanceof JvmStringAnnotationValue) { return ((JvmStringAnnotationValue) value).getValues().get(0); } if (value instanceof JvmTypeAnnotationValue) { return ((JvmTypeAnnotationValue) value).getValues().get(0).getIdentifier(); } return null; } private static Collection<JvmTypeReference> getAnnotationTypeValue(JvmAnnotationValue value) { if (value instanceof JvmTypeAnnotationValue) { return ((JvmTypeAnnotationValue) value).getValues(); } return Collections.emptyList(); } @Override @SuppressWarnings({"checkstyle:cyclomaticcomplexity", "checkstyle:npathcomplexity"}) protected synchronized void appendInlineFeatureCall(XAbstractFeatureCall call, ITreeAppendable target) { final JvmAnnotationReference inlineAnnotation = this.expressionHelper.findInlineAnnotation(call); String formatString = null; final List<JvmTypeReference> importedTypes = Lists.newArrayListWithCapacity(2); for (final JvmAnnotationValue annotationValue: inlineAnnotation.getValues()) { final String valueName = annotationValue.getValueName(); if (Strings.isNullOrEmpty(valueName)) { // Special case: the annotation value as no associated operation. // If it appends, we could assumes that the operation is "value()" if (!Strings.isNullOrEmpty(formatString)) { throw new IllegalStateException(); } formatString = getAnnotationStringValue(annotationValue); } else if (INLINE_VALUE_NAME.equals(valueName)) { if (!Strings.isNullOrEmpty(formatString)) { throw new IllegalStateException(); } formatString = getAnnotationStringValue(annotationValue); } else if (INLINE_IMPORTED_NAME.equals(valueName)) { importedTypes.addAll(getAnnotationTypeValue(annotationValue)); } } if (formatString == null) { throw new IllegalStateException(); } final IResolvedTypes resolvedTypes = this.batchTypeResolver.resolveTypes(call); final List<XExpression> arguments = getActualArguments(call); final JvmIdentifiableElement calledFeature = call.getFeature(); int numberVariadicParameter = 0; final int numberFormalParameters; JvmFormalParameter formalVariadicParameter = null; if (calledFeature instanceof JvmExecutable) { final JvmExecutable jvmexec = (JvmExecutable) calledFeature; numberFormalParameters = jvmexec.getParameters().size(); if (numberFormalParameters > 0) { formalVariadicParameter = jvmexec.getParameters().get(numberFormalParameters - 1); if (jvmexec.isVarArgs()) { numberVariadicParameter = 1; } } } else { numberFormalParameters = arguments.size(); } final Matcher matcher = INLINE_VARIABLE_PATTERN.matcher(formatString); int prevEnd = 0; while (matcher.find()) { final int start = matcher.start(); if (start != prevEnd) { target.append(formatString.substring(prevEnd, start)); } final String indexOrDollar = matcher.group(1); if (INLINE_VARIABLE_PREFIX.equals(indexOrDollar)) { target.append(INLINE_VARIABLE_PREFIX); } else { final int index = Integer.parseInt(indexOrDollar) - 1; // Treat the $0 parameter in the inline expression if (index < 0) { final boolean hasReceiver = appendReceiver(call, target, true); if (hasReceiver) { target.append("."); //$NON-NLS-1$ } } else { final int numberImports = importedTypes.size(); final int numberFormalParametersImports = numberFormalParameters + numberImports; if (numberVariadicParameter != 0 && index < arguments.size() && index == (numberFormalParameters - 1)) { XExpression argument = arguments.get(index); appendArgument(argument, target, index > 0); for (int i = index + 1; i < arguments.size(); ++i) { target.append(", "); //$NON-NLS-1$ argument = arguments.get(i); appendArgument(argument, target, true); } } else if (index > numberFormalParametersImports) { final List<LightweightTypeReference> typeArguments = resolvedTypes.getActualTypeArguments(call); final LightweightTypeReference typeArgument = typeArguments.get(index - numberFormalParametersImports - 1); serialize(typeArgument.getRawTypeReference().toTypeReference(), call, target); } else if (index >= numberFormalParameters && index < numberFormalParametersImports) { serialize(importedTypes.get(index - numberFormalParameters), call, target); } else if (index == numberFormalParametersImports) { appendTypeArguments(call, target); } else if (index < arguments.size()) { final XExpression argument = arguments.get(index); appendArgument(argument, target, index > 0); } else if (formalVariadicParameter != null) { appendNullValue(formalVariadicParameter.getParameterType(), calledFeature, target); } else { throw new IllegalStateException(); } } } prevEnd = matcher.end(); } if (prevEnd != formatString.length()) { target.append(formatString.substring(prevEnd)); } } private static boolean isConstantExpression(JvmAnnotationReference reference) { //TODO: Remove when Xtext issue is fixed; https://github.com/eclipse/xtext-extras/issues/43 for (final JvmAnnotationValue annotationValue: reference.getValues()) { if (CONSTANT_EXPRESSION_NAME.equals(annotationValue.getValueName())) { return ((JvmBooleanAnnotationValue) annotationValue).getValues().get(0).booleanValue(); } } return false; } @Override protected void featureCalltoJavaExpression(final XAbstractFeatureCall call, ITreeAppendable output, boolean isExpressionContext) { //TODO: Remove when Xtext issue is fixed; https://github.com/eclipse/xtext-extras/issues/43 if (call instanceof XAssignment) { assignmentToJavaExpression((XAssignment) call, output, isExpressionContext); } else { if (needMultiAssignment(call)) { appendLeftOperand(call, output, isExpressionContext).append(" = "); //$NON-NLS-1$ } ITreeAppendable child = output; final JvmAnnotationReference annotationRef = this.expressionHelper.findInlineAnnotation(call); if (annotationRef == null || !isConstantExpression(annotationRef)) { final boolean hasReceiver = appendReceiver(call, output, isExpressionContext); if (hasReceiver) { output.append("."); //$NON-NLS-1$ child = appendTypeArguments(call, output); } } appendFeatureCall(call, child); } } @Override public void doInternalToJavaStatement(XExpression obj, ITreeAppendable appendable, boolean isReferenced) { if (obj instanceof SarlBreakExpression) { _toJavaStatement((SarlBreakExpression) obj, appendable, isReferenced); } else { try { super.doInternalToJavaStatement(obj, appendable, isReferenced); } catch (IllegalStateException exception) { // Log the exception but do not fail the generation. logInternalError(exception); } } } /** Generate the JAva code for the break keyword. * * @param breakExpression the expression. * @param appendable the output. * @param isReferenced indicates if the expression is referenced. */ @SuppressWarnings("static-method") protected void _toJavaStatement(SarlBreakExpression breakExpression, ITreeAppendable appendable, boolean isReferenced) { appendable.newLine().append("break;"); //$NON-NLS-1$ } /** Log an internal error but do not fail. * * @param exception the exception to log. */ protected void logInternalError(Throwable exception) { if (exception != null && this.log.isLoggable(Level.FINEST)) { this.log.log(Level.FINEST, Messages.SARLJvmModelInferrer_0, exception); } } }