/** * 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; } }