2
* Copyright (C) 2009-2011 Mathias Doenitz
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
* you may not use this file except in compliance with the License.
6
* You may obtain a copy of the License at
8
* http://www.apache.org/licenses/LICENSE-2.0
10
* Unless required by applicable law or agreed to in writing, software
11
* distributed under the License is distributed on an "AS IS" BASIS,
12
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
* See the License for the specific language governing permissions and
14
* limitations under the License.
17
package org.parboiled.matchers;
19
import static org.parboiled.common.Preconditions.*;
20
import org.parboiled.MatcherContext;
21
import org.parboiled.Rule;
22
import org.parboiled.errors.GrammarException;
23
import org.parboiled.buffers.InputBuffer;
25
import java.util.HashSet;
28
import java.util.TreeMap;
31
* A specialized FirstOfMatcher that handles FirstOf(string, string, ...) rules much faster that the regular
32
* FirstOfMatcher. If fast string matching is enabled this matcher uses a prebuilt character tree to efficiently
33
* determine whether the next input characters match the rule expression.
35
public class FirstOfStringsMatcher extends FirstOfMatcher {
37
// a node in the character tree
39
final char[] chars; // the sub characters of this node
40
final Record[] subs; // the sub records corresponding to the respective character
41
final boolean complete; // flag indicating that the path up to this record also constitutes a valid match
43
private Record(char[] chars, Record[] subs, boolean complete) {
46
this.complete = complete;
50
private final Record root; // the root of the character tree
51
public final char[][] strings;
53
public FirstOfStringsMatcher(Rule[] subRules, char[][] strings) {
54
super(checkArgNotNull(subRules, "subRules"));
56
this.strings = strings;
57
root = createRecord(0, strings);
61
public boolean match(MatcherContext context) {
62
if (!context.fastStringMatching()) {
63
return super.match(context);
67
int ix = context.getCurrentIndex();
68
InputBuffer buffer = context.getInputBuffer();
69
char c = context.getCurrentChar();
74
char[] chars = rec.chars;
75
for (int i = 0; i < chars.length; i++) {
79
if (rec == null) { // success, we complected a tree path to a leave
83
if (rec.complete) { // we completed a valid match path, but continue looking for a longer match
86
c = buffer.charAt(ix);
90
// we checked all sub branches of the current node, none matched, so we are done
94
if (endIx == -1) return false; // we matched no complete path, so fail
96
context.advanceIndex(endIx - context.getCurrentIndex());
101
static Record createRecord(int pos, char[][] strings) {
102
Map<Character, Set<char[]>> map = new TreeMap<Character, Set<char[]>>();
103
boolean complete = false;
104
for (char[] s : strings) {
105
if (s.length == pos) complete = true;
106
if (s.length <= pos) continue;
108
Set<char[]> charStrings = map.get(c);
109
if (charStrings == null) {
110
charStrings = new HashSet<char[]>();
111
map.put(c, charStrings);
116
if (map.isEmpty()) return null;
118
char[] chars = new char[map.size()];
119
Record[] subs = new Record[map.size()];
121
for (Map.Entry<Character, Set<char[]>> entry : map.entrySet()) {
122
chars[i] = entry.getKey();
123
subs[i++] = createRecord(pos + 1, entry.getValue().toArray(new char[entry.getValue().size()][]));
125
return new Record(chars, subs, complete);
128
// make sure that a string is no prefix of another string later in the array
129
// this would cause the second string to never match without fast-string-matching,
130
// but match in the fast implementation
132
private static void verify(char[][] strings) {
133
int length = strings.length;
134
for (int i = 0; i < length; i++) {
135
char[] a = strings[i];
137
for (int j = i + 1; j < length; j++) {
138
char[] b = strings[j];
139
if (b.length < a.length) continue;
140
for (int k = 0; k < a.length; k++) {
141
if (a[k] != b[k]) continue inner;
143
String sa = '"' + String.valueOf(a) + '"';
144
String sb = '"' + String.valueOf(b) + '"';
145
String msg = a.length == b.length ? sa + " is specified twice in a FirstOf(String...)" : sa +
146
" is a prefix of " + sb + " in a FirstOf(String...) and comes before " +
147
sb + ", which prevents " + sb +
148
" from ever matching! You should reverse the order of the two alternatives.";
149
throw new GrammarException(msg);
b'\\ No newline at end of file'