/******************************************************************************* * Copyright (c) 2015 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 *******************************************************************************/ package com.github.rustdt.tooling.ops; import java.io.File; import melnorme.lang.tooling.CompletionProposalKind; import melnorme.lang.tooling.ElementAttributes; import melnorme.lang.tooling.ToolCompletionProposal; import melnorme.lang.tooling.ToolingMessages; import melnorme.lang.tooling.ast.SourceRange; import melnorme.lang.tooling.common.SourceLineColumnRange; import melnorme.lang.tooling.toolchain.ops.AbstractToolResultParser; import melnorme.lang.tooling.toolchain.ops.SourceLocation; import melnorme.lang.tooling.toolchain.ops.ToolOutputParseHelper; 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.Location; import melnorme.utilbox.misc.StringUtil; public abstract class RacerCompletionOutputParser extends AbstractToolResultParser<ArrayList2<ToolCompletionProposal>> implements ToolOutputParseHelper { protected final int offset; protected int prefixLength; public RacerCompletionOutputParser(int offset) { this.offset = offset; } @Override protected String getToolName() throws CommonException { return "Racer"; } @Override public ArrayList2<ToolCompletionProposal> parseOutput(StringCharSource source) throws CommonException { ArrayList2<ToolCompletionProposal> proposals = new ArrayList2<>(); prefixLength = 0; while(true) { String line = LexingUtils.consumeUntilDelimiterOrEOS(source, '\n', '\\'); if(line == null || line.isEmpty()) { return proposals; } if(line.startsWith("PREFIX ")) { parsePrefix(line); } else if(line.startsWith("MATCH ")) { proposals.add(parseProposal(line, source)); } else if(line.startsWith("END")) { continue; } else { handleUnknownLineFormat(new CommonException("Unknown line format: " + line)); } } } protected void handleUnknownLineFormat(CommonException ce) throws CommonException { throw ce; } protected void parsePrefix(String line) throws CommonException { StringCharSource lineLexer = new StringCharSource(line); lineLexer.tryConsume("PREFIX "); int prefixStart = parsePositiveInt(consumeCommaDelimitedText(lineLexer)); int prefixEnd = parsePositiveInt(consumeCommaDelimitedText(lineLexer)); prefixLength = prefixEnd - prefixStart; } protected String consumeCommaDelimitedText(StringCharSource lineLexer) { return LexingUtils.consumeUntilDelimiterOrEOS(lineLexer, ','); } @SuppressWarnings("unused") public ToolCompletionProposal parseProposal(String line, StringCharSource source) throws CommonException { StringCharSource lineLexer = new StringCharSource(line); lineLexer.tryConsume("MATCH "); String baseName = consumeSemicolonDelimitedText(lineLexer); String rawLabel = consumeSemicolonDelimitedText(lineLexer); consumeSemicolonDelimitedText(lineLexer); // Line no consumeSemicolonDelimitedText(lineLexer); // Char no String rawModuleName = consumeSemicolonDelimitedText(lineLexer); String rawKind = consumeSemicolonDelimitedText(lineLexer); String description = consumeSemicolonDelimitedText(lineLexer); // Commented out - This wasn't really correct parsing // while(source.hasCharAhead() && !source.lookaheadMatches("MATCH ")) { // // This is a multiline description. // description += "\n" + assertNotNull(source.consumeLine()); // } CompletionProposalKind kind = parseKind(rawKind); String moduleName = parseModuleName(rawModuleName); ArrayList2<String> params = parseParametersFromRawLabel(rawLabel); String label = baseName; ArrayList2<SourceRange> subElements = null; String fullReplaceString = baseName; if(params != null) { label = baseName + "(" + StringUtil.collToString(params, ", ") + ")"; subElements = new ArrayList2<>(); fullReplaceString = buildFullReplacementStringAndSubElements(baseName, params, subElements); } if(kind == CompletionProposalKind.Module) { fullReplaceString = baseName + "::"; } ElementAttributes attributes = new ElementAttributes(null); // TODO int completionOffset = offset - prefixLength; return new ToolCompletionProposal( completionOffset, prefixLength, baseName, label, kind, attributes, null, moduleName, description, fullReplaceString, subElements); } protected String consumeSemicolonDelimitedText(StringCharSource lineLexer) { return LexingUtils.consumeUntilDelimiterOrEOS(lineLexer, ';'); } protected CompletionProposalKind parseKind(String rawKind) throws CommonException { String matchKindString = rawKind; try { return CompletionProposalKind.valueOf(matchKindString); } catch (IllegalArgumentException e) { handleInvalidMatchKindString(matchKindString); return CompletionProposalKind.UNKNOWN; } } protected abstract void handleInvalidMatchKindString(String matchKindString) throws CommonException; protected String parseModuleName(String rawModuleName) { String moduleName = StringUtil.substringAfterLastMatch(rawModuleName, File.separator); // For Window OSes, also trim module name using "/" moduleName = StringUtil.substringAfterLastMatch(moduleName, "/"); return moduleName; } /* ----------------- ----------------- */ protected ArrayList2<String> parseParametersFromRawLabel(String rawLabel) { StringCharSource lexer = new StringCharSource(rawLabel); lexer.consumeUntil("("); if(!lexer.tryConsume("(")) { return null; } ArrayList2<String> args = new ArrayList2<>(); while(lexer.hasCharAhead()){ if(lexer.tryConsume(")")) { break; } if(lexer.tryConsume("${")) { args.add(parseRawLabelArg(lexer)); continue; } lexer.consume(); } return args; } protected String parseRawLabelArg(StringCharSource lexer) { lexer.consumeUntil(":", true); return lexer.consumeUntil("}", true); } protected String buildFullReplacementStringAndSubElements(String baseName, ArrayList2<String> params, ArrayList2<SourceRange> subElements) { StringBuilder sb = new StringBuilder(); sb.append(baseName); sb.append("("); boolean first = true; for (String param : params) { if(!first) { sb.append(", "); } String paramName = getParamNameSuggestion(param); subElements.add(new SourceRange(sb.length(), paramName.length())); sb.append(paramName); first = false; } sb.append(")"); return sb.toString(); } protected String getParamNameSuggestion(String param) { String declaredParamName = StringUtil.substringUntilMatch(param, ":"); if(declaredParamName.isEmpty()) { return "__"; } return declaredParamName; } /* ----------------- find-definition ----------------- */ public SourceLocation parseResolvedMatch(String lineInput) throws CommonException { return doParseResolvedMatch(lineInput); } protected SourceLocation doParseResolvedMatch(String lineInput) throws CommonException { StringCharSource lineLexer = new StringCharSource(lineInput); if(lineLexer.tryConsume("MATCH ") == false) { throw new CommonException(ToolingMessages.FIND_DEFINITION_NoTargetFound); } @SuppressWarnings("unused") String elementName = consumeCommaDelimitedText(lineLexer); int line_1 = parsePositiveInt(consumeCommaDelimitedText(lineLexer)); int column_0 = parsePositiveInt(consumeCommaDelimitedText(lineLexer)); Location loc = parseLocation(consumeCommaDelimitedText(lineLexer)); SourceLineColumnRange position = new SourceLineColumnRange(line_1, column_0 + 1); return new SourceLocation(loc, position); } }