package org.jetbrains.plugins.cucumber.psi;
import com.intellij.lang.ASTNode;
import com.intellij.lang.PsiBuilder;
import com.intellij.lang.PsiParser;
import com.intellij.psi.tree.IElementType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author yole
*/
public class GherkinParser implements PsiParser {
@NotNull
public ASTNode parse(IElementType root, PsiBuilder builder) {
final PsiBuilder.Marker marker = builder.mark();
parseFileTopLevel(builder);
marker.done(GherkinElementTypes.GHERKIN_FILE);
return builder.getTreeBuilt();
}
private static void parseFileTopLevel(PsiBuilder builder) {
while(!builder.eof()) {
final IElementType tokenType = builder.getTokenType();
if (tokenType == GherkinTokenTypes.FEATURE_KEYWORD) {
parseFeature(builder);
} else if (tokenType == GherkinTokenTypes.TAG) {
parseTags(builder);
} else {
builder.advanceLexer();
}
}
}
private static void parseFeature(PsiBuilder builder) {
final PsiBuilder.Marker marker = builder.mark();
assert builder.getTokenType() == GherkinTokenTypes.FEATURE_KEYWORD;
final int featureEnd = builder.getCurrentOffset() + getTokenLength(builder.getTokenText());
PsiBuilder.Marker descMarker = null;
while(true) {
final IElementType tokenType = builder.getTokenType();
if (tokenType == GherkinTokenTypes.TEXT && descMarker == null) {
if (hadLineBreakBefore(builder, featureEnd)) {
descMarker = builder.mark();
}
}
if (tokenType == GherkinTokenTypes.SCENARIO_KEYWORD ||
tokenType == GherkinTokenTypes.BACKGROUND_KEYWORD ||
tokenType == GherkinTokenTypes.SCENARIO_OUTLINE_KEYWORD ||
tokenType == GherkinTokenTypes.TAG) {
if (descMarker != null) {
descMarker.done(GherkinElementTypes.FEATURE_HEADER);
descMarker = null;
}
parseFeatureElements(builder);
if (builder.getTokenType() == GherkinTokenTypes.FEATURE_KEYWORD) {
break;
}
}
builder.advanceLexer();
if (builder.eof()) break;
}
if (descMarker != null) {
descMarker.done(GherkinElementTypes.FEATURE_HEADER);
}
marker.done(GherkinElementTypes.FEATURE);
}
private static boolean hadLineBreakBefore(PsiBuilder builder, int prevTokenEnd) {
if (prevTokenEnd < 0) return false;
final String precedingText = builder.getOriginalText().subSequence(prevTokenEnd, builder.getCurrentOffset()).toString();
return precedingText.contains("\n");
}
private static void parseTags(PsiBuilder builder) {
while (builder.getTokenType() == GherkinTokenTypes.TAG) {
final PsiBuilder.Marker tagMarker = builder.mark();
builder.advanceLexer();
tagMarker.done(GherkinElementTypes.TAG);
}
}
private static void parseFeatureElements(PsiBuilder builder) {
while (builder.getTokenType() != GherkinTokenTypes.FEATURE_KEYWORD && !builder.eof()) {
final PsiBuilder.Marker marker = builder.mark();
// tags
parseTags(builder);
// scenarios
IElementType startTokenType = builder.getTokenType();
final boolean outline = startTokenType == GherkinTokenTypes.SCENARIO_OUTLINE_KEYWORD;
builder.advanceLexer();
parseScenario(builder, outline);
marker.done(outline ? GherkinElementTypes.SCENARIO_OUTLINE : GherkinElementTypes.SCENARIO);
}
}
private static void parseScenario(PsiBuilder builder, boolean outline) {
while (!atScenarioEnd(builder)) {
if (builder.getTokenType() == GherkinTokenTypes.TAG) {
final PsiBuilder.Marker marker = builder.mark();
builder.advanceLexer();
if (atScenarioEnd(builder)) {
marker.rollbackTo();
break;
} else {
marker.drop();
}
}
if (parseStepParameter(builder)) {
continue;
}
if (builder.getTokenType() == GherkinTokenTypes.STEP_KEYWORD) {
parseStep(builder);
}
else if (builder.getTokenType() == GherkinTokenTypes.EXAMPLES_KEYWORD && outline) {
parseExamplesBlock(builder);
}
else {
builder.advanceLexer();
}
}
}
private static boolean atScenarioEnd(PsiBuilder builder) {
int i = 0;
while (builder.lookAhead(i) == GherkinTokenTypes.TAG) {
i++;
}
final IElementType tokenType = builder.lookAhead(i);
return tokenType == null ||
tokenType == GherkinTokenTypes.BACKGROUND_KEYWORD ||
tokenType == GherkinTokenTypes.SCENARIO_KEYWORD ||
tokenType == GherkinTokenTypes.SCENARIO_OUTLINE_KEYWORD ||
tokenType == GherkinTokenTypes.FEATURE_KEYWORD;
}
private static boolean parseStepParameter(PsiBuilder builder) {
if (builder.getTokenType() == GherkinTokenTypes.STEP_PARAMETER_TEXT) {
final PsiBuilder.Marker stepParameterMarker = builder.mark();
builder.advanceLexer();
stepParameterMarker.done(GherkinElementTypes.STEP_PARAMETER);
return true;
}
return false;
}
private static void parseStep(PsiBuilder builder) {
final PsiBuilder.Marker marker = builder.mark();
builder.advanceLexer();
int prevTokenEnd = -1;
while (builder.getTokenType() == GherkinTokenTypes.TEXT || builder.getTokenType() == GherkinTokenTypes.STEP_PARAMETER_BRACE
|| builder.getTokenType() == GherkinTokenTypes.STEP_PARAMETER_TEXT) {
String tokenText = builder.getTokenText();
if (hadLineBreakBefore(builder, prevTokenEnd)) {
break;
}
prevTokenEnd = builder.getCurrentOffset() + getTokenLength(tokenText);
if (!parseStepParameter(builder)) {
builder.advanceLexer();
}
}
final IElementType tokenTypeAfterName = builder.getTokenType();
if (tokenTypeAfterName == GherkinTokenTypes.PIPE) {
parseTable(builder);
} else if (tokenTypeAfterName == GherkinTokenTypes.PYSTRING) {
parsePystring(builder);
}
marker.done(GherkinElementTypes.STEP);
}
private static void parsePystring(PsiBuilder builder) {
if (!builder.eof()) {
final PsiBuilder.Marker marker = builder.mark();
builder.advanceLexer();
while (!builder.eof() && builder.getTokenType() != GherkinTokenTypes.PYSTRING) {
if (!parseStepParameter(builder)) {
builder.advanceLexer();
}
}
if (!builder.eof()) {
builder.advanceLexer();
}
marker.done(GherkinElementTypes.PYSTRING);
}
}
private static void parseExamplesBlock(PsiBuilder builder) {
final PsiBuilder.Marker marker = builder.mark();
builder.advanceLexer();
if (builder.getTokenType() == GherkinTokenTypes.COLON) builder.advanceLexer();
while(builder.getTokenType() == GherkinTokenTypes.TEXT) {
builder.advanceLexer();
}
if (builder.getTokenType() == GherkinTokenTypes.PIPE) {
parseTable(builder);
}
marker.done(GherkinElementTypes.EXAMPLES_BLOCK);
}
private static void parseTable(PsiBuilder builder) {
final PsiBuilder.Marker marker = builder.mark();
PsiBuilder.Marker rowMarker = builder.mark();
int prevCellEnd = -1;
boolean isHeaderRow = true;
PsiBuilder.Marker cellMarker = null;
IElementType prevToken = null;
while (builder.getTokenType() == GherkinTokenTypes.PIPE || builder.getTokenType() == GherkinTokenTypes.TABLE_CELL) {
final IElementType tokenType = builder.getTokenType();
final boolean hasLineBreakBefore = hadLineBreakBefore(builder, prevCellEnd);
// cell - is all between pipes
if (prevToken == GherkinTokenTypes.PIPE) {
// Don't start new cell if prev was last in the row
// it's not a cell, we just need to close a row
if (!hasLineBreakBefore) {
cellMarker = builder.mark();
}
}
if (tokenType == GherkinTokenTypes.PIPE) {
if (cellMarker != null) {
closeCell(cellMarker);
cellMarker = null;
}
}
if (hasLineBreakBefore) {
closeRowMarker(rowMarker, isHeaderRow);
isHeaderRow = false;
rowMarker = builder.mark();
}
prevCellEnd = builder.getCurrentOffset() + getTokenLength(builder.getTokenText());
prevToken = tokenType;
builder.advanceLexer();
}
if (cellMarker != null) {
closeCell(cellMarker);
}
closeRowMarker(rowMarker, isHeaderRow);
marker.done(GherkinElementTypes.TABLE);
}
private static void closeCell(PsiBuilder.Marker cellMarker) {
cellMarker.done(GherkinElementTypes.TABLE_CELL);
}
private static void closeRowMarker(PsiBuilder.Marker rowMarker, boolean headerRow) {
rowMarker.done(headerRow ? GherkinElementTypes.TABLE_HEADER_ROW : GherkinElementTypes.TABLE_ROW);
}
private static int getTokenLength(@Nullable final String tokenText) {
return tokenText != null ? tokenText.length() : 0;
}
}