blob: 867e5147f9651eabcc62db77388e707db50d291c [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.avro.io.parsing;
import java.io.IOException;
import java.util.Arrays;
import org.apache.avro.AvroTypeException;
/**
* Parser is the class that maintains the stack for parsing. This class
* is used by encoders, which are not required to skip.
*/
public class Parser {
/**
* The parser knows how to handle the terminal and non-terminal
* symbols. But it needs help from outside to handle implicit
* and explicit actions. The clients implement this interface to
* provide this help.
*/
public interface ActionHandler {
/**
* Handle the action symbol <tt>top</tt> when the <tt>input</tt> is
* sought to be taken off the stack.
* @param input The input symbol from the caller of advance
* @param top The symbol at the top the stack.
* @return <tt>null</tt> if advance() is to continue processing the
* stack. If not <tt>null</tt> the return value will be returned
* by advance().
* @throws IOException
*/
Symbol doAction(Symbol input, Symbol top) throws IOException;
}
protected final ActionHandler symbolHandler;
protected Symbol[] stack;
protected int pos;
public Parser(Symbol root, ActionHandler symbolHandler)
throws IOException {
this.symbolHandler = symbolHandler;
this.stack = new Symbol[5]; // Start small to make sure expansion code works
this.stack[0] = root;
this.pos = 1;
}
/**
* If there is no sufficient room in the stack, use this expand it.
*/
private void expandStack() {
stack = Arrays.copyOf(stack, stack.length+Math.max(stack.length,1024));
}
/**
* Recursively replaces the symbol at the top of the stack with its
* production, until the top is a terminal. Then checks if the
* top symbol matches the terminal symbol suppled <tt>terminal</tt>.
* @param input The symbol to match against the terminal at the
* top of the stack.
* @return The terminal symbol at the top of the stack unless an
* implicit action resulted in another symbol, in which case that
* symbol is returned.
*/
public final Symbol advance(Symbol input) throws IOException {
for (; ;) {
Symbol top = stack[--pos];
if (top == input) {
return top; // A common case
}
Symbol.Kind k = top.kind;
if (k == Symbol.Kind.IMPLICIT_ACTION) {
Symbol result = symbolHandler.doAction(input, top);
if (result != null) {
return result;
}
} else if (k == Symbol.Kind.TERMINAL) {
throw new AvroTypeException("Attempt to process a "
+ input + " when a "
+ top + " was expected.");
} else if (k == Symbol.Kind.REPEATER
&& input == ((Symbol.Repeater) top).end) {
return input;
} else {
pushProduction(top);
}
}
}
/**
* Performs any implicit actions at the top the stack, expanding any
* production (other than the root) that may be encountered.
* This method will fail if there are any repeaters on the stack.
* @throws IOException
*/
public final void processImplicitActions() throws IOException {
while (pos > 1) {
Symbol top = stack[pos - 1];
if (top.kind == Symbol.Kind.IMPLICIT_ACTION) {
pos--;
symbolHandler.doAction(null, top);
} else if (top.kind != Symbol.Kind.TERMINAL) {
pos--;
pushProduction(top);
} else {
break;
}
}
}
/**
* Performs any "trailing" implicit actions at the top the stack.
*/
public final void processTrailingImplicitActions() throws IOException {
while (pos >= 1) {
Symbol top = stack[pos - 1];
if (top.kind == Symbol.Kind.IMPLICIT_ACTION
&& ((Symbol.ImplicitAction) top).isTrailing) {
pos--;
symbolHandler.doAction(null, top);
} else {
break;
}
}
}
/**
* Pushes the production for the given symbol <tt>sym</tt>.
* If <tt>sym</tt> is a repeater and <tt>input</tt> is either
* {@link Symbol#ARRAY_END} or {@link Symbol#MAP_END} pushes nothing.
* @param sym
*/
public final void pushProduction(Symbol sym) {
Symbol[] p = sym.production;
while (pos + p.length > stack.length) {
expandStack();
}
System.arraycopy(p, 0, stack, pos, p.length);
pos += p.length;
}
/**
* Pops and returns the top symbol from the stack.
*/
public Symbol popSymbol() {
return stack[--pos];
}
/**
* Returns the top symbol from the stack.
*/
public Symbol topSymbol() {
return stack[pos - 1];
}
/**
* Pushes <tt>sym</tt> on to the stack.
*/
public void pushSymbol(Symbol sym) {
if (pos == stack.length) {
expandStack();
}
stack[pos++] = sym;
}
/**
* Returns the depth of the stack.
*/
public int depth() {
return pos;
}
public void reset() {
pos = 1;
}
}