2016-06-13 65 views

risposta

4

Utilizzare il tokenize library cercare token.OP tokens, dove il secondo elemento è un ;*. Sostituisci questi token con un token.NEWLINE token.

Avresti bisogno di regolare i tuoi offset di token e generare indentazione di corrispondenza troppo però; quindi dopo un NEWLINE devi regolare i numeri di riga (incrementa di un offset incrementato per ogni NEWLINE che inserisci) e la riga 'next' (resto della riga corrente) dovrebbe avere gli indici aggiustati per corrispondere all'indentazione corrente livello:

import tokenize 

TokenInfo = getattr(tokenize, 'TokenInfo', lambda *a: a) # Python 3 compat 

def semicolon_to_newline(tokens): 
    line_offset = 0 
    last_indent = None 
    col_offset = None # None or an integer 
    for ttype, tstr, (slno, scol), (elno, ecol), line in tokens: 
     slno, elno = slno + line_offset, elno + line_offset 
     if ttype in (tokenize.INDENT, tokenize.DEDENT): 
      last_indent = ecol # block is indented to this column 
     elif ttype == tokenize.OP and tstr == ';': 
      # swap out semicolon with a newline 
      ttype = tokenize.NEWLINE 
      tstr = '\n' 
      line_offset += 1 
      if col_offset is not None: 
       scol, ecol = scol - col_offset, ecol - col_offset 
      col_offset = 0 # next tokens should start at the current indent 
     elif col_offset is not None: 
      if not col_offset: 
       # adjust column by starting column of next token 
       col_offset = scol - last_indent 
      scol, ecol = scol - col_offset, ecol - col_offset 
      if ttype == tokenize.NEWLINE: 
       col_offset = None 
     yield TokenInfo(
      ttype, tstr, (slno, scol), (elno, ecol), line) 

with open(sourcefile, 'r') as source, open(destination, 'w') as dest: 
    generator = tokenize.generate_tokens(source.readline) 
    dest.write(tokenize.untokenize(semicolon_to_newline(generator))) 

Nota che non si preoccupano di correggere il valore line; è solo informativo, i dati che sono stati letti dal file non vengono effettivamente utilizzati durante la tokenizzazione.

Demo:

>>> from io import StringIO 
>>> source = StringIO('''\ 
... def main(): 
...  a = "a;b"; return a 
... ''') 
>>> generator = tokenize.generate_tokens(source.readline) 
>>> result = tokenize.untokenize(semicolon_to_newline(generator)) 
>>> print(result) 
def main(): 
    a = "a;b" 
    return a 

e un po 'più complesso:

>>> source = StringIO('''\ 
... class Foo(object): 
...  def bar(self): 
...   a = 10; b = 11; c = 12 
...   if self.spam: 
...    x = 12; return x 
...   x = 15; return y 
... 
...  def baz(self): 
...   return self.bar; 
...   # note, nothing after the semicolon 
... ''') 
>>> generator = tokenize.generate_tokens(source.readline) 
>>> result = tokenize.untokenize(semicolon_to_newline(generator)) 
>>> print(result) 
class Foo(object): 
    def bar(self): 
     a = 10 
     b = 11 
     c = 12 
     if self.spam: 
      x = 12 
      return x 
     x = 15 
     return y 

    def baz(self): 
     return self.bar 

     # note, nothing after the semicolon 

>>> print(result.replace(' ', '.')) 
class.Foo(object): 
....def.bar(self): 
........a.=.10 
........b.=.11 
........c.=.12 
........if.self.spam: 
............x.=.12 
............return.x 
........x.=.15 
........return.y 

....def.baz(self): 
........return.self.bar 
........ 
........#.note,.nothing.after.the.semicolon 

* La Python 3 versione di tokenize uscite più informativi TokenInfo tuple di nome, che hanno un attributo aggiuntivo exact_type che può essere utilizzato al posto di una corrispondenza testuale: tok.exact_type == tokenize.SEMI. Ho mantenuto il precedente compatibile con Python 2 e 3 comunque.

1

Ecco una soluzione pyparsing - vedi commenti nel codice qui sotto:

from pyparsing import Literal, restOfLine, quotedString, pythonStyleComment, line 

SEMI = Literal(';') 
patt = SEMI + restOfLine 
patt.ignore(quotedString) 
patt.ignore(pythonStyleComment) 

def split_at(s, locs): 
    """ 
    break up s into pieces, given list of break locations 
    """ 
    current = 0 
    ret = [] 
    for loc in locs: 
     ret.append(s[current:loc].lstrip()) 
     current = loc+1 
    ret.append(s[current:].lstrip()) 
    return ret 

def split_on_semicolon(s,l,tokens): 
    """ 
    parse time callback, when finding first unquoted ';' on a line 
    """ 
    current_line = line(l,s) 
    line_body = current_line.lstrip() 
    indent = current_line.index(line_body) 
    indent = current_line[:indent] 

    # may be more than one ';' on this line, find them all 
    # (the second token contains everything after the ';') 
    remainder = tokens[1] 
    if remainder.strip(): 
     all_semis = [s for _,s,_ in SEMI.scanString(remainder)] 

     # break line into pieces 
     pieces = split_at(remainder, all_semis) 

     # rejoin pieces, with leading indents 
     return '\n'+'\n'.join(indent+piece for piece in pieces) 
    else: 
     return '' 

patt.addParseAction(split_on_semicolon) 

sample = """ 
def main(): 
    this_semi_does_nothing(); 
    neither_does_this_but_there_are_spaces_afterward(); 
    a = "a;b"; return a # this is a comment; it has a semicolon! 

def b(): 
    if False: 
     z=1000;b("; in quotes"); c=200;return z 
    return ';' 

class Foo(object): 
    def bar(self): 
     '''a docstring; with a semicolon''' 
     a = 10; b = 11; c = 12 

     # this comment; has several; semicolons 
     if self.spam: 
      x = 12; return x # so; does; this; one 
     x = 15;;; y += x; return y 

    def baz(self): 
     return self.bar 
""" 
print(patt.transformString(sample)) 

Dà:

def main(): 
    this_semi_does_nothing() 
    neither_does_this_but_there_are_spaces_afterward() 
    a = "a;b" 
    return a # this is a comment; it has a semicolon! 

def b(): 
    if False: 
     z=1000 
     b("; in quotes") 
     c=200 
     return z 
    return ';' 

class Foo(object): 
    def bar(self): 
     '''a docstring; with a semicolon''' 
     a = 10 
     b = 11 
     c = 12 

     # this comment; has several; semicolons 
     if self.spam: 
      x = 12 
      return x # so; does; this; one 
     x = 15 
     y += x 


     return y 

    def baz(self): 
     return self.bar 
+0

A cura di aggiungere un po ';' s nei commenti, e alcuni casi patologici con ripetuti '; 'S. – PaulMcG