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.parser;
018
019import org.parboiled.Context;
020import org.parboiled.ContextAware;
021import org.parboiled.buffers.InputBuffer;
022import org.parboiled.errors.GrammarException;
023import org.parboiled.support.Checks;
024import org.parboiled.support.IndexRange;
025import org.parboiled.support.Position;
026import org.parboiled.support.Checks;
027import org.parboiled.support.IndexRange;
028import org.parboiled.support.Position;
029
030import static org.parboiled.common.Preconditions.checkArgNotNull;
031
032/**
033 * Convenience context aware base class defining a number of useful helper methods.
034 *
035 * @param <V> the type of the parser values
036 */
037@SuppressWarnings( {"UnusedDeclaration"})
038public abstract class BaseActions<V> implements ContextAware<V> {
039
040    private Context<V> context;
041
042    /**
043     * The current context for use with action methods. Updated immediately before action calls.
044     *
045     * @return the current context
046     */
047    public Context<V> getContext() {
048        return context;
049    }
050
051    /**
052     * ContextAware interface implementation.
053     *
054     * @param context the context
055     */
056    public void setContext(Context<V> context) {
057        this.context = checkArgNotNull(context, "context");
058    }
059
060    /**
061     * Returns the current index in the input buffer.
062     *
063     * @return the current index
064     */
065    public int currentIndex() {
066        check();
067        return context.getCurrentIndex();
068    }
069
070    /**
071     * <p>Returns the input text matched by the rule immediately preceding the action expression that is currently
072     * being evaluated. This call can only be used in actions that are part of a Sequence rule and are not at first
073     * position in this Sequence.</p>
074     *
075     * @return the input text matched by the immediately preceding subrule
076     */
077    public String match() {
078        check();
079        return context.getMatch();
080    }
081    
082    /**
083     * Creates a new {@link IndexRange} instance covering the input text matched by the rule immediately preceding the
084     * action expression that is currently being evaluated. This call can only be used in actions that are part of a
085     * Sequence rule and are not at first position in this Sequence.
086     *  
087     * @return a new IndexRange instance
088     */
089    public IndexRange matchRange() {
090        check();
091        return context.getMatchRange();
092    }
093
094    /**
095     * <p>Returns the input text matched by the rule immediately preceding the action expression that is currently
096     * being evaluated. If the matched input text is empty the given default string is returned.
097     * This call can only be used in actions that are part of a Sequence rule and are not at first
098     * position in this Sequence.</p>
099     *
100     * @param defaultString the default string to return if the matched input text is empty
101     * @return the input text matched by the immediately preceding subrule or the default string
102     */
103    public String matchOrDefault(String defaultString) {
104        check();
105        String match = context.getMatch();
106        return match.length() == 0 ? defaultString : match;
107    }
108
109    /**
110     * <p>Returns the first character of the input text matched by the rule immediately preceding the action
111     * expression that is currently being evaluated. This call can only be used in actions that are part of a Sequence
112     * rule and are not at first position in this Sequence.</p>
113     * <p>If the immediately preceding rule did not match anything this method throws a GrammarException. If you need
114     * to able to handle that case use the getMatch() method.</p>
115     *
116     * @return the first input char of the input text matched by the immediately preceding subrule or null,
117     *         if the previous rule matched nothing
118     */
119    public char matchedChar() {
120        check();
121        return context.getFirstMatchChar();
122    }
123
124    /**
125     * <p>Returns the start index of the rule immediately preceding the action expression that is currently
126     * being evaluated. This call can only be used in actions that are part of a Sequence rule and are not at first
127     * position in this Sequence.</p>
128     *
129     * @return the start index of the context immediately preceding current action
130     */
131    public int matchStart() {
132        check();
133        return context.getMatchStartIndex();
134    }
135
136    /**
137     * <p>Returns the end location of the rule immediately preceding the action expression that is currently
138     * being evaluated. This call can only be used in actions that are part of a Sequence rule and are not at first
139     * position in this Sequence.</p>
140     *
141     * @return the end index of the context immediately preceding current action, i.e. the index of the character
142     *         immediately following the last matched character
143     */
144    public int matchEnd() {
145        check();
146        return context.getMatchEndIndex();
147    }
148    
149    /**
150     * <p>Returns the number of characters matched by the rule immediately preceding the action expression that is
151     * currently being evaluated. This call can only be used in actions that are part of a Sequence rule and are not
152     * at first position in this Sequence.</p>
153     * 
154     * @return the number of characters matched
155     */
156    public int matchLength() {
157        check();
158        return context.getMatchLength();
159    }
160
161    /**
162     * <p>Returns the current position in the underlying {@link InputBuffer} as a
163     * {@link Position} instance.</p>
164     * 
165     * @return the current position in the underlying inputbuffer
166     */
167    public Position position() {
168        check();
169        return context.getPosition();
170    }
171
172    /**
173     * Pushes the given value onto the value stack. Equivalent to push(0, value).
174     *
175     * @param value the value to push
176     * @return true
177     */
178    public boolean push(V value) {
179        check();
180        context.getValueStack().push(value);
181        return true;
182    }
183
184    /**
185     * Inserts the given value a given number of elements below the current top of the value stack.
186     *
187     * @param down  the number of elements to skip before inserting the value (0 being equivalent to push(value))
188     * @param value the value
189     * @return true
190     * @throws IllegalArgumentException if the stack does not contain enough elements to perform this operation
191     */
192    public boolean push(int down, V value) {
193        check();
194        context.getValueStack().push(down, value);
195        return true;
196    }
197
198    /**
199     * Pushes all given elements onto the value stack (in the order as given).
200     *
201     * @param firstValue the first value
202     * @param moreValues the other values
203     * @return true
204     */
205    public boolean pushAll(V firstValue, V... moreValues) {
206        check();
207        context.getValueStack().pushAll(firstValue, moreValues);
208        return true;
209    }
210
211    /**
212     * Removes the value at the top of the value stack and returns it.
213     *
214     * @return the current top value
215     * @throws IllegalArgumentException if the stack is empty
216     */
217    public V pop() {
218        check();
219        return context.getValueStack().pop();
220    }
221
222    /**
223     * Removes the value the given number of elements below the top of the value stack.
224     *
225     * @param down the number of elements to skip before removing the value (0 being equivalent to pop())
226     * @return the value
227     * @throws IllegalArgumentException if the stack does not contain enough elements to perform this operation
228     */
229    public V pop(int down) {
230        check();
231        return context.getValueStack().pop(down);
232    }
233
234    /**
235     * Removes the value at the top of the value stack.
236     *
237     * @return true
238     * @throws IllegalArgumentException if the stack is empty
239     */
240    public boolean drop() {
241        check();
242        context.getValueStack().pop();
243        return true;
244    }
245
246    /**
247     * Removes the value the given number of elements below the top of the value stack.
248     *
249     * @param down the number of elements to skip before removing the value (0 being equivalent to drop())
250     * @return true
251     * @throws IllegalArgumentException if the stack does not contain enough elements to perform this operation
252     */
253    public boolean drop(int down) {
254        check();
255        context.getValueStack().pop(down);
256        return true;
257    }
258
259    /**
260     * Returns the value at the top of the value stack without removing it.
261     *
262     * @return the current top value
263     * @throws IllegalArgumentException if the stack is empty
264     */
265    public V peek() {
266        check();
267        return context.getValueStack().peek();
268    }
269
270    /**
271     * Returns the value the given number of elements below the top of the value stack without removing it.
272     *
273     * @param down the number of elements to skip (0 being equivalent to peek())
274     * @return the value
275     * @throws IllegalArgumentException if the stack does not contain enough elements to perform this operation
276     */
277    public V peek(int down) {
278        check();
279        return context.getValueStack().peek(down);
280    }
281
282    /**
283     * Replaces the current top value of the value stack with the given value. Equivalent to poke(0, value).
284     *
285     * @param value the value
286     * @return true
287     * @throws IllegalArgumentException if the stack is empty
288     */
289    public boolean poke(V value) {
290        check();
291        context.getValueStack().poke(value);
292        return true;
293    }
294
295    /**
296     * Replaces the element the given number of elements below the current top of the value stack.
297     *
298     * @param down  the number of elements to skip before replacing the value (0 being equivalent to poke(value))
299     * @param value the value to replace with
300     * @return true
301     * @throws IllegalArgumentException if the stack does not contain enough elements to perform this operation
302     */
303    public boolean poke(int down, V value) {
304        check();
305        context.getValueStack().poke(down, value);
306        return true;
307    }
308
309    /**
310     * Duplicates the top value of the value stack. Equivalent to push(peek()).
311     *
312     * @return true
313     * @throws IllegalArgumentException if the stack is empty
314     */
315    public boolean dup() {
316        check();
317        context.getValueStack().dup();
318        return true;
319    }
320
321    /**
322     * Swaps the top two elements of the value stack.
323     *
324     * @return true
325     * @throws GrammarException
326     *          if the stack does not contain at least two elements
327     */
328    public boolean swap() {
329        check();
330        context.getValueStack().swap();
331        return true;
332    }
333
334    /**
335     * Reverses the order of the top 3 value stack elements.
336     *
337     * @return true
338     * @throws GrammarException
339     *          if the stack does not contain at least 3 elements
340     */
341    public boolean swap3() {
342        check();
343        context.getValueStack().swap3();
344        return true;
345    }
346
347    /**
348     * Reverses the order of the top 4 value stack elements.
349     *
350     * @return true
351     * @throws GrammarException
352     *          if the stack does not contain at least 4 elements
353     */
354    public boolean swap4() {
355        check();
356        context.getValueStack().swap4();
357        return true;
358    }
359
360    /**
361     * Reverses the order of the top 5 value stack elements.
362     *
363     * @return true
364     * @throws GrammarException
365     *          if the stack does not contain at least 5 elements
366     */
367    public boolean swap5() {
368        check();
369        context.getValueStack().swap5();
370        return true;
371    }
372
373    /**
374     * Reverses the order of the top 6 value stack elements.
375     *
376     * @return true
377     * @throws GrammarException
378     *          if the stack does not contain at least 6 elements
379     */
380    public boolean swap6() {
381        check();
382        context.getValueStack().swap6();
383        return true;
384    }
385
386    /**
387     * Returns the next input character about to be matched.
388     *
389     * @return the next input character about to be matched
390     */
391    public Character currentChar() {
392        check();
393        return context.getCurrentChar();
394    }
395
396    /**
397     * Returns true if the current rule is running somewhere underneath a Test/TestNot rule.
398     * Useful for example for making sure actions are not run inside of a predicate evaluation:
399     * <pre>
400     * return Sequence(
401     * ...,
402     * inPredicate() || actions.doSomething()
403     * );
404     * </pre>
405     *
406     * @return true if in a predicate
407     */
408    public boolean inPredicate() {
409        check();
410        return context.inPredicate();
411    }
412
413    /**
414     * Returns true if the current context is for or below a rule marked {@literal @SuppressNode} or below one
415     * marked {@literal @SuppressSubnodes}.
416     *
417     * @return true or false
418     */
419    public boolean nodeSuppressed() {
420        check();
421        return context.isNodeSuppressed();
422    }
423
424    /**
425     * Determines whether the current rule or a sub rule has recorded a parse error.
426     * Useful for example for making sure actions are not run on erroneous input:
427     * <pre>
428     * return Sequence(
429     * ...,
430     * !hasError() &amp;&amp; actions.doSomething()
431     * );
432     * </pre>
433     *
434     * @return true if either the current rule or a sub rule has recorded a parse error
435     */
436    public boolean hasError() {
437        check();
438        return context.hasError();
439    }
440
441    private void check() {
442        Checks.ensure(context != null && context.getMatcher() != null,
443                "Illegal rule definition: Unwrapped action expression!");
444    }
445
446}