blob: 2785321b05d05c53970fc4eec89a379505b2b5a0 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.calcite.runtime;
import org.apache.calcite.runtime.Automaton.EpsilonTransition;
import org.apache.calcite.runtime.Automaton.State;
import org.apache.calcite.runtime.Automaton.SymbolTransition;
import org.apache.calcite.runtime.Automaton.Transition;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableList.toImmutableList;
/** Builds a state-transition graph for deterministic finite automaton. */
public class AutomatonBuilder {
private final Map<String, Integer> symbolIds = new HashMap<>();
private final List<State> stateList = new ArrayList<>();
private final List<Transition> transitionList = new ArrayList<>();
@SuppressWarnings("method.invocation.invalid")
private final State startState = createState();
@SuppressWarnings("method.invocation.invalid")
private final State endState = createState();
/** Adds a pattern as a start-to-end transition. */
AutomatonBuilder add(Pattern pattern) {
return add(pattern, startState, endState);
}
private AutomatonBuilder add(Pattern pattern, State fromState,
State toState) {
final Pattern.AbstractPattern p = (Pattern.AbstractPattern) pattern;
switch (p.op) {
case SEQ:
final Pattern.OpPattern pSeq = (Pattern.OpPattern) p;
return seq(fromState, toState, pSeq.patterns);
case STAR:
final Pattern.OpPattern pStar = (Pattern.OpPattern) p;
return star(fromState, toState, pStar.patterns.get(0));
case PLUS:
final Pattern.OpPattern pPlus = (Pattern.OpPattern) p;
return plus(fromState, toState, pPlus.patterns.get(0));
case REPEAT:
final Pattern.RepeatPattern pRepeat = (Pattern.RepeatPattern) p;
return repeat(fromState, toState, pRepeat.patterns.get(0),
pRepeat.minRepeat, pRepeat.maxRepeat);
case SYMBOL:
final Pattern.SymbolPattern pSymbol = (Pattern.SymbolPattern) p;
return symbol(fromState, toState, pSymbol.name);
case OR:
final Pattern.OpPattern pOr = (Pattern.OpPattern) p;
return or(fromState, toState, pOr.patterns.get(0), pOr.patterns.get(1));
case OPTIONAL:
// Rewrite as {0,1}
final Pattern.OpPattern pOptional = (Pattern.OpPattern) p;
return optional(fromState, toState, pOptional.patterns.get(0));
default:
throw new AssertionError("unknown op " + p.op);
}
}
private State createState() {
final State state = new State(stateList.size());
stateList.add(state);
return state;
}
/** Builds the automaton. */
public Automaton build() {
final ImmutableList.Builder<SymbolTransition> symbolTransitions =
ImmutableList.builder();
final ImmutableList.Builder<EpsilonTransition> epsilonTransitions =
ImmutableList.builder();
for (Transition transition : transitionList) {
if (transition instanceof SymbolTransition) {
symbolTransitions.add((SymbolTransition) transition);
}
if (transition instanceof EpsilonTransition) {
epsilonTransitions.add((EpsilonTransition) transition);
}
}
// Sort the symbols by their ids, which are assumed consecutive and
// starting from zero.
final ImmutableList<String> symbolNames = symbolIds.entrySet()
.stream()
.sorted(Comparator.comparingInt(Map.Entry::getValue))
.map(Map.Entry::getKey)
.collect(toImmutableList());
return new Automaton(stateList.get(0), endState,
symbolTransitions.build(), epsilonTransitions.build(), symbolNames);
}
/** Adds a symbol transition. */
AutomatonBuilder symbol(State fromState, State toState,
String name) {
Objects.requireNonNull(name, "name");
final int symbolId =
symbolIds.computeIfAbsent(name, k -> symbolIds.size());
transitionList.add(new SymbolTransition(fromState, toState, symbolId));
return this;
}
/** Adds a transition made up of a sequence of patterns. */
AutomatonBuilder seq(State fromState, State toState,
List<Pattern> patterns) {
State prevState = fromState;
for (int i = 0; i < patterns.size(); i++) {
Pattern pattern = patterns.get(i);
final State nextState;
if (i == patterns.size() - 1) {
nextState = toState;
} else {
nextState = createState();
}
add(pattern, prevState, nextState);
prevState = nextState;
}
return this;
}
/** Adds a transition for the 'or' pattern. */
AutomatonBuilder or(State fromState, State toState, Pattern left,
Pattern right) {
//
// left
// / --------> toState
// fromState
// \ --------> toState
// right
add(left, fromState, toState);
add(right, fromState, toState);
return this;
}
/** Adds a transition made up of the Kleene star applied to a pattern. */
AutomatonBuilder star(State fromState, State toState, Pattern pattern) {
//
// +------------------------- e ------------------------+
// | +-------- e -------+ |
// | | | |
// | V | V
// fromState -----> beforeState -----> afterState -----> toState
// e pattern e
//
final State beforeState = createState();
final State afterState = createState();
transitionList.add(new EpsilonTransition(fromState, beforeState));
add(pattern, beforeState, afterState);
transitionList.add(new EpsilonTransition(afterState, beforeState));
transitionList.add(new EpsilonTransition(afterState, toState));
transitionList.add(new EpsilonTransition(fromState, toState));
return this;
}
/** Adds a transition made up of a pattern repeated 1 or more times. */
AutomatonBuilder plus(State fromState, State toState, Pattern pattern) {
//
// +-------- e -------+
// | |
// V |
// fromState -----> beforeState -----> afterState -----> toState
// e pattern e
//
final State beforeState = createState();
final State afterState = createState();
transitionList.add(new EpsilonTransition(fromState, beforeState));
add(pattern, beforeState, afterState);
transitionList.add(new EpsilonTransition(afterState, beforeState));
transitionList.add(new EpsilonTransition(afterState, toState));
return this;
}
/** Adds a transition made up of a pattern repeated between {@code minRepeat}
* and {@code maxRepeat} times. */
AutomatonBuilder repeat(State fromState, State toState, Pattern pattern,
int minRepeat, int maxRepeat) {
// Diagram for repeat(1, 3)
// +-------- e --------------+
// | +---- e ----+ |
// | | V V
// fromState ---> state0 ---> state1 ---> state2 ---> state3 ---> toState
// e pattern pattern pattern e
//
checkArgument(0 <= minRepeat);
checkArgument(minRepeat <= maxRepeat);
checkArgument(1 <= maxRepeat);
State prevState = fromState;
for (int i = 0; i <= maxRepeat; i++) {
final State s = createState();
if (i == 0) {
transitionList.add(new EpsilonTransition(fromState, s));
} else {
add(pattern, prevState, s);
}
if (i >= minRepeat) {
transitionList.add(new EpsilonTransition(s, toState));
}
prevState = s;
}
transitionList.add(new EpsilonTransition(prevState, toState));
return this;
}
private AutomatonBuilder optional(State fromState, State toState, Pattern pattern) {
add(pattern, fromState, toState);
transitionList.add(new EpsilonTransition(fromState, toState));
return this;
}
}