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() && 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}