Knihovna LibCST umožňující snadnou modifikaci zdrojových kódů Pythonu (2. část)

14. 3. 2024
Doba čtení: 35 minut

Sdílet

Autor: © Sergey Nivens - Fotolia.com
Ve druhém článku o knihovně LibCST, která umožňuje modifikaci zdrojových kódů napsaných v Pythonu s využitím CST (derivačního stromu), si ukážeme, jak naprogramovat přejmenování proměnné, záměnu operátorů, přejmenování funkce apod.

Obsah

1. Krátké zopakování – průchod uzly CST stromu

2. Průchod atributy všech uzlů ve stromu

3. Vytištění zdrojového kódu získaného zpětným převodem z CST

4. Modifikace stromu při jeho průchodu s využitím třídy CSTTransformer

5. Speciální metody volané při vstupu či opuštění uzlů určitých typů

6. Záměna jména zvolené proměnné za jiné jméno

7. Průběh transformace, zobrazení výsledného výrazu po transformaci

8. Zobrazení rozdílů mezi původní a transformovaným kódem s využitím knihovny difflib

9. Vylepšení třídy pro přejmenování symbolů

10. Otestování funkcionality

11. Komplikovanější transformace

12. Záměna zvolených binárních aritmetických operátorů

13. Průběh transformace a tvar výsledného zdrojového kódu

14. Přejmenování jména volané funkce

15. Průběh transformace a tvar výsledného zdrojového kódu

16. Přejmenování jména funkce v její definici

17. Spuštění příkladu a ukázka modifikovaného zdrojového kódu

18. Obsah poslední části článku

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

1. Krátké zopakování – průchod uzly CST stromu

Připomeňme si nejdříve, jakým způsobem se prochází CST stromem s využitím třídy odvozené od třídy CSTVisitor. Tímto tématem jsme se podrobněji zabývali v úvodním článku o knihovně LibCST. Ze třídy CSTVisitor si odvodíme vlastní třídu nazvanou například Visitor. Tato třída bude (samozřejmě kromě konstruktoru __init__) obsahovat metody nazvané on_visit a on_leave. První z těchto metod je volána při návštěvě uzlu při průchodu CST, druhá metoda je volána naopak při opuštění tohoto uzlu, tedy ve chvíli, kdy již došlo k průchodu všemi uzlu podstromu daného uzlu (pochopitelně za předpokladu, že takový podstrom existuje):

class Visitor(CSTVisitor):
    def __init__(self):
        ...
        ...
        ...
 
    def on_visit(self, node):
        ...
        ...
        ...
        return True nebo False
 
    def on_leave(self, node):
        self.nest_level -= 1

Význam první metody on_visit spočívá v tom, že její návratovou (pravdivostní) hodnotou lze řídit, zda se má projít i všemi poduzly či nikoli (někdy nás totiž například nezajímá průchod všemi prvky výrazu či těla funkce). Metoda on_leave zde zdánlivě postrádá smysl, ale v dalších kapitolách uvidíme, že pokud nahradíme CSTVisitor za CSTTransformer, umožní nám modifikaci uzlu, jeho náhradu či v některých případech dokonce jeho odstranění ze stromu (nahradí se za sentinel).

#!/usr/bin/python
# vim: set fileencoding=utf-8
 
from libcst import parse_module, CSTVisitor
 
 
class Visitor(CSTVisitor):
    def __init__(self):
        self.nest_level = 0
 
    def on_visit(self, node):
        indent = " " * self.nest_level * 2
        print(indent, node.__class__.__name__)
        self.nest_level += 1
        return True
 
    def on_leave(self, node):
        self.nest_level -= 1
 
 
    expression = "1 + 2 * 3 - 4 / 5"
 
parsed = parse_module(expression)
visitor = Visitor()
parsed.visit(visitor)

Průchod stromem (CST) vypadá následovně (odsazení je řízeno atributem nest_level):

 Module
   SimpleStatementLine
     Expr
       BinaryOperation
         BinaryOperation
           Integer
           Add
             SimpleWhitespace
             SimpleWhitespace
           BinaryOperation
             Integer
             Multiply
               SimpleWhitespace
               SimpleWhitespace
             Integer
         Subtract
           SimpleWhitespace
           SimpleWhitespace
         BinaryOperation
           Integer
           Divide
             SimpleWhitespace
             SimpleWhitespace
           Integer
     TrailingWhitespace
       SimpleWhitespace
       Newline

2. Průchod atributy všech uzlů ve stromu

Kromě metod on_visit a on_leave je možné definovat i metody nazvané on_visit_attribute a on_leave_attribute. Jak již názvy těchto metod naznačují, jsou tyto metody zavolány pro všechny atributy každého uzlu, kterým se prochází. Kromě vlastního uzlu je těmto metodám předán i příslušný atribut. A je zde ještě jedna výjimka – návratová hodnota metody on_visit_attribute neurčuje způsob průchodu atributy – metoda se zavolá pro všechny atributy uzlu (naproti tomu návratovou hodnotou metody on_visit je možné řídit způsob průchodu stromem).

Zkusme si tedy předchozí demonstrační příklad nepatrně upravit tak, aby se procházelo všemi uzly stromu a všemi atributy uzlů:

#!/usr/bin/python
# vim: set fileencoding=utf-8
 
from libcst import parse_module, CSTVisitor
 
 
class Visitor(CSTVisitor):
    def __init__(self):
        self.nest_level = 0
 
    def on_visit(self, node):
        indent = " " * self.nest_level * 2
        print(indent, node.__class__.__name__)
        self.nest_level += 1
        return True
 
    def on_leave(self, node):
        self.nest_level -= 1
 
    def on_visit_attribute(self, node, attribute):
        indent = " " * (self.nest_level + 1) * 2
        print(indent, "-> attribute", attribute)
 
    def on_leave_attribute(self, node, attribute):
        pass
 
 
expression = "1 + 2 * 3 - 4 / 5"
 
parsed = parse_module(expression)
visitor = Visitor()
parsed.visit(visitor)

V tomto případě bude výsledek při průchodu celým stromem mnohem delší, protože prakticky každý uzel má jeden či více atributů:

 Module
     -> attribute header
     -> attribute body
   SimpleStatementLine
       -> attribute leading_lines
       -> attribute body
     Expr
         -> attribute value
       BinaryOperation
           -> attribute lpar
           -> attribute left
         BinaryOperation
             -> attribute lpar
             -> attribute left
           Integer
               -> attribute lpar
               -> attribute rpar
             -> attribute operator
           Add
               -> attribute whitespace_before
             SimpleWhitespace
               -> attribute whitespace_after
             SimpleWhitespace
             -> attribute right
           BinaryOperation
               -> attribute lpar
               -> attribute left
             Integer
                 -> attribute lpar
                 -> attribute rpar
               -> attribute operator
             Multiply
                 -> attribute whitespace_before
               SimpleWhitespace
                 -> attribute whitespace_after
               SimpleWhitespace
               -> attribute right
             Integer
                 -> attribute lpar
                 -> attribute rpar
               -> attribute rpar
             -> attribute rpar
           -> attribute operator
         Subtract
             -> attribute whitespace_before
           SimpleWhitespace
             -> attribute whitespace_after
           SimpleWhitespace
           -> attribute right
         BinaryOperation
             -> attribute lpar
             -> attribute left
           Integer
               -> attribute lpar
               -> attribute rpar
             -> attribute operator
           Divide
               -> attribute whitespace_before
             SimpleWhitespace
               -> attribute whitespace_after
             SimpleWhitespace
             -> attribute right
           Integer
               -> attribute lpar
               -> attribute rpar
             -> attribute rpar
           -> attribute rpar
         -> attribute semicolon
       -> attribute trailing_whitespace
     TrailingWhitespace
         -> attribute whitespace
       SimpleWhitespace
         -> attribute comment
         -> attribute newline
       Newline
     -> attribute footer

