Как да генерирам контролен поток на AST (представен в XML) с помощта на Python?

Имам AST на езика WHILE (http://www.program-analysis.com/while.html), присъстващ в XML формат. В момента не обработвам извиквания на функции или рекурсия. Трябва да генерирам контролния поток за тази програма.

Примерна програма (числата след // показват етикети, генерирани от анализатора):

begin

x:=1;        // 1
z:= 2+x;     // 2
x  := x+z;   // 3
y:=z-x+z;    // 4
w:=x+y+z;    // 5

while(not (y<z)) {   // 12
    x:=x+1;          // 6
    if (w <=x) {     // 9
        w:= w-x; // 7
    }
    else {
        w:=w+x;   // 8
    }
    z:=z-1;          // 10
    y:=y+1;          // 11
}

x:=z+y;              // 13
w:=x;                // 14

end

AST за гореспоменатата програма е представена като:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<program>
    <assignment label="1" variable="x">
        <value>
            <number value="1"/>
        </value>
    </assignment>
    <assignment label="2" variable="z">
        <value>
            <binary operator="+">
                <left>
                    <number value="2"/>
                </left>
                <right>
                    <variable name="x"/>
                </right>
            </binary>
        </value>
    </assignment>
    <assignment label="3" variable="x">
        <value>
            <binary operator="+">
                <left>
                    <variable name="x"/>
                </left>
                <right>
                    <variable name="z"/>
                </right>
            </binary>
        </value>
    </assignment>
    <assignment label="4" variable="y">
        <value>
            <binary operator="+">
                <left>
                    <binary operator="-">
                        <left>
                            <variable name="z"/>
                        </left>
                        <right>
                            <variable name="x"/>
                        </right>
                    </binary>
                </left>
                <right>
                    <variable name="z"/>
                </right>
            </binary>
        </value>
    </assignment>
    <assignment label="5" variable="w">
        <value>
            <binary operator="+">
                <left>
                    <binary operator="+">
                        <left>
                            <variable name="x"/>
                        </left>
                        <right>
                            <variable name="y"/>
                        </right>
                    </binary>
                </left>
                <right>
                    <variable name="z"/>
                </right>
            </binary>
        </value>
    </assignment>
    <while condition-label="12">
        <condition>
            <not>
                <binary operator="&lt;">
                    <left>
                        <variable name="y"/>
                    </left>
                    <right>
                        <variable name="z"/>
                    </right>
                </binary>
            </not>
        </condition>
        <body>
            <assignment label="6" variable="x">
                <value>
                    <binary operator="+">
                        <left>
                            <variable name="x"/>
                        </left>
                        <right>
                            <number value="1"/>
                        </right>
                    </binary>
                </value>
            </assignment>
            <if condition-label="9">
                <condition>
                    <binary operator="&lt;=">
                        <left>
                            <variable name="w"/>
                        </left>
                        <right>
                            <variable name="x"/>
                        </right>
                    </binary>
                </condition>
                <true-branch>
                    <assignment label="7" variable="w">
                        <value>
                            <binary operator="-">
                                <left>
                                    <variable name="w"/>
                                </left>
                                <right>
                                    <variable name="x"/>
                                </right>
                            </binary>
                        </value>
                    </assignment>
                </true-branch>
                <false-branch>
                    <assignment label="8" variable="w">
                        <value>
                            <binary operator="+">
                                <left>
                                    <variable name="w"/>
                                </left>
                                <right>
                                    <variable name="x"/>
                                </right>
                            </binary>
                        </value>
                    </assignment>
                </false-branch>
            </if>
            <assignment label="10" variable="z">
                <value>
                    <binary operator="-">
                        <left>
                            <variable name="z"/>
                        </left>
                        <right>
                            <number value="1"/>
                        </right>
                    </binary>
                </value>
            </assignment>
            <assignment label="11" variable="y">
                <value>
                    <binary operator="+">
                        <left>
                            <variable name="y"/>
                        </left>
                        <right>
                            <number value="1"/>
                        </right>
                    </binary>
                </value>
            </assignment>
        </body>
    </while>
    <assignment label="13" variable="x">
        <value>
            <binary operator="+">
                <left>
                    <variable name="z"/>
                </left>
                <right>
                    <variable name="y"/>
                </right>
            </binary>
        </value>
    </assignment>
    <assignment label="14" variable="w">
        <value>
            <variable name="x"/>
        </value>
    </assignment>
</program>

Трябва да генерирам контролния поток на програмата.

Контролният поток за гореспоменатата програма е следният:

1->2,
2->3,
3->4,
4->5,
5->12,
12->6,
12->13,
11->12,
6->9 ,
9->7,
9->8,
7->10,
8->10,
10->11,
13->14.

Забележка: докато може да има вложени изрази if и оператори while в него и обратно. За предпочитане търся общо решение в Python/Java/C.

Благодаря предварително, Рой


person user916315    schedule 16.02.2012    source източник
comment
Само едно наблюдение: не съм сигурен, че бихте могли да поискате помощ в по-разнообразен набор от езици... Python: интерпретиран и динамично строго въведен, Java компилиран и статично силно въведен, C компилиран статично, но сравнително слабо въведен ... Изобщо не е критика; всъщност е страхотно, защото означава, че много повече хора могат да помогнат, но ме кара да се интересувам какво се опитвате да направите. :)   -  person Chris Pfohl    schedule 16.02.2012
comment
Ъмм... Java: защото много разработчици, които познавам, се кълнат в нея Python: защото мисля, че е забавно и съм почти сигурен, че същият фрагмент, който Дан е написал, би се превел на около 100+ реда на Java, включително обекти и класове. C: защото това беше нещо, което научих преди години и е относително лесно за четене. Можете да посочите други езици като Scheme, Lisp, C#, но мисля, че всички те попадат в една от тези категории.   -  person user916315    schedule 16.02.2012


Отговори (3)


Ето едно възможно решение. Той връща дъгите в малко по-различен ред от вашия пример, но това не би трябвало да има значение.

from xml.dom import minidom
dom = minidom.parse('test1.wl.xml')


def print_arcs(from_list, to_list):
    '''
    Print arcs from every member of the from list, to every member of
    the to list
    '''
    for source in from_list:
        for target in to_list:
            print "%s -> %s" % (source, target)

def parse(node, came_from):
    '''
    Descend an XML structure representing an AST
    '''
    if not node:
        return came_from

    if node.nodeName=="#text":
        return parse(node.nextSibling, came_from)

    if node.nodeName=="program":
        return parse(node.firstChild, came_from)

    if node.nodeName=="assignment":
        this = node.getAttribute('label')
        print_arcs(came_from, [this])
        return parse(node.nextSibling, [this])

    if node.nodeName=="while":
        loop_start = node.getAttribute('condition-label')
        print_arcs(came_from, [loop_start])
        next = [loop_start]
        for s in node.childNodes:
            if s.nodeName=="body":
                loop_end = parse(s, [loop_start])
                print_arcs(loop_end, [loop_start])
        return parse(node.nextSibling, next)

    if node.nodeName=="if":
        if_start = node.getAttribute('condition-label')
        print_arcs(came_from, [if_start])
        next = []
        for s in node.childNodes:
            if s.nodeName=="#text":
                continue
            item = parse(s, [if_start])
            if item:
                next.extend(item)
        return parse(node.nextSibling, next)

    if node.nodeName=="condition":
        return None

    if node.nodeName=="true-branch":
        return parse(node.firstChild, came_from)

    if node.nodeName=="false-branch":
        return parse(node.firstChild, came_from)

    if node.nodeName=="body":
        return parse(node.firstChild, came_from)


parse(dom.firstChild, [])

Това се рекурсира през структурата на AST и неговият изход зависи от вида на срещания възел. Едно присвояване просто извежда дъги от предишния възел(и) към текущия възел; if се нуждае от дъги към двете възможности, а while се нуждае от дъги, представящи цикъла и възможното падане. Даденият код поддържа списък откъде е могло да дойде изпълнението, за да стигне до текущото местоположение. Функцията parse връща местоположението, което завършва текущия блок.

Обърнете внимание, че прилагането на while и if тук е малко хакерско и ще падне при определени видове синтактични грешки.

person Dan    schedule 16.02.2012
comment
Дан! Ти си страхотен! Работи перфектно! :D Как да го отбележа като полезен отговор, моля помогнете! ЗАБЕЛЕЖКА: Не мислите ли, че етикетът: 11-13 не е правилен? - person user916315; 16.02.2012
comment
Добра точка! Краят на цикъла while преминава 11-›12-›13, така че не трябва да има директна дъга. Ще оправя кода. - person Dan; 16.02.2012
comment
Мислите ли, че вместо да пишете #text, ако напишете ['binary', 'number','variable'] в списък с нежелани етикети и след това го анализирате, ще бъде ли по-добре? - person user916315; 16.02.2012
comment
Мисля, че възелът #text е представянето в XML DOM на необработения текст между отворените и затварящите тагове -- в този случай той винаги е празен, но трябва да преминем през него, когато преминаваме през всички възли на дадено ниво. Програмата никога не среща променлива/число/ляво и т.н., защото те са на различни нива на влагане от елементите, които разглеждаме -- така че няма да има значение. Разбира се, ако искате да разширите този анализатор, за да прави по-интересни неща, ще трябва да ги вземете предвид. - person Dan; 17.02.2012

Ще трябва да внедрите виртуална машина, за да изпълните този код. За щастие всичко, с което трябва да се справите в този код, е операторите за присвояване, if и while. Вие също ще имате нужда от вашата виртуална машина, за да поддържате регистър с имена на променливи, което може да се направи просто с Python dict. Започнете от първия оператор, заданията просто се изпълняват и продължете към следващия. операторите if трябва да оценят условието и след това да се разклонят съответно. while е подобно на if, но ще ви е необходим стек от адреси, за да излезете, когато условието оцени false (използвайте стек за обработка на вложени while). И следете номерата на етикетите, докато вървите.

person PaulMcG    schedule 16.02.2012
comment
Здравейте, направих малка мръсна програма, която генерира кода за програми без if/while. from xml.dom import minidom dom = minidom.parse('test1.wl.xml') assignments = dom.getElementsByTagName('assignment') numbers = [statement.getAttribute('label') for statement in assignments] #this works for програми без while/if за x, y в zip(numbers[:-1], numbers[1:]): print %s --› %s %(x, y) # намиране на родители за възел в заданията: print node .parentNode.nodeName, node.getAttribute('label') Можете ли да ми покажете малък фрагмент, който обработва условията while? - person user916315; 16.02.2012
comment
Също така не мога да разбера необходимостта от диктовка. Не е нужно да проверявам за променливи. - person user916315; 16.02.2012
comment
Ако възнамерявате да картографирате контролния поток, тогава ще трябва да оцените условията на if, за да видите кой път да поемете. За да оцените условията if, ще трябва да знаете стойностите на променливите, за да можете да тествате условието за вярно/невярно. Освен ако не съм разбрал погрешно това, което наричате контролен поток. - person PaulMcG; 16.02.2012
comment
Ще трябва да навигирате в dom, като използвате извикванията от типа 'getChildNode', 'getSiblingNode' - 'getElementsByTagName' няма да запази структурата на влагане на операторите if и while. - person PaulMcG; 16.02.2012
comment
Предполагам, че намерението е да се създаде CFG чрез прост статичен анализ, вместо да се изпълнява кодът или да се използва абстрактна интерпретация, разбира се? - person snim2; 16.02.2012
comment
@Пол: Да. Прав си! Можете ли да ми покажете малък кодов фрагмент, който ще направи същото? snim2: Да, и ти си прав! - person user916315; 16.02.2012
comment
@snim2: О, разбира се! статичен анализ, трябват нови очила. Така че не, OP няма да се нуждае от dict за проследяване на стойности на променливи. - person PaulMcG; 16.02.2012
comment
хаха! Кара ме да се чувствам като дребно джудже сред група питони нинджи! - person user916315; 16.02.2012

Не е необходимо да внедрявате пълна виртуална машина, за да извършвате анализ на контролен поток. Трябва да преминете през AST. Наистина книгата, спомената от сайта, към който сте направили връзка (Принципи на програмния анализ) обяснява как да направите това. Помислете как всеки вид изявление на вашия език има входни и изходни точки. На процедурен език оператор if/else if/else може да бъде въведен от оператора, който го предхожда и може да има няколко изходни точки (по един за всеки клон на if/else if/else). Оператор while има входна точка от оператора, който го предхожда, и две изходни точки - един изход, който заобикаля до върха на while и един, който излиза от while и се придвижва към следващия оператор. Операторите за присвояване имат един вход от оператора, предхождащ присвояването, и една изходна точка, която води до следващите оператори.

Това е насочената графика, която трябва да начертаете. И така, искате да повторите XML, който имате там, "излъчващи" възли във вашия CFG. Най-лесният начин да направите това, IMO, е да използвате модел за посетители, тъй като пишете на OO език.

Например тази програма:

begin
    x := 10          // 1
    if x < 100 {     // 2
        print "foo"  // 3
    }
    else {
        print "bar"  // 4
    }
end

ще има следния CFG:

CFG

person snim2    schedule 16.02.2012
comment
Благодаря ти за обяснението snim2! - person user916315; 16.02.2012
comment
Моля. Между другото, ако това е въпрос за домашна работа, моля, маркирайте го като такъв. - person snim2; 16.02.2012
comment
Да и не. Одитирам този курс! Просто го приех като упражнение, но ужасно се заби. Липсата на предишен опит с този вид анализ на потока вероятно е причината. - person user916315; 16.02.2012
comment
А, концепциите са най-важното нещо, изпълнението трябва да е ясно, след като разберете всичко ;) - person snim2; 16.02.2012