/*
* 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.run;
import com.github.fge.grappa.Grappa;
import com.github.fge.grappa.buffers.CharSequenceInputBuffer;
import com.github.fge.grappa.buffers.InputBuffer;
import com.github.fge.grappa.exceptions.GrappaException;
import com.github.fge.grappa.internal.NonFinalForTesting;
import com.github.fge.grappa.matchers.base.Matcher;
import com.github.fge.grappa.rules.Rule;
import com.github.fge.grappa.run.context.DefaultMatcherContext;
import com.github.fge.grappa.run.context.MatcherContext;
import com.github.fge.grappa.run.events.MatchContextEvent;
import com.github.fge.grappa.run.events.MatchFailureEvent;
import com.github.fge.grappa.run.events.MatchSuccessEvent;
import com.github.fge.grappa.run.events.PostParseEvent;
import com.github.fge.grappa.run.events.PreMatchEvent;
import com.github.fge.grappa.run.events.PreParseEvent;
import com.github.fge.grappa.stack.ArrayValueStack;
import com.github.fge.grappa.stack.ValueStack;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.eventbus.EventBus;
import javax.annotation.Nonnull;
import java.util.Objects;
/**
* Class to run a parser on an input, and retrieve a result
*
* <p>This is the class which is used when you want to actually run an instance
* of a parser (created using {@link Grappa#createParser(Class, Object...)})
* against a text input.</p>
*
* <p>The basic text input is a {@link CharSequence}; since {@link String}
* implements this interface, this is the most direct way to run a parser.
* If you wish to, however, you may implement your own {@link InputBuffer} and
* pass is as an alternative.</p>
*
* <p>You can also register a number of listeners implementing {@link
* ParseEventListener}. Such listeners have the ability to listen to the parse
* events of your choice (before parsing, before matching, match failure
* or success, after parsing), further extending the use of grappa.</p>
*
* @param <V> type parameter of the parser's stack values
*
* @see ParseEventListener
*/
@NonFinalForTesting
public class ParseRunner<V>
implements MatchHandler
{
// TODO: does it need to be volatile?
private volatile Throwable throwable = null;
private final EventBus bus = new EventBus((exception, context) -> {
if (throwable == null)
throwable = exception;
else
throwable.addSuppressed(exception);
});
protected final Matcher rootMatcher;
protected ValueStack<V> valueStack;
protected Object stackSnapshot;
/**
* Constructor
*
* @param rule the rule
*/
public ParseRunner(@Nonnull final Rule rule)
{
rootMatcher = Objects.requireNonNull((Matcher) rule, "rule");
}
public final ParsingResult<V> run(final CharSequence input)
{
Objects.requireNonNull(input, "input");
return run(new CharSequenceInputBuffer(input));
}
public final ParsingResult<V> run(final InputBuffer inputBuffer)
{
Objects.requireNonNull(inputBuffer, "inputBuffer");
resetValueStack();
final MatcherContext<V> context = createRootContext(inputBuffer, this);
bus.post(new PreParseEvent<>(context));
if (throwable != null)
throw new GrappaException("parsing listener error (before parse)",
throwable);
final boolean matched = context.runMatcher();
final ParsingResult<V> result
= createParsingResult(matched, context);
bus.post(new PostParseEvent<>(result));
if (throwable != null)
throw new GrappaException("parsing listener error (after parse)",
throwable);
return result;
}
private void resetValueStack()
{
// TODO: write a "memoizing" API
valueStack = new ArrayValueStack<>();
stackSnapshot = null;
}
@VisibleForTesting
MatcherContext<V> createRootContext(
final InputBuffer inputBuffer, final MatchHandler matchHandler)
{
return new DefaultMatcherContext<>(inputBuffer, valueStack,
matchHandler, rootMatcher);
}
@VisibleForTesting
ParsingResult<V> createParsingResult(final boolean matched,
final MatcherContext<V> context)
{
return new ParsingResult<>(matched, valueStack, context);
}
public final void registerListener(final ParseEventListener<V> listener)
{
bus.register(listener);
}
/**
* Internal method. DO NOT USE!
*
* @param context the MatcherContext
* @param <T> type parameter of the values on the parser stack
* @return true on a match; false otherwise
*/
@Override
public <T> boolean match(final MatcherContext<T> context)
{
final Matcher matcher = context.getMatcher();
final PreMatchEvent<T> preMatchEvent = new PreMatchEvent<>(context);
bus.post(preMatchEvent);
if (throwable != null)
throw new GrappaException("parsing listener error (before match)",
throwable);
// FIXME: is there any case at all where context.getMatcher() is null?
@SuppressWarnings("ConstantConditions")
final boolean match = matcher.match(context);
final MatchContextEvent<T> postMatchEvent = match
? new MatchSuccessEvent<>(context)
: new MatchFailureEvent<>(context);
bus.post(postMatchEvent);
if (throwable != null)
throw new GrappaException("parsing listener error (after match)",
throwable);
return match;
}
}