3. Vytištění zdrojového kódu získaného zpětným převodem z CST

Knihovna LibCST dokáže, jak jsme již ostatně viděli, převést zdrojový kód na CST (tedy na derivační strom). S tímto stromem lze provádět různé manipulace spočívající v modifikaci uzlů, přidání uzlů, odstranění uzlů a popř. úpravě atributů. A nakonec pochopitelně potřebujeme provést i opak první operace – tedy převod CST zpět na zdrojový kód, a to se zachováním formátování i poznámek. I tuto operaci pochopitelně knihovna LibCST podporuje. Zdrojový kód lze z CST vygenerovat přečtením atributu code (to není zcela přesné, protože třída CSTVisitor neumožňuje modifikace stromu, ale jak uvidíme dále, třída CSTTransformer již ano).

Pokusme se tedy upravit skript z předchozí kapitoly takovým způsobem, aby na konci vypsal i kód získaný z CST:

#!/usr/bin/python
# vim: set fileencoding=utf-8
 
from libcst import parse_module, CSTVisitor
 
 
class Visitor(CSTVisitor):
    def __init__(self):
        self.nest_level = 0
 
    def on_visit(self, node):
        indent = " " * self.nest_level * 2
        print(indent, node.__class__.__name__)
        self.nest_level += 1
        return True
 
    def on_leave(self, node):
        self.nest_level -= 1
 
    def on_visit_attribute(self, node, attribute):
        indent = " " * (self.nest_level + 1) * 2
        print(indent, "-> attribute", attribute)
 
    def on_leave_attribute(self, node, attribute):
        pass
 
 
expression = "1 + 2 * 3 - 4 / 5"
 
parsed = parse_module(expression)
visitor = Visitor()
parsed.visit(visitor)
 
print()
print("-" * 60)
print(parsed.code)
Poznámka: úplný zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/cst/gene­rate_code1.py.

Tento skript nejdříve vypíše obsah stromu tak, jak jsme to již viděli:

 Module
     -> attribute header
     -> attribute body
   SimpleStatementLine
       -> attribute leading_lines
       -> attribute body
     Expr
         -> attribute value
       BinaryOperation
           -> attribute lpar
           -> attribute left
         BinaryOperation
             -> attribute lpar
             -> attribute left
           Integer
               -> attribute lpar
               -> attribute rpar
             -> attribute operator
           Add
               -> attribute whitespace_before
             SimpleWhitespace
               -> attribute whitespace_after
             SimpleWhitespace
             -> attribute right
           BinaryOperation
               -> attribute lpar
               -> attribute left
             Integer
                 -> attribute lpar
                 -> attribute rpar
               -> attribute operator
             Multiply
                 -> attribute whitespace_before
               SimpleWhitespace
                 -> attribute whitespace_after
               SimpleWhitespace
               -> attribute right
             Integer
                 -> attribute lpar
                 -> attribute rpar
               -> attribute rpar
             -> attribute rpar
           -> attribute operator
         Subtract
             -> attribute whitespace_before
           SimpleWhitespace
             -> attribute whitespace_after
           SimpleWhitespace
           -> attribute right
         BinaryOperation
             -> attribute lpar
             -> attribute left
           Integer
               -> attribute lpar
               -> attribute rpar
             -> attribute operator
           Divide
               -> attribute whitespace_before
             SimpleWhitespace
               -> attribute whitespace_after
             SimpleWhitespace
             -> attribute right
           Integer
               -> attribute lpar
               -> attribute rpar
             -> attribute rpar
           -> attribute rpar
         -> attribute semicolon
       -> attribute trailing_whitespace
     TrailingWhitespace
         -> attribute whitespace
       SimpleWhitespace
         -> attribute comment
         -> attribute newline
       Newline
     -> attribute footer

A nakonec se vypíše zdrojový kód reprezentovaný daným stromem:

1 + 2 * 3 - 4 / 5

4. Modifikace stromu při jeho průchodu s využitím třídy CSTTransformer

Nyní se dostáváme k nejdůležitější funkcionalitě nabízené knihovnou LibCST, tj. k podpoře modifikace stromu a tím pádem i řízené modifikace zdrojového kódu, který je stromem reprezentován. Aby bylo možné CST modifikovat, musíme naši třídu odvodit nikoli ze třídy CSTVisitor, ale ze třídy CSTTransformer. V této třídě došlo ke změně významu metody on_leave. Nyní jsou totiž této metodě předány dva uzly – původní uzel a uzel, který lze modifikovat nebo nahradit za uzel jiný. Návratovou hodnotou této metody je uzel, který je vložen do CST namísto uzlu původního. Tento koncept nám umožňuje měnit atributy uzlů, náhradu uzlu za uzel jiný, změnu podstromu (k tomu se dostaneme příště), popř.  je možné uzel nahradit za sentinel a tak ho ve výsledku odstranit.

Poznámka: uzel lze tedy modifikovat při návratu z něho, nikoli při vstupu.

Předchozí demonstrační příklad nejdříve upravíme takovým způsobem, že namísto třídy Visitor odvozené od třídy CSTVisitor nadeklarujeme třídu nazvanou například Transformer a odvodíme ji od třídy CSTTransformer. Další změna spočívá v odlišných parametrech metody on_leave i v tom, že z této metody budeme vracet původní uzel (žádný strom tedy prozatím nebudeme modifikovat, i když používáme CSTTransformer):

#!/usr/bin/python
# vim: set fileencoding=utf-8
 
from libcst import parse_module, CSTTransformer
 
 
class Transformer(CSTTransformer):
    def __init__(self):
        self.nest_level = 0
 
    def on_visit(self, node):
        indent = " " * self.nest_level * 2
        print(indent, node.__class__.__name__)
        self.nest_level += 1
        return True
 
    def on_leave(self, original_node, updated_node):
        self.nest_level -= 1
        return original_node
 
    def on_visit_attribute(self, node, attribute):
        indent = " " * (self.nest_level + 1) * 2
        print(indent, "-> attribute", attribute)
 
    def on_leave_attribute(self, node, attribute):
        pass
 
 
expression = "1 + 2 * 3 - 4 / 5"
 
parsed = parse_module(expression)
transformer = Transformer()
transformed = parsed.visit(transformer)
 
print()
print("-" * 60)
print(transformed.code)
Poznámka: úplný zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/cst/gene­rate_code2.py.

