1 /*
2   mxte_impl -- A table driven Tagging Engine for Python (Version 0.9)
3 
4   This is the Tagging Engine implementation. It can be compiled for
5   8-bit strings and Unicode by setting the TE_* defines appropriately.
6 
7   Copyright (c) 2000, Marc-Andre Lemburg; mailto:mal@lemburg.com
8   Copyright (c) 2000-2002, eGenix.com Software GmbH; mailto:info@egenix.com
9   Copyright (c) 2003-2006, Mike Fletcher; mailto:mcfletch@vrplumber.com
10 */
11 
12 #ifndef TE_STRING_CHECK
13 # define TE_STRING_CHECK(obj) PyString_Check(obj)
14 #endif
15 #ifndef TE_STRING_AS_STRING
16 # define TE_STRING_AS_STRING(obj) PyString_AS_STRING(obj)
17 #endif
18 #ifndef TE_STRING_GET_SIZE
19 # define TE_STRING_GET_SIZE(obj) PyString_GET_SIZE(obj)
20 #endif
21 #ifndef TE_STRING_FROM_STRING
22 # define TE_STRING_FROM_STRING(str, size) PyString_FromStringAndSize(str, size)
23 #endif
24 #ifndef TE_CHAR
25 # define TE_CHAR char
26 #endif
27 #ifndef TE_HANDLE_MATCH
28 # define TE_HANDLE_MATCH string_match_append
29 #endif
30 #ifndef TE_ENGINE_API
31 # define TE_ENGINE_API mxTextTools_TaggingEngine
32 #endif
33 
34 
35 /* --- Tagging Engine ----------------------------------------------------- */
36 /*  Non-recursive restructuring by Mike Fletcher to support SimpleParse
37 
38   This restructuring eliminates the use of the C call stack for
39   processing sub-table and table directives, allowing these to be
40   used for repetition calls if desired.
41 
42 
43 while 1:
44 	while (index_in_table() and returnCode == NULL_CODE):
45 		decode the current table[index]
46 		if the current tag is new (not already processed):
47 			reset tag variables
48 			switch( tag command ):
49 				do what tag wants to do()
50 				set tag-related variables
51 				set childReturnCode (tag variable)
52 				if table:
53 					push_frame_stack()
54 					set childReturnCode == PENDING
55 		switch(childReturnCode):
56 			# figure out what to do with child's results
57 			# possibly set table-wide returnValue
58 			childSuccess
59 				append values
60 				update table-wide values
61 				set new index
62 			childFailure
63 				rewind position
64 				set new index
65 			childError
66 				signal error for whole table
67 			childPending
68 				ignore/continue processing without updating list values
69 		reset childReturnCode
70 	#done table, figure out what to do now...
71 	if no explicit return value:
72 		figure out implicit
73 	if failure:
74 		truncate result list to previous length
75 		reset position
76 	if error:
77 		report error as exception
78 		exit
79 	else:
80 		if frame_stack():
81 			pop_frame_stack()
82 		else:
83 			return result
84 
85 */
86 
87 /* call-stack structures used in non-recursive implementation */
88 #ifndef TEXTTOOLS_CALL_STACK_STRUCTURES
89 # define TEXTTOOLS_CALL_STACK_STRUCTURES
90 
91 /* codes for returnCode and childReturnCode variables */
92 #define EOF_CODE 3
93 #define SUCCESS_CODE 2
94 #define FAILURE_CODE 1
95 #define ERROR_CODE 0
96 #define NULL_CODE -1
97 #define PENDING_CODE -2
98 
99 typedef struct stack_entry {
100 	/* represents data stored for a particular stack recursion
101 
102 	  We want to make this as small as possible, so anything that
103 	  is duplicate information (such as unpacked values of the tag or table)
104 	  is ignored.
105 
106 	  Eventually this may support another field "available branches"
107 	  recording backtracking points for the engine.
108 	*/
109 	void * parent; /* pointer to a parent table or NULL */
110 
111 
112 	Py_ssize_t position; /* where the engine is currently parsing for the parent table*/
113 	Py_ssize_t startPosition; /* position where we started parsing for the parent table */
114 
115 	mxTagTableObject * table; /* the parent table */
116 	Py_ssize_t index; /* index of the child tag in the parent table */
117 
118 	Py_ssize_t childStart; /* text start position for the child table */
119 	PyObject * results; /* the result-target of the parent */
120 	Py_ssize_t resultsLength; /* the length of the results list before the sub-table is called */
121 } recursive_stack_entry;
122 
123 
124 /* Macro to reset table-specific variables
125 
126 XXX Not sure if loop vars are table or tag specific
127 */
128 #define RESET_TABLE_VARIABLES {\
129 	index=0;\
130 	table_len = table->numentries;\
131 	returnCode = NULL_CODE;\
132 	loopcount = -1;\
133 	loopstart = startPosition;\
134 	taglist_len = PyList_Size( taglist );\
135 }
136 
137 /* Macro to reset tag-specific variables
138 
139 */
140 #define RESET_TAG_VARIABLES {\
141 	childStart = position;\
142 	childPosition = position;\
143 	childReturnCode = NULL_CODE;\
144 	childResults = NULL;\
145 }
146 /* Macro to decode a tag-entry into local variables */
147 #define DECODE_TAG {\
148 	mxTagTableEntry *entry;\
149 	entry = &table->entry[index];\
150 	command = entry->cmd;\
151 	flags = entry->flags;\
152 	match = entry->args;\
153 	failureJump = entry->jne;\
154 	successJump = entry->je;\
155 	tagobj = entry->tagobj;\
156 	if (tagobj == NULL) { tagobj = Py_None;}\
157 }
158 
159 /* macro to push relevant local variables onto the stack and setup for child table
160 	newTable becomes table, newResults becomes taglist
161 
162 	This is currently only called in the Table/SubTable family of commands,
163 	could be inlined there, but I find it cleaner to read here.
164 */
165 #define PUSH_STACK( newTable, newResults ) {\
166 	stackTemp = (recursive_stack_entry *) PyMem_Malloc( sizeof( recursive_stack_entry ));\
167 	stackTemp->parent = stackParent;\
168 	stackTemp->position = position;\
169 	stackTemp->startPosition = startPosition;\
170 	stackTemp->table = table;\
171 	stackTemp->index = index;\
172 	stackTemp->childStart = childStart;\
173 	stackTemp->resultsLength = taglist_len;\
174 	stackTemp->results = taglist;\
175 	\
176 	stackParent = stackTemp;\
177 	childReturnCode = PENDING_CODE;\
178 	\
179 	startPosition = position;\
180 	table = (mxTagTableObject *) newTable;\
181 	taglist = newResults;\
182 }
183 #define POP_STACK {\
184 	if (stackParent) {\
185 		childStart = stackParent->childStart;\
186 		childPosition = position;\
187 		position = stackParent->position;\
188 		\
189 		startPosition = stackParent->startPosition;\
190 		\
191 		childResults = taglist;\
192 		taglist_len = stackParent->resultsLength;\
193 		taglist = stackParent->results;\
194 		if (table != stackParent->table ) { Py_DECREF( table ); }\
195 		table = stackParent->table;\
196 		table_len = table->numentries;\
197 		index = stackParent->index;\
198 		\
199 		stackTemp = stackParent->parent;\
200 		PyMem_Free( stackParent );\
201 		stackParent = stackTemp;\
202 		stackTemp = NULL;\
203 		\
204 		childReturnCode = returnCode;\
205 		returnCode = NULL_CODE;\
206 	}\
207 }
208 
209 
210 #endif
211 
212 /* mxTextTools_TaggingEngine(): a table driven parser engine
213 
214    - return codes: returnCode = 2: match ok; returnCode = 1: match failed; returnCode = 0: error
215    - doesn't check type of passed arguments !
216    - doesn't increment reference counts of passed objects !
217 */
218 
219 
220 
TE_ENGINE_API(PyObject * textobj,Py_ssize_t sliceleft,Py_ssize_t sliceright,mxTagTableObject * table,PyObject * taglist,PyObject * context,Py_ssize_t * next)221 int TE_ENGINE_API(
222 	PyObject *textobj,
223 	Py_ssize_t sliceleft,
224 	Py_ssize_t sliceright,
225 	mxTagTableObject *table,
226 	PyObject *taglist,
227 	PyObject *context,
228 	Py_ssize_t *next
229 ) {
230     TE_CHAR *text = NULL;		/* Pointer to the text object's data */
231 
232 	/* local variables pushed into stack on recurse */
233 		/* whole-table variables */
234 		Py_ssize_t position = sliceleft;		/* current (head) position in text for whole table */
235 		Py_ssize_t startPosition = sliceleft;	/* start position for current tag */
236 		Py_ssize_t table_len = table->numentries; /* table length */
237 		short returnCode = NULL_CODE;		/* return code: -1 not set, 0 error, 1
238 					   not ok, 2 ok */
239 		Py_ssize_t index=0; 			/* index of current table entry */
240 		Py_ssize_t taglist_len = PyList_Size( taglist );
241 
242 
243 		/* variables tracking status of the current tag */
244 		register short childReturnCode = NULL_CODE; /* the current child's return code value */
245 		Py_ssize_t childStart = startPosition;
246 		register Py_ssize_t childPosition = startPosition;
247 		PyObject *childResults = NULL; /* store's the current child's results (for table children) */
248 		int flags=0;			/* flags set in command */
249 		int command=0;			/* command */
250 		int failureJump=0;			/* rel. jump distance on 'not matched', what should the default be? */
251 		int successJump=1;			/* dito on 'matched', what should the default be? */
252 		PyObject *match=NULL;		/* matching parameter */
253 		int loopcount = -1; 	/* loop counter */
254 		Py_ssize_t loopstart = startPosition;	/* loop start position */
255 		PyObject *tagobj = NULL;
256 
257 
258 	/* parentTable is our nearest parent, i.e. the next item to pop
259 	   off the processing stack.  We copied our local variables to it
260 	   before starting a child table, and will copy back from it when
261 	   we finish the child table.  It's normally NULL
262 	*/
263 	recursive_stack_entry * stackParent = NULL;
264 	recursive_stack_entry * stackTemp = NULL; /* just temporary storage for parent pointers */
265 
266 	/* Error-management variables */
267 	PyObject * errorType = NULL;
268 	PyObject * errorMessage = NULL;
269 
270     /* Initialise the buffer
271 
272 	Here is where we will add memory-mapped file support I think...
273 
274 		expand the TE_STRING macros to check for mmap file objects
275 		(only for str-type) and to access their values appropriately
276 			f = open('c:\\temp\\test.mem', 'r')
277 			buffer = mmap.mmap( f.fileno(), 0, access = mmap.ACCESS_READ )
278 
279 	*/
280 	if (!TE_STRING_CHECK(textobj)) {
281 		returnCode = ERROR_CODE;
282 		errorType = PyExc_TypeError;
283 		errorMessage = PyString_FromFormat(
284 		     "Expected a string or unicode object to parse: found %.50s",
285 		     Py_TYPE(textobj)->tp_name
286 		);
287 	} else {
288 	    text = TE_STRING_AS_STRING(textobj);
289 		if (text == NULL) {
290 			returnCode = ERROR_CODE;
291 		}
292 	}
293 
294 	while (1) {
295 		/* this loop processes a whole table */
296 		while (
297 			(index < table_len) &
298 			(returnCode == NULL_CODE) &
299 			(index >= 0)
300 		) {
301 			DPRINTF( "index %i\n", index );
302 			DECODE_TAG
303 			if (childReturnCode == NULL_CODE ) {
304 				/* if we are not continuing processing of the child
305 				   from a previous iteration we need to unpack the
306 				   child into local variables
307 				*/
308 				RESET_TAG_VARIABLES
309 				childStart = position;
310 				childPosition = position;
311 
312 			}
313 			if (command < MATCH_MAX_LOWLEVEL) {
314 #include "lowlevelcommands.h"
315 			} else {
316 				switch (command) {
317 /* Jumps & special commands */
318 #include "speccommands.h"
319 /* non-table-recursion high-level stuff */
320 #include "highcommands.h"
321 /* the recursive table commands */
322 #include "recursecommands.h"
323 					default:
324 						{
325 							childReturnCode = ERROR_CODE;
326 							errorType = PyExc_ValueError;
327 							errorMessage = PyString_FromFormat(
328 								 "Unrecognised command code %i",
329 								 command
330 							);
331 						}
332 				}
333 			}
334 			/* we're done a single tag, process partial results for the current child
335 
336 				This is a major re-structuring point.  Previously
337 				all of this was scattered around (and duplicated among)
338 				the various command and command-group clauses.
339 
340 				There also used to be a function call to handle the
341 				append/call functions.  That's now handled inline
342 
343 			*/
344 			/* sanity check wanted by Marc-André for skip-before-buffer */
345 			if (childPosition < 0) {
346 				childReturnCode = ERROR_CODE;
347 				errorType = PyExc_TypeError;
348 				errorMessage = PyString_FromFormat(
349 					 "tagobj (type %.50s) table entry %d moved/skipped beyond start of text (to position %d)",
350 					 Py_TYPE(tagobj)->tp_name,
351 					 (unsigned int)index,
352 					 (unsigned int)childPosition
353 				);
354 			}
355 			DPRINTF( "switch on return code %i\n", childReturnCode );
356 			switch(childReturnCode) {
357 				case NULL_CODE:
358 				case SUCCESS_CODE:
359 					/* childReturnCode wasn't set or we positively matched
360 
361 					positions are always:
362 						childStart, childPosition
363 					sub-results are:
364 						childResults
365 							unless childResults is taglist
366 								in which case we use Py_None  for the tag's children
367 							unless childResults is NULL
368 								in which case we create an empty list object
369 
370 					we call:
371 						tagobj == Py_None :
372 							do nothing...
373 
374 						[ result tuple needed ]
375 							CallTag:
376 								entry->tagobj( resultTuple )
377 							AppendToTagobj:
378 								entry->tagobj.append( resultTuple )
379 							General Case:
380 								taglist.append( resultTuple )
381 
382 						AppendMatch:
383 							taglist.append( text[childStart:childPosition] )
384 						AppendTagobj:
385 							taglist.append( entry->tagobj )
386 
387 					if LookAhead is specified:
388 						childPosition is set to childStart before continuing
389 
390 					finally we set position = childPosition
391 					*/
392 					{
393 						PyObject * objectToCall = NULL;
394 						PyObject * objectCallResult = NULL;
395 						int releaseCallObject = 0;
396 						int releaseChildResults = 0;
397 						int releaseParameter = 1;
398 						PyObject * parameter = NULL;
399 						DPRINTF( "finishing success-code or null \n" );
400 
401 						if (tagobj == Py_None  ) {
402 							/* XXX note: this short-circuits around "AppendTagobj" flagged items which
403 							specified tagobj == None... don't know if that's wanted or not.  Similarly
404 							doesn't report AppendMatch's.  Not sure what's appropriate there either.
405 							*/
406 							DPRINTF( "tagobj was none\n" );
407 							DPRINTF( "Matched %i:%i but result not saved", childStart, childPosition );
408 						} else {
409 							/* get the callable object */
410 							/* normally it's taglist.append, do the exceptions first */
411 							DPRINTF( "tagobj non-None, finding callable\n" );
412 							if (flags & MATCH_CALLTAG) {
413 								/* want the tag itself */
414 								objectToCall = tagobj;
415 							} else if (flags & MATCH_APPENDTAG) {
416 								/* AppendToTagobj -> want the tag's append method */
417 								DPRINTF( "append to tag obj\n" );
418 								objectToCall = PyObject_GetAttrString( tagobj, "append" );
419 								DPRINTF( "got object\n");
420 								if (objectToCall == NULL) {
421 									DPRINTF( "got invalid object\n");
422 									returnCode = ERROR_CODE;
423 									errorType = PyExc_AttributeError;
424 									errorMessage = PyString_FromFormat(
425 										 "tagobj (type %.50s) for table entry %d (flags include AppendTag) doesn't have an append method",
426 										 Py_TYPE(tagobj)->tp_name,
427 										 (unsigned int)index
428 									);
429 								} else {
430 									DPRINTF( "got valid object\n");
431 									releaseCallObject = 1;
432 								}
433 							} else {
434 								DPRINTF( "appending to tag-list\n" );
435 								/* append of the taglist, which we know exists, because it's a list
436 								We optimise this to use the raw List API
437 								*/
438 								objectToCall = NULL; /*PyObject_GetAttrString( taglist, "append" );*/
439 							}
440 							if (returnCode == NULL_CODE && objectToCall && PyCallable_Check(objectToCall)==0) {
441 								/* object to call isn't callable */
442 								DPRINTF( "object not callable\n" );
443 								returnCode = ERROR_CODE;
444 								errorType = PyExc_TypeError;
445 								errorMessage = PyString_FromFormat(
446 									 "The object to call type(%.50s) for table entry %d isn't callable",
447 									 Py_TYPE(objectToCall)->tp_name,
448 									 (unsigned int)index
449 								);
450 							}
451 							if (returnCode == NULL_CODE) {
452 								/* get the parameter with which to call */
453 								/* normally it's a result tuple, do exceptions first */
454 								DPRINTF( "getting parameter\n" );
455 								if (flags & MATCH_APPENDMATCH) {
456 									/* XXX need to do bounds-checking here
457 									so that:
458 										childStart >= sliceleft
459 										childPosition >= sliceleft
460 										childPosition <= sliceright
461 									*/
462 									/* MATCH_APPENDMATCH cannot occur with any
463 									other flag (makes no sense) so objectToCall
464 									_must_ be the taglist, and we just want to append
465 									the string, not a tuple wrapping the string.  That is,
466 									everywhere else we use tuples, here we don't
467 									*/
468 									parameter = TE_STRING_FROM_STRING(
469 										TE_STRING_AS_STRING(textobj) + childStart,
470 										childPosition - childStart
471 									);
472 									if (parameter == NULL) {
473 										/* error occured getting parameter, report the exception */
474 										returnCode = ERROR_CODE;
475 									}
476 								} else if ( flags & MATCH_APPENDTAGOBJ) {
477 									/* append the tagobj itself to the results list */
478 									if (tagobj == NULL) {
479 										parameter = Py_None;
480 									} else {
481 										parameter = tagobj;
482 									}
483 									releaseParameter = 0;
484 								} else {
485 									/* need to know what the child-list is to build resultsTuple
486 									if childResults is non-null and not taglist use it
487 									if childResults == taglist, use Py_None
488 									otherwise use Py_None ( originally we created a new empty list object, that was wrong :) ).
489 									*/
490 									if (childResults == taglist) {
491 										childResults = Py_None ;
492 									} else if (childResults != NULL) {
493 										/* exists already, with a reference from PUSH's creation */
494 										releaseChildResults = 1;
495 									} else {
496 										/* turns out mxTextTools declares the return value to be
497 										None or [], using None is far more efficient, so I've made
498 										the code use it here */
499 										childResults = Py_None;
500 										releaseChildResults = 0; /* we aren't increfing it locally */
501 									}
502 									if (childResults == NULL || tagobj == NULL) {
503 										returnCode = ERROR_CODE;
504 									} else {
505 										if (flags & MATCH_CALLTAG) {
506 											parameter = Py_BuildValue( "OOiiO", taglist, textobj, childStart, childPosition, childResults );
507 										} else if (flags & MATCH_APPENDTAG) {
508 											/* AppendToTagobj -> want to call append with a 4-tuple of values, so parameter needs to be ((x,y,z,w),) */
509 											/* XXX can't get the darn thing to accept "((OiiO))" :( */
510 											parameter = Py_BuildValue(
511 												"((OiiO))",
512 												Py_None,
513 												childStart,
514 												childPosition,
515 												childResults
516 											);
517 										} else {
518 											/* either we are calling a method that requires the 4 args, or we're appending the 4-tuple to a list */
519 											parameter = Py_BuildValue( "OiiO", tagobj, childStart, childPosition, childResults );
520 										}
521 										if (parameter == NULL) {
522 											returnCode = ERROR_CODE;
523 										}
524 									}
525 								}
526 								DPRINTF( "done getting parameter\n" );
527 								if (parameter == NULL && returnCode == ERROR_CODE && errorType == NULL) {
528 									errorType = PyExc_SystemError;
529 									/* following may fail, as we may have run out of memory */
530 									errorMessage = PyString_FromFormat(
531 										 "Unable to build return-value tuple"
532 									);
533 								}
534 								/* now have both object and parameter and object is callable */
535 								if (returnCode == NULL_CODE) {
536 									/* no errors yet */
537 									DPRINTF( "doing call\n" );
538 									if (objectToCall) {
539 										DPRINTF( " object call\n" );
540 										/* explicit object to call */
541 										Py_INCREF( objectToCall );
542 										Py_INCREF( parameter );
543 										DPRINTF( " lock released\n" );
544 										objectCallResult =  PyEval_CallObject( objectToCall, parameter );
545 										DPRINTF( " call finished\n" );
546 										Py_DECREF( objectToCall );
547 										Py_DECREF( parameter );
548 										DPRINTF( " lock acquired\n" );
549 										if (objectCallResult == NULL) {
550 											DPRINTF( " null result\n" );
551 											returnCode = ERROR_CODE;
552 											/* exception is already there, should alter error-handler to check for it */
553 										} else {
554 											DPRINTF( " non-null result, decrefing\n" );
555 											Py_DECREF( objectCallResult );
556 											DPRINTF( " decrefd\n" );
557 										}
558 										objectCallResult = NULL;
559 									} else {
560 										/* list steals reference */
561 										DPRINTF( " list append\n" );
562 										if (PyList_Append( taglist, parameter ) == -1) {
563 											returnCode = ERROR_CODE;
564 											/* list didn't steal ref yet */
565 											errorType = PyExc_SystemError;
566 											/* following is likely to fail, as we've likely run out of memory */
567 											errorMessage = PyString_FromFormat(
568 												 "Unable to append result tuple to result list!"
569 											);
570 										}
571 									}
572 								}
573 							}
574 							DPRINTF( "checking whether to release object\n" );
575 							if (releaseCallObject) {
576 								Py_DECREF( objectToCall );
577 							}
578 							objectToCall = NULL;
579 							releaseCallObject = 0;
580 
581 							if (releaseChildResults) {
582 								Py_DECREF( childResults );
583 							}
584 							childResults = NULL;
585 							releaseChildResults = 0;
586 							if (releaseParameter && parameter ) {
587 								Py_DECREF( parameter );
588 							}
589 							parameter = NULL;
590 							releaseParameter = 1;
591 						} /* ends the else clause for reporting a result */
592 						/* reset for lookahead */
593 						if (flags & MATCH_LOOKAHEAD) {
594 							position = childStart;
595 						} else {
596 							position = childPosition;
597 						}
598 						index += successJump;
599 						DPRINTF( "finished success-handler code\n" );
600 						break;
601 					}
602 				case FAILURE_CODE:
603 					/* failed, if failure jump is default, should set table returnCode */
604 					if (childResults) {
605 						if (childResults != taglist) {
606 							/* different list, decref it since we won't be using it any more */
607 							Py_DECREF( childResults );
608 						}
609 						childResults = NULL;
610 					}
611 					/* XXX possible (eventual) logic error here?
612 
613 						fail with jump of 0 might work in certain cases where the
614 						"parsing" is actually occuring outside of the current buffer
615 						(i.e. a side-effect-based parsing node that fails X times before
616 						finally succeeding).
617 
618 						Don't see anything in current commands that can cause a problem
619 						but we may need to make this an explicitly watched idea, rather
620 						than a consequence of the child failing with a 0 failureJump value.
621 					*/
622 					position = childStart;
623 					if (failureJump == 0) {
624 						returnCode = 1;
625 					} else {
626 						index += failureJump;
627 					}
628 					break;
629 				case PENDING_CODE:
630 					/* the child tag hasn't begun parsing, this was a
631 					recursive-tag-start loop pass. PENDING_CODE is set
632 					by the stack push operation
633 					*/
634 					break;
635 				case ERROR_CODE:
636 					{
637 						/* explicit error encountered while processing this child
638 
639 							Handle this as gracefully as possible, potentially triggering
640 							huge sets of operations, but therefore needing to be very careful
641 							about system-level errors (such as memory errors).
642 
643 							1) Signal whole table as err-d
644 							2) Record any extra values for the error message?
645 						*/
646 						returnCode = ERROR_CODE;
647 						break;
648 					}
649 				default:
650 					{
651 						/* what error should be raised when an un-recognised return code is generated? */
652 						returnCode = ERROR_CODE;
653 						errorType = PyExc_SystemError;
654 						errorMessage = PyString_FromFormat(
655 							 "An unknown child return code %i was generated by tag-table item %d",
656 							childReturnCode,
657 							(unsigned int)index
658 						);
659 					}
660 			}
661 			childReturnCode = NULL_CODE;
662 			/* single entry processing loop complete */
663 		}
664 		/* we're done the table, figure out what to do. */
665 		if (returnCode == NULL_CODE) {
666 			/* no explicit return code was set, but done table:
667 
668 			index went beyond table_len (>=table_len) -> success
669 			index moved before table start (<= 0) -> failure
670 			*/
671 			if (index >= table_len) {
672 				/* success */
673 				returnCode = SUCCESS_CODE;
674 			} else if (position >= sliceright) {
675 				/* EOF while parsing, special type of failure
676 
677 				Eventually allow for returning the whole parse-stack
678 				for restarting the parser from a particular point.
679 				*/
680 				/*returnCode = EOF_CODE;*/
681 				returnCode = FAILURE_CODE;
682 			} else if (index < 0) {
683 				/* explicit jump before table */
684 				returnCode = FAILURE_CODE;
685 			} else {
686 				returnCode = FAILURE_CODE;
687 			}
688 		}
689 		if (returnCode == FAILURE_CODE) {
690 			/* truncate result list */
691 			if (PyList_SetSlice(
692 					taglist,
693 					taglist_len,
694 					PyList_Size(taglist),
695 					NULL)
696 			) {
697 				returnCode = ERROR_CODE;
698 				errorMessage = PyString_FromFormat(
699 					 "Unable to truncate list object (likely tagging engine error) type(%.50s)",
700 					 Py_TYPE(taglist)->tp_name
701 				);
702 			}
703 			/* reset position */
704 			position = startPosition;
705 		}
706 		if (returnCode == ERROR_CODE) {
707 			/*
708 			DO_FANCY_ERROR_REPORTING( );
709 
710 			This is where we will do the user-triggered error reporting
711 			(as well as reporting low-level errors such as memory/type/value).
712 
713 			We have 3 values possibly available:
714 				errorType -> PyObject * to current error class (or NULL)
715 					if it is a MemoryError:
716 
717 						Jettison some ballast then attempt to return a short
718 						message.  Need to create this ballast somewhere for that
719 						to work.
720 
721 					if is any other error class:
722 
723 						create the error object and raise it
724 
725 						decorate it with details:
726 
727 							current table (need to incref to keep alive)
728 							current index
729 							current position
730 							childStart
731 							childPosition
732 
733 						if it is simpleparse.stt.TextTools.ParsingError:
734 							(triggered by the user in their grammar)
735 
736 							create a list of non-None parent tagobjs (a stack
737 							report) and add it to the object
738 
739 
740 
741 
742 
743 				3) Build an actual error object if possible?
744 				4) Report the parent hierarchy of the failure point
745 				5)
746 			*/
747 			char * msg = NULL;
748 			if (errorMessage && errorType) {
749 				/* we only report our own error if we've got all the information for it
750 
751 				XXX Need to check that we don't have cases that are just setting type
752 				*/
753 				msg = PyString_AsString( errorMessage);
754 				PyErr_SetString( errorType, msg );
755 				Py_DECREF( errorMessage );
756 			}
757 
758 
759 
760 			/* need to free the whole stack at once */
761 			while (stackParent != NULL) {
762 				/* this is inefficient, should do it all-in-one-go without copying values back
763 				save for startPosition and returnCode in the last item*/
764 				POP_STACK
765 				/* need to clean up all INCREF'd objects as we go... */
766 				if (childResults != taglist) {
767 					/* different list, decref it since we won't be using it any more */
768 					Py_DECREF( childResults );
769 				}
770 				childResults = NULL;
771 			}
772 			*next = startPosition;
773 			return 0;
774 		} else {
775 			if (stackParent != NULL) {
776 				/* pop stack also sets the childReturnCode for us... */
777 				POP_STACK
778 			} else {
779 				/* this was the root table,
780 				 return the final results */
781 				if (returnCode == FAILURE_CODE) {
782 					/* there is a clause in the docs for tag that says
783 					this will return the "error position" for the table.
784 					That requires reporting childPosition for the the
785 					last-matched position */
786 					*next = childPosition;
787 				} else {
788 					*next = position;
789 				}
790 				return returnCode;
791 			}
792 		}
793 	} /* end of infinite loop */
794 }
795 
796