- Mihajlo Živković
- Svetozar Iković
- Petar Vuković
LINVAST prevodi izvorni kod različitih imperativnih jezika u zajednički AST model. Takav model omogućava da se analiza i verifikacija programa rade nad istom strukturom, nezavisno od konkretne sintakse ulaznog jezika.
Cilj ovog rada bio je dodavanje podrške za Python. To je podrazumevalo:
- prepoznavanje Python sintakse,
- povezivanje Python parsera sa LINVAST infrastrukturom,
- prevođenje Python konstrukcija u postojeće LINVAST čvorove,
- dodavanje novih čvorova za Python konstrukcije koje nisu bile pokrivene postojećim AST modelom,
- pokrivanje implementacije testovima.
Python se razlikuje od klasičnih C-like jezika po tome što nema eksplicitne
deklaracije promenljivih, dinamički je tipiziran, blokove određuje
uvučenošću i ima konstrukcije kao što su tuple unpacking, comprehension
izrazi, f-stringovi, yield, await i match/case.
Zadatak nije bio izvršavanje Python programa, već strukturno prevođenje Python koda u LINVAST AST.
Python podrška je uklopljena u postojeću LINVAST arhitekturu.
Projekat LINVAST sadrži osnovne apstrakcije:
ASTNode, baznu klasu svih AST čvorova,- interfejse za AST buildere,
- registraciju buildera preko atributa,
- izuzetke za sintaksne i semantičke greške,
- kopiranje, zamenu podstabala, poređenje i JSON serijalizaciju.
Svi konkretni čvorovi nasleđuju ASTNode i čuvaju decu kroz listu
Children.
Projekat LINVAST.Imperative sadrži zajedničke čvorove za imperativne
jezike:
- izraze:
IdNode,LitExprNode,ArithmExprNode,LogicExprNode,RelExprNode,FuncCallExprNode,AssignExprNode, - naredbe:
BlockStatNode,IfStatNode,WhileStatNode,ForStatNode,JumpStatNode, - deklaracije:
DeclStatNode,VarDeclNode,FuncNode,ClassNode, - pomoćne čvorove za operatore, import naredbe, nizove, rečnike i tagove.
Za Python su dodati ili prošireni čvorovi:
DeleteStatNode,GlobalStatNode,NonlocalStatNode,WithStatNode,TryStatNode,CatchClauseNode,AsyncStatNode,MatchStatNode,YieldExprNode,EllipsisLitExprNode,Pattern*Nodečvorovi zamatch/caseobrasce.
Python sintaksa se prepoznaje pomoću ANTLR parsera. Direktorijum
LINVAST.Imperative/Builders/Python/ANTLR/ sadrži:
Python3Lexer.g4,Python3Parser.g4,- generisani C# lexer,
- generisani C# parser,
- generisane visitor klase.
ANTLR pravi Python parse tree. Taj parse tree se zatim prevodi u LINVAST AST.
PythonASTBuilder je centralni modul Python implementacije. On obilazi ANTLR
parse tree i pravi LINVAST čvorove.
Implementacija je podeljena na fajlove:
PythonASTBuilder.cs- kreiranje parsera, obrada fajla i registracija za.pyekstenziju,PythonASTBuilder.Expressions.cs- izrazi, literali, pozivi, slicing, comprehension izrazi i f-stringovi,PythonASTBuilder.Statements.cs- blokovi, kontrola toka,try,with,asyncimatch/case,PythonASTBuilder.Declarations.cs- importi, dodele, anotirane dodele,del,globalinonlocal,PythonASTBuilder.Functions.cs- funkcije, parametri, lambda izrazi, dekoratori i asinhrone funkcije,PythonASTBuilder.Types.cs- klase,PythonASTBuilder.TupleUnpacking.cs- tuple/list unpacking.
Builder je registrovan za Python fajlove atributom:
[ASTBuilder(".py")]Tok obrade Python programa je:
Python kod
-> Python3Lexer
-> Python3Parser
-> Python parse tree
-> PythonASTBuilder
-> LINVAST SourceNode
- Python kod se prosleđuje ANTLR lexeru.
- Lexer proizvodi tok tokena.
- Parser pravi Python parse tree.
PythonASTBuilderobilazi parse tree.- Python pravila se prevode u LINVAST čvorove.
- Na nivou fajla, funkcija i klasa dodatno se obrađuju deklaracije.
- Rezultat je
SourceNode, koren LINVAST AST-a.
Osnovni izrazi se prevode u postojeće LINVAST čvorove:
- identifikatori u
IdNode, - literali u
LitExprNode, NoneuNullLitExprNode,- aritmetički izrazi u
ArithmExprNode, - logički izrazi u
LogicExprNode, - poređenja u
RelExprNode, - pozivi funkcija u
FuncCallExprNode, - indeksiranje u
ArrAccessExprNode.
Ulančana poređenja, kao a < f() < b, predstavljaju se kao kombinacija
relacionih i logičkih izraza. Srednji operand se kopira kao posebno podstablo
da bi svaki AST čvor imao tačno jednog roditelja.
Pošto Python nema eksplicitne deklaracije promenljivih, builder prepoznaje
prvu dodelu promenljivoj i može je predstaviti kao DeclStatNode.
x = 1
x = 2Prva naredba može postati deklaracija, a druga ostaje obična dodela.
x: int = 1Prevodi se u deklaraciju sa tipom int i inicijalizatorom 1.
Za dodele oblika:
a, b = 1, 2
a, *rest = 1, 2, 3, 4builder izdvaja ciljeve sa leve strane, vrednosti sa desne strane i starred
target ako postoji. Kada je prevod bezbedan, formira više VarDeclNode
čvorova u jednoj deklaraciji. Ako se broj elemenata ne poklapa ili je
promenljiva već deklarisana, zadržava se obična dodela.
Python funkcije se prevode u FuncNode. Parametri se čuvaju u
FuncParamsNode, tipovi iz anotacija u DeclSpecsNode, a podrazumevane
vrednosti kao inicijalizatori. Dekoratori se čuvaju kao TagNode, a
async def dobija tag async.
Python klase se prevode u ClassNode. Ime klase postaje deo TypeDeclNode,
bazne klase se čuvaju kao lista tipova, a telo klase kao skup deklaracija i
funkcija.
Kontrola toka se prevodi ovako:
if,elif,else->IfStatNode,while->WhileStatNode,for->ForStatNode,try/except/else/finally->TryStatNodeiCatchClauseNode,with->WithStatNode,async for,async with->AsyncStatNode.
Novi čvorovi su uvedeni tamo gde bi predstavljanje postojećim čvorovima izgubilo bitne informacije o Python programu.
Konstrukcije bez direktnog LINVAST čvora predstavljene su sintetičkim pozivima
samo kada ih nije moguće prirodno izraziti postojećim čvorovima. Python
comprehension izrazi se spuštaju u inicijalizaciju akumulatora i ugnježdene
ForStatNode/IfStatNode čvorove. Kada se nalaze unutar većeg izraza,
builder ih podiže u prethodne naredbe sa privremenim akumulatorom, a u
originalnom izrazu ostaje običan IdNode tog akumulatora.
- slicing ->
slice(...), await f()->await(f()),- f-string ->
format(...).
Ovim se čuva struktura programa bez uvođenja posebnog čvora za svaku Python specifičnu konstrukciju.
Za match/case su uvedeni posebni čvorovi:
MatchStatNode,CaseNode,PatternLiteralNode,PatternCaptureNode,PatternWildcardNode,PatternOrNode,PatternAsNode,PatternSequenceNode,PatternMappingNode,PatternClassNode.
Primer:
match value:
case [first, *rest] if first > 0:
result = firstprevodi se u MatchStatNode sa CaseNode granom. Obrazac [first, *rest]
postaje PatternSequenceNode, *rest postaje PatternStarNode, a uslov
postaje guard izraz u CaseNode.