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