/*******************************************************************************
* Copyright (c) 2016 Bruno Medeiros and other Contributors.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Bruno Medeiros - initial API and implementation
* Pieter Penninckx - small refactoring
*******************************************************************************/
package com.github.rustdt.tooling;
import static melnorme.utilbox.core.Assert.AssertNamespace.assertNotNull;
import static melnorme.utilbox.core.CoreUtil.areEqual;
import java.util.Collections;
import melnorme.lang.tooling.toolchain.ops.BuildOutputParser2;
import melnorme.lang.tooling.toolchain.ops.OperationSoftFailure;
import melnorme.lang.tooling.toolchain.ops.ToolMessageData;
import melnorme.lang.utils.parse.LexingUtils;
import melnorme.lang.utils.parse.StringCharSource;
import melnorme.utilbox.collections.ArrayList2;
import melnorme.utilbox.core.CommonException;
import melnorme.utilbox.misc.HashcodeUtil;
import melnorme.utilbox.misc.IByteSequence;
import melnorme.utilbox.misc.StringUtil;
import melnorme.utilbox.process.ExternalProcessHelper.ExternalProcessResult;
/**
* Class output parser.
* This has been made obsolete with new error format in Rust 1.12,
* so it will be eventually phased out.
*/
public abstract class RustBuildOutputParser extends BuildOutputParser2 {
@Override
protected void handleNonZeroExitCode(ExternalProcessResult result) throws CommonException, OperationSoftFailure {
// Do nothing
}
@Override
protected IByteSequence getOutputFromProcessResult(ExternalProcessResult result) {
return result.getStdErrBytes();
}
protected boolean isMessageEnd(String nextLine) {
return nextLine.startsWith("error: aborting due to ");
}
@Override
protected CompositeToolMessageData parseMessageData(StringCharSource output) throws CommonException {
String outputLine = LexingUtils.consumeLine(output);
if(isMessageEnd(outputLine)) {
// We reached the end of messages, exhaust remaining tool output.
while(output.consumeAny()) {
}
return null;
}
ToolMessageData messageData = RUSTC_OUTPUT_LINE_PARSER.parseLine(outputLine);
if(messageData == null) {
ToolMessageData cargoMessageData = CARGO_OUTPUT_LINE_PARSER.parseLine(outputLine);
if (cargoMessageData != null) {
return new CompositeToolMessageData(cargoMessageData);
} else {
throw createUnknownLineSyntaxError(outputLine);
}
}
CompositeToolMessageData compositeMessageData = new CompositeToolMessageData(messageData);
parseMultiLineMessageText(output, compositeMessageData);
parseAssociatedMessage(output, compositeMessageData);
return compositeMessageData;
}
protected static final AbstractRustBuildOutputLineParser CARGO_OUTPUT_LINE_PARSER =
new CargoRustBuildOutputLineParser();
protected static final AbstractRustBuildOutputLineParser RUSTC_OUTPUT_LINE_PARSER =
new ClassicRustcRustBuildOutputLineParser();
protected void parseMultiLineMessageText(StringCharSource output, CompositeToolMessageData msg) {
while(true) {
String lineAhead = LexingUtils.stringUntilNewline(output);
if(lineAhead.isEmpty() || RUSTC_OUTPUT_LINE_PARSER.canParseLine(lineAhead)) {
break;
}
if(isMessageEnd(lineAhead)) {
break;
}
// Then this should be a multi line message text.
LexingUtils.consumeLine(output);
// However, first try to determine if this is the source snippet text,
// which we don't need to be part of message text
String thirdLine = LexingUtils.stringUntilNewline(output);
String thirdLineTrimmed = thirdLine.trim();
if(thirdLineTrimmed.startsWith("^") &&
(thirdLineTrimmed.endsWith("^") || thirdLineTrimmed.endsWith("~"))) {
LexingUtils.consumeLine(output);
// don't add this message, or nextLine, to actual error message.
break;
}
msg.messageText += "\n" + lineAhead;
}
}
protected void parseAssociatedMessage(StringCharSource output, CompositeToolMessageData msg)
throws CommonException {
String lineAhead = LexingUtils.stringUntilNewline(output);
msg.simpleMessageText = msg.messageText;
ToolMessageData nextMessage = RUSTC_OUTPUT_LINE_PARSER.parseLine(lineAhead);
if(nextMessage != null) {
if(isTemplateInstantiationMessage(nextMessage)) {
CompositeToolMessageData compositeNextMessage = parseMessageData(output);
msg.nextMessage = compositeNextMessage;
msg.messageText += "\n" + compositeNextMessage.getFullMessageSource();
}
}
}
public static class CompositeToolMessageData extends ToolMessageData {
public CompositeToolMessageData nextMessage;
public String simpleMessageText;
public CompositeToolMessageData() {
}
public CompositeToolMessageData(ToolMessageData init) {
super(init);
}
public String getFullMessageSource() {
assertNotNull(sourceBeforeMessageText);
assertNotNull(messageText);
return sourceBeforeMessageText + messageText;
}
@Override
public boolean equals(Object obj) {
if(this == obj) return true;
if(!(obj instanceof CompositeToolMessageData)) return false;
CompositeToolMessageData other = (CompositeToolMessageData) obj;
return
equalsOther(other) &&
areEqual(nextMessage, other.nextMessage) &&
areEqual(simpleMessageText, other.simpleMessageText)
;
}
@Override
public int hashCode() {
return HashcodeUtil.combinedHashCode(super.hashCode(), nextMessage);
}
@Override
public String toString() {
return getFullMessageSource();
}
}
protected boolean isTemplateInstantiationMessage(ToolMessageData nextMessage) {
String messageText = nextMessage.messageText;
return
areEqual(nextMessage.messageTypeString, "info") && messageText != null &&
(messageText.startsWith("in expansion of") || areEqual(messageText, "expansion site"));
}
@Override
protected void addBuildMessage(ToolMessageData toolMessage_) throws CommonException {
if(StringUtil.nullAsEmpty(toolMessage_.messageTypeString).isEmpty()) {
toolMessage_.messageTypeString = "error";
}
super.addBuildMessage(toolMessage_);
if(toolMessage_ instanceof CompositeToolMessageData) {
CompositeToolMessageData toolMessage = (CompositeToolMessageData) toolMessage_;
if(toolMessage.nextMessage != null && areEqual(toolMessage.messageTypeString, "error")) {
// This error message has linked errors - try to find expansion site error
ArrayList2<CompositeToolMessageData> linkedErrors = new ArrayList2<>();
buildChain(toolMessage, linkedErrors);
Collections.reverse(linkedErrors);
CompositeToolMessageData topError = linkedErrors.get(0);
if(topError.messageText.equals("expansion site")) {
super.addBuildMessage(createExpansionSiteErrorMessage(linkedErrors, topError));
}
}
}
}
protected void buildChain(CompositeToolMessageData msg, ArrayList2<CompositeToolMessageData> errorsChain) {
errorsChain.add(msg);
if(msg.nextMessage == null) {
return;
}
buildChain(msg.nextMessage, errorsChain);
}
protected ToolMessageData createExpansionSiteErrorMessage(ArrayList2<CompositeToolMessageData> linkedErrors,
CompositeToolMessageData topError) {
// Note: it's ok to mutate topError, since it is otherwise no longer used
topError.messageTypeString = "error";
topError.messageText = "[macro expansion error]";
for (int ix = 1; ix < linkedErrors.size(); ix++) {
CompositeToolMessageData linkedError = linkedErrors.get(ix);
topError.messageText += "\n" + linkedError.sourceBeforeMessageText + linkedError.simpleMessageText;
}
return topError;
}
}