1 # CONFIGURATION MANAGEMENT OF EDF VERSION
2 # ======================================================================
3 # COPYRIGHT (C) 1991 - 2002 EDF R&D WWW.CODE-ASTER.ORG
4 # THIS PROGRAM IS FREE SOFTWARE; YOU CAN REDISTRIBUTE IT AND/OR MODIFY
5 # IT UNDER THE TERMS OF THE GNU GENERAL PUBLIC LICENSE AS PUBLISHED BY
6 # THE FREE SOFTWARE FOUNDATION; EITHER VERSION 2 OF THE LICENSE, OR
7 # (AT YOUR OPTION) ANY LATER VERSION.
9 # THIS PROGRAM IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, BUT
10 # WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF
11 # MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. SEE THE GNU
12 # GENERAL PUBLIC LICENSE FOR MORE DETAILS.
14 # YOU SHOULD HAVE RECEIVED A COPY OF THE GNU GENERAL PUBLIC LICENSE
15 # ALONG WITH THIS PROGRAM; IF NOT, WRITE TO EDF R&D CODE_ASTER,
16 # 1 AVENUE DU GENERAL DE GAULLE, 92141 CLAMART CEDEX, FRANCE.
19 # ======================================================================
22 #!/tools/net/app/Python-1.5.2/bin/python1.5
24 """Translate - a first attempt at parsing my little language
26 Usage: Translate [switches] <infile> [<outfile>]
28 -stdout -- write to standard output instead of a file
29 -force -- write to the <outfile> even if it already
30 exists (overwrite any existing file)
32 -import -- import tag table from Translate_tags.py,
33 instead of using the internal table
35 -compare -- compare the imported and internal tag tables
38 -test -- use our internal test data and write to stdout
39 -pytag -- use the interpreted tagging engine
40 -debug -- if -pytag, enable its debugger
41 -diag -- enable general debugging
42 Beware that this currently also writes line
43 numbers to the start of each line in the output,
44 so it doesn't emit legal Python...
46 -help -- show this text
47 -history -- show the module history
48 -version -- show the module version
50 If <outfile> is not specified, <infile> will be used with its extension
54 __author__ = """Tibs (Tony J Ibbs)
55 tony@lsl.co.uk or tibs@tibsnjoan.demon.co.uk or tibs@bigfoot.com
56 http://www.tibsnjoan.demon.co.uk/
58 __version__ = "0.3 (tiepin) of 1999-11-15"
60 Originally created 1999-08-13
62 First released version is 0.2 (bootstrap)/1999-09-09, which gave
63 an idea of how the thing would work, and nearly did.
65 Second released version is 0.3 (tiepin)/1999-11-15, which is sufficient
66 to allow the parser used within this utility to be written in the little
67 language, translated and then used as such.
74 # ............................................................
75 # How we want to work things - this is fudge for me in initial development
76 if os.name == "posix":
82 TEXTTOOLS_PATH = "C:\\Program Files\\Python"
83 PYTAG_PATH = "C:\\Program Files\\Python\\TextTools\\Examples"
87 if TEXTTOOLS_PATH not in sys.path:
88 print "Adding",TEXTTOOLS_PATH
89 sys.path.append(TEXTTOOLS_PATH)
91 if PYTAG_PATH not in sys.path:
92 print "Adding",PYTAG_PATH
93 sys.path.append(PYTAG_PATH)
94 # ............................................................
96 # Import the TextTools themselves
97 # - I'm not personally too keen on import *, but it seems to be
98 # the recommended thing, so I'll leave it for now...
100 from TextTools import *
102 from mx.TextTools import *
103 #from TextTools.Constants.TagTables import *
104 #from TextTools.Constants.Sets import *
107 # ------------------------------------------------------------
108 # Useful little constants for unpicking the parsed tuples
114 # We want to align inline comments when possible - so this is
115 # the column at which we will try to place their "#" marks...
118 # Are we (generally) debugging?
121 # Do we want a comma after the last tuple (or item) in a table?
125 # ------------------------------------------------------------
126 def define_tagtable():
127 """Returns our tag table, if we're not importing it."""
129 # We are not, initially, going to try for anything very sophisticated
130 # - just something that will get us bootstrapped, so that I can use the
131 # "little language" to write more sophisticated stuff (without having
132 # to worry about dropped commas between tuples, and so on!)
135 # Whitespace is always useful
136 t_whitespace = (None,AllIn,' \t')
137 t_opt_whitespace = t_whitespace + (+1,)
139 # Comments are fairly simple
140 t_comment = ('comment',Table,
142 (None,AllNotIn,'\n\r',MatchOk))
145 # We care about the "content" of the indentation at the start of a line,
146 # but note that it is optional
147 t_indent = ('indent',AllIn,' \t')
148 t_indentation = t_indent + (+1,) # zero indentation doesn't show
150 # A string is text within single or double quotes
151 # (of course, this is an oversimplification, because we should also
152 # deal with things like "This is a \"substring\"", and it would be
153 # nice to be able to cope with triple-quoted strings too, but it
154 # will do for a start)
156 # Major bug - doesn't recognised zero length strings...
157 # (since "AllNotIn" must match at least one character)
158 t_string = ('str',Table,
159 ((None,Is,"'",+3,+1),
160 ('text',AllNotIn,"'"),
161 (None,Is,"'",MatchFail,MatchOk),
163 ('text',AllNotIn,'"'),
167 # An integer is a series of digits...
168 t_integer = ('int',AllIn,number)
170 t_signed_integer = ('signed_int',Table,
171 (('sign',Is,"+",+1,+2),
172 ('sign',Is,"-",+1,+1),
176 # Remember to be careful to specify the LONGEST possible match first,
177 # so that we try for "IsIn" before we try for "Is" (because "IsIn"
178 # would *match* "Is", leaving us with a spurious "In" hanging around...)
179 t_operation = ('op',Table,
180 (('op',Word,"AllInSet", +1,MatchOk),
181 ('op',Word,"AllIn", +1,MatchOk),
182 ('op',Word,"AllNotIn", +1,MatchOk),
183 ('op',Word,"CallArg", +1,MatchOk),
184 ('op',Word,"Call", +1,MatchOk),
185 ('op',Word,"EOF", +1,MatchOk),
186 ('op',Word,"Fail", +1,MatchOk),
187 ('op',Word,"IsInSet", +1,MatchOk),
188 ('op',Word,"IsIn", +1,MatchOk),
189 ('op',Word,"IsNotIn", +1,MatchOk),
190 ('op',Word,"IsNot", +1,MatchOk),
191 ('op',Word,"Is", +1,MatchOk),
192 ('op',Word,"Jump", +1,MatchOk),
193 ('op',Word,"LoopControl",+1,MatchOk),
194 ('op',Word,"Loop", +1,MatchOk),
195 ('op',Word,"Move", +1,MatchOk),
196 ('op',Word,"NoWord", +1,MatchOk), # alias for WordStart
197 ('op',Word,"Skip", +1,MatchOk),
198 ('op',Word,"SubTableInList",+1,MatchOk),
199 ('op',Word,"SubTable", +1,MatchOk),
200 ('op',Word,"sFindWord", +1,MatchOk),
201 ('op',Word,"sWordStart", +1,MatchOk),
202 ('op',Word,"sWordEnd", +1,MatchOk),
203 ('op',Word,"TableInList",+1,MatchOk),
204 ('op',Word,"Table", +1,MatchOk),
205 ('op',Word,"WordStart", +1,MatchOk),
206 ('op',Word,"WordEnd", +1,MatchOk),
207 ('op',Word,"Word", MatchFail,MatchOk),
211 t_keyword = ('keyword',Table,
212 ((None,Word,"and", +1,+28),
213 (None,Word,"assert", +1,+27),
214 (None,Word,"break", +1,+26),
215 (None,Word,"class", +1,+25),
216 (None,Word,"continue",+1,+24),
217 (None,Word,"def", +1,+23),
218 (None,Word,"del", +1,+22),
219 (None,Word,"elif", +1,+21),
220 (None,Word,"else", +1,+20),
221 (None,Word,"except", +1,+19),
222 (None,Word,"exec", +1,+18),
223 (None,Word,"finally", +1,+17),
224 (None,Word,"for", +1,+16),
225 (None,Word,"from", +1,+15),
226 (None,Word,"global", +1,+14),
227 (None,Word,"if", +1,+13),
228 (None,Word,"import", +1,+12),
229 (None,Word,"in", +1,+11),
230 (None,Word,"is", +1,+10),
231 (None,Word,"lambda", +1,+9),
232 (None,Word,"not", +1,+8),
233 (None,Word,"or", +1,+7),
234 (None,Word,"pass", +1,+6),
235 (None,Word,"print", +1,+5),
236 (None,Word,"raise", +1,+4),
237 (None,Word,"return", +1,+3),
238 (None,Word,"try", +1,+2),
239 (None,Word,"while", MatchFail,+1),
240 # In order to not recognise things like "in_THIS_CASE"
241 # we must check that the next character is not legitimate
242 # within an identifier
243 (None,IsIn,alpha+'_'+number,+1,MatchFail),
244 # If it wasn't another identifier character, we need to
245 # unread it so that it can be recognised as something else
246 # (so that, for instance, "else:" is seen as "else" followed
251 # Do the same for mxText commands
252 t_mxkeyword = ('mxKeyword',Table,
254 (None,IsIn,alpha+'_'+number,+1,MatchFail),
258 # Traditional identifiers
259 t_identifier = ('identifier',Table,
260 (t_keyword + (+1,MatchFail), # don't allow Python keywords
261 t_mxkeyword + (+1,MatchFail), # don't allow mxText commands
262 (None,IsIn,alpha+'_'), # can't start with a digit
263 (None,AllIn,alpha+'_'+number,MatchOk))
266 # We don't yet deal with the following with anything in parentheses,
267 # which means we can't handle functions or command lists, or other
268 # things which "look like" a tuple
269 t_argument = ('arg',Table,
270 (('arg',Word,"Here", +1,MatchOk), # EOF Here, Fail Here
271 ('arg',Word,"ToEOF", +1,MatchOk), # Move ToEOF
272 ('arg',Word,"To", +1,MatchOk), # Jump To
273 ('arg',Word,"ThisTable",+1,MatchOk), # [Sub]Table ThisTable
274 ('arg',Word,"back", +1,MatchOk), # Skip back
275 ('arg',Word,"Break", +1,MatchOk), # LoopControl Break
276 ('arg',Word,"Reset", +1,MatchOk), # LoopControl Reset
277 t_string + (+1,MatchOk), # e.g., Word "Fred"
278 t_signed_integer + (+1,MatchOk), # e.g., Skip -4, Move 3
279 t_identifier # e.g., Table Fred
282 t_plus = ('plus',Table,
288 # Arguments can contain "+"
289 t_plus_arg = ('plusarg',Table,
290 (t_argument, # start with a single argument
291 t_plus + (MatchOk,), # if we have a "+"
292 t_argument, # then we expect another argument
293 (None,Jump,To,-2), # then look for another "+"
296 # Match, for example:
298 t_label = ('label',Table,
304 # Targets for Jump and F:/T:
305 t_target = ('target',Table,
306 (('tgt',Word,"next", +1,MatchOk),
307 ('tgt',Word,"previous", +1,MatchOk),
308 ('tgt',Word,"repeat", +1,MatchOk),
309 ('tgt',Word,"MatchOk", +1,MatchOk),
310 ('tgt',Word,"MatchOK", +1,MatchOk), # For kindness sake
311 ('tgt',Word,"MatchFail",+1,MatchOk),
315 # A value is either an identifier, or a string, or an integer
316 t_value = ('val',Table,
317 (t_identifier +(+1,MatchOk),
318 t_string +(+1,MatchOk),
322 # An assignment is (optionally) used in Tuple and Table definitions...
323 t_assignment = ('assignment',Table,
329 # A common error when writing tuples is to miss off the "=" sign
330 # - the following is used in diagnosing that (see t_bad_tuple below)
331 # (it's useful to have something with identical structure to the
333 t_bad_tagobj = ('tagobj',Table,
337 t_bad_assignment = ('assignment',Table,
341 # This is the line that starts the definition of a single tuple.
342 # For the moment, restrict what it gets assigned to to a simple identifier.
343 # Match, for example:
345 t_tupleblock = ('tupleblock',Table,
351 # This is the line that starts a new table or sub-table.
352 # For the moment, we only cope with full Tables.
353 # NOTE that this is used for the "outer" declaration of a tag table,
354 # and also for the "inner" declaration of an inner table or sub-table.
355 # The discrimination between these is done after initial parsing.
356 # Match, for example:
357 # 'keyword' = Table is: (inner)
358 # tagtable = Table is: (outer)
359 t_tableblock = ('tableblock',Table,
360 (t_assignment + (+2,+1), # left hand side is optional
362 ('type',Word,"Table",+1,+2), # Either "Table"
363 ('type',Word,"SubTable"), # or "SubTable" is required
364 t_whitespace, # whitespace is required
365 (None,Word,"is:") # "is:" is required
368 # This is the line that starts an "if" block
369 # Match, for example:
372 t_ifblock = ('ifblock',Table,
373 (t_assignment + (+2,+1), # left hand side is optional
375 t_operation + (+4,+1),
378 (None,Is,":",MatchFail,MatchOk),
383 # Note that we don't allow spaces WITHIN our false and true thingies
385 t_onfalse = ('onfalse',Table,
391 t_ontrue = ('ontrue',Table,
397 # Valid examples are things like:
398 # 'fred' = Is "xxx" F:<wow> T:MatchOk
399 # AllIn jim T:<foundJim>
401 # For the moment, we're not trying to recognise things in any detail
402 t_tuple = ('tuple',Table,
403 (t_assignment + (+2,+1), # left hand side is optional
405 t_operation, # operation is required
406 t_whitespace, # for the moment, we always require space here
407 t_plus_arg, # argument is required
408 t_onfalse + (+1,+1), # F:target is optional
409 t_ontrue + (MatchOk,MatchOk) # T:target is also optional
412 # If the user has defined a "partial" tuple, they might use something
414 # match_fred F:MatchFalse T:MatchOk
415 t_tupleplus = ('tupleplus',Table,
417 t_onfalse + (+1,+1), # F:target is optional
418 t_ontrue + (MatchOk,MatchOk) # T:target is also optional
421 # Treat Jump To specially - for example:
423 # so that they don't have to do the less obvious "Jump To F:<label>"
424 # (although that will still be recognised, of course, for people who
425 # are used to the tag tuple format itself)
426 t_jumpto = ('jumpto',Table,
434 # Is it worth coping with these?
435 t_bad_jumpto = ('jumpto',Table,
436 ((None,Word,"Jump",+2), # cope with Jump to
437 (None,Word,"to",MatchFail,+2),
438 (None,Word,"JumpTo"), # and with JumpTo
442 # The "content" of a line is the bit after any indentation, and before
444 # For the moment, we won't try to maintain ANY context, so it is up to the
445 # user of the tuples produced to see if they make sense...
446 t_content = ('content',Table,
447 (t_label + (+1,MatchOk),
448 t_tableblock + (+1,MatchOk), # [<value> =] [Sub]Table is:
449 t_tupleblock + (+1,MatchOk), # <identifier> is:
450 t_ifblock + (+1,MatchOk), # <cmd> <arg>: OR <identifier>:
451 t_jumpto + (+1,MatchOk), # Jump To <target>
452 t_tuple + (+1,MatchOk),
453 t_tupleplus + (+1,MatchOk), # name [F:<label> [T:<label>]]
456 t_contentline = ('contentline',Table,
457 (t_content, # something that we care about
459 t_comment +(+1,+1), # always allow a comment
460 (None,IsIn,newline) # the end of the line
463 # Sometimes, the user (e.g., me) writes:
467 # Unfortunately, without the "is", it would get too confusing whether
468 # we actually wanted an if block...
469 t_bad_tableblock = ('tableblock',Table,
470 (t_assignment + (+2,+1), # left hand side is optional
472 (None,Word,"Table"), # "Table" is required
473 (None,Is,":") # "is" is needed before the ":"
476 # Sometimes, the use (e.g., me again) write:
480 # Whilst I'm not entirely convinced that "=" is the best character
481 # to use here, I think we do need something!
482 t_bad_tuple = ('tuple',Table,
483 (t_bad_assignment, # obviously we have to have this!
484 t_whitespace, # in which case the whitespace IS needed
485 t_operation, # operation is required
486 t_whitespace, # for the moment, we must have space here
487 t_plus_arg, # argument is required
488 t_onfalse + (+1,+1), # F:target is optional
489 t_ontrue + (MatchOk,MatchOk) # T:target is also optional
492 # Make some attempt to recognise common errors...
493 t_badcontent = ('badcontent',Table,
494 (t_bad_tableblock +(+1,MatchOk),
498 t_badline = ('badline',Table,
499 (t_badcontent, # something that we sort of care about
501 t_comment +(+1,+1), # always allow a comment
502 (None,IsIn,newline) # the end of the line
505 t_emptyline = ('emptyline',Table,
507 (None,IsIn,newline) # the end of the line
510 t_commentline = ('commentline',Table,
512 (None,IsIn,newline) # the end of the line
515 t_passthruline = ('passthruline',Table,
516 (('passthru',AllNotIn,newline,+1), # owt else on the line
517 (None,IsIn,newline) # the end of the line
520 # Basically, a file is a series of lines
521 t_line = ('line',Table,
522 (t_emptyline +(+1,MatchOk), # empty lines are simple enough
523 t_indent +(+1,+1), # optional indentation
524 t_commentline +(+1,MatchOk), # always allow a comment
525 t_contentline +(+1,MatchOk), # a line we care about
526 t_badline +(+1,MatchOk), # a line we think is wrong
527 t_passthruline # a line we don't care about
537 # ------------------------------------------------------------
538 # We'll define some moderately interesting test data
541 # This example isn't *meant* to make any sense!
542 # It's just an accumulation of things that got checked for various reasons
543 from TextTools import *
552 # And the rest is our business...
554 'int' = AllIn '0123456789'
558 # A comment here is OK
559 <label> # Strangely enough, so is a label
560 'indent' = AllIn ' \t'
562 'int' AllIn number # BUGGY LINE (missing "=")
563 (None,"AllIn",number) # BUGGY LINE (an actual tuple)
564 fred = jim # BUGGY LINE (not our business)
565 tagobj F:<op> T:next # label <op> is undefined
566 # The next line is totally empty
568 # The next line contains just indentation
570 # This line is just a comment
571 # And this comment should be JUST after the preceding block...
572 t_indentation is: # This should be "= Table is:"
576 t_indent F:previous T:previous
580 'this' = Table ThisTable
586 'a' = AllIn 'a' F:previous
587 'a' = AllIn 'a' T:previous
588 'a' = AllIn 'a' F:previous T:previous
590 AllIn 'xyz' F:<later> T:<top>
597 AllIn number+"_"+alpha
602 # ------------------------------------------------------------
605 class OutsideError(Exception):
606 """The entity is not permitted outside a block."""
609 class IndentError(Exception):
610 """An indentation error has been detected."""
613 class NoIdentifier(Exception):
614 """We're missing an identifier (to assign to)."""
618 # ------------------------------------------------------------
619 def LineFactory(lineno,tagtuple,text):
620 """Take some tagged data and return an appropriate line class.
622 lineno -- the line number in the "file". Note that the first line
623 in the file is line 1
624 tagtuple -- a tag tuple for a single line of data
625 text -- the text for the "file". All the "left" and "right" offsets
626 are relative to this text (i.e., it is the entire content
629 The tag tuples we get back from the parser will be of the form:
632 ('indent',left,right,None), -- this is optional
633 ('content',left,right,[<data>])
636 Looking at <type> should enable us to decide what to do with
640 # Extract the list of tuples from this 'line'
641 tuples = tagtuple[SUBLIST]
643 # First off, do we have any indentation?
645 if tup[OBJECT] == "indent":
646 # This is inefficient, because it actually copies strings
647 # around - better would be to duplicate the calculation
648 # that string.expandtabs does internally...
649 indent_str = string.expandtabs(text[tup[LEFT]:tup[RIGHT]])
655 # Now, work out which class we want an instance of
656 # (this is the 'fun' bit)
658 type = tuples[0][OBJECT]
659 if type == 'emptyline':
660 return EmptyLine(lineno,indent_str,tuples[0],text)
661 elif type == 'commentline':
662 return CommentLine(lineno,indent_str,tuples[0],text)
663 elif type == 'passthruline':
664 return PassThruLine(lineno,indent_str,tuples[0],text)
665 elif type == 'contentline':
666 # OK - we need to go down another level
667 sublist = tuples[0][SUBLIST]
669 # Do we also have an in-line comment?
675 # And the actual DATA for our line is down yet another level...
676 sublist = sublist[0][SUBLIST]
677 type = sublist[0][OBJECT]
679 return LabelLine(lineno,indent_str,sublist[0],comment,text)
680 elif type == 'tableblock':
681 return TableBlockLine(lineno,indent_str,sublist[0],comment,text)
682 elif type == 'tupleblock':
683 return TupleBlockLine(lineno,indent_str,sublist[0],comment,text)
684 elif type == 'ifblock':
685 return IfBlockLine(lineno,indent_str,sublist[0],comment,text)
686 elif type == 'tuple':
687 return TupleLine(lineno,indent_str,sublist[0],comment,text)
688 elif type == 'tupleplus':
689 return TuplePlusLine(lineno,indent_str,sublist[0],comment,text)
690 elif type == 'jumpto':
691 return JumpToLine(lineno,indent_str,sublist[0],comment,text)
694 "Line %d is of unexpected type 'contentline/%s'"%(lineno,
696 elif type == 'badline':
697 # OK - we need to go down another level
698 sublist = tuples[0][SUBLIST]
700 # Do we also have an in-line comment?
706 # And the actual DATA for our line is down yet another level...
707 sublist = sublist[0][SUBLIST]
708 type = sublist[0][OBJECT]
709 if type == 'tableblock':
710 return BadTableBlockLine(lineno,indent_str,sublist[0],comment,text)
712 return BadTupleLine(lineno,indent_str,sublist[0],comment,text)
715 "Line %d is of unexpected type 'badline/%s'"%(lineno,type)
717 raise ValueError,"Line %d is of unexpected type '%s'"%(lineno,type)
721 # ------------------------------------------------------------
723 """The base class on which the various line types depend
727 tagtuple -- the tagtuple we (our subclass instance) represent(s)
728 lineno -- the line number in the file (first line is line 1)
729 indent -- our indentation (integer)
730 indent_str -- our indentation (a string of spaces)
731 text -- the text of the "file" we're within
732 class_name -- the name of the actual class this instance belongs to
733 (i.e., the name of the subclass, suitable for printing)
735 Some things only get useful values after we've been instantiated
737 next_indent -- the indentation of the next line
738 index -- for a line in a block, its index therein
741 def __init__(self,lineno,indent_str,tagtuple,text):
742 """Instantiate a BaseLine.
744 lineno -- the line number in the "file". Note that the first line
745 in the file is line 1
746 indent_str -- the indentation of the line (a string of spaces)
747 tagtuple -- the tag tuple for this line of data
748 text -- the text for the "file". All the "left" and "right"
749 offsets are relative to this text (i.e., it is the
750 entire content of the file)
752 The content of the tagtuple depends on which of our subclasses
753 is being used. Refer to the relevant doc string.
756 self.tagtuple = tagtuple
760 self.class_name = self._class_name()
761 self.indent_str = indent_str
762 self.indent = len(indent_str)
764 # OK - we don't really know! (but this will do for "EOF")
767 # We don't always HAVE a sensible value for this
771 # print "Line %3d: %s%s"%(lineno,indent_str,self.class_name)
773 def change_indent(self,count=None,spaces=""):
774 """Change our indentation.
776 Specify either "count" or "spaces" (if both are given,
777 "count" will be used, if neither is given, then the
778 indentation will be set to zero)
780 count -- the number of spaces we're indented by
781 spaces -- a string of spaces
785 self.indent_str = count * " "
787 self.indent_str = spaces
788 self.indent = len(spaces)
790 def _class_name(self):
791 """Return a representation of the class name."""
793 full_name = "%s"%self.__class__
794 bits = string.split(full_name,".")
797 def starts_block(self):
798 """Return true if we start a new block."""
801 def only_in_block(self):
802 """Return true if we can only occur inside a block."""
805 def our_business(self):
806 """Return true if we are a line we understand."""
810 return "%3d %s%-10s"%(self.lineno,self.indent_str,self.class_name)
813 """Returns a useful 'introductory' string."""
814 return "%3d %-10s %s"%(self.lineno,self.class_name,self.indent_str)
817 """Returns a "truncated" representation of our text."""
819 text = "%s %s"%(self._intro(),
820 `self.text[self.tagtuple[LEFT]:self.tagtuple[RIGHT]]`)
823 return text[:60]+"..."
827 def resolve_labels(self,block):
828 """Called to resolve any labels use in this line.
830 block -- the block that contains us
832 # The default is to do nothing as we don't HAVE any labels...
835 def expand(self,stream,block=None):
836 """Write out the expanded equivalent of ourselves.
838 stream -- an object with a "write" method, e.g., a file
839 newline -- true if we should output a terminating newline
840 block -- used to pass the containing Block down to lines
841 within a block, or None if we're not in a block
845 stream.write("Line %3d: "%self.lineno)
847 stream.write(self.indent_str)
848 stream.write(self.text[self.tagtuple[LEFT]:self.tagtuple[RIGHT]])
851 def warning(self,text):
852 """Report a warning message.
854 text -- the text to report
857 lines = string.split(text,"\n")
858 print "###WARNING: line %d (%s)"%(self.lineno,self.class_name)
862 def error(self,text):
865 text -- the error text to report
868 lines = string.split(text,"\n")
869 print "###ERROR: line %d (%s)"%(self.lineno,self.class_name)
874 # ------------------------------------------------------------
875 class EmptyLine(BaseLine):
878 Note that the indentation of an empty line is taken to be the
879 same as that of the next (non-empty) line. This is because it
880 seems to me that (a) an empty line should not per-se close a
881 block (which it would do if it had indentation 0) and (b) we
882 don't remember any whitespace in an empty line, so the user
883 can't assign an indentation themselves (which is a Good Thing!)
886 def __init__(self,lineno,indent_str,tagtuple,text):
887 """Instantiate an EmptyLine.
889 The content of the tagtuple is:
893 BaseLine.__init__(self,lineno,indent_str,tagtuple,text)
895 def expand(self,stream,block=None):
896 """Write out the expanded equivalent of ourselves.
898 stream -- an object with a "write" method, e.g., a file
899 block -- used to pass the containing Block down to lines
900 within a block, or None if we're not in a block
904 stream.write("Line %3d: "%self.lineno)
906 # um - there's nothing to do, folks
909 def our_business(self):
910 """Return true if we are a line we understand."""
914 """Returns a "truncated" representation of our text."""
919 # ------------------------------------------------------------
920 class CommentLine(BaseLine):
921 """A comment line."""
923 def __init__(self,lineno,indent_str,tagtuple,text):
924 """Instantiate a CommentLine.
926 The content of the tagtuple is:
927 ('comment',left,right,None)
928 and the demarcated text includes the initial '#' character
931 BaseLine.__init__(self,lineno,indent_str,tagtuple,text)
933 # We actually want the next tuple down (so to speak) so that
934 # we lose the trailing newline...
935 tup = self.tagtuple[SUBLIST][0]
936 self.data = self.text[tup[LEFT]:tup[RIGHT]]
938 def our_business(self):
939 """Return true if we are a line we understand."""
942 def expand(self,stream,block=None):
943 """Write out the expanded equivalent of ourselves.
945 stream -- an object with a "write" method, e.g., a file
946 block -- used to pass the containing Block down to lines
947 within a block, or None if we're not in a block
951 stream.write("Line %3d: "%self.lineno)
953 stream.write(self.indent_str)
954 stream.write("%s\n"%self.data)
957 # ------------------------------------------------------------
958 class PassThruLine(BaseLine):
959 """A line we just pass throught without interpretation."""
961 def __init__(self,lineno,indent_str,tagtuple,text):
962 """Instantiate a PassThruLine.
964 The content of the tagtuple is:
965 ('passthru',left,right,None)
968 BaseLine.__init__(self,lineno,indent_str,tagtuple,text)
970 # We actually want the next tuple down (so to speak) so that
971 # we lose the trailing newline...
972 tup = self.tagtuple[SUBLIST][0]
973 self.data = self.text[tup[LEFT]:tup[RIGHT]]
975 def our_business(self):
976 """Return true if we are a line we understand."""
979 def expand(self,stream,block=None):
980 """Write out the expanded equivalent of ourselves.
982 stream -- an object with a "write" method, e.g., a file
983 block -- used to pass the containing Block down to lines
984 within a block, or None if we're not in a block
988 stream.write("Line %3d: "%self.lineno)
991 err_str = "Unparsed line inside a block"\
992 " - it has been commented out"
993 # Hmm - the following advice is less often useful than I
994 # had hoped - leave it out for now...
995 #if string.find(self.data,",") != -1:
996 # err_str = err_str + "\nCheck for a trailing comma?"
1000 # Always output the indentation, 'cos otherwise it looks silly
1001 stream.write(self.indent_str)
1004 stream.write("#[ignored]#")
1006 stream.write("%s\n"%self.data)
1009 # ------------------------------------------------------------
1010 class ContentLine(BaseLine):
1011 """A line we have to interpret - another base class.
1013 Adds the following variables:
1015 comment -- any in-line comment on this line
1018 def __init__(self,lineno,indent_str,tagtuple,comment,text):
1019 """Instantiate a ContentLine.
1021 comment -- either a comment tuple or None
1023 The content of the tagtuple is:
1024 ('contentline',left,right,
1025 [('content',left,right,[<data>]),
1026 ('comment',left,right,None) -- optional
1028 where <data> is used in the internals of one of our subclasses
1029 (i.e., it is what is passed down in the "tagtuple" argument)
1032 BaseLine.__init__(self,lineno,indent_str,tagtuple,text)
1033 self.comment = comment
1035 # Assume we're not the last "our business" line in a block...
1038 def _write_comment(self,stream,sofar):
1039 """Write out the in-line comment string.
1041 Since we're the only people to call this, we can safely
1042 rely on it only being called when there IS a comment tuple
1045 stream -- an object with a "write" method, e.g., a file
1046 sofar -- the number of characters written to the line
1049 if sofar < COMMENT_COLUMN:
1050 stream.write(" "*(COMMENT_COLUMN - sofar))
1052 # always write at least one space...
1054 stream.write(self.text[self.comment[LEFT]:self.comment[RIGHT]])
1056 def _write_text(self,stream,block):
1057 """Write out the main tuple text.
1059 stream -- an object with a "write" method, e.g., a file
1060 block -- used to pass the containing Block down to lines
1061 within a block, or None if we're not in a block
1063 This should generally be the method that subclasses override.
1064 It returns the number of characters written, or -1 if we had
1067 stream.write(self.text[self.tagtuple[LEFT]:self.tagtuple[RIGHT]])
1068 return self.tagtuple[RIGHT] - self.tagtuple[LEFT]
1070 def expand(self,stream,block=None):
1071 """Write out the expanded equivalent of ourselves.
1073 stream -- an object with a "write" method, e.g., a file
1074 block -- used to pass the containing Block down to lines
1075 within a block, or None if we're not in a block
1079 stream.write("Line %3d: "%self.lineno)
1081 stream.write(self.indent_str)
1082 nchars = self._write_text(stream,block)
1083 # Don't write any in-line comment out if we had an error,
1084 # as the layout won't work!
1085 if nchars > -1 and self.comment:
1086 self._write_comment(stream,sofar=nchars+self.indent)
1090 # ------------------------------------------------------------
1091 class LabelLine(ContentLine):
1092 """A line containing a label.
1095 label -- our label string
1098 def __init__(self,lineno,indent_str,tagtuple,comment,text):
1099 """Instantiate a LabelLine.
1105 The content of the tagtuple is:
1107 ('label',left,right,[
1108 ('identifier',left,right,None)
1112 ContentLine.__init__(self,lineno,indent_str,tagtuple,comment,text)
1114 self.label = self.text[self.tagtuple[LEFT]:self.tagtuple[RIGHT]]
1116 def _write_text(self,stream,block):
1117 """Write out the main tuple text.
1119 stream -- an object with a "write" method, e.g., a file
1120 block -- used to pass the containing Block down to lines
1121 within a block, or None if we're not in a block
1123 # Enough difficult length calculation - let's do this one
1126 text = "# Label %s at index %d"%(self.label,self.index)
1128 text = "# %s"%(self.label) # surely enough for most people...
1132 def translate(self,index,block):
1133 """Return the translation of a use of this label as a target.
1135 index -- the index of the line which uses the label as a target
1136 block -- the Block we are within
1139 # Hmm - I don't think this CAN go wrong at this point...
1140 return block.translate_label(self.label,self)
1142 def only_in_block(self):
1143 """Return true if we can only occur inside a block."""
1147 # ------------------------------------------------------------
1148 class TableBlockLine(ContentLine):
1149 """A line starting a table block."""
1151 def __init__(self,lineno,indent_str,tagtuple,comment,text):
1152 """Instantiate a TableBlockLine.
1159 This is used for two purposes:
1160 1. To define the actual tag table itself (i.e., at the outer
1161 level). Only "Table" is allowed in this instance, but since
1162 that is all we recognised for now, we shan't worry about it...
1163 2. To define an inner table (i.e., at an inner level)
1165 The content of the tagtuple is:
1167 ('tableblock',left,right,[
1168 ('assignment',left,right,[ -- optional if inner
1171 ('identifier',left,right,[])
1174 ('text',left,right,None)
1177 ('int',left,right,[])
1181 ('type',left,right,[]) -- either "Table" or "SubTable"
1184 NOTE: as an "emergency" measure (so we can `pretend' that a
1185 TupleBlock was actually a TableBlock as part of attempted
1186 error correction), if tagtuple == ("error",tagobj) then we
1187 short-circuit some of the initialisation...
1190 ContentLine.__init__(self,lineno,indent_str,tagtuple,comment,text)
1192 if tagtuple[0] == "error":
1193 # We're "bluffing" at the creation of a TableBlock
1194 self.tagobj = tagtuple[1]
1195 self.is_subtable = 0
1196 elif len(self.tagtuple[SUBLIST]) == 1:
1197 self.tagobj = "None"
1198 tup = self.tagtuple[SUBLIST][0]
1199 self.is_subtable = (self.text[tup[LEFT]:tup[RIGHT]] == "SubTable")
1201 # The first tuple down gives us the "<value> = " string
1202 tup = self.tagtuple[SUBLIST][0]
1203 # The next tuple down gives us "<value>" which is what we want
1204 tup = tup[SUBLIST][0]
1205 self.tagobj = self.text[tup[LEFT]:tup[RIGHT]]
1206 # Then we have the type of table
1207 tup = self.tagtuple[SUBLIST][1]
1208 self.is_subtable = (self.text[tup[LEFT]:tup[RIGHT]] == "SubTable")
1210 def got_tagobj(self):
1211 return (self.tagobj != "None")
1213 def starts_block(self):
1214 """Return true if we start a new block."""
1217 def _write_text(self,stream,block):
1218 """Write out the main tuple text.
1220 stream -- an object with a "write" method, e.g., a file
1221 block -- used to pass the containing Block down to lines
1222 within a block, or None if we're not in a block
1224 It returns the number of characters written, or -1 if we had
1229 if self.is_subtable:
1230 stream.write("(%s,SubTable,("%self.tagobj)
1231 return len(self.tagobj) + 11
1233 stream.write("(%s,Table,("%self.tagobj)
1234 return len(self.tagobj) + 8
1236 stream.write("%s = ("%self.tagobj)
1237 return len(self.tagobj) + 4
1240 # ------------------------------------------------------------
1241 class TupleBlockLine(ContentLine):
1242 """A line starting a tuple block (i.e., defining a single tuple)
1246 name -- the "name" of this tuple (i.e., what comes
1250 def __init__(self,lineno,indent_str,tagtuple,comment,text):
1251 """Instantiate a TupleBlockLine.
1257 The content of the tagtuple is:
1259 ('tupleblock',left,right,[
1260 ('identifier',left,right,None)
1264 ContentLine.__init__(self,lineno,indent_str,tagtuple,comment,text)
1266 tup = self.tagtuple[SUBLIST][0]
1267 self.name = self.text[tup[LEFT]:tup[RIGHT]]
1269 def starts_block(self):
1270 """Return true if we start a new block."""
1273 def only_in_block(self):
1274 """Return true if we can only occur inside a block."""
1277 def _write_text(self,stream,block):
1278 """Write out the main tuple text.
1280 stream -- an object with a "write" method, e.g., a file
1281 block -- used to pass the containing Block down to lines
1282 within a block, or None if we're not in a block
1284 It returns the number of characters written, or -1 if we had
1287 # The "\" at the end is somewhat clumsy looking, but the
1288 # only obvious way of preserving layout...
1289 stream.write("%s = \\"%self.name)
1290 return len(self.name) + 5
1293 # ------------------------------------------------------------
1294 class IfBlockLine(ContentLine):
1295 """A line starting an if block.
1298 cmd -- the command within this if block
1299 arg -- the argument for said command
1301 name -- the name within this if block
1304 def __init__(self,lineno,indent_str,tagtuple,comment,text):
1305 """Instantiate an IfBlockLine.
1313 The content of the tagtuple is:
1315 ('ifblock',left,right,[
1316 ('assignment',left,right,[
1319 ('identifier',left,right,[])
1322 ('text',left,right,None)
1325 ('int',left,right,[])
1329 ('op',left,right,None),
1330 ('arg',left,right,None),
1333 ('ifblock',left,right,[
1334 ('op',left,right,None),
1335 ('arg',left,right,None),
1338 ('ifblock',left,right,[
1339 ('identifier',left,right,None)
1343 ContentLine.__init__(self,lineno,indent_str,tagtuple,comment,text)
1345 tuples = self.tagtuple[SUBLIST]
1346 if tuples[0][OBJECT] == 'op':
1349 self.tagobj = "None"
1350 self.cmd = self.text[tup1[LEFT]:tup1[RIGHT]]
1351 self.arg = self.text[tup2[LEFT]:tup2[RIGHT]]
1353 elif tuples[0][OBJECT] == 'assignment':
1354 # The "<value>" in the "<value> = " string is down
1355 # one level more than the others
1356 tup0 = tuples[0][SUBLIST][0]
1357 self.tagobj = self.text[tup0[LEFT]:tup0[RIGHT]]
1360 self.cmd = self.text[tup1[LEFT]:tup1[RIGHT]]
1361 self.arg = self.text[tup2[LEFT]:tup2[RIGHT]]
1363 elif tuples[0][OBJECT] == 'identifier':
1365 self.name = self.text[tup[LEFT]:tup[RIGHT]]
1370 # Hmm - try to continue with anything unexpected
1372 self.error("Unexpected IfBlock subtype %s"%tup[OBJECT])
1373 self.name = self.text[tup[LEFT]:tup[RIGHT]]
1378 # Currently, we have one 'special' argument
1379 if self.arg == "back": self.arg = "-1"
1381 # We don't yet know the offset of the "virtual label" at the
1382 # end of this if block...
1383 self.end_label = None
1385 def starts_block(self):
1386 """Return true if we start a new block."""
1389 def only_in_block(self):
1390 """Return true if we can only occur inside a block."""
1393 def resolve_labels(self,block):
1394 """Called to resolve any labels used in this line.
1396 block -- the block that contains us
1398 Note that this only does something the first time it
1399 is called - this will be when the IF block's startline
1400 is asked to resolve its labels. If it is called again,
1401 as a 'normal' line, it will do nothing...
1403 if not self.end_label:
1404 self.end_label = "%+d"%(len(block.business)+1)
1406 def _write_text(self,stream,block):
1407 """Write out the main tuple text.
1409 stream -- an object with a "write" method, e.g., a file
1410 block -- used to pass the containing Block down to lines
1411 within a block, or None if we're not in a block
1413 It returns the number of characters written, or -1 if we had
1416 if not self.end_label:
1417 # This should never happen, but just in case, warn the user!
1418 self.error("Unable to determine 'onFalse' destination in IF")
1421 stream.write("%s + (%s,+1),"%(self.name,
1422 self.end_label or "<undefined>"))
1423 return len(self.name) + 20
1425 stream.write("(%s,%s,%s,%s,+1),"%(self.tagobj,self.cmd,self.arg,
1426 self.end_label or "<undefined>"))
1427 return len(self.tagobj) + len(self.cmd) + len(self.arg) + \
1428 len(self.end_label) + 20
1431 # ------------------------------------------------------------
1432 class TupleLine(ContentLine):
1433 """A line containing a basic tuple.
1440 ontrue -- what to do if true
1441 onfalse -- ditto false
1444 def __init__(self,lineno,indent_str,tagtuple,comment,text):
1445 """Instantiate a TupleLine.
1447 The content of the tagtuple is:
1449 ('tuple',left,right,[
1450 ('tagobj',left,right,[ -- optional
1452 ('text',left,right,None)
1455 ('op',left,right,None),
1456 ('arg',left,right,None),
1457 ('onfalse',left,right,[ -- optional
1458 ('target',left,right,[
1459 ('tgt',left,right,None)
1461 ('ontrue',left,right,[ -- optional
1462 ('target',left,right,[
1463 ('tgt',left,right,None)
1469 ContentLine.__init__(self,lineno,indent_str,tagtuple,comment,text)
1475 """Unpack our contents from our tagtuple."""
1477 # This is doubtless not the most efficient way of doing this,
1478 # but it IS relatively simple...
1480 #for key in ("assignment","op","arg","onfalse","ontrue"):
1481 for key in ("assignment","op","plusarg","onfalse","ontrue"):
1484 tuples = self.tagtuple[SUBLIST]
1487 if name == "onfalse" or name == "ontrue" or name == "assignment":
1488 # For these, we need to go "down one level" for our data
1489 tup = item[SUBLIST][0]
1490 dict[name] = (tup[LEFT],tup[RIGHT])
1492 dict[name] = (item[LEFT],item[RIGHT])
1494 # The tag object is optional
1495 if dict["assignment"]:
1496 left,right = dict["assignment"]
1497 self.tagobj = self.text[left:right]
1499 self.tagobj = "None"
1501 # The operation (command) and argument are required
1502 left,right = dict["op"]
1503 self.cmd = self.text[left:right]
1505 #left,right = dict["arg"]
1506 left,right = dict["plusarg"]
1507 self.arg = self.text[left:right]
1509 # Currently, we have one 'special' argument
1510 if self.arg == "back": self.arg = "-1"
1512 # Actually, we don't want the F and T jumps explicit if not
1513 # given, since we mustn't output them for a single tuple if
1514 # they're not given (so they can be "added in" later on)
1516 left,right = dict["onfalse"]
1517 self.onfalse = self.text[left:right]
1519 self.onfalse = None # "MatchFail"
1521 left,right = dict["ontrue"]
1522 self.ontrue = self.text[left:right]
1524 self.ontrue = None # "next"
1526 def only_in_block(self):
1527 """Return true if we can only occur inside a block."""
1530 def resolve_labels(self,block):
1531 """Called to resolve any labels use in this line.
1533 block -- the block that contains us
1536 self.onfalse = block.translate_label(self.onfalse,self)
1538 self.ontrue = block.translate_label(self.ontrue,self)
1540 def _write_text(self,stream,block):
1541 """Write out the main tuple text.
1543 stream -- an object with a "write" method, e.g., a file
1544 block -- used to pass the containing Block down to lines
1545 within a block, or None if we're not in a block
1547 It returns the number of characters written, or -1 if we had
1551 # Start with the stuff we must have...
1552 stream.write("(%s,%s,%s"%(self.tagobj,self.cmd,self.arg))
1553 length = len(self.tagobj) + len(self.cmd) + len(self.arg) + 3
1556 if not self.onfalse:
1557 # OK, we didn't get an explicit F, but because it comes
1558 # before the T jump in the tuple, we need to fake it
1560 stream.write(",%s,%s)"%("MatchFail",self.ontrue))
1561 length = length + len("MatchFail") + len(self.ontrue) + 3
1563 # We had both F and T
1564 stream.write(",%s,%s)"%(self.onfalse,self.ontrue))
1565 length = length + len(self.onfalse) + len(self.ontrue) + 3
1567 # We only had F. We shan't "fake" the T jump, *just* in case
1568 # the user is defining a single tuple that they'll add the
1569 # T jump to later on (although that *is* a bit dodgy, I think)
1570 # [[The option would be to "fake" it if we're IN a block - I may
1571 # go for that approach later on]]
1572 stream.write(",%s)"%self.onfalse)
1573 length = length + len(self.onfalse) + 2
1575 # Neither F nor T - so don't write the defaults for either,
1576 # in case this is a top level tuple they're going to add to
1578 # [[Comments as for the case above, I think]]
1582 if block and not self.is_last:
1588 # ------------------------------------------------------------
1589 class TuplePlusLine(ContentLine):
1590 """A line containing a tuple "plus" (e.g., "fred + (+1,+1)").
1594 name -- the name/identifier
1595 ontrue -- what to do if true
1596 onfalse -- ditto false
1599 def __init__(self,lineno,indent_str,tagtuple,comment,text):
1600 """Instantiate a TuplePlusLine.
1602 <identifier> + (onF,onT)
1604 The content of the tagtuple is:
1606 ('tupleplus',left,right,[
1607 ('identifier',left,right,None)
1608 ('onfalse',left,right,[ -- optional
1609 ('target',left,right,[
1610 ('tgt',left,right,None)
1612 ('ontrue',left,right,[ -- optional
1613 ('target',left,right,[
1614 ('tgt',left,right,None)
1620 ContentLine.__init__(self,lineno,indent_str,tagtuple,comment,text)
1626 """Unpack our contents from our tagtuple."""
1628 # This is doubtless not the most efficient way of doing this,
1629 # but it IS relatively simple...
1631 for key in ("identifier","onfalse","ontrue"):
1634 tuples = self.tagtuple[SUBLIST]
1637 if name == "onfalse" or name == "ontrue":
1638 # For these, we need to go "down one level" for our data
1639 tup = item[SUBLIST][0]
1640 dict[name] = (tup[LEFT],tup[RIGHT])
1642 dict[name] = (item[LEFT],item[RIGHT])
1644 # Start with the identifier
1645 left,right = dict["identifier"]
1646 self.name = self.text[left:right]
1648 # Actually, we don't want the F and T jumps explicit if not
1649 # given, since we mustn't output them for a single tuple if
1650 # they're not given (so they can be "added in" later on)
1652 left,right = dict["onfalse"]
1653 self.onfalse = self.text[left:right]
1655 self.onfalse = None # "MatchFail"
1657 left,right = dict["ontrue"]
1658 self.ontrue = self.text[left:right]
1660 self.ontrue = None # "next"
1662 def only_in_block(self):
1663 """Return true if we can only occur inside a block."""
1666 def resolve_labels(self,block):
1667 """Called to resolve any labels use in this line.
1669 block -- the block that contains us
1672 self.onfalse = block.translate_label(self.onfalse,self)
1674 self.ontrue = block.translate_label(self.ontrue,self)
1676 def _write_text(self,stream,block):
1677 """Write out the main tuple text.
1679 stream -- an object with a "write" method, e.g., a file
1680 block -- used to pass the containing Block down to lines
1681 within a block, or None if we're not in a block
1683 It returns the number of characters written, or -1 if we had
1687 if not self.onfalse and not self.ontrue:
1688 stream.write("%s"%self.name)
1689 length = len(self.name)
1691 # Make a feeble attempt to cause successive such lines to
1692 # look neater, by aligning the "+" signs (if we output them)
1693 stream.write("%-15s + ("%(self.name))
1694 length = max(len(self.name),15) + 4
1695 if self.ontrue and self.onfalse:
1696 stream.write("%s,%s)"%(self.onfalse,self.ontrue))
1697 length = length + len(self.onfalse) + len(self.ontrue) + 2
1699 stream.write("MatchFail,%s)"%(self.ontrue))
1700 length = length + len(self.ontrue) + 11
1702 # Don't forget that comma to make this a tuple!
1703 stream.write("%s,)"%(self.onfalse))
1704 length = length + len(self.onfalse) + 1
1706 if not self.is_last:
1713 # ------------------------------------------------------------
1714 class JumpToLine(ContentLine):
1715 """A line containing "Jump To <label>"
1719 name -- the name/identifier
1720 onfalse -- the target (which is technically an "on false" jump)
1723 def __init__(self,lineno,indent_str,tagtuple,comment,text):
1724 """Instantiate a JumpLine.
1728 The content of the tagtuple is:
1730 ('jumpto',left,right,[
1731 ('target',left,right,[
1732 ('tgt',left,right,None)
1737 ContentLine.__init__(self,lineno,indent_str,tagtuple,comment,text)
1739 tup = self.tagtuple[SUBLIST][0]
1740 self.onfalse = self.text[tup[LEFT]:tup[RIGHT]]
1742 def only_in_block(self):
1743 """Return true if we can only occur inside a block."""
1746 def resolve_labels(self,block):
1747 """Called to resolve any labels use in this line.
1749 block -- the block that contains us
1751 self.onfalse = block.translate_label(self.onfalse,self)
1753 def _write_text(self,stream,block):
1754 """Write out the main tuple text.
1756 stream -- an object with a "write" method, e.g., a file
1757 block -- used to pass the containing Block down to lines
1758 within a block, or None if we're not in a block
1760 It returns the number of characters written, or -1 if we had
1764 stream.write("(None,Jump,To,%s)"%(self.onfalse))
1765 length = len(self.onfalse) + 15
1767 if not self.is_last:
1774 # ------------------------------------------------------------
1775 class BadTableBlockLine(TableBlockLine):
1776 """We think they MEANT this to be a table block line."""
1778 def __init__(self,lineno,indent_str,tagtuple,comment,text):
1779 """Instantiate a BadTableBlockLine.
1786 TableBlockLine.__init__(self,lineno,indent_str,tagtuple,comment,text)
1787 self.error("Suspected missing 'is' before the colon\n"
1788 "pretending it's there")
1791 # ------------------------------------------------------------
1792 class BadTupleLine(TupleLine):
1793 """We think they MEANT this to be a tuple line."""
1795 def __init__(self,lineno,indent_str,tagtuple,comment,text):
1796 """Instantiate a BadTupleLine.
1802 TupleLine.__init__(self,lineno,indent_str,tagtuple,comment,text)
1803 self.error("Suspected missing '=' between tag object and command\n"
1804 "pretending it's there")
1807 # ------------------------------------------------------------
1808 class Block(ContentLine):
1809 """This class represents a "block".
1811 A "block" is a section of code which starts with a line ending in
1812 a colon (":"), with the next line and subsequent lines ("in" the
1813 block) having an extra indent. The block ends when a dedent is
1816 Each instance "eats" lines from the input until (if) it finds the first
1817 "sub" block. That then "eats" lines until it finds its own end, and
1818 then hands control back to the first instance, which does the same thing
1821 Note that we "pretend" to be a content line - it is convenient to
1822 look like a line class, so that line processing can cope with us,
1823 and indeed what we do is "pretend" to be a clone of our start line
1824 with some extra information...
1827 startline -- the line that "introduces" this block
1828 items -- a list of the lines and blocks within this block
1829 label_dict -- a dictionary of {label name : line index}
1830 inner_indent -- the indentation of our "inner" lines
1831 outer -- true if we are an "outer" block
1832 (i.e., not contained within another block)
1835 def __init__(self,startline=None,outer=0,file=None):
1836 """Instantiate a new block.
1838 startline -- the line that introduces this block
1839 outer -- true if we are an outer block
1840 file -- the "file" we're reading lines from
1843 # Pretend to be our own startline (as a generic)
1844 ContentLine.__init__(self,
1845 startline.lineno,startline.indent_str,
1846 startline.tagtuple,startline.comment,
1849 # But also remember the specifics of the startline
1850 self.startline = startline
1852 # We "fudge" our class name
1853 self.class_name = self._block_class_name(startline)
1858 # If we're an outer table block, do we have a tagobj?
1859 if self.startline.class_name == "TableBlockLine" and outer:
1860 if not self.startline.got_tagobj():
1861 raise NoIdentifier,\
1862 "Tag table at line %d is not assigned to a variable"%\
1864 elif self.startline.is_subtable:
1865 raise OutsideError,\
1866 "SubTable is not allowed outside a block at line %d"%\
1869 self.items = [] # all lines within this block
1870 self.business = [] # just those that are "our business"
1871 self.label_dict = {} # remember our labels and their locations
1872 self.next_index = 0 # 'business' line indices
1873 self.inner_indent = None
1875 # Eat lines until we reach the end of our block...
1876 if DEBUGGING: print "%sStart %s"%(self.indent_str,self.class_name)
1880 def _block_class_name(self,startline):
1881 """Return a representation of the class name."""
1883 full_name = "%s"%self.__class__
1884 bits = string.split(full_name,".")
1885 return "%s/%s"%(bits[-1],startline.class_name)
1887 def _eat_lines(self):
1888 """Eat lines until we run out of block..."""
1892 nextline = self.file.next()
1896 # Check the indentation makes sense...
1897 if self.inner_indent:
1898 # We already know how much our block is indented
1899 # - is this line part of the block?
1900 if nextline.indent < self.inner_indent:
1901 # Apparently a dedent - is it what we expect?
1902 if nextline.indent <= self.indent:
1903 # Unread that line - it isn't one of ours!
1908 "Line %d (%s) is indented less than the previous "\
1909 "line, but its indentation doesn't match the "\
1910 "start of the block at line %d"%\
1911 (nextline.lineno,nextline.class_name,self.lineno)
1912 elif nextline.indent > self.inner_indent:
1914 # (note that doing this stops us from coping with,
1915 # for instance, things in (..), but then we also don't
1916 # cope with any form of continued line, or lots of other
1917 # things, so let's not worry too much for now!)
1919 "Line %d (%s) is indented more than the previous line"%\
1920 (nextline.lineno,nextline.class_name)
1922 # This is the first line of the (inside of) the block
1923 # - check its indentation makes sense...
1924 self.inner_indent = nextline.indent
1925 if self.inner_indent <= self.indent:
1927 "Line %d (%s) should be indented more than line %d (%s)"%\
1928 (nextline.lineno,nextline.class_name,
1929 self.lineno,self.startline.class_name)
1931 # Is it a line or the start of another block?
1932 if nextline.starts_block():
1933 # Heh - it's the start of an inner block - add it
1934 # (remember that instantiating it causes it to
1935 # "eat" the lines that belong to it)
1936 self.items.append(Block(startline=nextline,
1937 outer=0,file=self.file))
1939 self.items.append(nextline)
1941 def _end_block(self):
1944 if DEBUGGING: print "%sEnd %s"%(self.indent_str,self.class_name)
1946 # If we're a tuple block, we should only have one line...
1947 # (that is, one "business" line)
1948 if self.startline.class_name == "TupleBlockLine" and \
1949 len(self.items) > 1:
1950 # Are all but one of them not "our business"?
1952 for item in self.items:
1953 if item.our_business():
1957 self.error("Tuple declaration can only contain one 'business'"
1959 "Assuming it's a table instead (i.e.,"
1960 "'Table is:' instead of 'is:')"%len(self.items))
1961 # Can we correct this by "pretending" its a table?
1962 temp = TableBlockLine(self.startline.lineno,
1963 self.startline.indent_str,
1964 ("error",self.startline.name),
1965 self.startline.comment,
1967 self.startline = temp
1969 # We've now got all of our lines, and so we can go back over
1970 # them, expanding out any IF blocks (whose content is actually
1971 # within this block's scope, so who need to have their labels
1972 # (come from or go to) in that scope), working out the label
1973 # indices, and so on...
1974 # This uses "next_index" to calculate the indices of business
1975 # lines (needed for label calculation), and also populates the
1976 # "business" list with just the items that are "our_business()"
1978 print "Expanding IF blocks, sorting out labels, etc."
1983 if item.class_name == "Block/IfBlockLine":
1984 self._add(item.startline)
1985 for thing in item.items:
1990 # Go back through our contents and resolve any labels
1992 print "%s...processing labels (next_index=%d)"%(self.indent_str,
1994 self.startline.resolve_labels(self)
1995 # If we're an IF block, we mustn't try to resolve our component
1996 # lines' labels, as they're actually in our parent block's scope...
1997 if self.startline.class_name != "IfBlockLine":
1998 for item in self.items:
1999 item.resolve_labels(self)
2001 # If we're in a block that wants to suppress the comma at the
2002 # end of the last item in that block, tell the last item so...
2003 # (this is debatable for [Bad]TableBlockLine - it might be
2004 # better to leave the last comma there - so we have an option
2005 # to determine it...
2006 if self.startline.class_name == "TupleBlockLine" or \
2007 (not WANT_LAST_COMMA and \
2008 (self.startline.class_name == "TableBlockLine" or \
2009 self.startline.class_name == "BadTableBlockLine")):
2010 if len(self.business) > 0:
2011 self.business[-1].is_last = 1
2013 def _add(self,item):
2014 """Add a line or block to our list of items.
2016 item -- the Line or Block instance to add
2018 NB: Also adds it to our "business" list if it is our business
2022 if item.class_name == "LabelLine":
2023 self.label_dict[item.label] = self.next_index
2025 print "%sadd [%2d] %s"%(item.indent_str,self.next_index,item)
2026 # Might as well give it the index it is labelling
2027 item.index = self.next_index
2028 self.items.append(item)
2029 elif item.our_business():
2030 item.index = self.next_index
2031 self.items.append(item)
2032 self.business.append(item)
2034 print "%sadd %2d %s"%(item.indent_str,
2035 self.next_index,item)
2036 self.next_index = self.next_index + 1
2038 # It's not something we can assign a sensible index to, so don't
2040 print "%sadd xx %s"%(item.indent_str,item)
2041 self.items.append(item)
2043 def translate_label(self,label,line):
2044 """Given a label, return its translation.
2046 label -- either a string of the form "<...>" to look up in
2047 this block's label dictionary, or one of the special
2048 targets (e.g., next, MatchOk, etc.)
2049 line -- the line using this label
2051 Reports an error and just returns the original "label" if it
2054 if self.label_dict.has_key(label):
2055 # How far do we have to jump?
2056 offset = self.label_dict[label] - line.index
2058 elif label == "MatchOk":
2060 elif label == "MatchOK":
2061 line.warning("Label 'MatchOK' should be spelt 'MatchOk'"
2062 " (using 'MatchOk')")
2064 elif label == "MatchFail":
2066 elif label == "next":
2068 elif label == "previous":
2070 elif label == "repeat":
2073 line.error("Undefined label '%s'"%label)
2076 def expand(self,stream,block=None):
2077 """Write out the expanded equivalent of ourselves.
2079 stream -- an object with a "write" method, e.g., a file
2080 block -- if we're in a block, this is it, otherwise None
2083 self.startline.expand(stream,block=block)
2084 for item in self.items[:-1]:
2085 item.expand(stream,block=self)
2087 self.items[-1].expand(stream,block=self)
2089 # Deal with closing any block parentheses
2090 if self.startline.class_name == "TableBlockLine" or \
2091 self.startline.class_name == "BadTableBlockLine":
2093 stream.write("Line ...: ")
2095 stream.write(self.indent_str)
2097 # Outer block - just close it
2100 # Inner block is a Table block, and we need to close both
2101 # the tuple-of-tuples, and also the tuple containing the
2104 if not self.is_last:
2109 # ------------------------------------------------------------
2111 """This is the class that holds our processed data
2114 lines -- a list of the line instances for each "line" in our text
2115 items -- a list of lines and BLOCKs
2118 def __init__(self,tagtuples,text):
2119 """Instantiate a File
2121 tagtuples -- the list of mxTextTools tag tuples generated by
2122 parsing the data in "text"
2123 text -- the text we parsed
2127 self.tagtuples = tagtuples
2129 # Assemble our list of lines
2130 print "Pass 1: assembling lines"
2131 if DEBUGGING: print "~~~~~~~~~~~~~~~~~~~~~~~~"
2135 for tagtuple in tagtuples:
2137 thisline = LineFactory(lineno,tagtuple,text)
2140 prevline.next_indent = thisline.indent
2142 self.lines.append(thisline)
2145 #if DEBUGGING: print
2147 # The indentation of an empty line is taken to be the same
2148 # as the indentation of the first following non-empty line
2149 # The easiest way to do that is to work backwards through
2150 # the list (is it better to take a copy and reverse THAT,
2151 # or to reverse our original list twice?)
2152 print "Pass 2: sorting out indentation of empty lines"
2153 if DEBUGGING: print "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
2154 revlist = self.lines[:]
2157 for line in revlist:
2158 if line.class_name == "EmptyLine":
2159 line.change_indent(indent)
2161 indent = line.indent
2165 print "Pass 2.5 - the contents of those lines..."
2166 print "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
2167 for line in self.lines:
2168 print "Line %d %s"%(line.lineno,line.class_name)
2169 #print_tuples([line.tagtuple],self.text," ")
2172 # Now we need to assemble blocks
2173 print "Pass 3: assembling blocks"
2174 if DEBUGGING: print "~~~~~~~~~~~~~~~~~~~~~~~~~"
2185 print "%sTOP %s"%(item.indent_str,item)
2186 if item.starts_block():
2187 block = Block(startline=item,outer=1,file=self)
2188 self.items.append(block)
2189 block.is_last = 1 # Everything at outer level is "last"
2191 if item.only_in_block():
2192 item.error("This line is not allowed outside a block "
2193 "- continuing anyway")
2194 self.items.append(item)
2195 if item.our_business():
2196 item.is_last = 1 # Everything at outer level is "last"
2202 """Ensure that the next call of "nextline" returns the first line."""
2206 """Unread the current line."""
2207 self.index = self.index - 1
2212 """Retrieve the next line from the list of lines in this "file".
2214 Raises EOFError if there is no next line (i.e., "end of file")
2216 self.index = self.index + 1
2218 return self.lines[self.index]
2220 # leave the index off the end, so we get EOF again if
2221 # we're called again - but there's no point courting overflow...
2222 self.index = self.index -1
2225 def expand(self,stream):
2226 """Expand out the result."""
2227 for item in self.items:
2231 # ------------------------------------------------------------
2232 def print_tuples(tuples,text,indent=""):
2233 """Print out a list of tuples in a neat form
2235 tuples -- our tuple list
2236 text -- the text it tags
2237 indent -- our current indentation
2240 # Tuples are of the form:
2241 # (object,left_index,right_index,sublist)
2243 for obj,left,right,sub in tuples:
2245 print "%s%s"%(indent,obj)
2246 print_tuples(sub,text,indent+" ")
2248 # Terminal node - show the actual text we've tagged!
2249 print "%s%s = %s"%(indent,obj,`text[left:right]`)
2252 # ------------------------------------------------------------
2253 def print_text(text):
2254 """Print out text with line numbers."""
2255 lines = string.split(text,"\n")
2258 print "Original text"
2259 print "============="
2262 print "%3d: %s"%(lineno,`line`)
2265 # ------------------------------------------------------------
2266 def print_usage(argv0):
2267 #script_name = string.split(argv0, os.sep)[-1]
2268 #print __doc__%(script_name)
2273 # ------------------------------------------------------------
2274 def show_tup(indent,nn,tup):
2277 if type(item) == type((1,)) or type(item) == type([]):
2283 print "%s%d: (%s)"%(indent,nn,string.join(ll,","))
2285 print "%s(%s)"%(indent,string.join(ll,","))
2287 def comp_sub(indent,one,two):
2289 if len(two) != len(one):
2290 print "%sTuple lengths differ - 1:%d, 2:%d"%(indent,len1,len(two))
2291 show_tup(indent,1,one)
2292 show_tup(indent,2,two)
2293 # If this is all, let's try to continue...
2294 len1 = min(len1,len(two))
2296 for count in range(len1):
2299 if type(a) != type(b):
2300 print "%sValue types differ, item %d: 1:%s, 2:%s"%(indent,count,
2302 show_tupe(indent,1,one)
2303 show_tupe(indent2,two)
2305 if type(a) == type((1,)) or type(a) == type([]):
2306 if not comp_sub(indent+" ",a,b):
2307 # They're the same at this level, so show only one...
2308 show_tup(indent,0,one)
2312 print "%sValues differ, item %d: 1:%s, 2:%s"%(indent,count,
2314 show_tup(indent,1,one)
2315 show_tup(indent,2,two)
2319 def compare_tagtables(one,two):
2320 # Each table is made up of tuples of the form
2321 # (tagobj,action,arg,onfalse,ontrue)
2322 # but if action is Table or SubTable then arg may be a tuple
2324 if comp_sub("",one,two):
2325 print "They appear to be the same"
2328 # ------------------------------------------------------------
2330 """Used to test the module."""
2332 debug_pytag = DEFAULT_DEBUG
2333 use_pytag = DEFAULT_PYTAG
2339 if os.name == "posix":
2348 # Do we have command line arguments?
2349 arg_list = sys.argv[1:]
2353 if len(arg_list) == 0:
2358 if word == "-pytag":
2360 elif word == "-debug":
2362 elif word == "-stdout":
2364 elif word == "-force":
2366 elif word == "-import":
2368 elif word == "-compare":
2370 elif word == "-diag":
2373 elif word == "-test":
2376 elif word == "-help":
2377 print_usage(sys.argv[0])
2379 elif word == "-version":
2380 print "Version:",__version__
2382 elif word == "-history":
2389 arg_list = arg_list[1:]
2393 from Translate_tags import t_file
2394 i_file = define_tagtable()
2395 print "Comparing internal table (1) against external (2)"
2396 compare_tagtables(i_file,t_file)
2399 if not use_testdata and (not args or len(args) > 2):
2400 print_usage(sys.argv[0])
2403 if not use_testdata:
2407 print "Importing tag table definition"
2408 from Translate_tags import t_file
2410 print "Using internal tag table definition"
2411 t_file = define_tagtable()
2414 outfile = "standard output"
2418 base,ext = os.path.splitext(infile)
2420 outfile = base + ".py"
2422 print "Input file has extension .py so won't guess"\
2426 if outfile != "standard output":
2427 if outfile == infile:
2428 print "The output file is the same as the input file"
2429 print "Refusing to overwrite %s"%outfile
2431 elif os.path.exists(outfile):
2433 print "Output file %s already exists"\
2434 " - overwriting it"%outfile
2436 print "Output file %s already exists"%outfile
2439 # Read the input file
2442 print "Using test data"
2443 if DEBUGGING: print "==============="
2447 print "Reading text from %s"%infile
2448 if DEBUGGING: print "=================="+"="*len(infile)
2449 file = open(infile,"r")
2453 # Show what we are trying to parse
2454 if DEBUGGING or use_testdata:
2460 print "Tagging text"
2461 if DEBUGGING: print "============"
2464 pytag.set_verbosity(0)
2466 pytag.set_verbosity(1)
2467 pytag.use_debugger()
2468 result,taglist,next = pytag.pytag(text,t_file)
2470 timer = TextTools._timer()
2472 result, taglist, next = tag(text,t_file)
2473 #result, taglist, next = tag(text,t_file,0,len(text),taglist)
2474 print "Tagging took",timer.stop()[0],"seconds"
2476 # Now print out the result of the tagging
2478 print "Manipulating tagged data"
2479 if DEBUGGING: print "========================"
2480 tagfile = File(taglist,text)
2483 print "Writing translation to %s"%outfile
2484 if DEBUGGING: print "======================="+"="*len(outfile)
2486 # Open the output file, if necessary
2490 file = open(outfile,"w")
2492 tagfile.expand(file)
2495 # ------------------------------------------------------------
2496 if __name__ == '__main__':