%PDF- %PDF-
Direktori : /opt/cloudlinux/venv/lib/python3.11/site-packages/guppy/gsl/ |
Current File : //opt/cloudlinux/venv/lib/python3.11/site-packages/guppy/gsl/DottedTree.py |
""" Handling of tree structures given in a special 'dotted' syntax. This represents trees of nodes with strings as tags, in a readable and writable and easy to parse syntax. There are two main functions, unparse_sexpr and parse_string. When parsing, the result is by default given in 'sexpr' format: each node is a tuple of the form (tag, ) or (tag, node) or (tag, node, node) ... The following invariant is intended to hold for every node x, parse_string(unparse_sexpr(x)) == x Currently the following invariant has been tested for some strings: unparse_sexpr(parse_string(s)).strip() == s.strip() [It only holds on stripped results but may be fixed sometime.] """ class Node(object): __slots__ = 'tag', 'children', 'index', def __init__(self, tag, children, index): self.tag = tag self.children = children self.index = index def as_sexpr(self): return (self.tag,) + tuple([c.as_sexpr() for c in self.children]) def __repr__(self): return '%s(%r, %r, %r)' % ( self.__class__.__name__, self.tag, self.children, self.index) class _GLUECLAMP_: _imports_ = ( '_parent.FileIO:IO', ) ## # The name of attributes that are configurable in instances. # _chgable_ = 'node', 'dotchar' ## # The character that begins the 'dotted' indentation. dotchar = '.' ## # The character that quotes lines beginning with dots and itself. quotechar = '\\' ## # Construct a new node. # @return # This variant returns a tuple in s-expression form. # @param tag a string # @param children a sequence of nodes # @param lineindex line index of tag, not used in s-expressions def node_sexpr(self, tag, children, lineindex): return (tag,) + tuple(children) ## # Construct a new node. # @return # This variant returns a Node object. # @param tag a string # @param children a sequence of nodes # @param lineindex line index of beginning tag, first line = 0 def node_node(self, tag, children, lineindex): return Node(tag, tuple(children), lineindex) node = node_node def parse_file(self, file, src=None): return self.parse_string(self.IO.read_file(file), src) ## # Parse a dotted tree text given as a sequence of lines. # @param pos # @param tag list with first line of tag, if any # @param lineindex line index of tag # @param it iterator yielding remaining lines # @return a triple (index, nextvar, node) where # index is the index of line 'nextvar', # nextvar is the first line of next node to parse, and # node is the resulting node of this parse. def parse_iter(self, pos, tag, lineindex, it, src=None): dotchar = self.dotchar quotechar = self.quotechar children = [] firstline = lineindex while 1: try: lineindex, nextvar = next(it) except StopIteration: nextvar = None break if not nextvar.startswith(dotchar): tag.append(nextvar) else: break for (i, t) in enumerate(tag): if (t.startswith(quotechar+dotchar) or t.startswith(quotechar+quotechar+dotchar)): tag[i] = t[len(quotechar):] if tag == ['']: tag = '\n' else: tag = '\n'.join(tag) while 1: if (nextvar is None or len(nextvar) <= pos or nextvar[pos] != dotchar or not nextvar.startswith(dotchar*(pos+1))): return lineindex, nextvar, self.node(tag, children, firstline) if len(nextvar) > pos+1 and nextvar[pos+1] == dotchar: if src is None: raise SyntaxError('Level must increase with 1 max') else: src.error('Level must increase with 1 max', lineindex) lineindex, nextvar, child = self.parse_iter(pos+1, [nextvar[pos+1:]], lineindex, it, src) children.append(child) def parse_lines(self, lines, src=None): it = enumerate(lines) lineindex, nextvar, node = self.parse_iter(0, [], 0, it, src) assert nextvar is None return node def parse_string(self, string, src=None): if string: lines = string.split('\n') else: lines = [] return self.parse_lines(lines, src) ## # Unparse a tree given on Node form def unparse_node(self, node): return self.unparse_sexpr(node.as_sexpr()) ## # Unparse a tree given on sexpr form # @return a string in dotted-tree form def unparse_sexpr(self, sexpr): li = [] def unparse(depth, sexpr): li.append(self.unparse_tag(depth, sexpr[0])) for x in sexpr[1:]: unparse(depth+1, x) unparse(0, sexpr) return '\n'.join(li) def unparse_tag(self, depth, tag): dotchar, quotechar = self.dotchar, self.quotechar tag = tag.split('\n') for (i, t) in enumerate(tag): if (t.startswith(dotchar) or t.startswith(quotechar + dotchar)): tag[i] = quotechar + t tag = '\n'.join(tag) tag = dotchar*depth+tag return tag def test_1(): # Test parsing to sexpr's and back # for a variety of cases from guppy import Root dt = Root().guppy.gsl.DottedTree dt.node = dt.node_sexpr parse = dt.parse_string unparse = dt.unparse_sexpr for x, y in [ ['', ('',)], ['a', ('a',)], ['.a', ('', ('a',))], ['a\n.b', ('a', ('b',))], ['a\nb\n.c', ('a\nb', ('c',))], ["""\n.a\n..a""", ('\n', ('a', ('a',)))], ["""hello\n.a\n.b\n..ba\nx\n..bb""", ('hello', ('a',), ('b', ('ba\nx',), ('bb',)))], # Quoting dots [r'\.', ('.',)], [r'.\.', ('', ('.',))], # Preserving quote ['\\', ('\\',)], ['.\n\\', ('', ('\n\\',))], # Quoting quote-dots [r'\\.', (r'\.',)], # Preserving whitespace starting a tag # Or should it be stripped? I think better not, it would complicate transparency. [r'. tag', ('', (' tag', ))], # Preserving initial whitespace [' ', (' ',)], # Preserving initial newline ['\n', ('\n',)], ['\na', ('\na',)], # A n intended usage example [''' initial text .aspect for guppy.hsp ..returns ...type A ...latex ~\\ \..~|begincolorbox|~raw::~LaTeX~\\ ~\\ ~~~{\textbackslash}{\textbackslash}begin{\{}center{\}}~\\ .aspect for guppy.gsl ..contains DottedTree ''', ('\ninitial\ntext', ('aspect for guppy.hsp', ('returns', ('type A',), ('latex\n~\\\n..~|begincolorbox|~raw::~LaTeX~\\\n~\\\n~~~{\textbackslash}{\textbackslash}begin{\\{}center{\\}}~\\',))), ('aspect for guppy.gsl', ('contains DottedTree\n',)))] ]: z = parse(x) if y is not None: assert z == y assert unparse(z).strip() == x.strip() # Unparsing x and then parsing should give back x transparently # for any tree x, involving any combination of dots, quotes and other characters. # List of special chars and one normal chars = [dt.quotechar, dt.dotchar, '\n', ' ', 'a'] import random ## # Generate a random node with random number of children. # Shuffles the chars list to make the tag string. # @param maxchild maximum number of children def randnode(maxchild): numchild = random.randint(0, maxchild) random.shuffle(chars) tag = ''.join(chars) children = [randnode(maxchild-1) for i in range(numchild)] return dt.node(tag, children, 0) for i in range(10): y = randnode(3) x = unparse(y) z = parse(x) assert z == y def test_2(): # Test parsing to Node # that the line lineindex are correct # They start from 0, since enumerate() generates them, # It seemed inconsistent to change them to start from 1. # Which will be made in error prints. from guppy import Root dt = Root().guppy.gsl.DottedTree parse = dt.parse_string unparse = dt.unparse_node node = parse("""\ line 0 .line 1 ..line 2 line 3 .line 4 """) exp = Node('line 0', ( Node('line 1', (Node('line 2\nline 3', (), 2),), 1), Node('line 4\n', (), 4)), 0) assert repr(node) == repr(exp) def test_main(): test_1() test_2()