package fitnesse.wikitext.parser;
import java.util.ArrayList;
import java.util.Arrays;
public class ParseSpecification {
public static final int nestingPriority = 2;
public static final int tablePriority = 1;
public static final int normalPriority = 0;
private SymbolProvider provider = SymbolProvider.wikiParsingProvider;
private ArrayList<SymbolType> terminators = new ArrayList<>();
private ArrayList<SymbolType> ignoresFirst = new ArrayList<>();
private ArrayList<SymbolType> ends = new ArrayList<>();
private int priority = 0;
public ParseSpecification provider(SymbolProvider provider) {
this.provider = provider;
return this;
}
public ParseSpecification provider(ParseSpecification specification) {
this.provider = specification.provider;
return this;
}
public ParseSpecification priority(int priority) {
this.priority = priority;
return this;
}
public ParseSpecification terminator(SymbolType terminator) {
terminators.add(terminator);
return this;
}
public ParseSpecification ignoreFirst(SymbolType ignoreFirst) {
ignoresFirst.add(ignoreFirst);
return this;
}
public void clearIgnoresFirst() {
ignoresFirst.clear();
}
public ParseSpecification end(SymbolType end) {
ends.add(end);
return this;
}
public ParseSpecification makeSpecification(SymbolProvider providerModel, SymbolType[] providerTypes) {
SymbolProvider newProvider = new SymbolProvider(providerModel);
newProvider.addTypes(ends);
newProvider.addTypes(terminators);
newProvider.addTypes(Arrays.asList(providerTypes));
return new ParseSpecification().provider(newProvider);
}
public boolean endsOn(SymbolType symbolType) {
return contains(ends, symbolType);
}
public SymbolMatch findMatch(final ScanString input, final int startPosition, final SymbolStream symbols) {
return provider.findMatch(input.charAt(0), new SymbolMatcher() {
@Override
public SymbolMatch makeMatch(Matchable candidate) {
if (input.getOffset() != startPosition || !ignores(candidate)) {
SymbolMatch match = candidate.makeMatch(input, symbols);
if (match.isMatch()) return match;
}
return SymbolMatch.noMatch;
}
});
}
public boolean matchesFor(SymbolType symbolType) {
return provider.matchesFor(symbolType);
}
public boolean owns(SymbolType current, ParseSpecification other) {
return terminatesOn(current) && priority > other.priority;
}
public Symbol parse(Parser parser, Scanner scanner) {
Symbol result = new Symbol(SymbolType.SymbolList);
result.setStartOffset(scanner.getOffset());
while (true) {
Maybe<Symbol> parsedSymbol = parseSymbol(parser, scanner);
if (parsedSymbol.isNothing()) {
break;
} else {
result.add(parsedSymbol.getValue());
}
}
result.setEndOffset(scanner.getOffset());
return result;
}
/**
*
* @param parser parser
* @param scanner scanner
* @return a possible value if parser should stop.
*/
public Maybe<Symbol> parseSymbol(Parser parser, Scanner scanner) {
while (true) {
Scanner backup = new Scanner(scanner);
scanner.moveNextIgnoreFirst(this);
if (scanner.isEnd()) return Maybe.nothingBecause("scanner is at end of buffer");
Symbol currentToken = scanner.getCurrent();
int startOffset = currentToken.getStartOffset();
if (endsOn(currentToken.getType()) || parser.parentOwns(currentToken.getType(), this)) {
scanner.copy(backup);
return Maybe.nothingBecause("At termination symbol or parent owns symbol");
}
if (terminatesOn(currentToken.getType())) return Maybe.nothingBecause("At termination symbol");
Rule currentRule = currentToken.getType().getWikiRule();
Maybe<Symbol> parsedSymbol = currentRule.parse(currentToken, parser);
if (parsedSymbol.isNothing()) {
ignoreFirst(currentToken.getType());
scanner.copy(backup);
} else {
parsedSymbol.getValue().setStartOffset(startOffset).setEndOffset(scanner.getOffset());
clearIgnoresFirst();
return parsedSymbol;
}
}
}
private boolean terminatesOn(SymbolType symbolType) {
return contains(terminators, symbolType);
}
private boolean contains(Iterable<SymbolType> terminators, Matchable currentType) {
for (SymbolType terminator: terminators)
if (currentType.matchesFor(terminator)) return true;
return false;
}
private boolean ignores(Matchable symbolType) {
return contains(ignoresFirst, symbolType);
}
}