Po spuštění skriptu bychom měli získat naprosto stejný výsledek, jako při použití třídy CSTVisitor:

 Module
     -> attribute header
     -> attribute body
   SimpleStatementLine
       -> attribute leading_lines
       -> attribute body
     Expr
         -> attribute value
       BinaryOperation
           -> attribute lpar
           -> attribute left
         BinaryOperation
             -> attribute lpar
             -> attribute left
           Integer
               -> attribute lpar
               -> attribute rpar
             -> attribute operator
           Add
               -> attribute whitespace_before
             SimpleWhitespace
               -> attribute whitespace_after
             SimpleWhitespace
             -> attribute right
           BinaryOperation
               -> attribute lpar
               -> attribute left
             Integer
                 -> attribute lpar
                 -> attribute rpar
               -> attribute operator
             Multiply
                 -> attribute whitespace_before
               SimpleWhitespace
                 -> attribute whitespace_after
               SimpleWhitespace
               -> attribute right
             Integer
                 -> attribute lpar
                 -> attribute rpar
               -> attribute rpar
             -> attribute rpar
           -> attribute operator
         Subtract
             -> attribute whitespace_before
           SimpleWhitespace
             -> attribute whitespace_after
           SimpleWhitespace
           -> attribute right
         BinaryOperation
             -> attribute lpar
             -> attribute left
           Integer
               -> attribute lpar
               -> attribute rpar
             -> attribute operator
           Divide
               -> attribute whitespace_before
             SimpleWhitespace
               -> attribute whitespace_after
             SimpleWhitespace
             -> attribute right
           Integer
               -> attribute lpar
               -> attribute rpar
             -> attribute rpar
           -> attribute rpar
         -> attribute semicolon
       -> attribute trailing_whitespace
     TrailingWhitespace
         -> attribute whitespace
       SimpleWhitespace
         -> attribute comment
         -> attribute newline
       Newline
     -> attribute footer
 
------------------------------------------------------------
1 + 2 * 3 - 4 / 5

5. Speciální metody volané při vstupu či opuštění uzlů určitých typů

V předchozích demonstračních příkladech jsme využívali obecné metody nazvané on_visit a on_leave. Tyto metody byly volány při návštěvě každého uzlu při traverzaci CST, popř. naopak při opouštění uzlu s tím, že právě při opouštění uzlu bylo možné provést jeho záměnu či modifikaci. Ovšem v těchto metodách bylo (většinou) nutné zjišťovat typ uzlu, tj. například to, zda se jedná o uzel reprezentující řetězec, jméno proměnné, jméno volané funkce, typ operátoru atd. A to vede ke zbytečně dlouhému a repetitivnímu kódu. Z tohoto důvodu nalezneme v knihovně LibCST ještě jednu možnost – pro každý typ uzlu jsou totiž definovány specializované metody volané pouze při vstupu či naopak výstupu z konkrétního typu uzlu.

Příkladem může být uzel typu Multiply, tedy uzel reprezentující operaci násobení. Při vstupu do tohoto uzlu se volá metoda visit_Multiply (pokud existuje) a při výstupu metoda leave_Multiply:

def visit_Multiply(self, original_node):
    print("multiply node visit")
    return True
 
def leave_Divide(self, original_node, updated_node):
    print("multiply node leave")
    return original_node

Jména všech metod volaných při vstupu do uzlu jsem extrahoval do tohoto souboru. A jména všech metod volaných naopak při opouštění uzlu daného typu naleznete zde.

6. Záměna jména zvolené proměnné za jiné jméno

Nyní již máme k dispozici všechny informace proto, abychom realizovali průchod uzly CST s tím, že pokud budeme opouštět uzel typu Name a hodnota jména bude nastavena na „foo“, nahradíme tento uzel uzlem, v němž bude hodnota jména nastavená na „bar“. Jinými slovy provedeme takovou transformaci zdrojového kódu, při níž dojde k náhradě všech identifikátorů foo na bar. Tato záměna se však nebude týkat ani řetězců, ani komentářů. Samotná realizace popsané transformace vypadá takto:

    def leave_Name(self, original_node, updated_node):
        if original_node.value == "foo":
            print("Renaming 'foo' to 'bar'")
            return updated_node.with_changes(value="bar")
        return original_node
Poznámka: povšimněte si, jak se modifikuje pouze jeden (nebo více) vybraných atributů uzlu s využitím metody with_changes. Nemusíme tedy pracně kopírovat jednotlivé atributy atd.

Transformovat přitom budeme tento výraz:

expression = "1 + foo * 3 - 4 / foo"

Celý skript, který tuto transformaci provádí, bude vypadat následovně:

#!/usr/bin/python
# vim: set fileencoding=utf-8
 
from libcst import parse_module, CSTTransformer
from libcst import SimpleWhitespace, Name
 
 
class Transformer(CSTTransformer):
    def __init__(self):
        pass
 
    def on_visit(self, node):
        print(node.__class__.__name__)
        return True
 
    def leave_Name(self, original_node, updated_node):
        if original_node.value == "foo":
            print("Renaming 'foo' to 'bar'")
            return updated_node.with_changes(value="bar")
        return original_node
 
    def on_visit_attribute(self, node, attribute):
        print("-> attribute", attribute)
 
    def on_leave_attribute(self, node, attribute):
        pass
 
 
expression = "1 + foo * 3 - 4 / foo"
 
parsed = parse_module(expression)
transformer = Transformer()
transformed = parsed.visit(transformer)
 
print()
print("-" * 60)
print(parsed.code)
print(transformed.code)
Poznámka: úplný zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/cst/gene­rate_code3.py.

7. Průběh transformace, zobrazení výsledného výrazu po transformaci

V případě, že výše uvedený skript spustíme, vypíšou se postupně informace o tom, že se prochází jednotlivými uzly i jejich atributy:

Module
-> attribute header
-> attribute body
SimpleStatementLine
-> attribute leading_lines
-> attribute body
Expr
-> attribute value
BinaryOperation
-> attribute lpar
-> attribute left
BinaryOperation
-> attribute lpar
-> attribute left
Integer
-> attribute lpar
-> attribute rpar
-> attribute operator
Add
-> attribute whitespace_before
SimpleWhitespace
-> attribute whitespace_after
SimpleWhitespace
-> attribute right
BinaryOperation
-> attribute lpar
-> attribute left
Name
-> attribute lpar
-> attribute rpar

Jakmile se opustí uzel typu Name s hodnotou jména „foo“, provede se záměna tohoto uzlu, o čemž jsme taktéž informováni:

Renaming 'foo' to 'bar'

Pokračování v průchodu CST:

-> attribute operator
Multiply
-> attribute whitespace_before
SimpleWhitespace
-> attribute whitespace_after
SimpleWhitespace
-> attribute right
Integer
-> attribute lpar
-> attribute rpar
-> attribute rpar
-> attribute rpar
-> attribute operator
Subtract
-> attribute whitespace_before
SimpleWhitespace
-> attribute whitespace_after
SimpleWhitespace
-> attribute right
BinaryOperation
-> attribute lpar
-> attribute left
Integer
-> attribute lpar
-> attribute rpar
-> attribute operator
Divide
-> attribute whitespace_before
SimpleWhitespace
-> attribute whitespace_after
SimpleWhitespace
-> attribute right
Name
-> attribute lpar
-> attribute rpar

Další přejmenování:

Renaming 'foo' to 'bar'

Skript posléze dokončí průchod CST:

-> attribute rpar
-> attribute rpar
-> attribute semicolon
-> attribute trailing_whitespace
TrailingWhitespace
-> attribute whitespace
SimpleWhitespace
-> attribute comment
-> attribute newline
Newline
-> attribute footer

V samotném závěru se vypíše původní tvar zdrojového kódu a následně tvar upravený (tedy s přejmenovanými identifikátory):

------------------------------------------------------------
1 + foo * 3 - 4 / foo
1 + bar * 3 - 4 / bar

8. Zobrazení rozdílů mezi původní a transformovaným kódem s využitím knihovny difflib

V případě, že se přes CST provádí složitější transformace kódu, nebude dostačující pouze zobrazit původní kód a pod ním kód modifikovaný. Výhodnější je v takovém případě použít zobrazení formou diffu, resp. přesněji unifikovaného diffu (viz též tento článek o nástroji Diff. V programovacím jazyku Python můžeme pro tento účel použít standardní knihovnu difflib tak, jak je to ukázáno v dalším demonstračním příkladu. Ten se od příkladu předchozího odlišuje pouze posledními příkazy, v nichž zobrazíme původní kód, pod ním modifikovaný kód a na konci rozdíly mezi oběma kódy formou unifikovaného diffu:

print(parsed.code)
print(transformed.code)
 
diff = "".join(unified_diff(parsed.code.splitlines(1), transformed.code.splitlines(1)))
print(diff)

Úplný zdrojový kód takto upraveného demonstračního příkladu vypadá následovně:

#!/usr/bin/python
# vim: set fileencoding=utf-8
 
from libcst import parse_module, CSTTransformer
from libcst import SimpleWhitespace, Name
from difflib import unified_diff
 
 
class Transformer(CSTTransformer):
    def __init__(self):
        pass
 
    def on_visit(self, node):
        print(node.__class__.__name__)
        return True
 
    def leave_Name(self, original_node, updated_node):
        if original_node.value == "foo":
            print("Renaming 'foo' to 'bar'")
            return updated_node.with_changes(value="bar")
        return original_node
 
    def on_visit_attribute(self, node, attribute):
        print("-> attribute", attribute)
 
    def on_leave_attribute(self, node, attribute):
        pass
 
 
expression = "1 + foo * 3 - 4 / foo\n"
 
parsed = parse_module(expression)
transformer = Transformer()
transformed = parsed.visit(transformer)
 
print()
print("-" * 60)
print(parsed.code)
print(transformed.code)
 
diff = "".join(unified_diff(parsed.code.splitlines(1), transformed.code.splitlines(1)))
print(diff)
Poznámka: úplný zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/cst/gene­rate_code4.py.

Výsledky získané po spuštění skriptu (pro stručnost vypustím zprávy o návštěvách jednotlivých uzlů):

------------------------------------------------------------
1 + foo * 3 - 4 / foo
 
1 + bar * 3 - 4 / bar
 
---
+++
@@ -1 +1 @@
-1 + foo * 3 - 4 / foo
+1 + bar * 3 - 4 / bar

Řádky „---“ a „+++“ začíná porovnání souborů ve formě unifikovaného diffu (což je pro jednořádkový „program“ sice poněkud zbytečné, ale v dalších kapitolách již budeme modifikovat delší zdrojové kódy).

9. Vylepšení třídy pro přejmenování symbolů

První varianta třídy určené pro přejmenování symbolů obsahovala jak původní jméno symbolu, tak i nové jméno symbolu přímo ve svém programovém kódu, což není příliš obecné:

class SymbolRenamer(CSTTransformer):
    ...
    ...
    ...
    def leave_Name(self, original_node, updated_node):
        if original_node.value == "foo":
            print("Renaming 'foo' to 'bar'")
            return updated_node.with_changes(value="bar")
        return original_node
    ...
    ...
    ...

Výhodnější a obecnější bude, aby bylo nahrazované jméno i jeho náhrada uloženy v atributech objektu:

class SymbolRenamer(CSTTransformer):
    ...
    ...
    ...
    def leave_Name(self, original_node, updated_node):
        if original_node.value == self.orig_name:
            print(f"Renaming '{self.orig_name}' to '{self.new_name}'")
            return updated_node.with_changes(value=self.new_name)
        return original_node
    ...
    ...
    ...

Příslušné atributy se nastaví v konstruktoru:

    def __init__(self, orig_name, new_name):
        self.orig_name = orig_name
        self.new_name = new_name

A samotná transformace (což je vlastně jednoduchý refaktoring) bude vyvolána takto:

transformer = SymbolRenamer("foo", "baz")
transformed = parsed.visit(transformer)

Pro úplnost si ukažme, jak bude vypadat zdrojový kód takto upraveného skriptu:

#!/usr/bin/python
# vim: set fileencoding=utf-8
 
from libcst import parse_module, CSTTransformer
from libcst import SimpleWhitespace, Name
from difflib import unified_diff
 
 
class SymbolRenamer(CSTTransformer):
    def __init__(self, orig_name, new_name):
        self.orig_name = orig_name
        self.new_name = new_name
 
    def on_visit(self, node):
        print(node.__class__.__name__)
        return True
 
    def leave_Name(self, original_node, updated_node):
        if original_node.value == self.orig_name:
            print(f"Renaming '{self.orig_name}' to '{self.new_name}'")
            return updated_node.with_changes(value=self.new_name)
        return original_node
 
    def on_visit_attribute(self, node, attribute):
        print("-> attribute", attribute)
 
    def on_leave_attribute(self, node, attribute):
        pass
 
 
expression = "1 + foo * 3 - 4 / bar\n"
 
parsed = parse_module(expression)
transformer = SymbolRenamer("foo", "baz")
transformed = parsed.visit(transformer)
 
print()
print("-" * 60)
print(parsed.code)
print(transformed.code)
 
diff = "".join(unified_diff(parsed.code.splitlines(1), transformed.code.splitlines(1)))
print(diff)
Poznámka: úplný zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/cst/gene­rate_code5.py.

10. Otestování funkcionality

Demonstrační příklad z předchozí kapitoly by se měl po svém spuštění chovat naprosto stejným způsobem jako původní třída SymbolRenamer. Opět si to můžeme velmi snadno ověřit spuštěním příslušného skriptu:

Module
-> attribute header
-> attribute body
SimpleStatementLine
-> attribute leading_lines
-> attribute body
Expr
-> attribute value
BinaryOperation
-> attribute lpar
-> attribute left
BinaryOperation
-> attribute lpar
-> attribute left
Integer
-> attribute lpar
-> attribute rpar
-> attribute operator
Add
-> attribute whitespace_before
SimpleWhitespace
-> attribute whitespace_after
SimpleWhitespace
-> attribute right
BinaryOperation
-> attribute lpar
-> attribute left
Name
-> attribute lpar
-> attribute rpar
Renaming 'foo' to 'baz'
-> attribute operator
Multiply
-> attribute whitespace_before
SimpleWhitespace
-> attribute whitespace_after
SimpleWhitespace
-> attribute right
Integer
-> attribute lpar
-> attribute rpar
-> attribute rpar
-> attribute rpar
-> attribute operator
Subtract
-> attribute whitespace_before
SimpleWhitespace
-> attribute whitespace_after
SimpleWhitespace
-> attribute right
BinaryOperation
-> attribute lpar
-> attribute left
Integer
-> attribute lpar
-> attribute rpar
-> attribute operator
Divide
-> attribute whitespace_before
SimpleWhitespace
-> attribute whitespace_after
SimpleWhitespace
-> attribute right
Name
-> attribute lpar
-> attribute rpar
-> attribute rpar
-> attribute rpar
-> attribute semicolon
-> attribute trailing_whitespace
TrailingWhitespace
-> attribute whitespace
SimpleWhitespace
-> attribute comment
-> attribute newline
Newline
-> attribute footer
 
------------------------------------------------------------
1 + foo * 3 - 4 / bar
 
1 + baz * 3 - 4 / bar
 
---
+++
@@ -1 +1 @@
-1 + foo * 3 - 4 / bar
+1 + baz * 3 - 4 / bar

11. Komplikovanější transformace

Prozatím jsme si ukázali pouze ten nejtriviálnější způsob transformace programového kódu na úrovni CST. Tato transformace spočívala v modifikaci nějakého atributu nalezeného uzlu. Ovšem v praxi lze provádět i složitější transformace, zejména pak:

  1. Náhradu jednoho typu uzlu za uzel odlišného typu. Tato operace ovšem ne vždy dává smysl, ovšem i takové příklady lze najít.
  2. Odstranění uzlu ze stromu (CST), což se provádí náhradou původního uzlu za takzvaný sentinel. Opět platí, že tato náhrada není vždy možná; záleží na konkrétním typu uzlu a na jeho umístění v CST.
  3. Namísto uzlu se do CST vloží celý podstrom, což odpovídá složitějšímu refaktoringu.

Dnes si ukážeme ještě způsob náhrady uzlu jednoho typu za uzel odlišného typu. Složitějšími transformacemi se budeme podrobněji zabývat v závěrečném článku této série.

12. Záměna zvolených binárních aritmetických operátorů

Vyzkoušejme si nyní poněkud umělý příklad. Budeme v něm provádět záměnu dvou zvolených binárních aritmetických operátorů, konkrétně operátoru součinu a operátoru podílu. To tedy znamená, že každý součin bude nahrazen za podíl a naopak každý podíl bude nahrazen součinem. Realizace takové náhrady bude v tomto případě jednoduchá, protože namísto původního uzlu vrátíme uzel odlišný (typu Multiply či Divide):

def leave_Multiply(self, original_node, updated_node):
    print("Replacing multiply by divide")
    return Divide()
 
def leave_Divide(self, original_node, updated_node):
    print("Replacing divide by multiply")
    return Multiply()
Poznámka: mohlo by se zdát, že ihned po náhradě součinu za podíl se nahradí tento podíl zpět za součin. Ovšem ve skutečnosti tomu tak není, protože náhradu uzlu provádíme ve chvíli, kdy se tento uzel již opouští a již se na toto místo CST (v daném běhu transformace) nevrátíme.

Transformaci si otestujeme na výrazu, v němž je použita jak operace součinu, tak i operace podílu:

1 + 2 * 3 - 4 / 5

Podívejme se nyní na úplný zdrojový kód skriptu, který tuto transformaci provádí:

#!/usr/bin/python
# vim: set fileencoding=utf-8
 
from libcst import parse_module, CSTTransformer
from libcst import SimpleWhitespace, Name
from libcst import Multiply, Divide
from difflib import unified_diff
 
 
class BinaryOpReplacer(CSTTransformer):
    def __init__(self):
        pass
 
    def on_visit(self, node):
        print(node.__class__.__name__)
        return True
 
    def leave_Multiply(self, original_node, updated_node):
        print("Replacing multiply by divide")
        return Divide()
 
    def leave_Divide(self, original_node, updated_node):
        print("Replacing divide by multiply")
        return Multiply()
 
    def on_visit_attribute(self, node, attribute):
        print("-> attribute", attribute)
 
    def on_leave_attribute(self, node, attribute):
        pass
 
 
expression = "1 + 2 * 3 - 4 / 5\n"
 
parsed = parse_module(expression)
transformer = BinaryOpReplacer()
transformed = parsed.visit(transformer)
 
print()
print("-" * 60)
print(parsed.code)
print(transformed.code)
 
diff = "".join(unified_diff(parsed.code.splitlines(1), transformed.code.splitlines(1)))
print(diff)
Poznámka: úplný zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/cst/gene­rate_code6.py.

13. Průběh transformace a tvar výsledného zdrojového kódu

Opět pochopitelně můžeme sledovat průběh transformace:

Module
-> attribute header
-> attribute body
SimpleStatementLine
-> attribute leading_lines
-> attribute body
Expr
-> attribute value
BinaryOperation
-> attribute lpar
-> attribute left
BinaryOperation
-> attribute lpar
-> attribute left
Integer
-> attribute lpar
-> attribute rpar
-> attribute operator
Add
-> attribute whitespace_before
SimpleWhitespace
-> attribute whitespace_after
SimpleWhitespace
-> attribute right
BinaryOperation
-> attribute lpar
-> attribute left
Integer
-> attribute lpar
-> attribute rpar
-> attribute operator
Multiply
-> attribute whitespace_before
SimpleWhitespace
-> attribute whitespace_after
SimpleWhitespace

Ve zprávách se objevuje i informace o náhradě uzlu za jiný typ:

Replacing multiply by divide
-> attribute right
Integer
-> attribute lpar
-> attribute rpar
-> attribute rpar
-> attribute rpar
-> attribute operator
Subtract
-> attribute whitespace_before
SimpleWhitespace
-> attribute whitespace_after
SimpleWhitespace
-> attribute right
BinaryOperation
-> attribute lpar
-> attribute left
Integer
-> attribute lpar
-> attribute rpar
-> attribute operator
Divide
-> attribute whitespace_before
SimpleWhitespace
-> attribute whitespace_after
SimpleWhitespace

Další náhrada, tentokrát ovšem odlišná:

Replacing divide by multiply
-> attribute right
Integer
-> attribute lpar
-> attribute rpar
-> attribute rpar
-> attribute rpar
-> attribute semicolon
-> attribute trailing_whitespace
TrailingWhitespace
-> attribute whitespace
SimpleWhitespace
-> attribute comment
-> attribute newline
Newline
-> attribute footer

Nakonec se vypíše původní tvar výrazu a jeho nový tvar (s korektně prohozenými operátory):

1 + 2 * 3 - 4 / 5
 
1 + 2 / 3 - 4 * 5

A na úplný závěr pak unifikovaný diff původního a modifikovaného zdrojového kódu:

---
+++
@@ -1 +1 @@
-1 + 2 * 3 - 4 / 5
+1 + 2 / 3 - 4 * 5

14. Přejmenování jména volané funkce

Mnohem užitečnější, než náhrada operátorů, bude mnohem častěji prováděná transformace zdrojového kódu. Ta bude spočívat ve změně jména volané funkce. Můžeme například chtít nahradit volání funkce foo za volání funkce bar. Pro tento účel postačuje do transformátoru přidat metodu leave_Call volanou při opouštění uzlu typu Call. A tento uzel v atributu func.value obsahuje jméno volané metody:

class FunctionRenamer(CSTTransformer):
    ...
    ...
    ...
    def leave_Call(self, original_node, updated_node):
        print("Function call: ", original_node.func.value)
        if original_node.func.value == self.orig_name:
            print(f"Renaming '{self.orig_name}' to '{self.new_name}'")
            return updated_node.with_changes(
                    func=Name(self.new_name))
        return original_node

Budeme nahrazovat jméno funkce v tomto kódu:

def A(m: int, n: int) -> int:
    """Ackermannova funkce."""
    if m == 0:
        return n + 1
    if n == 0:
        return A(m - 1, 1)
    return A(m - 1, A(m, n - 1))

Jméno volané funkce A nahradíme za ackermann:

parsed = parse_module(code)
transformer = FunctionRenamer("A", "ackermann")
transformed = parsed.visit(transformer)

A takto vypadá úplný tvar skriptu, který tuto transformaci provede:

#!/usr/bin/python
# vim: set fileencoding=utf-8
 
from libcst import parse_module, CSTTransformer
from libcst import SimpleWhitespace, Name
from difflib import unified_diff
 
 
class FunctionRenamer(CSTTransformer):
    def __init__(self, orig_name, new_name):
        self.orig_name = orig_name
        self.new_name = new_name
 
    def on_visit(self, node):
        return True
 
    def leave_Call(self, original_node, updated_node):
        print("Function call: ", original_node.func.value)
        if original_node.func.value == self.orig_name:
            print(f"Renaming '{self.orig_name}' to '{self.new_name}'")
            return updated_node.with_changes(
                    func=Name(self.new_name))
        return original_node
 
 
code = '''
def A(m: int, n: int) -> int:
    """Ackermannova funkce."""
    if m == 0:
        return n + 1
    if n == 0:
        return A(m - 1, 1)
    return A(m - 1, A(m, n - 1))
'''
 
parsed = parse_module(code)
transformer = FunctionRenamer("A", "ackermann")
transformed = parsed.visit(transformer)
 
print()
print("-" * 60)
print(parsed.code)
print(transformed.code)
 
diff = "".join(unified_diff(parsed.code.splitlines(1), transformed.code.splitlines(1)))
print(diff)
Poznámka: úplný zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/cst/gene­rate_code7.py.

15. Průběh transformace a tvar výsledného zdrojového kódu

Nyní se podívejme na průběh transformace zdrojového kódu ve chvíli, kdy spustíme skript z předchozí kapitoly. Nejdříve se vypíšou tři zprávy o tom, že bylo nalezeno volání funkce A a toto volání bylo nahrazeno za volání funkce ackermann:

Function call:  A
Renaming 'A' to 'ackermann'
Function call:  A
Renaming 'A' to 'ackermann'
Function call:  A
Renaming 'A' to 'ackermann'

Po těchto třech modifikacích CST se vypíše původní zdrojový kód (získaný vygenerováním z původního CST) i modifikovaný zdrojový kód (získaný vygenerováním z nového CST):

def A(m: int, n: int) -> int:
    """Ackermannova funkce."""
    if m == 0:
        return n + 1
    if n == 0:
        return A(m - 1, 1)
    return A(m - 1, A(m, n - 1))
 
 
def A(m: int, n: int) -> int:
    """Ackermannova funkce."""
    if m == 0:
        return n + 1
    if n == 0:
        return ackermann(m - 1, 1)
    return ackermann(m - 1, ackermann(m, n - 1))

Následně se zobrazí rozdíly mezi oběma kódy ve formě unifikovaného diffu. Tyto rozdíly vypadají takto:

---
+++
@@ -4,5 +4,5 @@
     if m == 0:
         return n + 1
     if n == 0:
-        return A(m - 1, 1)
-    return A(m - 1, A(m, n - 1))
+        return ackermann(m - 1, 1)
+    return ackermann(m - 1, ackermann(m, n - 1))

16. Přejmenování jména funkce v její definici

Předchozí demonstrační příklad transformoval zdrojový kód takovým způsobem, že nahradil všechny výskyty volání funkce A za volání funkce ackermann. Jenže musíme ještě nahradit i jméno této funkce přímo v její definici. K tomu můžeme využít modifikaci uzlu typu FunctionDef, a to při opouštění tohoto uzlu při traverzaci stromem. Vytvoříme si tedy metodu nazvanou leave_FunctionDef, která slouží přesně k tomuto účelu. V této metodě posléze otestujeme, zda atribut name obsahuje jméno A a pokud tomu tak je, nahradíme toto jméno za ackermann a vrátíme takto upravený uzel:

class FunctionRenamer(CSTTransformer):
    ...
    ...
    ...
 
    def leave_FunctionDef(self, original_node, updated_node):
        print("Function definition: ", original_node.name.value)
        if original_node.name.value == self.orig_name:
            print(f"Renaming '{self.orig_name}' to '{self.new_name}'")
            return updated_node.with_changes(
                    name=Name(self.new_name))
        return original_node
 
    ...
    ...
    ...

Úplný zdrojový kód takto upraveného příkladu vypadá následovně:

#!/usr/bin/python
# vim: set fileencoding=utf-8
 
from libcst import parse_module, CSTTransformer
from libcst import SimpleWhitespace, Name
from difflib import unified_diff
 
 
class FunctionRenamer(CSTTransformer):
    def __init__(self, orig_name, new_name):
        self.orig_name = orig_name
        self.new_name = new_name
 
    def on_visit(self, node):
        return True
 
    def leave_FunctionDef(self, original_node, updated_node):
        print("Function definition: ", original_node.name.value)
        if original_node.name.value == self.orig_name:
            print(f"Renaming '{self.orig_name}' to '{self.new_name}'")
            return updated_node.with_changes(
                    name=Name(self.new_name))
        return original_node
 
    def leave_Call(self, original_node, updated_node):
        print("Function call: ", original_node.func.value)
        if original_node.func.value == self.orig_name:
            print(f"Renaming '{self.orig_name}' to '{self.new_name}'")
            return updated_node.with_changes(
                    func=Name(self.new_name))
        return original_node
 
 
code = '''
def A(m: int, n: int) -> int:
    """Ackermannova funkce."""
    if m == 0:
        return n + 1
    if n == 0:
        return A(m - 1, 1)
    return A(m - 1, A(m, n - 1))
'''
 
parsed = parse_module(code)
transformer = FunctionRenamer("A", "ackermann")
transformed = parsed.visit(transformer)
 
print()
print("-" * 60)
print(parsed.code)
print(transformed.code)
 
diff = "".join(unified_diff(parsed.code.splitlines(1), transformed.code.splitlines(1)))
print(diff)
Poznámka: úplný zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/cst/gene­rate_code8.py.

17. Spuštění příkladu a ukázka modifikovaného zdrojového kódu

Pokud transformaci kódu popsanou v předchozí kapitole spustíme, vypíšou se nejdříve zprávy o tom, že se našly a modifikovaly tři volání funkce A a že se tato volání nahradila za funkci ackermann. Dále, na posledním místě, je zobrazena zpráva o nalezení definice funkce A s tím, že i toto jméno bylo nahrazeno. Proč se ovšem tato zpráva vypíše jako poslední? V kódu totiž reagujeme na operaci opuštění uzlu, nikoli vstupu do uzlu (a nejdříve se opustí všechny poduzly a až poté nadřazený uzel):

Function call:  A
Renaming 'A' to 'ackermann'
Function call:  A
Renaming 'A' to 'ackermann'
Function call:  A
Renaming 'A' to 'ackermann'
Function definition:  A
Renaming 'A' to 'ackermann'

Poté se vypíše originální zdrojový kód získaný zpětnou transformací původního CST:

def A(m: int, n: int) -> int:
    """Ackermannova funkce."""
    if m == 0:
        return n + 1
    if n == 0:
        return A(m - 1, 1)
    return A(m - 1, A(m, n - 1))

A následně se vypíše kód získaný zpětnou transformací nového CST. Povšimněte si, že došlo k náhradě jména funkce ve všech částech programu:

def ackermann(m: int, n: int) -> int:
    """Ackermannova funkce."""
    if m == 0:
        return n + 1
    if n == 0:
        return ackermann(m - 1, 1)
    return ackermann(m - 1, ackermann(m, n - 1))

A v posledním kroku se zobrazí rozdíly mezi původním kódem a kódem novým; používá se zde unifikovaný diff:

bitcoin školení listopad 24

---
+++
@@ -1,8 +1,8 @@
 
-def A(m: int, n: int) -> int:
+def ackermann(m: int, n: int) -> int:
     """Ackermannova funkce."""
     if m == 0:
         return n + 1
     if n == 0:
-        return A(m - 1, 1)
-    return A(m - 1, A(m, n - 1))
+        return ackermann(m - 1, 1)
+    return ackermann(m - 1, ackermann(m, n - 1))

18. Obsah poslední části článku

Ve třetí a současně i závěrečné části článku o knihovně LibCST se seznámíme s některými složitějšími transformacemi kódu. Transformace, které jsme prováděli až doposud, totiž nepotřebovaly znát kontext (náhrady se prováděly pro celý zdrojový kód bez ohledu na předchozí transformace atd.). Ovšem v praxi si s těmito typy transformací nevystačíme, takže se příště podíváme na sice složitější, ale o to užitečnější realizaci sofistikovanějšího refaktoringu.

19. Repositář s demonstračními příklady

Zdrojové kódy všech prozatím popsaných demonstračních příkladů určených pro programovací jazyk Python 3 a knihovnu libcst byly uloženy do Git repositáře dostupného na adrese https://github.com/tisnik/most-popular-python-libs:

# Demonstrační příklad Stručný popis příkladu Cesta
1 parse_constant1.py parsing kódu obsahujícího celočíselnou konstantu https://github.com/tisnik/most-popular-python-libs/blob/master/cst/parse_con­stant1.py
2 parse_constant2.py parsing kódu obsahujícího celočíselnou konstantu s podtržítkem ve funkci oddělovače řádů https://github.com/tisnik/most-popular-python-libs/blob/master/cst/parse_con­stant2.py
3 parse_constant3.py parsing kódu obsahujícího logickou konstantu https://github.com/tisnik/most-popular-python-libs/blob/master/cst/parse_con­stant3.py
4 parse_constant4.py parsing kódu obsahujícího řetězcový literál https://github.com/tisnik/most-popular-python-libs/blob/master/cst/parse_con­stant4.py
5 parse_constant5.py parsing kódu obsahujícího komplexní číslo https://github.com/tisnik/most-popular-python-libs/blob/master/cst/parse_con­stant5.py
       
6 parse_expression1.py parsing jednoduchého aritmetického výrazu https://github.com/tisnik/most-popular-python-libs/blob/master/cst/parse_ex­pression1.py
7 parse_expression2.py parsing jednoduchého aritmetického výrazu s mezerami mezi operandy a operátorem https://github.com/tisnik/most-popular-python-libs/blob/master/cst/parse_ex­pression2.py
8 parse_expression3.py parsing výrazu, před nímž je zapsána mezera https://github.com/tisnik/most-popular-python-libs/blob/master/cst/parse_ex­pression3.py
9 parse_expression4.py parsing složitějšího aritmetického výrazu https://github.com/tisnik/most-popular-python-libs/blob/master/cst/parse_ex­pression4.py
10 parse_expression5.py výraz obalený závorkami https://github.com/tisnik/most-popular-python-libs/blob/master/cst/parse_ex­pression5.py
       
11 parse_module1.py parsing celého modulu, který obsahuje jediný výraz, první příklad https://github.com/tisnik/most-popular-python-libs/blob/master/cst/parse_mo­dule1.py
12 parse_module2.py parsing celého modulu, který obsahuje jediný výraz, druhý příklad https://github.com/tisnik/most-popular-python-libs/blob/master/cst/parse_mo­dule2.py
13 parse_module3.py parsing celého modulu, který obsahuje jediný výraz, třetí příklad https://github.com/tisnik/most-popular-python-libs/blob/master/cst/parse_mo­dule3.py
14 parse_module4.py parsing celého modulu s definicí funkce https://github.com/tisnik/most-popular-python-libs/blob/master/cst/parse_mo­dule4.py
       
15 traverse_code1.py průchod CST stromem, nejjednodušší řešení https://github.com/tisnik/most-popular-python-libs/blob/master/cst/traver­se_code1.py
16 traverse_code2.py průchod CST stromem, vizualizace zanoření uzlů https://github.com/tisnik/most-popular-python-libs/blob/master/cst/traver­se_code2.py
17 traverse_code3.py průchod CST stromem, test na typ uzlů https://github.com/tisnik/most-popular-python-libs/blob/master/cst/traver­se_code3.py
18 traverse_code4.py průchod CST stromem, filtrace uzlů podle jejich typu https://github.com/tisnik/most-popular-python-libs/blob/master/cst/traver­se_code4.py
19 traverse_code5.py průchod CST stromem, ještě výraznější filtrace uzlů podle jejich typu https://github.com/tisnik/most-popular-python-libs/blob/master/cst/traver­se_code5.py
20 traverse_code6.py průchod CST stromem, výpis atributů jednotlivých uzlů https://github.com/tisnik/most-popular-python-libs/blob/master/cst/traver­se_code6.py
       
21 generate_code1.py použití třídy odvozené od CSTVisitor pro průchod stromem, výpis zpětně vygenerovaného zdrojového kódu https://github.com/tisnik/most-popular-python-libs/blob/master/cst/gene­rate_code1.py
22 generate_code2.py použití třídy odvozené od CSTTransformer pro průchod stromem a konstrukcí stromu nového https://github.com/tisnik/most-popular-python-libs/blob/master/cst/gene­rate_code2.py
23 generate_code3.py transformace kódu – modifikace zvoleného jména na jméno odlišné https://github.com/tisnik/most-popular-python-libs/blob/master/cst/gene­rate_code3.py
24 generate_code4.py vylepšený výpis rozdílů mezi původním a novým zdrojovým kódem (unified_diff) https://github.com/tisnik/most-popular-python-libs/blob/master/cst/gene­rate_code4.py
25 generate_code5.py vylepšený zápis transformace stromu se změnou jména symbolu https://github.com/tisnik/most-popular-python-libs/blob/master/cst/gene­rate_code5.py
26 generate_code6.py transformace stromu: záměna binárních operátorů https://github.com/tisnik/most-popular-python-libs/blob/master/cst/gene­rate_code6.py
27 generate_code7.py transformace stromu: změna jména vybrané volané funkce https://github.com/tisnik/most-popular-python-libs/blob/master/cst/gene­rate_code7.py
28 generate_code8.py transformace stromu: změna jména definované funkce i volané funkce https://github.com/tisnik/most-popular-python-libs/blob/master/cst/gene­rate_code8.py

20. Odkazy na Internetu

  1. Lexikální a syntaktická analýza zdrojových kódů programovacího jazyka Python
    https://www.root.cz/clanky/lexikalni-a-syntakticka-analyza-zdrojovych-kodu-programovaciho-jazyka-python/
  2. Lexikální a syntaktická analýza zdrojových kódů programovacího jazyka Python (2.část)
    https://www.root.cz/clanky/lexikalni-a-syntakticka-analyza-zdrojovych-kodu-programovaciho-jazyka-python-2-cast/
  3. Lexikální a syntaktická analýza zdrojových kódů programovacího jazyka Python (3.část)
    https://www.root.cz/clanky/lexikalni-a-syntakticka-analyza-zdrojovych-kodu-programovaciho-jazyka-python-3-cast/
  4. Lexikální a syntaktická analýza zdrojových kódů jazyka Python (4.část)
    https://www.root.cz/clanky/lexikalni-a-syntakticka-analyza-zdrojovych-kodu-jazyka-python-4-cast/
  5. Knihovna LibCST umožňující snadnou modifikaci zdrojových kódů Pythonu
    https://www.root.cz/clanky/knihovna-libcst-umoznujici-snadnou-modifikaci-zdrojovych-kodu-pythonu/
  6. LibCST – dokumentace
    https://libcst.readthedoc­s.io/en/latest/index.html
  7. libCST na PyPi
    https://pypi.org/project/libcst/
  8. libCST na GitHubu
    https://github.com/Instagram/LibCST
  9. Inside The Python Virtual Machine
    https://leanpub.com/insidet­hepythonvirtualmachine
  10. module-py_compile
    https://docs.python.org/3­.8/library/py_compile.html
  11. Given a python .pyc file, is there a tool that let me view the bytecode?
    https://stackoverflow.com/qu­estions/11141387/given-a-python-pyc-file-is-there-a-tool-that-let-me-view-the-bytecode
  12. The structure of .pyc files
    https://nedbatchelder.com/blog/200804/the_str­ucture_of_pyc_files.html
  13. Python Bytecode: Fun With Dis
    http://akaptur.github.io/blog/2013/08/14/pyt­hon-bytecode-fun-with-dis/
  14. Python's Innards: Hello, ceval.c!
    http://tech.blog.aknin.na­me/category/my-projects/pythons-innards/
  15. Byterun
    https://github.com/nedbat/byterun
  16. Python Byte Code Instructions
    http://document.ihg.uni-duisburg.de/Documentation/Pyt­hon/lib/node56.html
  17. Python Byte Code Instructions
    https://docs.python.org/3­.2/library/dis.html#python-bytecode-instructions
  18. dis – Python module
    https://docs.python.org/2/li­brary/dis.html
  19. Comparison of Python virtual machines
    http://polishlinux.org/ap­ps/cli/comparison-of-python-virtual-machines/
  20. O-code
    http://en.wikipedia.org/wiki/O-code_machine
  21. Abstract syntax tree
    https://en.wikipedia.org/wi­ki/Abstract_syntax_tree
  22. Lexical analysis
    https://en.wikipedia.org/wi­ki/Lexical_analysis
  23. Parser
    https://en.wikipedia.org/wi­ki/Parsing#Parser
  24. Parse tree
    https://en.wikipedia.org/wi­ki/Parse_tree
  25. Derivační strom
    https://cs.wikipedia.org/wi­ki/Deriva%C4%8Dn%C3%AD_strom
  26. Python doc: ast — Abstract Syntax Trees
    https://docs.python.org/3/li­brary/ast.html
  27. Python doc: tokenize — Tokenizer for Python source
    https://docs.python.org/3/li­brary/tokenize.html
  28. SymbolTable
    https://docs.python.org/3­.8/library/symtable.html
  29. 5 Amazing Python AST Module Examples
    https://www.pythonpool.com/python-ast/
  30. Intro to Python ast Module
    https://medium.com/@wshanshan/intro-to-python-ast-module-bbd22cd505f7
  31. Golang AST Package
    https://golangdocs.com/golang-ast-package
  32. AP8, IN8 Regulární jazyky
    http://statnice.dqd.cz/home:inf:ap8
  33. AP9, IN9 Konečné automaty
    http://statnice.dqd.cz/home:inf:ap9
  34. AP10, IN10 Bezkontextové jazyky
    http://statnice.dqd.cz/home:inf:ap10
  35. AP11, IN11 Zásobníkové automaty, Syntaktická analýza
    http://statnice.dqd.cz/home:inf:ap11
  36. Introduction to YACC
    https://www.geeksforgeeks­.org/introduction-to-yacc/
  37. Introduction of Lexical Analysis
    https://www.geeksforgeeks­.org/introduction-of-lexical-analysis/?ref=lbp
  38. Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů
    https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu/
  39. Pygments – Python syntax highlighter
    http://pygments.org/
  40. Pygments (dokumentace)
    http://pygments.org/docs/
  41. Write your own filter
    http://pygments.org/docs/fil­terdevelopment/
  42. Write your own lexer
    http://pygments.org/docs/le­xerdevelopment/
  43. Write your own formatter
    http://pygments.org/docs/for­matterdevelopment/
  44. Jazyky podporované knihovnou Pygments
    http://pygments.org/languages/
  45. Pygments FAQ
    http://pygments.org/faq/
  46. Compiler Construction/Lexical analysis
    https://en.wikibooks.org/wi­ki/Compiler_Construction/Le­xical_analysis
  47. Compiler Design – Lexical Analysis
    https://www.tutorialspoin­t.com/compiler_design/com­piler_design_lexical_analy­sis.htm
  48. Lexical Analysis – An Intro
    https://www.scribd.com/do­cument/383765692/Lexical-Analysis
  49. Python AST Visualizer
    https://github.com/pombredanne/python-ast-visualizer
  50. What is an Abstract Syntax Tree
    https://blog.bitsrc.io/what-is-an-abstract-syntax-tree-7502b71bde27
  51. Why is AST so important
    https://medium.com/@obernar­dovieira/why-is-ast-so-important-b1e7d6c29260
  52. Emily Morehouse-Valcarcel – The AST and Me – PyCon 2018
    https://www.youtube.com/wat­ch?v=XhWvz4dK4ng
  53. Python AST Parsing and Custom Linting
    https://www.youtube.com/wat­ch?v=OjPT15y2EpE
  54. Chase Stevens – Exploring the Python AST Ecosystem
    https://www.youtube.com/wat­ch?v=Yq3wTWkoaYY
  55. Full Grammar specification
    https://docs.python.org/3/re­ference/grammar.html
ikonka

Zajímá vás toto téma? Chcete se o něm dozvědět víc?

Objednejte si upozornění na nově vydané články do vašeho mailu. Žádný článek vám tak neuteče.

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.