leoTangle.py :  » Development » Leo » Leo-4.7.1-final » leo » core » Python Open Source

Home
Python Open Source
1.3.1.2 Python
2.Ajax
3.Aspect Oriented
4.Blog
5.Build
6.Business Application
7.Chart Report
8.Content Management Systems
9.Cryptographic
10.Database
11.Development
12.Editor
13.Email
14.ERP
15.Game 2D 3D
16.GIS
17.GUI
18.IDE
19.Installer
20.IRC
21.Issue Tracker
22.Language Interface
23.Log
24.Math
25.Media Sound Audio
26.Mobile
27.Network
28.Parser
29.PDF
30.Project Management
31.RSS
32.Search
33.Security
34.Template Engines
35.Test
36.UML
37.USB Serial
38.Web Frameworks
39.Web Server
40.Web Services
41.Web Unit
42.Wiki
43.Windows
44.XML
Python Open Source » Development » Leo 
Leo » Leo 4.7.1 final » leo » core » leoTangle.py
#@+leo-ver=4-thin
#@+node:ekr.20031218072017.3446:@thin leoTangle.py
#@@language python
#@@tabwidth -4
#@@pagewidth 70

# Tangle and Untangle.
import leo.core.leoGlobals as g
import os
import string

#@<< about Tangle and Untangle >>
#@+node:ekr.20031218072017.2411:<< About Tangle and Untangle >>
#@+at 
#@nonl
# The Tangle command translates the selected @root tree into one or 
# more well-formatted C source files. The outline should contain 
# directives, sections references and section definitions, as 
# described in Chapter 4. The Untangle command is essentially the 
# reverse of the Tangle command. The Tangle command creates a 
# derived file from an @root tree; the Untangle command incorporates 
# changes made to derived files back into the @root tree.
# 
# The Tangle command operates in two passes. The first pass 
# discovers the complete definitions of all sections and places 
# these definitions in a symbol table. The first pass also makes a 
# list of root sections. Definitions can appear in any order, so we 
# must scan the entire input file to know whether any particular 
# definition has been completed.
# 
# Tangle's second pass creates one file for each @root node. Tangle 
# rescans each section in the list of roots, copying the root text 
# to the output and replacing each section reference by the 
# section's definition. This is a recursive process because any 
# definition may contain other references. We can not allow a 
# section to be defined in terms of itself, either directly or 
# indirectly. We check for such illegally recursive definitions in 
# pass 2 using the section stack class.  Tangle indicates where 
# sections begin and end using comment lines called sentinel lines.  
# The this part of the appendix discusses the format of the 
# sentinels output by the Tangle command.
# 
# The key design principle of the Tangle command is this: Tangle 
# must output newlines in a context-free manner. That is, Tangle 
# must never output conditional newlines, either directly or 
# indirectly. Without this rule Untangle could not determine whether 
# to skip or copy newlines.
# 
# The Tangle command increases the indentation level of a section 
# expansion the minimum necessary to align the section expansion 
# with the surrounding code. In essence, this scheme aligns all 
# section expansions with the line of code in which the reference to 
# the section occurs. In some cases, several nested sections 
# expansions will have the same indentation level. This can occur, 
# for example, when a section reference in an outline occurs at the 
# left margin of the outline.
# 
# This scheme is probably better than more obvious schemes that 
# indent more "consistently." Such schemes would produce too much 
# indentation for deeply nested outlines. The present scheme is 
# clear enough and avoids indentation wherever possible, yet indents 
# sections adequately. End sentinel lines make this scheme work by 
# making clear where the expansion of one section ends and the 
# expansion of a containing section resumes.
# 
# Tangle increases indentation if the section reference does not 
# start a line. Untangle is aware of this hack and adjusts 
# accordingly. This extra indentation handles several common code 
# idioms, which otherwise would create under-indented code. In 
# short, Tangle produces highly readable, given the necessity of 
# preserving newlines for Untangle.
# 
# Untangle is inherently complex.  It must do a perfect job of 
# updating the outline, especially whitespace, from expansions of 
# section definitions created by the Tangle command.  Such 
# expansions need not be identical because they may have been 
# generated at different levels of indentation.  The Untangle 
# command can not assume that all expansions of a section will be 
# identical in the derived file; within the derived file, the 
# programmer may have made incompatible changes to two different 
# expansions of the same section. Untangle must check to see that 
# all expansions of a section are "equivalent".  As an added 
# complication, derived files do not contain all the information 
# found in @root trees.  @root trees may contain headlines that 
# generate no code at all.  Also, an outline may define a section in 
# several ways: with an @c or @code directive or with a section 
# definition line.  To be useful, Untangle must handle all these 
# complications flawlessly. The appendix discusses the various 
# conventions used in the sentinels output by the Tangle command.  
# These conventions allow the Untangle command to recreate 
# whitespace correctly.
# 
# Untangle operates in two passes. The first pass finds definitions 
# in the derived file and enters them into the Untangle Symbol 
# Table, or UST.   Definitions often include references to other 
# sections, so definitions often include nested definitions of 
# referenced sections. The first pass of Untangle uses a definition 
# stack to keep track of nested definitions. The top of the stack 
# represents the definition following the latest reference, except 
# for the very first entry pushed on the stack, which represents the 
# code in the outline that contains the @root directive. The stack 
# never becomes empty because of the entry for the @root section. 
# All definitions of a section should match--otherwise there is an 
# inconsistent definition. This pass uses a forgiving compare 
# routine that ignores differences that do not affect the meaning of 
# a program.
# 
# Untangle's second pass enters definitions from the outline into 
# the Tangle Symbol Table, or TST. The second pass simultaneously 
# updates all sections in the outline whose definition in the TST 
# does not match the definition in the UST.  The central coding 
# insight of the Untangle command is that the second pass of 
# Untangle is almost identical to the first pass of Tangle! That is, 
# Tangle and Untangle share key parts of code, namely the 
# skip_body() method and its allies.  Just when skip_body() enters a 
# definition into the symbol table, all the information is present 
# that Untangle needs to update that definition.
#@-at
#@-node:ekr.20031218072017.2411:<< About Tangle and Untangle >>
#@nl
#@<< constants >>
#@+node:ekr.20031218072017.3447:<< constants >>
max_errors = 20

# All these must be defined together, because they form a single enumeration.
# Some of these are used by utility functions.

# Used by token_type().
plain_line = 1 # all other lines
at_at      = 2 # double-at sign.
at_chapter = 3 # @chapter
# at_c     = 4 # @c in noweb mode
at_code    = 5 # @code, or @c or @p in CWEB mode.
at_doc     = 6 # @doc
at_other   = 7 # all other @directives
at_root    = 8 # @root or noweb * sections
at_section = 9 # @section
# at_space = 10 # @space
at_web     = 11 # any CWEB control code, except at_at.

# Returned by self.skip_section_name() and allies and used by token_type.
bad_section_name = 12  # < < with no matching > >
section_ref  = 13  # < < name > >
section_def  = 14  # < < name > > =

# Returned by is_sentinal_line.
non_sentinel_line   = 15
start_sentinel_line = 16
end_sentinel_line   = 17

# Stephen P. Schaefer 9/13/2002
# add support for @first
at_last    = 18
#@-node:ekr.20031218072017.3447:<< constants >>
#@nl

#@+others
#@+node:ekr.20031218072017.3448:node classes
#@+node:ekr.20031218072017.3449:class tst_node
class tst_node:
    #@    @+others
    #@+node:ekr.20031218072017.3450:tst_node.__init__
    def __init__ (self,name,root_flag):

        # g.trace("tst_node.__init__",name)
        self.name = name
        self.is_root = root_flag
        self.referenced = False
        self.parts = []
    #@-node:ekr.20031218072017.3450:tst_node.__init__
    #@+node:ekr.20031218072017.3451:tst_node.__repr__
    def __repr__ (self):

        return "tst_node:" + self.name
    #@-node:ekr.20031218072017.3451:tst_node.__repr__
    #@-others
#@-node:ekr.20031218072017.3449:class tst_node
#@+node:ekr.20031218072017.3452:class part_node
class part_node:
    #@    @+others
    #@+node:ekr.20031218072017.3453:part_node.__init__
    def __init__ (self,name,code,doc,is_root,is_dirty):

        # g.trace("part_node.__init__",name)
        self.name = name # Section or file name.
        self.code = code # The code text.
        self.doc = doc # The doc text.
        self.is_dirty = is_dirty # True: vnode for body text is dirty.
        self.is_root = is_root # True: name is a root name.
    #@-node:ekr.20031218072017.3453:part_node.__init__
    #@+node:ekr.20031218072017.3454:part_node.__repr__
    def __repr__ (self):

        return "part_node:" + self.name
    #@-node:ekr.20031218072017.3454:part_node.__repr__
    #@-others
#@-node:ekr.20031218072017.3452:class part_node
#@+node:ekr.20031218072017.3455:class ust_node
class ust_node:
    #@    @+others
    #@+node:ekr.20031218072017.3456:ust_node.__init__
    #@+at 
    #@nonl
    # The text has been masssaged so that 1) it contains no leading 
    # indentation and 2) all code arising from section references 
    # have been replaced by the reference line itself.  Text for all 
    # copies of the same part can differ only in non-critical white 
    # space.
    #@-at
    #@@c

    def __init__ (self,name,code,part,of,nl_flag,update_flag):

        # g.trace("ust_node.__init__",name,part)
        self.name = name # section name
        self.parts = {} # parts dict
        self.code = code # code text
        self.part = part # n in "(part n of m)" or zero.
        self.of = of  # m in "(part n of m)" or zero.
        self.nl_flag = nl_flag  # True: section starts with a newline.
        self.update_flag = update_flag # True: section corresponds to a section in the outline.
    #@-node:ekr.20031218072017.3456:ust_node.__init__
    #@+node:ekr.20031218072017.3457:ust_node.__repr__
    def __repr__ (self):

        return "ust_node:" + self.name
    #@-node:ekr.20031218072017.3457:ust_node.__repr__
    #@-others
#@-node:ekr.20031218072017.3455:class ust_node
#@+node:ekr.20031218072017.3458:class def_node
class def_node:
    #@    @+others
    #@+node:ekr.20031218072017.3459:def_node.__init__
    #@+at 
    #@nonl
    # The text has been masssaged so that 1) it contains no leading 
    # indentation and 2) all code arising from section references 
    # have been replaced by the reference line itself.  Text for all 
    # copies of the same part can differ only in non-critical white 
    # space.
    #@-at
    #@@c

    def __init__ (self,name,indent,part,of,nl_flag,code):

        if 0:
            g.trace("def_node.__init__:",
                "name:",name," part:",part," of:",of," indent:",indent)
        self.name = name
        self.indent = indent
        self.code = code
        if self.code == None: self.code = ""
        self.part = part
        self.of = of
        self.nl_flag = nl_flag
    #@-node:ekr.20031218072017.3459:def_node.__init__
    #@+node:ekr.20031218072017.3460:def_node.__repr__
    def __repr__ (self):

        return "def_node:" + self.name
    #@-node:ekr.20031218072017.3460:def_node.__repr__
    #@-others
#@-node:ekr.20031218072017.3458:class def_node
#@+node:ekr.20031218072017.3461:class root_attributes (Stephen P. Schaefer)
#@+at 
#@nonl
# Stephen P. Schaefer, 9/2/2002
# Collect the root node specific attributes in an
# easy-to-use container.
#@-at
#@@c

class root_attributes:
    #@    @+others
    #@+node:ekr.20031218072017.3462:root_attributes.__init__
    #@+at 
    #@nonl
    # Stephen P. Schaefer, 9/2/2002
    # Keep track of the attributes of a root node
    #@-at
    #@@c

    def __init__ (self, tangle_state):

        if 0:
            #@        << trace the state >>
            #@+node:ekr.20031218072017.3463:<< trace the state >>
            try:
                if tangle_state.path: pass
            except AttributeError:
                tangle_state.path = None

            g.trace("def_root_attribute.__init__",
                "language:" + tangle_state.language +
                ", single_comment_string: " + tangle_state.single_comment_string +
                ", start_comment_string: " + tangle_state.start_comment_string +
                ", end_comment_string: " + tangle_state.end_comment_string +
                ", use_header_flag: " + tangle_state.use_header_flag +
                ", print_mode: " + tangle_state.print_mode +
                ", path: " + g.choose(tangle_state.path, tangle_state.path, "") +
                ", page_width: " + tangle_state.page_width +
                ", tab_width: " + tangle_state.tab_width +
                # Stephen P. Schaefer 9/13/2002
                ", first_lines: " + tangle_state.first_lines)
            #@-node:ekr.20031218072017.3463:<< trace the state >>
            #@nl
        self.language = tangle_state.language
        self.single_comment_string = tangle_state.single_comment_string
        self.start_comment_string = tangle_state.start_comment_string
        self.end_comment_string = tangle_state.end_comment_string
        self.use_header_flag = tangle_state.use_header_flag
        self.print_mode = tangle_state.print_mode

        # of all the state variables, this one isn't set in tangleCommands.__init__
        # peculiar
        try:
            self.path = tangle_state.path
        except AttributeError:
            self.path = None

        self.page_width = tangle_state.page_width
        self.tab_width = tangle_state.tab_width
        self.first_lines = tangle_state.first_lines # Stephen P. Schaefer 9/13/2002
    #@-node:ekr.20031218072017.3462:root_attributes.__init__
    #@+node:ekr.20031218072017.3464:root_attributes.__repr__
    def __repr__ (self):

        return ("root_attributes: language: " + self.language +
            ", single_comment_string: " + self.single_comment_string +
            ", start_comment_string: " + self.start_comment_string +
            ", end_comment_string: " + self.end_comment_string +
            ", use_header_flag: " + self.use_header_flag +
            ", print_mode: " + self.print_mode +
            ", path: " + self.path +
            ", page_width: " + self.page_width +
            ", tab_width: " + self.tab_width +
            # Stephen P. Schaefer 9/13/2002
            ", first_lines: " + self.first_lines)
    #@-node:ekr.20031218072017.3464:root_attributes.__repr__
    #@-others
