/*
* Copyright (C) 2009-2011 Mathias Doenitz
*
* Licensed 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 com.github.fge.grappa.parsers;
import com.github.fge.grappa.buffers.InputBuffer;
import com.github.fge.grappa.exceptions.InvalidGrammarException;
import com.github.fge.grappa.rules.Action;
import com.github.fge.grappa.run.context.Context;
import com.github.fge.grappa.run.context.ContextAware;
import com.github.fge.grappa.stack.ValueStack;
import com.github.fge.grappa.support.IndexRange;
import com.github.fge.grappa.support.Position;
import javax.annotation.Nonnull;
import java.util.Objects;
/**
* The basic class for all {@link ContextAware} implementations.
*
* <p>This class is used by all basic parsers which you may extend ({@link
* BaseParser}, {@link EventBusParser}), but also by all of a parser's {@link
* Action actions} (that is, any boolean expressions in rules, or any methods in
* a parser returning a boolean).</p>
*
* @param <V> parameter type of the values on the parser stack
*/
public abstract class BaseActions<V>
implements ContextAware<V>
{
@SuppressWarnings("InstanceVariableMayNotBeInitialized")
private Context<V> context;
/**
* The current context for use with action methods. Updated immediately
* before action calls.
*
* @return the current context
*/
public final Context<V> getContext()
{
return context;
}
/**
* ContextAware interface implementation.
*
* @param context the context
*/
@Override
public final void setContext(final Context<V> context)
{
this.context = Objects.requireNonNull(context, "context");
}
/**
* Returns the current index in the input buffer.
*
* @return the current index
*/
public final int currentIndex()
{
check();
return context.getCurrentIndex();
}
/**
* Return the input text matched by the immediately preceding rule
*
* <p>This call can only be used in actions (or any implementation of {@link
* ContextAware} that are part of a sequence rule and are not at first
* position in this sequence.</p>
*
* @return the input text matched by the immediately previous rule
*/
public String match()
{
check();
return context.getMatch();
}
/**
* Returns the range covering the input text matched by the immediately
* preceding rule
*
* <p>This call can only be used in actions (or any implementation of {@link
* ContextAware} that are part of a sequence rule and are not at first
* position in this sequence.</p>
*
* @return a new IndexRange instance
*/
public IndexRange matchRange()
{
check();
return context.getMatchRange();
}
/**
* Returns the start index of the immediately preceding rule's context
*
* <p>This call can only be used in actions (or any implementation of {@link
* ContextAware} that are part of a sequence rule and are not at first
* position in this sequence.</p>
*
* @return see description
*/
public int matchStart()
{
check();
return context.getMatchStartIndex();
}
/**
* Returns the end index of the immediately preceding rule's context
*
* <p>Note that the index is exclusive; that is, it will point to the next
* character in the buffer (possibly past the end of the sequence if the
* previous rule matched until the end of the input).</p>
*
* <p>This call can only be used in actions (or any implementation of {@link
* ContextAware} that are part of a sequence rule and are not at first
* position in this sequence.</p>
*
* @return see description
*/
public int matchEnd()
{
check();
return context.getMatchEndIndex();
}
/**
* Returns the current position in the underlying {@link InputBuffer} as a
* {@link Position}
*
* @return the current position in the underlying inputbuffer
*/
public Position position()
{
check();
return context.getPosition();
}
/**
* Pushes the given value onto the value stack
*
* <p>Equivalent to {@code push(0, value)}.
*
* @param value the value to push
* @return true
*/
public boolean push(final V value)
{
check();
context.getValueStack().push(value);
return true;
}
/**
* Inserts the given value a given number of elements below the current top
* of the value stack
*
* @param down the number of elements to skip before inserting the value (0
* being equivalent to {@code push(value)})
* @param value the value
* @return true
*
* @throws IllegalArgumentException the stack does not contain enough
* elements to perform this operation
*/
public boolean push(final int down, final V value)
{
check();
context.getValueStack().push(down, value);
return true;
}
/**
* Removes the value at the top of the value stack and returns it
*
* @return the current top value
*
* @throws IllegalArgumentException the stack is empty
*/
public V pop()
{
check();
return context.getValueStack().pop();
}
/**
* Removes the value at the top of the value stack and casts it before
* returning it
*
* @param c the class to cast to
* @param <E> type of the class
* @return the current top value
* @throws IllegalArgumentException the stack is empty
*
* @see #pop()
*/
public <E extends V> E popAs(@Nonnull final Class<E> c)
{
return c.cast(pop());
}
/**
* Removes the value the given number of elements below the top of the value
* stack and returns it
*
* @param down the number of elements to skip before removing the value (0
* being equivalent to {@code pop()})
* @return the value
*
* @throws IllegalArgumentException the stack does not contain enough
* elements to perform this operation
*/
public V pop(final int down)
{
check();
return context.getValueStack().pop(down);
}
/**
* Removes the value the given number of elements below the top of the value
* stack and casts it before returning it
*
* @param c the class to cast to
* @param down the number of elements to skip before removing the value (0
* being equivalent to {@code pop()})
* @param <E> type of the class
* @return the value
* @throws IllegalArgumentException the stack does not contain enough
* elements to perform this operation
*
* @see #pop(int)
*/
public <E extends V> E popAs(final Class<E> c, final int down)
{
return c.cast(pop(down));
}
/**
* Removes the value at the top of the value stack
*
* @return true
*
* @throws IllegalArgumentException the stack is empty
*/
public boolean drop()
{
check();
context.getValueStack().pop();
return true;
}
/**
* Removes the value the given number of elements below the top of the value
* stack
*
* @param down the number of elements to skip before removing the value (0
* being equivalent to {@code drop()})
* @return true
*
* @throws IllegalArgumentException the stack does not contain enough
* elements to perform this operation
*/
public boolean drop(final int down)
{
check();
context.getValueStack().pop(down);
return true;
}
/**
* Returns the value at the top of the value stack without removing it
*
* @return the current top value
*
* @throws IllegalArgumentException if the stack is empty
*/
public V peek()
{
check();
return context.getValueStack().peek();
}
/**
* Returns and casts the value at the top of the value stack without
* removing it
*
* @param c the class to cast to
* @param <E> type of the class
* @return the value
*
* @see #peek()
*/
public <E extends V> E peekAs(final Class<E> c)
{
return c.cast(peek());
}
/**
* Returns the value the given number of elements below the top of the value
* stack without removing it
*
* @param down the number of elements to skip (0 being equivalent to {@code
* peek()})
* @return the value
*
* @throws IllegalArgumentException the stack does not contain enough
* elements to perform this operation
*/
public V peek(final int down)
{
check();
return context.getValueStack().peek(down);
}
/**
* Returns and casts the value the given number of elements below the top
* of the value stack without removing it.
*
* @param c the class to cast to
* @param down the number of elements to skip (0 being equivalent to {@code
* peek()})
* @param <E> type of the class
* @return the value
*
* @see #peek(int)
*/
public <E extends V> E peekAs(final Class<E> c, final int down)
{
return c.cast(peek(down));
}
/**
* Replaces the current top value of the value stack with the given value
*
* <p>Equivalent to {@code poke(0, value)}.
*
* @param value the value
* @return true
*
* @throws IllegalArgumentException the stack is empty
*/
public boolean poke(final V value)
{
check();
context.getValueStack().poke(value);
return true;
}
/**
* Replaces the element the given number of elements below the current top
* of the value stack
*
* @param down the number of elements to skip before replacing the value (0
* being equivalent to {@code poke(value)})
* @param value the value to replace with
* @return true
*
* @throws IllegalArgumentException the stack does not contain enough
* elements to perform this operation
*/
public boolean poke(final int down, final V value)
{
check();
context.getValueStack().poke(down, value);
return true;
}
/**
* Duplicates the top value of the value stack
*
* @return true
*
* @throws IllegalArgumentException the stack is empty
*/
public boolean dup()
{
check();
context.getValueStack().dup();
return true;
}
/**
* Swaps the top two elements of the value stack.
*
* @return true
*
* @throws IllegalArgumentException the stack does not contain at least
* two elements
*/
public boolean swap()
{
check();
context.getValueStack().swap();
return true;
}
/**
* Reverse the order of the top n elements of this context's value stack
*
* @param n the number of elements to swap
* @return always true
* @throws IllegalArgumentException stack does not contain enough elements
*
* @see ValueStack#swap(int)
*/
public boolean swap(final int n)
{
check();
context.getValueStack().swap(n);
return true;
}
/**
* Check whether the end of input has been reached by the current context
*
* @return true if the end of the input has been reached
*/
public boolean atEnd()
{
check();
return context.atEnd();
}
/**
* Returns the next input character about to be matched
*
* <p>If you use this method, you MUST first check whether you have reached
* the end of the buffer using {@link #atEnd()}.</p>
*
* @return the next input character about to be matched
*/
public Character currentChar()
{
check();
return context.getCurrentChar();
}
/**
* Check whether the current context is within a predicate ({@code test()}
* or {@code testNot()})
*
* <p>Useful for example for making sure actions are not run inside of a
* predicate evaluation:</p>
*
* <pre>
* return sequence(inPredicate() || someActionHere());
* </pre>
*
* @return true if in a predicate
*/
public boolean inPredicate()
{
check();
return context.inPredicate();
}
/**
* Check whether the current context has recorded a parse error
*
* @return true if either the current rule or a sub rule has recorded a
* parse error
*/
public boolean hasError()
{
check();
return context.hasError();
}
// TODO: pain point here
private void check()
{
if (context == null || context.getMatcher() == null)
throw new InvalidGrammarException("rule has an unwrapped action"
+ " expression");
}
}