package org.asciidoc.intellij.parser;
import com.intellij.lang.PsiBuilder;
import com.intellij.psi.tree.IElementType;
import java.util.Stack;
import static org.asciidoc.intellij.lexer.AsciiDocTokenTypes.*;
/**
* @author yole
*/
public class AsciiDocParserImpl {
private static class SectionMarker {
int level;
PsiBuilder.Marker marker;
public SectionMarker(int level, PsiBuilder.Marker marker) {
this.level = level;
this.marker = marker;
}
}
private final PsiBuilder myBuilder;
private final Stack<SectionMarker> mySectionStack = new Stack<SectionMarker>();
private PsiBuilder.Marker myPreBlockMarker = null;
public AsciiDocParserImpl(PsiBuilder builder) {
myBuilder = builder;
}
public void parse() {
myBuilder.setDebugMode(true);
while (!myBuilder.eof()) {
if (at(HEADING) || at(HEADING_OLDSTYLE)) {
dropPreBlock();
int level = headingLevel(myBuilder.getTokenText());
closeSections(level);
SectionMarker newMarker = new SectionMarker(level, myBuilder.mark());
mySectionStack.push(newMarker);
}
else if (at(BLOCK_MACRO_ID)) {
markPreBlock();
next();
while (at(BLOCK_MACRO_BODY) || at(BLOCK_MACRO_ATTRIBUTES)) {
next();
}
myPreBlockMarker.done(AsciiDocElementTypes.BLOCK_MACRO);
myPreBlockMarker = null;
continue;
}
else if (at(EXAMPLE_BLOCK_DELIMITER) || at(SIDEBAR_BLOCK_DELIMITER) || at(QUOTE_BLOCK_DELIMITER)
|| at(LISTING_BLOCK_DELIMITER) || at(COMMENT_BLOCK_DELIMITER) || at(PASSTRHOUGH_BLOCK_DELIMITER)) {
parseBlock();
continue;
}
else if (at(TITLE)) {
markPreBlock();
next();
continue;
}
else if (at(BLOCK_ATTRS_START)) {
markPreBlock();
PsiBuilder.Marker blockAttrsMarker = myBuilder.mark();
next();
while (at(BLOCK_ATTR_NAME) || at(BLOCK_ATTRS_END)) {
next();
}
blockAttrsMarker.done(AsciiDocElementTypes.BLOCK_ATTRIBUTES);
continue;
}
dropPreBlock();
next();
}
dropPreBlock();
closeSections(0);
}
private void parseBlock() {
PsiBuilder.Marker myBlockStartMarker;
if (myPreBlockMarker != null) {
myBlockStartMarker = myPreBlockMarker;
myPreBlockMarker = null;
}
else {
myBlockStartMarker = beginBlock();
}
String marker = myBuilder.getTokenText();
IElementType type = myBuilder.getTokenType();
next();
while (true) {
if (myBuilder.eof()) {
myBlockStartMarker.done(AsciiDocElementTypes.BLOCK);
break;
}
// the block needs to be terminated by the same sequence that started it
if (at(type) && myBuilder.getTokenText().equals(marker)) {
next();
myBlockStartMarker.done(AsciiDocElementTypes.BLOCK);
break;
}
next();
}
}
private void markPreBlock() {
if (myPreBlockMarker == null) {
myPreBlockMarker = myBuilder.mark();
}
}
private void dropPreBlock() {
if (myPreBlockMarker != null) {
myPreBlockMarker.drop();
myPreBlockMarker = null;
}
}
private PsiBuilder.Marker beginBlock() {
return myBuilder.mark();
}
private void closeSections(int level) {
while (!mySectionStack.isEmpty() && mySectionStack.peek().level >= level) {
mySectionStack.pop().marker.done(AsciiDocElementTypes.SECTION);
}
}
private boolean at(IElementType elementType) {
return myBuilder.getTokenType() == elementType;
}
private void next() {
myBuilder.advanceLexer();
}
private static int headingLevel(String headingText) {
int result = 0;
while (result < headingText.length() && headingText.charAt(result) == '=') {
result++;
}
if (result == 0) {
// this is old header style
switch (headingText.charAt(headingText.length() - 2)) {
case '+':
++ result;
case '^':
++ result;
case '~':
++ result;
case '-':
++ result;
case '=':
++ result;
}
}
return result;
}
}