package com.igormaznitsa.ideamindmap.lang; import com.igormaznitsa.ideamindmap.lang.tokens.MMTokens; import com.igormaznitsa.meta.common.utils.Assertions; import com.igormaznitsa.mindmap.model.ModelUtils; import com.intellij.lang.ASTNode; import com.intellij.lang.LightPsiParser; import com.intellij.lang.PsiBuilder; import com.intellij.lang.PsiParser; import com.intellij.openapi.application.ApplicationManager; import com.intellij.psi.tree.IElementType; import javax.annotation.Nonnull; public class MMPsiParser implements PsiParser, LightPsiParser { @Nonnull @Override public ASTNode parse(@Nonnull final IElementType root, @Nonnull final PsiBuilder builder) { parseLight(root, builder); final ASTNode result = builder.getTreeBuilt(); return result; } @Override public void parseLight(final IElementType root, final PsiBuilder builder) { builder.setDebugMode(ApplicationManager.getApplication().isUnitTestMode()); final PsiBuilder.Marker marker = builder.mark(); parseHeader(builder); parseTopics(builder); marker.done(root); } private void parseHeader(@Nonnull final PsiBuilder builder) { boolean doLoop = true; while (doLoop && !builder.eof()) { final PsiBuilder.Marker marker = builder.mark(); if (builder.getTokenType() == null) { marker.drop(); } else { final IElementType token = builder.getTokenType(); if (token == MMTokens.HEADER_DELIMITER) { marker.done(token); doLoop = false; } else if (token == MMTokens.HEADER_LINE || token == MMTokens.UNKNOWN || token == MMTokens.WHITE_SPACE || token == MMTokens.ATTRIBUTES) { marker.done(token); } else { throw Assertions.fail("Unexpected header token : " + token); } } builder.advanceLexer(); } } private void parseTopics(@Nonnull final PsiBuilder builder) { while (!builder.eof()) { final PsiBuilder.Marker marker = builder.mark(); final IElementType token = builder.getTokenType(); if (token == null) { marker.drop(); } else { if (token == MMTokens.TOPIC_LEVEL) { final PsiBuilder.Marker levelMarker = builder.mark(); levelMarker.done(token); final int topicLevel = ModelUtils.calcCharsOnStart('#', builder.getTokenText()); if (topicLevel != 1) { marker.done(MMTokens.UNKNOWN); } else { builder.advanceLexer(); recursiveParseTopic(builder, topicLevel); marker.done(MMTokens.TOPIC); } } else { marker.done(MMTokens.UNKNOWN); } } builder.advanceLexer(); } } private int recursiveParseTopic(@Nonnull final PsiBuilder builder, final int level) { while (!builder.eof()) { final PsiBuilder.Marker marker = builder.mark(); final IElementType token = builder.getTokenType(); if (token == null) { marker.drop(); } else { if (token == MMTokens.TOPIC_LEVEL) { final PsiBuilder.Marker levelMarker = builder.mark(); levelMarker.done(token); final int theTopicLevel = ModelUtils.calcCharsOnStart('#', builder.getTokenText()); if (theTopicLevel <= 1) { marker.done(MMTokens.UNKNOWN); } else { if (theTopicLevel <= level) { marker.rollbackTo(); return theTopicLevel; } else { builder.advanceLexer(); final int parsedTopicLevel = recursiveParseTopic(builder, theTopicLevel); marker.done(MMTokens.TOPIC); if (parsedTopicLevel < theTopicLevel) return parsedTopicLevel; if (parsedTopicLevel == theTopicLevel) continue; } } } else if (token == MMTokens.TOPIC_TITLE || token == MMTokens.CODE_SNIPPET_BODY || token == MMTokens.CODE_SNIPPET_END || token == MMTokens.CODE_SNIPPET_START || token == MMTokens.ATTRIBUTES) { marker.done(token); } else if (token == MMTokens.EXTRA_TYPE) { try { if (parseExtraBlock(builder)) continue; }finally { marker.done(MMTokens.EXTRA_DATA); } } else if (token == MMTokens.WHITE_SPACE) { marker.done(token); } else { marker.done(MMTokens.UNKNOWN); } } builder.advanceLexer(); } return level; } private boolean parseExtraBlock(@Nonnull final PsiBuilder builder) { // read type final PsiBuilder.Marker type = builder.mark(); if (builder.getTokenType()!= MMTokens.EXTRA_TYPE) throw Assertions.fail("Unexpected token "+builder.getTokenType()); builder.advanceLexer(); type.done(MMTokens.EXTRA_TYPE); boolean dataFound = false; // read body while (!builder.eof()) { final PsiBuilder.Marker marker = builder.mark(); if (builder.eof() || builder.getTokenType() == null) { marker.drop(); break; } else { final IElementType token = builder.getTokenType(); if (token == MMTokens.TOPIC_LEVEL || token == MMTokens.EXTRA_TYPE) { marker.rollbackTo(); return true; } else if (token == MMTokens.EXTRA_BODY || token == MMTokens.WHITE_SPACE) { if (dataFound && token == MMTokens.EXTRA_BODY) { builder.advanceLexer(); marker.done(MMTokens.UNKNOWN); break; }else { builder.advanceLexer(); marker.done(token); dataFound = dataFound || token == MMTokens.EXTRA_BODY; } } else { marker.done(MMTokens.UNKNOWN); break; } } } return false; } }