Salome HOME
matrice coloree
[tools/eficas.git] / convert / Parserv5 / Translate.py
1 # -*- coding: utf-8 -*-
2 #            CONFIGURATION MANAGEMENT OF EDF VERSION
3 # ======================================================================
4 # COPYRIGHT (C) 1991 - 2002  EDF R&D                  WWW.CODE-ASTER.ORG
5 # THIS PROGRAM IS FREE SOFTWARE; YOU CAN REDISTRIBUTE IT AND/OR MODIFY
6 # IT UNDER THE TERMS OF THE GNU GENERAL PUBLIC LICENSE AS PUBLISHED BY
7 # THE FREE SOFTWARE FOUNDATION; EITHER VERSION 2 OF THE LICENSE, OR
8 # (AT YOUR OPTION) ANY LATER VERSION.
9 #
10 # THIS PROGRAM IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, BUT
11 # WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF
12 # MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. SEE THE GNU
13 # GENERAL PUBLIC LICENSE FOR MORE DETAILS.
14 #
15 # YOU SHOULD HAVE RECEIVED A COPY OF THE GNU GENERAL PUBLIC LICENSE
16 # ALONG WITH THIS PROGRAM; IF NOT, WRITE TO EDF R&D CODE_ASTER,
17 #    1 AVENUE DU GENERAL DE GAULLE, 92141 CLAMART CEDEX, FRANCE.
18 #
19 #
20 # ======================================================================
21
22 #!/bin/env python -d
23 #!/tools/net/app/Python-1.5.2/bin/python1.5
24
25 """Translate - a first attempt at parsing my little language
26
27 Usage: Translate [switches] <infile> [<outfile>]
28
29         -stdout         -- write to standard output instead of a file
30         -force          -- write to the <outfile> even if it already
31                            exists (overwrite any existing file)
32
33         -import         -- import tag table from Translate_tags.py,
34                            instead of using the internal table
35
36         -compare        -- compare the imported and internal tag tables
37                            (development option!)
38
39         -test           -- use our internal test data and write to stdout
40         -pytag          -- use the interpreted tagging engine
41         -debug          -- if -pytag, enable its debugger
42         -diag           -- enable general debugging
43                            Beware that this currently also writes line
44                            numbers to the start of each line in the output,
45                            so it doesn't emit legal Python...
46
47         -help           -- show this text
48         -history        -- show the module history
49         -version        -- show the module version
50
51 If <outfile> is not specified, <infile> will be used with its extension
52 replaced by ".py".
53 """
54
55 __author__  = """Tibs (Tony J Ibbs)
56 tony@lsl.co.uk or tibs@tibsnjoan.demon.co.uk or tibs@bigfoot.com
57 http://www.tibsnjoan.demon.co.uk/
58 """
59 __version__ = "0.3 (tiepin) of 1999-11-15"
60 __history__ = """\
61 Originally created 1999-08-13
62
63 First released version is 0.2 (bootstrap)/1999-09-09, which gave
64 an idea of how the thing would work, and nearly did.
65
66 Second released version is 0.3 (tiepin)/1999-11-15, which is sufficient
67 to allow the parser used within this utility to be written in the little
68 language, translated and then used as such.
69 """
70
71 import sys
72 import os
73 import string
74
75 # ............................................................
76 # How we want to work things - this is fudge for me in initial development
77 if os.name == "posix":
78     # Unix at work
79     DEFAULT_DEBUG = 0
80     DEFAULT_PYTAG = 0
81 else:
82     # Windows 95 at home
83     TEXTTOOLS_PATH = "C:\\Program Files\\Python"
84     PYTAG_PATH  = "C:\\Program Files\\Python\\TextTools\\Examples"
85     DEFAULT_DEBUG = 0
86     DEFAULT_PYTAG = 0
87
88     if TEXTTOOLS_PATH not in sys.path:
89         print "Adding",TEXTTOOLS_PATH
90         sys.path.append(TEXTTOOLS_PATH)
91
92     if PYTAG_PATH not in sys.path:
93         print "Adding",PYTAG_PATH
94         sys.path.append(PYTAG_PATH)
95 # ............................................................
96
97 # Import the TextTools themselves
98 # - I'm not personally too keen on import *, but it seems to be
99 #   the recommended thing, so I'll leave it for now...
100 try:
101     from TextTools import *
102 except:
103     from mx.TextTools import *
104     #from TextTools.Constants.TagTables import *
105     #from TextTools.Constants.Sets import *
106
107 \f
108 # ------------------------------------------------------------
109 # Useful little constants for unpicking the parsed tuples
110 OBJECT  = 0
111 LEFT    = 1
112 RIGHT   = 2
113 SUBLIST = 3
114
115 # We want to align inline comments when possible - so this is
116 # the column at which we will try to place their "#" marks...
117 COMMENT_COLUMN = 40
118
119 # Are we (generally) debugging?
120 DEBUGGING = 0
121
122 # Do we want a comma after the last tuple (or item) in a table?
123 WANT_LAST_COMMA = 1
124
125 \f
126 # ------------------------------------------------------------
127 def define_tagtable():
128     """Returns our tag table, if we're not importing it."""
129
130     # We are not, initially, going to try for anything very sophisticated
131     # - just something that will get us bootstrapped, so that I can use the
132     #   "little language" to write more sophisticated stuff (without having
133     #   to worry about dropped commas between tuples, and so on!)
134
135
136     # Whitespace is always useful
137     t_whitespace = (None,AllIn,' \t')
138     t_opt_whitespace = t_whitespace + (+1,)
139
140     # Comments are fairly simple
141     t_comment = ('comment',Table,
142                  ((None,Is,'#'),
143                   (None,AllNotIn,'\n\r',MatchOk))
144                  )
145
146     # We care about the "content" of the indentation at the start of a line,
147     # but note that it is optional
148     t_indent = ('indent',AllIn,' \t')
149     t_indentation = t_indent + (+1,)        # zero indentation doesn't show
150
151     # A string is text within single or double quotes
152     # (of course, this is an oversimplification, because we should also
153     #  deal with things like "This is a \"substring\"", and it would be
154     #  nice to be able to cope with triple-quoted strings too, but it
155     #  will do for a start)
156
157     # Major bug - doesn't recognised zero length strings...
158     # (since "AllNotIn" must match at least one character)
159     t_string = ('str',Table,
160                 ((None,Is,"'",+3,+1),
161                  ('text',AllNotIn,"'"),
162                  (None,Is,"'",MatchFail,MatchOk),
163                  (None,Is,'"'),
164                  ('text',AllNotIn,'"'),
165                  (None,Is,'"'),
166                  ))
167
168     # An integer is a series of digits...
169     t_integer = ('int',AllIn,number)
170     
171     t_signed_integer = ('signed_int',Table,
172                         (('sign',Is,"+",+1,+2),
173                          ('sign',Is,"-",+1,+1),
174                          t_integer
175                          ))
176
177     # Remember to be careful to specify the LONGEST possible match first,
178     # so that we try for "IsIn" before we try for "Is" (because "IsIn"
179     # would *match* "Is", leaving us with a spurious "In" hanging around...)
180     t_operation = ('op',Table,
181                    (('op',Word,"AllInSet",   +1,MatchOk),
182                     ('op',Word,"AllIn",      +1,MatchOk),
183                     ('op',Word,"AllNotIn",   +1,MatchOk),
184                     ('op',Word,"CallArg",    +1,MatchOk),
185                     ('op',Word,"Call",       +1,MatchOk),
186                     ('op',Word,"EOF",        +1,MatchOk),
187                     ('op',Word,"Fail",       +1,MatchOk),
188                     ('op',Word,"IsInSet",    +1,MatchOk),
189                     ('op',Word,"IsIn",       +1,MatchOk),
190                     ('op',Word,"IsNotIn",    +1,MatchOk),
191                     ('op',Word,"IsNot",      +1,MatchOk),
192                     ('op',Word,"Is",         +1,MatchOk),
193                     ('op',Word,"Jump",       +1,MatchOk),
194                     ('op',Word,"LoopControl",+1,MatchOk),
195                     ('op',Word,"Loop",       +1,MatchOk),
196                     ('op',Word,"Move",       +1,MatchOk),
197                     ('op',Word,"NoWord",     +1,MatchOk), # alias for WordStart
198                     ('op',Word,"Skip",       +1,MatchOk),
199                     ('op',Word,"SubTableInList",+1,MatchOk),
200                     ('op',Word,"SubTable",   +1,MatchOk),
201                     ('op',Word,"sFindWord",  +1,MatchOk),
202                     ('op',Word,"sWordStart", +1,MatchOk),
203                     ('op',Word,"sWordEnd",   +1,MatchOk),
204                     ('op',Word,"TableInList",+1,MatchOk),
205                     ('op',Word,"Table",      +1,MatchOk),
206                     ('op',Word,"WordStart",  +1,MatchOk),
207                     ('op',Word,"WordEnd",    +1,MatchOk),
208                     ('op',Word,"Word",       MatchFail,MatchOk),
209                     ))
210
211     # Python keywords
212     t_keyword = ('keyword',Table,
213                  ((None,Word,"and",     +1,+28),
214                   (None,Word,"assert",  +1,+27),
215                   (None,Word,"break",   +1,+26),
216                   (None,Word,"class",   +1,+25),
217                   (None,Word,"continue",+1,+24),
218                   (None,Word,"def",     +1,+23),
219                   (None,Word,"del",     +1,+22),
220                   (None,Word,"elif",    +1,+21),
221                   (None,Word,"else",    +1,+20),
222                   (None,Word,"except",  +1,+19),
223                   (None,Word,"exec",    +1,+18),
224                   (None,Word,"finally", +1,+17),
225                   (None,Word,"for",     +1,+16),
226                   (None,Word,"from",    +1,+15),
227                   (None,Word,"global",  +1,+14),
228                   (None,Word,"if",      +1,+13),
229                   (None,Word,"import",  +1,+12),
230                   (None,Word,"in",      +1,+11),
231                   (None,Word,"is",      +1,+10),
232                   (None,Word,"lambda",  +1,+9),
233                   (None,Word,"not",     +1,+8),
234                   (None,Word,"or",      +1,+7),
235                   (None,Word,"pass",    +1,+6),
236                   (None,Word,"print",   +1,+5),
237                   (None,Word,"raise",   +1,+4),
238                   (None,Word,"return",  +1,+3),
239                   (None,Word,"try",     +1,+2),
240                   (None,Word,"while",   MatchFail,+1),
241                   # In order to not recognise things like "in_THIS_CASE"
242                   # we must check that the next character is not legitimate
243                   # within an identifier
244                   (None,IsIn,alpha+'_'+number,+1,MatchFail),
245                   # If it wasn't another identifier character, we need to
246                   # unread it so that it can be recognised as something else
247                   # (so that, for instance, "else:" is seen as "else" followed
248                   #  by ":")
249                   (None,Skip,-1)
250                   ))
251
252     # Do the same for mxText commands
253     t_mxkeyword = ('mxKeyword',Table,
254                    (t_operation,
255                     (None,IsIn,alpha+'_'+number,+1,MatchFail),
256                     (None,Skip,-1)
257                     ))
258
259     # Traditional identifiers
260     t_identifier = ('identifier',Table,
261                     (t_keyword   + (+1,MatchFail), # don't allow Python keywords
262                      t_mxkeyword + (+1,MatchFail), # don't allow mxText commands
263                      (None,IsIn,alpha+'_'),        # can't start with a digit
264                      (None,AllIn,alpha+'_'+number,MatchOk))
265                     )
266
267     # We don't yet deal with the following with anything in parentheses,
268     # which means we can't handle functions or command lists, or other
269     # things which "look like" a tuple
270     t_argument = ('arg',Table,
271                   (('arg',Word,"Here",     +1,MatchOk), # EOF Here, Fail Here
272                    ('arg',Word,"ToEOF",    +1,MatchOk), # Move ToEOF
273                    ('arg',Word,"To",       +1,MatchOk), # Jump To
274                    ('arg',Word,"ThisTable",+1,MatchOk), # [Sub]Table ThisTable
275                    ('arg',Word,"back",     +1,MatchOk), # Skip back
276                    ('arg',Word,"Break",    +1,MatchOk), # LoopControl Break
277                    ('arg',Word,"Reset",    +1,MatchOk), # LoopControl Reset
278                    t_string             + (+1,MatchOk), # e.g., Word "Fred"
279                    t_signed_integer     + (+1,MatchOk), # e.g., Skip -4, Move 3
280                    t_identifier                         # e.g., Table Fred
281                    ))
282
283     t_plus = ('plus',Table,
284               (t_opt_whitespace,
285                (None,Is,"+"),
286                t_opt_whitespace
287                ))
288
289     # Arguments can contain "+"
290     t_plus_arg = ('plusarg',Table,
291                   (t_argument,              # start with a single argument
292                    t_plus + (MatchOk,),     # if we have a "+"
293                    t_argument,              # then we expect another argument
294                    (None,Jump,To,-2),       # then look for another "+"
295                    ))
296
297     # Match, for example:
298     #        <fred>
299     t_label = ('label',Table,
300                ((None,Is,"<"),
301                 t_identifier,
302                 (None,Is,">")
303                 ))
304
305     # Targets for Jump and F:/T:
306     t_target = ('target',Table,
307                 (('tgt',Word,"next",     +1,MatchOk),
308                  ('tgt',Word,"previous", +1,MatchOk),
309                  ('tgt',Word,"repeat",   +1,MatchOk),
310                  ('tgt',Word,"MatchOk",  +1,MatchOk),
311                  ('tgt',Word,"MatchOK",  +1,MatchOk), # For kindness sake
312                  ('tgt',Word,"MatchFail",+1,MatchOk),
313                  t_label
314                  ))
315
316     # A value is either an identifier, or a string, or an integer
317     t_value = ('val',Table,
318                (t_identifier +(+1,MatchOk),
319                 t_string     +(+1,MatchOk),
320                 t_integer
321                 ))
322
323     # An assignment is (optionally) used in Tuple and Table definitions...
324     t_assignment = ('assignment',Table,
325                     (t_value,
326                      t_opt_whitespace,
327                      (None,Is,'='),
328                      ))
329
330     # A common error when writing tuples is to miss off the "=" sign
331     # - the following is used in diagnosing that (see t_bad_tuple below)
332     # (it's useful to have something with identical structure to the
333     #  "real thing")
334     t_bad_tagobj = ('tagobj',Table,
335                     (t_string,
336                      ))
337
338     t_bad_assignment = ('assignment',Table,
339                         (t_value,
340                          ))
341
342     # This is the line that starts the definition of a single tuple.
343     # For the moment, restrict what it gets assigned to to a simple identifier.
344     # Match, for example:
345     #        Fred is:
346     t_tupleblock = ('tupleblock',Table,
347                     (t_identifier,
348                      t_whitespace,
349                      (None,Word,"is:")
350                      ))
351
352     # This is the line that starts a new table or sub-table.
353     # For the moment, we only cope with full Tables.
354     # NOTE that this is used for the "outer" declaration of a tag table,
355     # and also for the "inner" declaration of an inner table or sub-table.
356     # The discrimination between these is done after initial parsing.
357     # Match, for example:
358     #        'keyword' = Table is:      (inner)
359     #        tagtable = Table is:       (outer)
360     t_tableblock = ('tableblock',Table,
361                     (t_assignment + (+2,+1),  # left hand side is optional
362                      t_opt_whitespace,
363                      ('type',Word,"Table",+1,+2),  # Either "Table"
364                      ('type',Word,"SubTable"),     # or "SubTable" is required
365                      t_whitespace,            # whitespace is required
366                      (None,Word,"is:")        # "is:" is required
367                      ))
368
369     # This is the line that starts an "if" block
370     # Match, for example:
371     #        Is "Fred":
372     #        controlsymbol:
373     t_ifblock = ('ifblock',Table,
374                  (t_assignment + (+2,+1),      # left hand side is optional
375                   t_opt_whitespace,
376                   t_operation + (+4,+1),
377                   t_whitespace,
378                   t_plus_arg,
379                   (None,Is,":",MatchFail,MatchOk),
380                   t_identifier,
381                   (None,Is,":")
382                   ))
383
384     # Note that we don't allow spaces WITHIN our false and true thingies
385
386     t_onfalse = ('onfalse',Table,
387                  (t_whitespace,
388                   (None,Word,"F:"),
389                   t_target
390                   ))
391
392     t_ontrue = ('ontrue',Table,
393                 (t_whitespace,
394                  (None,Word,"T:"),
395                  t_target
396                  ))
397
398     # Valid examples are things like:
399     #        'fred' = Is "xxx" F:<wow> T:MatchOk
400     #       AllIn jim T:<foundJim>
401     #
402     # For the moment, we're not trying to recognise things in any detail
403     t_tuple = ('tuple',Table,
404                (t_assignment + (+2,+1),  # left hand side is optional
405                 t_opt_whitespace,
406                 t_operation,         # operation is required
407                 t_whitespace,        # for the moment, we always require space here
408                 t_plus_arg,          # argument is required
409                 t_onfalse + (+1,+1),          # F:target is optional
410                 t_ontrue  + (MatchOk,MatchOk) # T:target is also optional
411                 ))
412
413     # If the user has defined a "partial" tuple, they might use something
414     # of the form:
415     #       match_fred  F:MatchFalse T:MatchOk
416     t_tupleplus = ('tupleplus',Table,
417                    (t_identifier,
418                     t_onfalse + (+1,+1),          # F:target is optional
419                     t_ontrue  + (MatchOk,MatchOk) # T:target is also optional
420                     ))
421
422     # Treat Jump To specially - for example:
423     #       Jump To <top>
424     # so that they don't have to do the less obvious "Jump To F:<label>"
425     # (although that will still be recognised, of course, for people who
426     # are used to the tag tuple format itself)
427     t_jumpto = ('jumpto',Table,
428                 ((None,Word,"Jump"),
429                  t_whitespace,
430                  (None,Word,"To"),
431                  t_whitespace,
432                  t_target
433                  ))
434
435     # Is it worth coping with these?
436     t_bad_jumpto = ('jumpto',Table,
437                     ((None,Word,"Jump",+2),         # cope with Jump to
438                      (None,Word,"to",MatchFail,+2),
439                      (None,Word,"JumpTo"),          # and with JumpTo
440                      t_target
441                      ))
442
443     # The "content" of a line is the bit after any indentation, and before
444     # any comment...
445     # For the moment, we won't try to maintain ANY context, so it is up to the
446     # user of the tuples produced to see if they make sense...
447     t_content = ('content',Table,
448                  (t_label        + (+1,MatchOk),
449                   t_tableblock   + (+1,MatchOk), # [<value> =] [Sub]Table is:
450                   t_tupleblock   + (+1,MatchOk), # <identifier> is:
451                   t_ifblock      + (+1,MatchOk), # <cmd> <arg>: OR <identifier>:
452                   t_jumpto       + (+1,MatchOk), # Jump To <target>
453                   t_tuple        + (+1,MatchOk),
454                   t_tupleplus    + (+1,MatchOk), # name [F:<label> [T:<label>]]
455                   ))
456
457     t_contentline = ('contentline',Table,
458                      (t_content,            # something that we care about
459                       t_opt_whitespace,
460                       t_comment   +(+1,+1), # always allow a comment
461                       (None,IsIn,newline)   # the end of the line
462                       ))
463
464     # Sometimes, the user (e.g., me) writes:
465     #        'fred' = Table:
466     # instead of:
467     #        'fred' = Table is:
468     # Unfortunately, without the "is", it would get too confusing whether
469     # we actually wanted an if block...
470     t_bad_tableblock = ('tableblock',Table,
471                         (t_assignment + (+2,+1),  # left hand side is optional
472                          t_opt_whitespace,
473                          (None,Word,"Table"),     # "Table" is required
474                          (None,Is,":")            # "is" is needed before the ":"
475                          ))
476
477     # Sometimes, the use (e.g., me again) write:
478     #        'fred' IsIn jim
479     # instead of:
480     #        'fred' = IsIn jim
481     # Whilst I'm not entirely convinced that "=" is the best character
482     # to use here, I think we do need something!
483     t_bad_tuple = ('tuple',Table,
484                    (t_bad_assignment, # obviously we have to have this!
485                     t_whitespace,     # in which case the whitespace IS needed
486                     t_operation,      # operation is required
487                     t_whitespace,     # for the moment, we must have space here
488                     t_plus_arg,       # argument is required
489                     t_onfalse + (+1,+1),          # F:target is optional
490                     t_ontrue  + (MatchOk,MatchOk) # T:target is also optional
491                     ))
492
493     # Make some attempt to recognise common errors...
494     t_badcontent = ('badcontent',Table,
495                     (t_bad_tableblock +(+1,MatchOk),
496                      t_bad_tuple
497                      ))
498
499     t_badline = ('badline',Table,
500                  (t_badcontent,         # something that we sort of care about
501                   t_opt_whitespace,
502                   t_comment   +(+1,+1), # always allow a comment
503                   (None,IsIn,newline)   # the end of the line
504                   ))
505
506     t_emptyline = ('emptyline',Table,
507                    (t_opt_whitespace,
508                     (None,IsIn,newline)     # the end of the line
509                     ))
510
511     t_commentline = ('commentline',Table,
512                      (t_comment,
513                       (None,IsIn,newline)   # the end of the line
514                       ))
515
516     t_passthruline = ('passthruline',Table,
517                       (('passthru',AllNotIn,newline,+1), # owt else on the line
518                        (None,IsIn,newline)               # the end of the line
519                        ))
520
521     # Basically, a file is a series of lines
522     t_line = ('line',Table,
523               (t_emptyline   +(+1,MatchOk),    # empty lines are simple enough
524                t_indent      +(+1,+1),         # optional indentation
525                t_commentline +(+1,MatchOk),    # always allow a comment
526                t_contentline +(+1,MatchOk),    # a line we care about
527                t_badline     +(+1,MatchOk),    # a line we think is wrong
528                t_passthruline                  # a line we don't care about
529                ))
530
531     t_file = (t_line,
532               (None,EOF,Here,-1)
533               )
534
535     return t_file
536
537 \f
538 # ------------------------------------------------------------
539 # We'll define some moderately interesting test data
540
541 test_data = """\
542 # This example isn't *meant* to make any sense!
543 # It's just an accumulation of things that got checked for various reasons 
544 from TextTools import *
545 # Some Python code
546 a = b;
547 fred = 3;
548 if a == 1:
549     print "a == 1"
550 else:
551     print "a != 1"
552
553 # And the rest is our business...
554 t_integer is:
555     'int' = AllIn '0123456789'
556 t_integer is:
557     'int' = AllIn number
558 t_indent is:
559     # A comment here is OK
560     <label> # Strangely enough, so is a label
561     'indent' = AllIn ' \t'
562 t_buggy = Table is:
563     'int' AllIn number    # BUGGY LINE (missing "=")
564     (None,"AllIn",number) # BUGGY LINE (an actual tuple)
565     fred = jim            # BUGGY LINE (not our business)
566     tagobj F:<op> T:next  # label <op> is undefined
567     # The next line is totally empty
568
569     # The next line contains just indentation
570
571     # This line is just a comment
572 # And this comment should be JUST after the preceding block...
573 t_indentation is:          # This should be "= Table is:"
574     t_indent
575     t_indent F:previous
576     t_indent T:previous
577     t_indent F:previous T:previous
578 t_deep = Table is:
579     'a' = SubTable is:
580         SubTable is:
581             'this' = Table ThisTable
582             t_integer
583 t_fred = Table is:
584     <top>
585     AllIn 'a'
586     'a' = AllIn 'a'
587     'a' = AllIn 'a' F:previous
588     'a' = AllIn 'a' T:previous
589     'a' = AllIn 'a' F:previous T:previous
590     AllIn 'abcd':
591         AllIn 'xyz' F:<later> T:<top>
592     'a' = AllIn 'abcd':
593         AllIn 'xyz'
594     <later>
595     t_indent:
596         AllIn 'xyz'
597     AllIn number + '_'
598     AllIn number+"_"+alpha
599     Jump To <top>
600 """
601
602 \f
603 # ------------------------------------------------------------
604 # Our own exceptions
605
606 class OutsideError(Exception):
607     """The entity is not permitted outside a block."""
608     pass
609
610 class IndentError(Exception):
611     """An indentation error has been detected."""
612     pass
613
614 class NoIdentifier(Exception):
615     """We're missing an identifier (to assign to)."""
616     pass
617
618 \f
619 # ------------------------------------------------------------
620 def LineFactory(lineno,tagtuple,text):
621     """Take some tagged data and return an appropriate line class.
622
623     lineno   -- the line number in the "file". Note that the first line
624                 in the file is line 1
625     tagtuple -- a tag tuple for a single line of data
626     text     -- the text for the "file". All the "left" and "right" offsets
627                 are relative to this text (i.e., it is the entire content
628                 of the file)
629
630     The tag tuples we get back from the parser will be of the form:
631
632         ('line',left,right,[
633           ('indent',left,right,None),    -- this is optional
634           ('content',left,right,[<data>])
635         ])
636
637     Looking at <type> should enable us to decide what to do with
638     the <data>.
639     """
640
641     # Extract the list of tuples from this 'line'
642     tuples = tagtuple[SUBLIST]
643
644     # First off, do we have any indentation?
645     tup = tuples[0]
646     if tup[OBJECT] == "indent":
647         # This is inefficient, because it actually copies strings
648         # around - better would be to duplicate the calculation
649         # that string.expandtabs does internally...
650         indent_str = string.expandtabs(text[tup[LEFT]:tup[RIGHT]])
651         tuples = tuples[1:]
652     else:
653         indent_str = ""
654         tuples = tuples
655
656     # Now, work out which class we want an instance of
657     # (this is the 'fun' bit)
658
659     type = tuples[0][OBJECT]
660     if type == 'emptyline':
661         return EmptyLine(lineno,indent_str,tuples[0],text)
662     elif type == 'commentline':
663         return CommentLine(lineno,indent_str,tuples[0],text)
664     elif type == 'passthruline':
665         return PassThruLine(lineno,indent_str,tuples[0],text)
666     elif type == 'contentline':
667         # OK - we need to go down another level
668         sublist = tuples[0][SUBLIST]
669
670         # Do we also have an in-line comment?
671         if len(sublist) > 1:
672             comment = sublist[1]
673         else:
674             comment = None
675
676         # And the actual DATA for our line is down yet another level...
677         sublist = sublist[0][SUBLIST]
678         type = sublist[0][OBJECT]
679         if type == 'label':
680             return LabelLine(lineno,indent_str,sublist[0],comment,text)
681         elif type == 'tableblock':
682             return TableBlockLine(lineno,indent_str,sublist[0],comment,text)
683         elif type == 'tupleblock':
684             return TupleBlockLine(lineno,indent_str,sublist[0],comment,text)
685         elif type == 'ifblock':
686             return IfBlockLine(lineno,indent_str,sublist[0],comment,text)
687         elif type == 'tuple':
688             return TupleLine(lineno,indent_str,sublist[0],comment,text)
689         elif type == 'tupleplus':
690             return TuplePlusLine(lineno,indent_str,sublist[0],comment,text)
691         elif type == 'jumpto':
692             return JumpToLine(lineno,indent_str,sublist[0],comment,text)
693         else:
694             raise ValueError,\
695                   "Line %d is of unexpected type 'contentline/%s'"%(lineno,
696                                                                     type)
697     elif type == 'badline':
698         # OK - we need to go down another level
699         sublist = tuples[0][SUBLIST]
700
701         # Do we also have an in-line comment?
702         if len(sublist) > 1:
703             comment = sublist[1]
704         else:
705             comment = None
706
707         # And the actual DATA for our line is down yet another level...
708         sublist = sublist[0][SUBLIST]
709         type = sublist[0][OBJECT]
710         if type == 'tableblock':
711             return BadTableBlockLine(lineno,indent_str,sublist[0],comment,text)
712         if type == 'tuple':
713             return BadTupleLine(lineno,indent_str,sublist[0],comment,text)
714         else:
715             raise ValueError,\
716                   "Line %d is of unexpected type 'badline/%s'"%(lineno,type)
717     else:
718         raise ValueError,"Line %d is of unexpected type '%s'"%(lineno,type)
719
720
721 \f
722 # ------------------------------------------------------------
723 class BaseLine:
724     """The base class on which the various line types depend
725
726     Contains:
727
728       tagtuple    -- the tagtuple we (our subclass instance) represent(s)
729       lineno      -- the line number in the file (first line is line 1)
730       indent      -- our indentation (integer)
731       indent_str  -- our indentation (a string of spaces)
732       text        -- the text of the "file" we're within
733       class_name  -- the name of the actual class this instance belongs to
734                      (i.e., the name of the subclass, suitable for printing)
735
736     Some things only get useful values after we've been instantiated
737     
738       next_indent -- the indentation of the next line
739       index       -- for a line in a block, its index therein
740     """
741
742     def __init__(self,lineno,indent_str,tagtuple,text):
743         """Instantiate a BaseLine.
744
745         lineno     -- the line number in the "file". Note that the first line
746                       in the file is line 1
747         indent_str -- the indentation of the line (a string of spaces)
748         tagtuple   -- the tag tuple for this line of data
749         text       -- the text for the "file". All the "left" and "right"
750                       offsets are relative to this text (i.e., it is the
751                       entire content of the file)
752
753         The content of the tagtuple depends on which of our subclasses
754         is being used. Refer to the relevant doc string.
755         """
756
757         self.tagtuple = tagtuple
758         self.lineno   = lineno
759         self.text     = text
760
761         self.class_name = self._class_name()
762         self.indent_str = indent_str
763         self.indent     = len(indent_str)
764
765         # OK - we don't really know! (but this will do for "EOF")
766         self.next_indent = 0
767
768         # We don't always HAVE a sensible value for this
769         self.index = None
770
771         #if DEBUGGING:
772         #    print "Line %3d: %s%s"%(lineno,indent_str,self.class_name)
773
774     def change_indent(self,count=None,spaces=""):
775         """Change our indentation.
776
777         Specify either "count" or "spaces" (if both are given,
778         "count" will be used, if neither is given, then the
779         indentation will be set to zero)
780         
781         count  -- the number of spaces we're indented by
782         spaces -- a string of spaces
783         """
784         if count:
785             self.indent = count
786             self.indent_str = count * " "
787         else:
788             self.indent_str = spaces
789             self.indent = len(spaces)
790
791     def _class_name(self):
792         """Return a representation of the class name."""
793
794         full_name = "%s"%self.__class__
795         bits = string.split(full_name,".")
796         return bits[-1]
797
798     def starts_block(self):
799         """Return true if we start a new block."""
800         return 0
801
802     def only_in_block(self):
803         """Return true if we can only occur inside a block."""
804         return 0
805
806     def our_business(self):
807         """Return true if we are a line we understand."""
808         return 1
809
810     def __str__(self):
811         return "%3d %s%-10s"%(self.lineno,self.indent_str,self.class_name)
812
813     def _intro(self):
814         """Returns a useful 'introductory' string."""
815         return "%3d %-10s %s"%(self.lineno,self.class_name,self.indent_str)
816
817     def _trunc(self):
818         """Returns a "truncated" representation of our text."""
819
820         text = "%s %s"%(self._intro(),
821                         `self.text[self.tagtuple[LEFT]:self.tagtuple[RIGHT]]`)
822
823         if len(text) > 60:
824             return text[:60]+"..."
825         else:
826             return text
827
828     def resolve_labels(self,block):
829         """Called to resolve any labels use in this line.
830
831         block -- the block that contains us
832         """
833         # The default is to do nothing as we don't HAVE any labels...
834         return
835
836     def expand(self,stream,block=None):
837         """Write out the expanded equivalent of ourselves.
838
839         stream  -- an object with a "write" method, e.g., a file
840         newline -- true if we should output a terminating newline
841         block   -- used to pass the containing Block down to lines
842                    within a block, or None if we're not in a block
843         """
844
845         if DEBUGGING:
846             stream.write("Line %3d: "%self.lineno)
847
848         stream.write(self.indent_str)
849         stream.write(self.text[self.tagtuple[LEFT]:self.tagtuple[RIGHT]])
850         stream.write(",\n")
851
852     def warning(self,text):
853         """Report a warning message.
854
855         text -- the text to report
856         """
857
858         lines = string.split(text,"\n")
859         print "###WARNING: line %d (%s)"%(self.lineno,self.class_name)
860         for line in lines:
861             print "###         %s"%line
862
863     def error(self,text):
864         """Report an error.
865
866         text -- the error text to report
867         """
868
869         lines = string.split(text,"\n")
870         print "###ERROR: line %d (%s)"%(self.lineno,self.class_name)
871         for line in lines:
872             print "###       %s"%line
873
874 \f
875 # ------------------------------------------------------------
876 class EmptyLine(BaseLine):
877     """An empty line.
878
879     Note that the indentation of an empty line is taken to be the
880     same as that of the next (non-empty) line. This is because it
881     seems to me that (a) an empty line should not per-se close a
882     block (which it would do if it had indentation 0) and (b) we
883     don't remember any whitespace in an empty line, so the user
884     can't assign an indentation themselves (which is a Good Thing!)
885     """
886
887     def __init__(self,lineno,indent_str,tagtuple,text):
888         """Instantiate an EmptyLine.
889
890         The content of the tagtuple is:
891             None
892         """
893
894         BaseLine.__init__(self,lineno,indent_str,tagtuple,text)
895
896     def expand(self,stream,block=None):
897         """Write out the expanded equivalent of ourselves.
898
899         stream  -- an object with a "write" method, e.g., a file
900         block   -- used to pass the containing Block down to lines
901                    within a block, or None if we're not in a block
902         """
903
904         if DEBUGGING:
905             stream.write("Line %3d: "%self.lineno)
906
907         # um - there's nothing to do, folks
908         stream.write("\n")
909
910     def our_business(self):
911         """Return true if we are a line we understand."""
912         return 0
913
914     def _trunc(self):
915         """Returns a "truncated" representation of our text."""
916
917         return self._intro()
918
919 \f
920 # ------------------------------------------------------------
921 class CommentLine(BaseLine):
922     """A comment line."""
923
924     def __init__(self,lineno,indent_str,tagtuple,text):
925         """Instantiate a CommentLine.
926
927         The content of the tagtuple is:
928             ('comment',left,right,None)
929         and the demarcated text includes the initial '#' character
930         """
931
932         BaseLine.__init__(self,lineno,indent_str,tagtuple,text)
933
934         # We actually want the next tuple down (so to speak) so that
935         # we lose the trailing newline...
936         tup = self.tagtuple[SUBLIST][0]
937         self.data = self.text[tup[LEFT]:tup[RIGHT]]
938
939     def our_business(self):
940         """Return true if we are a line we understand."""
941         return 0
942
943     def expand(self,stream,block=None):
944         """Write out the expanded equivalent of ourselves.
945
946         stream  -- an object with a "write" method, e.g., a file
947         block   -- used to pass the containing Block down to lines
948                    within a block, or None if we're not in a block
949         """
950
951         if DEBUGGING:
952             stream.write("Line %3d: "%self.lineno)
953
954         stream.write(self.indent_str)
955         stream.write("%s\n"%self.data)
956
957 \f
958 # ------------------------------------------------------------
959 class PassThruLine(BaseLine):
960     """A line we just pass throught without interpretation."""
961
962     def __init__(self,lineno,indent_str,tagtuple,text):
963         """Instantiate a PassThruLine.
964
965         The content of the tagtuple is:
966             ('passthru',left,right,None)
967         """
968
969         BaseLine.__init__(self,lineno,indent_str,tagtuple,text)
970
971         # We actually want the next tuple down (so to speak) so that
972         # we lose the trailing newline...
973         tup = self.tagtuple[SUBLIST][0]
974         self.data = self.text[tup[LEFT]:tup[RIGHT]]
975
976     def our_business(self):
977         """Return true if we are a line we understand."""
978         return 0
979
980     def expand(self,stream,block=None):
981         """Write out the expanded equivalent of ourselves.
982
983         stream  -- an object with a "write" method, e.g., a file
984         block   -- used to pass the containing Block down to lines
985                    within a block, or None if we're not in a block
986         """
987
988         if DEBUGGING:
989             stream.write("Line %3d: "%self.lineno)
990
991         if block:
992             err_str = "Unparsed line inside a block"\
993                       " - it has been commented out"
994             # Hmm - the following advice is less often useful than I
995             # had hoped - leave it out for now...
996             #if string.find(self.data,",") != -1:
997             #    err_str = err_str + "\nCheck for a trailing comma?"
998
999             self.error(err_str)
1000
1001         # Always output the indentation, 'cos otherwise it looks silly
1002         stream.write(self.indent_str)
1003
1004         if block:
1005             stream.write("#[ignored]#")
1006
1007         stream.write("%s\n"%self.data)
1008
1009 \f
1010 # ------------------------------------------------------------
1011 class ContentLine(BaseLine):
1012     """A line we have to interpret - another base class.
1013
1014     Adds the following variables:
1015
1016     comment -- any in-line comment on this line
1017     """
1018
1019     def __init__(self,lineno,indent_str,tagtuple,comment,text):
1020         """Instantiate a ContentLine.
1021
1022         comment -- either a comment tuple or None
1023
1024         The content of the tagtuple is:
1025             ('contentline',left,right,
1026               [('content',left,right,[<data>]),
1027                ('comment',left,right,None)      -- optional
1028               ])
1029         where <data> is used in the internals of one of our subclasses
1030         (i.e., it is what is passed down in the "tagtuple" argument)
1031         """
1032
1033         BaseLine.__init__(self,lineno,indent_str,tagtuple,text)
1034         self.comment = comment
1035
1036         # Assume we're not the last "our business" line in a block...
1037         self.is_last = 0
1038
1039     def _write_comment(self,stream,sofar):
1040         """Write out the in-line comment string.
1041
1042         Since we're the only people to call this, we can safely
1043         rely on it only being called when there IS a comment tuple
1044         to output...
1045
1046         stream  -- an object with a "write" method, e.g., a file
1047         sofar   -- the number of characters written to the line
1048                    so far
1049         """
1050         if sofar < COMMENT_COLUMN:
1051             stream.write(" "*(COMMENT_COLUMN - sofar))
1052         else:
1053             # always write at least one space...
1054             stream.write(" ")
1055         stream.write(self.text[self.comment[LEFT]:self.comment[RIGHT]])
1056
1057     def _write_text(self,stream,block):
1058         """Write out the main tuple text.
1059
1060         stream  -- an object with a "write" method, e.g., a file
1061         block   -- used to pass the containing Block down to lines
1062                    within a block, or None if we're not in a block
1063
1064         This should generally be the method that subclasses override.
1065         It returns the number of characters written, or -1 if we had
1066         an error.
1067         """
1068         stream.write(self.text[self.tagtuple[LEFT]:self.tagtuple[RIGHT]])
1069         return self.tagtuple[RIGHT] - self.tagtuple[LEFT]
1070
1071     def expand(self,stream,block=None):
1072         """Write out the expanded equivalent of ourselves.
1073
1074         stream  -- an object with a "write" method, e.g., a file
1075         block   -- used to pass the containing Block down to lines
1076                    within a block, or None if we're not in a block
1077         """
1078
1079         if DEBUGGING:
1080             stream.write("Line %3d: "%self.lineno)
1081
1082         stream.write(self.indent_str)
1083         nchars = self._write_text(stream,block)
1084         # Don't write any in-line comment out if we had an error,
1085         # as the layout won't work!
1086         if nchars > -1 and self.comment:
1087             self._write_comment(stream,sofar=nchars+self.indent)
1088         stream.write("\n")
1089
1090 \f
1091 # ------------------------------------------------------------
1092 class LabelLine(ContentLine):
1093     """A line containing a label.
1094
1095     Contains:
1096         label -- our label string
1097     """
1098
1099     def __init__(self,lineno,indent_str,tagtuple,comment,text):
1100         """Instantiate a LabelLine.
1101
1102         For instance:
1103
1104             <fred>
1105
1106         The content of the tagtuple is:
1107
1108             ('label',left,right,[
1109               ('identifier',left,right,None)
1110              ])
1111         """
1112
1113         ContentLine.__init__(self,lineno,indent_str,tagtuple,comment,text)
1114
1115         self.label = self.text[self.tagtuple[LEFT]:self.tagtuple[RIGHT]]
1116
1117     def _write_text(self,stream,block):
1118         """Write out the main tuple text.
1119
1120         stream  -- an object with a "write" method, e.g., a file
1121         block   -- used to pass the containing Block down to lines
1122                    within a block, or None if we're not in a block
1123         """
1124         # Enough difficult length calculation - let's do this one
1125         # the easy way...
1126         if DEBUGGING:
1127             text = "# Label %s at index %d"%(self.label,self.index)
1128         else:
1129             text = "# %s"%(self.label)  # surely enough for most people...
1130         stream.write(text)
1131         return len(text)
1132
1133     def translate(self,index,block):
1134         """Return the translation of a use of this label as a target.
1135
1136         index -- the index of the line which uses the label as a target
1137         block -- the Block we are within
1138         """
1139
1140         # Hmm - I don't think this CAN go wrong at this point...
1141         return block.translate_label(self.label,self)
1142
1143     def only_in_block(self):
1144         """Return true if we can only occur inside a block."""
1145         return 1
1146
1147 \f
1148 # ------------------------------------------------------------
1149 class TableBlockLine(ContentLine):
1150     """A line starting a table block."""
1151
1152     def __init__(self,lineno,indent_str,tagtuple,comment,text):
1153         """Instantiate a TableBlockLine.
1154
1155         For instance:
1156
1157             "fred" = Table is:
1158             Table is:
1159
1160         This is used for two purposes:
1161         1. To define the actual tag table itself (i.e., at the outer
1162            level). Only "Table" is allowed in this instance, but since
1163            that is all we recognised for now, we shan't worry about it...
1164         2. To define an inner table (i.e., at an inner level)
1165
1166         The content of the tagtuple is:
1167
1168             ('tableblock',left,right,[
1169               ('assignment',left,right,[           -- optional if inner
1170                  ('val',left,right,[
1171
1172                     ('identifier',left,right,[])
1173                  OR
1174                     ('str',left,right,[
1175                        ('text',left,right,None)
1176                      ])
1177                  OR
1178                     ('int',left,right,[])
1179
1180                   ])
1181                ])
1182               ('type',left,right,[])       -- either "Table" or "SubTable"
1183              ])
1184
1185         NOTE: as an "emergency" measure (so we can `pretend' that a
1186         TupleBlock was actually a TableBlock as part of attempted
1187         error correction), if tagtuple == ("error",tagobj) then we
1188         short-circuit some of the initialisation...
1189         """
1190
1191         ContentLine.__init__(self,lineno,indent_str,tagtuple,comment,text)
1192
1193         if tagtuple[0] == "error":
1194             # We're "bluffing" at the creation of a TableBlock
1195             self.tagobj = tagtuple[1]
1196             self.is_subtable = 0
1197         elif len(self.tagtuple[SUBLIST]) == 1:
1198             self.tagobj = "None"
1199             tup = self.tagtuple[SUBLIST][0]
1200             self.is_subtable = (self.text[tup[LEFT]:tup[RIGHT]] == "SubTable")
1201         else:
1202             # The first tuple down gives us the "<value> = " string
1203             tup = self.tagtuple[SUBLIST][0]
1204             # The next tuple down gives us "<value>" which is what we want
1205             tup = tup[SUBLIST][0]
1206             self.tagobj = self.text[tup[LEFT]:tup[RIGHT]]
1207             # Then we have the type of table
1208             tup = self.tagtuple[SUBLIST][1]
1209             self.is_subtable = (self.text[tup[LEFT]:tup[RIGHT]] == "SubTable")
1210
1211     def got_tagobj(self):
1212         return (self.tagobj != "None")
1213
1214     def starts_block(self):
1215         """Return true if we start a new block."""
1216         return 1
1217
1218     def _write_text(self,stream,block):
1219         """Write out the main tuple text.
1220
1221         stream  -- an object with a "write" method, e.g., a file
1222         block   -- used to pass the containing Block down to lines
1223                    within a block, or None if we're not in a block
1224
1225         It returns the number of characters written, or -1 if we had
1226         an error.
1227         """
1228
1229         if block:
1230             if self.is_subtable:
1231                 stream.write("(%s,SubTable,("%self.tagobj)
1232                 return len(self.tagobj) + 11
1233             else:
1234                 stream.write("(%s,Table,("%self.tagobj)
1235                 return len(self.tagobj) + 8
1236         else:
1237             stream.write("%s = ("%self.tagobj)
1238             return len(self.tagobj) + 4
1239
1240 \f
1241 # ------------------------------------------------------------
1242 class TupleBlockLine(ContentLine):
1243     """A line starting a tuple block (i.e., defining a single tuple)
1244
1245     Contains:
1246     
1247         name -- the "name" of this tuple (i.e., what comes
1248                 before the "is:")
1249     """
1250
1251     def __init__(self,lineno,indent_str,tagtuple,comment,text):
1252         """Instantiate a TupleBlockLine.
1253
1254         For instance:
1255
1256             Fred is:
1257
1258         The content of the tagtuple is:
1259         
1260             ('tupleblock',left,right,[
1261               ('identifier',left,right,None)
1262              ])
1263         """
1264
1265         ContentLine.__init__(self,lineno,indent_str,tagtuple,comment,text)
1266
1267         tup = self.tagtuple[SUBLIST][0]
1268         self.name = self.text[tup[LEFT]:tup[RIGHT]]
1269
1270     def starts_block(self):
1271         """Return true if we start a new block."""
1272         return 1
1273
1274     def only_in_block(self):
1275         """Return true if we can only occur inside a block."""
1276         return 0
1277
1278     def _write_text(self,stream,block):
1279         """Write out the main tuple text.
1280
1281         stream  -- an object with a "write" method, e.g., a file
1282         block   -- used to pass the containing Block down to lines
1283                    within a block, or None if we're not in a block
1284
1285         It returns the number of characters written, or -1 if we had
1286         an error.
1287         """
1288         # The "\" at the end is somewhat clumsy looking, but the
1289         # only obvious way of preserving layout...
1290         stream.write("%s = \\"%self.name)
1291         return len(self.name) + 5
1292
1293 \f
1294 # ------------------------------------------------------------
1295 class IfBlockLine(ContentLine):
1296     """A line starting an if block.
1297
1298     Contains:
1299         cmd  -- the command within this if block
1300         arg  -- the argument for said command
1301     or:
1302         name -- the name within this if block
1303     """
1304
1305     def __init__(self,lineno,indent_str,tagtuple,comment,text):
1306         """Instantiate an IfBlockLine.
1307
1308         For instance:
1309
1310             'jim' = Is "Fred":
1311             Is "Fred":
1312             fred:
1313
1314         The content of the tagtuple is:
1315
1316             ('ifblock',left,right,[
1317               ('assignment',left,right,[
1318                  ('val',left,right,[
1319
1320                     ('identifier',left,right,[])
1321                  OR
1322                     ('str',left,right,[
1323                        ('text',left,right,None)
1324                      ])
1325                  OR
1326                     ('int',left,right,[])
1327
1328                   ])
1329                ])
1330               ('op',left,right,None),
1331               ('arg',left,right,None),
1332              ])
1333         or:
1334             ('ifblock',left,right,[
1335               ('op',left,right,None),
1336               ('arg',left,right,None),
1337              ])
1338         or:
1339             ('ifblock',left,right,[
1340               ('identifier',left,right,None)
1341              ])
1342         """
1343
1344         ContentLine.__init__(self,lineno,indent_str,tagtuple,comment,text)
1345
1346         tuples = self.tagtuple[SUBLIST]
1347         if tuples[0][OBJECT] == 'op':
1348             tup1 = tuples[0]
1349             tup2 = tuples[1]
1350             self.tagobj = "None"
1351             self.cmd    = self.text[tup1[LEFT]:tup1[RIGHT]]
1352             self.arg    = self.text[tup2[LEFT]:tup2[RIGHT]]
1353             self.name   = None
1354         elif tuples[0][OBJECT] == 'assignment':
1355             # The "<value>" in the "<value> = " string is down
1356             # one level more than the others
1357             tup0 = tuples[0][SUBLIST][0]
1358             self.tagobj = self.text[tup0[LEFT]:tup0[RIGHT]]
1359             tup1 = tuples[1]
1360             tup2 = tuples[2]
1361             self.cmd    = self.text[tup1[LEFT]:tup1[RIGHT]]
1362             self.arg    = self.text[tup2[LEFT]:tup2[RIGHT]]
1363             self.name   = None
1364         elif tuples[0][OBJECT] == 'identifier':
1365             tup = tuples[0]
1366             self.name   = self.text[tup[LEFT]:tup[RIGHT]]
1367             self.cmd    = None
1368             self.arg    = None
1369             self.tagobj = None
1370         else:
1371             # Hmm - try to continue with anything unexpected
1372             tup = tuples[0]
1373             self.error("Unexpected IfBlock subtype %s"%tup[OBJECT])
1374             self.name   = self.text[tup[LEFT]:tup[RIGHT]]
1375             self.cmd    = None
1376             self.arg    = None
1377             self.tagobj = None
1378
1379         # Currently, we have one 'special' argument
1380         if self.arg == "back": self.arg = "-1"
1381
1382         # We don't yet know the offset of the "virtual label" at the
1383         # end of this if block...
1384         self.end_label = None
1385
1386     def starts_block(self):
1387         """Return true if we start a new block."""
1388         return 1
1389
1390     def only_in_block(self):
1391         """Return true if we can only occur inside a block."""
1392         return 1
1393
1394     def resolve_labels(self,block):
1395         """Called to resolve any labels used in this line.
1396
1397         block -- the block that contains us
1398
1399         Note that this only does something the first time it
1400         is called - this will be when the IF block's startline
1401         is asked to resolve its labels. If it is called again,
1402         as a 'normal' line, it will do nothing...
1403         """
1404         if not self.end_label:
1405             self.end_label = "%+d"%(len(block.business)+1)
1406
1407     def _write_text(self,stream,block):
1408         """Write out the main tuple text.
1409
1410         stream  -- an object with a "write" method, e.g., a file
1411         block   -- used to pass the containing Block down to lines
1412                    within a block, or None if we're not in a block
1413
1414         It returns the number of characters written, or -1 if we had
1415         an error.
1416         """
1417         if not self.end_label:
1418             # This should never happen, but just in case, warn the user!
1419             self.error("Unable to determine 'onFalse' destination in IF")
1420
1421         if self.name:
1422             stream.write("%s + (%s,+1),"%(self.name,
1423                                           self.end_label or "<undefined>"))
1424             return len(self.name) + 20
1425         else:
1426             stream.write("(%s,%s,%s,%s,+1),"%(self.tagobj,self.cmd,self.arg,
1427                                               self.end_label or "<undefined>"))
1428             return len(self.tagobj) + len(self.cmd) + len(self.arg) + \
1429                    len(self.end_label) + 20
1430
1431 \f
1432 # ------------------------------------------------------------
1433 class TupleLine(ContentLine):
1434     """A line containing a basic tuple.
1435
1436
1437     Contains:
1438         tagobj  -- optional
1439         cmd     -- the command
1440         arg     -- the argument
1441         ontrue  -- what to do if true
1442         onfalse -- ditto false
1443     """
1444
1445     def __init__(self,lineno,indent_str,tagtuple,comment,text):
1446         """Instantiate a TupleLine.
1447
1448         The content of the tagtuple is:
1449         
1450             ('tuple',left,right,[
1451               ('tagobj',left,right,[           -- optional
1452                  ('str',left,right,[
1453                     ('text',left,right,None)
1454                   ])
1455                ])
1456               ('op',left,right,None),
1457               ('arg',left,right,None),
1458               ('onfalse',left,right,[          -- optional
1459                  ('target',left,right,[
1460                    ('tgt',left,right,None)
1461                  ]),
1462               ('ontrue',left,right,[           -- optional
1463                  ('target',left,right,[
1464                    ('tgt',left,right,None)
1465                  ])
1466                ])
1467              ])
1468         """
1469
1470         ContentLine.__init__(self,lineno,indent_str,tagtuple,comment,text)
1471
1472         self.unpack()
1473
1474
1475     def unpack(self):
1476         """Unpack our contents from our tagtuple."""
1477
1478         # This is doubtless not the most efficient way of doing this,
1479         # but it IS relatively simple...
1480         dict = {}
1481         #for key in ("assignment","op","arg","onfalse","ontrue"):
1482         for key in ("assignment","op","plusarg","onfalse","ontrue"):
1483             dict[key] = None
1484
1485         tuples = self.tagtuple[SUBLIST]
1486         for item in tuples:
1487             name = item[OBJECT]
1488             if name == "onfalse" or name == "ontrue" or name == "assignment":
1489                 # For these, we need to go "down one level" for our data
1490                 tup = item[SUBLIST][0]
1491                 dict[name] = (tup[LEFT],tup[RIGHT])
1492             else:
1493                 dict[name] = (item[LEFT],item[RIGHT])
1494
1495         # The tag object is optional
1496         if dict["assignment"]:
1497             left,right = dict["assignment"]
1498             self.tagobj = self.text[left:right]
1499         else:
1500             self.tagobj = "None"
1501
1502         # The operation (command) and argument are required
1503         left,right = dict["op"]
1504         self.cmd = self.text[left:right]
1505
1506         #left,right = dict["arg"]
1507         left,right = dict["plusarg"]
1508         self.arg = self.text[left:right]
1509
1510         # Currently, we have one 'special' argument
1511         if self.arg == "back": self.arg = "-1"
1512
1513         # Actually, we don't want the F and T jumps explicit if not
1514         # given, since we mustn't output them for a single tuple if
1515         # they're not given (so they can be "added in" later on)
1516         if dict["onfalse"]:
1517             left,right = dict["onfalse"]
1518             self.onfalse = self.text[left:right]
1519         else:
1520             self.onfalse = None                # "MatchFail"
1521         if dict["ontrue"]:
1522             left,right = dict["ontrue"]
1523             self.ontrue = self.text[left:right]
1524         else:
1525             self.ontrue = None                 # "next"
1526
1527     def only_in_block(self):
1528         """Return true if we can only occur inside a block."""
1529         return 1
1530
1531     def resolve_labels(self,block):
1532         """Called to resolve any labels use in this line.
1533
1534         block -- the block that contains us
1535         """
1536         if self.onfalse:
1537             self.onfalse = block.translate_label(self.onfalse,self)
1538         if self.ontrue:
1539             self.ontrue  = block.translate_label(self.ontrue,self)
1540
1541     def _write_text(self,stream,block):
1542         """Write out the main tuple text.
1543
1544         stream  -- an object with a "write" method, e.g., a file
1545         block   -- used to pass the containing Block down to lines
1546                    within a block, or None if we're not in a block
1547
1548         It returns the number of characters written, or -1 if we had
1549         an error.
1550         """
1551
1552         # Start with the stuff we must have...
1553         stream.write("(%s,%s,%s"%(self.tagobj,self.cmd,self.arg))
1554         length = len(self.tagobj) + len(self.cmd) + len(self.arg) + 3
1555
1556         if self.ontrue:
1557             if not self.onfalse:
1558                 # OK, we didn't get an explicit F, but because it comes
1559                 # before the T jump in the tuple, we need to fake it
1560                 # anyway...
1561                 stream.write(",%s,%s)"%("MatchFail",self.ontrue))
1562                 length = length + len("MatchFail") + len(self.ontrue) + 3
1563             else:
1564                 # We had both F and T
1565                 stream.write(",%s,%s)"%(self.onfalse,self.ontrue))
1566                 length = length + len(self.onfalse) + len(self.ontrue) + 3
1567         elif self.onfalse:
1568             # We only had F. We shan't "fake" the T jump, *just* in case
1569             # the user is defining a single tuple that they'll add the
1570             # T jump to later on (although that *is* a bit dodgy, I think)
1571             # [[The option would be to "fake" it if we're IN a block - I may
1572             #   go for that approach later on]]
1573             stream.write(",%s)"%self.onfalse)
1574             length = length + len(self.onfalse) + 2
1575         else:
1576             # Neither F nor T - so don't write the defaults for either,
1577             # in case this is a top level tuple they're going to add to
1578             # later on...
1579             # [[Comments as for the case above, I think]]
1580             stream.write(")")
1581             length = length + 1
1582
1583         if block and not self.is_last:
1584             stream.write(",")
1585             length = length + 1
1586
1587         return length
1588 \f
1589 # ------------------------------------------------------------
1590 class TuplePlusLine(ContentLine):
1591     """A line containing a tuple "plus" (e.g., "fred + (+1,+1)").
1592
1593     Contains:
1594
1595         name    -- the name/identifier
1596         ontrue  -- what to do if true
1597         onfalse -- ditto false
1598     """
1599
1600     def __init__(self,lineno,indent_str,tagtuple,comment,text):
1601         """Instantiate a TuplePlusLine.
1602
1603             <identifier> + (onF,onT)
1604
1605         The content of the tagtuple is:
1606         
1607             ('tupleplus',left,right,[
1608               ('identifier',left,right,None)
1609               ('onfalse',left,right,[          -- optional
1610                  ('target',left,right,[
1611                    ('tgt',left,right,None)
1612                  ]),
1613               ('ontrue',left,right,[           -- optional
1614                  ('target',left,right,[
1615                    ('tgt',left,right,None)
1616                  ])
1617                ])
1618              ])
1619         """
1620
1621         ContentLine.__init__(self,lineno,indent_str,tagtuple,comment,text)
1622
1623         self.unpack()
1624
1625
1626     def unpack(self):
1627         """Unpack our contents from our tagtuple."""
1628
1629         # This is doubtless not the most efficient way of doing this,
1630         # but it IS relatively simple...
1631         dict = {}
1632         for key in ("identifier","onfalse","ontrue"):
1633             dict[key] = None
1634
1635         tuples = self.tagtuple[SUBLIST]
1636         for item in tuples:
1637             name = item[OBJECT]
1638             if name == "onfalse" or name == "ontrue":
1639                 # For these, we need to go "down one level" for our data
1640                 tup = item[SUBLIST][0]
1641                 dict[name] = (tup[LEFT],tup[RIGHT])
1642             else:
1643                 dict[name] = (item[LEFT],item[RIGHT])
1644
1645         # Start with the identifier
1646         left,right = dict["identifier"]
1647         self.name = self.text[left:right]
1648
1649         # Actually, we don't want the F and T jumps explicit if not
1650         # given, since we mustn't output them for a single tuple if
1651         # they're not given (so they can be "added in" later on)
1652         if dict["onfalse"]:
1653             left,right = dict["onfalse"]
1654             self.onfalse = self.text[left:right]
1655         else:
1656             self.onfalse = None                # "MatchFail"
1657         if dict["ontrue"]:
1658             left,right = dict["ontrue"]
1659             self.ontrue = self.text[left:right]
1660         else:
1661             self.ontrue = None                 # "next"
1662
1663     def only_in_block(self):
1664         """Return true if we can only occur inside a block."""
1665         return 1
1666
1667     def resolve_labels(self,block):
1668         """Called to resolve any labels use in this line.
1669
1670         block -- the block that contains us
1671         """
1672         if self.onfalse:
1673             self.onfalse = block.translate_label(self.onfalse,self)
1674         if self.ontrue:
1675             self.ontrue  = block.translate_label(self.ontrue,self)
1676
1677     def _write_text(self,stream,block):
1678         """Write out the main tuple text.
1679
1680         stream  -- an object with a "write" method, e.g., a file
1681         block   -- used to pass the containing Block down to lines
1682                    within a block, or None if we're not in a block
1683
1684         It returns the number of characters written, or -1 if we had
1685         an error.
1686         """
1687
1688         if not self.onfalse and not self.ontrue:
1689             stream.write("%s"%self.name)
1690             length = len(self.name)
1691         else:
1692             # Make a feeble attempt to cause successive such lines to
1693             # look neater, by aligning the "+" signs (if we output them)
1694             stream.write("%-15s + ("%(self.name))
1695             length = max(len(self.name),15) + 4
1696             if self.ontrue and self.onfalse:
1697                 stream.write("%s,%s)"%(self.onfalse,self.ontrue))
1698                 length = length + len(self.onfalse) + len(self.ontrue) + 2
1699             elif self.ontrue:
1700                 stream.write("MatchFail,%s)"%(self.ontrue))
1701                 length = length + len(self.ontrue) + 11
1702             else:
1703                 # Don't forget that comma to make this a tuple!
1704                 stream.write("%s,)"%(self.onfalse))
1705                 length = length + len(self.onfalse) + 1
1706
1707         if not self.is_last:
1708             stream.write(",")
1709             length = length + 1
1710
1711         return length
1712
1713 \f
1714 # ------------------------------------------------------------
1715 class JumpToLine(ContentLine):
1716     """A line containing "Jump To <label>"
1717
1718     Contains:
1719
1720         name    -- the name/identifier
1721         onfalse -- the target (which is technically an "on false" jump)
1722     """
1723
1724     def __init__(self,lineno,indent_str,tagtuple,comment,text):
1725         """Instantiate a JumpLine.
1726
1727             Jump To <label>
1728
1729         The content of the tagtuple is:
1730         
1731             ('jumpto',left,right,[
1732                ('target',left,right,[
1733                  ('tgt',left,right,None)
1734                ]),
1735              ])
1736         """
1737
1738         ContentLine.__init__(self,lineno,indent_str,tagtuple,comment,text)
1739
1740         tup = self.tagtuple[SUBLIST][0]
1741         self.onfalse = self.text[tup[LEFT]:tup[RIGHT]]
1742
1743     def only_in_block(self):
1744         """Return true if we can only occur inside a block."""
1745         return 1
1746
1747     def resolve_labels(self,block):
1748         """Called to resolve any labels use in this line.
1749
1750         block -- the block that contains us
1751         """
1752         self.onfalse = block.translate_label(self.onfalse,self)
1753
1754     def _write_text(self,stream,block):
1755         """Write out the main tuple text.
1756
1757         stream  -- an object with a "write" method, e.g., a file
1758         block   -- used to pass the containing Block down to lines
1759                    within a block, or None if we're not in a block
1760
1761         It returns the number of characters written, or -1 if we had
1762         an error.
1763         """
1764
1765         stream.write("(None,Jump,To,%s)"%(self.onfalse))
1766         length = len(self.onfalse) + 15
1767
1768         if not self.is_last:
1769             stream.write(",")
1770             length = length + 1
1771
1772         return length
1773
1774 \f
1775 # ------------------------------------------------------------
1776 class BadTableBlockLine(TableBlockLine):
1777     """We think they MEANT this to be a table block line."""
1778
1779     def __init__(self,lineno,indent_str,tagtuple,comment,text):
1780         """Instantiate a BadTableBlockLine.
1781
1782         For instance:
1783
1784             "fred" = Table:
1785             Table:
1786         """
1787         TableBlockLine.__init__(self,lineno,indent_str,tagtuple,comment,text)
1788         self.error("Suspected missing 'is' before the colon\n"
1789                    "pretending it's there")
1790
1791 \f
1792 # ------------------------------------------------------------
1793 class BadTupleLine(TupleLine):
1794     """We think they MEANT this to be a tuple line."""
1795
1796     def __init__(self,lineno,indent_str,tagtuple,comment,text):
1797         """Instantiate a BadTupleLine.
1798
1799         For instance:
1800
1801             "fred" = IsIn "abc"
1802         """
1803         TupleLine.__init__(self,lineno,indent_str,tagtuple,comment,text)
1804         self.error("Suspected missing '=' between tag object and command\n"
1805                    "pretending it's there")
1806
1807 \f
1808 # ------------------------------------------------------------
1809 class Block(ContentLine):
1810     """This class represents a "block".
1811
1812     A "block" is a section of code which starts with a line ending in
1813     a colon (":"), with the next line and subsequent lines ("in" the
1814     block) having an extra indent. The block ends when a dedent is
1815     encountered.
1816
1817     Each instance "eats" lines from the input until (if) it finds the first
1818     "sub" block.  That then "eats" lines until it finds its own end, and
1819     then hands control back to the first instance, which does the same thing
1820     again, and so on.
1821
1822     Note that we "pretend" to be a content line - it is convenient to
1823     look like a line class, so that line processing can cope with us,
1824     and indeed what we do is "pretend" to be a clone of our start line
1825     with some extra information...
1826
1827     Contains:
1828         startline    -- the line that "introduces" this block
1829         items        -- a list of the lines and blocks within this block
1830         label_dict   -- a dictionary of {label name : line index}
1831         inner_indent -- the indentation of our "inner" lines
1832         outer        -- true if we are an "outer" block
1833                         (i.e., not contained within another block)
1834     """
1835
1836     def __init__(self,startline=None,outer=0,file=None):
1837         """Instantiate a new block.
1838
1839         startline -- the line that introduces this block
1840         outer     -- true if we are an outer block
1841         file      -- the "file" we're reading lines from
1842         """
1843
1844         # Pretend to be our own startline (as a generic)
1845         ContentLine.__init__(self,
1846                              startline.lineno,startline.indent_str,
1847                              startline.tagtuple,startline.comment,
1848                              startline.text)
1849
1850         # But also remember the specifics of the startline
1851         self.startline = startline
1852
1853         # We "fudge" our class name
1854         self.class_name = self._block_class_name(startline)
1855
1856         self.outer    = outer
1857         self.file     = file
1858
1859         # If we're an outer table block, do we have a tagobj?
1860         if self.startline.class_name == "TableBlockLine" and outer:
1861             if not self.startline.got_tagobj():
1862                 raise NoIdentifier,\
1863                       "Tag table at line %d is not assigned to a variable"%\
1864                       (self.lineno)
1865             elif self.startline.is_subtable:
1866                 raise OutsideError,\
1867                       "SubTable is not allowed outside a block at line %d"%\
1868                       (self.lineno)
1869
1870         self.items    = []        # all lines within this block
1871         self.business = []        # just those that are "our business"
1872         self.label_dict = {}    # remember our labels and their locations
1873         self.next_index = 0     # 'business' line indices
1874         self.inner_indent = None
1875
1876         # Eat lines until we reach the end of our block...
1877         if DEBUGGING: print "%sStart %s"%(self.indent_str,self.class_name)
1878         self._eat_lines()
1879         self._end_block()
1880
1881     def _block_class_name(self,startline):
1882         """Return a representation of the class name."""
1883
1884         full_name = "%s"%self.__class__
1885         bits = string.split(full_name,".")
1886         return "%s/%s"%(bits[-1],startline.class_name)
1887
1888     def _eat_lines(self):
1889         """Eat lines until we run out of block..."""
1890
1891         while 1:
1892             try:
1893                 nextline = self.file.next()
1894             except EOFError:
1895                 return
1896
1897             # Check the indentation makes sense...
1898             if self.inner_indent:
1899                 # We already know how much our block is indented
1900                 # - is this line part of the block?
1901                 if nextline.indent < self.inner_indent:
1902                     # Apparently a dedent - is it what we expect?
1903                     if nextline.indent <= self.indent:
1904                         # Unread that line - it isn't one of ours!
1905                         self.file.unget()
1906                         return
1907                     else:
1908                         raise IndentError,\
1909                               "Line %d (%s) is indented less than the previous "\
1910                               "line, but its indentation doesn't match the "\
1911                               "start of the block at line %d"%\
1912                               (nextline.lineno,nextline.class_name,self.lineno)
1913                 elif nextline.indent > self.inner_indent:
1914                     # A spurious indent
1915                     # (note that doing this stops us from coping with,
1916                     #  for instance, things in (..), but then we also don't
1917                     #  cope with any form of continued line, or lots of other
1918                     #  things, so let's not worry too much for now!)
1919                     raise IndentError,\
1920                           "Line %d (%s) is indented more than the previous line"%\
1921                           (nextline.lineno,nextline.class_name)
1922             else:
1923                 # This is the first line of the (inside of) the block
1924                 # - check its indentation makes sense...
1925                 self.inner_indent = nextline.indent
1926                 if self.inner_indent <= self.indent:
1927                     raise IndentError,\
1928                           "Line %d (%s) should be indented more than line %d (%s)"%\
1929                           (nextline.lineno,nextline.class_name,
1930                            self.lineno,self.startline.class_name)
1931
1932             # Is it a line or the start of another block?
1933             if nextline.starts_block():
1934                 # Heh - it's the start of an inner block - add it
1935                 # (remember that instantiating it causes it to
1936                 #  "eat" the lines that belong to it)
1937                 self.items.append(Block(startline=nextline,
1938                                         outer=0,file=self.file))
1939             else:
1940                 self.items.append(nextline)
1941
1942     def _end_block(self):
1943         """End our block"""
1944
1945         if DEBUGGING: print "%sEnd %s"%(self.indent_str,self.class_name)
1946
1947         # If we're a tuple block, we should only have one line...
1948         # (that is, one "business" line)
1949         if self.startline.class_name == "TupleBlockLine" and \
1950            len(self.items) > 1:
1951             # Are all but one of them not "our business"?
1952             count = 0
1953             for item in self.items:
1954                 if item.our_business():
1955                     count = count + 1
1956                     if count > 1: break
1957             if count > 1:
1958                 self.error("Tuple declaration can only contain one 'business'"
1959                            " line, not %d\n"
1960                            "Assuming it's a table instead (i.e.,"
1961                            "'Table is:' instead of 'is:')"%len(self.items))
1962                 # Can we correct this by "pretending" its a table?
1963                 temp = TableBlockLine(self.startline.lineno,
1964                                       self.startline.indent_str,
1965                                       ("error",self.startline.name),
1966                                       self.startline.comment,
1967                                       self.text)
1968                 self.startline = temp
1969
1970         # We've now got all of our lines, and so we can go back over
1971         # them, expanding out any IF blocks (whose content is actually
1972         # within this block's scope, so who need to have their labels
1973         # (come from or go to) in that scope), working out the label
1974         # indices, and so on...
1975         # This uses "next_index" to calculate the indices of business
1976         # lines (needed for label calculation), and also populates the
1977         # "business" list with just the items that are "our_business()"
1978         if DEBUGGING:
1979             print "Expanding IF blocks, sorting out labels, etc."
1980
1981         temp       = self.items
1982         self.items = []
1983         for item in temp:
1984             if item.class_name == "Block/IfBlockLine":
1985                 self._add(item.startline)
1986                 for thing in item.items:
1987                     self._add(thing)
1988             else:
1989                 self._add(item)
1990
1991         # Go back through our contents and resolve any labels
1992         if DEBUGGING:
1993             print "%s...processing labels (next_index=%d)"%(self.indent_str,
1994                                                             self.next_index)
1995         self.startline.resolve_labels(self)
1996         # If we're an IF block, we mustn't try to resolve our component
1997         # lines' labels, as they're actually in our parent block's scope...
1998         if self.startline.class_name != "IfBlockLine":
1999             for item in self.items:
2000                 item.resolve_labels(self)
2001
2002         # If we're in a block that wants to suppress the comma at the
2003         # end of the last item in that block, tell the last item so...
2004         # (this is debatable for [Bad]TableBlockLine - it might be
2005         # better to leave the last comma there - so we have an option
2006         # to determine it...
2007         if self.startline.class_name == "TupleBlockLine" or \
2008            (not WANT_LAST_COMMA and \
2009             (self.startline.class_name == "TableBlockLine" or \
2010              self.startline.class_name == "BadTableBlockLine")):
2011             if len(self.business) > 0:
2012                 self.business[-1].is_last = 1
2013
2014     def _add(self,item):
2015         """Add a line or block to our list of items.
2016
2017         item -- the Line or Block instance to add
2018
2019         NB: Also adds it to our "business" list if it is our business
2020             (and not a label)
2021         """
2022
2023         if item.class_name == "LabelLine":
2024             self.label_dict[item.label] = self.next_index
2025             if DEBUGGING:
2026                 print "%sadd [%2d] %s"%(item.indent_str,self.next_index,item)
2027             # Might as well give it the index it is labelling
2028             item.index = self.next_index
2029             self.items.append(item)
2030         elif item.our_business():
2031             item.index = self.next_index
2032             self.items.append(item)
2033             self.business.append(item)
2034             if DEBUGGING:
2035                 print "%sadd  %2d  %s"%(item.indent_str,
2036                                             self.next_index,item)
2037             self.next_index = self.next_index + 1
2038         else:
2039             # It's not something we can assign a sensible index to, so don't
2040             if DEBUGGING:
2041                 print "%sadd  xx  %s"%(item.indent_str,item)
2042             self.items.append(item)
2043
2044     def translate_label(self,label,line):
2045         """Given a label, return its translation.
2046
2047         label -- either a string of the form "<...>" to look up in
2048                  this block's label dictionary, or one of the special
2049                  targets (e.g., next, MatchOk, etc.)
2050         line  -- the line using this label
2051
2052         Reports an error and just returns the original "label" if it
2053         can't translate it.
2054         """
2055         if self.label_dict.has_key(label):
2056             # How far do we have to jump?
2057             offset = self.label_dict[label] - line.index
2058             return "%+d"%offset
2059         elif label == "MatchOk":
2060             return "MatchOk"
2061         elif label == "MatchOK":
2062             line.warning("Label 'MatchOK' should be spelt 'MatchOk'"
2063                          " (using 'MatchOk')")
2064             return "MatchOk"
2065         elif label == "MatchFail":
2066             return "MatchFail"
2067         elif label == "next":
2068             return "+1"
2069         elif label == "previous":
2070             return "-1"
2071         elif label == "repeat":
2072             return "0"
2073         else:
2074             line.error("Undefined label '%s'"%label)
2075             return label
2076
2077     def expand(self,stream,block=None):
2078         """Write out the expanded equivalent of ourselves.
2079
2080         stream  -- an object with a "write" method, e.g., a file
2081         block   -- if we're in a block, this is it, otherwise None
2082         """
2083
2084         self.startline.expand(stream,block=block)
2085         for item in self.items[:-1]:
2086             item.expand(stream,block=self)
2087
2088         self.items[-1].expand(stream,block=self)
2089
2090         # Deal with closing any block parentheses
2091         if self.startline.class_name == "TableBlockLine" or \
2092            self.startline.class_name == "BadTableBlockLine":
2093             if DEBUGGING:
2094                 stream.write("Line ...: ")
2095
2096             stream.write(self.indent_str)
2097             if self.outer:
2098                 # Outer block - just close it
2099                 stream.write(")")
2100             else:
2101                 # Inner block is a Table block, and we need to close both
2102                 # the tuple-of-tuples, and also the tuple containing the
2103                 # Table command...
2104                 stream.write("))")
2105             if not self.is_last:
2106                 stream.write(",")
2107             stream.write("\n")
2108
2109 \f
2110 # ------------------------------------------------------------
2111 class File:
2112     """This is the class that holds our processed data
2113
2114     Contains:
2115         lines   -- a list of the line instances for each "line" in our text
2116         items   -- a list of lines and BLOCKs
2117     """
2118
2119     def __init__(self,tagtuples,text):
2120         """Instantiate a File
2121
2122         tagtuples -- the list of mxTextTools tag tuples generated by
2123                      parsing the data in "text"
2124         text      -- the text we parsed
2125         """
2126
2127         self.text      = text
2128         self.tagtuples = tagtuples
2129
2130         # Assemble our list of lines
2131         print "Pass 1: assembling lines"
2132         if DEBUGGING: print "~~~~~~~~~~~~~~~~~~~~~~~~"
2133         self.lines = []
2134         lineno     = 0
2135         prevline   = None
2136         for tagtuple in tagtuples:
2137             lineno = lineno + 1
2138             thisline = LineFactory(lineno,tagtuple,text)
2139
2140             if prevline:
2141                 prevline.next_indent = thisline.indent
2142
2143             self.lines.append(thisline)
2144             prevline = thisline
2145
2146         #if DEBUGGING: print
2147
2148         # The indentation of an empty line is taken to be the same
2149         # as the indentation of the first following non-empty line
2150         # The easiest way to do that is to work backwards through
2151         # the list (is it better to take a copy and reverse THAT,
2152         # or to reverse our original list twice?)
2153         print "Pass 2: sorting out indentation of empty lines"
2154         if DEBUGGING: print "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
2155         revlist = self.lines[:]
2156         revlist.reverse()
2157         indent = 0
2158         for line in revlist:
2159             if line.class_name == "EmptyLine":
2160                 line.change_indent(indent)
2161             else:
2162                 indent = line.indent
2163         del revlist
2164
2165         if DEBUGGING:
2166             print "Pass 2.5 - the contents of those lines..."
2167             print "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
2168             for line in self.lines:
2169                 print "Line %d %s"%(line.lineno,line.class_name)
2170                 #print_tuples([line.tagtuple],self.text,"  ")
2171             print
2172
2173         # Now we need to assemble blocks
2174         print "Pass 3: assembling blocks"
2175         if DEBUGGING: print "~~~~~~~~~~~~~~~~~~~~~~~~~"
2176         self.reset()
2177         self.items = []
2178
2179         while 1:
2180             try:
2181                 item = self.next()
2182             except EOFError:
2183                 break
2184
2185             if DEBUGGING:
2186                 print "%sTOP    %s"%(item.indent_str,item)
2187             if item.starts_block():
2188                 block = Block(startline=item,outer=1,file=self)
2189                 self.items.append(block)
2190                 block.is_last = 1   # Everything at outer level is "last"
2191             else:
2192                 if item.only_in_block():
2193                     item.error("This line is not allowed outside a block "
2194                                "- continuing anyway")
2195                 self.items.append(item)
2196                 if item.our_business():
2197                     item.is_last = 1    # Everything at outer level is "last"
2198
2199         if DEBUGGING: print
2200                 
2201
2202     def reset(self):
2203         """Ensure that the next call of "nextline" returns the first line."""
2204         self.index = -1
2205
2206     def unget(self):
2207         """Unread the current line."""
2208         self.index = self.index - 1
2209         if self.index < 0:
2210             self.index = 0
2211
2212     def next(self):
2213         """Retrieve the next line from the list of lines in this "file".
2214
2215         Raises EOFError if there is no next line (i.e., "end of file")
2216         """
2217         self.index = self.index + 1
2218         try:
2219             return self.lines[self.index]
2220         except IndexError:
2221             # leave the index off the end, so we get EOF again if
2222             # we're called again - but there's no point courting overflow...
2223             self.index = self.index -1
2224             raise EOFError
2225
2226     def expand(self,stream):
2227         """Expand out the result."""
2228         for item in self.items:
2229             item.expand(stream)
2230
2231 \f
2232 # ------------------------------------------------------------
2233 def print_tuples(tuples,text,indent=""):
2234     """Print out a list of tuples in a neat form
2235
2236     tuples -- our tuple list
2237     text   -- the text it tags
2238     indent -- our current indentation
2239     """
2240
2241     # Tuples are of the form:
2242     # (object,left_index,right_index,sublist)
2243
2244     for obj,left,right,sub in tuples:
2245         if sub:
2246             print "%s%s"%(indent,obj)
2247             print_tuples(sub,text,indent+"  ")
2248         else:
2249             # Terminal node - show the actual text we've tagged!
2250             print "%s%s = %s"%(indent,obj,`text[left:right]`)
2251
2252 \f
2253 # ------------------------------------------------------------
2254 def print_text(text):
2255     """Print out text with line numbers."""
2256     lines = string.split(text,"\n")
2257     lineno = 0
2258
2259     print "Original text"
2260     print "============="
2261     for line in lines:
2262         lineno = lineno + 1
2263         print "%3d: %s"%(lineno,`line`)
2264
2265 \f
2266 # ------------------------------------------------------------
2267 def print_usage(argv0):
2268     #script_name = string.split(argv0, os.sep)[-1]
2269     #print __doc__%(script_name)
2270     print argv0
2271     print __doc__
2272
2273 \f
2274 # ------------------------------------------------------------
2275 def show_tup(indent,nn,tup):
2276     ll = []
2277     for item in tup:
2278         if type(item) == type((1,)) or type(item) == type([]):
2279             ll.append("(..)")
2280         else:
2281             ll.append(`item`)
2282
2283     if nn:
2284         print "%s%d: (%s)"%(indent,nn,string.join(ll,","))
2285     else:
2286         print "%s(%s)"%(indent,string.join(ll,","))
2287
2288 def comp_sub(indent,one,two):
2289     len1 = len(one)
2290     if len(two) != len(one):
2291         print "%sTuple lengths differ - 1:%d, 2:%d"%(indent,len1,len(two))
2292         show_tup(indent,1,one)
2293         show_tup(indent,2,two)
2294         # If this is all, let's try to continue...
2295         len1 = min(len1,len(two))
2296
2297     for count in range(len1):
2298         a = one[count]
2299         b = two[count]
2300         if type(a) != type(b):
2301             print "%sValue types differ, item %d: 1:%s, 2:%s"%(indent,count,
2302                                                                type(a),type(b))
2303             show_tupe(indent,1,one)
2304             show_tupe(indent2,two)
2305             return 0
2306         if type(a) == type((1,)) or type(a) == type([]):
2307             if not comp_sub(indent+"  ",a,b):
2308                 # They're the same at this level, so show only one...
2309                 show_tup(indent,0,one)
2310                 return 0
2311         else:
2312             if a != b:
2313                 print "%sValues differ, item %d: 1:%s, 2:%s"%(indent,count,
2314                                                               `a`,`b`)
2315                 show_tup(indent,1,one)
2316                 show_tup(indent,2,two)
2317                 return 0
2318     return 1
2319
2320 def compare_tagtables(one,two):
2321     # Each table is made up of tuples of the form
2322     # (tagobj,action,arg,onfalse,ontrue)
2323     # but if action is Table or SubTable then arg may be a tuple
2324     # itself...
2325     if comp_sub("",one,two):
2326         print "They appear to be the same"
2327
2328 \f
2329 # ------------------------------------------------------------
2330 def main():
2331     """Used to test the module."""
2332
2333     debug_pytag  = DEFAULT_DEBUG
2334     use_pytag    = DEFAULT_PYTAG
2335     use_stdout   = 0
2336     import_tags  = 0
2337     force_overwrite = 0
2338     compare_tables  = 0
2339
2340     if os.name == "posix":
2341         use_testdata = 0
2342     else:
2343         # At home...
2344         use_testdata = 1
2345         use_stdout   = 1
2346         global DEBUGGING
2347         DEBUGGING    = 0
2348
2349     # Do we have command line arguments?
2350     arg_list = sys.argv[1:]
2351     args = []
2352
2353     while 1:
2354         if len(arg_list) == 0:
2355             break
2356
2357         word = arg_list[0]
2358
2359         if word == "-pytag":
2360             use_pytag = 1
2361         elif word == "-debug":
2362             debug_pytag = 1
2363         elif word == "-stdout":
2364             use_stdout = 1
2365         elif word == "-force":
2366             force_overwrite = 1
2367         elif word == "-import":
2368             import_tags = 1
2369         elif word == "-compare":
2370             compare_tables = 1
2371         elif word == "-diag":
2372             global DEBUGGING
2373             DEBUGGING = 1
2374         elif word == "-test":
2375             use_testdata = 1
2376             use_stdout = 1
2377         elif word == "-help":
2378             print_usage(sys.argv[0])
2379             return
2380         elif word == "-version":
2381             print "Version:",__version__
2382             return
2383         elif word == "-history":
2384             print "History:"
2385             print __history__
2386             return
2387         else:
2388             args.append(word)
2389
2390         arg_list = arg_list[1:]
2391         continue
2392
2393     if compare_tables:
2394         from Translate_tags import t_file
2395         i_file = define_tagtable()
2396         print "Comparing internal table (1) against external (2)"
2397         compare_tagtables(i_file,t_file)
2398         return
2399
2400     if not use_testdata and (not args or len(args) > 2):
2401         print_usage(sys.argv[0])
2402         return
2403
2404     if not use_testdata:
2405         infile = args[0]
2406
2407     if import_tags:
2408         print "Importing tag table definition"
2409         from Translate_tags import t_file
2410     else:
2411         print "Using internal tag table definition"
2412         t_file = define_tagtable()
2413
2414     if use_stdout:
2415         outfile = "standard output"
2416     elif len(args) > 1:
2417         outfile = args[1]
2418     else:
2419         base,ext = os.path.splitext(infile)
2420         if ext != ".py":
2421             outfile = base + ".py"
2422         else:
2423             print "Input file has extension .py so won't guess"\
2424                   " an output file"
2425             return
2426
2427     if outfile != "standard output":
2428         if outfile == infile:
2429             print "The output file is the same as the input file"
2430             print "Refusing to overwrite %s"%outfile
2431             return
2432         elif os.path.exists(outfile):
2433             if force_overwrite:
2434                 print "Output file %s already exists"\
2435                       " - overwriting it"%outfile
2436             else:
2437                 print "Output file %s already exists"%outfile
2438                 return
2439
2440     # Read the input file
2441     if use_testdata:
2442         if DEBUGGING: print
2443         print "Using test data"
2444         if DEBUGGING: print "==============="
2445         text = test_data
2446     else:
2447         if DEBUGGING: print
2448         print "Reading text from %s"%infile
2449         if DEBUGGING: print "=================="+"="*len(infile)
2450         file = open(infile,"r")
2451         text = file.read()
2452         file.close()
2453
2454     # Show what we are trying to parse
2455     if DEBUGGING or use_testdata:
2456         print
2457         print_text(text)
2458
2459     # Tag it
2460     print
2461     print "Tagging text"
2462     if DEBUGGING: print "============"
2463     if use_pytag:
2464         import pytag
2465         pytag.set_verbosity(0)
2466         if debug_pytag:
2467             pytag.set_verbosity(1)
2468             pytag.use_debugger()
2469         result,taglist,next = pytag.pytag(text,t_file)
2470     else:
2471         timer = TextTools._timer()
2472         timer.start()
2473         result, taglist, next = tag(text,t_file)
2474         #result, taglist, next = tag(text,t_file,0,len(text),taglist)
2475         print "Tagging took",timer.stop()[0],"seconds"
2476
2477     # Now print out the result of the tagging
2478     print
2479     print "Manipulating tagged data"
2480     if DEBUGGING: print "========================"
2481     tagfile = File(taglist,text)
2482
2483     print
2484     print "Writing translation to %s"%outfile
2485     if DEBUGGING: print "======================="+"="*len(outfile)
2486
2487     # Open the output file, if necessary
2488     if use_stdout:
2489         file = sys.stdout
2490     else:
2491         file = open(outfile,"w")
2492
2493     tagfile.expand(file)
2494
2495 \f
2496 # ------------------------------------------------------------
2497 if __name__ == '__main__':
2498     main()