1 # -*- coding: utf-8 -*-
2 # CONFIGURATION MANAGEMENT OF EDF VERSION
3 # ======================================================================
4 # COPYRIGHT (C) 1991 - 2002 EDF R&D WWW.CODE-ASTER.ORG
5 # THIS PROGRAM IS FREE SOFTWARE; YOU CAN REDISTRIBUTE IT AND/OR MODIFY
6 # IT UNDER THE TERMS OF THE GNU GENERAL PUBLIC LICENSE AS PUBLISHED BY
7 # THE FREE SOFTWARE FOUNDATION; EITHER VERSION 2 OF THE LICENSE, OR
8 # (AT YOUR OPTION) ANY LATER VERSION.
10 # THIS PROGRAM IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, BUT
11 # WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF
12 # MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. SEE THE GNU
13 # GENERAL PUBLIC LICENSE FOR MORE DETAILS.
15 # YOU SHOULD HAVE RECEIVED A COPY OF THE GNU GENERAL PUBLIC LICENSE
16 # ALONG WITH THIS PROGRAM; IF NOT, WRITE TO EDF R&D CODE_ASTER,
17 # 1 AVENUE DU GENERAL DE GAULLE, 92141 CLAMART CEDEX, FRANCE.
20 # ======================================================================
23 #!/tools/net/app/Python-1.5.2/bin/python1.5
25 """Translate - a first attempt at parsing my little language
27 Usage: Translate [switches] <infile> [<outfile>]
29 -stdout -- write to standard output instead of a file
30 -force -- write to the <outfile> even if it already
31 exists (overwrite any existing file)
33 -import -- import tag table from Translate_tags.py,
34 instead of using the internal table
36 -compare -- compare the imported and internal tag tables
39 -test -- use our internal test data and write to stdout
40 -pytag -- use the interpreted tagging engine
41 -debug -- if -pytag, enable its debugger
42 -diag -- enable general debugging
43 Beware that this currently also writes line
44 numbers to the start of each line in the output,
45 so it doesn't emit legal Python...
47 -help -- show this text
48 -history -- show the module history
49 -version -- show the module version
51 If <outfile> is not specified, <infile> will be used with its extension
55 __author__ = """Tibs (Tony J Ibbs)
56 tony@lsl.co.uk or tibs@tibsnjoan.demon.co.uk or tibs@bigfoot.com
57 http://www.tibsnjoan.demon.co.uk/
59 __version__ = "0.3 (tiepin) of 1999-11-15"
61 Originally created 1999-08-13
63 First released version is 0.2 (bootstrap)/1999-09-09, which gave
64 an idea of how the thing would work, and nearly did.
66 Second released version is 0.3 (tiepin)/1999-11-15, which is sufficient
67 to allow the parser used within this utility to be written in the little
68 language, translated and then used as such.
75 # ............................................................
76 # How we want to work things - this is fudge for me in initial development
77 if os.name == "posix":
83 TEXTTOOLS_PATH = "C:\\Program Files\\Python"
84 PYTAG_PATH = "C:\\Program Files\\Python\\TextTools\\Examples"
88 if TEXTTOOLS_PATH not in sys.path:
89 print "Adding",TEXTTOOLS_PATH
90 sys.path.append(TEXTTOOLS_PATH)
92 if PYTAG_PATH not in sys.path:
93 print "Adding",PYTAG_PATH
94 sys.path.append(PYTAG_PATH)
95 # ............................................................
97 # Import the TextTools themselves
98 # - I'm not personally too keen on import *, but it seems to be
99 # the recommended thing, so I'll leave it for now...
101 from TextTools import *
103 from mx.TextTools import *
104 #from TextTools.Constants.TagTables import *
105 #from TextTools.Constants.Sets import *
108 # ------------------------------------------------------------
109 # Useful little constants for unpicking the parsed tuples
115 # We want to align inline comments when possible - so this is
116 # the column at which we will try to place their "#" marks...
119 # Are we (generally) debugging?
122 # Do we want a comma after the last tuple (or item) in a table?
126 # ------------------------------------------------------------
127 def define_tagtable():
128 """Returns our tag table, if we're not importing it."""
130 # We are not, initially, going to try for anything very sophisticated
131 # - just something that will get us bootstrapped, so that I can use the
132 # "little language" to write more sophisticated stuff (without having
133 # to worry about dropped commas between tuples, and so on!)
136 # Whitespace is always useful
137 t_whitespace = (None,AllIn,' \t')
138 t_opt_whitespace = t_whitespace + (+1,)
140 # Comments are fairly simple
141 t_comment = ('comment',Table,
143 (None,AllNotIn,'\n\r',MatchOk))
146 # We care about the "content" of the indentation at the start of a line,
147 # but note that it is optional
148 t_indent = ('indent',AllIn,' \t')
149 t_indentation = t_indent + (+1,) # zero indentation doesn't show
151 # A string is text within single or double quotes
152 # (of course, this is an oversimplification, because we should also
153 # deal with things like "This is a \"substring\"", and it would be
154 # nice to be able to cope with triple-quoted strings too, but it
155 # will do for a start)
157 # Major bug - doesn't recognised zero length strings...
158 # (since "AllNotIn" must match at least one character)
159 t_string = ('str',Table,
160 ((None,Is,"'",+3,+1),
161 ('text',AllNotIn,"'"),
162 (None,Is,"'",MatchFail,MatchOk),
164 ('text',AllNotIn,'"'),
168 # An integer is a series of digits...
169 t_integer = ('int',AllIn,number)
171 t_signed_integer = ('signed_int',Table,
172 (('sign',Is,"+",+1,+2),
173 ('sign',Is,"-",+1,+1),
177 # Remember to be careful to specify the LONGEST possible match first,
178 # so that we try for "IsIn" before we try for "Is" (because "IsIn"
179 # would *match* "Is", leaving us with a spurious "In" hanging around...)
180 t_operation = ('op',Table,
181 (('op',Word,"AllInSet", +1,MatchOk),
182 ('op',Word,"AllIn", +1,MatchOk),
183 ('op',Word,"AllNotIn", +1,MatchOk),
184 ('op',Word,"CallArg", +1,MatchOk),
185 ('op',Word,"Call", +1,MatchOk),
186 ('op',Word,"EOF", +1,MatchOk),
187 ('op',Word,"Fail", +1,MatchOk),
188 ('op',Word,"IsInSet", +1,MatchOk),
189 ('op',Word,"IsIn", +1,MatchOk),
190 ('op',Word,"IsNotIn", +1,MatchOk),
191 ('op',Word,"IsNot", +1,MatchOk),
192 ('op',Word,"Is", +1,MatchOk),
193 ('op',Word,"Jump", +1,MatchOk),
194 ('op',Word,"LoopControl",+1,MatchOk),
195 ('op',Word,"Loop", +1,MatchOk),
196 ('op',Word,"Move", +1,MatchOk),
197 ('op',Word,"NoWord", +1,MatchOk), # alias for WordStart
198 ('op',Word,"Skip", +1,MatchOk),
199 ('op',Word,"SubTableInList",+1,MatchOk),
200 ('op',Word,"SubTable", +1,MatchOk),
201 ('op',Word,"sFindWord", +1,MatchOk),
202 ('op',Word,"sWordStart", +1,MatchOk),
203 ('op',Word,"sWordEnd", +1,MatchOk),
204 ('op',Word,"TableInList",+1,MatchOk),
205 ('op',Word,"Table", +1,MatchOk),
206 ('op',Word,"WordStart", +1,MatchOk),
207 ('op',Word,"WordEnd", +1,MatchOk),
208 ('op',Word,"Word", MatchFail,MatchOk),
212 t_keyword = ('keyword',Table,
213 ((None,Word,"and", +1,+28),
214 (None,Word,"assert", +1,+27),
215 (None,Word,"break", +1,+26),
216 (None,Word,"class", +1,+25),
217 (None,Word,"continue",+1,+24),
218 (None,Word,"def", +1,+23),
219 (None,Word,"del", +1,+22),
220 (None,Word,"elif", +1,+21),
221 (None,Word,"else", +1,+20),
222 (None,Word,"except", +1,+19),
223 (None,Word,"exec", +1,+18),
224 (None,Word,"finally", +1,+17),
225 (None,Word,"for", +1,+16),
226 (None,Word,"from", +1,+15),
227 (None,Word,"global", +1,+14),
228 (None,Word,"if", +1,+13),
229 (None,Word,"import", +1,+12),
230 (None,Word,"in", +1,+11),
231 (None,Word,"is", +1,+10),
232 (None,Word,"lambda", +1,+9),
233 (None,Word,"not", +1,+8),
234 (None,Word,"or", +1,+7),
235 (None,Word,"pass", +1,+6),
236 (None,Word,"print", +1,+5),
237 (None,Word,"raise", +1,+4),
238 (None,Word,"return", +1,+3),
239 (None,Word,"try", +1,+2),
240 (None,Word,"while", MatchFail,+1),
241 # In order to not recognise things like "in_THIS_CASE"
242 # we must check that the next character is not legitimate
243 # within an identifier
244 (None,IsIn,alpha+'_'+number,+1,MatchFail),
245 # If it wasn't another identifier character, we need to
246 # unread it so that it can be recognised as something else
247 # (so that, for instance, "else:" is seen as "else" followed
252 # Do the same for mxText commands
253 t_mxkeyword = ('mxKeyword',Table,
255 (None,IsIn,alpha+'_'+number,+1,MatchFail),
259 # Traditional identifiers
260 t_identifier = ('identifier',Table,
261 (t_keyword + (+1,MatchFail), # don't allow Python keywords
262 t_mxkeyword + (+1,MatchFail), # don't allow mxText commands
263 (None,IsIn,alpha+'_'), # can't start with a digit
264 (None,AllIn,alpha+'_'+number,MatchOk))
267 # We don't yet deal with the following with anything in parentheses,
268 # which means we can't handle functions or command lists, or other
269 # things which "look like" a tuple
270 t_argument = ('arg',Table,
271 (('arg',Word,"Here", +1,MatchOk), # EOF Here, Fail Here
272 ('arg',Word,"ToEOF", +1,MatchOk), # Move ToEOF
273 ('arg',Word,"To", +1,MatchOk), # Jump To
274 ('arg',Word,"ThisTable",+1,MatchOk), # [Sub]Table ThisTable
275 ('arg',Word,"back", +1,MatchOk), # Skip back
276 ('arg',Word,"Break", +1,MatchOk), # LoopControl Break
277 ('arg',Word,"Reset", +1,MatchOk), # LoopControl Reset
278 t_string + (+1,MatchOk), # e.g., Word "Fred"
279 t_signed_integer + (+1,MatchOk), # e.g., Skip -4, Move 3
280 t_identifier # e.g., Table Fred
283 t_plus = ('plus',Table,
289 # Arguments can contain "+"
290 t_plus_arg = ('plusarg',Table,
291 (t_argument, # start with a single argument
292 t_plus + (MatchOk,), # if we have a "+"
293 t_argument, # then we expect another argument
294 (None,Jump,To,-2), # then look for another "+"
297 # Match, for example:
299 t_label = ('label',Table,
305 # Targets for Jump and F:/T:
306 t_target = ('target',Table,
307 (('tgt',Word,"next", +1,MatchOk),
308 ('tgt',Word,"previous", +1,MatchOk),
309 ('tgt',Word,"repeat", +1,MatchOk),
310 ('tgt',Word,"MatchOk", +1,MatchOk),
311 ('tgt',Word,"MatchOK", +1,MatchOk), # For kindness sake
312 ('tgt',Word,"MatchFail",+1,MatchOk),
316 # A value is either an identifier, or a string, or an integer
317 t_value = ('val',Table,
318 (t_identifier +(+1,MatchOk),
319 t_string +(+1,MatchOk),
323 # An assignment is (optionally) used in Tuple and Table definitions...
324 t_assignment = ('assignment',Table,
330 # A common error when writing tuples is to miss off the "=" sign
331 # - the following is used in diagnosing that (see t_bad_tuple below)
332 # (it's useful to have something with identical structure to the
334 t_bad_tagobj = ('tagobj',Table,
338 t_bad_assignment = ('assignment',Table,
342 # This is the line that starts the definition of a single tuple.
343 # For the moment, restrict what it gets assigned to to a simple identifier.
344 # Match, for example:
346 t_tupleblock = ('tupleblock',Table,
352 # This is the line that starts a new table or sub-table.
353 # For the moment, we only cope with full Tables.
354 # NOTE that this is used for the "outer" declaration of a tag table,
355 # and also for the "inner" declaration of an inner table or sub-table.
356 # The discrimination between these is done after initial parsing.
357 # Match, for example:
358 # 'keyword' = Table is: (inner)
359 # tagtable = Table is: (outer)
360 t_tableblock = ('tableblock',Table,
361 (t_assignment + (+2,+1), # left hand side is optional
363 ('type',Word,"Table",+1,+2), # Either "Table"
364 ('type',Word,"SubTable"), # or "SubTable" is required
365 t_whitespace, # whitespace is required
366 (None,Word,"is:") # "is:" is required
369 # This is the line that starts an "if" block
370 # Match, for example:
373 t_ifblock = ('ifblock',Table,
374 (t_assignment + (+2,+1), # left hand side is optional
376 t_operation + (+4,+1),
379 (None,Is,":",MatchFail,MatchOk),
384 # Note that we don't allow spaces WITHIN our false and true thingies
386 t_onfalse = ('onfalse',Table,
392 t_ontrue = ('ontrue',Table,
398 # Valid examples are things like:
399 # 'fred' = Is "xxx" F:<wow> T:MatchOk
400 # AllIn jim T:<foundJim>
402 # For the moment, we're not trying to recognise things in any detail
403 t_tuple = ('tuple',Table,
404 (t_assignment + (+2,+1), # left hand side is optional
406 t_operation, # operation is required
407 t_whitespace, # for the moment, we always require space here
408 t_plus_arg, # argument is required
409 t_onfalse + (+1,+1), # F:target is optional
410 t_ontrue + (MatchOk,MatchOk) # T:target is also optional
413 # If the user has defined a "partial" tuple, they might use something
415 # match_fred F:MatchFalse T:MatchOk
416 t_tupleplus = ('tupleplus',Table,
418 t_onfalse + (+1,+1), # F:target is optional
419 t_ontrue + (MatchOk,MatchOk) # T:target is also optional
422 # Treat Jump To specially - for example:
424 # so that they don't have to do the less obvious "Jump To F:<label>"
425 # (although that will still be recognised, of course, for people who
426 # are used to the tag tuple format itself)
427 t_jumpto = ('jumpto',Table,
435 # Is it worth coping with these?
436 t_bad_jumpto = ('jumpto',Table,
437 ((None,Word,"Jump",+2), # cope with Jump to
438 (None,Word,"to",MatchFail,+2),
439 (None,Word,"JumpTo"), # and with JumpTo
443 # The "content" of a line is the bit after any indentation, and before
445 # For the moment, we won't try to maintain ANY context, so it is up to the
446 # user of the tuples produced to see if they make sense...
447 t_content = ('content',Table,
448 (t_label + (+1,MatchOk),
449 t_tableblock + (+1,MatchOk), # [<value> =] [Sub]Table is:
450 t_tupleblock + (+1,MatchOk), # <identifier> is:
451 t_ifblock + (+1,MatchOk), # <cmd> <arg>: OR <identifier>:
452 t_jumpto + (+1,MatchOk), # Jump To <target>
453 t_tuple + (+1,MatchOk),
454 t_tupleplus + (+1,MatchOk), # name [F:<label> [T:<label>]]
457 t_contentline = ('contentline',Table,
458 (t_content, # something that we care about
460 t_comment +(+1,+1), # always allow a comment
461 (None,IsIn,newline) # the end of the line
464 # Sometimes, the user (e.g., me) writes:
468 # Unfortunately, without the "is", it would get too confusing whether
469 # we actually wanted an if block...
470 t_bad_tableblock = ('tableblock',Table,
471 (t_assignment + (+2,+1), # left hand side is optional
473 (None,Word,"Table"), # "Table" is required
474 (None,Is,":") # "is" is needed before the ":"
477 # Sometimes, the use (e.g., me again) write:
481 # Whilst I'm not entirely convinced that "=" is the best character
482 # to use here, I think we do need something!
483 t_bad_tuple = ('tuple',Table,
484 (t_bad_assignment, # obviously we have to have this!
485 t_whitespace, # in which case the whitespace IS needed
486 t_operation, # operation is required
487 t_whitespace, # for the moment, we must have space here
488 t_plus_arg, # argument is required
489 t_onfalse + (+1,+1), # F:target is optional
490 t_ontrue + (MatchOk,MatchOk) # T:target is also optional
493 # Make some attempt to recognise common errors...
494 t_badcontent = ('badcontent',Table,
495 (t_bad_tableblock +(+1,MatchOk),
499 t_badline = ('badline',Table,
500 (t_badcontent, # something that we sort of care about
502 t_comment +(+1,+1), # always allow a comment
503 (None,IsIn,newline) # the end of the line
506 t_emptyline = ('emptyline',Table,
508 (None,IsIn,newline) # the end of the line
511 t_commentline = ('commentline',Table,
513 (None,IsIn,newline) # the end of the line
516 t_passthruline = ('passthruline',Table,
517 (('passthru',AllNotIn,newline,+1), # owt else on the line
518 (None,IsIn,newline) # the end of the line
521 # Basically, a file is a series of lines
522 t_line = ('line',Table,
523 (t_emptyline +(+1,MatchOk), # empty lines are simple enough
524 t_indent +(+1,+1), # optional indentation
525 t_commentline +(+1,MatchOk), # always allow a comment
526 t_contentline +(+1,MatchOk), # a line we care about
527 t_badline +(+1,MatchOk), # a line we think is wrong
528 t_passthruline # a line we don't care about
538 # ------------------------------------------------------------
539 # We'll define some moderately interesting test data
542 # This example isn't *meant* to make any sense!
543 # It's just an accumulation of things that got checked for various reasons
544 from TextTools import *
553 # And the rest is our business...
555 'int' = AllIn '0123456789'
559 # A comment here is OK
560 <label> # Strangely enough, so is a label
561 'indent' = AllIn ' \t'
563 'int' AllIn number # BUGGY LINE (missing "=")
564 (None,"AllIn",number) # BUGGY LINE (an actual tuple)
565 fred = jim # BUGGY LINE (not our business)
566 tagobj F:<op> T:next # label <op> is undefined
567 # The next line is totally empty
569 # The next line contains just indentation
571 # This line is just a comment
572 # And this comment should be JUST after the preceding block...
573 t_indentation is: # This should be "= Table is:"
577 t_indent F:previous T:previous
581 'this' = Table ThisTable
587 'a' = AllIn 'a' F:previous
588 'a' = AllIn 'a' T:previous
589 'a' = AllIn 'a' F:previous T:previous
591 AllIn 'xyz' F:<later> T:<top>
598 AllIn number+"_"+alpha
603 # ------------------------------------------------------------
606 class OutsideError(Exception):
607 """The entity is not permitted outside a block."""
610 class IndentError(Exception):
611 """An indentation error has been detected."""
614 class NoIdentifier(Exception):
615 """We're missing an identifier (to assign to)."""
619 # ------------------------------------------------------------
620 def LineFactory(lineno,tagtuple,text):
621 """Take some tagged data and return an appropriate line class.
623 lineno -- the line number in the "file". Note that the first line
624 in the file is line 1
625 tagtuple -- a tag tuple for a single line of data
626 text -- the text for the "file". All the "left" and "right" offsets
627 are relative to this text (i.e., it is the entire content
630 The tag tuples we get back from the parser will be of the form:
633 ('indent',left,right,None), -- this is optional
634 ('content',left,right,[<data>])
637 Looking at <type> should enable us to decide what to do with
641 # Extract the list of tuples from this 'line'
642 tuples = tagtuple[SUBLIST]
644 # First off, do we have any indentation?
646 if tup[OBJECT] == "indent":
647 # This is inefficient, because it actually copies strings
648 # around - better would be to duplicate the calculation
649 # that string.expandtabs does internally...
650 indent_str = string.expandtabs(text[tup[LEFT]:tup[RIGHT]])
656 # Now, work out which class we want an instance of
657 # (this is the 'fun' bit)
659 type = tuples[0][OBJECT]
660 if type == 'emptyline':
661 return EmptyLine(lineno,indent_str,tuples[0],text)
662 elif type == 'commentline':
663 return CommentLine(lineno,indent_str,tuples[0],text)
664 elif type == 'passthruline':
665 return PassThruLine(lineno,indent_str,tuples[0],text)
666 elif type == 'contentline':
667 # OK - we need to go down another level
668 sublist = tuples[0][SUBLIST]
670 # Do we also have an in-line comment?
676 # And the actual DATA for our line is down yet another level...
677 sublist = sublist[0][SUBLIST]
678 type = sublist[0][OBJECT]
680 return LabelLine(lineno,indent_str,sublist[0],comment,text)
681 elif type == 'tableblock':
682 return TableBlockLine(lineno,indent_str,sublist[0],comment,text)
683 elif type == 'tupleblock':
684 return TupleBlockLine(lineno,indent_str,sublist[0],comment,text)
685 elif type == 'ifblock':
686 return IfBlockLine(lineno,indent_str,sublist[0],comment,text)
687 elif type == 'tuple':
688 return TupleLine(lineno,indent_str,sublist[0],comment,text)
689 elif type == 'tupleplus':
690 return TuplePlusLine(lineno,indent_str,sublist[0],comment,text)
691 elif type == 'jumpto':
692 return JumpToLine(lineno,indent_str,sublist[0],comment,text)
695 "Line %d is of unexpected type 'contentline/%s'"%(lineno,
697 elif type == 'badline':
698 # OK - we need to go down another level
699 sublist = tuples[0][SUBLIST]
701 # Do we also have an in-line comment?
707 # And the actual DATA for our line is down yet another level...
708 sublist = sublist[0][SUBLIST]
709 type = sublist[0][OBJECT]
710 if type == 'tableblock':
711 return BadTableBlockLine(lineno,indent_str,sublist[0],comment,text)
713 return BadTupleLine(lineno,indent_str,sublist[0],comment,text)
716 "Line %d is of unexpected type 'badline/%s'"%(lineno,type)
718 raise ValueError,"Line %d is of unexpected type '%s'"%(lineno,type)
722 # ------------------------------------------------------------
724 """The base class on which the various line types depend
728 tagtuple -- the tagtuple we (our subclass instance) represent(s)
729 lineno -- the line number in the file (first line is line 1)
730 indent -- our indentation (integer)
731 indent_str -- our indentation (a string of spaces)
732 text -- the text of the "file" we're within
733 class_name -- the name of the actual class this instance belongs to
734 (i.e., the name of the subclass, suitable for printing)
736 Some things only get useful values after we've been instantiated
738 next_indent -- the indentation of the next line
739 index -- for a line in a block, its index therein
742 def __init__(self,lineno,indent_str,tagtuple,text):
743 """Instantiate a BaseLine.
745 lineno -- the line number in the "file". Note that the first line
746 in the file is line 1
747 indent_str -- the indentation of the line (a string of spaces)
748 tagtuple -- the tag tuple for this line of data
749 text -- the text for the "file". All the "left" and "right"
750 offsets are relative to this text (i.e., it is the
751 entire content of the file)
753 The content of the tagtuple depends on which of our subclasses
754 is being used. Refer to the relevant doc string.
757 self.tagtuple = tagtuple
761 self.class_name = self._class_name()
762 self.indent_str = indent_str
763 self.indent = len(indent_str)
765 # OK - we don't really know! (but this will do for "EOF")
768 # We don't always HAVE a sensible value for this
772 # print "Line %3d: %s%s"%(lineno,indent_str,self.class_name)
774 def change_indent(self,count=None,spaces=""):
775 """Change our indentation.
777 Specify either "count" or "spaces" (if both are given,
778 "count" will be used, if neither is given, then the
779 indentation will be set to zero)
781 count -- the number of spaces we're indented by
782 spaces -- a string of spaces
786 self.indent_str = count * " "
788 self.indent_str = spaces
789 self.indent = len(spaces)
791 def _class_name(self):
792 """Return a representation of the class name."""
794 full_name = "%s"%self.__class__
795 bits = string.split(full_name,".")
798 def starts_block(self):
799 """Return true if we start a new block."""
802 def only_in_block(self):
803 """Return true if we can only occur inside a block."""
806 def our_business(self):
807 """Return true if we are a line we understand."""
811 return "%3d %s%-10s"%(self.lineno,self.indent_str,self.class_name)
814 """Returns a useful 'introductory' string."""
815 return "%3d %-10s %s"%(self.lineno,self.class_name,self.indent_str)
818 """Returns a "truncated" representation of our text."""
820 text = "%s %s"%(self._intro(),
821 `self.text[self.tagtuple[LEFT]:self.tagtuple[RIGHT]]`)
824 return text[:60]+"..."
828 def resolve_labels(self,block):
829 """Called to resolve any labels use in this line.
831 block -- the block that contains us
833 # The default is to do nothing as we don't HAVE any labels...
836 def expand(self,stream,block=None):
837 """Write out the expanded equivalent of ourselves.
839 stream -- an object with a "write" method, e.g., a file
840 newline -- true if we should output a terminating newline
841 block -- used to pass the containing Block down to lines
842 within a block, or None if we're not in a block
846 stream.write("Line %3d: "%self.lineno)
848 stream.write(self.indent_str)
849 stream.write(self.text[self.tagtuple[LEFT]:self.tagtuple[RIGHT]])
852 def warning(self,text):
853 """Report a warning message.
855 text -- the text to report
858 lines = string.split(text,"\n")
859 print "###WARNING: line %d (%s)"%(self.lineno,self.class_name)
863 def error(self,text):
866 text -- the error text to report
869 lines = string.split(text,"\n")
870 print "###ERROR: line %d (%s)"%(self.lineno,self.class_name)
875 # ------------------------------------------------------------
876 class EmptyLine(BaseLine):
879 Note that the indentation of an empty line is taken to be the
880 same as that of the next (non-empty) line. This is because it
881 seems to me that (a) an empty line should not per-se close a
882 block (which it would do if it had indentation 0) and (b) we
883 don't remember any whitespace in an empty line, so the user
884 can't assign an indentation themselves (which is a Good Thing!)
887 def __init__(self,lineno,indent_str,tagtuple,text):
888 """Instantiate an EmptyLine.
890 The content of the tagtuple is:
894 BaseLine.__init__(self,lineno,indent_str,tagtuple,text)
896 def expand(self,stream,block=None):
897 """Write out the expanded equivalent of ourselves.
899 stream -- an object with a "write" method, e.g., a file
900 block -- used to pass the containing Block down to lines
901 within a block, or None if we're not in a block
905 stream.write("Line %3d: "%self.lineno)
907 # um - there's nothing to do, folks
910 def our_business(self):
911 """Return true if we are a line we understand."""
915 """Returns a "truncated" representation of our text."""
920 # ------------------------------------------------------------
921 class CommentLine(BaseLine):
922 """A comment line."""
924 def __init__(self,lineno,indent_str,tagtuple,text):
925 """Instantiate a CommentLine.
927 The content of the tagtuple is:
928 ('comment',left,right,None)
929 and the demarcated text includes the initial '#' character
932 BaseLine.__init__(self,lineno,indent_str,tagtuple,text)
934 # We actually want the next tuple down (so to speak) so that
935 # we lose the trailing newline...
936 tup = self.tagtuple[SUBLIST][0]
937 self.data = self.text[tup[LEFT]:tup[RIGHT]]
939 def our_business(self):
940 """Return true if we are a line we understand."""
943 def expand(self,stream,block=None):
944 """Write out the expanded equivalent of ourselves.
946 stream -- an object with a "write" method, e.g., a file
947 block -- used to pass the containing Block down to lines
948 within a block, or None if we're not in a block
952 stream.write("Line %3d: "%self.lineno)
954 stream.write(self.indent_str)
955 stream.write("%s\n"%self.data)
958 # ------------------------------------------------------------
959 class PassThruLine(BaseLine):
960 """A line we just pass throught without interpretation."""
962 def __init__(self,lineno,indent_str,tagtuple,text):
963 """Instantiate a PassThruLine.
965 The content of the tagtuple is:
966 ('passthru',left,right,None)
969 BaseLine.__init__(self,lineno,indent_str,tagtuple,text)
971 # We actually want the next tuple down (so to speak) so that
972 # we lose the trailing newline...
973 tup = self.tagtuple[SUBLIST][0]
974 self.data = self.text[tup[LEFT]:tup[RIGHT]]
976 def our_business(self):
977 """Return true if we are a line we understand."""
980 def expand(self,stream,block=None):
981 """Write out the expanded equivalent of ourselves.
983 stream -- an object with a "write" method, e.g., a file
984 block -- used to pass the containing Block down to lines
985 within a block, or None if we're not in a block
989 stream.write("Line %3d: "%self.lineno)
992 err_str = "Unparsed line inside a block"\
993 " - it has been commented out"
994 # Hmm - the following advice is less often useful than I
995 # had hoped - leave it out for now...
996 #if string.find(self.data,",") != -1:
997 # err_str = err_str + "\nCheck for a trailing comma?"
1001 # Always output the indentation, 'cos otherwise it looks silly
1002 stream.write(self.indent_str)
1005 stream.write("#[ignored]#")
1007 stream.write("%s\n"%self.data)
1010 # ------------------------------------------------------------
1011 class ContentLine(BaseLine):
1012 """A line we have to interpret - another base class.
1014 Adds the following variables:
1016 comment -- any in-line comment on this line
1019 def __init__(self,lineno,indent_str,tagtuple,comment,text):
1020 """Instantiate a ContentLine.
1022 comment -- either a comment tuple or None
1024 The content of the tagtuple is:
1025 ('contentline',left,right,
1026 [('content',left,right,[<data>]),
1027 ('comment',left,right,None) -- optional
1029 where <data> is used in the internals of one of our subclasses
1030 (i.e., it is what is passed down in the "tagtuple" argument)
1033 BaseLine.__init__(self,lineno,indent_str,tagtuple,text)
1034 self.comment = comment
1036 # Assume we're not the last "our business" line in a block...
1039 def _write_comment(self,stream,sofar):
1040 """Write out the in-line comment string.
1042 Since we're the only people to call this, we can safely
1043 rely on it only being called when there IS a comment tuple
1046 stream -- an object with a "write" method, e.g., a file
1047 sofar -- the number of characters written to the line
1050 if sofar < COMMENT_COLUMN:
1051 stream.write(" "*(COMMENT_COLUMN - sofar))
1053 # always write at least one space...
1055 stream.write(self.text[self.comment[LEFT]:self.comment[RIGHT]])
1057 def _write_text(self,stream,block):
1058 """Write out the main tuple text.
1060 stream -- an object with a "write" method, e.g., a file
1061 block -- used to pass the containing Block down to lines
1062 within a block, or None if we're not in a block
1064 This should generally be the method that subclasses override.
1065 It returns the number of characters written, or -1 if we had
1068 stream.write(self.text[self.tagtuple[LEFT]:self.tagtuple[RIGHT]])
1069 return self.tagtuple[RIGHT] - self.tagtuple[LEFT]
1071 def expand(self,stream,block=None):
1072 """Write out the expanded equivalent of ourselves.
1074 stream -- an object with a "write" method, e.g., a file
1075 block -- used to pass the containing Block down to lines
1076 within a block, or None if we're not in a block
1080 stream.write("Line %3d: "%self.lineno)
1082 stream.write(self.indent_str)
1083 nchars = self._write_text(stream,block)
1084 # Don't write any in-line comment out if we had an error,
1085 # as the layout won't work!
1086 if nchars > -1 and self.comment:
1087 self._write_comment(stream,sofar=nchars+self.indent)
1091 # ------------------------------------------------------------
1092 class LabelLine(ContentLine):
1093 """A line containing a label.
1096 label -- our label string
1099 def __init__(self,lineno,indent_str,tagtuple,comment,text):
1100 """Instantiate a LabelLine.
1106 The content of the tagtuple is:
1108 ('label',left,right,[
1109 ('identifier',left,right,None)
1113 ContentLine.__init__(self,lineno,indent_str,tagtuple,comment,text)
1115 self.label = self.text[self.tagtuple[LEFT]:self.tagtuple[RIGHT]]
1117 def _write_text(self,stream,block):
1118 """Write out the main tuple text.
1120 stream -- an object with a "write" method, e.g., a file
1121 block -- used to pass the containing Block down to lines
1122 within a block, or None if we're not in a block
1124 # Enough difficult length calculation - let's do this one
1127 text = "# Label %s at index %d"%(self.label,self.index)
1129 text = "# %s"%(self.label) # surely enough for most people...
1133 def translate(self,index,block):
1134 """Return the translation of a use of this label as a target.
1136 index -- the index of the line which uses the label as a target
1137 block -- the Block we are within
1140 # Hmm - I don't think this CAN go wrong at this point...
1141 return block.translate_label(self.label,self)
1143 def only_in_block(self):
1144 """Return true if we can only occur inside a block."""
1148 # ------------------------------------------------------------
1149 class TableBlockLine(ContentLine):
1150 """A line starting a table block."""
1152 def __init__(self,lineno,indent_str,tagtuple,comment,text):
1153 """Instantiate a TableBlockLine.
1160 This is used for two purposes:
1161 1. To define the actual tag table itself (i.e., at the outer
1162 level). Only "Table" is allowed in this instance, but since
1163 that is all we recognised for now, we shan't worry about it...
1164 2. To define an inner table (i.e., at an inner level)
1166 The content of the tagtuple is:
1168 ('tableblock',left,right,[
1169 ('assignment',left,right,[ -- optional if inner
1172 ('identifier',left,right,[])
1175 ('text',left,right,None)
1178 ('int',left,right,[])
1182 ('type',left,right,[]) -- either "Table" or "SubTable"
1185 NOTE: as an "emergency" measure (so we can `pretend' that a
1186 TupleBlock was actually a TableBlock as part of attempted
1187 error correction), if tagtuple == ("error",tagobj) then we
1188 short-circuit some of the initialisation...
1191 ContentLine.__init__(self,lineno,indent_str,tagtuple,comment,text)
1193 if tagtuple[0] == "error":
1194 # We're "bluffing" at the creation of a TableBlock
1195 self.tagobj = tagtuple[1]
1196 self.is_subtable = 0
1197 elif len(self.tagtuple[SUBLIST]) == 1:
1198 self.tagobj = "None"
1199 tup = self.tagtuple[SUBLIST][0]
1200 self.is_subtable = (self.text[tup[LEFT]:tup[RIGHT]] == "SubTable")
1202 # The first tuple down gives us the "<value> = " string
1203 tup = self.tagtuple[SUBLIST][0]
1204 # The next tuple down gives us "<value>" which is what we want
1205 tup = tup[SUBLIST][0]
1206 self.tagobj = self.text[tup[LEFT]:tup[RIGHT]]
1207 # Then we have the type of table
1208 tup = self.tagtuple[SUBLIST][1]
1209 self.is_subtable = (self.text[tup[LEFT]:tup[RIGHT]] == "SubTable")
1211 def got_tagobj(self):
1212 return (self.tagobj != "None")
1214 def starts_block(self):
1215 """Return true if we start a new block."""
1218 def _write_text(self,stream,block):
1219 """Write out the main tuple text.
1221 stream -- an object with a "write" method, e.g., a file
1222 block -- used to pass the containing Block down to lines
1223 within a block, or None if we're not in a block
1225 It returns the number of characters written, or -1 if we had
1230 if self.is_subtable:
1231 stream.write("(%s,SubTable,("%self.tagobj)
1232 return len(self.tagobj) + 11
1234 stream.write("(%s,Table,("%self.tagobj)
1235 return len(self.tagobj) + 8
1237 stream.write("%s = ("%self.tagobj)
1238 return len(self.tagobj) + 4
1241 # ------------------------------------------------------------
1242 class TupleBlockLine(ContentLine):
1243 """A line starting a tuple block (i.e., defining a single tuple)
1247 name -- the "name" of this tuple (i.e., what comes
1251 def __init__(self,lineno,indent_str,tagtuple,comment,text):
1252 """Instantiate a TupleBlockLine.
1258 The content of the tagtuple is:
1260 ('tupleblock',left,right,[
1261 ('identifier',left,right,None)
1265 ContentLine.__init__(self,lineno,indent_str,tagtuple,comment,text)
1267 tup = self.tagtuple[SUBLIST][0]
1268 self.name = self.text[tup[LEFT]:tup[RIGHT]]
1270 def starts_block(self):
1271 """Return true if we start a new block."""
1274 def only_in_block(self):
1275 """Return true if we can only occur inside a block."""
1278 def _write_text(self,stream,block):
1279 """Write out the main tuple text.
1281 stream -- an object with a "write" method, e.g., a file
1282 block -- used to pass the containing Block down to lines
1283 within a block, or None if we're not in a block
1285 It returns the number of characters written, or -1 if we had
1288 # The "\" at the end is somewhat clumsy looking, but the
1289 # only obvious way of preserving layout...
1290 stream.write("%s = \\"%self.name)
1291 return len(self.name) + 5
1294 # ------------------------------------------------------------
1295 class IfBlockLine(ContentLine):
1296 """A line starting an if block.
1299 cmd -- the command within this if block
1300 arg -- the argument for said command
1302 name -- the name within this if block
1305 def __init__(self,lineno,indent_str,tagtuple,comment,text):
1306 """Instantiate an IfBlockLine.
1314 The content of the tagtuple is:
1316 ('ifblock',left,right,[
1317 ('assignment',left,right,[
1320 ('identifier',left,right,[])
1323 ('text',left,right,None)
1326 ('int',left,right,[])
1330 ('op',left,right,None),
1331 ('arg',left,right,None),
1334 ('ifblock',left,right,[
1335 ('op',left,right,None),
1336 ('arg',left,right,None),
1339 ('ifblock',left,right,[
1340 ('identifier',left,right,None)
1344 ContentLine.__init__(self,lineno,indent_str,tagtuple,comment,text)
1346 tuples = self.tagtuple[SUBLIST]
1347 if tuples[0][OBJECT] == 'op':
1350 self.tagobj = "None"
1351 self.cmd = self.text[tup1[LEFT]:tup1[RIGHT]]
1352 self.arg = self.text[tup2[LEFT]:tup2[RIGHT]]
1354 elif tuples[0][OBJECT] == 'assignment':
1355 # The "<value>" in the "<value> = " string is down
1356 # one level more than the others
1357 tup0 = tuples[0][SUBLIST][0]
1358 self.tagobj = self.text[tup0[LEFT]:tup0[RIGHT]]
1361 self.cmd = self.text[tup1[LEFT]:tup1[RIGHT]]
1362 self.arg = self.text[tup2[LEFT]:tup2[RIGHT]]
1364 elif tuples[0][OBJECT] == 'identifier':
1366 self.name = self.text[tup[LEFT]:tup[RIGHT]]
1371 # Hmm - try to continue with anything unexpected
1373 self.error("Unexpected IfBlock subtype %s"%tup[OBJECT])
1374 self.name = self.text[tup[LEFT]:tup[RIGHT]]
1379 # Currently, we have one 'special' argument
1380 if self.arg == "back": self.arg = "-1"
1382 # We don't yet know the offset of the "virtual label" at the
1383 # end of this if block...
1384 self.end_label = None
1386 def starts_block(self):
1387 """Return true if we start a new block."""
1390 def only_in_block(self):
1391 """Return true if we can only occur inside a block."""
1394 def resolve_labels(self,block):
1395 """Called to resolve any labels used in this line.
1397 block -- the block that contains us
1399 Note that this only does something the first time it
1400 is called - this will be when the IF block's startline
1401 is asked to resolve its labels. If it is called again,
1402 as a 'normal' line, it will do nothing...
1404 if not self.end_label:
1405 self.end_label = "%+d"%(len(block.business)+1)
1407 def _write_text(self,stream,block):
1408 """Write out the main tuple text.
1410 stream -- an object with a "write" method, e.g., a file
1411 block -- used to pass the containing Block down to lines
1412 within a block, or None if we're not in a block
1414 It returns the number of characters written, or -1 if we had
1417 if not self.end_label:
1418 # This should never happen, but just in case, warn the user!
1419 self.error("Unable to determine 'onFalse' destination in IF")
1422 stream.write("%s + (%s,+1),"%(self.name,
1423 self.end_label or "<undefined>"))
1424 return len(self.name) + 20
1426 stream.write("(%s,%s,%s,%s,+1),"%(self.tagobj,self.cmd,self.arg,
1427 self.end_label or "<undefined>"))
1428 return len(self.tagobj) + len(self.cmd) + len(self.arg) + \
1429 len(self.end_label) + 20
1432 # ------------------------------------------------------------
1433 class TupleLine(ContentLine):
1434 """A line containing a basic tuple.
1441 ontrue -- what to do if true
1442 onfalse -- ditto false
1445 def __init__(self,lineno,indent_str,tagtuple,comment,text):
1446 """Instantiate a TupleLine.
1448 The content of the tagtuple is:
1450 ('tuple',left,right,[
1451 ('tagobj',left,right,[ -- optional
1453 ('text',left,right,None)
1456 ('op',left,right,None),
1457 ('arg',left,right,None),
1458 ('onfalse',left,right,[ -- optional
1459 ('target',left,right,[
1460 ('tgt',left,right,None)
1462 ('ontrue',left,right,[ -- optional
1463 ('target',left,right,[
1464 ('tgt',left,right,None)
1470 ContentLine.__init__(self,lineno,indent_str,tagtuple,comment,text)
1476 """Unpack our contents from our tagtuple."""
1478 # This is doubtless not the most efficient way of doing this,
1479 # but it IS relatively simple...
1481 #for key in ("assignment","op","arg","onfalse","ontrue"):
1482 for key in ("assignment","op","plusarg","onfalse","ontrue"):
1485 tuples = self.tagtuple[SUBLIST]
1488 if name == "onfalse" or name == "ontrue" or name == "assignment":
1489 # For these, we need to go "down one level" for our data
1490 tup = item[SUBLIST][0]
1491 dict[name] = (tup[LEFT],tup[RIGHT])
1493 dict[name] = (item[LEFT],item[RIGHT])
1495 # The tag object is optional
1496 if dict["assignment"]:
1497 left,right = dict["assignment"]
1498 self.tagobj = self.text[left:right]
1500 self.tagobj = "None"
1502 # The operation (command) and argument are required
1503 left,right = dict["op"]
1504 self.cmd = self.text[left:right]
1506 #left,right = dict["arg"]
1507 left,right = dict["plusarg"]
1508 self.arg = self.text[left:right]
1510 # Currently, we have one 'special' argument
1511 if self.arg == "back": self.arg = "-1"
1513 # Actually, we don't want the F and T jumps explicit if not
1514 # given, since we mustn't output them for a single tuple if
1515 # they're not given (so they can be "added in" later on)
1517 left,right = dict["onfalse"]
1518 self.onfalse = self.text[left:right]
1520 self.onfalse = None # "MatchFail"
1522 left,right = dict["ontrue"]
1523 self.ontrue = self.text[left:right]
1525 self.ontrue = None # "next"
1527 def only_in_block(self):
1528 """Return true if we can only occur inside a block."""
1531 def resolve_labels(self,block):
1532 """Called to resolve any labels use in this line.
1534 block -- the block that contains us
1537 self.onfalse = block.translate_label(self.onfalse,self)
1539 self.ontrue = block.translate_label(self.ontrue,self)
1541 def _write_text(self,stream,block):
1542 """Write out the main tuple text.
1544 stream -- an object with a "write" method, e.g., a file
1545 block -- used to pass the containing Block down to lines
1546 within a block, or None if we're not in a block
1548 It returns the number of characters written, or -1 if we had
1552 # Start with the stuff we must have...
1553 stream.write("(%s,%s,%s"%(self.tagobj,self.cmd,self.arg))
1554 length = len(self.tagobj) + len(self.cmd) + len(self.arg) + 3
1557 if not self.onfalse:
1558 # OK, we didn't get an explicit F, but because it comes
1559 # before the T jump in the tuple, we need to fake it
1561 stream.write(",%s,%s)"%("MatchFail",self.ontrue))
1562 length = length + len("MatchFail") + len(self.ontrue) + 3
1564 # We had both F and T
1565 stream.write(",%s,%s)"%(self.onfalse,self.ontrue))
1566 length = length + len(self.onfalse) + len(self.ontrue) + 3
1568 # We only had F. We shan't "fake" the T jump, *just* in case
1569 # the user is defining a single tuple that they'll add the
1570 # T jump to later on (although that *is* a bit dodgy, I think)
1571 # [[The option would be to "fake" it if we're IN a block - I may
1572 # go for that approach later on]]
1573 stream.write(",%s)"%self.onfalse)
1574 length = length + len(self.onfalse) + 2
1576 # Neither F nor T - so don't write the defaults for either,
1577 # in case this is a top level tuple they're going to add to
1579 # [[Comments as for the case above, I think]]
1583 if block and not self.is_last:
1589 # ------------------------------------------------------------
1590 class TuplePlusLine(ContentLine):
1591 """A line containing a tuple "plus" (e.g., "fred + (+1,+1)").
1595 name -- the name/identifier
1596 ontrue -- what to do if true
1597 onfalse -- ditto false
1600 def __init__(self,lineno,indent_str,tagtuple,comment,text):
1601 """Instantiate a TuplePlusLine.
1603 <identifier> + (onF,onT)
1605 The content of the tagtuple is:
1607 ('tupleplus',left,right,[
1608 ('identifier',left,right,None)
1609 ('onfalse',left,right,[ -- optional
1610 ('target',left,right,[
1611 ('tgt',left,right,None)
1613 ('ontrue',left,right,[ -- optional
1614 ('target',left,right,[
1615 ('tgt',left,right,None)
1621 ContentLine.__init__(self,lineno,indent_str,tagtuple,comment,text)
1627 """Unpack our contents from our tagtuple."""
1629 # This is doubtless not the most efficient way of doing this,
1630 # but it IS relatively simple...
1632 for key in ("identifier","onfalse","ontrue"):
1635 tuples = self.tagtuple[SUBLIST]
1638 if name == "onfalse" or name == "ontrue":
1639 # For these, we need to go "down one level" for our data
1640 tup = item[SUBLIST][0]
1641 dict[name] = (tup[LEFT],tup[RIGHT])
1643 dict[name] = (item[LEFT],item[RIGHT])
1645 # Start with the identifier
1646 left,right = dict["identifier"]
1647 self.name = self.text[left:right]
1649 # Actually, we don't want the F and T jumps explicit if not
1650 # given, since we mustn't output them for a single tuple if
1651 # they're not given (so they can be "added in" later on)
1653 left,right = dict["onfalse"]
1654 self.onfalse = self.text[left:right]
1656 self.onfalse = None # "MatchFail"
1658 left,right = dict["ontrue"]
1659 self.ontrue = self.text[left:right]
1661 self.ontrue = None # "next"
1663 def only_in_block(self):
1664 """Return true if we can only occur inside a block."""
1667 def resolve_labels(self,block):
1668 """Called to resolve any labels use in this line.
1670 block -- the block that contains us
1673 self.onfalse = block.translate_label(self.onfalse,self)
1675 self.ontrue = block.translate_label(self.ontrue,self)
1677 def _write_text(self,stream,block):
1678 """Write out the main tuple text.
1680 stream -- an object with a "write" method, e.g., a file
1681 block -- used to pass the containing Block down to lines
1682 within a block, or None if we're not in a block
1684 It returns the number of characters written, or -1 if we had
1688 if not self.onfalse and not self.ontrue:
1689 stream.write("%s"%self.name)
1690 length = len(self.name)
1692 # Make a feeble attempt to cause successive such lines to
1693 # look neater, by aligning the "+" signs (if we output them)
1694 stream.write("%-15s + ("%(self.name))
1695 length = max(len(self.name),15) + 4
1696 if self.ontrue and self.onfalse:
1697 stream.write("%s,%s)"%(self.onfalse,self.ontrue))
1698 length = length + len(self.onfalse) + len(self.ontrue) + 2
1700 stream.write("MatchFail,%s)"%(self.ontrue))
1701 length = length + len(self.ontrue) + 11
1703 # Don't forget that comma to make this a tuple!
1704 stream.write("%s,)"%(self.onfalse))
1705 length = length + len(self.onfalse) + 1
1707 if not self.is_last:
1714 # ------------------------------------------------------------
1715 class JumpToLine(ContentLine):
1716 """A line containing "Jump To <label>"
1720 name -- the name/identifier
1721 onfalse -- the target (which is technically an "on false" jump)
1724 def __init__(self,lineno,indent_str,tagtuple,comment,text):
1725 """Instantiate a JumpLine.
1729 The content of the tagtuple is:
1731 ('jumpto',left,right,[
1732 ('target',left,right,[
1733 ('tgt',left,right,None)
1738 ContentLine.__init__(self,lineno,indent_str,tagtuple,comment,text)
1740 tup = self.tagtuple[SUBLIST][0]
1741 self.onfalse = self.text[tup[LEFT]:tup[RIGHT]]
1743 def only_in_block(self):
1744 """Return true if we can only occur inside a block."""
1747 def resolve_labels(self,block):
1748 """Called to resolve any labels use in this line.
1750 block -- the block that contains us
1752 self.onfalse = block.translate_label(self.onfalse,self)
1754 def _write_text(self,stream,block):
1755 """Write out the main tuple text.
1757 stream -- an object with a "write" method, e.g., a file
1758 block -- used to pass the containing Block down to lines
1759 within a block, or None if we're not in a block
1761 It returns the number of characters written, or -1 if we had
1765 stream.write("(None,Jump,To,%s)"%(self.onfalse))
1766 length = len(self.onfalse) + 15
1768 if not self.is_last:
1775 # ------------------------------------------------------------
1776 class BadTableBlockLine(TableBlockLine):
1777 """We think they MEANT this to be a table block line."""
1779 def __init__(self,lineno,indent_str,tagtuple,comment,text):
1780 """Instantiate a BadTableBlockLine.
1787 TableBlockLine.__init__(self,lineno,indent_str,tagtuple,comment,text)
1788 self.error("Suspected missing 'is' before the colon\n"
1789 "pretending it's there")
1792 # ------------------------------------------------------------
1793 class BadTupleLine(TupleLine):
1794 """We think they MEANT this to be a tuple line."""
1796 def __init__(self,lineno,indent_str,tagtuple,comment,text):
1797 """Instantiate a BadTupleLine.
1803 TupleLine.__init__(self,lineno,indent_str,tagtuple,comment,text)
1804 self.error("Suspected missing '=' between tag object and command\n"
1805 "pretending it's there")
1808 # ------------------------------------------------------------
1809 class Block(ContentLine):
1810 """This class represents a "block".
1812 A "block" is a section of code which starts with a line ending in
1813 a colon (":"), with the next line and subsequent lines ("in" the
1814 block) having an extra indent. The block ends when a dedent is
1817 Each instance "eats" lines from the input until (if) it finds the first
1818 "sub" block. That then "eats" lines until it finds its own end, and
1819 then hands control back to the first instance, which does the same thing
1822 Note that we "pretend" to be a content line - it is convenient to
1823 look like a line class, so that line processing can cope with us,
1824 and indeed what we do is "pretend" to be a clone of our start line
1825 with some extra information...
1828 startline -- the line that "introduces" this block
1829 items -- a list of the lines and blocks within this block
1830 label_dict -- a dictionary of {label name : line index}
1831 inner_indent -- the indentation of our "inner" lines
1832 outer -- true if we are an "outer" block
1833 (i.e., not contained within another block)
1836 def __init__(self,startline=None,outer=0,file=None):
1837 """Instantiate a new block.
1839 startline -- the line that introduces this block
1840 outer -- true if we are an outer block
1841 file -- the "file" we're reading lines from
1844 # Pretend to be our own startline (as a generic)
1845 ContentLine.__init__(self,
1846 startline.lineno,startline.indent_str,
1847 startline.tagtuple,startline.comment,
1850 # But also remember the specifics of the startline
1851 self.startline = startline
1853 # We "fudge" our class name
1854 self.class_name = self._block_class_name(startline)
1859 # If we're an outer table block, do we have a tagobj?
1860 if self.startline.class_name == "TableBlockLine" and outer:
1861 if not self.startline.got_tagobj():
1862 raise NoIdentifier,\
1863 "Tag table at line %d is not assigned to a variable"%\
1865 elif self.startline.is_subtable:
1866 raise OutsideError,\
1867 "SubTable is not allowed outside a block at line %d"%\
1870 self.items = [] # all lines within this block
1871 self.business = [] # just those that are "our business"
1872 self.label_dict = {} # remember our labels and their locations
1873 self.next_index = 0 # 'business' line indices
1874 self.inner_indent = None
1876 # Eat lines until we reach the end of our block...
1877 if DEBUGGING: print "%sStart %s"%(self.indent_str,self.class_name)
1881 def _block_class_name(self,startline):
1882 """Return a representation of the class name."""
1884 full_name = "%s"%self.__class__
1885 bits = string.split(full_name,".")
1886 return "%s/%s"%(bits[-1],startline.class_name)
1888 def _eat_lines(self):
1889 """Eat lines until we run out of block..."""
1893 nextline = self.file.next()
1897 # Check the indentation makes sense...
1898 if self.inner_indent:
1899 # We already know how much our block is indented
1900 # - is this line part of the block?
1901 if nextline.indent < self.inner_indent:
1902 # Apparently a dedent - is it what we expect?
1903 if nextline.indent <= self.indent:
1904 # Unread that line - it isn't one of ours!
1909 "Line %d (%s) is indented less than the previous "\
1910 "line, but its indentation doesn't match the "\
1911 "start of the block at line %d"%\
1912 (nextline.lineno,nextline.class_name,self.lineno)
1913 elif nextline.indent > self.inner_indent:
1915 # (note that doing this stops us from coping with,
1916 # for instance, things in (..), but then we also don't
1917 # cope with any form of continued line, or lots of other
1918 # things, so let's not worry too much for now!)
1920 "Line %d (%s) is indented more than the previous line"%\
1921 (nextline.lineno,nextline.class_name)
1923 # This is the first line of the (inside of) the block
1924 # - check its indentation makes sense...
1925 self.inner_indent = nextline.indent
1926 if self.inner_indent <= self.indent:
1928 "Line %d (%s) should be indented more than line %d (%s)"%\
1929 (nextline.lineno,nextline.class_name,
1930 self.lineno,self.startline.class_name)
1932 # Is it a line or the start of another block?
1933 if nextline.starts_block():
1934 # Heh - it's the start of an inner block - add it
1935 # (remember that instantiating it causes it to
1936 # "eat" the lines that belong to it)
1937 self.items.append(Block(startline=nextline,
1938 outer=0,file=self.file))
1940 self.items.append(nextline)
1942 def _end_block(self):
1945 if DEBUGGING: print "%sEnd %s"%(self.indent_str,self.class_name)
1947 # If we're a tuple block, we should only have one line...
1948 # (that is, one "business" line)
1949 if self.startline.class_name == "TupleBlockLine" and \
1950 len(self.items) > 1:
1951 # Are all but one of them not "our business"?
1953 for item in self.items:
1954 if item.our_business():
1958 self.error("Tuple declaration can only contain one 'business'"
1960 "Assuming it's a table instead (i.e.,"
1961 "'Table is:' instead of 'is:')"%len(self.items))
1962 # Can we correct this by "pretending" its a table?
1963 temp = TableBlockLine(self.startline.lineno,
1964 self.startline.indent_str,
1965 ("error",self.startline.name),
1966 self.startline.comment,
1968 self.startline = temp
1970 # We've now got all of our lines, and so we can go back over
1971 # them, expanding out any IF blocks (whose content is actually
1972 # within this block's scope, so who need to have their labels
1973 # (come from or go to) in that scope), working out the label
1974 # indices, and so on...
1975 # This uses "next_index" to calculate the indices of business
1976 # lines (needed for label calculation), and also populates the
1977 # "business" list with just the items that are "our_business()"
1979 print "Expanding IF blocks, sorting out labels, etc."
1984 if item.class_name == "Block/IfBlockLine":
1985 self._add(item.startline)
1986 for thing in item.items:
1991 # Go back through our contents and resolve any labels
1993 print "%s...processing labels (next_index=%d)"%(self.indent_str,
1995 self.startline.resolve_labels(self)
1996 # If we're an IF block, we mustn't try to resolve our component
1997 # lines' labels, as they're actually in our parent block's scope...
1998 if self.startline.class_name != "IfBlockLine":
1999 for item in self.items:
2000 item.resolve_labels(self)
2002 # If we're in a block that wants to suppress the comma at the
2003 # end of the last item in that block, tell the last item so...
2004 # (this is debatable for [Bad]TableBlockLine - it might be
2005 # better to leave the last comma there - so we have an option
2006 # to determine it...
2007 if self.startline.class_name == "TupleBlockLine" or \
2008 (not WANT_LAST_COMMA and \
2009 (self.startline.class_name == "TableBlockLine" or \
2010 self.startline.class_name == "BadTableBlockLine")):
2011 if len(self.business) > 0:
2012 self.business[-1].is_last = 1
2014 def _add(self,item):
2015 """Add a line or block to our list of items.
2017 item -- the Line or Block instance to add
2019 NB: Also adds it to our "business" list if it is our business
2023 if item.class_name == "LabelLine":
2024 self.label_dict[item.label] = self.next_index
2026 print "%sadd [%2d] %s"%(item.indent_str,self.next_index,item)
2027 # Might as well give it the index it is labelling
2028 item.index = self.next_index
2029 self.items.append(item)
2030 elif item.our_business():
2031 item.index = self.next_index
2032 self.items.append(item)
2033 self.business.append(item)
2035 print "%sadd %2d %s"%(item.indent_str,
2036 self.next_index,item)
2037 self.next_index = self.next_index + 1
2039 # It's not something we can assign a sensible index to, so don't
2041 print "%sadd xx %s"%(item.indent_str,item)
2042 self.items.append(item)
2044 def translate_label(self,label,line):
2045 """Given a label, return its translation.
2047 label -- either a string of the form "<...>" to look up in
2048 this block's label dictionary, or one of the special
2049 targets (e.g., next, MatchOk, etc.)
2050 line -- the line using this label
2052 Reports an error and just returns the original "label" if it
2055 if self.label_dict.has_key(label):
2056 # How far do we have to jump?
2057 offset = self.label_dict[label] - line.index
2059 elif label == "MatchOk":
2061 elif label == "MatchOK":
2062 line.warning("Label 'MatchOK' should be spelt 'MatchOk'"
2063 " (using 'MatchOk')")
2065 elif label == "MatchFail":
2067 elif label == "next":
2069 elif label == "previous":
2071 elif label == "repeat":
2074 line.error("Undefined label '%s'"%label)
2077 def expand(self,stream,block=None):
2078 """Write out the expanded equivalent of ourselves.
2080 stream -- an object with a "write" method, e.g., a file
2081 block -- if we're in a block, this is it, otherwise None
2084 self.startline.expand(stream,block=block)
2085 for item in self.items[:-1]:
2086 item.expand(stream,block=self)
2088 self.items[-1].expand(stream,block=self)
2090 # Deal with closing any block parentheses
2091 if self.startline.class_name == "TableBlockLine" or \
2092 self.startline.class_name == "BadTableBlockLine":
2094 stream.write("Line ...: ")
2096 stream.write(self.indent_str)
2098 # Outer block - just close it
2101 # Inner block is a Table block, and we need to close both
2102 # the tuple-of-tuples, and also the tuple containing the
2105 if not self.is_last:
2110 # ------------------------------------------------------------
2112 """This is the class that holds our processed data
2115 lines -- a list of the line instances for each "line" in our text
2116 items -- a list of lines and BLOCKs
2119 def __init__(self,tagtuples,text):
2120 """Instantiate a File
2122 tagtuples -- the list of mxTextTools tag tuples generated by
2123 parsing the data in "text"
2124 text -- the text we parsed
2128 self.tagtuples = tagtuples
2130 # Assemble our list of lines
2131 print "Pass 1: assembling lines"
2132 if DEBUGGING: print "~~~~~~~~~~~~~~~~~~~~~~~~"
2136 for tagtuple in tagtuples:
2138 thisline = LineFactory(lineno,tagtuple,text)
2141 prevline.next_indent = thisline.indent
2143 self.lines.append(thisline)
2146 #if DEBUGGING: print
2148 # The indentation of an empty line is taken to be the same
2149 # as the indentation of the first following non-empty line
2150 # The easiest way to do that is to work backwards through
2151 # the list (is it better to take a copy and reverse THAT,
2152 # or to reverse our original list twice?)
2153 print "Pass 2: sorting out indentation of empty lines"
2154 if DEBUGGING: print "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
2155 revlist = self.lines[:]
2158 for line in revlist:
2159 if line.class_name == "EmptyLine":
2160 line.change_indent(indent)
2162 indent = line.indent
2166 print "Pass 2.5 - the contents of those lines..."
2167 print "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
2168 for line in self.lines:
2169 print "Line %d %s"%(line.lineno,line.class_name)
2170 #print_tuples([line.tagtuple],self.text," ")
2173 # Now we need to assemble blocks
2174 print "Pass 3: assembling blocks"
2175 if DEBUGGING: print "~~~~~~~~~~~~~~~~~~~~~~~~~"
2186 print "%sTOP %s"%(item.indent_str,item)
2187 if item.starts_block():
2188 block = Block(startline=item,outer=1,file=self)
2189 self.items.append(block)
2190 block.is_last = 1 # Everything at outer level is "last"
2192 if item.only_in_block():
2193 item.error("This line is not allowed outside a block "
2194 "- continuing anyway")
2195 self.items.append(item)
2196 if item.our_business():
2197 item.is_last = 1 # Everything at outer level is "last"
2203 """Ensure that the next call of "nextline" returns the first line."""
2207 """Unread the current line."""
2208 self.index = self.index - 1
2213 """Retrieve the next line from the list of lines in this "file".
2215 Raises EOFError if there is no next line (i.e., "end of file")
2217 self.index = self.index + 1
2219 return self.lines[self.index]
2221 # leave the index off the end, so we get EOF again if
2222 # we're called again - but there's no point courting overflow...
2223 self.index = self.index -1
2226 def expand(self,stream):
2227 """Expand out the result."""
2228 for item in self.items:
2232 # ------------------------------------------------------------
2233 def print_tuples(tuples,text,indent=""):
2234 """Print out a list of tuples in a neat form
2236 tuples -- our tuple list
2237 text -- the text it tags
2238 indent -- our current indentation
2241 # Tuples are of the form:
2242 # (object,left_index,right_index,sublist)
2244 for obj,left,right,sub in tuples:
2246 print "%s%s"%(indent,obj)
2247 print_tuples(sub,text,indent+" ")
2249 # Terminal node - show the actual text we've tagged!
2250 print "%s%s = %s"%(indent,obj,`text[left:right]`)
2253 # ------------------------------------------------------------
2254 def print_text(text):
2255 """Print out text with line numbers."""
2256 lines = string.split(text,"\n")
2259 print "Original text"
2260 print "============="
2263 print "%3d: %s"%(lineno,`line`)
2266 # ------------------------------------------------------------
2267 def print_usage(argv0):
2268 #script_name = string.split(argv0, os.sep)[-1]
2269 #print __doc__%(script_name)
2274 # ------------------------------------------------------------
2275 def show_tup(indent,nn,tup):
2278 if type(item) == type((1,)) or type(item) == type([]):
2284 print "%s%d: (%s)"%(indent,nn,string.join(ll,","))
2286 print "%s(%s)"%(indent,string.join(ll,","))
2288 def comp_sub(indent,one,two):
2290 if len(two) != len(one):
2291 print "%sTuple lengths differ - 1:%d, 2:%d"%(indent,len1,len(two))
2292 show_tup(indent,1,one)
2293 show_tup(indent,2,two)
2294 # If this is all, let's try to continue...
2295 len1 = min(len1,len(two))
2297 for count in range(len1):
2300 if type(a) != type(b):
2301 print "%sValue types differ, item %d: 1:%s, 2:%s"%(indent,count,
2303 show_tupe(indent,1,one)
2304 show_tupe(indent2,two)
2306 if type(a) == type((1,)) or type(a) == type([]):
2307 if not comp_sub(indent+" ",a,b):
2308 # They're the same at this level, so show only one...
2309 show_tup(indent,0,one)
2313 print "%sValues differ, item %d: 1:%s, 2:%s"%(indent,count,
2315 show_tup(indent,1,one)
2316 show_tup(indent,2,two)
2320 def compare_tagtables(one,two):
2321 # Each table is made up of tuples of the form
2322 # (tagobj,action,arg,onfalse,ontrue)
2323 # but if action is Table or SubTable then arg may be a tuple
2325 if comp_sub("",one,two):
2326 print "They appear to be the same"
2329 # ------------------------------------------------------------
2331 """Used to test the module."""
2333 debug_pytag = DEFAULT_DEBUG
2334 use_pytag = DEFAULT_PYTAG
2340 if os.name == "posix":
2349 # Do we have command line arguments?
2350 arg_list = sys.argv[1:]
2354 if len(arg_list) == 0:
2359 if word == "-pytag":
2361 elif word == "-debug":
2363 elif word == "-stdout":
2365 elif word == "-force":
2367 elif word == "-import":
2369 elif word == "-compare":
2371 elif word == "-diag":
2374 elif word == "-test":
2377 elif word == "-help":
2378 print_usage(sys.argv[0])
2380 elif word == "-version":
2381 print "Version:",__version__
2383 elif word == "-history":
2390 arg_list = arg_list[1:]
2394 from Translate_tags import t_file
2395 i_file = define_tagtable()
2396 print "Comparing internal table (1) against external (2)"
2397 compare_tagtables(i_file,t_file)
2400 if not use_testdata and (not args or len(args) > 2):
2401 print_usage(sys.argv[0])
2404 if not use_testdata:
2408 print "Importing tag table definition"
2409 from Translate_tags import t_file
2411 print "Using internal tag table definition"
2412 t_file = define_tagtable()
2415 outfile = "standard output"
2419 base,ext = os.path.splitext(infile)
2421 outfile = base + ".py"
2423 print "Input file has extension .py so won't guess"\
2427 if outfile != "standard output":
2428 if outfile == infile:
2429 print "The output file is the same as the input file"
2430 print "Refusing to overwrite %s"%outfile
2432 elif os.path.exists(outfile):
2434 print "Output file %s already exists"\
2435 " - overwriting it"%outfile
2437 print "Output file %s already exists"%outfile
2440 # Read the input file
2443 print "Using test data"
2444 if DEBUGGING: print "==============="
2448 print "Reading text from %s"%infile
2449 if DEBUGGING: print "=================="+"="*len(infile)
2450 file = open(infile,"r")
2454 # Show what we are trying to parse
2455 if DEBUGGING or use_testdata:
2461 print "Tagging text"
2462 if DEBUGGING: print "============"
2465 pytag.set_verbosity(0)
2467 pytag.set_verbosity(1)
2468 pytag.use_debugger()
2469 result,taglist,next = pytag.pytag(text,t_file)
2471 timer = TextTools._timer()
2473 result, taglist, next = tag(text,t_file)
2474 #result, taglist, next = tag(text,t_file,0,len(text),taglist)
2475 print "Tagging took",timer.stop()[0],"seconds"
2477 # Now print out the result of the tagging
2479 print "Manipulating tagged data"
2480 if DEBUGGING: print "========================"
2481 tagfile = File(taglist,text)
2484 print "Writing translation to %s"%outfile
2485 if DEBUGGING: print "======================="+"="*len(outfile)
2487 # Open the output file, if necessary
2491 file = open(outfile,"w")
2493 tagfile.expand(file)
2496 # ------------------------------------------------------------
2497 if __name__ == '__main__':