#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # Copyright (C) 2019-present ScyllaDB # # # SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.0 # import argparse import io import os import sys import os.path as path import re from enum import Enum from collections import deque ap = argparse.ArgumentParser(description='Pretty-print C++ names.') ap.add_argument('--indent', dest='indent', default=' ', help='Indentation') ap.add_argument('--break', dest='maxwidth', default=32, nargs='?', const=1, help='Break tokens longer than this parameter into multiple lines') ap.add_argument('--debug', dest='debug', type=int, default=0, nargs='?', const=1, help='Debugging level') ap.add_argument('strings', metavar='S', type=str, nargs='*', help='File(s) or strings to parse') args = ap.parse_args() def debug(s, level=1): if level <= args.debug: print('DEBUG[{}] {}'.format(level, s)) class Token: class Type(Enum): Empty = 0 Word = 1 Space = 2 Comma = 3 Open = 4 Close = 5 Compound = 6 def __init__(self, content, type, depth): self.content = content self.type = type self.depth = depth self.length = None def __repr__(self): return "Token:<{} depth={} len={} content='{}'>".format(self.type, self.depth, self.len(), self.content) def measure(self): if self.type != Token.Type.Compound: return len(self.content) l = 0 for t in self.content: l += t.len() return l def len(self): if self.length is None: self.length = self.measure() return self.length def singleline(self): return (self.type != Token.Type.Compound and self.type != Token.Type.Word) or \ self.len() <= int(args.maxwidth) def indentation(self, indent=None): if not indent: indent = args.indent return '\n' + indent * self.depth def format(self, depth=0, nested=False): debug("Format depth={} nested={} singleline={} {}".format(depth, nested, self.singleline(), self)) s = '' if self.type != Token.Type.Compound: if not self.singleline() and nested: s = self.indentation() return s + self.content if self.singleline(): for t in self.content: s += t.format(depth+1, nested=nested) return s last = None newl = False indentation = self.indentation() indent = True opened = False for t in self.content: if t.type == Token.Type.Space and \ (indent or (last and last.type == Token.Type.Comma)): continue last = t if indent and t.type != Token.Type.Comma: s += indentation indent = False s += t.format(depth=depth+1, nested=opened) if t.type == Token.Type.Comma or (opened and not t.singleline()): indent = True if t.type == Token.Type.Open: opened = True elif t.type == Token.Type.Close: opened = False return s class Parser: def __init__(self): self.delimiters = re.compile(r'(?P\s+)|(?P[\[\(\{<])|(?P[>\}\)\]])|(?P,)') self.lex_map = { 'space': Token.Type.Space, 'open': Token.Type.Open, 'close': Token.Type.Close, 'comma': Token.Type.Comma, } self.queue = None def debug_token(self, desc, t, debug_level=2): debug("{} {}".format(desc, t), debug_level) return t def lex(self, line): depth = 0 while line: m = self.delimiters.search(line) if not m: yield self.debug_token("Yield line", Token(line, Token.Type.Word, depth)) break if m.start(): yield self.debug_token("Yield", Token(line[:m.start()], Token.Type.Word, depth)) token_type = self.lex_map[m.lastgroup] if token_type == Token.Type.Close: depth -= 1 yield self.debug_token("Yield", Token(m[0], token_type, depth)) if token_type == Token.Type.Open: depth += 1 line = line[m.end():] def parse_tokens(self, depth=0): tokens = deque() while self.queue: t = self.queue[0] if t.depth < depth: if tokens and tokens[len(tokens)-1].type == Token.Type.Space: tokens.pop() break elif t.depth > depth: t = self.parse_tokens(t.depth) tokens.append(self.debug_token("Appending nested depth={}".format(depth), t, 3)) else: self.queue.popleft() tokens.append(self.debug_token("Appending depth={}".format(depth), t, 3)) if tokens and tokens[0].type == Token.Type.Space: tokens.popleft() if tokens and tokens[len(tokens)-1].type == Token.Type.Space: tokens.pop() if not tokens: ret = Token('', Token.Type.Empty, depth) elif len(tokens) == 1: ret = tokens[0] else: ret = Token(tokens, Token.Type.Compound, depth) return self.debug_token("Return depth={}".format(depth), ret) def parse(self, line): debug("Parse: '{}'".format(line), 1) self.queue = deque(Token.Parser().lex(line)) return self.parse_tokens() def parse_line(line): print(Token.Parser().parse(line).format()) def parse_file(f): for l in f: parse_line(l.strip()) if args.strings: for s in args.strings: if s == '-': parse_file(sys.stdin) elif path.exists(s): f = open(s, "r") parse_file(f) f.close() else: parse_line(s) else: parse_file(sys.stdin)