Атрибутные транслирующие грамматики — различия между версиями

Материал из Викиконспекты
Перейти к: навигация, поиск
(Пример работы с атрибутами в нисходящем разборе)
Строка 146: Строка 146:
  
 
Выпишем продукции (с транслирующими символами) и ассоциируем с ними семантические правила
 
Выпишем продукции (с транслирующими символами) и ассоциируем с ними семантические правила
(здесь {ENTRY...} - [[Атрибутные_транслирующие_грамматики#tr_char|транслирующий символ]]. Если в продукции несколько раз встречается одинаковый нетерминал, будем добавлять к нему индексы, считая от начала продукции.):
+
(здесь {ENTRY...} {{---}} [[Атрибутные_транслирующие_грамматики#tr_char|транслирующий символ]]. Если в продукции несколько раз встречается одинаковый нетерминал, будем добавлять к нему индексы, считая от начала продукции.):
  
 
{| style="background-color:#CCC;margin:0.5px"
 
{| style="background-color:#CCC;margin:0.5px"
Строка 276: Строка 276:
  
 
  exprP['''int''' i] '''returns''' ['''int''' val]
 
  exprP['''int''' i] '''returns''' ['''int''' val]
     :                                          { val=i; } // <tex>\varepsilon</tex>-правило
+
     :                                          { val=i; } <font color="green"> // <tex>\varepsilon</tex>-правило</font>
 
     | '+' term e = exprP[i+term.val]        { val=e.val; }
 
     | '+' term e = exprP[i+term.val]        { val=e.val; }
 
     ;
 
     ;

Версия 15:36, 6 июня 2015

Часто, осуществляя разбор, мы хотим извлечь какие-то данные или произвести какие-то действия, а не просто выяснить, разбирается ли текст в данной грамматике. Вообще говоря, сначала можно получить дерево разбора, а потом уже, обходя его, выполнять эти действия. В этом случае происходит дублирование функционала: промежуточное сохранение данных в виде дерева разбора не нужно, а иногда его просто слишком расточительно хранить в памяти целиком. В связи с этим хочется какие-то действия производить уже на этапе разбора.

Например, мы хотим не только построить дерево разбора для арифметических выражений, а ещё и вычислить значение этого выражения. Возможно, даже не строя само дерево разбора.

Такой подход называется синтаксически управляемой трансляцией.

Синтаксически управляемая трансляция

<wikitex>

Определение:
Синтаксически управляемое определение (англ. syntax-directed definition) является контекстно-свободной грамматикой с атрибутами и правилами. Атрибуты связаны с грамматическими символами, а правила — с продукциями.


Определение:
Синтаксически управляемая трансляция (англ. syntax-directed translation) — это трансляция, при которой в процессе разбора строки сразу выполняются какие-то действия, без использования промежуточного представления в виде дерева разбора.


Синтаксически управляемая трансляция вводит две новые сущности: атрибут и транслирующий символ.


Определение:
Атрибут (англ. attribute) — дополнительные данные, ассоциированные с грамматическими символами. Если X представляет собой символ, а a — один из его атрибутов, то значение a в некотором узле дерева разбора, помеченном X, записывается как X.a. Если узлы дерева разбора реализованы в виде записей или объектов, то атрибуты X могут быть реализованы как поля данных в записях, представляющих узлы X. Атрибуты могут быть любого вида: числами, типами, таблицами ссылок или строками.


Определение:
Дерево разбора, в каждом узле которого атрибуты уже вычислены, называется аннотированным (англ. annotated), а процесс вычисления этих атрибутов — аннотированием дерева разбора.


Определение:
Транслирующий символ — нетерминал, который раскрывается в ε и в момент раскрытия выполняет связанное с ним действие. Действия пишутся в фигурных скобках рядом с транслирующим символом.


Будем рассматривать в качестве примера грамматику для арифметических выражений с операторами + и :

SEEE+TTTTFFFn(E)


Стоит отметить, что не существует гарантии наличия даже одного порядка обхода дерева разбора, при котором вычислятся все атрибуты в узлах. Рассмотрим для примера следующие нетерминалы A и B:

Продукция Семантические правила
AB A.s=B.iB.i=A.s+1

Данные правила циклические: невозможно вычислить ни A.s в узле, ни B.i в дочернем узле, не зная значение другого атрибута. Далее будет рассмотрено два класса синтаксически управляемых грамматик, для которых можно однозначно определить порядок вычисления атрибутов. </wikitex>

Синтезируемые атрибуты

<wikitex>

Определение:
Атрибут, значение которого зависит от значений атрибутов детей данного узла или от других атрибутов этого узла, то атрибут называется синтезируемым (англ. synthesized attribute).


Определение:
Грамматика называется S-атрибутной (англ. S-attributed definition), если с атрибутами выполняются только операции присваивания значений других атрибутов, а внутри транслирующих символов происходят обращения только к атрибутам этого транслирующего символа. То есть в грамматике используются только синтезируемые атрибуты. Дерево разбора для такой грамматике всегда может быть аннотировано путем выполнения семантических правил снизу вверх, от листьев к корню.

</wikitex>

Пример S-атрибутной грамматики

<wikitex> Выпишем синтаксически управляемое определение для грамматики арифметических выражений с операторами + и (здесь $\{ADD Шаблон:... \}и\{MUL Шаблон:... \}$ — транслирующие символы. Если в продукции несколько раз встречается одинаковый нетерминал, будем добавлять к нему индексы, считая от начала продукции.):

Продукция Семантические правила Пояснения
SE S.val=E.val
E0E1+T {ADD res=op1+op2} ADD.op1=E1.valADD.op2=T.valE0.val=ADD.res В фигурных скобках — действия транслирующего символа ADD. op1, op2 и res — атрибуты транслирующего символа.
ET E.val=T.val
T0T1F {MUL res=op1×op2} MUL.op1=T.valMUL.op2=F.valT0.val=MUL.res В фигурных скобках — действия транслирующего символа MUL. op1, op2 и res — атрибуты транслирующего символа.
TF T.val=F.val
Fn F.val=n.val
F(E) F.val=E.val

В нашем примере видно, что val зависит только от детей в дереве разбора, то есть это синтезируемый атрибут. Результат умножителя (MUL.res) зависит только от атрибутов атрибутов самого умножителя (MUL.op1 и MUL.op2), а значит тоже является синтезируемым(аналогично с сумматором ADD).

Аннотированное дерево разбора для 35+4

После такого разбора в S.val будет лежать вычисленное значение выражения. Можно, например сразу напечатать его, добавив к нему правило {print(S.val)}.

</wikitex>

Наследуемые атрибуты

<wikitex>

Определение:
Атрибут, значение которого зависит от значений атрибутов братьев узла или атрибутов родителя, называется наследуемым (англ. inherited attribute).


Определение:
Грамматика называется L-атрибутной (англ. L-attributed definition), если значения наследуемых атрибутов зависят только от родителей и братьев слева (то есть не зависят от значений атрибутов братьев справа).

</wikitex>

Пример L-атрибутной грамматики

<wikitex> Для наглядности рассмотрим грамматику объявления переменных (в начале строки идет тип, затем через запятую имена переменных. Примеры строк, разбираемых в ней: int a или real x,y,z и подобные):

DTLTintrealLL,idid


Выпишем продукции (с транслирующими символами) и ассоциируем с ними семантические правила (здесь $\{ENTRY Шаблон:... \}$ — транслирующий символ. Если в продукции несколько раз встречается одинаковый нетерминал, будем добавлять к нему индексы, считая от начала продукции.):

Продукция Семантические правила
DTL L.inh=T.type
Tint T.type=integer
Treal T.type=real
L0L1,id {ENTRYaddtype(key,value)} L1.inh=L0.inhENTRY.key=id.textENTRY.value=L0.inh
Lid {ENTRYaddtype(key,value)} ENTRY.key=id.textENTRY.value=L.inh

Семантическое правило L.inh=T.type, связанное с продукцией DTL, определяет наследуемый атрибут L.inh как тип объявления. Затем приведенные правила распространяют этот тип вниз по дереву разбора с использованием атрибута L.inh. Транслирующий символ ENTRY, связанный с продукциями для L, вызывает процедуру addtype для добавления типа каждого идентификатора к его записи в таблице символов (по ключу, определяемому атрибутом text).

Аннотированное дерево разбора для real id1, id2, id3

</wikitex>

Пример работы с атрибутами в нисходящем разборе

<wikitex> Рассмотрим работы с атрибутами на примере LL(1)-грамматики арифметических выражений, которая уже была разобрана ранее и расширим код разборщика для нее:

ETEE+TEεTFTTFTεFn(E)

В данной реализации рекурсивные функции от нетерминалов получают на вход (если необходимо) наследуемые атрибуты узла и возвращают вершины дерева разбора, в атрибутах которых записан результат вычислений соответствующего подвыражения. Как мы видим, val - синтезируемый атрибут, acc - наследуемый атрибут, ADD - транслирующий символ. Синим подсвечены строки, отвечающие за работу с атрибутами.

Здесь Node — структура следующего вида:

struct Node
    children : map<String, Node>
    name: string
    val : int                  // атрибут нетерминала
    function addChild(Node)    // функция, подвешивающая поддерево к данному узлу


E() : Node
    Node res = Node("E")
    switch (curToken)
        case n, '('  :
            res.addChild(T())            // подвешиваем левого сына
            temp = res.children["T"].val // атрибут левого сына
            Node rightSon = E'(temp)     // отдадим атрибут левого сына правому как наследуемый атрибут
            res.addChild(rightSon)       // подвешиваем правого сына сына
            res.val = res.children["E'"].val
            break
        default :
            error("unexpected char")
    return res


E'(acc) : Node
    Node res = Node("E'")
    switch (curToken) 
        case '+' :
            consume('+')
            res.addChild(Node("+"))
            res.addChild(T())
            temp = res.children["T"].val
            ADD.res = ADD(acc, temp)  // ADD проведет вычисления из наследуемого атрибута add и атрибута ребенка "T"
            res.addChild(E'(ADD.res)) // результат вычислений будет передан правому ребенку как наследуемый атрибут
            res.val = res.children["E'"].val
            break
        case '$', ')' :
            res.val = acc
            break
        default :
            error("unexpected char")
     return res
F() : Node
    Node res = Node("F")
    switch (curToken)
        case n :
            consume(n)
            res.addChild(Node(curToken)) 
            res.val = n.val
            break
        case '(' :
            consume('(')
            res.addChild(Node("("))
            res.addChild(E())
            rev.val = res.children["E"].val
            consume(')')
            res.addChild(Node(")"))
        default :
            error("unexpected char")
    return res

Функции для T и T строятся аналогично.

Дерево разбора для 2 + 3 + 7

</wikitex>

Атрибуты в ANTLR

Общедоступный генератора разборщиков ANTLR[1] поддерживает синтаксически управляемое определение.

Рассмотрим для той же грамматики арифметических выражений с операторами +, скобками и выводом результата выражениая пример на ANTLR.

grammar Expression;
@header { package ru.ifmo.ctddev.wiki; } 

Естественным образом можно добавлять действия в продукции, где это нужно. Действия выполняются после предыдущего элемента грамматики и до следующего.

Стартовый нетерминал печатает резульат:

s : expr { System.out.println($e.value);};

Разобранные нетерминалы возвращают результат, вычисленный в поддереве(returns [int val]) как свой синтезируемый атрибут, процесс вычисления которого описан в фигурных скобках { $val = $exprP.val; }.

Наследуемые атрибуты передаются нетерминалу как параметр(exprP[$term.val]).

expr returns [int val]
    : term exprP[term.val]     {val = $exprP.val; }
    ;
exprP[int i] returns [int val]
    :                                           { val=i; }  // ε-правило
    | '+' term e = exprP[i+term.val]        { val=e.val; }
    ;
term returns [int val]
    : fact termP[fact.val]     {val = $termpP.val; }
    ;
termP[int i] returns [int val]
    :                                           { val=i; }
    | '*' fact e = termP[ifact.val]        { val=e.val; }
    ;
fact returns [int val]
    : '(' expr ')'                  { val=expr.val; }
    | NUM                           { val=Integer.parseInt(NUM.text); }
    ;

Техническая деталь для ANTLR, правила для лексического анализатора:

WS : [ \t \r \n]+ -> skip ;
NUM : [0-9]+ ;


В продукции для нетерминала e определяется возвращаемое значение ([Integer value]). Обращение к этому атрибуту имеет вид $e.value. В фигурных скобках записаны семантические правила.

Примечания

Источники информации

  • Альфред Ахо, Рави Сети, Джеффри Ульман. Компиляторы. Принципы, технологии, инструменты. Издательство Вильямс. Первое издание. 2003. Стр. 279 — 305.
  • Альфред Ахо, Рави Сети, Джеффри Ульман. Компиляторы. Принципы, технологии, инструменты. Издательство Вильямс. Второе издание. 2008. Стр. 383 — 398.
  • ANTLR Documentation — Rule Attribute Definitions
  • The Definitive ANTLR 4 Reference