gherkin.AstBuilder Maven / Gradle / Ivy
The newest version!
package gherkin;
import gherkin.ast.Background;
import gherkin.ast.Comment;
import gherkin.ast.DataTable;
import gherkin.ast.DocString;
import gherkin.ast.Examples;
import gherkin.ast.Feature;
import gherkin.ast.Location;
import gherkin.ast.Node;
import gherkin.ast.Scenario;
import gherkin.ast.ScenarioDefinition;
import gherkin.ast.ScenarioOutline;
import gherkin.ast.Step;
import gherkin.ast.TableCell;
import gherkin.ast.TableRow;
import gherkin.ast.Tag;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import static gherkin.Parser.Builder;
import static gherkin.Parser.RuleType;
import static gherkin.Parser.TokenType;
import static gherkin.StringUtils.join;
public class AstBuilder implements Builder {
private Deque stack;
private List comments;
public AstBuilder() {
reset();
}
@Override
public void reset() {
stack = new ArrayDeque<>();
stack.push(new AstNode(RuleType.None));
comments = new ArrayList<>();
}
private AstNode currentNode() {
return stack.peek();
}
@Override
public void build(Token token) {
RuleType ruleType = RuleType.cast(token.matchedType);
if (token.matchedType == TokenType.Comment) {
comments.add(new Comment(getLocation(token, 0), token.matchedText));
} else {
currentNode().add(ruleType, token);
}
}
@Override
public void startRule(RuleType ruleType) {
stack.push(new AstNode(ruleType));
}
@Override
public void endRule(RuleType ruleType) {
AstNode node = stack.pop();
Object transformedNode = getTransformedNode(node);
currentNode().add(node.ruleType, transformedNode);
}
private Object getTransformedNode(AstNode node) {
switch (node.ruleType) {
case Step: {
Token stepLine = node.getToken(TokenType.StepLine);
Node stepArg = node.getSingle(RuleType.DataTable, null);
if (stepArg == null) {
stepArg = node.getSingle(RuleType.DocString, null);
}
return new Step(getLocation(stepLine, 0), stepLine.matchedKeyword, stepLine.matchedText, stepArg);
}
case DocString: {
Token separatorToken = node.getTokens(TokenType.DocStringSeparator).get(0);
String contentType = separatorToken.matchedText;
List lineTokens = node.getTokens(TokenType.Other);
StringBuilder content = new StringBuilder();
boolean newLine = false;
for (Token lineToken : lineTokens) {
if (newLine) content.append("\n");
newLine = true;
content.append(lineToken.matchedText);
}
return new DocString(getLocation(separatorToken, 0), contentType, content.toString());
}
case DataTable: {
List rows = getTableRows(node);
return new DataTable(rows);
}
case Background: {
Token backgroundLine = node.getToken(TokenType.BackgroundLine);
String description = getDescription(node);
List steps = getSteps(node);
return new Background(getLocation(backgroundLine, 0), backgroundLine.matchedKeyword, backgroundLine.matchedText, description, steps);
}
case Scenario_Definition: {
List tags = getTags(node);
AstNode scenarioNode = node.getSingle(RuleType.Scenario, null);
if (scenarioNode != null) {
Token scenarioLine = scenarioNode.getToken(TokenType.ScenarioLine);
String description = getDescription(scenarioNode);
List steps = getSteps(scenarioNode);
return new Scenario(tags, getLocation(scenarioLine, 0), scenarioLine.matchedKeyword, scenarioLine.matchedText, description, steps);
} else {
AstNode scenarioOutlineNode = node.getSingle(RuleType.ScenarioOutline, null);
if (scenarioOutlineNode == null) {
throw new RuntimeException("Internal grammar error");
}
Token scenarioOutlineLine = scenarioOutlineNode.getToken(TokenType.ScenarioOutlineLine);
String description = getDescription(scenarioOutlineNode);
List steps = getSteps(scenarioOutlineNode);
List examplesList = scenarioOutlineNode.getItems(RuleType.Examples_Definition);
return new ScenarioOutline(tags, getLocation(scenarioOutlineLine, 0), scenarioOutlineLine.matchedKeyword, scenarioOutlineLine.matchedText, description, steps, examplesList);
}
}
case Examples_Definition: {
List tags = getTags(node);
AstNode examplesNode = node.getSingle(RuleType.Examples, null);
Token examplesLine = examplesNode.getToken(TokenType.ExamplesLine);
String description = getDescription(examplesNode);
List rows = getTableRows(examplesNode);
return new Examples(getLocation(examplesLine, 0), tags, examplesLine.matchedKeyword, examplesLine.matchedText, description, rows.get(0), rows.subList(1, rows.size()));
}
case Description: {
List lineTokens = node.getTokens(TokenType.Other);
// Trim trailing empty lines
int end = lineTokens.size();
while (end > 0 && lineTokens.get(end - 1).matchedText.matches("\\s*")) {
end--;
}
lineTokens = lineTokens.subList(0, end);
return join(new StringUtils.ToString() {
@Override
public String toString(Token t) {
return t.matchedText;
}
}, "\n", lineTokens);
}
case Feature: {
AstNode header = node.getSingle(RuleType.Feature_Header, new AstNode(RuleType.Feature_Header));
if (header == null) return null;
List tags = getTags(header);
Token featureLine = header.getToken(TokenType.FeatureLine);
if (featureLine == null) return null;
Background background = node.getSingle(RuleType.Background, null);
List scenarioDefinitions = node.getItems(RuleType.Scenario_Definition);
String description = getDescription(header);
if (featureLine.matchedGherkinDialect == null) return null;
String language = featureLine.matchedGherkinDialect.getLanguage();
return new Feature(tags, getLocation(featureLine, 0), language, featureLine.matchedKeyword, featureLine.matchedText, description, background, scenarioDefinitions, comments);
}
}
return node;
}
private List getTableRows(AstNode node) {
List rows = new ArrayList<>();
for (Token token : node.getTokens(TokenType.TableRow)) {
rows.add(new TableRow(getLocation(token, 0), getCells(token)));
}
ensureCellCount(rows);
return rows;
}
private void ensureCellCount(List rows) {
if (rows.isEmpty()) return;
int cellCount = rows.get(0).getCells().size();
for (TableRow row : rows) {
if (row.getCells().size() != cellCount) {
throw new ParserException.AstBuilderException("inconsistent cell count within the table", row.getLocation());
}
}
}
private List getCells(Token token) {
List cells = new ArrayList<>();
for (GherkinLineSpan cellItem : token.mathcedItems) {
cells.add(new TableCell(getLocation(token, cellItem.column), cellItem.text));
}
return cells;
}
private List getSteps(AstNode node) {
return node.getItems(RuleType.Step);
}
private Location getLocation(Token token, int column) {
return column == 0 ? token.location : new Location(token.location.getLine(), column);
}
private String getDescription(AstNode node) {
return node.getSingle(RuleType.Description, null);
}
private List getTags(AstNode node) {
AstNode tagsNode = node.getSingle(RuleType.Tags, new AstNode(RuleType.None));
if (tagsNode == null)
return new ArrayList<>();
List tokens = tagsNode.getTokens(TokenType.TagLine);
List tags = new ArrayList<>();
for (Token token : tokens) {
for (GherkinLineSpan tagItem : token.mathcedItems) {
tags.add(new Tag(getLocation(token, tagItem.column), tagItem.text));
}
}
return tags;
}
@Override
public Feature getResult() {
return currentNode().getSingle(RuleType.Feature, null);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy