Как создать поток управления 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.

Примечание: while может иметь вложенные операторы 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. Начните с первого оператора, присваивания просто выполняются и переходят к следующему. операторы 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') для оператора в назначениях] #это работает для программы без 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
Я также не могу понять необходимость dict. Мне не нужно проверять переменные. - 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. Действительно, книга, упомянутая на сайте, на который вы ссылаетесь (Principles of Program Analysis) объясняет, как это сделать. Подумайте о том, как у каждого типа утверждений в вашем языке есть точки входа и выхода. В процедурном языке оператор if/else if/else может быть введен из оператора, предшествующего ему, и может иметь несколько точек выхода (по одной для каждой ветви if/else if/else). Оператор while имеет точку входа из оператора, предшествующего ему, и две точки выхода: один выход зацикливается на вершину while, а другой выходит из while и переходит к следующему оператору. Операторы присваивания имеют одну запись из оператора, предшествующего присваиванию, и одну точку выхода, которая ведет к последующим операторам.

Это ориентированный граф, который вам нужно нарисовать. Итак, вы хотите перебрать XML, который у вас есть, "испуская" узлы в вашем CFG. Самый простой способ сделать это, IMO, — использовать шаблон посетителя, так как вы пишете в язык ОО.

Например, эта программа:

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