#@-node:ekr.20031218072017.3461:class root_attributes (Stephen P. Schaefer)
#@-node:ekr.20031218072017.3448:node classes
#@+node:ekr.20031218072017.3465:class tangleCommands methods
class baseTangleCommands:
    """The base class for Leo's tangle and untangle commands."""
    #@    @+others
    #@+node:ekr.20031218072017.3466:tangle.__init__
    def __init__ (self,c):

        self.c = c
        self.init_ivars()
    #@-node:ekr.20031218072017.3466:tangle.__init__
    #@+node:ekr.20031218072017.1356:tangle.init_ivars & init_directive_ivars
    # Called by __init__

    def init_ivars(self):

        c = self.c
        g.app.scanErrors = 0
        #@    << init tangle ivars >>
        #@+node:ekr.20031218072017.1357:<< init tangle ivars >>
        # Various flags and counts...

        self.errors = 0 # The number of errors seen.
        self.tangling = True # True if tangling, False if untangling.
        self.path_warning_given = False # True: suppress duplicate warnings.
        self.tangle_indent = 0 # Level of indentation during pass 2, in spaces.
        if c.frame:
            self.file_name = c.mFileName # The file name (was a bridge function)
        else:
            self.file_name = "<unknown file name>"
        self.p = None # position being processed.
        self.output_file = None # The file descriptor of the output file.
        self.start_mode = "doc" # "code" or "doc".  Use "doc" for compatibility.
        self.tangle_output = '' # For unit testing.

        #@+at 
        #@nonl
        # Symbol tables: the TST (Tangle Symbol Table) contains all 
        # section names in the outline.
        # The UST (Untangle Symbol Table) contains all sections 
        # defined in the derived file.
        #@-at
        #@@c
        self.tst = {}
        self.ust = {}

        # The section stack for Tangle and the definition stack for Untangle.
        self.section_stack = []
        self.def_stack = []

        #@+at 
        #@nonl
        # The list of all roots. The symbol table routines add roots 
        # to self list during pass 1. Pass 2 uses self list to 
        # generate code for all roots.
        #@-at
        #@@c
        self.root_list = []

        # The delimiters for comments created by the @comment directive.
        self.single_comment_string = "//"  # present comment delimiters.
        self.start_comment_string = "/*"
        self.end_comment_string = "*/"
        self.sentinel = None

        # g.trace(self.single_comment_string)

        # The filename following @root in a headline.
        # The code that checks for < < * > > = uses these globals.
        self.root = None
        self.root_name = None

        # Formerly the "tangle private globals"
        # These save state during tangling and untangling.
        # It is possible that these will be removed...
        if 1:
            self.head_root = None
            self.code = None
            self.doc = None
            self.header_name = None
            self.header = None
            self.section_name = None

        #@+at 
        #@nonl
        # The following records whether we have seen an @code 
        # directive in a body text.
        # If so, an @code represents < < header name > > = and it is 
        # valid to continue a section definition.
        #@-at
        #@@c
        self.code_seen = False # True if @code seen in body text.

        # Support of output_newline option
        self.output_newline = g.getOutputNewline(c=c)
        #@-node:ekr.20031218072017.1357:<< init tangle ivars >>
        #@nl
        #@    << init untangle ivars >>
        #@+node:ekr.20031218072017.1358:<< init untangle ivars >>
        #@+at 
        #@nonl
        # Untangle vars used while comparing.
        #@-at
        #@@c
        self.line_comment = self.comment = self.comment_end = None
        self.comment2 = self.comment2_end = None
        self.string1 = self.string2 = self.verbatim = None
        self.message = None # forgiving compare message.
        #@-node:ekr.20031218072017.1358:<< init untangle ivars >>
        #@nl

    # Called by scanAllDirectives

    def init_directive_ivars (self):

        c = self.c
        #@    << init directive ivars >>
        #@+node:ekr.20031218072017.1359:<< init directive ivars >> (tangle)
        # Global options
        self.page_width = c.page_width
        self.tab_width = c.tab_width

        # New in Leo 4.5: get these from settings.
        self.output_doc_flag = c.config.getBool('output_doc_flag')
        self.tangle_batch_flag = c.config.getBool('tangle_batch_flag')
        self.untangle_batch_flag = c.config.getBool('untangle_batch_flag')
        self.use_header_flag = c.config.getBool('use_header_flag')

        # Default tangle options.
        self.tangle_directory = None # Initialized by scanAllDirectives

        # Default tangle language
        if c.target_language: c.target_language = c.target_language.lower()
        self.language = c.target_language
        delim1,delim2,delim3 = g.set_delims_from_language(self.language)
        # g.trace(delim1,delim2,delim3)

        # 8/1/02: this now works as expected.
        self.single_comment_string = delim1
        self.start_comment_string = delim2
        self.end_comment_string = delim3

        # g.trace(self.single_comment_string)

        # Abbreviations for self.language.
        # Warning: these must also be initialized in tangle.scanAllDirectives.
        if 1: # 10/30/02: Don't change the code, just ignore @language cweb.
            self.use_cweb_flag = False
            self.raw_cweb_flag = self.language == "cweb" # A new ivar.
        else:
            self.use_cweb_flag = self.language == "cweb"
            self.raw_cweb_flag = False # was never used before.

        self.use_noweb_flag = not self.use_cweb_flag

        # Set only from directives.
        self.print_mode = "verbose"

        # Stephen P. Schaefer 9/13/2002
        # support @first directive
        self.first_lines = ""
        self.encoding = c.config.default_derived_file_encoding # 2/21/03
        self.output_newline = g.getOutputNewline(c=c) # 4/24/03: initialize from config settings.
        #@-node:ekr.20031218072017.1359:<< init directive ivars >> (tangle)
        #@nl
    #@-node:ekr.20031218072017.1356:tangle.init_ivars & init_directive_ivars
    #@+node:ekr.20031218072017.3467:top level
    #@+at 
    #@nonl
    # Only top-level drivers initialize ivars.
    #@-at
    #@+node:ekr.20031218072017.3468:cleanup
    # This code is called from tangleTree and untangleTree.

    def cleanup (self):

        c = self.c

        if self.errors + g.app.scanErrors == 0:
            #@        << call tangle_done.run() or untangle_done.run() >>
            #@+node:ekr.20031218072017.3469:<< call tangle_done.run() or untangle_done.run() >>
            # Create a list of root names:
            root_names = []
            theDir = self.tangle_directory # Bug fix: 12/04/02
            if not theDir: theDir = ""
            for section in self.root_list:
                for part in section.parts:
                    if part.is_root:
                        root_names.append(c.os_path_finalize_join(theDir,part.name))

            if self.tangling and self.tangle_batch_flag:
                try:
                    import tangle_done
                    tangle_done.run(root_names)
                except:
                    g.es("can not execute","tangle_done.run()")
                    g.es_exception()
            if not self.tangling and self.untangle_batch_flag:
                try:
                    import untangle_done
                    untangle_done.run(root_names)
                except:
                    g.es("can not execute","tangle_done.run()")
                    g.es_exception()
            #@-node:ekr.20031218072017.3469:<< call tangle_done.run() or untangle_done.run() >>
            #@nl

        # Reinitialize the symbol tables and lists.
        self.tst = {}
        self.ust = {}
        self.root_list = []
        self.def_stack = []
    #@-node:ekr.20031218072017.3468:cleanup
    #@+node:ekr.20031218072017.3470:initTangleCommand
    def initTangleCommand (self):

        c = self.c
        c.endEditing()

        if not g.unitTesting:
            g.es("tangling...")
        self.init_ivars()
        self.tangling = True
    #@-node:ekr.20031218072017.3470:initTangleCommand
    #@+node:ekr.20031218072017.3471:initUntangleCommand
    def initUntangleCommand (self):

        c = self.c
        c.endEditing()

        g.es("untangling...")
        self.init_ivars()
        self.tangling = False
    #@-node:ekr.20031218072017.3471:initUntangleCommand
    #@+node:ekr.20031218072017.3472:tangle
    def tangle(self,event=None,p=None):

        c = self.c
        if not p: p = c.p
        self.initTangleCommand()

        # Paul Paterson's patch.
        if not self.tangleTree(p,report_errors=True):
            g.es("looking for a parent to tangle...")
            while p:
                d = g.get_directives_dict(p,[self.head_root])
                if 'root' in d:
                    g.es("tangling parent")
                    self.tangleTree(p,report_errors=True)
                    break
                p.moveToParent()

        if not g.unitTesting:
            g.es("tangle complete")
    #@-node:ekr.20031218072017.3472:tangle
    #@+node:ekr.20031218072017.3473:tangleAll
    def tangleAll(self,event=None):

        c = self.c
        self.initTangleCommand()
        has_roots = False

        for p in c.rootPosition().self_and_siblings():
            ok = self.tangleTree(p,report_errors=False)
            if ok: has_roots = True
            if self.path_warning_given:
                break # Fatal error.

        self.errors += g.app.scanErrors

        if not has_roots:
            self.warning("----- the outline contains no roots")
        elif self.errors > 0 and not self.path_warning_given:
            self.warning("----- tangle halted because of errors")
        else:
            if not g.unitTesting:
                g.es("tangle complete")
    #@-node:ekr.20031218072017.3473:tangleAll
    #@+node:ekr.20031218072017.3474:tangleMarked
    def tangleMarked(self,event=None):

        c = self.c ; p = c.rootPosition()
        c.clearAllVisited() # No roots have been tangled yet.
        self.initTangleCommand()
        any_marked = False
        while p:
            is_ignore, i = g.is_special(p.b,0,"@ignore")
            # Only tangle marked and unvisited nodes.
            if is_ignore:
                p.moveToNodeAfterTree()
            elif p.isMarked():
                ok = self.tangleTree(p,report_errors=False)
                if ok: any_marked = True
                if self.path_warning_given:
                    break # Fatal error.
                p.moveToNodeAfterTree()
            else: p.moveToThreadNext()

        self.errors += g.app.scanErrors

        if not any_marked:
            self.warning("----- The outline contains no marked roots")
        elif self.errors > 0 and not self.path_warning_given:
            self.warning("----- Tangle halted because of errors")
        else:
            if not g.unitTesting:
                g.es("tangle complete")
    #@-node:ekr.20031218072017.3474:tangleMarked
    #@+node:ekr.20031218072017.3475:tanglePass1
    # Traverses the tree whose root is given, handling each headline and associated body text.

    def tanglePass1(self,p):

        """The main routine of tangle pass 1"""

        p = p.copy()
        next = p.nodeAfterTree()
        while p and p != next:
            self.p = p
            self.setRootFromHeadline(p)
            theDict = g.get_directives_dict(p,[self.head_root])
            is_ignore = 'ignore' in theDict
            if is_ignore:
                p.moveToNodeAfterTree()
                continue
            # This must be called after root_name has been set.
            if self.tangling:
                self.scanAllDirectives(p) # calls init_directive_ivars.
            # Scan the headline and body text.
            self.skip_headline(p)
            self.skip_body(p)
            p.moveToThreadNext()
            if self.errors + g.app.scanErrors >= max_errors:
                self.warning("----- Halting Tangle: too many errors")
                break

        if self.tangling:
            self.st_check()
            # g.trace(self.st_dump(verbose_flag=True))
    #@-node:ekr.20031218072017.3475:tanglePass1
    #@+node:ekr.20031218072017.3476:tanglePass2
    # At this point p is the root of the tree that has been tangled.

    def tanglePass2(self):

        self.p = None # self.p is not valid in pass 2.

        self.errors += g.app.scanErrors

        if self.errors > 0:
            self.warning("----- No file written because of errors")
        elif self.root_list == None:
            self.warning("----- The outline contains no roots")
        else:
            self.put_all_roots() # pass 2 top level function.
    #@-node:ekr.20031218072017.3476:tanglePass2
    #@+node:ekr.20031218072017.3477:tangleTree (calls cleanup)
    # This function is called only from the top level, so there is no need to initialize globals.

    def tangleTree(self,p,report_errors):

        """Tangles all nodes in the tree whose root is p.

        Reports on its results if report_errors is True."""

        p = p.copy() # 9/14/04
        assert(p)
        any_root_flag = False
        next = p.nodeAfterTree()
        self.path_warning_given = False

        while p and p != next:
            self.setRootFromHeadline(p)
            theDict = g.get_directives_dict(p,[self.head_root])
            is_ignore = 'ignore' in theDict
            is_root = 'root' in theDict
            is_unit = 'unit' in theDict
            if is_ignore:
                p.moveToNodeAfterTree()
            elif not is_root and not is_unit:
                p.moveToThreadNext()
            else:
                self.tanglePass1(p) # sets self.p
                if self.root_list and self.tangling:
                    any_root_flag = True
                    self.tanglePass2() # self.p invalid in pass 2.
                self.cleanup()
                p.moveToNodeAfterTree()
                if self.path_warning_given: break # Fatal error.

        if self.tangling and report_errors and not any_root_flag:
            # This is done by Untangle if we are untangling.
            self.warning("----- The outline contains no roots")
        return any_root_flag
    #@-node:ekr.20031218072017.3477:tangleTree (calls cleanup)
    #@+node:ekr.20031218072017.3478:untangle
    def untangle(self,event=None):

        c = self.c ; p = c.p
        self.initUntangleCommand()

        self.untangleTree(p,report_errors=True)
        if not g.unitTesting:
            g.es("untangle complete")
        c.redraw()
    #@-node:ekr.20031218072017.3478:untangle
    #@+node:ekr.20031218072017.3479:untangleAll
    def untangleAll(self,event=None):

        c = self.c
        self.initUntangleCommand()
        has_roots = False

        for p in c.rootPosition().self_and_siblings():
            ok = self.untangleTree(p,False)
            if ok: has_roots = True
        c.redraw()

        self.errors += g.app.scanErrors

        if not has_roots:
            self.warning("----- the outline contains no roots")
        elif self.errors > 0:
            self.warning("----- untangle command halted because of errors")
        else:
            if not g.unitTesting:
                g.es("untangle complete")
    #@-node:ekr.20031218072017.3479:untangleAll
    #@+node:ekr.20031218072017.3480:untangleMarked
    def untangleMarked(self,event=None):

        c = self.c ; p = c.rootPosition()
        self.initUntangleCommand()
        marked_flag = False

        while p: # Don't use an iterator.
            if p.isMarked():
                ok = self.untangleTree(p,report_errors=False)
                if ok: marked_flag = True
                if self.errors + g.app.scanErrors > 0: break
                p.moveToNodeAfterTree()
            else:
                p.moveToThreadNext()
        c.redraw()

        self.errors += g.app.scanErrors

        if not marked_flag:
            self.warning("----- the outline contains no marked roots")
        elif self.errors > 0:
            self.warning("----- untangle command halted because of errors")
        else:
            if not g.unitTesting:
                g.es("untangle complete")
    #@-node:ekr.20031218072017.3480:untangleMarked
    #@+node:ekr.20031218072017.3481:untangleRoot (calls cleanup)
    #@+at 
    #@nonl
    # This method untangles the derived files in a vnode known to 
    # contain at least one @root directive. The work is done in two 
    # passes. The first pass creates the UST by scanning the derived 
    # file. The second pass updates the outline using the UST and a 
    # TST that is created during the pass.
    # 
    # We assume that all sections from root to end are contained in 
    # the derived file, and we attempt to update all such sections. 
    # The begin/end params indicate the range of nodes to be scanned 
    # when building the TST.
    #@-at
    #@@c

    def untangleRoot(self,root,begin,end):

        # g.trace("root,begin,end:",root,begin,end)
        c = self.c
        #@    << Set path & root_name to the file specified in the @root directive >>
        #@+node:ekr.20031218072017.3483:<< Set path & root_name to the file specified in the @root directive >>
        s = root.b
        i = 0
        while i < len(s):
            code, junk = self.token_type(s,i,report_errors=True)
            if code == at_root:
                # token_type sets root_name unless there is a syntax error.
                if self.root_name: path = self.root_name
                break
            else: i = g.skip_line(s,i)

        if not self.root_name:
            # A bad @root command.  token_type has already given an error.
            self.cleanup()
            return
        #@-node:ekr.20031218072017.3483:<< Set path & root_name to the file specified in the @root directive >>
        #@nl
        #@    << return if @silent or unknown language >>
        #@+node:ekr.20031218072017.3482:<< return if @silent or unknown language >>
        if self.language == "unknown":
            g.es("@comment disables untangle for",path, color="blue")
            return

        if self.print_mode in ("quiet","silent"):
            g.es('','@%s' % (self.print_mode),"inhibits untangle for",path, color="blue")
            return
        #@-node:ekr.20031218072017.3482:<< return if @silent or unknown language >>
        #@nl
        path = c.os_path_finalize_join(self.tangle_directory,path)
        file_buf,e = g.readFileIntoString(path)
        if file_buf is None:
            self.cleanup()
            return
        else:
            file_buf = file_buf.replace('\r','')

        g.es('','@root ' + path)
        # Pass 1: Scan the C file, creating the UST
        self.scan_derived_file(file_buf)
        # g.trace(self.ust_dump())
        if self.errors + g.app.scanErrors == 0:
            #@        << Pass 2: Untangle the outline using the UST and a newly-created TST >>
            #@+node:ekr.20031218072017.3485:<< Pass 2:  Untangle the outline using the UST and a newly-created TST >>
            #@+at
            # This code untangles the root and all its siblings. We 
            # don't call tangleTree here
            # because we must handle all siblings. tanglePass1 
            # handles an entire tree. It also
            # handles @ignore.
            #@-at
            #@@c

            p = begin
            while p and p != end: # Don't use iterator.
                self.tanglePass1(p)
                if self.errors + g.app.scanErrors != 0:
                    break
                p.moveToNodeAfterTree()

            self.ust_warn_about_orphans()
            #@-node:ekr.20031218072017.3485:<< Pass 2:  Untangle the outline using the UST and a newly-created TST >>
            #@nl
        self.cleanup()
    #@-node:ekr.20031218072017.3481:untangleRoot (calls cleanup)
    #@+node:ekr.20031218072017.3486:untangleTree
    # This funtion is called when the user selects any "Untangle" command.

    def untangleTree(self,p,report_errors):

        p = p.copy() # 9/14/04
        c = self.c
        any_root_flag = False
        afterEntireTree = p.nodeAfterTree()
        # Initialize these globals here: they can't be cleared later.
        self.head_root = None
        self.errors = 0 ; g.app.scanErrors = 0
        c.clearAllVisited() # Used by untangle code.

        while p and p != afterEntireTree and self.errors + g.app.scanErrors == 0:
            self.setRootFromHeadline(p)
            theDict = g.get_directives_dict(p,[self.head_root])
            ignore = 'ignore' in theDict
            root = 'root' in theDict
            unit = 'unit' in theDict
            if ignore:
                p.moveToNodeAfterTree()
            elif unit:
                # Expand the context to the @unit directive.
                unitNode = p   # 9/27/99
                afterUnit = p.nodeAfterTree()
                p.moveToThreadNext()
                while p and p != afterUnit and self.errors + g.app.scanErrors== 0:
                    self.setRootFromHeadline(p)
                    theDict = g.get_directives_dict(p,[self.head_root])
                    root = 'root' in theDict
                    if root:
                        any_root_flag = True
                        end = None
                        #@                    << set end to the next root in the unit >>
                        #@+node:ekr.20031218072017.3487:<< set end to the next root in the unit >>
                        #@+at 
                        #@nonl
                        # The untangle_root function will untangle 
                        # an entire tree by calling untangleTree, so 
                        # the following code ensures that the next 
                        # @root node will not be an offspring of p.
                        #@-at
                        #@@c

                        end = p.threadNext()
                        while end and end != afterUnit:
                            flag, i = g.is_special(end.b,0,"@root")
                            if flag and not p.isAncestorOf(end):
                                break
                            end.moveToThreadNext()
                        #@-node:ekr.20031218072017.3487:<< set end to the next root in the unit >>
                        #@nl
                        # g.trace("end:",end)
                        self.scanAllDirectives(p)
                        self.untangleRoot(p,unitNode,afterUnit)
                        p = end.copy()
                    else: p.moveToThreadNext()
            elif root:
                # Limit the range of the @root to its own tree.
                afterRoot = p.nodeAfterTree()
                any_root_flag = True
                self.scanAllDirectives(p)
                self.untangleRoot(p,p,afterRoot)
                p = afterRoot.copy()
            else:
                p.moveToThreadNext()

        self.errors += g.app.scanErrors

        if report_errors:
            if not any_root_flag:
                self.warning("----- The outline contains no roots")
            elif self.errors > 0:
                self.warning("----- Untangle command halted because of errors")
        return any_root_flag
    #@-node:ekr.20031218072017.3486:untangleTree
    #@-node:ekr.20031218072017.3467:top level
    #@+node:ekr.20031218072017.3488:tangle
    #@+node:ekr.20031218072017.3489:Pass 1
    #@+node:ekr.20031218072017.3490:handle_newline
    #@+at 
    #@nonl
    # This method handles newline processing while skipping a code 
    # section. It sets 'done' if the line contains an @directive or 
    # section definition that terminates the present code section. 
    # On entry: i should point to the first character of a line.  
    # This routine scans past a line only if it could not contain a 
    # section reference.
    # 
    # Returns (i, done)
    #@-at
    #@@c

    def handle_newline(self,s,i):

        j = i ; done = False
        kind, end = self.token_type(s,i,report_errors=False)
        # token_type will not skip whitespace in noweb mode.
        i = g.skip_ws(s,i)
        # g.trace(kind,g.get_line(s,i))

        if kind == plain_line:
            pass
        elif (kind == at_code or kind == at_doc or
            kind == at_root or kind == section_def):
            i = j ; done = True # Terminate this code section and rescan.
        elif kind == section_ref:
            # Enter the reference.
            ref = s[i:end]
            self.st_enter_section_name(ref,None,None,allow_multiple_parts=False)
        # DTHEIN 13-OCT-2002: @first directives are OK in code sections
        elif (kind == at_other) and g.match_word(s,j,"@first"):
            pass
        elif kind == at_other or kind == at_chapter or kind == at_section:
            # We expect to see only @doc,@c or @root directives
            # while scanning a code section.
            i = g.skip_to_end_of_line(s,i)
            if 0: # 12/03/02: no longer needed
                self.error("directive not valid here: " + s[j:i])
        elif kind == bad_section_name:
            if self.use_cweb_flag:
                i = g.skip_to_end_of_line(s,i)
        elif kind == at_web or kind == at_at:
            i += 2 # Skip a CWEB control code.
        else: assert(False)

        return i, done
    #@-node:ekr.20031218072017.3490:handle_newline
    #@+node:ekr.20031218072017.3491:skip_body
    # This method handles all the body text.

    def skip_body (self,p):

        # g.trace(p)
        c = self.c
        s = p.b
        code_seen = False ; code = doc = None ; i = 0
        anyChanged = False

        if self.start_mode == "code":
            j = g.skip_blank_lines(s,i)
            i,code = self.skip_code(s,j)
            if code:
                #@            << Define a section for a leading code part >>
                #@+node:ekr.20031218072017.3494:<< Define a section for a leading code part >>
                if self.header_name:
                    # Tangle code.
                    part = self.st_enter_section_name(
                        self.header_name,code,doc,allow_multiple_parts=code_seen)
                    # Untangle code.
                    if not self.tangling: 
                        head = s[:j] ; tail = s[i:]
                        s,i,changed = self.update_def(self.header,part,head,code,tail)
                        if changed: anyChanged = True
                    code_seen = True
                    code = doc = None
                #@-node:ekr.20031218072017.3494:<< Define a section for a leading code part >>
                #@nl

        if not code:
            i,doc = self.skip_doc(s,i) # Start in doc section by default.
            if i >= len(s) and doc:
                #@            << Define a section containing only an @doc part >>
                #@+node:ekr.20031218072017.3493:<< Define a section containing only an @doc part >>
                #@+at 
                #@nonl
                # It's valid for an @doc directive to appear under a 
                # headline that does not contain a section name.  In 
                # that case, no section is defined.
                #@-at
                #@@c

                if self.header_name:
                    # Tangle code.
                    part = self.st_enter_section_name(self.header_name,code,doc,allow_multiple_parts=code_seen)
                    # Untangle code.
                    if not self.tangling: 
                        # Untangle no longer updates doc parts.
                        # 12/03/02: Mark the part as having been updated to suppress warning.
                        junk,junk = self.ust_lookup(self.header_name,part,update_flag=True)

                doc = None
                #@-node:ekr.20031218072017.3493:<< Define a section containing only an @doc part >>
                #@nl

        while i < len(s):
            progress = i # progress indicator
            # line = g.get_line(s,i) ; g.trace(line)
            kind, end = self.token_type(s,i,report_errors=True)
            # if g.is_nl(s,i): i = g.skip_nl(s,i)
            i = g.skip_ws(s,i)
            if kind == section_def:
                #@            << Scan and define a section definition >>
                #@+node:ekr.20031218072017.3495:<< Scan and define a section definition >>
                # We enter the code part and any preceding doc part into the symbol table.

                # Skip the section definition line.
                k = i ; i, kind, junk = self.skip_section_name(s,i)
                section_name = s[k:i]
                # g.trace(section_name)
                assert(kind == section_def)
                i = g.skip_to_end_of_line(s,i)

                # Tangle code: enter the section name even if the code part is empty.
                j = g.skip_blank_lines(s,i)
                i, code = self.skip_code(s,j)
                part = self.st_enter_section_name(section_name,code,doc,allow_multiple_parts=(kind==section_def))

                if not self.tangling: # Untangle code.
                    head = s[:j] ; tail = s[i:]
                    s,i,changed = self.update_def(section_name,part,head,code,tail)
                    if changed: anyChanged = True

                code = doc = None
                #@-node:ekr.20031218072017.3495:<< Scan and define a section definition >>
                #@nl
            elif kind == at_code:
                if self.use_cweb_flag:
                    i += 2 # Skip the at-c or at-p
                else:
                    i = g.skip_line(s,i)
                #@            << Scan and define an @code defininition >>
                #@+node:ekr.20031218072017.3496:<< Scan and define an @code defininition >>
                # All @c or @code directives denote < < headline_name > > =
                if self.header_name:

                    # Tangle code.
                    j = g.skip_blank_lines(s,i)
                    i, code = self.skip_code(s,j)
                    part = self.st_enter_section_name(self.header_name,code,doc,allow_multiple_parts=code_seen)
                    # Untangle code.
                    if not self.tangling: 
                        head = s[:j] ; tail = s[i:]
                        s,i,changed = self.update_def(self.header,part,head,code,tail)
                        if changed: anyChanged = True
                else:
                    self.error("@c expects the headline: " + self.header + " to contain a section name")

                code_seen = True
                code = doc = None
                #@-node:ekr.20031218072017.3496:<< Scan and define an @code defininition >>
                #@nl
            elif kind == at_root:
                i = g.skip_line(s,i)
                #@            << Scan and define a root section >>
                #@+node:ekr.20031218072017.3497:<< Scan and define a root section >>
                # We save the file name in case another @root ends the code section.
                old_root_name = self.root_name

                # Tangle code.
                j = g.skip_blank_lines(s,i)
                k, code = self.skip_code(s,j)

                # Stephen Schaefer, 9/2/02, later
                # st_enter_root_name relies on scanAllDirectives to have set
                # the root attributes, such as language, *_comment_string,
                # use_header_flag, etc.
                self.st_enter_root_name(old_root_name,code,doc)

                if not self.tangling: # Untangle code.
                    part = 1 # Use 1 for root part.
                    head = s[:j] ; tail = s[k:]
                    s,i,changed = self.update_def(old_root_name,part,head,code,tail,is_root_flag=True)
                    if changed: anyChanged = True

                code = doc = None
                #@-node:ekr.20031218072017.3497:<< Scan and define a root section >>
                #@nl
            elif kind == at_doc:
                if self.use_cweb_flag:
                    i += 2 # Skip the at-space
                else:
                    i = g.skip_line(s,i)
                i, doc = self.skip_doc(s,i)
            elif kind == at_chapter or kind == at_section:
                i = g.skip_line(s,i)
                i, doc = self.skip_doc(s,i)
            else:
                i = g.skip_line(s,i)
            assert(progress < i) # we must make progress!
        # Only call trimTrailingLines if we have changed its body.
        if anyChanged:
            c.trimTrailingLines(p)
    #@+node:ekr.20031218072017.3492:The interface between tangle and untangle
    #@+at 
    #@nonl
    # The following subsections contain the interface between the 
    # Tangle and Untangle commands.  This interface is an important 
    # hack, and allows Untangle to avoid duplicating the logic in 
    # skip_tree and its allies.
    # 
    # The aha is this: just at the time the Tangle command enters a 
    # definition into the symbol table, all the information is 
    # present that Untangle needs to update that definition.
    # 
    # To get whitespace exactly right we retain the outline's 
    # leading whitespace and remove leading whitespace from the 
    # updated definition.
    #@-at
    #@-node:ekr.20031218072017.3492:The interface between tangle and untangle
    #@-node:ekr.20031218072017.3491:skip_body
    #@+node:ekr.20031218072017.3498:skip_code
    #@+at 
    #@nonl
    # This method skips an entire code section. The caller is 
    # responsible for entering the completed section into the symbol 
    # table. On entry, i points at the line following the @directive 
    # or section definition that starts a code section. We skip code 
    # until we see the end of the body text or the next @ directive 
    # or section defintion that starts a code or doc part.
    #@-at
    #@@c

    def skip_code(self,s,i):

        # g.trace(g.get_line(s,i))
        code1 = i
        nl_i = i # For error messages
        done = False # True when end of code part seen.
        if self.use_noweb_flag:
            #@        << skip a noweb code section >>
            #@+node:ekr.20031218072017.3499:<< skip a noweb code section >>
            #@+at 
            #@nonl
            # This code handles the following escape conventions: 
            # double at-sign at the start of a line and at-<< and 
            # at.>.
            #@-at
            #@@c

            i, done = self.handle_newline(s,i)
            while not done and i < len(s):
                ch = s[i]
                if g.is_nl(s,i):
                    nl_i = i = g.skip_nl(s,i)
                    i, done = self.handle_newline(s,i)
                elif ch == '@' and (g.match(s,i+1,"<<") or # must be on different lines
                    g.match(s,i+1,">>")):
                    i += 3 # skip the noweb escape sequence.
                elif ch == '<':
                    #@        << handle possible noweb section reference >>
                    #@+node:ekr.20031218072017.3500:<< handle possible noweb section reference >>
                    j, kind, end = self.is_section_name(s,i)
                    if kind == section_def:
                        k = g.skip_to_end_of_line(s,i)
                        # We are in the middle of a line.
                        i += 1
                        self.error("chunk definition not valid here\n" + s[nl_i:k])
                    elif kind == bad_section_name:
                        i += 1 # This is not an error.  Just skip the '<'.
                    else:
                        assert(kind == section_ref)
                        # Enter the reference into the symbol table.
                        name = s[i:end]
                        self.st_enter_section_name(name,None,None,allow_multiple_parts=False)
                        i = end
                    #@-node:ekr.20031218072017.3500:<< handle possible noweb section reference >>
                    #@nl
                else: i += 1
            #@-node:ekr.20031218072017.3499:<< skip a noweb code section >>
            #@nl
        else:
            #@        << skip a CWEB code section >>
            #@+node:ekr.20031218072017.3501:<< skip a CWEB code section >>
            # This code is simple because CWEB control codes are valid anywhere.

            while not done and i < len(s):
                if s[i] == '@':
                    #@        << handle CWEB control code >>
                    #@+node:ekr.20031218072017.3502:<< handle CWEB control code >>
                    j, kind, end = self.is_section_name(s,i)

                    if kind == section_def:
                        done = True
                    elif kind == bad_section_name:
                        i += 2 # Any other control code.
                    else:
                        assert(kind == section_ref)
                        # Enter the reference into the symbol table.
                        name = s[i:j]
                        self.st_enter_section_name(name,None,None,allow_multiple_parts=False)
                        i = j
                    #@-node:ekr.20031218072017.3502:<< handle CWEB control code >>
                    #@nl
                else: i += 1
            #@-node:ekr.20031218072017.3501:<< skip a CWEB code section >>
            #@nl
        code = s[code1:i]
        # g.trace("returns:",code)
        return i,code
    #@-node:ekr.20031218072017.3498:skip_code
    #@+node:ekr.20031218072017.3503:skip_doc
    def skip_doc(self,s,i):

        # g.trace(g.get_line(s,i))
        # Skip @space, @*, @doc, @chapter and @section directives.
        doc1 = i
        while i < len(s):
            if g.is_nl(s,i):
                doc1 = i = g.skip_nl(s,i)
            elif g.match(s,i,"@ ") or g.match(s,i,"@\t") or g.match(s,i,"@*"):
                i = g.skip_ws(s,i+2) ; doc1 = i
            elif g.match(s,i,"@\n"):
                i += 1 ; doc1 = i
            elif (g.match_word(s,i,"@doc") or
                  g.match_word(s,i,"@chapter") or
                  g.match_word(s,i,"@section")):
                doc1 = i = g.skip_line(s,i)
            else: break

        while i < len(s):
            kind, end = self.token_type(s,i,report_errors=False)
            if kind == at_code or kind == at_root or kind == section_def:
                break
            i = g.skip_line(s,i)

        doc = s[doc1:i]
        # g.trace(doc)
        return i, doc
    #@-node:ekr.20031218072017.3503:skip_doc
    #@+node:ekr.20031218072017.3504:skip_headline
    #@+at 
    #@nonl
    # This function sets ivars that keep track of the indentation 
    # level. We also remember where the next line starts because it 
    # is assumed to be the first line of a documentation section.
    # 
    # A headline can contain a leading section name.  If it does, we 
    # substitute the section name if we see an @c directive in the 
    # body text.
    #@-at
    #@@c

    def skip_headline(self,p):

        self.header = s = p.h
        # Set self.header_name.
        j = i = g.skip_ws(s,0)
        i, kind, end = self.is_section_name(s,i)
        if kind == bad_section_name:
            self.header_name = None
        else:
            self.header_name = s[j:end]
    #@-node:ekr.20031218072017.3504:skip_headline
    #@-node:ekr.20031218072017.3489:Pass 1
    #@+node:ekr.20031218072017.3505:Pass 2
    #@+node:ekr.20031218072017.1488:oblank, oblanks, os, otab, otabs (Tangle)
    def oblank (self):
        self.oblanks(1)

    def oblanks (self,n):
        if abs(n) > 0:
            s = g.toEncodedString(' ' * abs(n),encoding=self.encoding)
            self.output_file.write(s)

    def onl(self):
        s = self.output_newline
        s = g.toEncodedString(s,self.encoding,reportErrors=True)
        self.output_file.write(s)

    def os (self,s):
        s = s.replace('\r','\n')
        s = g.toEncodedString(s,self.encoding,reportErrors=True)
        self.output_file.write(s)

    def otab (self):
        self.otabs(1)

    def otabs (self,n):
        if abs(n) > 0:
            s = g.toEncodedString('\t' * abs(n),self.encoding,reportErrors=True)
            self.output_file.write(s)
    #@-node:ekr.20031218072017.1488:oblank, oblanks, os, otab, otabs (Tangle)
    #@+node:ekr.20031218072017.1151:tangle.put_all_roots
    #@+at
    # This is the top level method of the second pass. It creates a 
    # separate C file
    # for each @root directive in the outline. The file is actually 
    # written only if
    # the new version of the file is different from the old 
    # version,or if the file did
    # not exist previously. If changed_only_flag FLAG is True only 
    # changed roots are
    # actually written.
    #@-at
    #@@c

    def put_all_roots(self):

        c = self.c ; outline_name = c.mFileName

        for section in self.root_list:

            # g.trace(section.name)
            file_name = c.os_path_finalize_join(self.tangle_directory,section.name)
            mode = c.config.output_newline
            textMode = mode == 'platform'
            if g.unitTesting:
                self.output_file = g.fileLikeObject()
                temp_name = 'temp-file'
            else:
                self.output_file,temp_name = g.create_temp_file(textMode=textMode)
            if not temp_name:
                g.es("can not create temp file")
                break
            #@        <<Get root specific attributes>>
            #@+node:ekr.20031218072017.1152:<<Get root specific attributes>>
            # Stephen Schaefer, 9/2/02
            # Retrieve the full complement of state for the root node
            self.language = section.root_attributes.language
            self.single_comment_string = section.root_attributes.single_comment_string
            self.start_comment_string = section.root_attributes.start_comment_string
            self.end_comment_string = section.root_attributes.end_comment_string
            self.use_header_flag = section.root_attributes.use_header_flag
            self.print_mode = section.root_attributes.print_mode
            self.path = section.root_attributes.path
            self.page_width = section.root_attributes.page_width
            self.tab_width = section.root_attributes.tab_width
            # Stephen P. Schaefer, 9/13/2002
            self.first_lines = section.root_attributes.first_lines

            # g.trace(self.single_comment_string)
            #@-node:ekr.20031218072017.1152:<<Get root specific attributes>>
            #@nl
            #@        <<Put @first lines>>
            #@+node:ekr.20031218072017.1153:<<Put @first lines>>
            # Stephen P. Schaefer 9/13/2002
            if self.first_lines:
                self.os(self.first_lines)
            #@-node:ekr.20031218072017.1153:<<Put @first lines>>
            #@nl
            if self.use_header_flag and self.print_mode == "verbose":
                #@            << Write a banner at the start of the output file >>
                #@+node:ekr.20031218072017.1154:<<Write a banner at the start of the output file>>
                if self.single_comment_string:
                    self.os(self.single_comment_string)
                    self.os(" Created by Leo from: ")
                    self.os(outline_name)
                    self.onl() ; self.onl()
                elif self.start_comment_string and self.end_comment_string:
                    self.os(self.start_comment_string)
                    self.os(" Created by Leo from: ")
                    self.os(outline_name)
                    self.oblank() ; self.os(self.end_comment_string)
                    self.onl() ; self.onl()
                #@-node:ekr.20031218072017.1154:<<Write a banner at the start of the output file>>
                #@nl
            for part in section.parts:
                if part.is_root:
                    self.tangle_indent = 0 # Initialize global.
                    self.put_part_node(part,False) # output first lws
            self.onl() # Make sure the file ends with a cr/lf
            if g.unitTesting:
                self.tangle_output = self.output_file.get()
            self.output_file.close()
            self.output_file = None
            if g.unitTesting:
                assert self.errors == 0
                g.app.unitTestDict ['tangle'] = True
                g.app.unitTestDict ['tangle_directory'] = self.tangle_directory
                g.app.unitTestDict ['tangle_output_fn'] = file_name
            else:
                if self.errors + g.app.scanErrors == 0:
                    g.update_file_if_changed(c,file_name,temp_name)
                else:
                    g.es("unchanged:",file_name)
                    #@                << Erase the temporary file >>
                    #@+node:ekr.20031218072017.1155:<< Erase the temporary file >>
                    try: # Just delete the temp file.
                        os.remove(temp_name)
                    except: pass
                    #@-node:ekr.20031218072017.1155:<< Erase the temporary file >>
                    #@nl
    #@nonl
    #@-node:ekr.20031218072017.1151:tangle.put_all_roots
    #@+node:ekr.20031218072017.3506:put_code
    #@+at 
    #@nonl
    # This method outputs a code section, expanding section 
    # references by their definition. We should see no @directives 
    # or section definitions that would end the code section.
    # 
    # Most of the differences bewteen noweb mode and CWEB mode are 
    # handled by token_type(called from put_newline). Here, the only 
    # difference is that noweb handles double-@ signs only at the 
    # start of a line.
    #@-at
    #@@c

    def put_code(self,s,no_first_lws_flag):

        # g.trace(g.get_line(s,0))
        i = 0
        if i < len(s):
            i = self.put_newline(s,i,no_first_lws_flag)
            # Double @ is valid in both noweb and CWEB modes here.
            if g.match(s,i,"@@"):
                self.os('@') ; i += 2
        while i < len(s):
            progress = i
            ch = s[i]
            if (g.match(s,i,"<<") and self.use_noweb_flag or
                g.match(s,i,"@<") and self.use_cweb_flag):
                #@            << put possible section reference >>
                #@+node:ekr.20031218072017.3507:<<put possible section reference >>
                j, kind, name_end = self.is_section_name(s,i)
                if kind == section_def:
                    # We are in the middle of a code section
                    self.error(
                        "Should never happen:\n" +
                        "section definition while putting a section reference: " +
                        s[i:j])
                    i += 1
                elif kind == bad_section_name:
                    self.os(s[i]) ; i += 1 # This is not an error.
                else:
                    assert(kind == section_ref)
                    name = s[i:name_end]
                    self.put_section(s,i,name,name_end)
                    i = j
                #@-node:ekr.20031218072017.3507:<<put possible section reference >>
                #@nl
            elif ch == '@': # We are in the middle of a line.
                if self.use_cweb_flag:
                    #@                << handle 2-character CWEB control codes >>
                    #@+node:ekr.20031218072017.3508:<< handle 2-character CWEB control codes >>
                    if g.match(s,i,"@@"):
                        # Handle double @ sign.
                        self.os('@') ; i += 2
                    else:
                        i += 1 # skip the @.
                        if i+1 >= len(s) or g.is_ws_or_nl(s,i):
                            # A control code: at-backslash is not a valid CWEB control code.
                            # We are in CWEB mode, so we can output C block comments.
                            self.os("/*@" + s[i] + "*/") ; i += 1
                        else:
                            self.os("@") # The at sign is not part of a control code.
                    #@-node:ekr.20031218072017.3508:<< handle 2-character CWEB control codes >>
                    #@nl
                else:
                    #@                << handle noweb @ < < convention >>
                    #@+node:ekr.20031218072017.3509:<< handle noweb @ < < convention >>
                    #@+at 
                    #@nonl
                    # The user must ensure that neither @ < < nor @ 
                    # > > occurs in comments or strings. However, it 
                    # is valid for @ < < or @ > > to appear in the 
                    # doc chunk or in a single-line comment.
                    #@-at
                    #@@c

                    if g.match(s,i,"@<<"):
                        self.os("/*@*/<<") ; i += 3

                    elif g.match(s,i,"@>>"):
                        self.os("/*@*/>>") ; i += 3

                    else: self.os("@") ; i += 1
                    #@-node:ekr.20031218072017.3509:<< handle noweb @ < < convention >>
                    #@nl
            elif ch == '\r':
                i += 1
            elif ch == '\n':
                i += 1 ; self.onl()
                i = self.put_newline(s,i,False) # Put full lws
                if self.use_cweb_flag and g.match(s,i,"@@"):
                    self.os('@') ; i += 2
            else: self.os(s[i]) ; i += 1
            assert(progress < i)
    #@-node:ekr.20031218072017.3506:put_code
    #@+node:ekr.20031218072017.3510:put_doc
    # This method outputs a doc section within a block comment.

    def put_doc(self,s):

        # g.trace(g.get_line(s,0))
        width = self.page_width
        words = 0 ; word_width = 0 ; line_width = 0
        # 8/1/02: can't use choose here!
        if self.single_comment_string == None: single_w = 0
        else: single_w = len(self.single_comment_string)
        # Make sure we put at least 20 characters on a line.
        if width - max(0,self.tangle_indent) < 20:
            width = max(0,self.tangle_indent) + 20
        # Skip Initial white space in the doc part.
        i = g.skip_ws_and_nl(s,0)
        if i < len(s) and (self.print_mode == "verbose" or self.print_mode == "quiet"):
            use_block_comment = self.start_comment_string and self.end_comment_string
            use_single_comment = not use_block_comment and self.single_comment_string
            # javadoc_comment = use_block_comment and self.start_comment_string == "/**"
            if use_block_comment or use_single_comment:
                if 0: # The section name ends in an self.onl().
                    self.onl()
                self.put_leading_ws(self.tangle_indent)
                if use_block_comment:
                    self.os(self.start_comment_string)
                #@            << put the doc part >>
                #@+node:ekr.20031218072017.3511:<<put the doc part>>
                #@+at 
                #@nonl
                # This code fills and outputs each line of a doc 
                # part. It keeps track of whether the next word will 
                # fit on a line,and starts a new line if needed.
                #@-at
                #@@c

                if use_single_comment:
                    # New code: 5/31/00
                    self.os(self.single_comment_string) ; self.otab()
                    line_width =(single_w / abs(self.tab_width) + 1) * abs(self.tab_width)
                else:
                    line_width = abs(self.tab_width)
                    self.onl() ; self.otab()
                self.put_leading_ws(self.tangle_indent)
                line_width += max(0,self.tangle_indent)
                words = 0 ; word_width = 0
                while i < len(s):
                    #@    <<output or skip whitespace or newlines>>
                    #@+node:ekr.20031218072017.3512:<<output or skip whitespace or newlines>>
                    #@+at 
                    #@nonl
                    # This outputs whitespace if it fits, and 
                    # ignores it otherwise, and starts a new line if 
                    # a newline is seen. The effect of self code is 
                    # that we never start a line with whitespace 
                    # that was originally at the end of a line.
                    #@-at
                    #@@c

                    while g.is_ws_or_nl(s,i):
                        ch = s[i]
                        if ch == '\t':
                            pad = abs(self.tab_width) - (line_width % abs(self.tab_width))
                            line_width += pad
                            if line_width < width: self.otab()
                            i += 1
                        elif ch == ' ':
                            line_width += 1
                            if line_width < width: self.os(ch)
                            i += 1
                        else:
                            assert(g.is_nl(s,i))
                            self.onl()
                            if use_single_comment:
                                # New code: 5/31/00
                                self.os(self.single_comment_string) ; self.otab()
                                line_width = (single_w / abs(self.tab_width) + 1) * abs(self.tab_width)
                            else:
                                self.otab()
                                line_width = abs(self.tab_width)
                            i = g.skip_nl(s,i)
                            words = 0
                            self.put_leading_ws(self.tangle_indent)
                            # tangle_indent is in spaces.
                            line_width += max(0,self.tangle_indent)
                    #@-node:ekr.20031218072017.3512:<<output or skip whitespace or newlines>>
                    #@nl
                    if i >= len(s):
                        break
                    #@    <<compute the width of the next word>>
                    #@+node:ekr.20031218072017.3513:<<compute the width of the next word>>
                    j = i ; word_width = 0
                    while j < len(s) and not g.is_ws_or_nl(s,j):
                        word_width += 1
                        j += 1
                    #@-node:ekr.20031218072017.3513:<<compute the width of the next word>>
                    #@nl
                    if words == 0 or line_width + word_width < width:
                        words += 1
                        #@        <<output next word>>
                        #@+node:ekr.20031218072017.3514:<<output next word>>
                        while i < len(s) and not g.is_ws_or_nl(s,i):
                            self.os(s[i])
                            i += 1
                        #@-node:ekr.20031218072017.3514:<<output next word>>
                        #@nl
                        line_width += word_width
                    else:
                        # 11-SEP-2002 DTHEIN: Fixed linewrapping bug in
                        # tab-then-comment sequencing
                        self.onl()
                        if use_single_comment:
                            self.os(self.single_comment_string) ; self.otab()
                            line_width = (single_w / abs(self.tab_width) + 1) * abs(self.tab_width)
                        else:
                            self.otab()
                            line_width = abs(self.tab_width)
                        words = 0
                        self.put_leading_ws(self.tangle_indent)
                        # tangle_indent is in spaces.
                        line_width += max(0,self.tangle_indent)
                #@-node:ekr.20031218072017.3511:<<put the doc part>>
                #@nl
                self.onl()
                self.put_leading_ws(self.tangle_indent)
                if use_block_comment:
                    self.os(self.end_comment_string)
                self.onl()
            else: self.onl()
    #@-node:ekr.20031218072017.3510:put_doc
    #@+node:ekr.20031218072017.3515:put_leading_ws
    # Puts tabs and spaces corresponding to n spaces, assuming that we are at the start of a line.

    def put_leading_ws(self,n):

        # g.trace("tab_width,indent:",self.tab_width,indent)
        w = self.tab_width

        if w > 1:
            q,r = divmod(n,w)
            self.otabs(q) 
            self.oblanks(r) 
        else:
            self.oblanks(n)
    #@-node:ekr.20031218072017.3515:put_leading_ws
    #@+node:ekr.20031218072017.3516:put_newline
    #@+at 
    #@nonl
    # This method handles scanning when putting the start of a new 
    # line. Unlike the corresponding method in pass one, this method 
    # doesn't need to set a done flag in the caller because the 
    # caller already knows where the code section ends.
    #@-at
    #@@c

    def put_newline(self,s,i,no_first_lws_flag):

        kind, end = self.token_type(s,i,report_errors=False)
        #@    << Output leading white space except for blank lines >>
        #@+node:ekr.20031218072017.3517:<< Output leading white space except for blank lines >>
        j = i ; i = g.skip_ws(s,i)
        if i < len(s) and not g.is_nl(s,i):
            # Conditionally output the leading previous leading whitespace.
            if not no_first_lws_flag:
                self.put_leading_ws(self.tangle_indent)
            # Always output the leading whitespace of _this_ line.
            k, width = g.skip_leading_ws_with_indent(s,j,self.tab_width)
            self.put_leading_ws(width)
        #@-node:ekr.20031218072017.3517:<< Output leading white space except for blank lines >>
        #@nl
        if i >= len(s):
            return i
        elif kind == at_web or kind == at_at:
            i += 2 # Allow the line to be scanned.
        elif kind == at_doc or kind == at_code:
            if self.use_cweb_flag:
                i += 2
        else:
            # These should have set limit in pass 1.
            assert(kind != section_def and kind != at_chapter and kind != at_section)
        return i
    #@-node:ekr.20031218072017.3516:put_newline
    #@+node:ekr.20031218072017.3518:put_part_node
    # This method outputs one part of a section definition.

    def put_part_node(self,part,no_first_lws_flag):

        if 0:
            if part: name = part.name # can't use choose.
            else: name = "<NULL part>"
            g.trace(name)

        if part.doc and self.output_doc_flag and self.print_mode != "silent":
            self.put_doc(part.doc)

        if part.code:
            self.put_code(part.code,no_first_lws_flag)
    #@-node:ekr.20031218072017.3518:put_part_node
    #@+node:ekr.20031218072017.3519:put_section
    #@+at 
    #@nonl
    # This method outputs the definition of a section and all 
    # sections referenced from the section. name is the section's 
    # name. This code checks for recursive definitions by calling 
    # section_check(). We can not allow section x to expand to code 
    # containing another call to section x, either directly or 
    # indirectly.
    #@-at
    #@@c

    def put_section(self,s,i,name,name_end):

        j = g.skip_line(s,i)
        # g.trace("indent:",self.tangle_indent,s[i:j])
        outer_old_indent = self.tangle_indent
        trailing_ws_indent = 0 # Set below.
        inner_old_indent = 0 # Set below.
        newline_flag = False  # True if the line ends with the reference.
        assert(g.match(name,0,"<<") or g.match(name,0,"@<"))
        #@    << Calculate the new value of tangle_indent >>
        #@+node:ekr.20031218072017.3520:<< Calculate the new value of tangle_indent >>
        # Find the start of the line containing the reference.
        j = i
        while j > 0 and not g.is_nl(s,j):
            j -= 1
        if g.is_nl(s,j):
            j = g.skip_nl(s,j)

        # Bump the indentation
        j, width = g.skip_leading_ws_with_indent(s,j,self.tab_width)
        self.tangle_indent += width
        # g.trace("leading ws,new indent:",width,self.tangle_indent)

        # 4/27/01: Force no trailing whitespace in @silent mode.
        if self.print_mode == "silent":
            trailing_ws_indent = 0
        else:
            trailing_ws_indent = self.tangle_indent

        # Increase the indentation if the section reference does not immediately follow
        # the leading white space.  4/3/01: Make no adjustment in @silent mode.
        if (j < len(s) and self.print_mode != "silent" and
                ((self.use_noweb_flag and s[j] != '<') or
                (self.use_cweb_flag and s[j] != '@'))):
            self.tangle_indent += abs(self.tab_width)
        #@-node:ekr.20031218072017.3520:<< Calculate the new value of tangle_indent >>
        #@nl
        #@    << Set 'newline_flag' if the line ends with the reference >>
        #@+node:ekr.20031218072017.3521:<< Set 'newline_flag' if the line ends with the reference >>
        if self.print_mode != "silent":
            i = name_end
            i = g.skip_ws(s,i)
            newline_flag = (i >= len(s) or g.is_nl(s,i))
        #@-node:ekr.20031218072017.3521:<< Set 'newline_flag' if the line ends with the reference >>
        #@nl
        section = self.st_lookup(name)
        if section and section.parts:
            # Expand the section only if we are not already expanding it.
            if self.section_check(name):
                self.section_stack.append(name)
                #@            << put all parts of the section definition >>
                #@+node:ekr.20031218072017.3522:<<put all parts of the section definition>>
                #@+at 
                #@nonl
                # This section outputs each part of a section 
                # definition. We first count how many parts there 
                # are so that the code can output a comment saying 
                # 'part x of y'.
                #@-at
                #@@c

                # Output each part of the section.
                sections = len(section.parts)
                count = 0
                for part in section.parts:
                    count += 1
                    # In @silent mode, there is no sentinel line to "use up" the previously output
                    # leading whitespace.  We set the flag to tell put_part_node and put_code
                    # not to call put_newline at the start of the first code part of the definition.
                    no_first_leading_ws_flag = (count == 1 and self.print_mode == "silent")
                    inner_old_indent = self.tangle_indent
                    # 4/3/01: @silent inhibits newlines after section expansion.
                    if self.print_mode != "silent":
                        #@        << Put the section name in a comment >>
                        #@+node:ekr.20031218072017.3523:<< Put the section name in a comment >>
                        if count > 1:
                            self.onl()
                            self.put_leading_ws(self.tangle_indent)

                        # Don't print trailing whitespace
                        name = name.rstrip()
                        if self.single_comment_string:
                            self.os(self.single_comment_string) ; self.oblank() ; self.os(name)
                            #@    << put (n of m) >>
                            #@+node:ekr.20031218072017.3524:<< put ( n of m ) >>
                            if sections > 1:
                                self.oblank()
                                self.os("(%d of %d)" % (count,sections))
                            #@-node:ekr.20031218072017.3524:<< put ( n of m ) >>
                            #@nl
                        else:
                            assert(
                                self.start_comment_string and len(self.start_comment_string) > 0 and
                                self.end_comment_string and len(self.end_comment_string)> 0)
                            self.os(self.start_comment_string) ; self.oblank() ; self.os(name)
                            #@    << put (n of m) >>
                            #@+node:ekr.20031218072017.3524:<< put ( n of m ) >>
                            if sections > 1:
                                self.oblank()
                                self.os("(%d of %d)" % (count,sections))
                            #@-node:ekr.20031218072017.3524:<< put ( n of m ) >>
                            #@nl
                            self.oblank() ; self.os(self.end_comment_string)

                        self.onl() # Always output a newline.
                        #@-node:ekr.20031218072017.3523:<< Put the section name in a comment >>
                        #@nl
                    self.put_part_node(part,no_first_leading_ws_flag)
                    # 4/3/01: @silent inhibits newlines after section expansion.
                    if count == sections and (self.print_mode != "silent" and self.print_mode != "quiet"):
                        #@        << Put the ending comment >>
                        #@+node:ekr.20031218072017.3525:<< Put the ending comment >>
                        #@+at 
                        #@nonl
                        # We do not produce an ending comment unless 
                        # we are ending the last part of the 
                        # section,and the comment is clearer if we 
                        # don't say(n of m).
                        #@-at
                        #@@c

                        self.onl() ; self.put_leading_ws(self.tangle_indent)
                        #  Don't print trailing whitespace
                        while name_end > 0 and g.is_ws(s[name_end-1]):
                            name_end -= 1

                        if self.single_comment_string:
                            self.os(self.single_comment_string) ; self.oblank()
                            self.os("-- end -- ") ; self.os(name)
                        else:
                            self.os(self.start_comment_string) ; self.oblank()
                            self.os("-- end -- ") ; self.os(name)
                            self.oblank() ; self.os(self.end_comment_string)

                        #@+at 
                        #@nonl
                        # The following code sets a flag for 
                        # untangle.
                        # 
                        # If something follows the section reference 
                        # we must add a newline, otherwise the 
                        # "something" would become part of the 
                        # comment.  Any whitespace following the 
                        # (!newline) should follow the section 
                        # defintion when Untangled.
                        #@-at
                        #@@c

                        if not newline_flag:
                            self.os(" (!newline)") # LeoCB puts the leading blank, so we must do so too.
                            # Put the whitespace following the reference.
                            while name_end < len(s) and g.is_ws(s[name_end]):
                                self.os(s[name_end])
                                name_end += 1
                            self.onl() # We must supply the newline!
                        #@-node:ekr.20031218072017.3525:<< Put the ending comment >>
                        #@nl
                    # Restore the old indent.
                    self.tangle_indent = inner_old_indent
                #@-node:ekr.20031218072017.3522:<<put all parts of the section definition>>
                #@nl
                self.section_stack.pop()
        else:
            #@        << Put a comment about the undefined section >>
            #@+node:ekr.20031218072017.3526:<<Put a comment about the undefined section>>
            self.onl() ; self.put_leading_ws(self.tangle_indent)

            if self.print_mode != "silent":
                if self.single_comment_string:
                    self.os(self.single_comment_string)
                    self.os(" undefined section: ") ; self.os(name) ; self.onl()
                else:
                    self.os(self.start_comment_string)
                    self.os(" undefined section: ") ; self.os(name)
                    self.oblank() ; self.os(self.end_comment_string) ; self.onl()

            self.error("Undefined section: " + name)
            #@-node:ekr.20031218072017.3526:<<Put a comment about the undefined section>>
            #@nl
        if not newline_flag:
            self.put_leading_ws(trailing_ws_indent)
        self.tangle_indent = outer_old_indent
        return i, name_end
    #@-node:ekr.20031218072017.3519:put_section
    #@+node:ekr.20031218072017.3527:section_check
    #@+at 
    #@nonl
    # We can not allow a section to be defined in terms of itself, 
    # either directly or indirectly.
    # 
    # We push an entry on the section stack whenever beginning to 
    # expand a section and pop the section stack at the end of each 
    # section.  This method checks whether the given name appears in 
    # the stack. If so, the section is defined in terms of itself.
    #@-at
    #@@c

    def section_check (self,name):

        if name in self.section_stack:
            s = "Invalid recursive reference of " + name + "\n"
            for n in self.section_stack:
                s += "called from: " + n + "\n"
            self.error(s)
            return False
        return True
    #@-node:ekr.20031218072017.3527:section_check
    #@-node:ekr.20031218072017.3505:Pass 2
    #@-node:ekr.20031218072017.3488:tangle
    #@+node:ekr.20031218072017.3528:tst
    #@+node:ekr.20031218072017.3529:st_check
    def st_check(self):

        """Checks the given symbol table for defined but never referenced sections."""

        # g.trace(keys)
        for name in sorted(self.tst):
            section = self.tst[name]
            if not section.referenced:
                lp = g.choose(self.use_noweb_flag,"<< ","@< ")
                rp = g.choose(self.use_noweb_flag," >>"," @>")
                g.es('',' ' * 4,'warning:',lp,'',section.name,'',rp,'has been defined but not used.')
    #@-node:ekr.20031218072017.3529:st_check
    #@+node:ekr.20031218072017.3530:st_dump
    # Dumps the given symbol table in a readable format.

    def st_dump(self,verbose_flag=True):

        s = "\ndump of symbol table...\n"

        for name in sorted(self.tst):
            section = self.tst[name]
            if verbose_flag:
                s += self.st_dump_node(section)
            else:
                theType = g.choose(len(section.parts)>0,"  ","un")
                s += ("\n" + theType + "defined:[" + section.name + "]")
        return s
    #@-node:ekr.20031218072017.3530:st_dump
    #@+node:ekr.20031218072017.3531:st_dump_node
    # Dumps each part of a section's definition.

    def st_dump_node(self,section):

        s = ("\nsection: " + section.name +
            ", referenced:" + str(section.referenced) +
            ", is root:" + str(section.is_root))

        if len(section.parts) > 0:
            s += "\n----- parts of " + g.angleBrackets(section.name)
            n = 1 # part list is in numeric order
            for part in section.parts:
                s += "\n----- Part " + str(n)
                n += 1
                s += "\ndoc:  [" + repr(part.doc)  + "]"
                s += "\ncode: [" + repr(part.code) + "]"
            s += "\n----- end of partList\n"
        return s
    #@-node:ekr.20031218072017.3531:st_dump_node
    #@+node:ekr.20031218072017.3532:st_enter
    def st_enter(self,name,code,doc,allow_multiple_parts,is_root_flag=False):
        # The allow_multiple_parts flag is used only if this is a code part.

        """Enters names and their associated code and doc parts into the given symbol table."""

        section = self.st_lookup(name,is_root_flag)
        assert(section)
        if doc:
            doc = doc.rstrip() # remove trailing lines.
        if code:
            if self.print_mode != "silent": # @silent supresses newline processing.
                i = g.skip_blank_lines(code,0) # remove leading lines.
                if i > 0: code = code[i:] 
                if code and len(code) > 0: code = code.rstrip() # remove trailing lines.
            if len(code) == 0: code = None
        if code:
            #@        << check for duplicate code definitions >>
            #@+node:ekr.20031218072017.3533:<<check for duplicate code definitions >>
            for part in section.parts:

                if part.code and not allow_multiple_parts:
                    # Give the message only for non-empty parts.
                    self.error("Multiple parts not allowed for " + name)
                    return 0 # part number

                if self.tangling and code and code == part.code:
                    s = g.angleBrackets(section.name)
                    g.es('warning: possible duplicate definition of:',s)
            #@nonl
            #@-node:ekr.20031218072017.3533:<<check for duplicate code definitions >>
            #@nl
        if code or doc:
            part = part_node(name,code,doc,is_root_flag,is_dirty=False)
            section.parts.append(part)
        else: # A reference
            section.referenced = True
        if is_root_flag:
            self.root_list.append(section)
            section.referenced = True # Mark the root as referenced.
            #@        <<remember root node attributes>>
            #@+node:ekr.20031218072017.3534:<<remember root node attributes>>
            # Stephen Schaefer, 9/2/02
            # remember the language and comment characteristics
            section.root_attributes = root_attributes(self)
            #@-node:ekr.20031218072017.3534:<<remember root node attributes>>
            #@nl
        # Stephen Schaefer, 9/2/02
        return len(section.parts) # part number
    #@-node:ekr.20031218072017.3532:st_enter
    #@+node:ekr.20031218072017.3535:st_enter_root_name
    # Enters a root name into the given symbol table.

    def st_enter_root_name(self,name,code,doc):

        # assert(code)
        if name: # User errors can result in an empty @root name.
            self.st_enter(name,code,doc,allow_multiple_parts=False,is_root_flag=True)
    #@-node:ekr.20031218072017.3535:st_enter_root_name
    #@+node:ekr.20031218072017.3536:st_enter_section_name
    def st_enter_section_name(self,name,code,doc,allow_multiple_parts):

        """Enters a section name into the given symbol table.

        The code and doc pointers are None for references."""

        return self.st_enter(name,code,doc,allow_multiple_parts)
    #@-node:ekr.20031218072017.3536:st_enter_section_name
    #@+node:ekr.20031218072017.3537:st_lookup
    def st_lookup(self,name,is_root_flag=False):

        """Looks up name in the symbol table and creates a tst_node for it if it does not exist."""

        if is_root_flag:
            key = name
        else:
            key = self.standardize_name(name)

        if key in self.tst:
            section = self.tst[key]
            # g.trace("found:" + key)
            return section
        else:
            # g.trace("not found:" + key)
            section = tst_node(key,is_root_flag)
            self.tst [key] = section
            return section
    #@-node:ekr.20031218072017.3537:st_lookup
    #@-node:ekr.20031218072017.3528:tst
    #@+node:ekr.20031218072017.3538:ust
    #@+node:ekr.20031218072017.3539:ust_dump
    def ust_dump (self):

        s = "\n---------- Untangle Symbol Table ----------"

        for name in sorted(self.ust):
            section = self.ust[name]
            s += "\n\n" + section.name
            for part in section.parts.values():
                assert(part.of == section.of)
                s += "\n----- part %d of %d -----\n" % (part.part,part.of)
                s += repr(g.get_line(part.code,0))

        s += "\n--------------------"

        return s
    #@-node:ekr.20031218072017.3539:ust_dump
    #@+node:ekr.20031218072017.3540:ust_enter
    #@+at 
    #@nonl
    # This routine enters names and their code parts into the given 
    # table. The 'part' and 'of' parameters are taken from the 
    # "(part n of m)" portion of the line that introduces the 
    # section definition in the C code.
    # 
    # If no part numbers are given the caller should set the 'part' 
    # and 'of' parameters to zero.  The caller is reponsible for 
    # checking for duplicate parts.
    # 
    # This function handles names scanned from a source file; the 
    # corresponding st_enter routine handles names scanned from 
    # outlines.
    #@-at
    #@@c

    def ust_enter (self,name,part,of,code,nl_flag,is_root_flag=False):

        if not is_root_flag:
            name = self.standardize_name(name)
        #@    << remove blank lines from the start and end of the text >>
        #@+node:ekr.20031218072017.3541:<< remove blank lines from the start and end of the text >>
        i = g.skip_blank_lines(code,0)
        if i > 0:
            code = code[i:].rstrip()

        #@-node:ekr.20031218072017.3541:<< remove blank lines from the start and end of the text >>
        #@nl
        u = ust_node(name,code,part,of,nl_flag,False) # update_flag
        if name not in self.ust:
            self.ust[name] = u
        section = self.ust[name]
        section.parts[part]=u # Parts may be defined in any order.
        # g.trace("section [%s](part %d of %d)...%s" % (name,part,of,g.get_line(code,0)))
    #@-node:ekr.20031218072017.3540:ust_enter
    #@+node:ekr.20031218072017.3542:ust_lookup
    # Searches the given table for a part matching the name and part number.

    def ust_lookup (self,name,part_number,is_root_flag=False,update_flag=False):

        # g.trace(name,part_number)

        if not is_root_flag:
            name = self.standardize_name(name)

        if part_number == 0: part_number = 1 # A hack: zero indicates the first part.
        if name in self.ust:
            section = self.ust[name]
            if part_number in section.parts:
                part = section.parts[part_number]
                if update_flag: part.update_flag = True
                # g.trace("found: %d (%d)...\n" % (name,part_number,g.get_line(part.code,0)))
                return part, True

        # g.trace("not found: %s(%d)...\n" % (name,part_number))
        return None, False
    #@-node:ekr.20031218072017.3542:ust_lookup
    #@+node:ekr.20031218072017.3543:ust_warn_about_orphans
    def ust_warn_about_orphans (self):

        """Issues a warning about any sections in the derived file for which
        no corresponding section has been seen in the outline."""

        for section in self.ust.values():
            # g.trace(section)
            for part in section.parts.values():
                assert(part.of == section.of)
                if not part.update_flag:
                    lp = g.choose(self.use_noweb_flag,"<< ","@< ")
                    rp = g.choose(self.use_noweb_flag," >>"," @>")
                    g.es("warning:",'%s%s%s' % (lp,part.name,rp),"is not in the outline")
                    break # One warning per section is enough.
    #@-node:ekr.20031218072017.3543:ust_warn_about_orphans
    #@-node:ekr.20031218072017.3538:ust
    #@+node:ekr.20031218072017.3544:untangle
    #@+node:ekr.20031218072017.3545:compare_comments
    #@+at 
    #@nonl
    # This function compares the interior of comments and returns 
    # True if they are identical except for whitespace or newlines. 
    # It is up to the caller to eliminate the opening and closing 
    # delimiters from the text to be compared.
    #@-at
    #@@c

    def compare_comments (self,s1,s2):

        tot_len = 0
        if self.comment: tot_len += len(self.comment)
        if self.comment_end: tot_len += len(self.comment_end)
        CWEB_flag = (self.language == "c" and not self.use_noweb_flag)

        p1, p2 = 0, 0
        while p1 < len(s1) and p2 < len(s2):
            p1 = g.skip_ws_and_nl(s1,p1)
            p2 = g.skip_ws_and_nl(s2,p2)
            if self.comment and self.comment_end:
                #@            << Check both parts for @ comment conventions >>
                #@+node:ekr.20031218072017.3546:<< Check both parts for @ comment conventions >>
                #@+at 
                #@nonl
                # This code is used in forgiving_compare()and in 
                # compare_comments().
                # 
                # In noweb mode we allow / * @ * /  (without the 
                # spaces)to be equal to @.
                # In CWEB mode we allow / * @ ? * / (without the 
                # spaces)to be equal to @?.
                # at-backslash is not a valid CWEB control code, so 
                # we don't have to equate
                # / * @ \\ * / with at-backslash.
                # 
                # We must be careful not to run afoul of this very 
                # convention here!
                #@-at
                #@@c

                if p1 < len(s1) and s1[p1] == '@':
                    if g.match(s2,p2,self.comment + '@' + self.comment_end):
                        p1 += 1
                        p2 += tot_len + 1
                        continue
                    elif (CWEB_flag and s1[p1] == '@' and p1 + 1 < len(s1) and
                        g.match(s2,p2,self.comment + '@' + s1[p1+1])):
                        p1 += 2
                        p2 += tot_len + 2
                        continue
                elif p2 < len(s2) and s2[p2] == '@':
                    if g.match(s1,p1,self.comment + '@' + self.comment_end):
                        p2 += 1
                        p1 += tot_len + 1
                        continue
                    elif (CWEB_flag and s1[p1] == '@' and p1 + 1 < len(s1) and
                        g.match(s1,p1,self.comment + '@' + s2[p2+1])):
                        p2 += 2
                        p1 += tot_len + 2
                        continue
                #@-node:ekr.20031218072017.3546:<< Check both parts for @ comment conventions >>
                #@nl
            if p1 >= len(s1) or p2 >= len(s2):
                break
            if s1[p1] != s2[p2]:
                return False
            p1 += 1 ; p2 += 1
        p1 = g.skip_ws_and_nl(s1,p1)
        p2 = g.skip_ws_and_nl(s2,p2)
        return p1 == len(s1) and p2 == len(s2)
    #@-node:ekr.20031218072017.3545:compare_comments
    #@+node:ekr.20031218072017.3547:massage_block_comment (no longer used)
    #@+at 
    #@nonl
    # This function is called to massage an @doc part in the ust. We 
    # call this routine only after a mismatch in @doc parts is found 
    # between the ust and tst. On entry, the parameters point to the 
    # inside of a block C comment: the opening and closing 
    # delimiters are not part of the text handled by self routine.
    # 
    # This code removes newlines that may have been inserted by the 
    # Tangle command in a block comment. Tangle may break lines 
    # differently in different expansions, but line breaks are 
    # ignored by forgiving_compare() and doc_compare() within block 
    # C comments.
    # 
    # We count the leading whitespace from the first non-blank line 
    # and remove this much whitespace from all lines. We also remove 
    # singleton newlines and replace sequences of two or more 
    # newlines by a single newline.
    #@-at
    #@@c

    def massage_block_comment (self,s):

        c = self.c
        newlines = 0  # Consecutive newlines seen.
        i = g.skip_blank_lines(s,0)
        # Copy the first line and set n
        i, n = g.skip_leading_ws_with_indent(s,i,c.tab_width)
        j = i ; i = g.skip_to_end_of_line(s,i)
        result = s[j:i]
        while i < len(s):
            assert(g.is_nl(s,i))
            newlines += 1
            # Replace the first newline with a blank.
            result += ' ' ; i += 1
            while i < len(s) and g.is_nl(s,i):
                i += 1 # skip the newline.
            j = i ; i = g.skip_ws(s,i)
            if g.is_nl(s,i)and newlines > 1:
                # Skip blank lines.
                while g.is_nl(s,i):
                    i += 1
            else:
                # Skip the leading whitespace.
                i = j # back track
                i = g.skip_leading_ws(s,i,n,c.tab_width)
                newlines = 0
                # Copy the rest of the line.
                j = i ; i = g.skip_to_end_of_line(s,i)
                result += s[j:i]
        return result
    #@-node:ekr.20031218072017.3547:massage_block_comment (no longer used)
    #@+node:ekr.20031218072017.3548:forgiving_compare
    #@+at 
    #@nonl
    # This is the "forgiving compare" function.  It compares two 
    # texts and returns True if they are identical except for 
    # comments or non-critical whitespace.  Whitespace inside 
    # strings or preprocessor directives must match exactly.
    #@-at
    #@@c

    def forgiving_compare (self,name,part,s1,s2):

        if 0:
            g.trace(name,part,
                "\n1:",g.get_line(s1,0),
                "\n2:",g.get_line(s2,0))
        s1 = g.toUnicode(s1,self.encoding)
        s2 = g.toUnicode(s2,self.encoding)
        #@    << Define forgiving_compare vars >>
        #@+node:ekr.20031218072017.3549:<< Define forgiving_compare vars >>
        # scan_derived_file has set the ivars describing comment delims.
        first1 = first2 = 0

        tot_len = 0
        if self.comment: tot_len += len(self.comment)
        if self.comment_end: tot_len += len(self.comment_end)

        CWEB_flag = (self.language == "c" and not self.use_noweb_flag)
        #@-node:ekr.20031218072017.3549:<< Define forgiving_compare vars >>
        #@nl
        p1 = g.skip_ws_and_nl(s1,0) 
        p2 = g.skip_ws_and_nl(s2,0)
        result = True
        while result and p1 < len(s1) and p2 < len(s2):
            first1 = p1 ; first2 = p2
            if self.comment and self.comment_end:
                #@            << Check both parts for @ comment conventions >>
                #@+node:ekr.20031218072017.3546:<< Check both parts for @ comment conventions >>
                #@+at 
                #@nonl
                # This code is used in forgiving_compare()and in 
                # compare_comments().
                # 
                # In noweb mode we allow / * @ * /  (without the 
                # spaces)to be equal to @.
                # In CWEB mode we allow / * @ ? * / (without the 
                # spaces)to be equal to @?.
                # at-backslash is not a valid CWEB control code, so 
                # we don't have to equate
                # / * @ \\ * / with at-backslash.
                # 
                # We must be careful not to run afoul of this very 
                # convention here!
                #@-at
                #@@c

                if p1 < len(s1) and s1[p1] == '@':
                    if g.match(s2,p2,self.comment + '@' + self.comment_end):
                        p1 += 1
                        p2 += tot_len + 1
                        continue
                    elif (CWEB_flag and s1[p1] == '@' and p1 + 1 < len(s1) and
                        g.match(s2,p2,self.comment + '@' + s1[p1+1])):
                        p1 += 2
                        p2 += tot_len + 2
                        continue
                elif p2 < len(s2) and s2[p2] == '@':
                    if g.match(s1,p1,self.comment + '@' + self.comment_end):
                        p2 += 1
                        p1 += tot_len + 1
                        continue
                    elif (CWEB_flag and s1[p1] == '@' and p1 + 1 < len(s1) and
                        g.match(s1,p1,self.comment + '@' + s2[p2+1])):
                        p2 += 2
                        p1 += tot_len + 2
                        continue
                #@-node:ekr.20031218072017.3546:<< Check both parts for @ comment conventions >>
                #@nl
            ch1 = s1[p1]
            if ch1 == '\r' or ch1 == '\n':
                #@            << Compare non-critical newlines >>
                #@+node:ekr.20031218072017.3550:<< Compare non-critical newlines >>
                p1 = g.skip_ws_and_nl(s1,p1)
                p2 = g.skip_ws_and_nl(s2,p2)
                #@-node:ekr.20031218072017.3550:<< Compare non-critical newlines >>
                #@nl
            elif ch1 ==  ' ' or ch1 == '\t':
                #@            << Compare non-critical whitespace >>
                #@+node:ekr.20031218072017.3551:<< Compare non-critical whitespace >>
                p1 = g.skip_ws(s1,p1)
                p2 = g.skip_ws(s2,p2)
                #@-node:ekr.20031218072017.3551:<< Compare non-critical whitespace >>
                #@nl
            elif ch1 == '\'' or ch1 == '"':
                #@            << Compare possible strings >>
                #@+node:ekr.20031218072017.3555:<< Compare possible strings >>
                # This code implicitly assumes that string1_len == string2_len == 1.
                # The match test ensures that the language actually supports strings.

                if (g.match(s1,p1,self.string1) or g.match(s1,p1,self.string2)) and s1[p1] == s2[p2]:

                    if self.language == "pascal":
                        #@        << Compare Pascal strings >>
                        #@+node:ekr.20031218072017.3557:<< Compare Pascal strings >>
                        #@+at 
                        #@nonl
                        # We assume the Pascal string is on a single 
                        # line so the problems with cr/lf do not 
                        # concern us.
                        #@-at
                        #@@c

                        first1 = p1 ; first2 = p2
                        p1 = g.skip_pascal_string(s1,p1)
                        p2 = g.skip_pascal_string(s2,p2)
                        result = s1[first1,p1] == s2[first2,p2]
                        #@-node:ekr.20031218072017.3557:<< Compare Pascal strings >>
                        #@nl
                    else:
                        #@        << Compare C strings >>
                        #@+node:ekr.20031218072017.3556:<< Compare C strings >>
                        delim = s1[p1]
                        result = s1[p1] == s2[p2]
                        p1 += 1 ; p2 += 1

                        while result and p1 < len(s1) and p2 < len(s2):
                            if s1[p1] == delim and self.is_end_of_string(s1,p1,delim):
                                result =(s2[p2] == delim and self.is_end_of_string(s2,p2,delim))
                                p1 += 1 ; p2 += 1
                                break
                            elif g.is_nl(s1,p1) and g.is_nl(s2,p2):
                                p1 = g.skip_nl(s1,p1)
                                p2 = g.skip_nl(s2,p2)
                            else:
                                result = s1[p1] == s2[p2]
                                p1 += 1 ; p2 += 1
                        #@-node:ekr.20031218072017.3556:<< Compare C strings >>
                        #@nl
                    if not result:
                        self.mismatch("Mismatched strings")
                else:
                    #@    << Compare single characters >>
                    #@+node:ekr.20031218072017.3553:<< Compare single characters >>
                    assert(p1 < len(s1) and p2 < len(s2))
                    result = s1[p1] == s2[p2]
                    p1 += 1 ; p2 += 1
                    if not result: self.mismatch("Mismatched single characters")
                    #@-node:ekr.20031218072017.3553:<< Compare single characters >>
                    #@nl
                #@-node:ekr.20031218072017.3555:<< Compare possible strings >>
                #@nl
            elif ch1 == '#':
                #@            << Compare possible preprocessor directives >>
                #@+node:ekr.20031218072017.3552:<< Compare possible preprocessor directives >>
                if self.language == "c":
                    #@    << compare preprocessor directives >>
                    #@+node:ekr.20031218072017.3554:<< Compare preprocessor directives >>
                    # We cannot assume that newlines are single characters.

                    result = s1[p1] == s2[p2]
                    p1 += 1 ; p2 += 1
                    while result and p1 < len(s1) and p2 < len(s2):
                        if g.is_nl(s1,p1):
                            result = g.is_nl(s2,p2)
                            if not result or self.is_end_of_directive(s1,p1):
                                break
                            p1 = g.skip_nl(s1,p1)
                            p2 = g.skip_nl(s2,p2)
                        else:
                            result = s1[p1] == s2[p2]
                            p1 += 1 ; p2 += 1
                    if not result:
                        self.mismatch("Mismatched preprocessor directives")
                    #@-node:ekr.20031218072017.3554:<< Compare preprocessor directives >>
                    #@nl
                else:
                    #@    << compare single characters >>
                    #@+node:ekr.20031218072017.3553:<< Compare single characters >>
                    assert(p1 < len(s1) and p2 < len(s2))
                    result = s1[p1] == s2[p2]
                    p1 += 1 ; p2 += 1
                    if not result: self.mismatch("Mismatched single characters")
                    #@-node:ekr.20031218072017.3553:<< Compare single characters >>
                    #@nl
                #@-node:ekr.20031218072017.3552:<< Compare possible preprocessor directives >>
                #@nl
            elif ch1 == '<' or ch1 == '@':
                #@            << Compare possible section references >>
                #@+node:ekr.20031218072017.3558:<< Compare possible section references >>
                if s1[p1] == '@' and CWEB_flag:  start_ref = "@<"
                elif s1[p1] == '<' and not CWEB_flag:  start_ref = "<<"
                else: start_ref = None

                # Tangling may insert newlines.
                p2 = g.skip_ws_and_nl(s2,p2)

                junk, kind1, junk2 = self.is_section_name(s1,p1)
                junk, kind2, junk2 = self.is_section_name(s2,p2)

                if start_ref and (kind1 != bad_section_name or kind2 != bad_section_name):
                    result = self.compare_section_names(s1[p1:],s2[p2:])
                    if result:
                        p1, junk1, junk2 = self.skip_section_name(s1,p1)
                        p2, junk1, junk2 = self.skip_section_name(s2,p2)
                    else: self.mismatch("Mismatched section names")
                else:
                    # Neither p1 nor p2 points at a section name.
                    result = s1[p1] == s2[p2]
                    p1 += 1 ; p2 += 1
                    if not result:
                        self.mismatch("Mismatch at '@' or '<'")
                #@-node:ekr.20031218072017.3558:<< Compare possible section references >>
                #@nl
            else:
                #@            << Compare comments or single characters >>
                #@+node:ekr.20031218072017.3559:<< Compare comments or single characters >>
                if g.match(s1,p1,self.sentinel) and g.match(s2,p2,self.sentinel):
                    first1 = p1 ; first2 = p2
                    p1 = g.skip_to_end_of_line(s1,p1)
                    p2 = g.skip_to_end_of_line(s2,p2)
                    result = self.compare_comments(s1[first1:p1],s2[first2:p2])
                    if not result:
                        self.mismatch("Mismatched sentinel comments")
                elif g.match(s1,p1,self.line_comment) and g.match(s2,p2,self.line_comment):
                    first1 = p1 ; first2 = p2
                    p1 = g.skip_to_end_of_line(s1,p1)
                    p2 = g.skip_to_end_of_line(s2,p2)
                    result = self.compare_comments(s1[first1:p1],s2[first2:p2])
                    if not result:
                        self.mismatch("Mismatched single-line comments")
                elif g.match(s1,p1,self.comment) and g.match(s2,p2,self.comment):
                    while (p1 < len(s1) and p2 < len(s2) and
                        not g.match(s1,p1,self.comment_end) and not g.match(s2,p2,self.comment_end)):
                        # ws doesn't have to match exactly either!
                        if g.is_nl(s1,p1)or g.is_ws(s1[p1]):
                            p1 = g.skip_ws_and_nl(s1,p1)
                        else: p1 += 1
                        if g.is_nl(s2,p2)or g.is_ws(s2[p2]):
                            p2 = g.skip_ws_and_nl(s2,p2)
                        else: p2 += 1
                    p1 = g.skip_ws_and_nl(s1,p1)
                    p2 = g.skip_ws_and_nl(s2,p2)
                    if g.match(s1,p1,self.comment_end) and g.match(s2,p2,self.comment_end):
                        first1 = p1 ; first2 = p2
                        p1 += len(self.comment_end)
                        p2 += len(self.comment_end)
                        result = self.compare_comments(s1[first1:p1],s2[first2:p2])
                    else: result = False
                    if not result:
                        self.mismatch("Mismatched block comments")
                elif g.match(s1,p1,self.comment2) and g.match(s2,p2,self.comment2):
                    while (p1 < len(s1) and p2 < len(s2) and
                        not g.match(s1,p1,self.comment2_end) and not g.match(s2,p2,self.comment2_end)):
                        # ws doesn't have to match exactly either!
                        if  g.is_nl(s1,p1)or g.is_ws(s1[p1]):
                            p1 = g.skip_ws_and_nl(s1,p1)
                        else: p1 += 1
                        if g.is_nl(s2,p2)or g.is_ws(s2[p2]):
                            p2 = g.skip_ws_and_nl(s2,p2)
                        else: p2 += 1
                    p1 = g.skip_ws_and_nl(s1,p1)
                    p2 = g.skip_ws_and_nl(s2,p2)
                    if g.match(s1,p1,self.comment2_end) and g.match(s2,p2,self.comment2_end):
                        first1 = p1 ; first2 = p2
                        p1 += len(self.comment2_end)
                        p2 += len(self.comment2_end)
                        result = self.compare_comments(s1[first1:p1],s2[first2:p2])
                    else: result = False
                    if not result:
                        self.mismatch("Mismatched alternalte block comments")
                else:
                    #@    << Compare single characters >>
                    #@+node:ekr.20031218072017.3553:<< Compare single characters >>
                    assert(p1 < len(s1) and p2 < len(s2))
                    result = s1[p1] == s2[p2]
                    p1 += 1 ; p2 += 1
                    if not result: self.mismatch("Mismatched single characters")
                    #@-node:ekr.20031218072017.3553:<< Compare single characters >>
                    #@nl
                #@-node:ekr.20031218072017.3559:<< Compare comments or single characters >>
                #@nl
        #@    << Make sure both parts have ended >>
        #@+node:ekr.20031218072017.3560:<< Make sure both parts have ended >>
        if result:
            p1 = g.skip_ws_and_nl(s1,p1)
            p2 = g.skip_ws_and_nl(s2,p2)
            result = p1 >= len(s1) and p2 >= len(s2)
            if not result:
                # Show the ends of both parts.
                p1 = len(s1)
                p2 = len(s2)
                self.mismatch("One part ends before the other.")
        #@-node:ekr.20031218072017.3560:<< Make sure both parts have ended >>
        #@nl
        if not result:
            #@        << trace the mismatch >>
            #@+node:ekr.20031218072017.3561:<< Trace the mismatch >>
            if 0:
                g.trace(self.message +
                    "\nPart ",part," section ",name,
                    "\n1:",g.get_line(s1,p1),
                    "\n2:",g.get_line(s2,p2))
            #@-node:ekr.20031218072017.3561:<< Trace the mismatch >>
            #@nl
        return result
    #@-node:ekr.20031218072017.3548:forgiving_compare
    #@+node:ekr.20031218072017.3562:mismatch
    def mismatch (self,message):

        self.message = message
    #@-node:ekr.20031218072017.3562:mismatch
    #@+node:ekr.20031218072017.3563:scan_derived_file (pass 1)
    #@+at
    # 
    # This function scans an entire derived file in s, discovering 
    # section or part
    # definitions.
    # 
    # This is the easiest place to delete leading whitespace from 
    # each line: we simply
    # don't copy it. We also ignore leading blank lines and trailing 
    # blank lines. The
    # resulting definition must compare equal using the "forgiving" 
    # compare to any
    # other definitions of that section or part.
    # 
    # We use a stack to handle nested expansions. The outermost 
    # level of expansion
    # corresponds to the @root directive that created the file. When 
    # the stack is
    # popped, the indent variable is restored.
    # 
    # self.root_name is the name of the file mentioned in the @root 
    # directive.
    # 
    # The caller has deleted all body_ignored_newlines from the 
    # text.
    #@-at
    #@@c

    def scan_derived_file (self,s):

        c = self.c
        self.def_stack = []
        #@    << set the private global matching vars >>
        #@+node:ekr.20031218072017.2368:<< set the private global matching vars >>
        # Set defaults from the public globals set by the @comment command.
        if self.single_comment_string:
            self.sentinel = self.single_comment_string
            self.sentinel_end = None
        elif self.end_comment_string:
            self.sentinel = self.start_comment_string
            self.sentinel_end = self.end_comment_string
        else:
            self.sentinel = self.sentinel_end = None

        if 0:
            g.trace("single,start,end,sentinel:",
                repr(self.single_comment_string),
                repr(self.start_comment_string),
                repr(self.end_comment_string),
                repr(self.sentinel))

        # Set defaults.  See set_delims_from_langauge.
        self.line_comment = self.single_comment_string
        self.comment = self.start_comment_string
        self.comment_end = self.end_comment_string
        self.comment2 = self.comment2_end = None
        self.string1 = "\""
        self.string2 = "'"
        self.verbatim = None

        # Set special cases.
        if self.language == "plain":
            self.string1 = self.string2 = None # This is debatable.
            self.line_comment = None
        if self.language == "pascal":
            self.comment2 = "(*" ; self.comment2_end = "*)"
        if self.language == "latex": # 3/10/03: Joo-won Jung
            self.string1 = self.string2 = None # This is debatable.
        if self.language == "html":
            self.string1 = '"' ; self.string2 = None # 12/3/03
        #@-node:ekr.20031218072017.2368:<< set the private global matching vars >>
        #@nl
        line_indent = 0  # The indentation to use if we see a section reference.
        # indent is the leading whitespace to be deleted.
        i, indent = g.skip_leading_ws_with_indent(s,0,self.tab_width)
        #@    << Skip the header line output by tangle >>
        #@+node:ekr.20031218072017.3564:<< Skip the header line output by tangle >>
        if self.sentinel or self.comment:
            line = g.choose(self.sentinel,self.sentinel,self.comment) + " Created by Leo from" 
            if g.match(s,i,line):
                # Even a block comment will end on the first line.
                i = g.skip_to_end_of_line(s,i)
        #@-node:ekr.20031218072017.3564:<< Skip the header line output by tangle >>
        #@nl
        # The top level of the stack represents the root.
        self.push_new_def_node(self.root_name,indent,1,1,True)
        while i < len(s):
            ch = s[i]
            if ch == '\r':
                i += 1 # ignore
            elif ch == '\n':
                #@            << handle the start of a new line >>
                #@+node:ekr.20031218072017.3565:<< handle the start of a new line >>
                self.copy(ch) ; i += 1 # This works because we have one-character newlines.

                # Set line_indent, used only if we see a section reference.
                junk, line_indent = g.skip_leading_ws_with_indent(s,i,c.tab_width)
                i = g.skip_leading_ws(s,i,indent,c.tab_width) # skip indent leading white space.
                #@-node:ekr.20031218072017.3565:<< handle the start of a new line >>
                #@nl
            elif g.match(s,i,self.sentinel) and self.is_sentinel_line(s,i):
                #@            << handle a sentinel line  >>
                #@+node:ekr.20031218072017.3566:<< handle a sentinel line >>
                #@+at 
                #@nonl
                # This is the place to eliminate the proper amount 
                # of whitespace from the start of each line. We do 
                # this by setting the 'indent' variable to the 
                # leading whitespace of the first _non-blank_ line 
                # following the opening sentinel.
                # 
                # Tangle increases the indentation by one tab if the 
                # section reference is not the first non-whitespace 
                # item on the line,so self code must do the same.
                #@-at
                #@@c

                # g.trace(g.get_line(s,i))
                result,junk,kind,name,part,of,end,nl_flag = self.is_sentinel_line_with_data(s,i)
                assert(result)
                #@<< terminate the previous part of this section if it exists >>
                #@+node:ekr.20031218072017.3567:<< terminate the previous part of this section if it exists >>
                #@+at 
                #@nonl
                # We have just seen a sentinel line. Any kind of 
                # sentinel line will terminate a previous part of 
                # the present definition. For end sentinel lines, 
                # the present section name must match the name on 
                # the top of the stack.
                #@-at
                #@@c

                if len(self.def_stack) > 0:
                    dn = self.def_stack[-1]
                    if self.compare_section_names(name,dn.name):
                        dn = self.def_stack.pop()
                        if len(dn.code) > 0:
                            thePart, found = self.ust_lookup(name,dn.part,False,False) # not root, not update
                            # Check for incompatible previous definition.
                            if found and not self.forgiving_compare(name,dn.part,dn.code,thePart.code):
                                self.error("Incompatible definitions of " + name)
                            elif not found:
                                self.ust_enter(name,dn.part,dn.of,dn.code,dn.nl_flag,False) # not root
                    elif kind == end_sentinel_line:
                        self.error("Missing sentinel line for: " + name)
                #@-node:ekr.20031218072017.3567:<< terminate the previous part of this section if it exists >>
                #@nl

                if kind == start_sentinel_line:
                    indent = line_indent
                    # Increase line_indent by one tab width if the
                    # the section reference does not start the line.
                    j = i - 1
                    while j >= 0:
                        if g.is_nl(s,j):
                            break
                        elif not g.is_ws(s[j]):
                            indent += abs(self.tab_width) ; break
                        j -= 1
                    # copy the section reference to the _present_ section,
                    # but only if this is the first part of the section.
                    if part < 2: self.copy(name)
                    # Skip to the first character of the new section definition.
                    i = g.skip_to_end_of_line(s,i)
                    # Start the new section.
                    self.push_new_def_node(name,indent,part,of,nl_flag)
                else:
                    assert(kind == end_sentinel_line)
                    # Skip the sentinel line.
                    i = g.skip_to_end_of_line(s,i)
                    # Skip a newline only if it was added after(!newline)
                    if not nl_flag:
                        i = g.skip_ws(s,i)
                        i = g.skip_nl(s,i)
                        i = g.skip_ws(s,i)
                        # Copy any whitespace following the (!newline)
                        while end and g.is_ws(s[end]):
                            self.copy(s[end])
                            end += 1
                    # Restore the old indentation level.
                    if len(self.def_stack) > 0:
                        indent = self.def_stack[-1].indent
                #@-node:ekr.20031218072017.3566:<< handle a sentinel line >>
                #@nl
            elif g.match(s,i,self.line_comment) or g.match(s,i,self.verbatim):
                #@            << copy the entire line >>
                #@+node:ekr.20031218072017.3568:<< copy the entire line >>
                j = i ; i = g.skip_to_end_of_line(s,i)
                self.copy(s[j:i])
                #@-node:ekr.20031218072017.3568:<< copy the entire line >>
                #@nl
            elif g.match(s,i,self.comment):
                #@            << copy a multi-line comment >>
                #@+node:ekr.20031218072017.3570:<< copy a multi-line comment >>
                assert(self.comment_end)

                # Scan for the ending delimiter.
                j = i ; i += len(self.comment)
                while i < len(s) and not g.match(s,i,self.comment_end):
                    i += 1
                if g.match(s,i,self.comment_end):
                    i += len(self.comment_end)
                self.copy(s[j:i])
                #@-node:ekr.20031218072017.3570:<< copy a multi-line comment >>
                #@nl
            elif g.match(s,i,self.comment2):
                #@            << copy an alternate multi-line comment >>
                #@+node:ekr.20031218072017.3571:<< copy an alternate multi-line comment >>
                assert(self.comment2_end)
                j = i
                # Scan for the ending delimiter.
                i += len(self.comment2)
                while i < len(s) and not g.match(s,i,self.comment2_end):
                    i += 1
                if g.match(s,i,self.comment2_end):
                    i += len(self.comment2)
                self.copy(s[j:i])
                #@-node:ekr.20031218072017.3571:<< copy an alternate multi-line comment >>
                #@nl
            elif g.match(s,i,self.string1) or g.match(s,i,self.string2):
                #@            << copy a string >>
                #@+node:ekr.20031218072017.3569:<< copy a string >>
                j = i
                if self.language == "pascal":
                    i = g.skip_pascal_string(s,i)
                else:
                    i = g.skip_string(s,i)
                self.copy(s[j:i])
                #@-node:ekr.20031218072017.3569:<< copy a string >>
                #@nl
            else:
                self.copy(ch) ; i += 1
        #@    << end all open sections >>
        #@+node:ekr.20031218072017.3572:<< end all open sections >>
        dn= None
        while len(self.def_stack) > 0:
            dn = self.def_stack.pop()
            if len(self.def_stack) > 0:
                self.error("Unterminated section: " + dn.name)
        if dn:
            # Terminate the root setcion.
            i = len(s)
            if dn.code and len(dn.code) > 0:
                self.ust_enter(dn.name,dn.part,dn.of,dn.code,dn.nl_flag,is_root_flag=True)
            else:
                self.error("Missing root part")
        else:
            self.error("Missing root section")
        #@-node:ekr.20031218072017.3572:<< end all open sections >>
        #@nl
    #@-node:ekr.20031218072017.3563:scan_derived_file (pass 1)
    #@+node:ekr.20031218072017.3573:update_def (pass 2)
    #@+at
    # This function handles the actual updating of section 
    # definitions in the web.
    # Only code parts are updated, never doc parts.
    # 
    # During pass 2 of Untangle, skip_body() calls this routine when 
    # it discovers the
    # definition of a section in the outline. We look up the name in 
    # the ust. If an
    # entry exists, we compare the code (the code part of an outline 
    # node) with the
    # code part in the ust. We update the code part if necessary.
    # 
    # We use the forgiving_compare() to compare code parts. It's not 
    # possible to
    # change only trivial whitespace using Untangle because 
    # forgiving_compare()
    # ignores trivial whitespace.
    #@-at
    #@@c

    # Major change: 2/23/01: Untangle never updates doc parts.

    def update_def (self,name,part_number,head,code,tail,is_root_flag=False): # Doc parts are never updated!

        # g.trace(name,part_number,code)
        p = self.p ; body = p.b
        if not head: head = ""
        if not tail: tail = ""
        if not code: code = ""
        false_ret = head + code + tail, len(head) + len(code), False
        part, found = self.ust_lookup(name,part_number,is_root_flag,update_flag=True)
        if not found:
            return false_ret  # Not an error.
        ucode = g.toUnicode(part.code,self.encoding)
        #@    << Remove leading blank lines and comments from ucode >>
        #@+node:ekr.20031218072017.3574:<< Remove leading blank lines and comments from ucode >>
        #@+at
        # We assume that any leading comments came from an @doc 
        # part. This isn't always
        # valid and this code will eliminate such leading comments. 
        # This is a defect in
        # Untangle; it can hardly be avoided.
        #@-at
        #@@c

        i = g.skip_blank_lines(ucode,0)
        j = g.skip_ws(ucode,i)
        # g.trace("comment,end,single:",self.comment,self.comment_end,self.line_comment)

        if self.comment and self.comment_end:
            if ucode and g.match(ucode,j,self.comment):
                # Skip to the end of the block comment.
                i = j + len(self.comment)
                i = ucode.find(self.comment_end,i)
                if i == -1: ucode = None # An unreported problem in the user code.
                else:
                    i += len(self.comment_end)
                    i = g.skip_blank_lines(ucode,i)
        elif self.line_comment:
            while ucode and g.match(ucode,j,self.line_comment):
                i = g.skip_line(ucode,i)
                i = g.skip_blank_lines(ucode,i)
                j = g.skip_ws(ucode,i)
        # Only the value of ucode matters here.
        if ucode: ucode = ucode[i:]
        #@-node:ekr.20031218072017.3574:<< Remove leading blank lines and comments from ucode >>
        #@nl
        # g.trace(ucode)
        if not ucode or len(ucode) == 0:
            return false_ret # Not an error.
        if code and self.forgiving_compare(name,part,code,ucode):
            return false_ret # Not an error.
        # Update the body.
        g.es("***Updating:",p.h)
        i = g.skip_blank_lines(ucode,0)
        ucode = ucode[i:]
        ucode = ucode.rstrip()
        # Add the trailing whitespace of code to ucode.
        code2 = code.rstrip()
        trail_ws = code[len(code2):]
        ucode = ucode + trail_ws
        body = head + ucode + tail
        self.update_current_vnode(body)
        # g.trace("\nhead:",head,"\nucode:"ucode,"\ntail:",tail)
        return body, len(head) + len(ucode),True
    #@-node:ekr.20031218072017.3573:update_def (pass 2)
    #@+node:ekr.20031218072017.3575:update_current_vnode
    def update_current_vnode (self,s):

        """Called from within the Untangle logic to update the body text of self.p."""

        c = self.c ; p = self.p
        assert(self.p)
        c.setBodyString(p,s)

        c.setChanged(True)
        p.setDirty()
        p.setMarked()

        # 2010/02/02: was update_after_icons_changed.
        c.redraw_after_icons_changed()
    #@-node:ekr.20031218072017.3575:update_current_vnode
    #@-node:ekr.20031218072017.3544:untangle
    #@+node:ekr.20031218072017.3576:utility methods
    #@+at 
    #@nonl
    # These utilities deal with tangle ivars, so they should be 
    # methods.
    #@-at
    #@+node:ekr.20031218072017.3577:compare_section_names
    # Compares section names or root names.
    # Arbitrary text may follow the section name on the same line.

    def compare_section_names (self,s1,s2):

        # g.trace(g.get_line(s1,0),':',g.get_line(s2,0))
        if g.match(s1,0,"<<") or g.match(s1,0,"@<"):
            # Use a forgiving compare of the two section names.
            delim = g.choose(self.use_cweb_flag,"@>",">>")
            i1 = i2 = 0
            while i1 < len(s1) and i2 < len(s2):
                ch1 = s1[i1] ; ch2 = s2[i2]
                if g.is_ws(ch1) and g.is_ws(ch2):
                    i1 = g.skip_ws(s1,i1)
                    i2 = g.skip_ws(s2,i2)
                elif g.match(s1,i1,delim) and g.match(s2,i2,delim):
                    return True
                elif ch1.lower() == ch2.lower():
                    i1 += 1 ; i2 += 1
                else: return False
            return False
        else: # A root name.
            return s1 == s2
    #@-node:ekr.20031218072017.3577:compare_section_names
    #@+node:ekr.20031218072017.3578:copy
    def copy (self, s):

        assert(len(self.def_stack) > 0)
        dn = self.def_stack[-1] # Add the code at the top of the stack.
        dn.code += s
    #@-node:ekr.20031218072017.3578:copy
    #@+node:ekr.20031218072017.3579:error, pathError, warning
    def error (self,s):
        self.errors += 1
        g.es_error(g.translateString(s))

    def pathError (self,s):
        if not self.path_warning_given:
            self.path_warning_given = True
            self.error(s)

    def warning (self,s):
        g.es_error(g.translateString(s))
    #@-node:ekr.20031218072017.3579:error, pathError, warning
    #@+node:ekr.20031218072017.3580:is_end_of_directive
    # This function returns True if we are at the end of preprocessor directive.

    def is_end_of_directive (self,s,i):

        return g.is_nl(s,i) and not self.is_escaped(s,i)
    #@-node:ekr.20031218072017.3580:is_end_of_directive
    #@+node:ekr.20031218072017.3581:is_end_of_string
    def is_end_of_string (self,s,i,delim):

        return i < len(s) and s[i] == delim and not self.is_escaped(s,i)
    #@-node:ekr.20031218072017.3581:is_end_of_string
    #@+node:ekr.20031218072017.3582:is_escaped
    # This function returns True if the s[i] is preceded by an odd number of back slashes.

    def is_escaped (self,s,i):

        back_slashes = 0 ; i -= 1
        while i >= 0 and s[i] == '\\':
            back_slashes += 1
            i -= 1
        return (back_slashes & 1) == 1
    #@-node:ekr.20031218072017.3582:is_escaped
    #@+node:ekr.20031218072017.3583:is_section_name
    def is_section_name(self,s,i):

        kind = bad_section_name ; end = -1

        if self.use_cweb_flag :
            if g.match(s,i,"@<"):
                i, kind, end = self.skip_cweb_section_name(s,i)
        elif g.match(s,i,"<<"):
            i, kind, end = self.skip_noweb_section_name(s,i)

        # g.trace(kind,g.get_line(s,end))
        return i, kind, end
    #@-node:ekr.20031218072017.3583:is_section_name
    #@+node:ekr.20031218072017.3584:is_sentinel_line & is_sentinel_line_with_data
    #@+at 
    #@nonl
    # This function returns True if i points to a line a sentinel 
    # line of one of the following forms:
    # 
    # start_sentinel <<section name>> end_sentinel
    # start_sentinel <<section name>> (n of m) end_sentinel
    # start_sentinel -- end -- <<section name>> end_sentinel
    # start_sentinel -- end -- <<section name>> (n of m) 
    # end_sentinel
    # 
    # start_sentinel: the string that signals the start of sentinel 
    # lines\
    # end_sentinel:   the string that signals the endof sentinel 
    # lines.
    # 
    # end_sentinel may be None,indicating that sentinel lines end 
    # with a newline.
    # 
    # Any of these forms may end with (!newline), indicating that 
    # the section reference was not followed by a newline in the 
    # orignal text.  We set nl_flag to False if such a string is 
    # seen. The name argument contains the section name.
    # 
    # The valid values of kind param are:
    # 
    # non_sentinel_line,   # not a sentinel line.
    # start_sentinel_line, #   /// <section name> or /// <section 
    # name>(n of m)
    # end_sentinel_line  //  /// -- end -- <section name> or /// -- 
    # end -- <section name>(n of m).
    #@-at
    #@@c
    def is_sentinel_line (self,s,i):

        result,i,kind,name,part,of,end,nl_flag = self.is_sentinel_line_with_data(s,i)
        return result

    def is_sentinel_line_with_data (self,s,i):

        start_sentinel = self.sentinel
        end_sentinel = self.sentinel_end
        #@    << Initialize the return values >>
        #@+node:ekr.20031218072017.3585:<< Initialize the return values  >>
        name = end = None
        part = of = 1
        kind = non_sentinel_line
        nl_flag = True
        false_data = (False,i,kind,name,part,of,end,nl_flag)
        #@-node:ekr.20031218072017.3585:<< Initialize the return values  >>
        #@nl
        #@    << Make sure the line starts with start_sentinel >>
        #@+node:ekr.20031218072017.3586:<< Make sure the line starts with start_sentinel >>
        if g.is_nl(s,i): i = g.skip_nl(s,i)
        i = g.skip_ws(s,i)

        # 4/18/00: We now require an exact match of the sentinel.
        if g.match(s,i,start_sentinel):
            i += len(start_sentinel)
        else:
            return false_data
        #@-node:ekr.20031218072017.3586:<< Make sure the line starts with start_sentinel >>
        #@nl
        #@    << Set end_flag if we have -- end -- >>
        #@+node:ekr.20031218072017.3587:<< Set end_flag if we have -- end -- >>
        # If i points to "-- end --", this code skips it and sets end_flag.

        end_flag = False
        i = g.skip_ws(s,i)
        if g.match(s,i,"--"):
            while i < len(s) and s[i] == '-':
                i += 1
            i = g.skip_ws(s,i)
            if not g.match(s,i,"end"):
                return false_data # Not a valid sentinel line.
            i += 3 ; i = g.skip_ws(s,i)
            if not g.match(s,i,"--"):
                return false_data # Not a valid sentinel line.
            while i < len(s) and s[i] == '-':
                i += 1
            end_flag = True
        #@-node:ekr.20031218072017.3587:<< Set end_flag if we have -- end -- >>
        #@nl
        #@    << Make sure we have a section reference >>
        #@+node:ekr.20031218072017.3588:<< Make sure we have a section reference >>
        i = g.skip_ws(s,i)

        if (self.use_noweb_flag and g.match(s,i,"<<") or
            self.use_cweb_flag  and g.match(s,i,"@<") ):

            j = i ; i, kind, end = self.skip_section_name(s,i)
            if kind != section_ref:
                return false_data
            name = s[j:i]
        else:
            return false_data
        #@-node:ekr.20031218072017.3588:<< Make sure we have a section reference >>
        #@nl
        #@    << Set part and of if they exist >>
        #@+node:ekr.20031218072017.3589:<< Set part and of if they exist >>
        # This code handles (m of n), if it exists.
        i = g.skip_ws(s,i)
        if g.match(s,i,'('):
            j = i
            i += 1 ; i = g.skip_ws(s,i)
            i, part = self.scan_short_val(s,i)
            if part == -1:
                i = j # back out of the scanning for the number.
                part = 1
            else:
                i = g.skip_ws(s,i)
                if not g.match(s,i,"of"):
                    return false_data
                i += 2 ; i = g.skip_ws(s,i)
                i, of = self.scan_short_val(s,i)
                if of == -1:
                    return false_data
                i = g.skip_ws(s,i)
                if g.match(s,i,')'):
                    i += 1 # Skip the paren and do _not_ return.
                else:
                    return false_data
        #@-node:ekr.20031218072017.3589:<< Set part and of if they exist >>
        #@nl
        #@    << Set nl_flag to False if !newline exists >>
        #@+node:ekr.20031218072017.3590:<< Set nl_flag to false if !newline exists >>
        line = "(!newline)"
        i = g.skip_ws(s,i)
        if g.match(s,i,line):
            i += len(line)
            nl_flag = False
        #@-node:ekr.20031218072017.3590:<< Set nl_flag to false if !newline exists >>
        #@nl
        #@    << Make sure the line ends with end_sentinel >>
        #@+node:ekr.20031218072017.3591:<< Make sure the line ends with end_sentinel >>
        i = g.skip_ws(s,i)
        if end_sentinel:
            # Make sure the line ends with the end sentinel.
            if g.match(s,i,end_sentinel):
                i += len(end_sentinel)
            else:
                return false_data

        end = i # Show the start of the whitespace.
        i = g.skip_ws(s,i)
        if i < len(s) and not g.is_nl(s,i):
            return false_data
        #@-node:ekr.20031218072017.3591:<< Make sure the line ends with end_sentinel >>
        #@nl
        kind = g.choose(end_flag,end_sentinel_line,start_sentinel_line)
        return True,i,kind,name,part,of,end,nl_flag
    #@-node:ekr.20031218072017.3584:is_sentinel_line & is_sentinel_line_with_data
    #@+node:ekr.20031218072017.3592:push_new_def_node
    # This function pushes a new def_node on the top of the section stack.

    def push_new_def_node (self,name,indent,part,of,nl_flag):

        # g.trace(name,part)
        node = def_node(name,indent,part,of,nl_flag,None)
        self.def_stack.append(node)
    #@-node:ekr.20031218072017.3592:push_new_def_node
    #@+node:ekr.20031218072017.3593:scan_short_val
    # This function scans a positive integer.
    # returns (i,val), where val == -1 if there is an error.

    def scan_short_val (self,s,i):


        if i >= len(s) or not s[i].isdigit():
            return i, -1
        j = i
        while i < len(s) and s[i].isdigit():
            i += 1
        val = int(s[j:i])
        # g.trace(s[j:i],val)
        return i, val
    #@nonl
    #@-node:ekr.20031218072017.3593:scan_short_val
    #@+node:ekr.20031218072017.3594:setRootFromHeadline
    def setRootFromHeadline (self,p):

        s = p.h

        if s[0:5] == "@root":
            i,self.start_mode = g.scanAtRootOptions(s,0)
            i = g.skip_ws(s,i)

            if i < len(s): # Non-empty file name.
                # self.root_name must be set later by token_type().
                self.root = s[i:]
    #@-node:ekr.20031218072017.3594:setRootFromHeadline
    #@+node:ekr.20031218072017.1259:setRootFromText
    #@+at 
    #@nonl
    # This code skips the file name used in @root directives.
    # 
    # File names may be enclosed in < and > characters, or in double 
    # quotes.  If a file name is not enclosed be these delimiters it 
    # continues until the next newline.
    #@-at
    #@@c
    def setRootFromText(self,s,report_errors=True):

        # g.trace(s)
        self.root_name = None
        i,self.start_mode = g.scanAtRootOptions(s,0)
        i = g.skip_ws(s,i)

        if i >= len(s): return i
        # Allow <> or "" as delimiters, or a bare file name.
        if s[i] == '"':
            i += 1 ; delim = '"'
        elif s[i] == '<':
            i += 1 ; delim = '>'
        else: delim = '\n'

        root1 = i # The name does not include the delimiter.
        while i < len(s) and s[i] != delim and not g.is_nl(s,i):
            i += 1
        root2 = i

        if delim != '\n' and not g.match(s,i,delim):
            if report_errors:
                g.scanError("bad filename in @root " + s[:i])
        else:
            self.root_name = s[root1:root2].strip()
        return i
    #@-node:ekr.20031218072017.1259:setRootFromText
    #@+node:ekr.20031218072017.3595:skip_CWEB_section_name
    #@+at 
    #@nonl
    # This function skips past a section name that starts with @< 
    # and ends with @>. This code also skips any = following the 
    # section name.
    # 
    # Returns (i, kind, end), where kind is:
    # 
    #     bad_section_name:  @ < with no matching @ >
    #     section_ref: @ < name @ >
    #     section_def: @ < name @ > =
    # 
    # Unlike noweb, bad section names generate errors.
    #@-at
    #@@c

    def skip_cweb_section_name(self,s,i):

        j = i # Used for error message.
        kind = bad_section_name ; end = -1
        runon = False ; empty_name = True
        assert(s[i:i+2]=="@<")
        i += 2
        while i < len(s):
            if g.match(s,i,"@>="):
                i += 3 ; end = i-1 ; kind = section_def ; break
            elif g.match(s,i,"@>"):
                i += 2 ; end = i ; kind = section_ref ; break
            elif g.match(s,i,"@<"):
                runon = True ; break
            elif g.match(s,i,"@@"): i += 2
            elif g.is_ws_or_nl(s,i): i += 1
            else:
                i += 1 ; empty_name = False

        if empty_name:
            g.scanError("empty CWEB section name: " + s[j:i])
            return i, bad_section_name, -1
        elif i >= len(s) or runon:
            g.scanError("Run on CWEB section name: " + s[j:i])
            return i, bad_section_name, -1
        else:
            return i, kind, end
    #@-node:ekr.20031218072017.3595:skip_CWEB_section_name
    #@+node:ekr.20031218072017.3596:skip_noweb_section_name
    #@+at 
    #@nonl
    # This function skips past a section name that starts with < < 
    # and might end with > > or > > =. The entire section name must 
    # appear on the same line.
    # 
    # Note: this code no longer supports extended noweb mode.
    # 
    # Returns (i, kind, end),
    #     end indicates the end of the section name itself (not 
    # counting the =).
    #     kind is one of:
    #         bad_section_name: "no matching ">>" or ">>"  This is 
    # _not_ a user error!
    #         section_ref: < < name > >
    #         section_def: < < name > > =
    #         at_root:     < < * > > =
    #@-at
    #@@c
    def skip_noweb_section_name(self,s,i):

        assert(g.match(s,i,"<<"))
        i += 2
        j = i # Return this value if no section name found.
        kind = bad_section_name ; end = -1 ; empty_name = True

        # Scan for the end of the section name.
        while i < len(s) and not g.is_nl(s,i):
            if g.match(s,i,">>="):
                i += 3 ; end = i - 1 ; kind = section_def ; break
            elif g.match(s,i,">>"):
                i += 2 ; end = i ; kind = section_ref ; break
            elif g.is_ws_or_nl(s,i):
                i += 1
            elif empty_name and s[i] == '*':
                empty_name = False
                i = g.skip_ws(s,i+1) # skip the '*'
                if g.match(s,i,">>="):
                    i += 3 ; end = i - 1 ; kind = at_root ; break
            else:
                i += 1 ; empty_name = False

        if empty_name:
            kind = bad_section_name
        if kind == bad_section_name:
            i = j
        return i, kind, end
    #@-node:ekr.20031218072017.3596:skip_noweb_section_name
    #@+node:ekr.20031218072017.3597:skip_section_name
    # Returns a tuple (i, kind, end)

    def skip_section_name(self,s,i):

        if self.use_noweb_flag:
            return self.skip_noweb_section_name(s,i)
        else:
            return self.skip_cweb_section_name(s,i)
    #@-node:ekr.20031218072017.3597:skip_section_name
    #@+node:ekr.20031218072017.3598:standardize_name
    def standardize_name (self,name):

        """Removes leading and trailing brackets, converts white space to a single blank and converts to lower case."""

        # Convert to lowercase.
        # Convert whitespace to a single space.
        name = name.lower().replace('\t',' ').replace('  ',' ')

        # Remove leading '<'
        i = 0 ; n = len(name)
        while i < n and name[i] == '<':
            i += 1
        j = i

        # Find the first '>'
        while i < n and name [i] != '>':
            i += 1
        name = name[j:i].strip()

        return name
    #@-node:ekr.20031218072017.3598:standardize_name
    #@+node:ekr.20080923124254.16:tangle.scanAllDirectives
    def scanAllDirectives(self,p):

        """Scan vnode p and p's ancestors looking for directives,
        setting corresponding tangle ivars and globals.
        """

        c = self.c
        self.init_directive_ivars()
        if p:
            s = p.b
            #@        << Collect @first attributes >>
            #@+node:ekr.20080923124254.17:<< Collect @first attributes >>
            #@+at 
            #@nonl
            # Stephen P. Schaefer 9/13/2002: Add support for @first.
            # Unlike other root attributes, does *NOT* inherit from 
            # parent nodes.
            #@-at
            #@@c
            tag = "@first"
            sizeString = len(s) # DTHEIN 13-OCT-2002: use to detect end-of-string
            i = 0
            while 1:
                # DTHEIN 13-OCT-2002: directives must start at beginning of a line
                if not g.match_word(s,i,tag):
                    i = g.skip_line(s,i)
                else:
                    i = i + len(tag)
                    j = i = g.skip_ws(s,i)
                    i = g.skip_to_end_of_line(s,i)
                    if i>j:
                        self.first_lines += s[j:i] + '\n'
                    i = g.skip_nl(s,i)
                if i >= sizeString:  # DTHEIN 13-OCT-2002: get out when end of string reached
                    break
            #@-node:ekr.20080923124254.17:<< Collect @first attributes >>
            #@nl

        # delims = (self.single_comment_string,self.start_comment_string,self.end_comment_string)
        lang_dict = {'language':self.language,'delims':None,} # Delims not used

        table = (
            ('encoding',    self.encoding,  g.scanAtEncodingDirectives),
            ('lang-dict',   lang_dict,      g.scanAtCommentAndAtLanguageDirectives),
            ('lineending',  None,           g.scanAtLineendingDirectives),
            ('pagewidth',   c.page_width,   g.scanAtPagewidthDirectives),
            ('path',        None,           c.scanAtPathDirectives), 
            ('tabwidth',    c.tab_width,    g.scanAtTabwidthDirectives),
        )

        # Set d by scanning all directives.
        aList = g.get_directives_dict_list(p)
        d = {}
        for key,default,func in table:
            val = func(aList)
            d[key] = g.choose(val is None,default,val)

        # Post process.
        lang_dict       = d.get('lang-dict')
        lineending      = d.get('lineending')
        if lineending:
            self.output_newline = lineending
        self.encoding             = d.get('encoding')
        self.language             = lang_dict.get('language')
        self.page_width           = d.get('pagewidth')
        self.tangle_directory     = d.get('path')
        self.tab_width            = d.get('tabwidth')

        # Handle the print-mode directives.
        self.print_mode = None
        for d in aList:
            for key in ('verbose','terse','quiet','silent'):
                if d.get(key) is not None:
                    self.print_mode = key ; break
            if self.print_mode: break
        if not self.print_mode: self.print_mode = 'verbose'

        # 2010/01/27: bug fix: make sure to set the ivars.
        delim1,delim2,delim3 = g.set_delims_from_language(self.language)
        self.single_comment_string = delim1
        self.start_comment_string = delim2
        self.end_comment_string = delim3

        # g.trace(self.tangle_directory)

        # For unit testing.
        return {
            "encoding"  : self.encoding,
            "language"  : self.language,
            "lineending": self.output_newline,
            "pagewidth" : self.page_width,
            "path"      : self.tangle_directory,
            "tabwidth"  : self.tab_width,
        }
    #@-node:ekr.20080923124254.16:tangle.scanAllDirectives
    #@+node:ekr.20031218072017.3599:token_type
    def token_type(self,s,i,report_errors=True):

        """This method returns a code indicating the apparent kind of token at the position i.

        The caller must determine whether section definiton tokens are valid.

        returns (kind, end) and sets global root_name using setRootFromText()."""

        kind = plain_line ; end = -1
        if self.use_noweb_flag:
            #@        << set token_type in noweb mode >>
            #@+node:ekr.20031218072017.3600:<< set token_type in noweb mode >>
            if g.match(s,i,"<<"):
                i, kind, end = self.skip_section_name(s,i)
                if kind == bad_section_name:
                    kind = plain_line # not an error.
                elif kind == at_root:
                    if self.head_root:
                        self.setRootFromText(self.head_root,report_errors)
                    else:
                        kind = bad_section_name # The warning has been given.
            elif g.match(s,i,"@ ") or g.match(s,i,"@\t") or g.match(s,i,"@\n"):
                # 10/30/02: Only @doc starts a noweb doc part in raw cweb mode.
                kind = g.choose(self.raw_cweb_flag,plain_line,at_doc)
            elif g.match(s,i,"@@"): kind = at_at
            elif i < len(s) and s[i] == '@': kind = at_other
            else: kind = plain_line
            #@-node:ekr.20031218072017.3600:<< set token_type in noweb mode >>
            #@nl
        else:
            #@        << set token_type for CWEB mode >>
            #@+node:ekr.20031218072017.3601:<< set token_type for CWEB mode >>
            i = g.skip_ws(s,i)
            if g.match(s,i,"@*") or g.match(s,i,"@ "): kind = at_doc
            elif g.match(s,i,"@<"): i, kind, end = self.skip_section_name(s,i)
            elif g.match(s,i,"@@"): kind = at_at
            elif g.match_word(s,i,"@c") or g.match_word(s,i,"@p"): kind = at_code
            elif i < len(s) and s[i] == '@':
                if   i + 1 >= len(s): kind = at_doc
                elif i + 1 < len(s) and s[i+1] not in string.ascii_letters:
                    kind = at_web
                else: kind = at_other # Set kind later
            else: kind = plain_line
            #@-node:ekr.20031218072017.3601:<< set token_type for CWEB mode >>
            #@nl
        if kind == at_other :
            #@        << set kind for directive >>
            #@+node:ekr.20031218072017.3602:<< set kind for directive >>
            # This code will return at_other for any directive other than those listed.

            if g.match_word(s,i,"@c"):
                # 10/30/02: Only @code starts a code section in raw cweb mode.
                kind = g.choose(self.raw_cweb_flag,plain_line,at_code)
            else:
                for name, theType in [
                    ("@chapter", at_chapter),
                    ("@code", at_code),
                    ("@doc", at_doc),
                    ("@root", at_root),
                    ("@section", at_section) ]:
                    if g.match_word(s,i,name):
                        kind = theType ; break

            if self.raw_cweb_flag and kind == at_other:
                # 10/30/02: Everything else is plain text in raw cweb mode.
                kind = plain_line

            if kind == at_root:
                i = self.setRootFromText(s[i:],report_errors)
            #@-node:ekr.20031218072017.3602:<< set kind for directive >>
            #@nl
        # g.trace(kind,g.get_line(s,i))
        return kind, end
    #@-node:ekr.20031218072017.3599:token_type
    #@-node:ekr.20031218072017.3576:utility methods
    #@-others

class tangleCommands (baseTangleCommands):
    """A class that implements Leo' tangle and untangle commands."""
    pass
#@-node:ekr.20031218072017.3465:class tangleCommands methods
#@-others
#@-node:ekr.20031218072017.3446:@thin leoTangle.py
#@-leo
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.