/******************************************************************************* * 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 static melnorme.utilbox.core.Assert.AssertNamespace.assertFail; import static melnorme.utilbox.core.Assert.AssertNamespace.assertNotNull; import java.util.Iterator; import melnorme.lang.tooling.EProtection; import melnorme.lang.tooling.ElementAttributes; import melnorme.lang.tooling.ast.ParserErrorTypes; import melnorme.lang.tooling.ast.SourceRange; import melnorme.lang.tooling.common.ParserError; import melnorme.lang.tooling.parser.TextBlocksReader; import melnorme.lang.tooling.parser.TextBlocksReader.BlockVisitorX; import melnorme.lang.tooling.parser.TextBlocksReader.TextBlocksSubReader; import melnorme.lang.tooling.structure.AbstractStructureParser; import melnorme.lang.tooling.structure.SourceFileStructure; import melnorme.lang.tooling.structure.StructureElement; import melnorme.lang.tooling.structure.StructureElementKind; 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; import melnorme.utilbox.status.Severity; public class RustParseDescribeParser extends AbstractStructureParser { public RustParseDescribeParser(Location location, String source) { super(location, source); } protected void reportError(String pattern, Object... arguments) throws CommonException { CommonException ce = CommonException.fromMsgFormat(pattern, arguments); handleParseError(ce); } /** The default implementation throws, but it could just log the error. */ protected void handleParseError(CommonException ce) throws CommonException { throw ce; } @Override public SourceFileStructure parse(String describeOutput) throws CommonException { TextBlocksReader reader = new TextBlocksReader(new StringCharSource(describeOutput)); reader.expectText("RUST_PARSE_DESCRIBE"); reader.consumeText(); try(TextBlocksSubReader subReader = reader.enterBlock()) { return parseSourceFileStructure_Contents(subReader); } } protected SourceFileStructure parseSourceFileStructure_Contents(TextBlocksSubReader reader) throws CommonException { ArrayList2<ParserError> parserProblems; reader.expectText("MESSAGES"); try(TextBlocksSubReader subReader = reader.enterBlock()) { parserProblems = parseSubElements(subReader, this::parseMessage); } ArrayList2<StructureElement> structureChildren = parseStructureElements(reader); return new SourceFileStructure(location, structureChildren, parserProblems); } /* ----------------- ----------------- */ protected ArrayList2<StructureElement> parseStructureElements(TextBlocksSubReader subReader) throws CommonException { ArrayList2<StructureElement> elements = parseSubElements(subReader, this::parseStructureElement); ArrayList2<StructureElement> reorganizedChildren = new ArrayList2<>(); reorganizeChildren(reorganizedChildren, elements.iterator()); return reorganizedChildren; } protected <RET> ArrayList2<RET> parseSubElements(TextBlocksSubReader subReader, BlockVisitorX<RET, CommonException> elementParser) throws CommonException { ArrayList2<RET> children = new ArrayList2<>(); while(!subReader.aheadIsEnd()) { RET element = elementParser.consumeChildren(subReader); children.add(element); } return children; } protected void reorganizeChildren(ArrayList2<StructureElement> reorganizedElems, Iterator<StructureElement> iter) { ArrayList2<StructureElement> useElements = null; while(iter.hasNext()) { StructureElement child = iter.next(); if(child.getKind() == StructureElementKind.USE) { if(useElements == null) { useElements = new ArrayList2<>(); } useElements.add(child); } if(child.getKind() != StructureElementKind.USE || !iter.hasNext()) { if(useElements != null) { int startPos = useElements.get(0).getSourceRange().getStartPos(); int endPos = useElements.get(useElements.size()-1).getSourceRange().getEndPos(); SourceRange sr = SourceRange.srStartToEnd(startPos, endPos); StructureElement aggregatedUses = new StructureElement("use declarations", SourceRange.srStartToEnd(startPos, startPos), sr, StructureElementKind.USE_GROUP, null, null, useElements); useElements = null; reorganizedElems.add(aggregatedUses); } } if(child.getKind() != StructureElementKind.USE) { reorganizedElems.add(child); } } } public ParserError parseMessage(TextBlocksSubReader topReader) throws CommonException { try(TextBlocksSubReader reader = topReader.enterBlock()) { String messageSeverity = reader.consumeText(); Severity severity = parseSeverity(messageSeverity); SourceRange sourceRange = parseSourceRange(reader); String messageText = reader.consumeText(); return new ParserError(ParserErrorTypes.GENERIC_ERROR, severity, sourceRange, messageText, null); } } public Severity parseSeverity(String severity) throws CommonException { try { return Severity.fromString(severity); } catch(CommonException e) { reportError("Invalid message severity `{0}`.", severity); return Severity.ERROR; } } protected StructureElement parseStructureElement(TextBlocksSubReader reader) throws CommonException { String item = reader.consumeText(); try(TextBlocksSubReader contentsReader = reader.enterBlock()) { StructureElement element = consumeStructureElement(contentsReader, item); assertNotNull(element); return element; } } protected StructureElement consumeStructureElement(TextBlocksSubReader reader, String kind) throws CommonException { StructureElementKind elementKind = parseElementKind(kind); String name = reader.consumeText(); SourceRange sourceRange = parseSourceRange(reader); if(sourceRange == null) { reportError("Empty source range."); sourceRange = SourceRange.srStartToEnd(0, 0); } SourceRange nameSourceRange = parseSourceRange(reader); if(nameSourceRange == null) { int start = sourceRange.getStartPos(); nameSourceRange = SourceRange.srStartToEnd(start, start); } String type = parseElementType(reader); ElementAttributes elementAttributes = parseElementAttributes(reader); ArrayList2<StructureElement> children = parseStructureElements(reader); return new StructureElement(name, nameSourceRange, sourceRange, elementKind, elementAttributes, type, children); } public SourceRange parseSourceRange(TextBlocksReader reader) throws CommonException { if(reader.aheadIsEnd()) { reportError("Missing source range."); return SourceRange.srStartToEnd(0, 0); } if(!reader.aheadIsBlockStart()) { reportError("Invalid source range."); return SourceRange.srStartToEnd(0, 0); } return reader.consumeBlock((subReader) -> { return parseSourceRangeContents(subReader); }); } public SourceRange parseSourceRangeContents(TextBlocksSubReader subReader) throws CommonException { if(subReader.aheadIsEnd()) { return null; } int start = parseSourceLocation(subReader.consumeText()); int end = !subReader.aheadIsEnd() ? parseSourceLocation(subReader.consumeText()) : start; return SourceRange.srStartToEnd(start, end); } public StructureElementKind parseElementKind(String kindText) throws CommonException { kindText = kindText.toUpperCase(); switch (kindText) { case "CRATE": case "EXTERNCRATE": kindText = StructureElementKind.EXTERN_CRATE.toString(); break; case "TYPEALIAS": kindText = StructureElementKind.TYPE_ALIAS.toString(); break; case "ENUMVARIANT": case "ENUMMEMBER": kindText = StructureElementKind.ENUM_VARIANT.toString(); break; } try { return StructureElementKind.valueOf(kindText); } catch(IllegalArgumentException e) { reportError("Unknown element kind `{0}`.", kindText); return StructureElementKind.UNKNOWN; } } public ElementAttributes parseElementAttributes(TextBlocksSubReader reader) throws CommonException { return reader.consumeBlock((subReader) -> { EProtection prot = null; if(reader.aheadIsText()) { String protText = reader.consumeText(); prot = parseProt_PubPriv(protText); } if(!subReader.aheadIsEnd()) { // reportError("Unknow attribute `{0}`.", consumeText); reportError("Unknown attribute."); subReader.skipToEnd(); } return new ElementAttributes(prot); }); } public EProtection parseProt_PubPriv(String consumeText) throws CommonException { String attribute = consumeText.toLowerCase(); switch (attribute) { case "pub": case "public": return EProtection.PUBLIC; case "priv": case "private": return EProtection.PRIVATE; default: reportError("Unknown protection `{0}`.", consumeText); return null; } } /* ----------------- ----------------- */ public String parseElementType(TextBlocksSubReader reader) throws CommonException { if(reader.aheadIsText()) { String type = reader.consumeText(); return StringUtil.emptyAsNull(type); } if(reader.aheadIsBlockStart()) { reader.consumeBlock((subReader) -> { if(!subReader.aheadIsEnd()) { reportError("Unknown element type."); } return null; }); return null; } reportError("Expected element type, {0}.", reader.errorAtTokenStart()); return null; } protected void unimplemented() { assertFail("BLAH"); } }