package fitnesse.wikitext.parser; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; public class Matcher { private interface ScanMatch { Maybe<Integer> match(ScanString input, SymbolStream symbols, int offset); } private static final List<Character> defaultList = Collections.unmodifiableList(Arrays.asList('\0')); private List<ScanMatch> matches = new ArrayList<>(4); private List<Character> firsts = null; public List<Character> getFirsts() { return firsts != null ? firsts : defaultList; } public Matcher whitespace() { if (firsts == null) firsts = defaultList; matches.add(new ScanMatch() { @Override public Maybe<Integer> match(ScanString input, SymbolStream symbols, int offset) { int length = input.whitespaceLength(offset); return length > 0 ? new Maybe<>(length) : Maybe.noInteger; } }); return this; } public Matcher startLine() { matches.add(new ScanMatch() { @Override public Maybe<Integer> match(ScanString input, SymbolStream symbols, int offset) { return isStartLine(input, symbols, offset) ? new Maybe<>(0) : Maybe.noInteger; } }); return this; } public Matcher startLineOrCell() { matches.add(new ScanMatch() { @Override public Maybe<Integer> match(ScanString input, SymbolStream symbols, int offset) { return isStartLine(input, symbols, offset) || isStartCell(symbols) ? new Maybe<>(0) : Maybe.noInteger; } }); return this; } private boolean isStartLine(ScanString input, SymbolStream symbols, int offset) { return input.startsLine(offset) || symbols.get(0).isStartLine(); } private boolean isStartCell(SymbolStream symbols) { return symbols.get(0).isStartCell() || (symbols.get(0).isType(SymbolType.Whitespace) && (symbols.get(1).isStartCell() || symbols.get(1).isLineType())); } public Matcher string(final String delimiter) { if (firsts == null) { firsts = Collections.singletonList(delimiter.charAt(0)); } matches.add(new ScanMatch() { @Override public Maybe<Integer> match(ScanString input, SymbolStream symbols, int offset) { return input.matches(delimiter, offset) ? new Maybe<>(delimiter.length()) : Maybe.noInteger; } }); return this; } public Matcher listDigit() { firstIsDigit('1'); matches.add(new ScanMatch() { @Override public Maybe<Integer> match(ScanString input, SymbolStream symbols, int offset) { return isDigitInput('1', input, offset) ? new Maybe<>(1) : Maybe.noInteger; } }); return this; } private boolean isDigitInput(char firstDigit, ScanString input, int offset) { for (char i = firstDigit; i <= '9'; i++) { if (input.matches(new String(new char[] {i}), offset)) return true; } return false; } private void firstIsDigit(char startDigit) { if (firsts == null) { firsts = new ArrayList<>(); for (char i = startDigit; i <= '9'; i++) firsts.add(i); } } public Matcher digits() { firstIsDigit('0'); matches.add(new ScanMatch() { @Override public Maybe<Integer> match(ScanString input, SymbolStream symbols, int offset) { int size = 0; while (isDigitInput('0', input, offset + size)) size++; return size > 0 ? new Maybe<>(size) : Maybe.noInteger; } }); return this; } public Matcher ignoreWhitespace() { matches.add(new ScanMatch() { @Override public Maybe<Integer> match(ScanString input, SymbolStream symbols, int offset) { return new Maybe<>(input.whitespaceLength(offset)); } }); return this; } public Matcher repeat(final char delimiter) { if (firsts == null) { firsts = Collections.singletonList(delimiter); } matches.add(new ScanMatch() { @Override public Maybe<Integer> match(ScanString input, SymbolStream symbols, int offset) { int size = 0; while (input.charAt(offset + size) == delimiter) size++; return size > 0 ? new Maybe<>(size) : Maybe.noInteger; } }); return this; } public Matcher endsWith(final char[] terminators) { matches.add(new ScanMatch() { @Override public Maybe<Integer> match(ScanString input, SymbolStream symbols, int offset) { int size = 0; while (true) { char candidate = input.charAt(offset + size); if (candidate == 0) return Maybe.noInteger; if (contains(terminators, candidate)) break; size++; } return size > 0 ? new Maybe<>(size + 1) : Maybe.noInteger; } private boolean contains(char[] terminators, char candidate) { for (char terminator: terminators) if (candidate == terminator) return true; return false; } }); return this; } public Maybe<Integer> makeMatch(ScanString input, SymbolStream symbols) { int totalLength = 0; for (ScanMatch match: matches) { Maybe<Integer> matchLength = match.match(input, symbols, totalLength); if (matchLength.isNothing()) return Maybe.noInteger; totalLength += matchLength.getValue(); } return new Maybe<>(totalLength); } }