001/* 002 * Copyright (C) 2009-2011 Mathias Doenitz 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.parboiled.matchers; 018 019import org.parboiled.Action; 020import org.parboiled.ContextAware; 021import org.parboiled.MatcherContext; 022import org.parboiled.Rule; 023import org.parboiled.SkippableAction; 024import org.parboiled.errors.ActionError; 025import org.parboiled.errors.ActionException; 026import org.parboiled.matchervisitors.MatcherVisitor; 027 028import java.lang.reflect.Field; 029import java.util.ArrayList; 030import java.util.List; 031 032import static org.parboiled.common.Preconditions.checkArgNotNull; 033 034/** 035 * A {@link Matcher} that not actually matches input but runs a given parser {@link Action}. 036 */ 037public class ActionMatcher extends AbstractMatcher { 038 public final Action action; 039 public final List<ContextAware> contextAwares = new ArrayList<ContextAware>(); 040 public final boolean skipInPredicates; 041 042 public ActionMatcher(Action action) { 043 super(checkArgNotNull(action, "action").toString()); 044 this.action = action; 045 046 skipInPredicates = action instanceof SkippableAction && ((SkippableAction) action).skipInPredicates(); 047 048 // check whether the action is a synthetic class generated by parboiled transformation 049 // if so it will take care of context management itself and we can return immediately 050 if (action.getClass().isSynthetic()) return; 051 052 if (action instanceof ContextAware) { 053 contextAwares.add((ContextAware) action); 054 } 055 // in order to make anonymous inner classes and other member classes work seamlessly 056 // we collect the synthetic references to the outer parent classes and inform them of 057 // the current parsing context if they implement ContextAware 058 for (Field field : action.getClass().getDeclaredFields()) { 059 if (field.isSynthetic() && ContextAware.class.isAssignableFrom(field.getType())) { 060 field.setAccessible(true); 061 try { 062 ContextAware contextAware = (ContextAware) field.get(action); 063 if (contextAware != null) contextAwares.add(contextAware); 064 } catch (IllegalAccessException e) { 065 // ignore 066 } finally { 067 field.setAccessible(false); 068 } 069 } 070 } 071 } 072 073 @Override 074 public MatcherContext getSubContext(MatcherContext context) { 075 MatcherContext subContext = context.getBasicSubContext(); 076 subContext.setMatcher(this); 077 if (context.getCurrentIndex() > context.getStartIndex()) { 078 // if we have already matched something we must be in a sequence at the second or later position 079 // the subcontext contains match data that the action might want to access, so we use the existing 080 // subcontext without reinitializing 081 return subContext; 082 } else { 083 return context.getSubContext(this); 084 } 085 } 086 087 @SuppressWarnings({"unchecked"}) 088 public <V> boolean match(MatcherContext<V> context) { 089 if (skipInPredicates && context.inPredicate()) return true; 090 091 // actions need to run in the parent context 092 MatcherContext parentContext = context.getParent(); 093 if (!contextAwares.isEmpty()) { 094 for (ContextAware contextAware : contextAwares) { 095 contextAware.setContext(parentContext); 096 } 097 } 098 099 try { 100 Object valueStackSnapshot = context.getValueStack().takeSnapshot(); 101 if (!action.run(parentContext)) { 102 // failing actions are not allowed to change the ValueStack 103 context.getValueStack().restoreSnapshot(valueStackSnapshot); 104 return false; 105 } 106 107 // since we initialize the actions own context only partially in getSubContext(MatcherContext) 108 // (in order to be able to still access the previous subcontexts fields in action expressions) 109 // we need to make sure to not accidentally advance the current index of our parent with some old 110 // index from a previous subcontext, so we explicitly set the marker here 111 context.setCurrentIndex(parentContext.getCurrentIndex()); 112 return true; 113 } catch (ActionException e) { 114 context.getParseErrors().add(new ActionError(context.getInputBuffer(), context.getCurrentIndex(), 115 e.getMessage(), context.getPath(), e)); 116 return false; 117 } 118 } 119 120 @Override 121 public Rule suppressNode() { 122 return this; // actions are already "suppressNode" 123 } 124 125 public <R> R accept(MatcherVisitor<R> visitor) { 126 checkArgNotNull(visitor, "visitor"); 127 return visitor.visit(this); 128 } 129 130}