Salome HOME
CCAR: Modification principale : ajout de la possibilité d'afficher les noms de
[tools/eficas.git] / convert / Parserv5 / Translate.py
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.
8 #
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.
13 #
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.
17 #
18 #
19 # ======================================================================
20
21 #!/bin/env python -d
22 #!/tools/net/app/Python-1.5.2/bin/python1.5
23
24 """Translate - a first attempt at parsing my little language
25
26 Usage: Translate [switches] <infile> [<outfile>]
27
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)
31
32         -import         -- import tag table from Translate_tags.py,
33                            instead of using the internal table
34
35         -compare        -- compare the imported and internal tag tables
36                            (development option!)
37
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...
45
46         -help           -- show this text
47         -history        -- show the module history
48         -version        -- show the module version
49
50 If <outfile> is not specified, <infile> will be used with its extension
51 replaced by ".py".
52 """
53
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/
57 """
58 __version__ = "0.3 (tiepin) of 1999-11-15"
59 __history__ = """\
60 Originally created 1999-08-13
61
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.
64
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.
68 """
69
70 import sys
71 import os
72 import string
73
74 # ............................................................
75 # How we want to work things - this is fudge for me in initial development
76 if os.name == "posix":
77     # Unix at work
78     DEFAULT_DEBUG = 0
79     DEFAULT_PYTAG = 0
80 else:
81     # Windows 95 at home
82     TEXTTOOLS_PATH = "C:\\Program Files\\Python"
83     PYTAG_PATH  = "C:\\Program Files\\Python\\TextTools\\Examples"
84     DEFAULT_DEBUG = 0
85     DEFAULT_PYTAG = 0
86
87     if TEXTTOOLS_PATH not in sys.path:
88         print "Adding",TEXTTOOLS_PATH
89         sys.path.append(TEXTTOOLS_PATH)
90
91     if PYTAG_PATH not in sys.path:
92         print "Adding",PYTAG_PATH
93         sys.path.append(PYTAG_PATH)
94 # ............................................................
95
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...
99 try:
100     from TextTools import *
101 except:
102     from mx.TextTools import *
103     #from TextTools.Constants.TagTables import *
104     #from TextTools.Constants.Sets import *
105
106 \f
107 # ------------------------------------------------------------
108 # Useful little constants for unpicking the parsed tuples
109 OBJECT  = 0
110 LEFT    = 1
111 RIGHT   = 2
112 SUBLIST = 3
113
114 # We want to align inline comments when possible - so this is
115 # the column at which we will try to place their "#" marks...
116 COMMENT_COLUMN = 40
117
118 # Are we (generally) debugging?
119 DEBUGGING = 0
120
121 # Do we want a comma after the last tuple (or item) in a table?
122 WANT_LAST_COMMA = 1
123
124 \f
125 # ------------------------------------------------------------
126 def define_tagtable():
127     """Returns our tag table, if we're not importing it."""
128
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!)
133
134
135     # Whitespace is always useful
136     t_whitespace = (None,AllIn,' \t')
137     t_opt_whitespace = t_whitespace + (+1,)
138
139     # Comments are fairly simple
140     t_comment = ('comment',Table,
141                  ((None,Is,'#'),
142                   (None,AllNotIn,'\n\r',MatchOk))
143                  )
144
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
149
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)
155
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),
162                  (None,Is,'"'),
163                  ('text',AllNotIn,'"'),
164                  (None,Is,'"'),
165                  ))
166
167     # An integer is a series of digits...
168     t_integer = ('int',AllIn,number)
169     
170     t_signed_integer = ('signed_int',Table,
171                         (('sign',Is,"+",+1,+2),
172                          ('sign',Is,"-",+1,+1),
173                          t_integer
174                          ))
175
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),
208                     ))
209
210     # Python keywords
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
247                   #  by ":")
248                   (None,Skip,-1)
249                   ))
250
251     # Do the same for mxText commands
252     t_mxkeyword = ('mxKeyword',Table,
253                    (t_operation,
254                     (None,IsIn,alpha+'_'+number,+1,MatchFail),
255                     (None,Skip,-1)
256                     ))
257
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))
264                     )
265
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
280                    ))
281
282     t_plus = ('plus',Table,
283               (t_opt_whitespace,
284                (None,Is,"+"),
285                t_opt_whitespace
286                ))
287
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 "+"
294                    ))
295
296     # Match, for example:
297     #        <fred>
298     t_label = ('label',Table,
299                ((None,Is,"<"),
300                 t_identifier,
301                 (None,Is,">")
302                 ))
303
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),
312                  t_label
313                  ))
314
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),
319                 t_integer
320                 ))
321
322     # An assignment is (optionally) used in Tuple and Table definitions...
323     t_assignment = ('assignment',Table,
324                     (t_value,
325                      t_opt_whitespace,
326                      (None,Is,'='),
327                      ))
328
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
332     #  "real thing")
333     t_bad_tagobj = ('tagobj',Table,
334                     (t_string,
335                      ))
336
337     t_bad_assignment = ('assignment',Table,
338                         (t_value,
339                          ))
340
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:
344     #        Fred is:
345     t_tupleblock = ('tupleblock',Table,
346                     (t_identifier,
347                      t_whitespace,
348                      (None,Word,"is:")
349                      ))
350
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
361                      t_opt_whitespace,
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
366                      ))
367
368     # This is the line that starts an "if" block
369     # Match, for example:
370     #        Is "Fred":
371     #        controlsymbol:
372     t_ifblock = ('ifblock',Table,
373                  (t_assignment + (+2,+1),      # left hand side is optional
374                   t_opt_whitespace,
375                   t_operation + (+4,+1),
376                   t_whitespace,
377                   t_plus_arg,
378                   (None,Is,":",MatchFail,MatchOk),
379                   t_identifier,
380                   (None,Is,":")
381                   ))
382
383     # Note that we don't allow spaces WITHIN our false and true thingies
384
385     t_onfalse = ('onfalse',Table,
386                  (t_whitespace,
387                   (None,Word,"F:"),
388                   t_target
389                   ))
390
391     t_ontrue = ('ontrue',Table,
392                 (t_whitespace,
393                  (None,Word,"T:"),
394                  t_target
395                  ))
396
397     # Valid examples are things like:
398     #        'fred' = Is "xxx" F:<wow> T:MatchOk
399     #       AllIn jim T:<foundJim>
400     #
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
404                 t_opt_whitespace,
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
410                 ))
411
412     # If the user has defined a "partial" tuple, they might use something
413     # of the form:
414     #       match_fred  F:MatchFalse T:MatchOk
415     t_tupleplus = ('tupleplus',Table,
416                    (t_identifier,
417                     t_onfalse + (+1,+1),          # F:target is optional
418                     t_ontrue  + (MatchOk,MatchOk) # T:target is also optional
419                     ))
420
421     # Treat Jump To specially - for example:
422     #       Jump To <top>
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,
427                 ((None,Word,"Jump"),
428                  t_whitespace,
429                  (None,Word,"To"),
430                  t_whitespace,
431                  t_target
432                  ))
433
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
439                      t_target
440                      ))
441
442     # The "content" of a line is the bit after any indentation, and before
443     # any comment...
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>]]
454                   ))
455
456     t_contentline = ('contentline',Table,
457                      (t_content,            # something that we care about
458                       t_opt_whitespace,
459                       t_comment   +(+1,+1), # always allow a comment
460                       (None,IsIn,newline)   # the end of the line
461                       ))
462
463     # Sometimes, the user (e.g., me) writes:
464     #   'fred' = Table:
465     # instead of:
466     #   'fred' = Table is:
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
471                          t_opt_whitespace,
472                          (None,Word,"Table"),     # "Table" is required
473                          (None,Is,":")            # "is" is needed before the ":"
474                          ))
475
476     # Sometimes, the use (e.g., me again) write:
477     #   'fred' IsIn jim
478     # instead of:
479     #   'fred' = IsIn jim
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
490                     ))
491
492     # Make some attempt to recognise common errors...
493     t_badcontent = ('badcontent',Table,
494                     (t_bad_tableblock +(+1,MatchOk),
495                      t_bad_tuple
496                      ))
497
498     t_badline = ('badline',Table,
499                  (t_badcontent,         # something that we sort of care about
500                   t_opt_whitespace,
501                   t_comment   +(+1,+1), # always allow a comment
502                   (None,IsIn,newline)   # the end of the line
503                   ))
504
505     t_emptyline = ('emptyline',Table,
506                    (t_opt_whitespace,
507                     (None,IsIn,newline)     # the end of the line
508                     ))
509
510     t_commentline = ('commentline',Table,
511                      (t_comment,
512                       (None,IsIn,newline)   # the end of the line
513                       ))
514
515     t_passthruline = ('passthruline',Table,
516                       (('passthru',AllNotIn,newline,+1), # owt else on the line
517                        (None,IsIn,newline)               # the end of the line
518                        ))
519
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
528                ))
529
530     t_file = (t_line,
531               (None,EOF,Here,-1)
532               )
533
534     return t_file
535
536 \f
537 # ------------------------------------------------------------
538 # We'll define some moderately interesting test data
539
540 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 *
544 # Some Python code
545 a = b;
546 fred = 3;
547 if a == 1:
548     print "a == 1"
549 else:
550     print "a != 1"
551
552 # And the rest is our business...
553 t_integer is:
554     'int' = AllIn '0123456789'
555 t_integer is:
556     'int' = AllIn number
557 t_indent is:
558     # A comment here is OK
559     <label> # Strangely enough, so is a label
560     'indent' = AllIn ' \t'
561 t_buggy = Table is:
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
567
568     # The next line contains just indentation
569
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:"
573     t_indent
574     t_indent F:previous
575     t_indent T:previous
576     t_indent F:previous T:previous
577 t_deep = Table is:
578     'a' = SubTable is:
579         SubTable is:
580             'this' = Table ThisTable
581             t_integer
582 t_fred = Table is:
583     <top>
584     AllIn 'a'
585     'a' = AllIn 'a'
586     'a' = AllIn 'a' F:previous
587     'a' = AllIn 'a' T:previous
588     'a' = AllIn 'a' F:previous T:previous
589     AllIn 'abcd':
590         AllIn 'xyz' F:<later> T:<top>
591     'a' = AllIn 'abcd':
592         AllIn 'xyz'
593     <later>
594     t_indent:
595         AllIn 'xyz'
596     AllIn number + '_'
597     AllIn number+"_"+alpha
598     Jump To <top>
599 """
600
601 \f
602 # ------------------------------------------------------------
603 # Our own exceptions
604
605 class OutsideError(Exception):
606     """The entity is not permitted outside a block."""
607     pass
608
609 class IndentError(Exception):
610     """An indentation error has been detected."""
611     pass
612
613 class NoIdentifier(Exception):
614     """We're missing an identifier (to assign to)."""
615     pass
616
617 \f
618 # ------------------------------------------------------------
619 def LineFactory(lineno,tagtuple,text):
620     """Take some tagged data and return an appropriate line class.
621
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
627                 of the file)
628
629     The tag tuples we get back from the parser will be of the form:
630
631         ('line',left,right,[
632           ('indent',left,right,None),    -- this is optional
633           ('content',left,right,[<data>])
634         ])
635
636     Looking at <type> should enable us to decide what to do with
637     the <data>.
638     """
639
640     # Extract the list of tuples from this 'line'
641     tuples = tagtuple[SUBLIST]
642
643     # First off, do we have any indentation?
644     tup = tuples[0]
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]])
650         tuples = tuples[1:]
651     else:
652         indent_str = ""
653         tuples = tuples
654
655     # Now, work out which class we want an instance of
656     # (this is the 'fun' bit)
657
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]
668
669         # Do we also have an in-line comment?
670         if len(sublist) > 1:
671             comment = sublist[1]
672         else:
673             comment = None
674
675         # And the actual DATA for our line is down yet another level...
676         sublist = sublist[0][SUBLIST]
677         type = sublist[0][OBJECT]
678         if type == 'label':
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)
692         else:
693             raise ValueError,\
694                   "Line %d is of unexpected type 'contentline/%s'"%(lineno,
695                                                                     type)
696     elif type == 'badline':
697         # OK - we need to go down another level
698         sublist = tuples[0][SUBLIST]
699
700         # Do we also have an in-line comment?
701         if len(sublist) > 1:
702             comment = sublist[1]
703         else:
704             comment = None
705
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)
711         if type == 'tuple':
712             return BadTupleLine(lineno,indent_str,sublist[0],comment,text)
713         else:
714             raise ValueError,\
715                   "Line %d is of unexpected type 'badline/%s'"%(lineno,type)
716     else:
717         raise ValueError,"Line %d is of unexpected type '%s'"%(lineno,type)
718
719
720 \f
721 # ------------------------------------------------------------
722 class BaseLine:
723     """The base class on which the various line types depend
724
725     Contains:
726
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)
734
735     Some things only get useful values after we've been instantiated
736     
737       next_indent -- the indentation of the next line
738       index       -- for a line in a block, its index therein
739     """
740
741     def __init__(self,lineno,indent_str,tagtuple,text):
742         """Instantiate a BaseLine.
743
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)
751
752         The content of the tagtuple depends on which of our subclasses
753         is being used. Refer to the relevant doc string.
754         """
755
756         self.tagtuple = tagtuple
757         self.lineno   = lineno
758         self.text     = text
759
760         self.class_name = self._class_name()
761         self.indent_str = indent_str
762         self.indent     = len(indent_str)
763
764         # OK - we don't really know! (but this will do for "EOF")
765         self.next_indent = 0
766
767         # We don't always HAVE a sensible value for this
768         self.index = None
769
770         #if DEBUGGING:
771         #    print "Line %3d: %s%s"%(lineno,indent_str,self.class_name)
772
773     def change_indent(self,count=None,spaces=""):
774         """Change our indentation.
775
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)
779         
780         count  -- the number of spaces we're indented by
781         spaces -- a string of spaces
782         """
783         if count:
784             self.indent = count
785             self.indent_str = count * " "
786         else:
787             self.indent_str = spaces
788             self.indent = len(spaces)
789
790     def _class_name(self):
791         """Return a representation of the class name."""
792
793         full_name = "%s"%self.__class__
794         bits = string.split(full_name,".")
795         return bits[-1]
796
797     def starts_block(self):
798         """Return true if we start a new block."""
799         return 0
800
801     def only_in_block(self):
802         """Return true if we can only occur inside a block."""
803         return 0
804
805     def our_business(self):
806         """Return true if we are a line we understand."""
807         return 1
808
809     def __str__(self):
810         return "%3d %s%-10s"%(self.lineno,self.indent_str,self.class_name)
811
812     def _intro(self):
813         """Returns a useful 'introductory' string."""
814         return "%3d %-10s %s"%(self.lineno,self.class_name,self.indent_str)
815
816     def _trunc(self):
817         """Returns a "truncated" representation of our text."""
818
819         text = "%s %s"%(self._intro(),
820                         `self.text[self.tagtuple[LEFT]:self.tagtuple[RIGHT]]`)
821
822         if len(text) > 60:
823             return text[:60]+"..."
824         else:
825             return text
826
827     def resolve_labels(self,block):
828         """Called to resolve any labels use in this line.
829
830         block -- the block that contains us
831         """
832         # The default is to do nothing as we don't HAVE any labels...
833         return
834
835     def expand(self,stream,block=None):
836         """Write out the expanded equivalent of ourselves.
837
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
842         """
843
844         if DEBUGGING:
845             stream.write("Line %3d: "%self.lineno)
846
847         stream.write(self.indent_str)
848         stream.write(self.text[self.tagtuple[LEFT]:self.tagtuple[RIGHT]])
849         stream.write(",\n")
850
851     def warning(self,text):
852         """Report a warning message.
853
854         text -- the text to report
855         """
856
857         lines = string.split(text,"\n")
858         print "###WARNING: line %d (%s)"%(self.lineno,self.class_name)
859         for line in lines:
860             print "###         %s"%line
861
862     def error(self,text):
863         """Report an error.
864
865         text -- the error text to report
866         """
867
868         lines = string.split(text,"\n")
869         print "###ERROR: line %d (%s)"%(self.lineno,self.class_name)
870         for line in lines:
871             print "###       %s"%line
872
873 \f
874 # ------------------------------------------------------------
875 class EmptyLine(BaseLine):
876     """An empty line.
877
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!)
884     """
885
886     def __init__(self,lineno,indent_str,tagtuple,text):
887         """Instantiate an EmptyLine.
888
889         The content of the tagtuple is:
890             None
891         """
892
893         BaseLine.__init__(self,lineno,indent_str,tagtuple,text)
894
895     def expand(self,stream,block=None):
896         """Write out the expanded equivalent of ourselves.
897
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
901         """
902
903         if DEBUGGING:
904             stream.write("Line %3d: "%self.lineno)
905
906         # um - there's nothing to do, folks
907         stream.write("\n")
908
909     def our_business(self):
910         """Return true if we are a line we understand."""
911         return 0
912
913     def _trunc(self):
914         """Returns a "truncated" representation of our text."""
915
916         return self._intro()
917
918 \f
919 # ------------------------------------------------------------
920 class CommentLine(BaseLine):
921     """A comment line."""
922
923     def __init__(self,lineno,indent_str,tagtuple,text):
924         """Instantiate a CommentLine.
925
926         The content of the tagtuple is:
927             ('comment',left,right,None)
928         and the demarcated text includes the initial '#' character
929         """
930
931         BaseLine.__init__(self,lineno,indent_str,tagtuple,text)
932
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]]
937
938     def our_business(self):
939         """Return true if we are a line we understand."""
940         return 0
941
942     def expand(self,stream,block=None):
943         """Write out the expanded equivalent of ourselves.
944
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
948         """
949
950         if DEBUGGING:
951             stream.write("Line %3d: "%self.lineno)
952
953         stream.write(self.indent_str)
954         stream.write("%s\n"%self.data)
955
956 \f
957 # ------------------------------------------------------------
958 class PassThruLine(BaseLine):
959     """A line we just pass throught without interpretation."""
960
961     def __init__(self,lineno,indent_str,tagtuple,text):
962         """Instantiate a PassThruLine.
963
964         The content of the tagtuple is:
965             ('passthru',left,right,None)
966         """
967
968         BaseLine.__init__(self,lineno,indent_str,tagtuple,text)
969
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]]
974
975     def our_business(self):
976         """Return true if we are a line we understand."""
977         return 0
978
979     def expand(self,stream,block=None):
980         """Write out the expanded equivalent of ourselves.
981
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
985         """
986
987         if DEBUGGING:
988             stream.write("Line %3d: "%self.lineno)
989
990         if block:
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?"
997
998             self.error(err_str)
999
1000         # Always output the indentation, 'cos otherwise it looks silly
1001         stream.write(self.indent_str)
1002
1003         if block:
1004             stream.write("#[ignored]#")
1005
1006         stream.write("%s\n"%self.data)
1007
1008 \f
1009 # ------------------------------------------------------------
1010 class ContentLine(BaseLine):
1011     """A line we have to interpret - another base class.
1012
1013     Adds the following variables:
1014
1015     comment -- any in-line comment on this line
1016     """
1017
1018     def __init__(self,lineno,indent_str,tagtuple,comment,text):
1019         """Instantiate a ContentLine.
1020
1021         comment -- either a comment tuple or None
1022
1023         The content of the tagtuple is:
1024             ('contentline',left,right,
1025               [('content',left,right,[<data>]),
1026                ('comment',left,right,None)      -- optional
1027               ])
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)
1030         """
1031
1032         BaseLine.__init__(self,lineno,indent_str,tagtuple,text)
1033         self.comment = comment
1034
1035         # Assume we're not the last "our business" line in a block...
1036         self.is_last = 0
1037
1038     def _write_comment(self,stream,sofar):
1039         """Write out the in-line comment string.
1040
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
1043         to output...
1044
1045         stream  -- an object with a "write" method, e.g., a file
1046         sofar   -- the number of characters written to the line
1047                    so far
1048         """
1049         if sofar < COMMENT_COLUMN:
1050             stream.write(" "*(COMMENT_COLUMN - sofar))
1051         else:
1052             # always write at least one space...
1053             stream.write(" ")
1054         stream.write(self.text[self.comment[LEFT]:self.comment[RIGHT]])
1055
1056     def _write_text(self,stream,block):
1057         """Write out the main tuple text.
1058
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
1062
1063         This should generally be the method that subclasses override.
1064         It returns the number of characters written, or -1 if we had
1065         an error.
1066         """
1067         stream.write(self.text[self.tagtuple[LEFT]:self.tagtuple[RIGHT]])
1068         return self.tagtuple[RIGHT] - self.tagtuple[LEFT]
1069
1070     def expand(self,stream,block=None):
1071         """Write out the expanded equivalent of ourselves.
1072
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
1076         """
1077
1078         if DEBUGGING:
1079             stream.write("Line %3d: "%self.lineno)
1080
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)
1087         stream.write("\n")
1088
1089 \f
1090 # ------------------------------------------------------------
1091 class LabelLine(ContentLine):
1092     """A line containing a label.
1093
1094     Contains:
1095         label -- our label string
1096     """
1097
1098     def __init__(self,lineno,indent_str,tagtuple,comment,text):
1099         """Instantiate a LabelLine.
1100
1101         For instance:
1102
1103             <fred>
1104
1105         The content of the tagtuple is:
1106
1107             ('label',left,right,[
1108               ('identifier',left,right,None)
1109              ])
1110         """
1111
1112         ContentLine.__init__(self,lineno,indent_str,tagtuple,comment,text)
1113
1114         self.label = self.text[self.tagtuple[LEFT]:self.tagtuple[RIGHT]]
1115
1116     def _write_text(self,stream,block):
1117         """Write out the main tuple text.
1118
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
1122         """
1123         # Enough difficult length calculation - let's do this one
1124         # the easy way...
1125         if DEBUGGING:
1126             text = "# Label %s at index %d"%(self.label,self.index)
1127         else:
1128             text = "# %s"%(self.label)  # surely enough for most people...
1129         stream.write(text)
1130         return len(text)
1131
1132     def translate(self,index,block):
1133         """Return the translation of a use of this label as a target.
1134
1135         index -- the index of the line which uses the label as a target
1136         block -- the Block we are within
1137         """
1138
1139         # Hmm - I don't think this CAN go wrong at this point...
1140         return block.translate_label(self.label,self)
1141
1142     def only_in_block(self):
1143         """Return true if we can only occur inside a block."""
1144         return 1
1145
1146 \f
1147 # ------------------------------------------------------------
1148 class TableBlockLine(ContentLine):
1149     """A line starting a table block."""
1150
1151     def __init__(self,lineno,indent_str,tagtuple,comment,text):
1152         """Instantiate a TableBlockLine.
1153
1154         For instance:
1155
1156             "fred" = Table is:
1157             Table is:
1158
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)
1164
1165         The content of the tagtuple is:
1166
1167             ('tableblock',left,right,[
1168               ('assignment',left,right,[           -- optional if inner
1169                  ('val',left,right,[
1170
1171                     ('identifier',left,right,[])
1172                  OR
1173                     ('str',left,right,[
1174                        ('text',left,right,None)
1175                      ])
1176                  OR
1177                     ('int',left,right,[])
1178
1179                   ])
1180                ])
1181               ('type',left,right,[])       -- either "Table" or "SubTable"
1182              ])
1183
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...
1188         """
1189
1190         ContentLine.__init__(self,lineno,indent_str,tagtuple,comment,text)
1191
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")
1200         else:
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")
1209
1210     def got_tagobj(self):
1211         return (self.tagobj != "None")
1212
1213     def starts_block(self):
1214         """Return true if we start a new block."""
1215         return 1
1216
1217     def _write_text(self,stream,block):
1218         """Write out the main tuple text.
1219
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
1223
1224         It returns the number of characters written, or -1 if we had
1225         an error.
1226         """
1227
1228         if block:
1229             if self.is_subtable:
1230                 stream.write("(%s,SubTable,("%self.tagobj)
1231                 return len(self.tagobj) + 11
1232             else:
1233                 stream.write("(%s,Table,("%self.tagobj)
1234                 return len(self.tagobj) + 8
1235         else:
1236             stream.write("%s = ("%self.tagobj)
1237             return len(self.tagobj) + 4
1238
1239 \f
1240 # ------------------------------------------------------------
1241 class TupleBlockLine(ContentLine):
1242     """A line starting a tuple block (i.e., defining a single tuple)
1243
1244     Contains:
1245     
1246         name -- the "name" of this tuple (i.e., what comes
1247                 before the "is:")
1248     """
1249
1250     def __init__(self,lineno,indent_str,tagtuple,comment,text):
1251         """Instantiate a TupleBlockLine.
1252
1253         For instance:
1254
1255             Fred is:
1256
1257         The content of the tagtuple is:
1258         
1259             ('tupleblock',left,right,[
1260               ('identifier',left,right,None)
1261              ])
1262         """
1263
1264         ContentLine.__init__(self,lineno,indent_str,tagtuple,comment,text)
1265
1266         tup = self.tagtuple[SUBLIST][0]
1267         self.name = self.text[tup[LEFT]:tup[RIGHT]]
1268
1269     def starts_block(self):
1270         """Return true if we start a new block."""
1271         return 1
1272
1273     def only_in_block(self):
1274         """Return true if we can only occur inside a block."""
1275         return 0
1276
1277     def _write_text(self,stream,block):
1278         """Write out the main tuple text.
1279
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
1283
1284         It returns the number of characters written, or -1 if we had
1285         an error.
1286         """
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
1291
1292 \f
1293 # ------------------------------------------------------------
1294 class IfBlockLine(ContentLine):
1295     """A line starting an if block.
1296
1297     Contains:
1298         cmd  -- the command within this if block
1299         arg  -- the argument for said command
1300     or:
1301         name -- the name within this if block
1302     """
1303
1304     def __init__(self,lineno,indent_str,tagtuple,comment,text):
1305         """Instantiate an IfBlockLine.
1306
1307         For instance:
1308
1309             'jim' = Is "Fred":
1310             Is "Fred":
1311             fred:
1312
1313         The content of the tagtuple is:
1314
1315             ('ifblock',left,right,[
1316               ('assignment',left,right,[
1317                  ('val',left,right,[
1318
1319                     ('identifier',left,right,[])
1320                  OR
1321                     ('str',left,right,[
1322                        ('text',left,right,None)
1323                      ])
1324                  OR
1325                     ('int',left,right,[])
1326
1327                   ])
1328                ])
1329               ('op',left,right,None),
1330               ('arg',left,right,None),
1331              ])
1332         or:
1333             ('ifblock',left,right,[
1334               ('op',left,right,None),
1335               ('arg',left,right,None),
1336              ])
1337         or:
1338             ('ifblock',left,right,[
1339               ('identifier',left,right,None)
1340              ])
1341         """
1342
1343         ContentLine.__init__(self,lineno,indent_str,tagtuple,comment,text)
1344
1345         tuples = self.tagtuple[SUBLIST]
1346         if tuples[0][OBJECT] == 'op':
1347             tup1 = tuples[0]
1348             tup2 = tuples[1]
1349             self.tagobj = "None"
1350             self.cmd    = self.text[tup1[LEFT]:tup1[RIGHT]]
1351             self.arg    = self.text[tup2[LEFT]:tup2[RIGHT]]
1352             self.name   = None
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]]
1358             tup1 = tuples[1]
1359             tup2 = tuples[2]
1360             self.cmd    = self.text[tup1[LEFT]:tup1[RIGHT]]
1361             self.arg    = self.text[tup2[LEFT]:tup2[RIGHT]]
1362             self.name   = None
1363         elif tuples[0][OBJECT] == 'identifier':
1364             tup = tuples[0]
1365             self.name   = self.text[tup[LEFT]:tup[RIGHT]]
1366             self.cmd    = None
1367             self.arg    = None
1368             self.tagobj = None
1369         else:
1370             # Hmm - try to continue with anything unexpected
1371             tup = tuples[0]
1372             self.error("Unexpected IfBlock subtype %s"%tup[OBJECT])
1373             self.name   = self.text[tup[LEFT]:tup[RIGHT]]
1374             self.cmd    = None
1375             self.arg    = None
1376             self.tagobj = None
1377
1378         # Currently, we have one 'special' argument
1379         if self.arg == "back": self.arg = "-1"
1380
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
1384
1385     def starts_block(self):
1386         """Return true if we start a new block."""
1387         return 1
1388
1389     def only_in_block(self):
1390         """Return true if we can only occur inside a block."""
1391         return 1
1392
1393     def resolve_labels(self,block):
1394         """Called to resolve any labels used in this line.
1395
1396         block -- the block that contains us
1397
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...
1402         """
1403         if not self.end_label:
1404             self.end_label = "%+d"%(len(block.business)+1)
1405
1406     def _write_text(self,stream,block):
1407         """Write out the main tuple text.
1408
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
1412
1413         It returns the number of characters written, or -1 if we had
1414         an error.
1415         """
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")
1419
1420         if self.name:
1421             stream.write("%s + (%s,+1),"%(self.name,
1422                                           self.end_label or "<undefined>"))
1423             return len(self.name) + 20
1424         else:
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
1429
1430 \f
1431 # ------------------------------------------------------------
1432 class TupleLine(ContentLine):
1433     """A line containing a basic tuple.
1434
1435
1436     Contains:
1437         tagobj  -- optional
1438         cmd     -- the command
1439         arg     -- the argument
1440         ontrue  -- what to do if true
1441         onfalse -- ditto false
1442     """
1443
1444     def __init__(self,lineno,indent_str,tagtuple,comment,text):
1445         """Instantiate a TupleLine.
1446
1447         The content of the tagtuple is:
1448         
1449             ('tuple',left,right,[
1450               ('tagobj',left,right,[           -- optional
1451                  ('str',left,right,[
1452                     ('text',left,right,None)
1453                   ])
1454                ])
1455               ('op',left,right,None),
1456               ('arg',left,right,None),
1457               ('onfalse',left,right,[          -- optional
1458                  ('target',left,right,[
1459                    ('tgt',left,right,None)
1460                  ]),
1461               ('ontrue',left,right,[           -- optional
1462                  ('target',left,right,[
1463                    ('tgt',left,right,None)
1464                  ])
1465                ])
1466              ])
1467         """
1468
1469         ContentLine.__init__(self,lineno,indent_str,tagtuple,comment,text)
1470
1471         self.unpack()
1472
1473
1474     def unpack(self):
1475         """Unpack our contents from our tagtuple."""
1476
1477         # This is doubtless not the most efficient way of doing this,
1478         # but it IS relatively simple...
1479         dict = {}
1480         #for key in ("assignment","op","arg","onfalse","ontrue"):
1481         for key in ("assignment","op","plusarg","onfalse","ontrue"):
1482             dict[key] = None
1483
1484         tuples = self.tagtuple[SUBLIST]
1485         for item in tuples:
1486             name = item[OBJECT]
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])
1491             else:
1492                 dict[name] = (item[LEFT],item[RIGHT])
1493
1494         # The tag object is optional
1495         if dict["assignment"]:
1496             left,right = dict["assignment"]
1497             self.tagobj = self.text[left:right]
1498         else:
1499             self.tagobj = "None"
1500
1501         # The operation (command) and argument are required
1502         left,right = dict["op"]
1503         self.cmd = self.text[left:right]
1504
1505         #left,right = dict["arg"]
1506         left,right = dict["plusarg"]
1507         self.arg = self.text[left:right]
1508
1509         # Currently, we have one 'special' argument
1510         if self.arg == "back": self.arg = "-1"
1511
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)
1515         if dict["onfalse"]:
1516             left,right = dict["onfalse"]
1517             self.onfalse = self.text[left:right]
1518         else:
1519             self.onfalse = None         # "MatchFail"
1520         if dict["ontrue"]:
1521             left,right = dict["ontrue"]
1522             self.ontrue = self.text[left:right]
1523         else:
1524             self.ontrue = None          # "next"
1525
1526     def only_in_block(self):
1527         """Return true if we can only occur inside a block."""
1528         return 1
1529
1530     def resolve_labels(self,block):
1531         """Called to resolve any labels use in this line.
1532
1533         block -- the block that contains us
1534         """
1535         if self.onfalse:
1536             self.onfalse = block.translate_label(self.onfalse,self)
1537         if self.ontrue:
1538             self.ontrue  = block.translate_label(self.ontrue,self)
1539
1540     def _write_text(self,stream,block):
1541         """Write out the main tuple text.
1542
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
1546
1547         It returns the number of characters written, or -1 if we had
1548         an error.
1549         """
1550
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
1554
1555         if self.ontrue:
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
1559                 # anyway...
1560                 stream.write(",%s,%s)"%("MatchFail",self.ontrue))
1561                 length = length + len("MatchFail") + len(self.ontrue) + 3
1562             else:
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
1566         elif self.onfalse:
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
1574         else:
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
1577             # later on...
1578             # [[Comments as for the case above, I think]]
1579             stream.write(")")
1580             length = length + 1
1581
1582         if block and not self.is_last:
1583             stream.write(",")
1584             length = length + 1
1585
1586         return length
1587 \f
1588 # ------------------------------------------------------------
1589 class TuplePlusLine(ContentLine):
1590     """A line containing a tuple "plus" (e.g., "fred + (+1,+1)").
1591
1592     Contains:
1593
1594         name    -- the name/identifier
1595         ontrue  -- what to do if true
1596         onfalse -- ditto false
1597     """
1598
1599     def __init__(self,lineno,indent_str,tagtuple,comment,text):
1600         """Instantiate a TuplePlusLine.
1601
1602             <identifier> + (onF,onT)
1603
1604         The content of the tagtuple is:
1605         
1606             ('tupleplus',left,right,[
1607               ('identifier',left,right,None)
1608               ('onfalse',left,right,[          -- optional
1609                  ('target',left,right,[
1610                    ('tgt',left,right,None)
1611                  ]),
1612               ('ontrue',left,right,[           -- optional
1613                  ('target',left,right,[
1614                    ('tgt',left,right,None)
1615                  ])
1616                ])
1617              ])
1618         """
1619
1620         ContentLine.__init__(self,lineno,indent_str,tagtuple,comment,text)
1621
1622         self.unpack()
1623
1624
1625     def unpack(self):
1626         """Unpack our contents from our tagtuple."""
1627
1628         # This is doubtless not the most efficient way of doing this,
1629         # but it IS relatively simple...
1630         dict = {}
1631         for key in ("identifier","onfalse","ontrue"):
1632             dict[key] = None
1633
1634         tuples = self.tagtuple[SUBLIST]
1635         for item in tuples:
1636             name = item[OBJECT]
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])
1641             else:
1642                 dict[name] = (item[LEFT],item[RIGHT])
1643
1644         # Start with the identifier
1645         left,right = dict["identifier"]
1646         self.name = self.text[left:right]
1647
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)
1651         if dict["onfalse"]:
1652             left,right = dict["onfalse"]
1653             self.onfalse = self.text[left:right]
1654         else:
1655             self.onfalse = None         # "MatchFail"
1656         if dict["ontrue"]:
1657             left,right = dict["ontrue"]
1658             self.ontrue = self.text[left:right]
1659         else:
1660             self.ontrue = None          # "next"
1661
1662     def only_in_block(self):
1663         """Return true if we can only occur inside a block."""
1664         return 1
1665
1666     def resolve_labels(self,block):
1667         """Called to resolve any labels use in this line.
1668
1669         block -- the block that contains us
1670         """
1671         if self.onfalse:
1672             self.onfalse = block.translate_label(self.onfalse,self)
1673         if self.ontrue:
1674             self.ontrue  = block.translate_label(self.ontrue,self)
1675
1676     def _write_text(self,stream,block):
1677         """Write out the main tuple text.
1678
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
1682
1683         It returns the number of characters written, or -1 if we had
1684         an error.
1685         """
1686
1687         if not self.onfalse and not self.ontrue:
1688             stream.write("%s"%self.name)
1689             length = len(self.name)
1690         else:
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
1698             elif self.ontrue:
1699                 stream.write("MatchFail,%s)"%(self.ontrue))
1700                 length = length + len(self.ontrue) + 11
1701             else:
1702                 # Don't forget that comma to make this a tuple!
1703                 stream.write("%s,)"%(self.onfalse))
1704                 length = length + len(self.onfalse) + 1
1705
1706         if not self.is_last:
1707             stream.write(",")
1708             length = length + 1
1709
1710         return length
1711
1712 \f
1713 # ------------------------------------------------------------
1714 class JumpToLine(ContentLine):
1715     """A line containing "Jump To <label>"
1716
1717     Contains:
1718
1719         name    -- the name/identifier
1720         onfalse -- the target (which is technically an "on false" jump)
1721     """
1722
1723     def __init__(self,lineno,indent_str,tagtuple,comment,text):
1724         """Instantiate a JumpLine.
1725
1726             Jump To <label>
1727
1728         The content of the tagtuple is:
1729         
1730             ('jumpto',left,right,[
1731                ('target',left,right,[
1732                  ('tgt',left,right,None)
1733                ]),
1734              ])
1735         """
1736
1737         ContentLine.__init__(self,lineno,indent_str,tagtuple,comment,text)
1738
1739         tup = self.tagtuple[SUBLIST][0]
1740         self.onfalse = self.text[tup[LEFT]:tup[RIGHT]]
1741
1742     def only_in_block(self):
1743         """Return true if we can only occur inside a block."""
1744         return 1
1745
1746     def resolve_labels(self,block):
1747         """Called to resolve any labels use in this line.
1748
1749         block -- the block that contains us
1750         """
1751         self.onfalse = block.translate_label(self.onfalse,self)
1752
1753     def _write_text(self,stream,block):
1754         """Write out the main tuple text.
1755
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
1759
1760         It returns the number of characters written, or -1 if we had
1761         an error.
1762         """
1763
1764         stream.write("(None,Jump,To,%s)"%(self.onfalse))
1765         length = len(self.onfalse) + 15
1766
1767         if not self.is_last:
1768             stream.write(",")
1769             length = length + 1
1770
1771         return length
1772
1773 \f
1774 # ------------------------------------------------------------
1775 class BadTableBlockLine(TableBlockLine):
1776     """We think they MEANT this to be a table block line."""
1777
1778     def __init__(self,lineno,indent_str,tagtuple,comment,text):
1779         """Instantiate a BadTableBlockLine.
1780
1781         For instance:
1782
1783             "fred" = Table:
1784             Table:
1785         """
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")
1789
1790 \f
1791 # ------------------------------------------------------------
1792 class BadTupleLine(TupleLine):
1793     """We think they MEANT this to be a tuple line."""
1794
1795     def __init__(self,lineno,indent_str,tagtuple,comment,text):
1796         """Instantiate a BadTupleLine.
1797
1798         For instance:
1799
1800             "fred" = IsIn "abc"
1801         """
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")
1805
1806 \f
1807 # ------------------------------------------------------------
1808 class Block(ContentLine):
1809     """This class represents a "block".
1810
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
1814     encountered.
1815
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
1819     again, and so on.
1820
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...
1825
1826     Contains:
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)
1833     """
1834
1835     def __init__(self,startline=None,outer=0,file=None):
1836         """Instantiate a new block.
1837
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
1841         """
1842
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,
1847                              startline.text)
1848
1849         # But also remember the specifics of the startline
1850         self.startline = startline
1851
1852         # We "fudge" our class name
1853         self.class_name = self._block_class_name(startline)
1854
1855         self.outer    = outer
1856         self.file     = file
1857
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"%\
1863                       (self.lineno)
1864             elif self.startline.is_subtable:
1865                 raise OutsideError,\
1866                       "SubTable is not allowed outside a block at line %d"%\
1867                       (self.lineno)
1868
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
1874
1875         # Eat lines until we reach the end of our block...
1876         if DEBUGGING: print "%sStart %s"%(self.indent_str,self.class_name)
1877         self._eat_lines()
1878         self._end_block()
1879
1880     def _block_class_name(self,startline):
1881         """Return a representation of the class name."""
1882
1883         full_name = "%s"%self.__class__
1884         bits = string.split(full_name,".")
1885         return "%s/%s"%(bits[-1],startline.class_name)
1886
1887     def _eat_lines(self):
1888         """Eat lines until we run out of block..."""
1889
1890         while 1:
1891             try:
1892                 nextline = self.file.next()
1893             except EOFError:
1894                 return
1895
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!
1904                         self.file.unget()
1905                         return
1906                     else:
1907                         raise IndentError,\
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:
1913                     # A spurious 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!)
1918                     raise IndentError,\
1919                           "Line %d (%s) is indented more than the previous line"%\
1920                           (nextline.lineno,nextline.class_name)
1921             else:
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:
1926                     raise IndentError,\
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)
1930
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))
1938             else:
1939                 self.items.append(nextline)
1940
1941     def _end_block(self):
1942         """End our block"""
1943
1944         if DEBUGGING: print "%sEnd %s"%(self.indent_str,self.class_name)
1945
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"?
1951             count = 0
1952             for item in self.items:
1953                 if item.our_business():
1954                     count = count + 1
1955                     if count > 1: break
1956             if count > 1:
1957                 self.error("Tuple declaration can only contain one 'business'"
1958                            " line, not %d\n"
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,
1966                                       self.text)
1967                 self.startline = temp
1968
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()"
1977         if DEBUGGING:
1978             print "Expanding IF blocks, sorting out labels, etc."
1979
1980         temp       = self.items
1981         self.items = []
1982         for item in temp:
1983             if item.class_name == "Block/IfBlockLine":
1984                 self._add(item.startline)
1985                 for thing in item.items:
1986                     self._add(thing)
1987             else:
1988                 self._add(item)
1989
1990         # Go back through our contents and resolve any labels
1991         if DEBUGGING:
1992             print "%s...processing labels (next_index=%d)"%(self.indent_str,
1993                                                             self.next_index)
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)
2000
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
2012
2013     def _add(self,item):
2014         """Add a line or block to our list of items.
2015
2016         item -- the Line or Block instance to add
2017
2018         NB: Also adds it to our "business" list if it is our business
2019             (and not a label)
2020         """
2021
2022         if item.class_name == "LabelLine":
2023             self.label_dict[item.label] = self.next_index
2024             if DEBUGGING:
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)
2033             if DEBUGGING:
2034                 print "%sadd  %2d  %s"%(item.indent_str,
2035                                             self.next_index,item)
2036             self.next_index = self.next_index + 1
2037         else:
2038             # It's not something we can assign a sensible index to, so don't
2039             if DEBUGGING:
2040                 print "%sadd  xx  %s"%(item.indent_str,item)
2041             self.items.append(item)
2042
2043     def translate_label(self,label,line):
2044         """Given a label, return its translation.
2045
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
2050
2051         Reports an error and just returns the original "label" if it
2052         can't translate it.
2053         """
2054         if self.label_dict.has_key(label):
2055             # How far do we have to jump?
2056             offset = self.label_dict[label] - line.index
2057             return "%+d"%offset
2058         elif label == "MatchOk":
2059             return "MatchOk"
2060         elif label == "MatchOK":
2061             line.warning("Label 'MatchOK' should be spelt 'MatchOk'"
2062                          " (using 'MatchOk')")
2063             return "MatchOk"
2064         elif label == "MatchFail":
2065             return "MatchFail"
2066         elif label == "next":
2067             return "+1"
2068         elif label == "previous":
2069             return "-1"
2070         elif label == "repeat":
2071             return "0"
2072         else:
2073             line.error("Undefined label '%s'"%label)
2074             return label
2075
2076     def expand(self,stream,block=None):
2077         """Write out the expanded equivalent of ourselves.
2078
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
2081         """
2082
2083         self.startline.expand(stream,block=block)
2084         for item in self.items[:-1]:
2085             item.expand(stream,block=self)
2086
2087         self.items[-1].expand(stream,block=self)
2088
2089         # Deal with closing any block parentheses
2090         if self.startline.class_name == "TableBlockLine" or \
2091            self.startline.class_name == "BadTableBlockLine":
2092             if DEBUGGING:
2093                 stream.write("Line ...: ")
2094
2095             stream.write(self.indent_str)
2096             if self.outer:
2097                 # Outer block - just close it
2098                 stream.write(")")
2099             else:
2100                 # Inner block is a Table block, and we need to close both
2101                 # the tuple-of-tuples, and also the tuple containing the
2102                 # Table command...
2103                 stream.write("))")
2104             if not self.is_last:
2105                 stream.write(",")
2106             stream.write("\n")
2107
2108 \f
2109 # ------------------------------------------------------------
2110 class File:
2111     """This is the class that holds our processed data
2112
2113     Contains:
2114         lines   -- a list of the line instances for each "line" in our text
2115         items   -- a list of lines and BLOCKs
2116     """
2117
2118     def __init__(self,tagtuples,text):
2119         """Instantiate a File
2120
2121         tagtuples -- the list of mxTextTools tag tuples generated by
2122                      parsing the data in "text"
2123         text      -- the text we parsed
2124         """
2125
2126         self.text      = text
2127         self.tagtuples = tagtuples
2128
2129         # Assemble our list of lines
2130         print "Pass 1: assembling lines"
2131         if DEBUGGING: print "~~~~~~~~~~~~~~~~~~~~~~~~"
2132         self.lines = []
2133         lineno     = 0
2134         prevline   = None
2135         for tagtuple in tagtuples:
2136             lineno = lineno + 1
2137             thisline = LineFactory(lineno,tagtuple,text)
2138
2139             if prevline:
2140                 prevline.next_indent = thisline.indent
2141
2142             self.lines.append(thisline)
2143             prevline = thisline
2144
2145         #if DEBUGGING: print
2146
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[:]
2155         revlist.reverse()
2156         indent = 0
2157         for line in revlist:
2158             if line.class_name == "EmptyLine":
2159                 line.change_indent(indent)
2160             else:
2161                 indent = line.indent
2162         del revlist
2163
2164         if DEBUGGING:
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,"  ")
2170             print
2171
2172         # Now we need to assemble blocks
2173         print "Pass 3: assembling blocks"
2174         if DEBUGGING: print "~~~~~~~~~~~~~~~~~~~~~~~~~"
2175         self.reset()
2176         self.items = []
2177
2178         while 1:
2179             try:
2180                 item = self.next()
2181             except EOFError:
2182                 break
2183
2184             if DEBUGGING:
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"
2190             else:
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"
2197
2198         if DEBUGGING: print
2199                 
2200
2201     def reset(self):
2202         """Ensure that the next call of "nextline" returns the first line."""
2203         self.index = -1
2204
2205     def unget(self):
2206         """Unread the current line."""
2207         self.index = self.index - 1
2208         if self.index < 0:
2209             self.index = 0
2210
2211     def next(self):
2212         """Retrieve the next line from the list of lines in this "file".
2213
2214         Raises EOFError if there is no next line (i.e., "end of file")
2215         """
2216         self.index = self.index + 1
2217         try:
2218             return self.lines[self.index]
2219         except IndexError:
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
2223             raise EOFError
2224
2225     def expand(self,stream):
2226         """Expand out the result."""
2227         for item in self.items:
2228             item.expand(stream)
2229
2230 \f
2231 # ------------------------------------------------------------
2232 def print_tuples(tuples,text,indent=""):
2233     """Print out a list of tuples in a neat form
2234
2235     tuples -- our tuple list
2236     text   -- the text it tags
2237     indent -- our current indentation
2238     """
2239
2240     # Tuples are of the form:
2241     # (object,left_index,right_index,sublist)
2242
2243     for obj,left,right,sub in tuples:
2244         if sub:
2245             print "%s%s"%(indent,obj)
2246             print_tuples(sub,text,indent+"  ")
2247         else:
2248             # Terminal node - show the actual text we've tagged!
2249             print "%s%s = %s"%(indent,obj,`text[left:right]`)
2250
2251 \f
2252 # ------------------------------------------------------------
2253 def print_text(text):
2254     """Print out text with line numbers."""
2255     lines = string.split(text,"\n")
2256     lineno = 0
2257
2258     print "Original text"
2259     print "============="
2260     for line in lines:
2261         lineno = lineno + 1
2262         print "%3d: %s"%(lineno,`line`)
2263
2264 \f
2265 # ------------------------------------------------------------
2266 def print_usage(argv0):
2267     #script_name = string.split(argv0, os.sep)[-1]
2268     #print __doc__%(script_name)
2269     print argv0
2270     print __doc__
2271
2272 \f
2273 # ------------------------------------------------------------
2274 def show_tup(indent,nn,tup):
2275     ll = []
2276     for item in tup:
2277         if type(item) == type((1,)) or type(item) == type([]):
2278             ll.append("(..)")
2279         else:
2280             ll.append(`item`)
2281
2282     if nn:
2283         print "%s%d: (%s)"%(indent,nn,string.join(ll,","))
2284     else:
2285         print "%s(%s)"%(indent,string.join(ll,","))
2286
2287 def comp_sub(indent,one,two):
2288     len1 = len(one)
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))
2295
2296     for count in range(len1):
2297         a = one[count]
2298         b = two[count]
2299         if type(a) != type(b):
2300             print "%sValue types differ, item %d: 1:%s, 2:%s"%(indent,count,
2301                                                                type(a),type(b))
2302             show_tupe(indent,1,one)
2303             show_tupe(indent2,two)
2304             return 0
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)
2309                 return 0
2310         else:
2311             if a != b:
2312                 print "%sValues differ, item %d: 1:%s, 2:%s"%(indent,count,
2313                                                               `a`,`b`)
2314                 show_tup(indent,1,one)
2315                 show_tup(indent,2,two)
2316                 return 0
2317     return 1
2318
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
2323     # itself...
2324     if comp_sub("",one,two):
2325         print "They appear to be the same"
2326
2327 \f
2328 # ------------------------------------------------------------
2329 def main():
2330     """Used to test the module."""
2331
2332     debug_pytag  = DEFAULT_DEBUG
2333     use_pytag    = DEFAULT_PYTAG
2334     use_stdout   = 0
2335     import_tags  = 0
2336     force_overwrite = 0
2337     compare_tables  = 0
2338
2339     if os.name == "posix":
2340         use_testdata = 0
2341     else:
2342         # At home...
2343         use_testdata = 1
2344         use_stdout   = 1
2345         global DEBUGGING
2346         DEBUGGING    = 0
2347
2348     # Do we have command line arguments?
2349     arg_list = sys.argv[1:]
2350     args = []
2351
2352     while 1:
2353         if len(arg_list) == 0:
2354             break
2355
2356         word = arg_list[0]
2357
2358         if word == "-pytag":
2359             use_pytag = 1
2360         elif word == "-debug":
2361             debug_pytag = 1
2362         elif word == "-stdout":
2363             use_stdout = 1
2364         elif word == "-force":
2365             force_overwrite = 1
2366         elif word == "-import":
2367             import_tags = 1
2368         elif word == "-compare":
2369             compare_tables = 1
2370         elif word == "-diag":
2371             global DEBUGGING
2372             DEBUGGING = 1
2373         elif word == "-test":
2374             use_testdata = 1
2375             use_stdout = 1
2376         elif word == "-help":
2377             print_usage(sys.argv[0])
2378             return
2379         elif word == "-version":
2380             print "Version:",__version__
2381             return
2382         elif word == "-history":
2383             print "History:"
2384             print __history__
2385             return
2386         else:
2387             args.append(word)
2388
2389         arg_list = arg_list[1:]
2390         continue
2391
2392     if compare_tables:
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)
2397         return
2398
2399     if not use_testdata and (not args or len(args) > 2):
2400         print_usage(sys.argv[0])
2401         return
2402
2403     if not use_testdata:
2404         infile = args[0]
2405
2406     if import_tags:
2407         print "Importing tag table definition"
2408         from Translate_tags import t_file
2409     else:
2410         print "Using internal tag table definition"
2411         t_file = define_tagtable()
2412
2413     if use_stdout:
2414         outfile = "standard output"
2415     elif len(args) > 1:
2416         outfile = args[1]
2417     else:
2418         base,ext = os.path.splitext(infile)
2419         if ext != ".py":
2420             outfile = base + ".py"
2421         else:
2422             print "Input file has extension .py so won't guess"\
2423                   " an output file"
2424             return
2425
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
2430             return
2431         elif os.path.exists(outfile):
2432             if force_overwrite:
2433                 print "Output file %s already exists"\
2434                       " - overwriting it"%outfile
2435             else:
2436                 print "Output file %s already exists"%outfile
2437                 return
2438
2439     # Read the input file
2440     if use_testdata:
2441         if DEBUGGING: print
2442         print "Using test data"
2443         if DEBUGGING: print "==============="
2444         text = test_data
2445     else:
2446         if DEBUGGING: print
2447         print "Reading text from %s"%infile
2448         if DEBUGGING: print "=================="+"="*len(infile)
2449         file = open(infile,"r")
2450         text = file.read()
2451         file.close()
2452
2453     # Show what we are trying to parse
2454     if DEBUGGING or use_testdata:
2455         print
2456         print_text(text)
2457
2458     # Tag it
2459     print
2460     print "Tagging text"
2461     if DEBUGGING: print "============"
2462     if use_pytag:
2463         import pytag
2464         pytag.set_verbosity(0)
2465         if debug_pytag:
2466             pytag.set_verbosity(1)
2467             pytag.use_debugger()
2468         result,taglist,next = pytag.pytag(text,t_file)
2469     else:
2470         timer = TextTools._timer()
2471         timer.start()
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"
2475
2476     # Now print out the result of the tagging
2477     print
2478     print "Manipulating tagged data"
2479     if DEBUGGING: print "========================"
2480     tagfile = File(taglist,text)
2481
2482     print
2483     print "Writing translation to %s"%outfile
2484     if DEBUGGING: print "======================="+"="*len(outfile)
2485
2486     # Open the output file, if necessary
2487     if use_stdout:
2488         file = sys.stdout
2489     else:
2490         file = open(outfile,"w")
2491
2492     tagfile.expand(file)
2493
2494 \f
2495 # ------------------------------------------------------------
2496 if __name__ == '__main__':
2497     main()